From 6586e3e2d32cdc808e6b386e29261e1b932c96cc Mon Sep 17 00:00:00 2001 From: Menghuan1918 Date: Tue, 22 Sep 2026 17:09:08 +0800 Subject: [PATCH] feat: adapt to DSH 0.1.7-alpha.1 and yield the read-only previews MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DSH 0.1.7-alpha.1 is 1299 commits past 0.1.6-alpha.2 and rewrites four contracts this plugin was built on. The peer floor moves to ^0.1.7-alpha.1 (semver's prerelease rule means ^0.1.6-alpha.2 can never match a 0.1.7 prerelease); 0.1.6-alpha.2 and earlier stay on v0.19.1. Released as 0.21.0-alpha.1 (dist-tag alpha). Hard breaks: - dsh-settings was rewritten: register(ns, schema) is gone, a form is addressed by the plugin row's Loader entry id and read from the module's exported Config. The preferences are merged into Config and marked volatile — which requires DSH's own schemastery, because only that build wraps a volatile field in the cosmokit reference the loader's volatile commit walks. With the public package a settings write reports success and revision 1 while the effective value never changes. - ui-primitives renamed its whole icon family (IconXxx14/16 -> IconXxxRegular/Medium): 26 imported symbols, 17 files. The failing mode is render-time, not load-time. Five call sites that relied on the old 14px default now pass size={14} explicitly. - Session format v4 rejects source.kind === 'plugin'; the side-chat boundary injection now writes plugin:dsh-better-sidebar, the same kind DSH's own v3->v4 migration produces, so historical threads survive. Tool results moved to role 'tool' with top-level content/isError; all four parsers accept both shapes. - The client session snapshot dropped subagentsByParent/jobsBySession and the on-demand subagent-catalog observation API. The Tasks page reads projectionsBySession and a new jobs.list route. Yields and convergence: - The read-only previews go to DSH's documentpreview (Office->PDF, a spreadsheet worker, zoom, per-directory refresh); editor.canOpen refuses those 26 extensions. Markdown, HTML (with its sandbox switches) and the editable code buffer stay. - The host browser kind is desktop-profile-only now, so the web e2e lanes expect no browser entry and assert its absence. - Link takeover claims only urlTarget links and passes the rest to the host, fixing a regression from the 0.1.6 round: the fallback pointed at a 'browser' type the plugin no longer registers, so http links in its own markdown did nothing. Also fixed, found along the way: - The file tree refreshes live (new directory watch over the plugin's own socket), since the plugin owns the `files` kind. - ctx.sessions.list has no `current` field at either tag; the three readers were dead. They now use ctx.sidebarRight.mounted. - package.json.files was missing lib/client-locale.js. - Two genuinely dead call sites: sessions.openSubagent/open/ refreshSubagents do not exist on the current baseline, so the topology click, the jump-back and the retry were all no-ops. - The test harness window shim lacked addEventListener, which the 0.1.7 primitives call at module-evaluation time; and the e2e token exchange needs Playwright's request stack, not Node's fetch. --- .github/workflows/ci.yml | 2 +- AGENTS.md | 69 +- README.md | 95 +- README_EN.md | 99 +- docs/external-plugin-guide.md | 138 +- ...2026-09-22-dsh-0.1.7-alpha.1-adaptation.md | 129 ++ dsh.plugin.json | 6 +- package.json | 91 +- pnpm-lock.yaml | 1302 +++++++++++------ pnpm-workspace.yaml | 3 - scripts/e2e-common.sh | 15 +- scripts/install.sh | 2 +- src/client/DiffTab.tsx | 4 +- src/client/EditorHost.tsx | 8 +- src/client/FileTree.tsx | 38 +- src/client/SideCardSection.tsx | 16 +- src/client/SideChatView.tsx | 40 +- src/client/Sidebar.tsx | 17 +- src/client/SubagentView.tsx | 256 ++-- src/client/TabBar.tsx | 6 +- src/client/TextEditor.tsx | 4 +- src/client/TreePanel.tsx | 6 +- src/client/api.ts | 13 +- src/client/builtins/tabs.tsx | 8 +- src/client/builtins/viewers.tsx | 77 +- src/client/changes/DiffPane.tsx | 8 +- src/client/changes/GitLens.tsx | 26 +- src/client/changes/ops.ts | 51 +- src/client/icons.tsx | 20 - src/client/index.tsx | 54 +- src/client/link-intercept.ts | 118 +- src/client/locales-ar.ts | 9 - src/client/locales-de.ts | 9 - src/client/locales-fr.ts | 9 - src/client/locales-hi.ts | 9 - src/client/locales-id.ts | 9 - src/client/locales-it.ts | 9 - src/client/locales-ja.ts | 9 - src/client/locales-ko.ts | 9 - src/client/locales-nl.ts | 9 - src/client/locales-pl.ts | 9 - src/client/locales-pt.ts | 9 - src/client/locales-ru.ts | 9 - src/client/locales-sv.ts | 9 - src/client/locales-th.ts | 9 - src/client/locales-tr.ts | 9 - src/client/locales-vi.ts | 9 - src/client/locales-zh-HK.ts | 9 - src/client/locales-zh-MO.ts | 9 - src/client/locales-zh-TW.ts | 9 - src/client/locales.ts | 18 - src/client/md-toc.tsx | 4 +- src/client/mermaid.tsx | 4 +- src/client/native/index.ts | 51 +- src/client/native/surface.ts | 109 +- src/client/open-with-settings.tsx | 4 +- src/client/prefs.ts | 9 - src/client/service.ts | 17 +- src/client/sidebar.module.css | 16 - src/client/sidebar/use-host-feeds.ts | 69 +- src/client/sidechat-transcript.ts | 46 +- src/client/subagent-catalog.ts | 104 ++ src/client/subagent-detect.ts | 30 +- src/client/subagent-jobs.ts | 36 +- src/client/use-dir-watch.ts | 140 ++ src/config.ts | 53 +- src/context-types.ts | 167 ++- src/fs-watch.ts | 113 ++ src/index.ts | 346 ++++- src/jobs-routes.ts | 109 +- src/prefs-shared.ts | 27 - src/sidechat-core.ts | 58 +- src/sidechat-routes.ts | 26 +- tests/browser-globals.ts | 7 + tests/builtins.spec.ts | 93 +- tests/changes-ops.spec.ts | 45 +- tests/e2e/host.ts | 48 +- tests/e2e/mount.e2e.ts | 27 +- tests/e2e/perf.e2e.ts | 15 +- tests/fs-watch.spec.ts | 127 ++ tests/jobs-routes.spec.ts | 96 +- tests/link-intercept.spec.ts | 222 ++- tests/prefs.spec.ts | 25 - tests/primitives-exports.spec.ts | 82 ++ tests/sidebar-auto-activation.spec.tsx | 179 ++- tests/sidebar-crash.spec.tsx | 12 +- tests/sidechat-core.spec.ts | 85 +- tests/sidechat-routes.spec.ts | 13 +- tests/sidechat-seed-validation.spec.ts | 72 +- tests/sidechat-transcript.spec.ts | 81 +- tests/sidechat-view.spec.tsx | 15 +- tests/smoke.spec.ts | 383 +++-- tests/subagent-detect.spec.ts | 75 +- tests/subagent-jobs-view.spec.tsx | 370 +++-- tests/subagent-jobs.spec.ts | 56 +- tests/subagent-live-polling.spec.tsx | 71 +- 96 files changed, 4567 insertions(+), 2080 deletions(-) create mode 100644 docs/plans/2026-09-22-dsh-0.1.7-alpha.1-adaptation.md create mode 100644 src/client/subagent-catalog.ts create mode 100644 src/client/use-dir-watch.ts create mode 100644 src/fs-watch.ts create mode 100644 tests/fs-watch.spec.ts create mode 100644 tests/primitives-exports.spec.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9a11a11..b7b7ca9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -175,7 +175,7 @@ jobs: - name: Install dsh CLI (pinned) env: NODE_OPTIONS: --max-old-space-size=4096 - run: npm install -g @deepseek-ai/dsh@0.1.6-alpha.2 + run: npm install -g @deepseek-ai/dsh@0.1.7-alpha.1 # lib/ is gitignored; the tarball is produced from this build. - name: Build and pack the npm tarball diff --git a/AGENTS.md b/AGENTS.md index 0b0e634..1c3cde9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,47 +17,60 @@ ## 2. CI 挂载冒烟(`plugin-mount` job / `pnpm test:mount`) -「npm 打包 → 真实挂载 → 无头渲染」门禁(证明打包产物在真实 DSH 挂载后不 crash):`pnpm build && pnpm pack` 产 tarball → `scripts/e2e-mount.sh` 装进全新 scratch profile(`dsh plugin --profile web add `)并启动真实 `dsh web`(keyless,`--port 0`)→ `tests/e2e/mount.e2e.ts`(Playwright)断言 `[data-dsh-better-sidebar]` 挂载、无错误条/pageerror/console 错误,展开 DSH 原生右侧栏后经其 guide 页逐个打开本插件贡献的 tab 类型**以及宿主自己的终端 / 浏览器条目**(后者在宿主装了对应包时必须恰好 1 条——多于 1 条即意味着插件又在遮蔽宿主),再经插件文件树(原生 `files` kind 接管)打开 seed 文件强制加载 editor chunk(`client-editor.js`),并跑 mermaid / README 预览与 sidechat 宿主路由烟测。 +「npm 打包 → 真实挂载 → 无头渲染」门禁(证明打包产物在真实 DSH 挂载后不 crash):`pnpm build && pnpm pack` 产 tarball → `scripts/e2e-mount.sh` 装进全新 scratch profile(`dsh plugin --profile web add `)并启动真实 `dsh web`(keyless,`--port 0`)→ `tests/e2e/mount.e2e.ts`(Playwright)断言 `[data-dsh-better-sidebar]` 挂载、无错误条/pageerror/console 错误,展开 DSH 原生右侧栏后经其 guide 页逐个打开本插件贡献的 tab 类型**以及宿主自己的终端条目**(必须恰好 1 条——多于 1 条即意味着插件又在遮蔽宿主),再经插件文件树(原生 `files` kind 接管)打开 seed 文件强制加载 editor chunk(`client-editor.js`),并跑 mermaid / README 预览与 sidechat 宿主路由烟测。**`browser` 不在这个清单里,而是显式断言缺席**:DSH 0.1.7 的 `ui-sidebar-browser` 只在 desktop profile 挂载(`web-app` 的 patch 里 `disabled: ctx.get('profileContext')?.name !== 'desktop'`),lane 全走 web profile,钉住「没有」比默默放过更能暴露宿主立场的变化。 -本地:`pnpm build && pnpm pack && pnpm exec playwright install chromium && pnpm test:mount`(`DSH_CMD` 未设时 `scripts/e2e-common.sh` 回退的 npx 钉版是 `DSH_NPX_SPEC`,默认 `@deepseek-ai/dsh@0.1.6-alpha.2`——**不钉就会解析 `latest`,而 `latest` 仍是插件已不支持的 0.1.5 线**)。CI 钉 `@deepseek-ai/dsh@0.1.6-alpha.2`(npm `alpha`;`latest` 仍是 0.1.5-rc.2;peer 下限 `^0.1.6-alpha.2`)。该步骤用 `NODE_OPTIONS=--max-old-space-size=4096`:钉版自身的传递依赖是浮动 `^` 范围,上游分阶段发布预发布版时(rc.2 于 2026-09-10 的 14:43–14:57 逐包上线)npm 会组出混合 peer 图,3062 条 ERESOLVE 后 OOM(exit 134);另一个失败模式是对未发布兄弟包 ETARGET,靠「钉一个已完整发布的版本」修掉。**不要加 `--legacy-peer-deps`**:它跳过的正是全局安装必须提供的 peer,`@deepseek-ai/dsh-app-boot` 在 boot 时 require 的 `@deepseek-ai/cordis-plugin-group` 是 peer 而非 dependency,加了它 CLI 直接 `ERR_MODULE_NOT_FOUND`(实测过一次,见 rc.2 计划 C 节)。`ci-windows` 的 `Test` 跑 `pnpm test:windows`(`--maxWorkers=1`)而非 `pnpm test`——多个 spec 真起进程(`smoke` / `install-powershell` / `git`;终端删除后 `agent-pty` / `pty-deps` / `pty-helpers` 已不在),2 核 runner 上并行起 ConPTY / 冷启 `powershell.exe` 是超时与 worker 静默死亡的放大器;`vitest.config.ts` 的全局 `testTimeout: 15_000`(默认 5000 在 Windows 上对真起进程的用例太低,三个不同文件先后翻车)是配套的一半。e2e spec 命名 `*.e2e.ts` + vitest `exclude` 双保险;**改 `exclude` 必须保留默认排除项**(exclude 整体替换默认值)。 +本地:`pnpm build && pnpm pack && pnpm exec playwright install chromium && pnpm test:mount`(`DSH_CMD` 未设时 `scripts/e2e-common.sh` 回退的 npx 钉版是 `DSH_NPX_SPEC`,默认 `@deepseek-ai/dsh@0.1.7-alpha.1`——**不钉就会解析 `latest`,而 `latest` 仍是插件已不支持的 0.1.5 线**)。CI 钉 `@deepseek-ai/dsh@0.1.7-alpha.1`(npm `alpha`;`latest` 仍是 0.1.5-rc.2;peer 下限 `^0.1.7-alpha.1`)。该步骤用 `NODE_OPTIONS=--max-old-space-size=4096`:钉版自身的传递依赖是浮动 `^` 范围,上游分阶段发布预发布版时(rc.2 于 2026-09-10 的 14:43–14:57 逐包上线)npm 会组出混合 peer 图,3062 条 ERESOLVE 后 OOM(exit 134);另一个失败模式是对未发布兄弟包 ETARGET,靠「钉一个已完整发布的版本」修掉。**不要加 `--legacy-peer-deps`**:它跳过的正是全局安装必须提供的 peer,`@deepseek-ai/dsh-app-boot` 在 boot 时 require 的 `@deepseek-ai/cordis-plugin-group` 是 peer 而非 dependency,加了它 CLI 直接 `ERR_MODULE_NOT_FOUND`(实测过一次,见 rc.2 计划 C 节)。`ci-windows` 的 `Test` 跑 `pnpm test:windows`(`--maxWorkers=1`)而非 `pnpm test`——多个 spec 真起进程(`smoke` / `install-powershell` / `git`),2 核 runner 上并行起 ConPTY / 冷启 `powershell.exe` 是超时与 worker 静默死亡的放大器;`vitest.config.ts` 的全局 `testTimeout: 15_000`(默认 5000 在 Windows 上对真起进程的用例太低,三个不同文件先后翻车)是配套的一半。e2e spec 命名 `*.e2e.ts` + vitest `exclude` 双保险;**改 `exclude` 必须保留默认排除项**(exclude 整体替换默认值)。 --- -## 3. DSH 0.1.6-alpha.2 适配要点(0.1.6-alpha.2+ 基线) +## 3. DSH 0.1.7-alpha.1 适配要点(0.1.7-alpha.1+ 基线) -**v0.20.0(正式版,npm `latest`)起仅支持 DSH 0.1.6-alpha.2+**(peer 下限 `^0.1.6-alpha.2`,CI 钉 `@deepseek-ai/dsh@0.1.6-alpha.2`)。**下限必须动**:semver 的预发布规则要求比较子与候选版本同 `[major,minor,patch]` 元组,`^0.1.5-rc.1` 在数学上**永远容纳不下任何 `0.1.6-*` 预发布版**(实测:`0.1.6-alpha.2` 对 `^0.1.5-rc.1` / `^0.1.5-rc.2` / 裸 `*` 全部 false),而 `^0.1.6-alpha.2` 天然容纳 0.1.6 的 alpha/rc 全线。**支持线取舍**:0.1.5-rc.* 用户停留在 **v0.19.1**——rc.2 → alpha.2 有真实破坏(`SidebarRightGuideEntry.id` 必填、`conversation.chat.turnTail` chain→list),按本仓库先例(v0.19.0 抬到 0.1.5-rc.1 时直接桥掉 0.1.2 线)不写运行时兼容层;0.1.5-alpha.2 及更早同样停留在旧版。发版:release.yml 按版本号是否含 `-` 自动选 `alpha`/`latest` dist-tag(0.20.0 无后缀 → `latest`)。 +**v0.21.0-alpha.1 起仅支持 DSH 0.1.7-alpha.1+**(peer 下限 `^0.1.7-alpha.1`,CI 钉 `@deepseek-ai/dsh@0.1.7-alpha.1`)。**下限必须动**:semver 的预发布规则要求比较子与候选版本同 `[major,minor,patch]` 元组,`^0.1.6-alpha.2` 在数学上**永远容纳不下任何 `0.1.7-*` 预发布版**(实测 `0.1.7-alpha.1` 对 `^0.1.6-alpha.2` / `>=0.1.6-alpha.2` 全是 false),而 `^0.1.7-alpha.1` 天然容纳 0.1.7 的 alpha/rc 全线。**支持线取舍**:0.1.6-alpha.2 及更早的用户停留在 **v0.19.1**(0.20.0 这一版从未发布到 npm)——本版破坏面足够大(`dsh-settings` 整体重写、26 个图标具名导出消失、会话格式 v3→v4),按本仓库先例不写运行时兼容层。发版:release.yml 按版本号是否含 `-` 自动选 `alpha`/`latest` dist-tag(`0.21.0-alpha.1` 含 `-` → `alpha`;npm `latest` 仍是 0.19.1)。 -**本版新增的两条硬契约(两者都是静默失败,已在 v0.20.0 修好并加测试守护)**: +### 3.1 本版四类硬破坏(都是「不修就崩或静默失效」) -1. **`SidebarRightGuideEntry.id` 必填且同一 provider 内唯一**(`tab-registry.ts`):缺 `id` 时两个条目在 `undefined` 上相撞,`SidebarRightTabRegistry.register` 抛 `duplicate guide entry id`。抛点在本插件 `ctx.inject(['sidebarRightTabs'], cb)` 的**回调体内**,异常被 cordis 吞掉——症状是整个原生承载面**静默空掉**(指南无条目、`openTab` 全拒),没有任何其它提示。`src/client/native/index.ts` 的两个 guide 条目各自带 `id`(descriptor id / `files`),`tests/native-surface.spec.ts` 断言「每个 guide 条目都有非空且互不重复的 id」。同处还加了 `reportFailure` 上报口:任何 descriptor 注册失败走 `index.tsx` 的可见诊断条,且单个失败不再带走其余注册。 -2. **`conversation.chat.turnTail` 由 `chain` 改为 `list`**(`ui-chat/src/client/contract/slots.ts`):list 槽要求 `options.id`,传 `select` 直接抛 `list slot "…" requires options.id`。**语义变了**:chain 的 `select` 让一个条目**替换**内置产物行,list 只能**追加**——上游是有意为之(「An additive list lets both plugins contribute without either knowing the other's data or rendering」),所以插件再注册一行只会与内置的产物卡**重复**。插件的轮尾接管(`intercept.tsx` / `produced-files.ts`)因此整体删除,产物行交回宿主;它开文件走宿主 `openFile` → 本插件的 editor,行为不变。**副作用(已接受)**:失去 F5 式「在侧边栏文件树里揭示该文件」的入口(树内右键「在文件夹中显示」仍在)。 +1. **`dsh-settings` 整体重写**(`packages/settings/settings`):`SettingsProvider.register(ns, schema)` 被删除,只剩 `SettingsForms` 的 `describe/update/replace/mutate/configure`;**命名空间就是插件 Loader 行的 `entry.options.id`**(不再是插件自选的字符串),schema 来自 `entry.fiber.runtime.Config`,且 `describe()` 只列出「有 `Config` 且 fiber 处于 ACTIVE」的条目。本插件的适配: + - `src/config.ts` 把用户偏好并进导出的 `Config`(原先只是 4 个部署限额),并把每个偏好字段收集为 volatile schema。**这一个标记就是「改设置不重挂插件」的全部机制**:Loader 的 `equalExceptVolatile` 跳过 volatile 字段,纯 volatile 变更走 `_commitVolatile` 提交进运行中的引用并发 `loader/volatile-update`,而普通字段(4 个限额)保持原来的重挂语义。**必须用 DSH 自己的 schemastery(`@deepseek-ai/schemastery`),这是本版最容易踩空的一处**。`schemastery` 的 `meta.volatile` 只是标记;DSH 的构建额外在**解析期把 volatile 字段包成 cosmokit `Volatile` 引用**(`createVolatile`),而 Loader 的 `_commitVolatile` 正是走 `volatileEntries`/`updateVolatile` 去提交那些引用。用公开的 `schemastery` 包:写入**报告成功、revision 也涨**,但有效值永远不变——静默丢弃每一次改设置(真机实测:`settings.update` 200 + `revision: 1`,而 `describe().value` 仍是旧值)。另一个同等致命的坑:`.volatile()` 走 `extra()`,**返回的是副本而不是原地修改**,`for (const f of ...) f.volatile()` 会把标记丢掉,随后宿主报 `Plugin entry "better-sidebar" has no volatile fields`;必须收集返回值。 + - `src/index.ts` 的 `ownEntryId(ctx)` **运行时自发现**自己那一行的 id(按 `options.name === 'dsh-better-sidebar'` + `entry.fiber === ctx.fiber` 匹配,回退到第一个启用的同名行),**不允许硬编码 `better-sidebar`**:聚合包会用 `web-ui-better-sidebar` 之类的 id 挂同一个包。 + - `viewOf()` = `describe({redactSecrets:true}).find(d => d.ns === ns)`;写入 = `update(ns, patch, expectedRevision)`(`SettingsConflictError` 仍导出、构造签名未变,409 分支保留)。 + - `ctx.effect(() => settings.configure({ auto: false }, ctx.fiber))` —— 官方 README 给「自带设置页的插件」的写法;不这么做 Settings 会把同一批字段渲染两遍。 + - **`settings/document-updated` 监听不到,不要写**:它在 settings 服务自己的 context 上 `emit`,而 cordis 的事件只向上冒泡到该 ctx 的祖先,插件的 fiber 是兄弟。本插件改用「客户端每次重新读取表单时重新求值」覆盖同样的触发点(`settingsFace.get()` 里跑门控同步)。 +2. **`ui-primitives` 图标整族改名**:`Icon<14|16>` → `IconRegular`(1px 描边)/ `IconMedium`(1.3px)。0.1.6 的 81 个 `export const Icon*` 在 0.1.7 变成 184 个,**旧的尺寸后缀名一个不剩**(`git grep -c Outline16 -- packages/client/ui-primitives/src` = 0)。插件 26 个具名导入消失、17 个文件受影响。**失败模式是 render 期而不是 load 期**:插件 client bundle 是 CJS + external,产物是裸属性读,缺导出 = `undefined` → React 抛 `Element type must be invalid`,不是 ESM link error。**改名必须同时钉回尺寸**:0.1.6 的 `IconXxx14` 默认 `size = 14`,0.1.7 的 `XxxRegular` 一律默认 16,所以原先依赖 14px 默认值的 5 处(`TabBar.tsx` / `Sidebar.tsx` / `FileTree.tsx` 的 `IconCloseFillRegular`、`SubagentView.tsx` 两处 `IconRefreshOutlineRegular`)必须显式传 `size={14}`,否则**静默变大 2px**。另有 `StateDot` 的 ongoing 态被整图重画(10px 像素矩阵 → 14px 圆环 spinner,默认尺寸 `10 → 14`),签名兼容故无编译错误;插件选择跟随宿主观感。 +3. **会话格式 v3 → v4**(`packages/session/session-format-v3-to-v4`):两处变化。 + - **`source.kind === 'plugin'` 被硬拒**(`message-sources.ts` 抛 `format v4 message requires a producer-owned source kind`),而插件的 sidechat 边界注入正是那个形状。改用 **`plugin:dsh-better-sidebar`**:与 DSH 自己的 v3→v4 迁移对历史行产出的 kind **逐字一致**(`producerKind()` 对未知插件名返回 `` `plugin:${plugin}` ``),所以新旧数据形状统一,**历史 sidechat 会话不会丢**。类型层面还需一条 `declare module '@deepseek-ai/dsh-llm'` 的 `MessageSourceMap` 增强(0.1.7 把它改成合并可扩展的封闭联合,没有共享 catch-all)。 + - **tool 结果消息重写**:`role: 'user'` + 嵌套 `type: 'tool-result'` 包装块 → `role: 'tool'` + **顶层** `content` / `isError` / `toolCallId`。插件的 4 个解析器原先靠 `candidate.type === 'tool-result'` 命中,在新形状下恒返回空串(**静默空白**)。现在四处都**同时接受新旧形状**(旧分支必须保留:历史日志里就是旧形状)。 +4. **客户端会话快照换字段**:`SessionListState.subagentsByParent` / `jobsBySession` 被 `projectionsBySession` 取代,且 **`setSubagentCatalogOpen` 这个按需观察 API 被整体删除**(0.1.7 在连接建立时一次性加载所有 Session 的 projection,所以那套 observe/unobserve 机制应当删除而不是改名)。子代理目录改读 `projectionsBySession[sid]?.values.subagentCatalog`(`{id, createdAt, mode: 'one-shot'|'continuable'|'unknown', label?}`,**注意 `unknown` 是新值**);后台任务列表**没有替代字段**,改由插件新增的 `jobs.list` 路由读 `ctx.jobs.list(sessionId)`。同一版里 `ctx.jobs.list/get/read/kill` 的 caller 从 `Agent` 变成 `SessionId`(插件因此可以彻底不碰 `ctx.agents` 来调注册表)。 -**本版新增的让位(宿主内置了插件原有的两个面)**: +### 3.2 本版的能力让出与收敛 -3. **终端**:`@deepseek-ai/dsh-client-ui-sidebar-terminal`(+ `dsh-api-terminal-controller`)是宿主自己的右列终端类型。插件侧整个 PTY 栈(`pty-manager` / `agent-pty` / `pty-deps` / `tools.ts` / `TerminalView` / 终端字体与链接 / 跨会话固定终端 / xterm chunk)已删除,`node-pty` 依赖、`pnpm-workspace.yaml` 的 `allowBuilds`、安装脚本的 `-Repair` 模式随之移除。**模型侧代价**:插件原有的 8 个 `terminal_*` 工具(默认关)是模型唯一的**跨调用持久**终端,而上游等价物 `@deepseek-ai/dsh-tool-terminal` **未被任何 shipped bundle 默认挂载**——删除后模型只剩一次性 `bash` / `pwsh`。这是「不维护第二套 PTY」的既定代价,README 给出自行启用的指引。 -4. **浏览器**:`@deepseek-ai/dsh-client-ui-sidebar-browser` 是宿主自己的 `browser` kind,且 `ui-chat` 已内置把正文里的 http(s) 链接委派给它(`ctx.sidebarRight.openTab('browser', { params: { url } })`)。插件删掉 `BrowserView` / `browser.ts` / `browser-probe.ts` 与自己的 `browser` tab 类型;**保留** DOM 层链接接管(宿主不做按协议分流,https 默认交系统浏览器是本插件唯一还能提供的能力)与已发布的 `urlTarget` 机制,接管目标改为**宿主的 browser kind**(`window.open` 兜底,宿主没装该包时不至于点了没反应)。宿主的 `browser` kind 不进插件注册表,所以 `browserNoSandbox` / `browserAllowedLoopback` 两个只配置插件自家 iframe 的设置项随 `browser.probe` 宿主路由一并删除。 +5. **只读文件预览交还宿主**:0.1.7 的 `ui-sidebar-documentpreview` 长齐了 code / excel(xlsx,xls,csv,tsv) / office(宿主侧转 PDF) / pdf / image / html / markdown / text,并带缩放与**按目录自动刷新**。插件删掉自己的 `image` / `pdf` / `binary-download` 三个 viewer,并在 `src/client/native/index.ts` 的 `editor.canOpen` 里**拒绝认领**这些扩展名(`canOpen` 返回 false 就把地址让给内置 `text` 的 `fallback` 档): + `xlsx xls xlsb xlt xltx xltm ods ots fods csv tsv pdf png jpg jpeg gif webp svg bmp ico avif doc docx dot dotx ppt pptx` + **保留在插件侧的三件**:`md/markdown`(自带渲染器,用户认为优于内置)、`html/htm`(自带沙箱预览 + `htmlViewerNoSandbox` / `htmlViewerDefaultUnsafe`,宿主没有这两个开关)、以及 `code` catch-all(**它是可编辑的 CodeMirror + 保存**,内置那几个是只读预览)。未知二进制(`.zip`/`.wasm`)仍走 `code` 认领 → 宿主 fsRead 判 binary → head 重匹配无 sniffer → `EditorHost.tsx` 的 `load.status === 'binary'` 兜底渲染下载面板,**功能不回归**。 +6. **宿主 `browser` kind 在 Web profile 不再挂载**:`packages/bundle/web-app/cordis.patch.yml` 给 `ui-sidebar-browser` 加了 `disabled: !!js "ctx.get('profileContext')?.name !== 'desktop'"`。因此 e2e 的 `HOST_OWNED_TABS` 只剩 `['terminal']`,并且**显式断言 browser 条目缺席**(钉住新真值,而不是默默放宽)。DSH 同时新增用户设置 `linkOpening` 决定正文链接进侧栏还是新标签页。 +7. **外链接管收敛**:删掉 `browserInterceptLinks` / `browserInterceptHttp` / `browserInterceptHttps` 三个偏好键(含 20 份词典的 key)。新行为是**只认领声明了 `urlTarget` 的链接**,其余一律不 `preventDefault`、放行给宿主;认领成功但目标类型此刻不可用时 `window.open(url, '_blank', 'noopener,noreferrer')` 兜底。**修掉了一个真实回归**:0.1.6 那版把浏览器交还宿主时,`openInSidebar` 仍兜底到 `'browser'`,而插件注册表里已无该类型、`service.openTab` 对未注册类型静默 `return`——默认配置下插件自绘 markdown(sidechat 转录 / 编辑器预览 / diff)里的 http 链接**点了没反应**。 +8. **文件树新增实时刷新**:插件接管了 `files` kind,所以宿主自己的按目录 watch 覆盖不到它。新增 `src/fs-watch.ts`(宿主侧,每条连接一组 `fs.watch`,150ms 去抖,上限 64 个句柄)+ `src/client/use-dir-watch.ts`(一条 `/sidebar/ws/fs-watch` socket 携带展开目录集,目录折叠即退订),客户端收到通知后只丢弃并重列那一层缓存。路径解析沿用 `fs.tree` 的同一道 workspace fence。 -**本版还需知道的**: +### 3.3 既有缺陷(不是 0.1.7 引入,但这次一并修) -5. **原生右列布局现在会持久化**(`localStorage` `dsh.sidebar-right.v1.`,zod 校验,tab 体渲染前恢复):§3.10 里「原生布局只在内存,不做持久化」的旧说法对 0.1.6 起**已失效**。同时新增 `ctx.sidebarRight.openTabs` / `tabsIn(sessionId)` / `registerCloseHandler(kind, handler)`(class 上而不在 `ISidebarRight` 接口里)、`SidebarRightTabDefinition.multiple`、`SidebarRightPlacement.preferNewPane`、keyed 槽 `sidebar.right.tab.guide.entry`。插件只用到 `multiple` 与既有面。 -6. **`dsh.profile.patchReload` 已从 manifest 删除且零代码读取**(静默失效):HMR 现在是 `packages/boot/hmr` 的一行。本仓库未使用它。 -7. **`dsh.plugin.json` 从来不是 DSH 契约**:任何 DSH 版本都没有读它的代码(tag `dsh-v0.1.5-rc.2` / `dsh-v0.1.6-alpha.2` 全树 grep 均 0 命中)。DSH 真正读的是 `package.json` 的 `dsh.bundle.patch` + `dsh.client`(`platform`/`inject`/`immediately`/`external`)+ `exports["./client"]`。`dsh.plugin.json` 只服务第三方 registry(社区市场)通道,`lib/client-registry.js` 同理;`tests/manifest-consistency.spec.ts` 守护的是这条通道的形状,不是 DSH 契约。0.1.6 新增的官方管控面是 `packages/boot/plugin-manager`(`ctx.pluginManager` + `plugin_manager` 工具,需 `danger-full-access`)与 Web 的 Plugins 页——**两者都会零额外声明地接管任何已声明 `dsh.bundle.patch` 的包**,本插件无需适配。 -8. **`@deepseek-ai/dsh-client-ui-primitives` 仍不声明 `dependencies`,且裸 import 的集合又变大了**:0.1.6-alpha.2 的 bundle 额外裸 import `diff` 与 `simple-icons`(前者此前只由传递依赖满足,后者是新引入的 3 万余品牌图标集,`lib/index.js` 里具名 import 32 个)。两者已提升进 devDependencies;`@deepseek-ai/dsh-code-runtime` 则**整包消失**(`packages/code-runtime` 已删,npm 上停在 0.1.5-alpha.2),devDependencies 同步移除。 -9. **`ui-primitives` 本版的三处破坏**(已适配):`IconSendOutline16` 不再导出(只剩 `IconSendOutline14`);`TerminalBlockLabels` 新增必填的 `noExitCode`;`ConnectionIndicator` 删掉 `reconnectLabel`(断线标签现在**自身**命名重试动作,跟随宿主文案)。 +9. **幽灵字段 `.current`**:插件读 `ctx.sessions.list.getSnapshot().current`,而 `SessionListState` **在两个 tag 里都没有这个字段**(插件 `context-types.ts` 自造了它,所以 tsc 一直放行)。后果:`src/client/native/surface.ts` 的 `onScreen` 判定恒 false(永远走 `*In` 分支),`src/client/Sidebar.tsx` 的 `store.setSession(current)` 恒收到 `undefined`(按会话持久化实际没绑上),`use-host-feeds.ts` 的窄视口 park 门控恒假。**改用 0.1.7 新增的 `ctx.sidebarRight.mounted`**(`ObservableSnapshot`,只在会话真变化时 set)。`Pending` 队列与 `openTabIn/openResourceIn/closeIn` **必须保留**:它们仍是唯一能写「未上屏会话 store」的路径(这三个方法在具体 class 上,不在 `ISidebarRight` 接口里,需结构化探测)。 +10. **`package.json.files` 漏了 `lib/client-locale.js`**:chunk 名在 8 处镜像,`files` 里只有 `client-editor.js` / `client-mermaid.js`,于是 locale chunk 从来没进过 npm tarball(社区 PR #739 覆盖同一问题)。已补。 +11. **测试 harness 的 `window` shim 缺 `addEventListener`**:0.1.7 的 `ui-primitives` 在**模块求值期**就 `window.addEventListener('scroll'|'resize'|'blur')`,而 `tests/browser-globals.ts` 的假 window 只有 `clearTimeout/setTimeout/innerWidth/innerHeight/getComputedStyle/location` → `TypeError: window.addEventListener is not a function`,**任何 import `service.ts` 的 spec 一个用例都跑不起来**。已补 `addEventListener` / `removeEventListener` / `dispatchEvent` / `matchMedia`。 +12. **测试期依赖闭包又长了一层**:`ui-primitives@0.1.7` 的产物新裸 import `@deepseek-ai/dsh-client-store` 与 `@deepseek-ai/dsh-util-workspace-path`,**而 `dsh-client-store` 自己的产物又裸 import `zustand`(×3)与 `immer`**,且这几个包都不声明 `dependencies`。所以 devDependencies 要补的是 **4 个**(两个 `@deepseek-ai` 包 + `zustand@~4.4.7` + `immer@^10.1.1`),不是 2 个——只补前两个,vitest 一加载就 `Cannot find package 'zustand'`。**反过来不要预加** `dsh-workspace` / `dsh-agent-preset-registry`:它们只是 `dsh-subagent` 的 optional peer,而该包 28 个 `lib/*.js` 里从不 import 它们(实测解包核对)。一律以 `pnpm peers check` 的实际输出为准。 +13. **e2e 的 token 交换不能走 Node 全局 `fetch`**:宿主静态 fallback 对纯 Node 的 GET 回 **空体 405 且无 `Set-Cookie`**(在 0.1.5-rc.2 与 0.1.7-alpha.1 上都复现,属 harness 怪癖而非宿主回归),于是 `cookieHeader` 永远拿不到、后续 `/api` 种子请求全部未认证、挂载 lane 卡在拿不到 tab bar。改用 Playwright 自己的 `request.newContext()`(`exchangeLaunchCookieViaPlaywright`,`maxRedirects: 0` 以免跟随 303 丢掉 `Set-Cookie`)。 -宿主契约(0.1.5 线起沿用,均经真机挂载冒烟验证): +### 3.4 宿主契约(0.1.5 线起沿用,均经真机挂载冒烟验证) 1. **一次性 token 鉴权**:就绪行 `dsh web: http://127.0.0.1:/?token=<43字符>`(导航换签名 cookie,干净 URL 401)。`e2e-mount.sh` 的 URL grep 必须延伸到空白(`[^ ]*`,在 `/` 截断丢 token);e2e 统一走 `tests/e2e/host.ts`(token 必选:`parseLaunchUrl` 对裸 origin 直接抛错),带 stamps 导航走 `gotoPage()`(先 addCookies 再直达——token 换 cookie 的 303 会丢弃同 URL 其它 query 参数)。插件 `/sidebar/*` 路由不受影响,同源 fetch 照旧。 -2. **Remote gateway 斜杠 RPC(唯一方言)**:`POST /api/workspace/create`,payload 恰为 `{args: {...}}`,**args 按控制器 TS 参数名包装**(`workspace/create`、`session/create` → `{args:{request:{...}}}`;`session/list` 参数名 `_request` **不可省略**——`{}` 也被拒 `args fields do not match the descriptor`);envelope `method` 与路径一致,点分路径 404。请求由 `tests/e2e/host-protocol.ts` 的 `rpcAttempt` 构造、`tests/e2e-host-protocol.spec.ts` 锁定;要调新方法先在真机验参数名再进 `RPC_ARGS_KEY`。 -3. **`MarkdownText` labels 嵌套契约**:必填 `labels: { code: { copyLabel, copiedLabel }, footnotes }`(漏传回退硬编码中文)。四个渲染点(mermaid.tsx / MarkdownHtml.tsx / TextEditor.tsx / SideChatView.tsx)统一走 `src/client/markdown-labels.tsx` 的 `markdownTextProps()`。 -4. **侧边对话转录走自有路由,不碰客户端宿主 RPC**:`ctx.connection.api`(含 `sessions.history`)在 alpha.1 整体移除,继任 `session/follow|page` 又对 `origin:'subagent'` 会话强制 subagent 地址(普通 `{kind:'session'}` 被 `agent-busy` 拒)且分页 `throughSeq` 不得超当前游标。转录因此由 **`sidechat.events`** 插件路由供给(`src/sidechat-routes.ts`:live 读 `agent.session.snapshotEvents()`、冷读 `sessionPersistence.inspect`,服务端 `session/end-seed` 切割 + `afterSeq` 增量);`ctx.connection` 镜像与 inject 已删。**0.1.5 起实时增量不在日志里**:`assistant/chunk` 事件被删除,进行中的模型增量改由 `agent/assistant-stream` 瞬时帧发布(`start` / `chunk` / `end`,不入会话日志),结算时才落 `assistant/message`(内嵌 `stream`)或 `assistant/attempt`(失败尝试,内嵌 `stream`)。插件在宿主侧 `src/assistant-live.ts` 折叠这些帧成有界缓冲,`sidechat.events` 的 `live` 字段按「当前 attempt 全量、每次轮询替换」下发(不是增量),转录映射与继承快照都读它。设计见 [docs/plans/2026-08-20-sidechat-tab-design.md](docs/plans/2026-08-20-sidechat-tab-design.md) §10。 -5. **`dsh-settings` 无运行时 `settingsNamespace`**:命名空间合法性校验转为编译期模板字面量 `SettingsNamespaceInput`(小写字母开头 + `[a-z0-9-]` 尾部),`'dsh-better-sidebar'` 字面量直接过——宿主侧直接传常量(`src/index.ts` 的 settings inject)。 -6. **`dsh-subagent` 的 `SUBAGENT_DESCRIPTOR_VERSION` 2 → 3**:sidechat 种子的 `subagent/descriptor` 版本由宿主包盖章,插件不硬编码;测试断言跟随常量(`tests/sidechat-routes.spec.ts`),勿钉字面量。 -7. **`@deepseek-ai/dsh-client-runtime` 包已消亡**(继任 seed 是裸名 `dsh-client-store`,无 `/client` 子路径):peerDependencies、devDependencies、`dsh.client.inject`、chunk externals 白名单(`src/client/chunk-loader.ts` / `tsdown.config.ts` / `tests/chunk-loader.spec.ts` / `tests/manifest-consistency.spec.ts` 四处同步)均已无该条目。 -8. **e2e scratch profile 的 `minimumReleaseAgeExclude` 含 `'@deepseek-ai/*'`**(`scripts/e2e-mount.sh` / `e2e-aggregate-mount.sh`,与仓库根 `pnpm-workspace.yaml` 同策):alpha 版本常在发布后 24h 内跑 lane,pnpm 11 的 `minimumReleaseAge` 默认会拒装新鲜包。 -9. **插件开发树的 dsh-* 传递 peer 需提升为 devDependencies**(alpha.3 首见):`dsh-subagent` 等 npm 包把 `dsh-attachment` 等 dsh-* 姊妹包全部声明为 peerDependencies(由宿主 bundle 树统一提供,宿主侧无此问题),插件仓库若只直接依赖其中一部分,其余 peer 在 pnpm 下会解析到树上残留的旧版——如 `dsh-attachment@0.1.1-rc.1` 缺 `admitPromptContent` 导出、`dsh-subagent` 产物 import 它时测试加载即崩。因此 devDependencies 需涵盖 dev 树实际触达的全部 peer(attachment / code-runtime / scope / session-projection / system-prompt / user-approval / util-time 七个即为此提升,与直接依赖同款精确钉版);`@deepseek-ai/cordis` peer 自 alpha.3 起要求 `^4.0.2`(上游全线 peer 已升)。适配新 alpha 版本时先跑 `pnpm peers check`,把新失配的传递 peer 一并提升进 devDependencies。例外:**`dsh-client-locale` 的 peer/devDep 允许落后于基线**(alpha.5 时上游停在 0.1.2-alpha.3 未发新版)——peer 下限 `^0.1.2-alpha.3` 天然容纳同 tuple 的 alpha.5 运行时,此时保持旧钉版并在 `pnpm-workspace.yaml` 注明,待上游发版再追平;rc.1 起上游恢复发版,该包回到与 DSH 同 tuple(0.1.2-rc.1 → 0.1.5-rc.1,peer 下限 `^0.1.5-rc.1`),该例外消除。**alpha.2 起还有一类:上游包自身的 `dependencies` 丢失**——`@deepseek-ai/dsh-client-ui-primitives@0.1.5-alpha.2` 的 manifest 不再声明任何 `dependencies`(alpha.1 声明了 19 个),但 `lib/*.js` 仍裸 import `anser` / `shiki` / `@shikijs/langs/*` / `mdast-util-*` / `micromark-*` / `katex`。宿主由预构建前端 bundle 满足,独立安装的 dev/test 树不会——vitest 一碰 primitives 就 `Cannot find package 'anser'`。此时把 alpha.1 声明的同一组版本提升进 devDependencies(`clsx` 已在 dependencies 无需重复)。**rc.1 复查:`@deepseek-ai/dsh-client-ui-primitives@0.1.5-rc.1` 仍不声明任何 `dependencies`,bundle 仍裸 import 同一组包**(rc.2 同:只把 `CodeFileIcon` 的 SVG 数据拆进了新文件,无 `dependencies`)——这组提升不得回退,`pnpm peers check` / 单测一旦报 `Cannot find package 'anser'` 即是有人回退了。 -10. **右侧栏是 DSH 原生栏,插件只提供 tab 类型**:聊天里一切文件打开(工具行 / 产物行 / 正文提及 / 行内代码路径)统一走 `ctx.sidebarRight.openResource(fileAddressFor(sessionId, cwd, path))`(`packages/client/ui-chat/src/client/apply.ts` 是唯一调用点),`remote.session.openWorkspacePath` 在 0.1.5 客户端已无调用者,插件的 openpath 拦截随之删除。插件的每个 `TabDescriptor` 注册成原生 tab 类型(`kind = descriptor.id`,`extension` 带)+ 原生 tab 体(`sidebar.right.pane.tab` keyed 槽,key = `dsh-better-sidebar:`);`editor` 类型同时认领 `dsh-resource://file/**`(压过内置 `text` 的 `fallback`),并接管内置 `files` 页面 kind(`openTab('files')` 打开插件文件树,注销即复位)。`ctx.sidebarRight.openTab/openResource/close` 只对**在屏会话**写入;跨会话用具体类上的 `openTabIn/openResourceIn/closeIn`(不在 `ISidebarRight` 接口里,需结构化探测),目标会话未挂载时排队到上屏重放(`src/client/native/surface.ts`)。原生 tab 的插件侧状态(合成 `SidebarTab`、树展开集合、实例编号)在 `src/client/native/tab-adapter.tsx`;**原生布局在 0.1.5 线只在内存,0.1.6 起由宿主持久化**(见上方第 5 条)。**注册必须等服务、不能等槽声明**:原生栏先声明 `sidebar.right.pane.tab` 再 `provide('sidebarRightTabs')`,真机 profile(web,0.1.5-alpha.1)上槽声明回调里 `ctx.get('sidebarRightTabs')` 仍是 undefined(实测 3 秒后才出现),按槽触发注册会静默什么都不注册且永不重试——用 `ctx.inject(['sidebarRightTabs'], cb)` 驱动类型注册(`tests/native-surface.spec.ts` 以「槽先触发、服务后到达」的顺序守护),槽声明只用来挂 tab 体。指南 §0 列了全部行为差异。**alpha.2 起(v0.19.0-alpha.1)**:指南条目一度改成「图标+标题」胶囊、`TabDescriptor.description` 随之下线,新建标签页的默认页改为从注册表选(恰好 1 个指南条目 → 直接开它,0 或 ≥2 → 开指南),`revealIfOpened` 对「页面」在同一 pane 内强制去重。**0.1.6-alpha.2 起**:`SidebarRightGuideEntry.id` 必填且同 provider 内唯一(见 §3 第 1 条——缺它会静默清空整个承载面);同 provider 的 guide 条目是**追加**关系,`chain` 那种「替换宿主条目」的语义只存在于 `conversation.chat.turnTail` 且已被上游改成 `list`(§3 第 2 条)。原生 tab 体宿主 `.paneBody` 是**有确定高度的块级滚动容器**(非 flex 容器),native 适配层因此给每个 tab 体包一层 `height:100%` 的列 flex 宿主(`sidebar.module.css` 的 `.nativeTabHost`),tab 组件根继续用 `flex:1`/`height:100%`——否则根盒塌成内容高度,sidechat 的输入框就贴不到面板底。全局面板(根级 `main` keyed 槽 + `sidebar.panellist` + `ctx.layout.selectPanel/beginNavigation`、`rightbar` 改根级并新增 `rightbar.session`)插件**不接入**,只做兼容。底部工作台的中心列锚点改认 `[data-slot="main.conversation"]`(并跳过 `display: contents` 祖先,同时保留 alpha.1 的 `[data-slot="conversation"]`);文件地址语法跟随 alpha.2(`fileAddressFor` 一律 session 作用域、绝对路径保留前导 `/`、`parseFileAddress` 前缀解析并去 `?`/`#`)。**rc.1 起(v0.19.0)**:`SidebarRightGuideEntry.description` 回归(可选),插件恢复 `TabDescriptor.description`,但**宿主的原生指南只在列出的条目 ≤ 4 条时渲染说明**(上游 `MAX_DESCRIBED_ENTRIES = 4`;更长的列表是整列丢弃,不是截断)——插件默认贡献 4 个 guide 条目(文件 / 文件变动 / 任务管理 / 侧边对话;终端与浏览器在 0.20.0 交还宿主),**与宿主自己的终端 / 浏览器条目合计后仍可能超过 4 条**,此时说明不渲染,只有读者在插件设置页关掉足够多 tab 类型把总数压到 ≤ 4 条时才出现;插件**不恢复**旧的 `nativeGuideDesc` 通用兜底句(宿主自己没有兜底,通用句是噪音),没声明说明的 descriptor 就不发 `description` 字段;条目缺 `icon` 时由宿主补方块占位。 -11. **自定义种子必须带 fork 标记对(`meta.isSeeded: true` + `inheritedEventCount`)**:dsh-session 契约「只给 seed 不标 isSeeded,种子算重放历史而非继承前缀」——缺标记时 `Session.ownEvents()` 含整个种子,子会话的 inbox 折(`inboxProjectionDefinition`,0.1.5 起 `@deepseek-ai/dsh-agent` 只导出 `Inbox` 接口,实现是该投影)重放种子里的 `agent/inbox/spliced`,**继承父会话切割时刻未领取的 inbox 输入**(排队用户消息 next-turn、长回合中工具结果上下文/steering next-step——后者即「上下文很长时侧边对话先把之前的 User msg 发出去」的根因,幽灵消息排在 boundary 之前最先发给模型)。宿主 `session.fork`(api-session-controller)的调用即规范形态;回归由 `tests/sidechat-seed-validation.spec.ts`(真实 `Session.create` + 对 `ownEvents()` 跑 inbox 折)守护。0.1.5 复评:无更优雅的 sidechat 宿主 API(`ContinuableStartSpec` 仍无 seed 字段),`AgentRegistry.create` 接缝补齐标记即为规范用法;`Session.create` 的 header `version` 必须用 `SESSION_FORMAT_VERSION`(0.1.5 是 `3` 字面量类型),勿钉 `0`。 +2. **Remote gateway 斜杠 RPC(唯一方言)**:`POST /api/workspace/create`,payload 恰为 `{args: {...}}`,**args 按控制器 TS 参数名包装**(`workspace/create`、`session/create` → `{args:{request:{...}}}`;`session/list` 参数名 `_request` **不可省略**——`{}` 也被拒 `args fields do not match the descriptor`);envelope `method` 与路径一致,点分路径 404。请求由 `tests/e2e/host-protocol.ts` 的 `rpcAttempt` 构造、`tests/e2e-host-protocol.spec.ts` 锁定。 +3. **`MarkdownText` labels 嵌套契约**:必填 `labels: { code: { copyLabel, copiedLabel }, footnotes }`(漏传回退硬编码中文)。四个渲染点(mermaid.tsx / MarkdownHtml.tsx / TextEditor.tsx / SideChatView.tsx)统一走 `src/client/markdown-labels.tsx` 的 `markdownTextProps()`。**0.1.7 逐字未变**。 +4. **侧边对话转录走自有路由,不碰客户端宿主 RPC**:`ctx.connection.api`(含 `sessions.history`)在 alpha.1 整体移除,继任 `session/follow|page` 又对 `origin:'subagent'` 会话强制 subagent 地址(普通 `{kind:'session'}` 被 `agent-busy` 拒)且分页 `throughSeq` 不得超当前游标。转录因此由 **`sidechat.events`** 插件路由供给(`src/sidechat-routes.ts`:live 读 `agent.session.snapshotEvents()`、冷读 `sessionPersistence.inspect`,服务端 `session/end-seed` 切割 + `afterSeq` 增量)。**0.1.5 起实时增量不在日志里**:`assistant/chunk` 事件被删除,进行中的模型增量改由 `agent/assistant-stream` 瞬时帧发布(`start`/`chunk`/`end`,不入会话日志),结算时才落 `assistant/message` 或 `assistant/attempt`。插件在宿主侧 `src/assistant-live.ts` 折叠这些帧成有界缓冲。**0.1.7 复查:`packages/core/agent/src/runtime-types.ts` 逐字未变,无需调整。** +5. **`dsh-subagent` 的 `SUBAGENT_DESCRIPTOR_VERSION` 仍是 3**(0.1.7 复查:`descriptor.ts` 两 tag 同值)。sidechat 种子的 `subagent/descriptor` 版本由宿主包盖章,插件不硬编码;测试断言跟随常量(`tests/sidechat-routes.spec.ts`),勿钉字面量。 +6. **`@deepseek-ai/dsh-client-runtime` 包已消亡**(继任 seed 是裸名 `dsh-client-store`,无 `/client` 子路径):peerDependencies、devDependencies、`dsh.client.inject`、chunk externals 白名单(`src/client/chunk-loader.ts` / `tsdown.config.ts` / `tests/chunk-loader.spec.ts` / `tests/manifest-consistency.spec.ts` 四处同步)均已无该条目。 +7. **e2e scratch profile 的 `minimumReleaseAgeExclude` 含 `'@deepseek-ai/*'`**(`scripts/e2e-mount.sh` / `e2e-aggregate-mount.sh`,与仓库根 `pnpm-workspace.yaml` 同策):alpha 版本常在发布后 24h 内跑 lane,pnpm 11 的 `minimumReleaseAge` 默认会拒装新鲜包。 +8. **插件开发树的 dsh-* 传递 peer 需提升为 devDependencies**(alpha.3 首见):`dsh-subagent` 等 npm 包把 `dsh-attachment` 等 dsh-* 姊妹包声明为 peerDependencies(由宿主 bundle 树统一提供,宿主侧无此问题),插件仓库若只直接依赖其中一部分,其余 peer 在 pnpm 下会解析到树上残留的旧版——如 `dsh-attachment@0.1.1-rc.1` 缺 `admitPromptContent` 导出、`dsh-subagent` 产物 import 它时测试加载即崩。因此 devDependencies 需涵盖 dev 树实际触达的全部 peer(attachment / scope / session-projection / system-prompt / user-approval / util-time 等,与直接依赖同款精确钉版);**`@deepseek-ai/cordis` peer 自 alpha.3 起要求 `^4.0.2`,0.1.7 起要求 `^4.0.3`**。适配新版本时先跑 `pnpm peers check`,把新失配的传递 peer 一并提升进 devDependencies。**0.1.7 新增的同类问题**见 §3.3 第 12 条(`ui-primitives` 的第二层裸 import)。例外:**`dsh-client-locale` 的 peer/devDep 允许落后于基线**的历史已消除,rc.1 起上游恢复发版,0.1.7 起与 DSH 同 tuple。 +9. **右侧栏是 DSH 原生栏,插件只提供 tab 类型**:聊天里一切文件打开(工具行 / 产物行 / 正文提及 / 行内代码路径)统一走 `ctx.sidebarRight.openResource(fileAddressFor(sessionId, cwd, path))`。插件的每个 `TabDescriptor` 注册成原生 tab 类型(`kind = descriptor.id`,`extension` 带)+ 原生 tab 体(`sidebar.right.pane.tab` keyed 槽,key = `dsh-better-sidebar:`);`editor` 类型同时认领 `dsh-resource://file/**`(**但按 §3.2 第 5 条的清单让出一部分**),并接管内置 `files` 页面 kind(`openTab('files')` 打开插件文件树,注销即复位)。`ctx.sidebarRight.openTab/openResource/close` 只对**在屏会话**写入;跨会话用具体类上的 `openTabIn/openResourceIn/closeIn`。原生 tab 的插件侧状态在 `src/client/native/tab-adapter.tsx`。**注册必须等服务、不能等槽声明**:原生栏先声明 `sidebar.right.pane.tab` 再 `provide('sidebarRightTabs')`,真机 profile 上槽声明回调里 `ctx.get('sidebarRightTabs')` 仍是 undefined(实测 3 秒后才出现),按槽触发注册会静默什么都不注册且永不重试——用 `ctx.inject(['sidebarRightTabs'], cb)` 驱动类型注册(`tests/native-surface.spec.ts` 以「槽先触发、服务后到达」的顺序守护)。**0.1.6-alpha.2 起**:`SidebarRightGuideEntry.id` 必填且同 provider 内唯一(缺它会静默清空整个承载面);同 provider 的 guide 条目是**追加**关系,`chain` 那种「替换宿主条目」的语义只存在于 `conversation.chat.turnTail` 且已被上游改成 `list`。**0.1.7 起**:`sidebar-right` 本体的契约**零破坏**——槽名无改名、`SidebarRightTabDefinition` 没有新的必填字段、只有新增的可选 `keepMounted` 与 `ISidebarRight.mounted`,`openTabIn/openResourceIn/closeIn` 在具体 class 上字节不变;`tab.visible` 语义收紧为 `active && (...)`(全局面板/后台会话时 body 不挂载);持久化校验收紧(非根 / 列轴 / 嵌套 / 3+ docked pane 的 split 一律判非法**整份丢弃**,0.1.6 的 sidebar 自己产不出这种布局);`RightbarRoot` 现在为每个保留会话渲染一个隐藏 seat(`SidebarSessionViews`,未导出、插件拿不到)。原生 tab 体宿主 `.paneBody` 是**有确定高度的块级滚动容器**(非 flex 容器),native 适配层因此给每个 tab 体包一层 `height:100%` 的列 flex 宿主(`sidebar.module.css` 的 `.nativeTabHost`),tab 组件根继续用 `flex:1`/`height:100%`。全局面板(根级 `main` keyed 槽 + `sidebar.panellist` + `ctx.layout.selectPanel/beginNavigation`、`rightbar` 改根级并新增 `rightbar.session`)插件**不接入**,只做兼容。底部工作台的中心列锚点认 `[data-slot="main.conversation"]`(并跳过 `display: contents` 祖先,同时保留 alpha.1 的 `[data-slot="conversation"]`);文件地址语法跟随 alpha.2(`fileAddressFor` 一律 session 作用域、绝对路径保留前导 `/`、`parseFileAddress` 前缀解析并去 `?`/`#`)。**rc.1 起**:宿主原生指南只在列出的条目 ≤ 4 条时渲染说明(上游 `MAX_DESCRIBED_ENTRIES = 4`;更长的列表是整列丢弃,不是截断)——插件默认贡献 4 个 guide 条目(文件 / 文件变动 / 任务管理 / 侧边对话),**与宿主的终端条目合计后可能超过 4 条**,此时说明不渲染;插件**不恢复**旧的 `nativeGuideDesc` 通用兜底句。 +10. **自定义种子必须带 fork 标记对(`meta.isSeeded: true` + `inheritedEventCount`)**:dsh-session 契约「只给 seed 不标 isSeeded,种子算重放历史而非继承前缀」——缺标记时 `Session.ownEvents()` 含整个种子,子会话的 inbox 折重放种子里的 `agent/inbox/spliced`,**继承父会话切割时刻未领取的 inbox 输入**。宿主 `session.fork`(api-session-controller)的调用即规范形态;回归由 `tests/sidechat-seed-validation.spec.ts`(真实 `Session.create` + 对 `ownEvents()` 跑 inbox 折)守护。**0.1.7 复查:`Session.create` 的五个参数与函数体逐字相同**;上游新增了 `buildForkSeed(events, boundary)` 导出,与本插件手工的 `buildSidechatInheritance` 职责重叠但**收益不明确**(插件还有 dangling-tool-call 退回结构化快照的自有需求),暂不替换。 +11. **`dsh.plugin.json` 从来不是 DSH 契约**:0.1.7 全树 grep 仍 **0 命中**。DSH 真正读的是 `package.json` 的 `dsh.bundle.patch` + `dsh.client`(`platform`/`inject`/`immediately`/`external`)+ `exports["./client"]`。`dsh.plugin.json` 只服务第三方 registry(社区市场)通道,`lib/client-registry.js` 同理;`tests/manifest-consistency.spec.ts` 守护的是这条通道的形状,不是 DSH 契约。**`dsh.manifestVersion` 也仍是「只声明、零读」**(0.1.7 的 `package-manifest` README 自己写明「Current installers and loaders do not enforce `dsh.manifestVersion` or `engines.dsh`」)。0.1.7 的官方管控面新增:`plugin-manager` 的**多 registry 回退**(`registryPlan()`:配置源 → `fallbackRegistries`(默认含 `registry.npmmirror.com`),`network`/`timeout`/`not-found`/`no-matching-version` 会自动问下一个源;`registry` 入参只存在于 Web 对话框与 `plugin_manager` 工具,**CLI 没有 registry flag**)、`waitForInstall`、以及**插件展示元数据** `package.json#icon` + `locale/.json`(全新面,本插件未采纳)。`dsh.bundle.patch` 现在多了形状校验(非法值会 `throw`,引导期跳过该 bundle),本插件是合法的单字符串。 --- @@ -89,4 +102,4 @@ - **接入 API 全参考**:[docs/external-plugin-guide.md](docs/external-plugin-guide.md)(消费插件开发者向;§0 原生栏承载面 / §4 Tab API / §5 FileViewer API / §7 服务方法 / §10 平台陷阱 / §11 已移除的自由窗口 / §12 皮肤契约 / §15 真实案例)。 - **设计文档**:[docs/plans/](docs/plans/)(30+ 份逐特性设计,含实施偏差记录)。 -- **关键测试守护**:`tests/service.spec.ts` / `builtins.spec.ts`(注册表与内置清单:**5 tab + 6 viewer**——终端与浏览器已交还宿主)/ `market-manifest.spec.ts`(市场约束,含 `pnpm pack` 后的产物面)/ `manifest-consistency.spec.ts`(registry 通道形状)/ `e2e-host-protocol.spec.ts`(RPC 双协议)/ `native-surface.spec.ts`(原生右侧栏承载面,含 guide 条目 id 唯一性)/ `theme.spec.ts`(皮肤契约,含空白面板胶囊几何)/ `plugin-list.spec.ts`(推荐插件目录)/ `fs-search.spec.ts`(host 文件名搜索)。 +- **关键测试守护**:`tests/service.spec.ts` / `builtins.spec.ts`(注册表与内置清单:**5 tab + 3 viewer**——终端与浏览器已交还宿主,只读预览也按 §3.2 第 5 条让出,只剩 markdown / html / code)/ `market-manifest.spec.ts`(市场约束,含 `pnpm pack` 后的产物面)/ `manifest-consistency.spec.ts`(registry 通道形状)/ `e2e-host-protocol.spec.ts`(RPC 双协议)/ `native-surface.spec.ts`(原生右侧栏承载面,含 guide 条目 id 唯一性)/ `theme.spec.ts`(皮肤契约,含空白面板胶囊几何)/ `plugin-list.spec.ts`(推荐插件目录)/ `fs-search.spec.ts`(host 文件名搜索)/ `fs-watch.spec.ts`(文件树目录 watch 的去抖、句柄上限与错误路径)/ `smoke.spec.ts` 的 settings 段(按 entry id 读写表单、**一次性 legacy 迁移**、open 工具门控)。 diff --git a/README.md b/README.md index 000a323..3cbf3aa 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ > [!IMPORTANT] > **已适配 DSH 原生侧边栏 API**(v0.19.0 起):右列就是 DSH 自己的右侧栏——插件的每个 tab 类型与 tab 体通过 `ctx.sidebarRightTabs` / `ctx.sidebarRight` 注册与打开,聊天里的文件打开统一走 `ctx.sidebarRight.openResource('dsh-resource://file/…')`,插件**不再自绘右侧面板**(旧的浮窗能力同步移除)。自绘的底部工作台与开放给其他插件的 `ctx.betterSidebar` 服务保持不变,接入方式见[插件接入指南](docs/external-plugin-guide.md)。 > -> **v0.20.0 起宿主支持下限是 DSH `0.1.6-alpha.2`**:DSH 该版本内置了自己的右侧栏**终端**与**浏览器**,因此插件**删掉了自研终端**(xterm + node-pty 整套)并把**浏览器视图让给内置**,只保留按协议分流的链接接管。0.1.5-rc.* 及更早用户请停留在 **v0.19.1**。 +> **v0.21.0-alpha.1 起宿主支持下限是 DSH `0.1.7-alpha.1`**(peer 下限 `^0.1.7-alpha.1`;npm dist-tag `alpha`,`latest` 仍是 **v0.19.1**)。DSH 0.1.7 自带完整的文档预览(表格 / PDF / 图片 / Office),因此插件**把只读预览整体让给内置**(只保留 Markdown / HTML / 可编辑的代码编辑器)、**把外链接管收敛为「只认领声明了 `urlTarget` 的链接」**(按协议分流的三个外链接管设置项已删除),并**重写了设置接入面**(偏好迁到 profile 里本插件的挂载行,旧的 `settings.yaml` 段在首次启动时自动回迁);文件树同时获得**实时刷新**。**0.1.6-alpha.2 及更早的用户请停留在 v0.19.1**——注意 **0.20.0 这一版从未发布到 npm**,这些变更全部落在 v0.21.0-alpha.1。
@@ -14,7 +14,7 @@ GitHub stars License: MIT dshfind

