better-dsh 0.2.5-d → 0.2.5-e

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,6 +1,6 @@
1
- # better-dsh 0.2.6 发布验收报告(2026-10-04,rig 4998)
1
+ # better-dsh 0.2.5-d 发布验收报告(2026-10-04,rig 4998)
2
2
 
3
- - **被测物**: `better-dsh@0.2.6`(commit `4981254`,`feat(ast): surface silent failure modes as diagnostic fields`)
3
+ - **被测物**: `better-dsh@0.2.5-d`(commit `4981254`,`feat(ast): surface silent failure modes as diagnostic fields`)
4
4
  - **环境**: 交付形态 tarball 安装位 rig,端口 **4998**,`DSH_HOME=.test/home/compat`,profile `web`;驱动 = 第一人称真实 session(HTTP RPC 两段形,与《2026-10-03-ast-engine-deomp-4999实测报告》§四/§五 同款 password-gate + `/api` 信封)
5
5
  - **发布状态**: **未发布**。`npm publish` 需 user 单独明确放行(AGENTS §〇 红线);本报告 §五 给出 pre-flight checklist 与 `--dry-run` 证据,发布动作不在本任务内。
6
6
 
@@ -8,17 +8,17 @@
8
8
 
9
9
  ## 0. 一句话
10
10
 
11
- 0.2.6 在 4998 rig 以交付形态(tarball remove→add)完成第一人称 live 验收:诊断契约三字段 `pathNotFound` / `overlapping` / `patternErrors` 全部在真实 session 里以原样 JSON 落实,`.vendor` 全程不重建、日志零 OMP/registry 行;一处处方偏差(任务给的"旧静默 pattern"实为单根 pattern,不触发 `patternErrors`)已用真 multi-root pattern 补测闭环,并给出引擎级根因。
11
+ 0.2.5-d 在 4998 rig 以交付形态(tarball remove→add)完成第一人称 live 验收:诊断契约三字段 `pathNotFound` / `overlapping` / `patternErrors` 全部在真实 session 里以原样 JSON 落实,`.vendor` 全程不重建、日志零 OMP/registry 行;一处处方偏差(任务给的"旧静默 pattern"实为单根 pattern,不触发 `patternErrors`)已用真 multi-root pattern 补测闭环,并给出引擎级根因。
12
12
 
13
- ## 1. 0.2.6 内容物
13
+ ## 1. 0.2.5-d 内容物
14
14
 
15
- ### 1.1 引擎替换回顾(0.2.5 系已完成,0.2.6 继承)
15
+ ### 1.1 引擎替换回顾(0.2.5 系已完成,0.2.5-d 继承)
16
16
 
17
17
  - 原生 addon(`@oh-my-pi` / pi_natives,静默供给)→ **包内 WASM ast-grep 引擎**:`lib/ast-assets/` 16 文件(ast-grep.wasm + tree-sitter.wasm + 14 语言 grammar wasm),冷启动零外部供给——`.vendor` 目录全程不出现,rig 日志 `omp|oh-my-pi|registry` **0 命中**(本轮全文件 grep,40 行)。
18
18
  - 交付物延续 docs-packaging-trim 形态:tarball 5.1 MB / **733 文件**(shasum `5b3e3e35e25aaa34ef160035e027767975912217`)。
19
19
  - 引擎行为面已被 0.2.5 系列锁定:对齐 pattern 34/8/11(4999 首测 §4.4 / 4998 复测 §4.8.3 逐字段一致)、dryRun 默认拦截、真写盘-回滚闭环、14 语言 match matrix(`a2d32a5`)。
20
20
 
21
- ### 1.2 诊断契约增量(0.2.6 本体 = commit `4981254`)
21
+ ### 1.2 诊断契约增量(0.2.5-d 本体 = commit `4981254`)
22
22
 
23
23
  对原生前辈三类静默失败(契约报告 A1/A3/A6)的**有意分歧**,契约见 `docs/20_specs/ast/spec.md` §41:
24
24
 
@@ -32,9 +32,9 @@
32
32
 
33
33
  ## 2. 安装与站点证据
34
34
 
35
- - 构建:`npm run build`(tsdown + build-client + kernel-bridge + ast-assets,输出 `ast-assets: 16 files, 14.4 MB`)→ `npm pack` → `better-dsh-0.2.6.tgz`。
36
- - 安装(file: tarball 不刷新,**remove→add 一个来回**):`plugin --profile web remove better-dsh` → `add …/better-dsh-0.2.6.tgz` → `Packages: +1`(pnpm 11.7.0)→ `PORT=4998 bash .test/seed/test123/start.sh`(unit `dsh-4998-test123` + `test123-lan-4998-relay`)。
37
- - 安装位核验:`package.json` version → **`"0.2.6"`**;`lib/ast-assets` → **16 文件**;`.vendor` → **ABSENT**(`ls` exit 2)。profile `package.json` 依赖行 `file:/home/u1/workspaces/dashr/better-dsh/better-dsh-0.2.6.tgz`。
35
+ - 构建:`npm run build`(tsdown + build-client + kernel-bridge + ast-assets,输出 `ast-assets: 16 files, 14.4 MB`)→ `npm pack` → `better-dsh-0.2.5-d.tgz`。
36
+ - 安装(file: tarball 不刷新,**remove→add 一个来回**):`plugin --profile web remove better-dsh` → `add …/better-dsh-0.2.5-d.tgz` → `Packages: +1`(pnpm 11.7.0)→ `PORT=4998 bash .test/seed/test123/start.sh`(unit `dsh-4998-test123` + `test123-lan-4998-relay`)。
37
+ - 安装位核验:`package.json` version → **`"0.2.5-d"`**;`lib/ast-assets` → **16 文件**;`.vendor` → **ABSENT**(`ls` exit 2)。profile `package.json` 依赖行 `file:/home/u1/workspaces/dashr/better-dsh/better-dsh-0.2.5-d.tgz`。
38
38
  - 站点:password gate `POST / -d password=admin` → **303** + `dsh-auth-<hash>` cookie(HttpOnly);boot graph 含 `"id":"better-dsh"`;`/plugins/??better-dsh/client.js&rev=4b9802368848` → **200**(29,700 B,= 安装位 `lib/client/index.js` 29,633 B + 服务端追加 sourceMappingURL 行,`cmp` 前缀逐字节一致)。
39
39
 
40
40
  ## 3. 第一人称 live 实测(真实 session,六项)
@@ -59,7 +59,7 @@
59
59
  }
60
60
  ```
61
61
 
62
- **无 `patternErrors`,且这是 0.2.6 的正确行为**:该 pattern 在 WASM ast-grep 里编译为**单根**(`export function $NAME($$$ARGS)` 解析为一个合法但永不匹配的声明节点),不抛 `Multiple AST nodes are detected`,因此不属于 patternErrors 契约面。独立对拍:本地以 0.2.6 源码(vitest 直驱 `dispatchDvcWrite`,同 pattern 同形 fixture)返回**逐字段一致**的静默形态——rig 与源码零漂移。旧契约报告 §7 把此 pattern 的 0.2.5-c 静默归因为 MULTI_NODE 吞噬,系排除法推断,对 WASM 引擎不成立(真正的静默机制是该 pattern 单根但结构不全,永不匹配)。**规避口径不变:pattern 必须写完整节点**。
62
+ **无 `patternErrors`,且这是 0.2.5-d 的正确行为**:该 pattern 在 WASM ast-grep 里编译为**单根**(`export function $NAME($$$ARGS)` 解析为一个合法但永不匹配的声明节点),不抛 `Multiple AST nodes are detected`,因此不属于 patternErrors 契约面。独立对拍:本地以 0.2.5-d 源码(vitest 直驱 `dispatchDvcWrite`,同 pattern 同形 fixture)返回**逐字段一致**的静默形态——rig 与源码零漂移。旧契约报告 §7 把此 pattern 的 0.2.5-c 静默归因为 MULTI_NODE 吞噬,系排除法推断,对 WASM 引擎不成立(真正的静默机制是该 pattern 单根但结构不全,永不匹配)。**规避口径不变:pattern 必须写完整节点**。
63
63
 
64
64
  ### 3.3 (a2) 补充:真 multi-root pattern → `patternErrors` live(A1 分歧验收)
65
65
 
@@ -176,15 +176,15 @@ export function overlapTarget(x: number): number { /*op1-applied*/ return x * 2
176
176
  按 AGENTS §〇:a(本报告第一人称实测)✅ → b(报告落盘)✅ → c(**user 明确放行,待办**)→ d(publish)。当前到 b 为止。
177
177
 
178
178
  - [x] **registry 目标**: `npm config get registry` → `https://registry.npmjs.org/`;whoami → `pgmi-builds`
179
- - [x] **版本**: `package.json` `name: better-dsh`, `version: 0.2.6`(与安装位一致)
179
+ - [x] **版本**: `package.json` `name: better-dsh`, `version: 0.2.5-d`(与安装位一致)
180
180
  - [x] **files 列表**: `lib`、`THIRD_PARTY_NOTICES.md`、`docs`、`dsh-docs`、`cordis.patch.yml`、`eval-description.md`、`url-schemes-instruction.md`、`scripts/kernel-provision.mjs`
181
181
  - [x] **`npm publish --dry-run`**(未发布,输出尾 8 行原样):
182
182
 
183
183
  ```text
184
184
  npm notice === Tarball Details ===
185
185
  npm notice name: better-dsh
186
- npm notice version: 0.2.6
187
- npm notice filename: better-dsh-0.2.6.tgz
186
+ npm notice version: 0.2.5-d
187
+ npm notice filename: better-dsh-0.2.5-d.tgz
188
188
  npm notice package size: 5.1 MB
189
189
  npm notice unpacked size: 27.0 MB
190
190
  npm notice shasum: 5b3e3e35e25aaa34ef160035e027767975912217
@@ -194,11 +194,11 @@ npm notice Publishing to https://registry.npmjs.org/ with tag latest and default
194
194
  ```
195
195
 
196
196
  - [x] 干跑确认:尾行带 `(dry-run)`,registry 无任何足迹。
197
- - [ ] **user 放行后**:`npm publish`(真实动作)→ tag `v0.2.6`(GitHub 侧可逆,随发布节奏)。
197
+ - [ ] **user 放行后**:`npm publish`(真实动作)→ tag `v0.2.5-d`(GitHub 侧可逆,随发布节奏)。
198
198
 
199
199
  ## 6. 偏差与关注项
200
200
 
201
- 1. **处方 (a) 预期未按字面达成(已闭环)**:任务处方的 `export function $NAME($$$ARGS)` 不触发 `patternErrors`——单根 pattern 永不匹配≠multi-root 编译失败;本地 0.2.6 源码对拍逐字段一致,判定为**处方对旧 §7 根因推断的继承误差**,非产品缺陷。A1 验收由 (a2) 真 multi-root pattern 补齐。建议:后续文档/指引里把"完整节点"口径与"multi-root 才报 patternErrors"口径并列写清(单根残缺 pattern 仍是静默 0 命中,属引擎语义)。
201
+ 1. **处方 (a) 预期未按字面达成(已闭环)**:任务处方的 `export function $NAME($$$ARGS)` 不触发 `patternErrors`——单根 pattern 永不匹配≠multi-root 编译失败;本地 0.2.5-d 源码对拍逐字段一致,判定为**处方对旧 §7 根因推断的继承误差**,非产品缺陷。A1 验收由 (a2) 真 multi-root pattern 补齐。建议:后续文档/指引里把"完整节点"口径与"multi-root 才报 patternErrors"口径并列写清(单根残缺 pattern 仍是静默 0 命中,属引擎语义)。
202
202
  2. **rig LLM 路由欠费**:三条既有路由 402 QUOTA;本轮以 `zai-plan`(Anthropic 端点)provider 行修复并留作 rig 默认(`agent-default-model` 现为 `zai-plan/glm-4.7`,原 `zai/glm-5.3-flash` 已欠费不可用)。rig home patch 为测试资产,该行建议保留至 paas 路由充值。
203
203
  3. **`filesSearched` 双形态口径**(旧 A4,未变):目录遍历跳过非代码文件、显式单文件计入——§3.2(11)与 §3.6(1)的数字口径不同属设计内。
204
204
  4. 首个 QUOTA 失败 turn 的会话(`ac7f2999`/`2ca4edea`)留在 rig home sessions 内,随 home 复用自然沉淀。
@@ -209,8 +209,12 @@ npm notice Publishing to https://registry.npmjs.org/ with tag latest and default
209
209
  cd ~/workspaces/dashr/better-dsh && npm run build && npm pack
210
210
  cd ~/workspaces/dashr
211
211
  DSH_HOME=$PWD/.test/home/compat node upstream/deepseek-harness/apps/cli/lib/bin.js plugin --profile web remove better-dsh
212
- DSH_HOME=$PWD/.test/home/compat node upstream/deepseek-harness/apps/cli/lib/bin.js plugin --profile web add $PWD/better-dsh/better-dsh-0.2.6.tgz
212
+ DSH_HOME=$PWD/.test/home/compat node upstream/deepseek-harness/apps/cli/lib/bin.js plugin --profile web add $PWD/better-dsh/better-dsh-0.2.5-d.tgz
213
213
  PORT=4998 bash .test/seed/test123/start.sh
214
214
  # 认证 + RPC 两段形:POST /(password gate 铸 cookie)→ /api/session/create → /api/session/selectModel(zai-plan/glm-4.7)→ /api/session/prompt(queue)
215
215
  # 会话日志:.test/home/compat/sessions/--home-u1-workspaces-dashr--/session-f4122ee1-…/session.v4.jsonl.zstd(seq 21/28/35/42/49/56/74)