- 支持的 DSH 版本(v0.20.0 正式版):0.1.6-alpha.2+ + 支持的 DSH 版本(v0.21.0-alpha.1):0.1.7-alpha.1+ 插件生态:GitHub topic dsh-better-sidebar

文件管理 编辑预览 内置浏览器 文件变动 后台任务 侧边对话 插件接入

右侧栏 + 底部面板双工作台,并把 ctx.betterSidebar 服务开放给所有插件——
@@ -45,10 +45,10 @@ ## ✨ 功能一览 -- **🗂️ 文件工作台**:资源管理器(懒加载目录树;软链接按目标类型展示——目录软链接可展开、失效链接标红;文件树与文件 tab 按扩展名显示图标——markdown / 图片 / PDF / 代码 / 配置 / 压缩包等各有 glyph,插件可经 `registerFileIcon` 注册自定义图标与目录图标)+ CodeMirror 编辑器;图片 / Markdown(含 Mermaid 图表,strict 安全渲染 + 点击放大;README 级内嵌 HTML——徽章墙 / `
` 折叠 / 表格内联标签经 DOMPurify 消毒真实渲染;浮动目录大纲一键跳转)/ HTML / PDF -- **🌐 内置浏览器 + 链接分流**:网页 tab 由 **DSH 内置**的 `ui-sidebar-browser` 提供(多开 / 后退前进刷新 / 沙箱 iframe);插件保留聊天与界面里的**外链接管**——HTTP 在侧边栏打开、HTTPS 走系统浏览器(设置页可分别调整),这是宿主自身不提供的按协议分流 -- **💻 终端(DSH 内置)**:右侧栏终端由 **DSH 内置**的 `ui-sidebar-terminal` 提供(shell 选择 / 重命名 / 断线重连 / 刷新后恢复 / 主题跟随)。插件不再自带 xterm + node-pty 实现 -- **📂 模型侧边栏打开(可选)**:全局设置开启后注入 `sidebar_open` 工具——模型可主动在侧边栏打开文件 / 文件夹(树以该目录为根)/ HTTP(S) 网页 +- **🗂️ 文件工作台**:资源管理器(懒加载目录树,**展开的目录由宿主按目录 watch、改动后自动重列**;软链接按目标类型展示——目录软链接可展开、失效链接标红;文件树与文件 tab 按扩展名显示图标——markdown / 图片 / PDF / 代码 / 配置 / 压缩包等各有 glyph,插件可经 `registerFileIcon` 注册自定义图标与目录图标)+ **可编辑**的 CodeMirror 编辑器;Markdown(含 Mermaid 图表,strict 安全渲染 + 点击放大;README 级内嵌 HTML——徽章墙 / `
` 折叠 / 表格内联标签经 DOMPurify 消毒真实渲染;浮动目录大纲一键跳转)与 HTML(沙箱 iframe + 两个宿主没有的逃生门开关)仍由插件渲染 +- **🌐 浏览器与文档预览(由 DSH 内置提供)**:网页 tab 是宿主自己的 `ui-sidebar-browser`(多开 / 后退前进刷新 / 沙箱 iframe,**0.1.7 起只在 desktop profile 挂载**);表格 / PDF / 图片 / Office 预览是宿主自己的 `ui-sidebar-documentpreview`(宿主侧 Office→PDF 转换、电子表格 worker 表格、图片 / PDF 缩放视口、按目录自动刷新)。插件**不再认领**这些格式,只保留宿主没有的那一半:**外链接管**——只认领有 tab 类型通过 `urlTarget` 明确声明的链接,其余一律放行给宿主(正文链接的去向由宿主用户设置 `linkOpening` 决定) +- **💻 终端(DSH 内置)**:右侧栏终端由 **DSH 内置**的 `ui-sidebar-terminal` 提供(shell 选择 / 重命名 / 断线重连 / 刷新后恢复 / 主题跟随)。插件不再自带终端实现 +- **📂 模型侧边栏打开(可选)**:全局设置开启后注入 `sidebar_open` 工具——模型可主动在侧边栏打开文件 / 文件夹(树以该目录为根)/ HTTP(S) 网页(网页 tab 需要宿主提供 `browser` kind,即 desktop profile) - **🌿 文件变动**:Git 视角(真 diff / 历史 / 暂存·提交·还原 / worktree·子仓库选择)与本轮文件视角(模型读 / 写 / 编辑实时追踪,按文件分组、按类型筛选)**双视角合一**;统一 diff 渲染(改蓝配对 + 行内字符级高亮 + 语法着色(含 mjs/cjs/mts/cts、CSS/SCSS/Less、HTML/XML/SVG/Vue、GraphQL、JSONC/JSON5)+ 上下文折叠),底部可拖拽预览面板,可一键展开为独立 diff tab(落进工作台的 diff 分栏);`.md` 操作(读 / 写 / 编辑)预览头部可切换**阅读模式**——经共享 MarkdownText 渲染 GFM 表格 / 任务列表 / 删除线 / 脚注 / 数学公式,本地图片自动改写为 `/sidebar/file` 媒体路由;含 ```mermaid 围栏时走编辑器同款懒加载 mermaid 渲染器(图可点击缩放 / 平移);**敏感内容脱敏**——凭据形态路径整文件遮罩、普通文件按内容形态遮值(api_key: / Bearer / sk- / AKIA / ghp_ / PEM 等,字段名保留),默认开启、预览面板一键开关(localStorage 记忆),仅影响显示、不改会话数据。已知边界:mermaid 无引号节点标签含被遮密钥时,图回退源码(规避:标签加引号);`.html` 操作(读 / 写 / 编辑)预览头部可切换**渲染模式**——复用编辑器同款 `/sidebar/html` 路由 iframe,相对资源(./style.css、img/x.png)同路由解析,分段读取也渲染完整文档,恒定沙箱(opaque origin + CSP 头,无逃生门);`.pdf` 操作(读 / 写 / 编辑)同样可切换**渲染模式**——复用编辑器同款 PDF 预览(媒体路由字节流 + 显式 Blob,浏览器原生查看器内嵌,附下载入口) - **🧩 后台任务页**:subagent 拓扑 + 后台任务(退出码 / 实时输出 / 强制终止) - **💬 侧边对话(beta)**:Codex 风格的侧边线程——继承主会话完整上下文(含进行中的回合与工具调用)独立运行,不进入主会话;线程内可持续追问,一键「保存为新会话」提升为顶层会话 @@ -58,22 +58,22 @@ - **⚡ 按需加载**:启动只拉 ~325KB 核心,编辑器 / Mermaid 图表 / 第三语言词典等重依赖用到才按需拉取([设计文档](docs/plans/2026-08-12-lazy-chunks-design.md)) - **🌏 多语言**:界面文案跟随 DSH 语言(zh / en)实时切换;安装 `@huanlin/dsh-plugin-better-locale` 后支持日语(ja)等第三语言覆盖(见下方「🌏 第三语言覆盖」) -> 🔌 **核心理念**:服务优先——内置的 8 tab + 6 viewer 与第三方插件通过同一套 `ctx.betterSidebar` API 注册,能力完全对等;官方不再内置、可由生态提供的功能,交由生态插件实现(已有 **28+ 生态插件**,见下方「🌐 插件生态」)。接入文档见「🔌 服务化扩展」与 [外部插件接入指南](./docs/external-plugin-guide.md)。 +> 🔌 **核心理念**:服务优先——内置的 5 tab + 3 viewer 与第三方插件通过同一套 `ctx.betterSidebar` API 注册,能力完全对等;官方不再内置、可由生态提供的功能,交由生态插件实现(已有 **28+ 生态插件**,见下方「🌐 插件生态」)。接入文档见「🔌 服务化扩展」与 [外部插件接入指南](./docs/external-plugin-guide.md)。 ## 🚀 安装 **前置**:已装好 DSH(`dsh web` 能正常运行),Node.js ≥ 20、pnpm ≥ 10。 **支持的 DSH 版本**: -支持的 DSH 版本(v0.20.0 正式版):0.1.6-alpha.2+ +支持的 DSH 版本(v0.21.0-alpha.1):0.1.7-alpha.1+ -> 📌 **正式版**:`v0.20.0` 起适配 DSH **0.1.6-alpha.2+**(npm dist-tag `latest`;peer 下限 `^0.1.6-alpha.2`)。**下限必须动**:semver 的预发布规则让 `^0.1.5-rc.1` 在数学上永远匹配不到任何 `0.1.6` 预发布版。仍停在 DSH 0.1.5-rc.*(含 npm `latest` 的 0.1.5-rc.2)的用户请固定安装 **`dsh-better-sidebar@0.19.1`**——0.1.6 有两个真实破坏契约,本版不写运行时兼容层;DSH 0.1.5-alpha.2 及更早同样请用旧版(`0.19.0-alpha.1` / `0.18.x` / `0.17.1`)。 +> 📌 **通道与支持线**:`v0.21.0-alpha.1` 是 **alpha 通道**(npm dist-tag `alpha`),适配 DSH **0.1.7-alpha.1+**(peer 下限 `^0.1.7-alpha.1`,CI 钉 `@deepseek-ai/dsh@0.1.7-alpha.1`)。**npm `latest` 仍是 `v0.19.1`**——注意 `v0.20.0` 这一版**从未发布到 npm**,本仓库从 0.19.1 直接跳到这条 0.21 开发线。**下限必须动**:semver 的预发布规则让 `^0.1.6-alpha.2` 在数学上永远匹配不到任何 `0.1.7` 预发布版。**DSH 0.1.6-alpha.2 及更早(含 npm `latest` 的 0.1.5-rc.2)的用户请固定安装 `dsh-better-sidebar@0.19.1`**——0.1.7 的破坏面足够大(`dsh-settings` 整体重写、`ui-primitives` 图标具名导出整族改名、会话格式 v3→v4),本版不写运行时兼容层;DSH 0.1.5-alpha.2 及更早同样请用旧版(`0.19.0-alpha.1` / `0.18.x` / `0.17.1`)。 ```sh -dsh plugin --profile web add dsh-better-sidebar@latest +dsh plugin --profile web add dsh-better-sidebar@alpha ``` -> v0.20.0 起插件不再依赖任何需要构建脚本的包(`node-pty` 已随自研终端删除),所以安装**一步到位**,不再需要 `pnpm approve-builds` 那轮重跑。DSH 0.1.6 也自带 Web 侧的 **Plugins 页面**(`ui-plugin-manager`)与 `plugin_manager` 工具,装完后可以直接在那里启停。 +> **装 `@alpha`,不要装 `@latest`**:`latest` 还停在 `v0.19.1`(只支持 DSH 0.1.5/0.1.6 线),而本版在 `alpha` 通道上。本版插件不依赖任何需要构建脚本的包(终端连同 `node-pty` 已整体交还 DSH),所以安装**一步到位**,不需要 `pnpm approve-builds` 那轮重跑。DSH 也自带 Web 侧的 **Plugins 页面**(`ui-plugin-manager`)与 `plugin_manager` 工具,装完后可以直接在那里启停。 装完**硬刷新浏览器**(Cmd/Ctrl+Shift+R)即可看到侧边栏(DSH 对 client 改动热加载,无需重启;仅 host 半更新时需要重启)。 @@ -81,7 +81,7 @@ dsh plugin --profile web add dsh-better-sidebar@latest ```text 帮我安装 dsh-better-sidebar 插件(DSH 侧边栏工作台),步骤: -1. 执行 dsh plugin --profile web add dsh-better-sidebar@latest(v0.20.0 起一步到位,无需构建脚本放行) +1. 执行 dsh plugin --profile web add dsh-better-sidebar@alpha(注意是 alpha 通道;latest 停在只支持 DSH 0.1.5/0.1.6 的 0.19.1,装它没用) 2. 完成后提醒我硬刷新浏览器(Cmd/Ctrl+Shift+R) 遇到报错先查 https://github.com/omdsh-dev/DSH-better-sidebar README 的常见问题表。 ``` @@ -92,7 +92,7 @@ dsh plugin --profile web add dsh-better-sidebar@latest 更新 ```sh -dsh plugin --profile web add dsh-better-sidebar@latest +dsh plugin --profile web add dsh-better-sidebar@alpha ``` 也可把 `~/.dsh/profiles/web/package.json` 里的版本号改高后 `pnpm install`。改完**硬刷新浏览器**(Cmd/Ctrl+Shift+R)即可(client 改动无需重启 DSH)。 @@ -104,12 +104,13 @@ dsh plugin --profile web add dsh-better-sidebar@latest | 现象 | 原因与解决 | |---|---| -| 报 `Ignored build scripts` | pnpm 11 拦截了某个传递依赖的构建脚本。在 profile 目录(`~/.dsh/profiles/web`)跑 `pnpm approve-builds` 按提示放行——v0.20.0 起本插件自身不再有构建脚本依赖。 | +| 报 `Ignored build scripts` | pnpm 11 拦截了某个传递依赖的构建脚本。在 profile 目录(`~/.dsh/profiles/web`)跑 `pnpm approve-builds` 按提示放行——本插件自身已无构建脚本依赖(终端删除后 `node-pty` 不在依赖里)。 | | 报 `minimum release age` / 版本不足 24h | 装的版本发布不足 24 小时。等 24h 或重跑一次(pnpm 会自动补 `minimumReleaseAgeExclude`)。 | | 报「找不到 profile 目录」 | 先跑一次 `dsh web`,让它初始化 `~/.dsh/profiles/web`。 | | 页面出现**两个侧边栏** | 双挂载。旧的手动挂载行:`~/.dsh/profiles/web/cordis.patch.yml` 还留着 `- insert: ... better-sidebar ...`,删掉那段(同 id 重复挂载 loader 会直接报 `duplicate loader entry id`)。聚合包(如 `@linxin666/dsh-web-ui-all`)以**不同 id** 挂载本包时,0.13.x 起插件自身 bundle patch 会自动退让(检测到已有启用中的同包名挂载就不挂自己),无需手动处理;若仍双挂载,先确认聚合包的 bundle 顺序在 `dsh-better-sidebar` 之前。 | -| 终端无法使用 / 提示 shell 启动失败 | 终端与 `node-pty` 现在都由 **DSH 自身的 `ui-sidebar-terminal`** 提供(本插件 v0.20.0 起不再自带)。遇到问题请查 DSH 侧文档;若报错提到构建脚本,见上一行。 | -| 提示 `dsh: command not found` | 先安装 DSH;或直接用 `npx -y --package @deepseek-ai/dsh dsh plugin --profile web add dsh-better-sidebar@latest`。 | +| 升级后设置页的值去哪了 | DSH 0.1.7 删除了插件可注册的设置命名空间:偏好现在写在 profile 里本插件的**挂载行**上(默认 entry id `better-sidebar`),不再是 `~/.dsh/settings.yaml`。插件会在首次启动时把旧 `settings.yaml`(已被宿主改名为 `settings.yaml.imported`)里 `dsh-better-sidebar` 段一次性回迁,只迁移当前 schema 仍声明的字段、且只在该行还没有用户值时执行,不会覆盖升级后新设的值。 | +| 终端无法使用 / 提示 shell 启动失败 | 终端由 **DSH 自身的 `ui-sidebar-terminal`** 提供(本插件不再自带终端与 `node-pty`,也没有终端相关设置项)。遇到问题请查 DSH 侧文档;若报错提到构建脚本,见上一行。 | +| 提示 `dsh: command not found` | 先安装 DSH;或直接用 `npx -y --package @deepseek-ai/dsh dsh plugin --profile web add dsh-better-sidebar@alpha`。 |
@@ -122,20 +123,17 @@ dsh plugin --profile web add dsh-better-sidebar@latest 1. git clone https://github.com/omdsh-dev/DSH-better-sidebar.git ~/Code/DSH-better-sidebar cd ~/Code/DSH-better-sidebar && pnpm install && pnpm build 2. ~/.dsh/profiles/web/package.json 的 dependencies 写 "dsh-better-sidebar": "link:<克隆目录绝对路径>" -3. ~/.dsh/profiles/web/cordis.patch.yml 追加挂载行(需要指定终端 shell 时,在行内加 `config.shell`;`config.shellArgs` 可带参启动,非空时替换默认的 `-l`。不填则自动解析 `$SHELL` / 登录 shell / powershell.exe): +3. ~/.dsh/profiles/web/cordis.patch.yml 追加挂载行(这一行的 `config` 就是本插件的设置表单:部署限额 `readLimit` / `mediaLimit` / `uploadLimit` / `listLimit` 加用户偏好字段,设置页写的就是它;不写则全部用 schema 默认值): - insert: - id: better-sidebar name: 'dsh-better-sidebar' config: - shell: /bin/zsh - shellArgs: - - --noprofile - - --no-rc + readLimit: 524288 4. 在 ~/.dsh/profiles/web 执行 pnpm install 5. 硬刷新浏览器(Cmd/Ctrl+Shift+R)即可看到效果(client 改动无需重启 DSH;host 半改动才需重启) ``` -更新:`git pull && pnpm install && pnpm build` → 硬刷新浏览器即可(client 改动热加载生效,无需重启 DSH;host 半改动才需重启)。切回 npm 通道时,把依赖改回 `"dsh-better-sidebar": "^0.16.1"` 再 `pnpm install`。 +更新:`git pull && pnpm install && pnpm build` → 硬刷新浏览器即可(client 改动热加载生效,无需重启 DSH;host 半改动才需重启)。切回 npm 通道时,把依赖改回 npm 上的对应版本(稳定线 `"^0.19.1"`;本 alpha 线 `"^0.21.0-alpha.1"`)再 `pnpm install`。
@@ -162,16 +160,16 @@ dsh registry enable dsh-external/dsh-better-sidebar | | | |---|---| -| **🗂️ 文件工作台:资源管理器**
支持两种格式的资源管理器:内嵌在文件预览中 / 独立显示文件树。懒加载目录树、软链接按目标类型展示(目录软链接可展开、失效链接标红)、全局文件名搜索、上传文件/文件夹与拖放上传、右键菜单(在新 Tab 打开 / 在侧边打开 / 复制路径)、悬浮 `@文件` 一键引用进输入框。
文件资源管理器
| **📝 Markdown · 图片 · PDF 内联预览**
Markdown 预览支持 **Mermaid 图表**(`securityLevel: 'strict'` 安全渲染 + 二次清洗;点击图表弹窗放大、滚轮缩放、拖拽平移)、**README 级内嵌 HTML**(徽章墙 `
`、`
` 折叠块内嵌 markdown、表格单元格内联标签——DOMPurify 白名单消毒真实渲染,`