216
216
  ```
217
+
218
+ ## 0.2.5-d 重打包说明(无重验)
219
+
220
+ 版本串按 user 指令从 0.2.6 改为 0.2.5-d。**未重跑 rig 验证**:改动仅为 `package.json` 的 `version` 字段,不触及任何已验证面。等价性证据:重打包后 tgz 内 `lib/` 65 个文件与 0.2.6 验证轮的构建产物 sha256 逐文件一致(清单比对),即发布的就是验证过的那个产物。
@@ -0,0 +1,137 @@
1
+ # 开发环境拓扑与历史记录(根 AGENTS.md 迁出)
2
+
3
+ 本文件按需阅读,不是自动加载的行为总纲。以下保留 2026-10-06 迁出时的原文,包含已退役路径、旧版本验证与待勘误事项,不能当作当前操作手册照抄。
4
+
5
+ 现行通用开发与测试规范以 [Cordis-dsh-dev-test-guides.md](../Cordis-dsh-dev-test-guides.md) 为准;rig、启动和测试资产以 [.test/README.md](../.test/README.md) 及对应 seed 文档为准。遇到冲突先核实当前脚本和上游官方入口,不将历史 workaround 当作必需机制。
6
+
7
+ ## 二、Dev/Test 1:源码级 4999 实例(upstream checkout + 内嵌 dashr)— 推荐回归路径
8
+
9
+ 整个 harness 从源码跑,dashr 作为 workspace 成员内嵌其中,与 prod 完全隔离。**2026-09-02 已全链路验证。**
10
+ 插件开发与测试环境的现行规范见仓库根 `Cordis-dsh-dev-test-guides.md`(两层文档制的第二层,2026-09-27 起;冲突以其为准);本节记录本仓拓扑事实,rig 清单(含 bun distro 产物 rig `bun-test`)见 `.test/README.md`。
11
+
12
+ ### 组成(2026-09-24 重构:干净 harness + plugin-add 交付形态;旧 monorepo 内嵌流已退役)
13
+
14
+ - Harness: `./upstream/deepseek-harness`,git tag **`dsh-v0.1.7-rc.1`**(2026-09-24 全新 clone 重建;本地 patch:unrun devDep / root vite devDep(rc.1 新增,apps/desktop tsdown config 运行时 import vite,unrun 缓存目录解析不到)/ storeDir+verifyDeps+zeromq+ssh2 / tsdown `resolveRepositoryRoot` / vite preact 三件套,重放流程与坑见 `docs/50_test-reports/2026-09-24-v0.1.7-rc.1对齐轮-compaction与failover设置面修复实测报告.md` §四)。**upstream 内不允许出现 better-dsh 的任何内容**(旧 `packages/better-dsh/` 内嵌副本 + devDeps 手术 + sync 脚本已于本轮废除)。
15
+ - better-dsh **独立开发**:devDependencies 不含任何 `@deepseek-ai/*`(范围符号在 peerDependencies = 发布契约);`node_modules/@deepseek-ai/*` 由 `scripts/link-upstream.mjs` 生成 symlink 农场指向 upstream 物理包——upstream 换 tag 后 farm 自动跟随(目录级 symlink),package.json 零改动。`.npmrc` `legacy-peer-deps=true` 防 npm 自动装 optional peers(tuple 规则会解析到三代前的 0.1.5-rc.3)。开发循环:`npm run build` → `tsc --noEmit`(0 错基线)→ `npx vitest run`(600/601)。
16
+ - 测试 rig **`.test/seed/test123/`**(现行,README 有完整种子再生步骤):home 在 `.test/home/`(2026-09-27 裁决:**home 复用不换场**——`compat/` = 默认长寿命 home,user data 与 `.credentials.yaml` 是测试资产跨重置保留;`RIG_HOME=clean` 仅在专门验证首次行为时临时建一个、用完即删;gitignored);~~`profiles/node_modules/@deepseek-ai/*` symlink 农场~~ **已于 2026-09-26 删除**(user 指令;0.1.7 安装域拦截供给全部 harness 面——无农场 boot 实证:shell 200 + boot graph 含 better-dsh + client.js 200 + zoom-guard/`__DASHR_MOBILE__` host 半注入在位、日志零解析错误;rig 与 prod 解析模型现完全同构);profile `web` 的 better-dsh 是 **`dsh plugin add <tarball>` 物理安装**(入场一律走 plugin add;`npm pack` 出交付物 → add → 重启;remove→add 一个来回才能刷新 file: tarball 内容)。旧 `.tests/dsh-test1/` 与 `.dsh-test/` 已删除(2026-09-24 清理,`.test/` 布局落地)。**0.1.7 settings 模型**:namespace = Loader entry id(如 `dashr-failover`),`installSection` 已删;volatile 字段 + `settings.configure({auto:false})` + configEditor 落 profile patch 用户层(upstream `agent-default-model` 范式)。
17
+
18
+ ### harness 本地 patch(该环境必须,缺一 build 即挂)
19
+
20
+ 1. `packages/client/tsdown.client.ts`: `REPOSITORY_ROOT` 由 `resolveRepositoryRoot()` 推导(`pnpm-workspace.yaml` 锚定,`process.cwd()` 兜底)。原因: 本机 Node 22.22.1 `process.features.typescript=false` → tsdown auto loader 选 unrun,unrun 的 bundle 级 define 把内联 preset 的 `import.meta.url` 改写成各包入口 config 的 URL,`packages/*/*` 深度下 `../..` 落到 `<repo>/packages/`,manifest glob 全空 → `no packages/*/*/package.json declares the name …`。upstream CI 的新 Node 走 native loader,看不到此问题。
21
+ 2. `pnpm-workspace.yaml`: `storeDir: /home/u1/workspaces/dashr/.scratch/pnpm-store` + `verifyDepsBeforeRun: false` + allowBuilds `zeromq: true`(better-dsh 自 0.2.2-a 起零 lifecycle script,无自身 allowBuilds 条目)。原因: pnpm 11 不读 `.npmrc`;默认 deps-check 会 spawn `pnpm install`,向只读的用户级 store 注册 project → EROFS;strictDepBuilds 下未列 build script = install 硬错。
22
+ (**另记 2026-09-13**:`pnpm add` 之后 `npx vite` 会从根解析到 vite 8/rolldown——web 构建一律走 `pnpm run build:web`(workspace 内 vite 6.4.3),勿用 npx 直呼。)
23
+ 3. **(2026-09-13,change `2026-09-13-preact-ui-shell`)** `apps/web/vite.config.ts`: `resolve.alias` 增 react 家族五条 → `preact/compat`(regex 锚定)+ `resolve.dedupe` 增 `'preact'`(dedupe 条目从本包 node_modules 解析——缺它则 `packages/client/web/lib` 的 seed 解析不到 apps/web 里的 preact);`apps/web` devDeps 加 `preact@10.29.8` 精确 pin。效果:shell 平台种子物化 preact 实例,全部 ui-* 插件 bundle 的 `require("react")` 零改动获得 Preact;shell index chunk 555,959 → 434,173 B(−21.9%),A/B 实测挂载面一致、console 零错。**同一 patch 顺带根除了 `vendor/CLAUDE.md` ENOTDIR/exit 236 老坑**:`vendor/` 根下的三个文档文件(AGENTS/CLAUDE/README.md)撞 `vendor/*` workspace glob——**0.1.6 起 `vendor/docs/` 也不行**(tsdown root tsdown.config 的 `vendor/*` workspace glob 会把无 package.json 的目录当幻影包,整 build 死,见对齐轮 §七),三个文件现放 **`.scratch/vendor-docs/`**(vendor 根下不允许存在任何子目录缺 package.json)。
24
+ ### 构建(setup 或 harness 变更后)
25
+
26
+ ```bash
27
+ cd ~/workspaces/dashr/upstream/deepseek-harness
28
+ pnpm install # store 已重定向到 .scratch/pnpm-store
29
+ pnpm run build # tsc lib/types + tsdown host/client + vite web + client build record(0.1.5-rc.2: 234 client artifacts,含 build:native-system)
30
+ ```
31
+
32
+ ### 启动 / 重启
33
+
34
+ ```bash
35
+ # 2026-09-24 起:测试实例一律用 `bash .test/seed/test123/start.sh`(PORT=xxxx 可覆盖,默认 4999;
36
+ # LAN 中继默认开——socat 只绑 LAN IP 转发 loopback;webserver 只收 127.0.0.1|0.0.0.0 字面量
37
+ # 且 startup 硬拒 0.0.0.0,直接绑 LAN IP 不可能)。脚本自带:停旧+等端口真释放、
38
+ # 测试口无主化接管(2026-09-27 裁决:占口者属测试 unit 即停掉接管,非测试监听才拒绝)、
39
+ # seed 感知输出(seed 持久时明说 cookie 仍有效)、本 boot token 轮询提取(append 日志防串台)。
40
+ # 脚本内部即下面的 canonical 命令(unit dsh-4999-test123 + test123-lan-4999-relay):
41
+ systemctl --user stop dsh-4999-test123 test123-lan-4999-relay 2>/dev/null
42
+ systemd-run --user --unit=dsh-4999-test123 \
43
+ -p WorkingDirectory=/home/u1/workspaces/dashr/upstream/deepseek-harness \
44
+ -p Environment=DSH_HOME=/home/u1/workspaces/dashr/.test/home/compat \
45
+ -p 'Environment="DSH_TRUSTED_HOSTS=test.pc.randomhash.app pc.randomhash.app 192.168.31.130"' \
46
+ -p 'UnsetEnvironment=DISPLAY WAYLAND_DISPLAY' \
47
+ -p StandardOutput=append:/home/u1/workspaces/dashr/.scratch/dsh-4999-test123.log \
48
+ -p StandardError=append:/home/u1/workspaces/dashr/.scratch/dsh-4999-test123.log \
49
+ "$(which node)" apps/cli/lib/bin.js web --no-open --port 4999
50
+ # ⚠⚠ 2026-09-22 双平面修复起:boot 必须走**构建产物** `apps/cli/lib/bin.js` + 裸 node,
51
+ # 勿再用 `node --import tsx/esm apps/cli/src/bin.ts`(tsx 源码启动)!
52
+ # 根因(dual-plane symbol split):tsx 源码启动下 TS 文件经 tsconfig.base.json `paths`
53
+ # 落 src/,而 plugin loader 把组合行解析到构建 lib 的 file URL → 同进程两份
54
+ # dsh-tools/cordis 模块实例 → `Symbol('@deepseek-ai/dsh-tools.scheduler')` 键在
55
+ # lib 侧 ToolRuntime 与 src 侧 agent-loop 之间错位 → 每个 session 的**第一个工具调用**
56
+ # 必崩 `Cannot read properties of undefined (reading 'prepare')`(UNKNOWN),孤儿
57
+ # tool/call 事件毒化 session log → 后续每轮 DeepSeek INVALID_REQUEST "tool calls
58
+ # need immediate results"。const enum(如 cordis `FiberState`)使 paths-less 收敛
59
+ # 不可行——唯一正解 = 产物面启动(与 prod 同构)。lib/bin.js 前置条件:改动 src 后先
60
+ # `pnpm run build` 全量。A/B + 真 session 证据见
61
+ # `docs/50_test-reports/2026-09-22-4988双平面启动修复实测报告.md`。
62
+ # ⚠ 与 prod 对齐的两行必须带:DSH_TRUSTED_HOSTS(fence + web-trust authorities 单源,
63
+ # 覆盖 test.pc… / pc.randomhash.app / LAN)与 UnsetEnvironment=DISPLAY WAYLAND_DISPLAY
64
+ # (prod dsh.service 同款——否则 directory-picker auto 在图形 env 下选 native,zenity
65
+ # 弹在宿主桌面,经 Caddy 远端访问时 Add workspace 表现为无响应后置灰;2026-09-08 实证)
66
+ # user 终端(非沙箱)等价简式:
67
+ # cd ~/workspaces/dashr/upstream/deepseek-harness && DSH_HOME=~/workspaces/dashr/.test/home/compat node apps/cli/lib/bin.js web --no-open --port 4999
68
+ # ⚠ 旧简式 `npm run dsh -- web …`(内部 tsx 源码启动)自 2026-09-22 起禁止用于回归验收——同上双平面必崩
69
+ # (`--dump-config` 这类 config 面检查不受影响,仍可用 tsx 形态)。
70
+ ```
71
+
72
+ > **勿从 agent 沙箱化 bash 直接拉 daemon(2026-09-03 实证)**:沙箱内启动的 daemon 继承嵌套沙箱环境,其 bwrap 功能探测(`sandbox-local defaultProbeBwrap`)报 `No permissions to create a new namespace` → `SANDBOX_UNAVAILABLE`(agent bash 无沙箱后端)。systemd-run --user 在沙箱外启动(对齐 prod 形态);沙箱内连 user bus 会被拒,需单命令 `danger-full-access` 升级。stop/日志:`systemctl --user ... dsh-4999-test` / `.scratch/dsh-4999.log`。
73
+
74
+ - token(`?token=`)是 per-boot 进程内随机 launch token,只负责首铸 cookie;**认证 seed 持久在 `$DSH_HOME/.credentials.yaml`**——home 不换则 seed 不变,**浏览器 cookie 30 天内跨重启直接可用,重启 rig 不需要换 token / 重注 cookie**(2026-09-27 裁决;机制见 guide §3.3)。从 `.scratch/dsh-4999-test123.log`(或 start.sh 输出)取 token URL;curl 冒烟需 cookie jar: `curl -c jar -L '<token-url>'`(303 重定向靠 cookie 保认证)。
75
+ - 数据只落 `.test/home/compat/`(sessions/storages),与 prod 隔离;重置只动 `profiles/web`,user data 保留。
76
+
77
+ ### 日常回归循环(recurring,2026-09-24 起:交付形态)
78
+
79
+ canonical 构建 → 验收走 tarball;快速迭代走 profile 内物理 lib 替换(**只碰 profile,永不碰 upstream**):
80
+
81
+ ```bash
82
+ cd ~/workspaces/dashr/better-dsh && npm run build # tsdown + build-client
83
+ # 快路径(client 半边改动):直接覆盖 profile 里的物理安装,刷新页面即生效
84
+ rsync -a --delete lib/ ~/workspaces/dashr/.test/home/compat/profiles/web/node_modules/better-dsh/lib/
85
+ # host 半边改动:同上覆盖后重启(start.sh)
86
+ # 阶段性验收(发布前必走):npm pack → remove → add → 重启
87
+ cd ~/workspaces/dashr && DSH_HOME=$PWD/.test/home/compat node upstream/deepseek-harness/apps/cli/lib/bin.js \
88
+ plugin --profile web remove better-dsh
89
+ DSH_HOME=$PWD/.test/home/compat node upstream/deepseek-harness/apps/cli/lib/bin.js \
90
+ plugin --profile web add $PWD/better-dsh/better-dsh-<version>.tgz
91
+ bash .test/seed/test123/start.sh
92
+ ```
93
+
94
+ - **⚠ file: tarball 内容更新 pnpm 不自动刷新**(同版本同路径 → `added 0`):改完包必须 remove → add 一个来回,别信 add 幂等。
95
+ - **⚠ lib/client 清洗陷阱仍在**:tsdown 清空 lib/ 会抹掉 lib/client/,`npm run build`(= tsdown && build-client)已串好两步,勿单跑 tsdown。
96
+ - 验证:boot graph 含 `"id":"better-dsh"`;`/plugins/??better-dsh/client.js&rev=…` 200;Settings/General 两行在(compaction stepper + failover selects)。
97
+ - `--dump-config`:`DSH_HOME=… node upstream/deepseek-harness/apps/cli/lib/bin.js web --dump-config | grep -A2 dashr-repl`。
98
+
99
+ ### 已验证 / 未验证
100
+
101
+ - ✅ `dashr-repl` 工具行 + `DASHR_KERNEL_PYTHON` config 注入;web shell + assets HTTP 200;`.dsh-test/storages/workspace.json` 落盘。
102
+ - ✅ **kernel 三级供给**(2026-09-03,change `kernel-provisioning-completeness`):postinstall/spin-up/首用 lazy 全 fail-open;冷启动 spin-up 自动重建 venv(pinned ipykernel 7.3.0 / dill 0.4.1 / CPython 3.11,无 uv 时 python3-venv 兜底 3.14.4 实测兼容);uv cache 重定向包内 `.uv-cache`,只读 HOME 实测无碍;一页文档 `docs/kernel-provisioning.md`。
103
+ - ✅ **client 卡片半边已通**(2026-09-03,change `model-failover-settings-surface`;2026-09-03 npm 改名后 id 为 `better-dsh`):副本内构建用 **`tsx scripts/build-client.ts` 直跑**(33ms;⚠ `npm run build-client` 在 monorepo 副本必死——npm 自身 pre-script 的 workspace 枚举读 pnpm-workspace `vendor/*` glob 匹配到普通文件 CLAUDE.md → ENOTDIR/exit 236,与脚本无关);验证法:鉴权拉 shell 页 → boot graph 应含 `"id":"better-dsh"` 与 `/plugins/??better-dsh/client.js&rev=…` URL → curl 该 URL 200 且与 `lib/client/index.js` 字节一致(服务端仅追加 sourceMappingURL 行)。原生注册路径已核实:settings 行 = client bundle 内 `ctx.slots.inject('settings.general.item', …)`(与 locale/ui-chat 原生行同构)。
104
+ - ✅ **web-trust 双腿 + mobile-layout 随插件发布**(2026-09-03,change `plugin-shipped-ui-patches`,v0.2.1-f;**round-4 手势整体重写为 z_dsh-alpha `1706b81` 逐条照搬**,commit `0676811`):**fence 腿** = bundle patch 整行重述 `connection` 行(`trustedHosts: !!js (process.env.DSH_TRUSTED_HOSTS ?? '').split(/\s+/).filter(Boolean).concat(ctx.webRuntime.trustedHosts)`;⚠ `!!js` 是 scalar tag,表达式**不能以 `[` 开头**否则 yaml 按 flow-seq 拒收);**isLoopback 腿** = host 半 `webserver/index-inject` 内联 boot script(trusted hostname → `window.__DSH_TRANSPORT__={ownsHost:true}`;mobile 阈值 → `__DASHR_MOBILE__`——client 半无 config,页面全局即配置通道);**mobile 手势 = 旧 ui-layout 补丁 exact port**:X120 左缘带 / 右 3/4 起点区(viewport/4)/ 距离 40px / 水平占优 ×1.3 / 断点 <768 / **pointermove 中途一次性触发**(pointerup 判定被浏览器滚动手势 pointercancel 吃掉 = "必须快划"的根因)/ document 捕获监听 / 右面板 = Better Sidebar 浮层,经 `[data-dsh-toggle-cluster]` 末按钮 DOM click、状态读 `body[data-dsh-sidebar-collapsed]`、会话门槛读 `[data-slot="conversation.session.header"]`;**唯一增量** = 速率门 `swipeVelocityPxPerMs` 默认 0.15(0=关闭)。round-2 发明的 48/28 值已废弃。425/425 + tsc 0;4999 经用户真实 URL `https://test.pc.randomhash.app` CDP 实证(开/关左栏 + 开/关右栏 + Models provider 列表全过;该域名已入 trustedPageAuthorities)。报告 `docs/50_test-reports/v0.2.1f-plugin-shipped-ui-patches实测报告.md`。
105
+
106
+ - ✅ **iOS focus 放大抑制 zoomGuard**(2026-09-03,change `ios-focus-zoom-suppression`,v0.2.4,commit `59870fc`;纯 host 半,client 零改动):boot script 增 zoomGuard 段 —— iOS 系 UA(iPhone|iPod|iPad + iPadOS 桌面冒充 Macintosh&&maxTouchPoints>1)∧ 窄视口(`matchMedia('(max-width:'+(breakpoint-0.02)+'px)')`,断点复用 mobile.breakpoint)双门控下 token 级合并 viewport meta `maximum-scale=1, user-scalable=no`(幂等、可还原、resize + MQ change 双通道复评 + **早期载入重评梯子** ~10ms×200tick —— 引擎载入期应用 viewport 不派发 resize 的实测缺口,deferred focus 可先于事件补评);**mobile 配置面新增 `zoomGuard`**:`'meta'`(默认,**v0.2.5 起自动双形态**:browser=meta 改写 / standalone=字号地板)| `'off'`(逃生门,'off' = 整段不发射),`'font'` 值位预留不进 enum(未实现值 fail-loud)。五个纯函数(`src/mobile/zoom-guard.ts`,ES5 自包含)经 `Function.prototype.toString` 嵌入 boot script = 单测源即发布源。**关键机制发现**:head 注入 splice 在 `<head>` 开标签紧后 = stock viewport meta 后于脚本解析 → provisional meta + MutationObserver reconcile(stock 插入时改写其本体并移除 provisional,单 meta 文档,S7 查表第 8 条盯两查点)。**v0.2.5 display-mode 分流**(2026-09-03,change `zoomguard-standalone-font-floor`,tag `v0.2.5`;真机实证:standalone PWA 态引擎尊重 `user-scalable=no` → pinch 失效):iOS 门后、meta 机器前一次性判定 standalone(`(display-mode: standalone)` MQ ∨ `navigator.standalone===true`,严格 `=== true` 双源 OR)→ 注入 `<style id="ios-zoom-font-floor" data-plugin="better-dsh" data-plugin-css="better-dsh/zoom-font-floor">`(16px 字号地板,`@media (max-width:{bp-0.02}px)` 与 meta 腿同派生 768→767.98;**注入不设宽度门**,宽度由 CSS media query 表达)后 return —— 零 meta 机器/零监听/零 timer/零窄带 MQ 探针,meta 字节不动;browser 态 v0.2.4 机器原样,'off' 两形态都不发射(web-trust 门不变零改动)。478/478 + tsc 0。466/466 + tsc 0(v0.2.4);**4999 已验**(CDP 矩阵 9/9 + off 逃生门 live + 桌面 user 复验,报告 §八;v0.2.5 双形态 CDP 待 lead,报告 §九);真机 PWA 复验待 user(报告 `docs/50_test-reports/v0.2.4-ios-focus-zoom-suppression实测报告.md`,键盘遮蔽自愈判定驱动 follow-up 取舍)。CLI 注:`web` 子命令不认 `--patch`,config 覆盖走 home 层 `cordis.patch.yml`。**发布定格(2026-09-03 user 裁决)**:本地过程版本 0.2.4/0.2.5 的 tag(v0.2.3~v0.2.5)已清,npm/GitHub 发布为 **`0.2.2-b` / tag `v0.2.2b`**(0.2.2 系列顺延;真机全过后 user 以普通用户身份从 npm 装 prod 实测)。
107
+
108
+ - ✅ **hashline content-locator**(2026-09-08,change `2026-09-08-v0-2-3b-hashline-content-locator`,v0.2.3-b):served 账本去 session 化(schema v7,**path 主键**,`E_RANGE_UNVERIFIED` 跨会话类消灭)、集中存储 **`$DSH_HOME/storages/dsh-better-edit/`**(`configDir()` 无参化,终结 12 个 workspace 点目录——"到处拉屎"裁决落地)、verifyServedRange 内容快道(账本双界未见→内容放行;STALE/UNSERVED 保留;retryHint 改 read-once-then-resubmit)、write-hook 删除(write 仅上游确认信封,无锚点回放)。单测 `test/hashline-store.spec.ts` 12/12;全量 491/2(2 挂经 stash 基线证实先于本改动);4999 第一人称实测通过(write 无回放 / 中心库无 session_id 列 / 无点目录 / read-then-edit 落盘)。实测定性:宿主 `dsh-fs-observation-policy` 的 `E_NOT_OBSERVED`(read-before-edit)为跨会话 edit 的**另一道设计内守卫,保留**。报告 `docs/50_test-reports/v0.2.3b-hashline-content-locator实测报告.md`。**新坑**:副本构建必须 `pnpm --filter better-dsh exec tsdown`——裸跑 `node_modules/.bin/tsdown` 产出 `.mjs` 形态,boot 即 `ERR_MODULE_NOT_FOUND lib/index.js`。已发布 **v0.2.3-b**(npm registry 2026-09-08 18:15 UTC+8,dist-tag `latest`)并部署 prod(18:19 装机、18:44 重启上线,中央 v7 库活体——本 3080 实例即运行其上)。
109
+
110
+ - ✅ **mobile wave:官方寻址 + 状态驱动响应式**(2026-09-11,对齐 0.1.5-rc.2;**已发布 `0.2.3-c` / tag `v0.2.3c`**,npm dist-tag latest,含随批的 0.1.5 对齐移植;prod 3080 未部署——user 未指令,待 user 以普通用户身份自装实测):①**手势状态输入只认官方面**(红线,起因:旧实现把 readRightOpen 挂在第三方 Better Sidebar 的 body 属性上,插件移除后恒 true 毒死整个状态机——左滑右滑全灭);左栏 = `ctx.layout.toggleSidebar()` + AppFrame `[data-sidebar-collapsed]`,右栏 = 官方控件 `[data-sidebar-right-expand]`(会话头角落,开)/`[data-sidebar-right-toggle]`(dock chrome,关;store 动作 setExpanded/toggleExpanded 为 slot-store 内部,跨插件硬边界 → 原生按钮即正规入口)+ 状态读双 facet:`[data-rightbar-fullscreen]` 在场即开(手机全屏右栏不占轨道、`data-rightbar-collapsed` 同场在场——单 facet 读法致右滑误开左栏,user 真机诊断后修复)。②**响应式零自设像素阈值**:CSS 键纯 `[data-sidebar-collapsed]`(无 @media)——上游 auto-collapse(今日 1024)为真收 56px rail 时我们压成 0px,上游改阈值自动跟随;手势 band = 粗指针(`pointer: coarse`)∨ 任一面板处于窄态,细指针全静息(桌面文本拖选永不误触)。③**`mobile.breakpoint` 配置删除**(schema+boot payload+client),zoomGuard 自带内部 768 兜底不受影响;所谓"mobile 配置卡"从未存在过(client 半唯一设置行是 failover)——MOBILE_CONFIG 一直是纯配置文件面。425→492 tests + tsc 0(monorepo 0.1.5-rc.2 手术环境);4988 已部署活体 + user iPhone 真机确认("没有问题,是成功的");0.1.3-alpha.2 prod host 兼容矩阵源码核验通过(stat/open、sendMessage、ptc-dispatch-log 均在;`tool/ptc-dispatch` 事件 vocabulary-growth 容忍 = 降级不破坏,host 升 0.1.5 自愈),报告 `docs/50_test-reports/v0.2.3c-mobile-wave实测报告.md`。
111
+
112
+
113
+ - ✅ **REPL 指引单源化:control prompt 并入 eval description**(2026-09-11,change `2026-09-11-control-prompt-into-eval-description`,**已发布 `0.2.3-d` / tag `v0.2.3d`**):`dashr:control-prompt` + `dashr:tool-catalog` 两 system-prompt section 整体删除,全部 REPL 指引改由 **`eval` 的 wire description** 承载(内容 = 包根 `dashr/eval-description.md`,`package.json files` 同步——坑:漏列即模块期 readFileSync ENOENT、插件加载即死,实测已修);`src/py-sdk.ts` 渲染器整体拆除只留 `isFlatBindableName`;桥接口径 = 统一调用形一句话(`await tool.<name>(args)` 单位置参数对象,`true/false/null`→`True/False/None`),**对返回值形态零陈述**(user 裁决:明写"不保证/试错"抑制使用);示例键 `path`(D1 修正——注意 vendored `normalizeRequest` 的 `file_path→path` 别名仍在,旧键侥幸可用,见报告 §5.1)。提示面 A/B:两 section **−8,759 chars**(本 change 净 −52.6%,残余 +1,944 为 v0.2.3-b hashline 指引)。实测报告 `docs/50_test-reports/2026-09-11-control-prompt-into-eval-description实测报告.md`;遗留:非 flat 名无真实 MCP 端到端样本(本 runtime 无 MCP 工具)、prod 3080 未部署(待 user 以普通 user 自装实测)。
114
+
115
+ ---
116
+
117
+ ## 三、Dev/Test 2:wire the prod core(prod 核 + dev 插件)— 未实操,细节待勘误
118
+
119
+ prod npm CLI 当 harness 核,只把插件本体和它的 harness 依赖换成 dev 版。
120
+
121
+ - 核: `~/.local` npm 全局 `@deepseek-ai/dsh` 0.1.7-rc.1(2026-09-23/24 user 升级);profile `web` 在 `~/.dsh/profiles/web`(pnpm hoisted 物理 tree)。
122
+ - 插件线: `./better-dsh` `npm run build`(= `tsdown && npm run build-client`)→ md5 核对同步 `lib/` 到 `~/.dsh/profiles/web/node_modules/better-dsh/lib`(v0.2.1c 的部署方式)→ 重启 `dsh.service`。
123
+ - harness 依赖线: **已退役**(原 `better-dsh/node_modules/@deepseek-ai/*` symlink 到 dsh-alpha 源码的方案,2026-09-02 已删——实测 host ②③ 层全量自供,嵌套 symlink 无一必需,且 alpha.1 指向对 alpha.3 host 是版本偏斜负债)。
124
+ - 解析即第一节 ①→④ 分层;现已验证插件运行期只依赖 ②③(host 自供)+ ① 的 schemastery/cosmokit。
125
+ - ~~剩余待验证~~ **已收敛(2026-09-02)**: `./better-dsh` 构建产物 md5 同步线的 prod 冒烟已跑(v0.2.1c 同步态活体 4 探针 4/4);profile 锁/lockfile/部署位已统一为 `0.2.1-d`(经 `dsh plugin add @pgmi-builds/better-dsh@0.2.1-d`,见第一节供应链年龄门)。**发布态部署的正道是 pnpm add 精确版本**,手工 md5 同步仅限未发布的本地迭代。
126
+
127
+ ---
128
+
129
+ ## ✅ prod 插件嵌套 symlink 悬空 — 已清理(2026-09-02),重启安全
130
+
131
+ `dsh-alpha` 改名 `z_dsh-alpha` 后,`~/.dsh/profiles/web/node_modules/better-dsh/node_modules/@deepseek-ai/*` 的 17 条 symlink 曾全部悬空。**实测: daemon 重启不会崩** —— 运行期实际 import 的 14 个 `@deepseek-ai/*` 包全部从 ②③ 层解析(host 自带);`dsh-client-ui-slots` 不是运行期 import(只在 `scripts/build-client.ts` 的 client external 列表,浏览器侧经 shell module table / `.dsh-module-fallback` 解析)。
132
+
133
+ **为什么声明 58 个 peerDeps**: 全是 dev-time 需要 —— `tsc --noEmit` 类型检查、`tsdown` dts、以及 vitest 单测(在 dsh host 之外直接 import harness 包;dev workspace 靠 pnpm `autoInstallPeers` 装上)。部署副本里它们是 `peerDependencies` 且 `peerDependenciesMeta` 全部 `optional: true`,pnpm-lock.yaml 对嵌套路径 0 引用 —— pnpm 从不要求它们在场。真正的 `dependencies` 只有 `@deepseek-ai/schemastery`(非 harness API,是插件自用的 Schema 描述符库;描述符是结构化数据、宿主 cordis 当数据解释,故可以自带副本,与 cordis 必须 peer 的身份要求相反)+ cosmokit(schemastery 的传递依赖)。
134
+
135
+ **已执行**: 删除 17 条悬空 symlink,保留 `schemastery`+`cosmokit`;清理后 14/14 运行期 import 解析复测通过。曾考虑的 A(重指 z_dsh-alpha,alpha.1 与 alpha.3 host 版本偏斜、「两份 cordis」身份风险)与 B(重指 upstream checkout)均已否决。另: ~~profile package.json 锁 `0.2.1-a` 而部署实为 `0.2.1-c`~~ 漂移已于 2026-09-02 收敛——锁/lockfile/部署位统一 `0.2.1-d`(pnpm add 精确版本线)。
136
+
137
+ ---
@@ -0,0 +1,51 @@
1
+ # Authentication-only Web admission
2
+
3
+ ## Goal
4
+
5
+ An operator can reach their authenticated DSH deployment through any domain or
6
+ LAN address without maintaining separate server and browser address allowlists.
7
+ The server keeps its current bind address; the existing rig's LAN relay supplies
8
+ LAN reachability. This feature does not change listening interfaces.
9
+
10
+ ## Configuration
11
+
12
+ Opt in through the existing `dashr-web-trust` row:
13
+
14
+ ```yaml
15
+ - id: dashr-web-trust
16
+ name: better-dsh/web-trust
17
+ config:
18
+ trustAllHosts: true
19
+ ```
20
+
21
+ The default is `false`, retaining existing declared-authority behavior. When
22
+ enabled, `--trusted-host`, `DSH_TRUSTED_HOSTS` and `trustedPageAuthorities` are
23
+ not needed for admission or page management privileges.
24
+
25
+ ## Contract
26
+
27
+ - Keep the original Connection instance, transport, RPC registrations and token
28
+ exchange. No upstream source changes, replacement service or private-field
29
+ access are required.
30
+ - Replace only its `requestRejection` method while this row is active. Delegate
31
+ cookie verification to public `authorizeIndex`, using the real request headers
32
+ and a fixed query-free index URL. Discard the index responder's output; the
33
+ transport remains responsible for the actual HTTP response.
34
+ - Any address, Origin and Fetch-Metadata marker can pass after authentication.
35
+ Missing, invalid, expired or wrong-authority cookies still return 401. Cookie
36
+ lifetime and signing remain the native authenticator's responsibility.
37
+ - This API admission delegate never exchanges a URL token: normal index login
38
+ still performs that exchange. Existing password-login behavior is unchanged.
39
+ - Inject `ownsHost: true` before browser application initialization, regardless
40
+ of page hostname, without overwriting an existing transport. Management UI
41
+ consumers continue to use the original Connection's common capability flag.
42
+ - Unloading or disabling this row restores the original admission method;
43
+ reload the page to update its initialization-time capability flag.
44
+
45
+ ## Acceptance
46
+
47
+ Use a packaged plugin installed through `dsh plugin add` in the long-lived 4999
48
+ rig, with no trusted-host CLI argument or environment variable. Verify loopback,
49
+ LAN and the actual HTTPS deployment domain; an additional unlisted Host must
50
+ authenticate successfully without bypassing cookie verification. Check settings
51
+ persistence, provider settings and plugin management in the real browser.
@@ -1,10 +1,10 @@
1
- import { f as UrlSchemesError, t as buildFsLayerResolver } from "../wrap-0VA0uFx8.js";
1
+ import { f as UrlSchemesError, t as buildFsLayerResolver } from "../wrap-kTohEeis.js";
2
2
  import { Context } from "@deepseek-ai/cordis";
3
3
  import { dirname, sep } from "node:path";
4
4
  import { stat } from "node:fs/promises";
5
- import { LocalFileSystem } from "@deepseek-ai/dsh-fs-local";
6
5
  import { FsError } from "@deepseek-ai/dsh-fs";
7
6
  import { writableRoots } from "@deepseek-ai/dsh-sandbox";
7
+ import { LocalFileSystem } from "@deepseek-ai/dsh-fs-local";
8
8
 
9
9
  //#region ../upstream/deepseek-harness/packages/fs/fs-sandbox/lib/index.js
10
10
  /**
@@ -1,5 +1,5 @@
1
1
  import { t as Schema } from "../index-DTDXUe6m.js";
2
- import { r as MobileConfig } from "../web-trust-DBsL1tG4.js";
2
+ import { r as MobileConfig } from "../web-trust-D9jFaQbH.js";
3
3
  import { Context } from "@deepseek-ai/cordis";
4
4
 
5
5
  //#region src/mobile/plugin.d.ts
@@ -1,5 +1,5 @@
1
1
  import { t as Schema } from "../lib-C8_jXvwA.js";
2
- import { r as buildMobileScript } from "../web-trust-B27wcZEw.js";
2
+ import { r as buildMobileScript } from "../web-trust-jR0o70JS.js";
3
3
  import { Context } from "@deepseek-ai/cordis";
4
4
 
5
5
  //#region src/mobile/plugin.ts
@@ -1,6 +1,6 @@
1
1
  import { Context } from "@deepseek-ai/cordis";
2
2
  import { ToolDefinition } from "@deepseek-ai/dsh-tools";
3
- import * as _deepseek_ai_schemastery5 from "@deepseek-ai/schemastery";
3
+ import * as _deepseek_ai_schemastery0 from "@deepseek-ai/schemastery";
4
4
 
5
5
  //#region src/url-schemes/tools/write.d.ts
6
6
 
@@ -50,12 +50,12 @@ declare const inject: string[];
50
50
  * real patch-line config. Schema-level defaults (both ON — the service is
51
51
  * opt-out) survive every overlay layer, per the v0.2.2a ruling.
52
52
  */
53
- declare const Config: _deepseek_ai_schemastery5.default<Schemastery.ObjectS<NoInfer<{
54
- urlSchemes: _deepseek_ai_schemastery5.default<boolean, boolean, "defined">;
55
- hashline: _deepseek_ai_schemastery5.default<boolean, boolean, "defined">;
53
+ declare const Config: _deepseek_ai_schemastery0.default<Schemastery.ObjectS<NoInfer<{
54
+ urlSchemes: _deepseek_ai_schemastery0.default<boolean, boolean, "defined">;
55
+ hashline: _deepseek_ai_schemastery0.default<boolean, boolean, "defined">;
56
56
  }>>, Schemastery.ObjectT<NoInfer<{
57
- urlSchemes: _deepseek_ai_schemastery5.default<boolean, boolean, "defined">;
58
- hashline: _deepseek_ai_schemastery5.default<boolean, boolean, "defined">;
57
+ urlSchemes: _deepseek_ai_schemastery0.default<boolean, boolean, "defined">;
58
+ hashline: _deepseek_ai_schemastery0.default<boolean, boolean, "defined">;
59
59
  }>>, "plain">;
60
60
  /** Register the four URL-aware tools on one agent's own scope layer. */
61
61
  /**
@@ -75,12 +75,12 @@ declare function apply(ctx: Context, config: UrlSchemesConfig | undefined): void
75
75
  declare const _default: {
76
76
  name: string;
77
77
  inject: string[];
78
- Config: _deepseek_ai_schemastery5.default<Schemastery.ObjectS<NoInfer<{
79
- urlSchemes: _deepseek_ai_schemastery5.default<boolean, boolean, "defined">;
80
- hashline: _deepseek_ai_schemastery5.default<boolean, boolean, "defined">;
78
+ Config: _deepseek_ai_schemastery0.default<Schemastery.ObjectS<NoInfer<{
79
+ urlSchemes: _deepseek_ai_schemastery0.default<boolean, boolean, "defined">;
80
+ hashline: _deepseek_ai_schemastery0.default<boolean, boolean, "defined">;
81
81
  }>>, Schemastery.ObjectT<NoInfer<{
82
- urlSchemes: _deepseek_ai_schemastery5.default<boolean, boolean, "defined">;
83
- hashline: _deepseek_ai_schemastery5.default<boolean, boolean, "defined">;
82
+ urlSchemes: _deepseek_ai_schemastery0.default<boolean, boolean, "defined">;
83
+ hashline: _deepseek_ai_schemastery0.default<boolean, boolean, "defined">;
84
84
  }>>, "plain">;
85
85
  apply: typeof apply;
86
86
  };
@@ -2,7 +2,7 @@ import { r as __toESM, t as __commonJS } from "../chunk-DQxbwPpZ.js";
2
2
  import { t as Schema } from "../lib-C8_jXvwA.js";
3
3
  import { a as PACKAGE_ROOT } from "../kernel-env-BLPZE-I3.js";
4
4
  import { n as captureNativeTools } from "../native-capture-BEByDXYM.js";
5
- import { a as createHttpHandler, c as listDvcDevices, d as UrlResolver, f as UrlSchemesError, h as SCHEME_NAMES, i as HTTP_SCHEMES, l as registerDvcDevice, m as parseUrl, n as wrapFsWithSchemes, o as createDvcHandler, p as applySelector, r as resolveDocsDir, s as dispatchDvcWrite, u as createDshHandler } from "../wrap-0VA0uFx8.js";
5
+ import { a as createDshHandler, c as createDvcHandler, d as registerDvcDevice, f as UrlSchemesError, h as SCHEME_NAMES, i as createHttpHandler, l as dispatchDvcWrite, m as parseUrl, n as wrapFsWithSchemes, o as UrlResolver, p as applySelector, r as HTTP_SCHEMES, s as resolveDocsDir, u as listDvcDevices } from "../wrap-kTohEeis.js";
6
6
  import { n as Parser } from "../web-tree-sitter-CPrPD4J2.js";
7
7
  import { a as primaryServerForFile, c as serversForFile, n as findWorkspaceRoot, o as resolveCommandPath, r as installHintFor, s as serverByName } from "../lsp-server-registry-D4VLs1Sm.js";
8
8
  import { createRequire } from "node:module";
@@ -16,13 +16,13 @@ import { fileURLToPath, pathToFileURL } from "node:url";
16
16
  import { defineTool } from "@deepseek-ai/dsh-tools";
17
17
  import * as fsPromises from "node:fs/promises";
18
18
  import { mkdir, mkdtemp, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
19
- import { FsError } from "@deepseek-ai/dsh-fs";
20
- import { ESCALATION_TARGETS, approveEscalation, escalationHintMarker, sandboxDenialMarker, validateEscalationArgs } from "@deepseek-ai/dsh-sandbox";
21
19
  import { gunzipSync } from "node:zlib";
22
20
  import { SessionId } from "@deepseek-ai/dsh-session";
23
21
  import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
24
22
  import { DatabaseSync } from "node:sqlite";
25
23
  import { AsyncLocalStorage } from "node:async_hooks";
24
+ import { FsError } from "@deepseek-ai/dsh-fs";
25
+ import { ESCALATION_TARGETS, approveEscalation, escalationHintMarker, sandboxDenialMarker, validateEscalationArgs } from "@deepseek-ai/dsh-sandbox";
26
26
 
27
27
  //#region src/devices/ast/engine/grammar-manifest.mjs
28
28
  /**
@@ -1,7 +1,7 @@
1
1
  import { Context } from "@deepseek-ai/cordis";
2
2
  import { CredentialRecord } from "@deepseek-ai/dsh-credentials";
3
3
  import { IncomingMessage } from "node:http";
4
- import * as _deepseek_ai_schemastery0 from "@deepseek-ai/schemastery";
4
+ import * as _deepseek_ai_schemastery19 from "@deepseek-ai/schemastery";
5
5
 
6
6
  //#region src/web-password.d.ts
7
7
 
@@ -123,10 +123,10 @@ interface WebPasswordConfig {
123
123
  /** Absolute browser-session lifetime in days. Default: 30 (the native default). */
124
124
  cookieMaxAgeDays?: number;
125
125
  }
126
- declare const Config: _deepseek_ai_schemastery0.default<Schemastery.ObjectS<NoInfer<{
127
- cookieMaxAgeDays: _deepseek_ai_schemastery0.default<number, number, "defined">;
126
+ declare const Config: _deepseek_ai_schemastery19.default<Schemastery.ObjectS<NoInfer<{
127
+ cookieMaxAgeDays: _deepseek_ai_schemastery19.default<number, number, "defined">;
128
128
  }>>, Schemastery.ObjectT<NoInfer<{
129
- cookieMaxAgeDays: _deepseek_ai_schemastery0.default<number, number, "defined">;
129
+ cookieMaxAgeDays: _deepseek_ai_schemastery19.default<number, number, "defined">;
130
130
  }>>, "plain">;
131
131
  /** Cordis plugin name; also the scope of this row's credential record. */
132
132
  declare const name = "dashr-web-password";
@@ -145,10 +145,10 @@ declare function apply(ctx: Context, config?: WebPasswordConfig): void;
145
145
  declare const _default: {
146
146
  name: string;
147
147
  inject: string[];
148
- Config: _deepseek_ai_schemastery0.default<Schemastery.ObjectS<NoInfer<{
149
- cookieMaxAgeDays: _deepseek_ai_schemastery0.default<number, number, "defined">;
148
+ Config: _deepseek_ai_schemastery19.default<Schemastery.ObjectS<NoInfer<{
149
+ cookieMaxAgeDays: _deepseek_ai_schemastery19.default<number, number, "defined">;
150
150
  }>>, Schemastery.ObjectT<NoInfer<{
151
- cookieMaxAgeDays: _deepseek_ai_schemastery0.default<number, number, "defined">;
151
+ cookieMaxAgeDays: _deepseek_ai_schemastery19.default<number, number, "defined">;
152
152
  }>>, "plain">;
153
153
  apply: typeof apply;
154
154
  };
@@ -1,5 +1,5 @@
1
1
  import { Context } from "@deepseek-ai/cordis";
2
- import * as _deepseek_ai_schemastery15 from "@deepseek-ai/schemastery";
2
+ import * as _deepseek_ai_schemastery9 from "@deepseek-ai/schemastery";
3
3
 
4
4
  //#region src/web-trust.d.ts
5
5
 
@@ -50,10 +50,11 @@ interface BootScriptRow {
50
50
  * inert-when-empty).
51
51
  *
52
52
  * @param trustedPageAuthorities - the operator-declared hostnames.
53
+ * @param trustAllHosts - delegate admission to native authentication without an address fence.
53
54
  * @returns the script text, or `undefined` when the list is empty (inject
54
55
  * nothing — the feature is fully inert).
55
56
  */
56
- declare function buildTrustScript(trustedPageAuthorities: readonly string[] | undefined): string | undefined;
57
+ declare function buildTrustScript(trustedPageAuthorities: readonly string[] | undefined, trustAllHosts?: boolean): string | undefined;
57
58
  /**
58
59
  * Build the mobile-leg script. Pure: same config in, same script out — the
59
60
  * unit tests pin the shape (mobile global, tuned thresholds, zoomGuard
@@ -69,14 +70,18 @@ declare function buildMobileScript(mobile: MobileConfig | undefined): string | u
69
70
  * the mobile knobs are the `dashr-mobile` row's own config.
70
71
  */
71
72
  interface TrustConfig {
73
+ /** Accept every serving address after native cookie authentication, including in the browser UI. */
74
+ trustAllHosts?: boolean;
72
75
  /** Hostnames this operator declares their own devices' pages run on. */
73
76
  trustedPageAuthorities?: readonly string[];
74
77
  }
75
78
  /** Row config schema (schema-level default derives from `DSH_TRUSTED_HOSTS`). */
76
- declare const Config: _deepseek_ai_schemastery15.default<Schemastery.ObjectS<NoInfer<{
77
- trustedPageAuthorities: _deepseek_ai_schemastery15.default<string[], string[], "defined">;
79
+ declare const Config: _deepseek_ai_schemastery9.default<Schemastery.ObjectS<NoInfer<{
80
+ trustAllHosts: _deepseek_ai_schemastery9.default<boolean, boolean, "defined">;
81
+ trustedPageAuthorities: _deepseek_ai_schemastery9.default<string[], string[], "defined">;
78
82
  }>>, Schemastery.ObjectT<NoInfer<{
79
- trustedPageAuthorities: _deepseek_ai_schemastery15.default<string[], string[], "defined">;
83
+ trustAllHosts: _deepseek_ai_schemastery9.default<boolean, boolean, "defined">;
84
+ trustedPageAuthorities: _deepseek_ai_schemastery9.default<string[], string[], "defined">;
80
85
  }>>, "plain">;
81
86
  /** Cordis plugin name. */
82
87
  declare const name = "dashr-web-trust";
@@ -95,10 +100,12 @@ declare function apply(ctx: Context, config: TrustConfig | undefined): void;
95
100
  declare const _default: {
96
101
  name: string;
97
102
  inject: string[];
98
- Config: _deepseek_ai_schemastery15.default<Schemastery.ObjectS<NoInfer<{
99
- trustedPageAuthorities: _deepseek_ai_schemastery15.default<string[], string[], "defined">;
103
+ Config: _deepseek_ai_schemastery9.default<Schemastery.ObjectS<NoInfer<{
104
+ trustAllHosts: _deepseek_ai_schemastery9.default<boolean, boolean, "defined">;
105
+ trustedPageAuthorities: _deepseek_ai_schemastery9.default<string[], string[], "defined">;
100
106
  }>>, Schemastery.ObjectT<NoInfer<{
101
- trustedPageAuthorities: _deepseek_ai_schemastery15.default<string[], string[], "defined">;
107
+ trustAllHosts: _deepseek_ai_schemastery9.default<boolean, boolean, "defined">;
108
+ trustedPageAuthorities: _deepseek_ai_schemastery9.default<string[], string[], "defined">;
102
109
  }>>, "plain">;
103
110
  apply: typeof apply;
104
111
  };
@@ -245,6 +245,30 @@ function buildZoomGuardSection() {
245
245
  ].join("");
246
246
  }
247
247
 
248
+ //#endregion
249
+ //#region src/connection-trust.ts
250
+ /**
251
+ * Use the native index authenticator as a cookie-only admission delegate.
252
+ * A fixed, query-free index path prevents token exchange or redirects here;
253
+ * original Host and Cookie headers preserve the native authority binding and
254
+ * lifetime. No upstream private fields or cookie-format copies are needed.
255
+ */
256
+ function installAuthenticationOnlyAdmission(connection) {
257
+ const original = connection.requestRejection;
258
+ const response = {
259
+ writeHead() {},
260
+ end() {}
261
+ };
262
+ connection.requestRejection = (request) => connection.authorizeIndex({
263
+ headers: request.headers,
264
+ method: "GET",
265
+ url: "/index.html"
266
+ }, response) ? void 0 : 401;
267
+ return () => {
268
+ connection.requestRejection = original;
269
+ };
270
+ }
271
+
248
272
  //#endregion
249
273
  //#region src/web-trust.ts
250
274
  /**
@@ -284,10 +308,12 @@ function frame(body) {
284
308
  * inert-when-empty).
285
309
  *
286
310
  * @param trustedPageAuthorities - the operator-declared hostnames.
311
+ * @param trustAllHosts - delegate admission to native authentication without an address fence.
287
312
  * @returns the script text, or `undefined` when the list is empty (inject
288
313
  * nothing — the feature is fully inert).
289
314
  */
290
- function buildTrustScript(trustedPageAuthorities) {
315
+ function buildTrustScript(trustedPageAuthorities, trustAllHosts = false) {
316
+ if (trustAllHosts) return frame(["if(!window.__DSH_TRANSPORT__)window.__DSH_TRANSPORT__={ownsHost:true};"]);
291
317
  const authorities = [...trustedPageAuthorities ?? []];
292
318
  if (authorities.length === 0) return void 0;
293
319
  for (const entry of authorities) assertBareHostname(entry);
@@ -323,7 +349,10 @@ function buildMobileScript(mobile) {
323
349
  return frame(body);
324
350
  }
325
351
  /** Row config schema (schema-level default derives from `DSH_TRUSTED_HOSTS`). */
326
- const Config = Schema.object({ trustedPageAuthorities: Schema.array(String).default(deriveDefaultPageAuthorities(process.env.DSH_TRUSTED_HOSTS)) });
352
+ const Config = Schema.object({
353
+ trustAllHosts: Schema.boolean().default(false),
354
+ trustedPageAuthorities: Schema.array(String).default(deriveDefaultPageAuthorities(process.env.DSH_TRUSTED_HOSTS))
355
+ });
327
356
  /** Cordis plugin name. */
328
357
  const name = "dashr-web-trust";
329
358
  /**
@@ -339,9 +368,12 @@ const inject = [];
339
368
  */
340
369
  function apply(ctx, config) {
341
370
  const authorities = config?.trustedPageAuthorities;
371
+ if (config?.trustAllHosts) ctx.inject(["connection"], (owner) => {
372
+ owner.effect(() => installAuthenticationOnlyAdmission(owner.connection), "dashr-web-trust: authentication-only admission");
373
+ });
342
374
  ctx.inject(["webServer"], () => {
343
375
  ctx.on("webserver/index-inject", (table) => {
344
- const text = buildTrustScript(authorities);
376
+ const text = buildTrustScript(authorities, config?.trustAllHosts);
345
377
  if (text !== void 0) table.push({
346
378
  kind: "script",
347
379
  placement: "head",
@@ -1,2 +1,2 @@
1
- import { a as _default, c as buildTrustScript, d as name, i as TrustConfig, l as deriveDefaultPageAuthorities, n as Config, o as apply, r as MobileConfig, s as buildMobileScript, t as BootScriptRow, u as inject } from "./web-trust-DBsL1tG4.js";
1
+ import { a as _default, c as buildTrustScript, d as name, i as TrustConfig, l as deriveDefaultPageAuthorities, n as Config, o as apply, r as MobileConfig, s as buildMobileScript, t as BootScriptRow, u as inject } from "./web-trust-D9jFaQbH.js";
2
2
  export { BootScriptRow, Config, MobileConfig, TrustConfig, apply, buildMobileScript, buildTrustScript, _default as default, deriveDefaultPageAuthorities, inject, name };
package/lib/web-trust.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import "./lib-C8_jXvwA.js";
2
- import { a as deriveDefaultPageAuthorities, c as web_trust_default, i as buildTrustScript, n as apply, o as inject, r as buildMobileScript, s as name, t as Config } from "./web-trust-B27wcZEw.js";
2
+ import { a as deriveDefaultPageAuthorities, c as web_trust_default, i as buildTrustScript, n as apply, o as inject, r as buildMobileScript, s as name, t as Config } from "./web-trust-jR0o70JS.js";
3
3
 
4
4
  export { Config, apply, buildMobileScript, buildTrustScript, web_trust_default as default, deriveDefaultPageAuthorities, inject, name };
@@ -242,6 +242,120 @@ function stringifyNode(node) {
242
242
  }
243
243
  }
244
244
 
245
+ //#endregion
246
+ //#region src/url-schemes/handlers/dvc.ts
247
+ /** Module-level device registry: insertion-ordered, shared by every handler instance. */
248
+ const devices = /* @__PURE__ */ new Map();
249
+ /** Register (or replace) the device mounted under `name`. */
250
+ function registerDvcDevice(name, device) {
251
+ devices.set(name, device);
252
+ }
253
+ /** Read-only view of the registered devices, in registration order. */
254
+ function listDvcDevices() {
255
+ return devices;
256
+ }
257
+ /**
258
+ * The first path segment is the device name (`dvc://name`, `dvc://name/sub`).
259
+ * A leading `dvc://` is tolerated so callers may pass either the parsed path
260
+ * (the write tool's contract) or the full URL.
261
+ */
262
+ /** Subpath after the device name (`status`, `diagnostics?file=…`), or undefined. */
263
+ function subpathOf(path$1) {
264
+ const trimmed = (path$1.startsWith("dvc://") ? path$1.slice(6) : path$1).replace(/^\/+/, "");
265
+ const slash = trimmed.indexOf("/");
266
+ if (slash === -1) return void 0;
267
+ const sub = trimmed.slice(slash + 1);
268
+ return sub === "" ? void 0 : sub;
269
+ }
270
+ function deviceNameFromPath(path$1) {
271
+ const trimmed = (path$1.startsWith("dvc://") ? path$1.slice(6) : path$1).replace(/^\/+/, "");
272
+ const slash = trimmed.indexOf("/");
273
+ return slash === -1 ? trimmed : trimmed.slice(0, slash);
274
+ }
275
+ /** Uniform `unknown`-error rendering for structured error messages. */
276
+ function messageOf(error) {
277
+ return error instanceof Error ? error.message : String(error);
278
+ }
279
+ /**
280
+ * Build the `dvc://` scheme handler over the module-level device registry.
281
+ * Bare `dvc://` yields the roster (`no devices mounted` while empty); a
282
+ * registered `<device>` yields its summary plus a usage hint; an unregistered
283
+ * name keeps the `unknown device` placeholder text.
284
+ */
285
+ function createDvcHandler(_deps = {}) {
286
+ return {
287
+ selectorAware: true,
288
+ async resolve(_env, path$1, selector) {
289
+ const name = deviceNameFromPath(path$1);
290
+ if (name === "") {
291
+ if (devices.size === 0) return "no devices mounted";
292
+ return applySelector([...devices].map(([n, device$1]) => `${n}\t${device$1.summary}`).join("\n"), selector ?? null);
293
+ }
294
+ const device = devices.get(name);
295
+ if (device === void 0) return `unknown device: ${name}`;
296
+ const subpath = subpathOf(path$1);
297
+ if (subpath !== void 0 && device.read !== void 0) {
298
+ const query = selector?.kind === "query" ? `?${selector.q}` : "";
299
+ let text;
300
+ try {
301
+ text = await device.read(`${subpath}${query}`, _env.agent?.id);
302
+ } catch (error) {
303
+ return `Error: ${error instanceof Error ? error.message : String(error)}`;
304
+ }
305
+ return selector !== void 0 && selector !== null && selector.kind !== "query" ? applySelector(text, selector) : text;
306
+ }
307
+ if (subpath !== void 0) return `unknown read path dvc://${name}/${subpath} (device serves doc only)`;
308
+ return applySelector(`${device.summary}\nusage: write dvc://${name} with a JSON args object to execute this device`, selector ?? null);
309
+ }
310
+ };
311
+ }
312
+ /**
313
+ * Write dispatch for `dvc://` URLs — called by the write tool's URL branch.
314
+ * `path` addresses the device (parsed path or full `dvc://` URL); `content`
315
+ * must be the JSON args payload.
316
+ *
317
+ * Routing and args failures (`DVC_NO_DEVICE`, `DVC_UNKNOWN_DEVICE`,
318
+ * `DVC_BAD_ARGS`) throw synchronously — the placeholder wave's observable
319
+ * contract — while a device-reported failure rejects the returned promise as
320
+ * `DVC_DEVICE_ERROR` carrying the device name.
321
+ */
322
+ function dispatchDvcWrite(path$1, content, session, cwd) {
323
+ if (devices.size === 0) throw new UrlSchemesError("DVC_NO_DEVICE", "dvc:// write dispatch: no devices mounted to route the write to");
324
+ const name = deviceNameFromPath(path$1);
325
+ const device = devices.get(name);
326
+ if (device === void 0) throw new UrlSchemesError("DVC_UNKNOWN_DEVICE", `dvc:// write dispatch: no device named "${name}" (registered: ${[...devices.keys()].sort().join(", ")})`);
327
+ let args;
328
+ try {
329
+ args = JSON.parse(content);
330
+ } catch (error) {
331
+ throw new UrlSchemesError("DVC_BAD_ARGS", `dvc:// write dispatch: device "${name}" requires a JSON args payload (${messageOf(error)})`);
332
+ }
333
+ if (session !== void 0 && args !== null && typeof args === "object" && !Array.isArray(args)) args = { ...args };
334
+ return Promise.resolve().then(() => device.execute(args, {
335
+ session,
336
+ ...cwd === void 0 ? {} : { cwd }
337
+ })).catch((error) => {
338
+ throw new UrlSchemesError("DVC_DEVICE_ERROR", `dvc:// device "${name}" execute failed: ${messageOf(error)}`);
339
+ });
340
+ }
341
+
342
+ //#endregion
343
+ //#region src/url-schemes/docs-dir.ts
344
+ function resolveDocsDir() {
345
+ let dir = dirname(fileURLToPath(import.meta.url));
346
+ for (;;) {
347
+ for (const name of ["dsh-docs", "docs"]) {
348
+ const candidate = join(dir, name);
349
+ try {
350
+ if (statSync(candidate).isDirectory()) return candidate;
351
+ } catch {}
352
+ }
353
+ const parent = dirname(dir);
354
+ if (parent === dir) return void 0;
355
+ dir = parent;
356
+ }
357
+ }
358
+
245
359
  //#endregion
246
360
  //#region src/url-schemes/resolver.ts
247
361
  /** Scheme→handler registry that resolves `scheme://` URLs end-to-end. */
@@ -427,103 +541,6 @@ function createDshHandler(deps) {
427
541
  };
428
542
  }
429
543
 
430
- //#endregion
431
- //#region src/url-schemes/handlers/dvc.ts
432
- /** Module-level device registry: insertion-ordered, shared by every handler instance. */
433
- const devices = /* @__PURE__ */ new Map();
434
- /** Register (or replace) the device mounted under `name`. */
435
- function registerDvcDevice(name, device) {
436
- devices.set(name, device);
437
- }
438
- /** Read-only view of the registered devices, in registration order. */
439
- function listDvcDevices() {
440
- return devices;
441
- }
442
- /**
443
- * The first path segment is the device name (`dvc://name`, `dvc://name/sub`).
444
- * A leading `dvc://` is tolerated so callers may pass either the parsed path
445
- * (the write tool's contract) or the full URL.
446
- */
447
- /** Subpath after the device name (`status`, `diagnostics?file=…`), or undefined. */
448
- function subpathOf(path$1) {
449
- const trimmed = (path$1.startsWith("dvc://") ? path$1.slice(6) : path$1).replace(/^\/+/, "");
450
- const slash = trimmed.indexOf("/");
451
- if (slash === -1) return void 0;
452
- const sub = trimmed.slice(slash + 1);
453
- return sub === "" ? void 0 : sub;
454
- }
455
- function deviceNameFromPath(path$1) {
456
- const trimmed = (path$1.startsWith("dvc://") ? path$1.slice(6) : path$1).replace(/^\/+/, "");
457
- const slash = trimmed.indexOf("/");
458
- return slash === -1 ? trimmed : trimmed.slice(0, slash);
459
- }
460
- /** Uniform `unknown`-error rendering for structured error messages. */
461
- function messageOf(error) {
462
- return error instanceof Error ? error.message : String(error);
463
- }
464
- /**
465
- * Build the `dvc://` scheme handler over the module-level device registry.
466
- * Bare `dvc://` yields the roster (`no devices mounted` while empty); a
467
- * registered `<device>` yields its summary plus a usage hint; an unregistered
468
- * name keeps the `unknown device` placeholder text.
469
- */
470
- function createDvcHandler(_deps = {}) {
471
- return {
472
- selectorAware: true,
473
- async resolve(_env, path$1, selector) {
474
- const name = deviceNameFromPath(path$1);
475
- if (name === "") {
476
- if (devices.size === 0) return "no devices mounted";
477
- return applySelector([...devices].map(([n, device$1]) => `${n}\t${device$1.summary}`).join("\n"), selector ?? null);
478
- }
479
- const device = devices.get(name);
480
- if (device === void 0) return `unknown device: ${name}`;
481
- const subpath = subpathOf(path$1);
482
- if (subpath !== void 0 && device.read !== void 0) {
483
- const query = selector?.kind === "query" ? `?${selector.q}` : "";
484
- let text;
485
- try {
486
- text = await device.read(`${subpath}${query}`, _env.agent?.id);
487
- } catch (error) {
488
- return `Error: ${error instanceof Error ? error.message : String(error)}`;
489
- }
490
- return selector !== void 0 && selector !== null && selector.kind !== "query" ? applySelector(text, selector) : text;
491
- }
492
- if (subpath !== void 0) return `unknown read path dvc://${name}/${subpath} (device serves doc only)`;
493
- return applySelector(`${device.summary}\nusage: write dvc://${name} with a JSON args object to execute this device`, selector ?? null);
494
- }
495
- };
496
- }
497
- /**
498
- * Write dispatch for `dvc://` URLs — called by the write tool's URL branch.
499
- * `path` addresses the device (parsed path or full `dvc://` URL); `content`
500
- * must be the JSON args payload.
501
- *
502
- * Routing and args failures (`DVC_NO_DEVICE`, `DVC_UNKNOWN_DEVICE`,
503
- * `DVC_BAD_ARGS`) throw synchronously — the placeholder wave's observable
504
- * contract — while a device-reported failure rejects the returned promise as
505
- * `DVC_DEVICE_ERROR` carrying the device name.
506
- */
507
- function dispatchDvcWrite(path$1, content, session, cwd) {
508
- if (devices.size === 0) throw new UrlSchemesError("DVC_NO_DEVICE", "dvc:// write dispatch: no devices mounted to route the write to");
509
- const name = deviceNameFromPath(path$1);
510
- const device = devices.get(name);
511
- if (device === void 0) throw new UrlSchemesError("DVC_UNKNOWN_DEVICE", `dvc:// write dispatch: no device named "${name}" (registered: ${[...devices.keys()].sort().join(", ")})`);
512
- let args;
513
- try {
514
- args = JSON.parse(content);
515
- } catch (error) {
516
- throw new UrlSchemesError("DVC_BAD_ARGS", `dvc:// write dispatch: device "${name}" requires a JSON args payload (${messageOf(error)})`);
517
- }
518
- if (session !== void 0 && args !== null && typeof args === "object" && !Array.isArray(args)) args = { ...args };
519
- return Promise.resolve().then(() => device.execute(args, {
520
- session,
521
- ...cwd === void 0 ? {} : { cwd }
522
- })).catch((error) => {
523
- throw new UrlSchemesError("DVC_DEVICE_ERROR", `dvc:// device "${name}" execute failed: ${messageOf(error)}`);
524
- });
525
- }
526
-
527
544
  //#endregion
528
545
  //#region src/url-schemes/handlers/http.ts
529
546
  /** The schemes one handler instance serves; register it under each of these. */
@@ -643,23 +660,6 @@ function createHttpHandler() {
643
660
  } };
644
661
  }
645
662
 
646
- //#endregion
647
- //#region src/url-schemes/docs-dir.ts
648
- function resolveDocsDir() {
649
- let dir = dirname(fileURLToPath(import.meta.url));
650
- for (;;) {
651
- for (const name of ["dsh-docs", "docs"]) {
652
- const candidate = join(dir, name);
653
- try {
654
- if (statSync(candidate).isDirectory()) return candidate;
655
- } catch {}
656
- }
657
- const parent = dirname(dir);
658
- if (parent === dir) return void 0;
659
- dir = parent;
660
- }
661
- }
662
-
663
663
  //#endregion
664
664
  //#region src/fs-aware/wrap.ts
665
665
  const WRAPPED = Symbol("dsh-url-schemes.fs-wrap");
@@ -747,4 +747,4 @@ function wrapFsWithSchemes(fs$1, services) {
747
747
  }
748
748
 
749
749
  //#endregion
750
- export { createHttpHandler as a, listDvcDevices as c, UrlResolver as d, UrlSchemesError as f, SCHEME_NAMES as h, HTTP_SCHEMES as i, registerDvcDevice as l, parseUrl as m, wrapFsWithSchemes as n, createDvcHandler as o, applySelector as p, resolveDocsDir as r, dispatchDvcWrite as s, buildFsLayerResolver as t, createDshHandler as u };
750
+ export { createDshHandler as a, createDvcHandler as c, registerDvcDevice as d, UrlSchemesError as f, SCHEME_NAMES as h, createHttpHandler as i, dispatchDvcWrite as l, parseUrl as m, wrapFsWithSchemes as n, UrlResolver as o, applySelector as p, HTTP_SCHEMES as r, resolveDocsDir as s, buildFsLayerResolver as t, listDvcDevices as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-dsh",
3
- "version": "0.2.5-d",
3
+ "version": "0.2.5-e",
4
4
  "description": "Make your dsh ready for serious coding tasks. (Tools x Schemas)^REPL. `skill://`, `ctx://`, `agent://`, `dvc://`, `dsh://`, IPython REPL, Context as Variables, cross compactions recallable, full context revive. `hash-edit`, `dvc://browser`, subagent as function, workflow as function.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",