docs: record the 0.1.6-alpha.2 adaptation and resync the user docs
Adds the per-version adaptation record (docs/plans/2026-09-21-…-adaptation.md), rewrites AGENTS.md §3 for the new baseline (the two silent contract breaks, the terminal/browser handoff, the new official plugin-manager surface, the three ui-primitives breaks, the widened primitives bare-import set), and updates both READMEs and the integration guide for the v0.20.0 feature set. Also retires the support text the terminal deletion made false: the install flow is one step again (no build-script approval), the FAQ's two node-pty rows collapse into one pointing at DSH's own terminal, the platform note no longer promises prebuilt native binaries (there are no native deps left), and the installers drop the -Repair mode and their allowBuilds writes along with pnpm-workspace.yaml's now-empty allowBuilds block. Removes the dead browserNoSandboxWarning locale key (its setting went with the plugin's browser) and tightens theme.spec's capsule guard so a literal colour fails it. The e2e resolver now refuses a PATH that is not on the baseline line: this machine's PATH is 0.1.6-alpha.1, and running the mount lane against it fails at 'the bundle was not registered' with a zero exit from the CLI — a misleading dead end worth one explicit error instead.
This commit is contained in:
@@ -17,17 +17,35 @@
|
||||
|
||||
## 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 <tarball>`)并启动真实 `dsh web`(keyless,`--port 0`)→ `tests/e2e/mount.e2e.ts`(Playwright)断言 `[data-dsh-better-sidebar]` 挂载、无错误条/pageerror/console 错误,展开 DSH 原生右侧栏后经其 guide 页逐个打开插件 tab 类型(含终端懒加载 chunk),再经插件文件树(原生 `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 <tarball>`)并启动真实 `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 宿主路由烟测。
|
||||
|
||||
本地:`pnpm build && pnpm pack && pnpm exec playwright install chromium && pnpm test:mount`。CI 钉 `@deepseek-ai/dsh@0.1.5-rc.2`(npm `next`;`latest` 仍是 rc.1;peer 下限 `^0.1.5-rc.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=2`)而非 `pnpm test`——多个 spec 真起进程(`agent-pty` / `pty-deps` / `pty-helpers` / `install-powershell` / `smoke`),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.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 整体替换默认值)。
|
||||
|
||||
---
|
||||
|
||||
## 3. DSH 0.1.5 适配要点(0.1.5-rc.1+ 基线)
|
||||
## 3. DSH 0.1.6-alpha.2 适配要点(0.1.6-alpha.2+ 基线)
|
||||
|
||||
**v0.19.0(正式版,npm `latest`)起仅支持 DSH 0.1.5-rc.1+**(peer 下限 `^0.1.5-rc.1`,CI 钉 `@deepseek-ai/dsh@0.1.5-rc.2`)。**v0.19.1 把钉版推进到 rc.2 而 peer 下限不动**:rc.1 → rc.2 的 delta 里没有任何触及本插件的面(零 `packages/api|host|session|agent` 变更,真正的代码改动只有消息反馈弹窗、产物卡片 CSS、`CodeFileIcon` 的 SVG 数据拆分),`^0.1.5-rc.1` 天然容纳 rc.2,rc.1 用户无需升级即可用新版插件。0.1.5-alpha.2 用户停留在 **v0.19.0-alpha.1**(npm `alpha`)——rc 线的 delta 都很小,插件不再对 alpha 线做运行时兼容;0.1.2-rc.1 稳定线用户继续用 **v0.18.1**——0.1.5 的会话事件模型与文件打开漏斗都变了,插件不再对 0.1.2 做运行时兼容。发版:release.yml 按版本号是否含 `-` 自动选 `alpha`/`latest` dist-tag(0.19.1 无后缀 → `latest`;npm `alpha` 标签仍指向 `0.19.0-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`)。
|
||||
|
||||
宿主契约(均经真机挂载冒烟 14/14 验证):
|
||||
**本版新增的两条硬契约(两者都是静默失败,已在 v0.20.0 修好并加测试守护)**:
|
||||
|
||||
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 式「在侧边栏文件树里揭示该文件」的入口(树内右键「在文件夹中显示」仍在)。
|
||||
|
||||
**本版新增的让位(宿主内置了插件原有的两个面)**:
|
||||
|
||||
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. **原生右列布局现在会持久化**(`localStorage` `dsh.sidebar-right.v1.<sessionId>`,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`(断线标签现在**自身**命名重试动作,跟随宿主文案)。
|
||||
|
||||
宿主契约(0.1.5 线起沿用,均经真机挂载冒烟验证):
|
||||
|
||||
1. **一次性 token 鉴权**:就绪行 `dsh web: http://127.0.0.1:<port>/?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`。
|
||||
@@ -38,7 +56,7 @@
|
||||
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:<id>`);`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`;原生布局只在内存,不做持久化。**注册必须等服务、不能等槽声明**:原生栏先声明 `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 内强制去重。原生 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`(六个内置类型各声明一条说明,`guideDesc*` 词条回到 20 份词典),但**宿主的原生指南只在列出的条目 ≤ 4 条时渲染说明**(上游 `MAX_DESCRIBED_ENTRIES = 4`;更长的列表是整列丢弃,不是截断)——插件默认贡献 6 个 guide 条目(文件 / 文件变动 / 任务管理 / 侧边对话 / 终端 / 浏览器),所以**默认组合下说明不渲染**,只有读者在插件设置页关掉足够多 tab 类型、把 guide 压到 ≤ 4 条时才出现;插件**不恢复**旧的 `nativeGuideDesc` 通用兜底句(宿主自己没有兜底,通用句是噪音),没声明说明的 descriptor 就不发 `description` 字段;条目缺 `icon` 时由宿主补方块占位。
|
||||
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:<id>`);`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`。
|
||||
|
||||
---
|
||||
@@ -57,13 +75,13 @@
|
||||
## 5. 开发规则速查
|
||||
|
||||
- **构建纯度门**:client bundle 禁止 value-import `@dsh-external/*` 或非白名单 `@deepseek-ai/*`(`tsdown.config.ts` 拦截);`import type {}` 被擦除不触发——类型可共享,运行时符号不行;跨插件交互走 `ctx.betterSidebar` 方法调用。
|
||||
- **懒加载 chunk**:重依赖(xterm/CodeMirror/mermaid)在独立 bundle(`lib/client-<name>.js`),经 `/sidebar/bundle` 按需下发、`globalThis.__dshChunks__` 物化(`src/client/chunk-loader.ts`),**核心 bundle 禁止静态 import `src/client/chunks/*`**。
|
||||
- **懒加载 chunk**:重依赖(CodeMirror / mermaid / 19 份第三语言词典)在独立 bundle(`lib/client-<name>.js`,0.20.0 起只剩 `editor` / `mermaid` / `locale`),经 `/sidebar/bundle` 按需下发、`globalThis.__dshChunks__` 物化(`src/client/chunk-loader.ts`),**核心 bundle 禁止静态 import `src/client/chunks/*`**。chunk 名在 8 处镜像(`tsdown.config.ts` / `src/bundle-route.ts` / `src/client/chunk-loader.ts` / `package.json.files` / `scripts/package-registry.mjs` / `tests/{bundle-route,chunk-loader,chunk-artifact,manifest-consistency}.spec.ts`)——删一个 chunk 必须同步全部。
|
||||
- **i18n**:词典在 `betterSidebar` 命名空间,跟随 DSH `ctx.locale`;**新增 zh key 必须同步 `src/client/locales-ja.ts` 的 ja 翻译**(否则 ja 下回退 en)。渲染 `MarkdownText` 必须经 `markdownTextProps()`(§3 第 3 条)。
|
||||
- **皮肤契约**:视觉值只消费 `--dsw-alias-*` / `--dsw-font-*` / `--ds-*` 令牌,无硬编码颜色,**插件没有任何豁免面**——文件/文件夹图标是 DSH 官方 `FileTypeIcon` 的图形(宿主自己的调色板),插件画的每个 glyph(含内置 tab 彩色图标)颜色都来自令牌;`tests/theme.spec.ts` 同时守护「图标模块零颜色字面量」「样式表每条 `color` 解析到令牌」「没有图标数据被做成 chunk」。契约全文与 titleBar 四方案模型见[指南 §12](docs/external-plugin-guide.md),改动必须同步该节与 `tests/theme.spec.ts`。
|
||||
- **契约反向引用**:皮肤契约被 `src/client/shell-presets.ts`、`tests/e2e/mount.e2e.ts` 的注释以「指南 §12」引用——调整指南章节结构时同步检查这两处。
|
||||
- **接入 API 即文档**:`src/client/service.ts` 与 `src/client/builtins/` 的任何行为变更,必须同步 [docs/external-plugin-guide.md](docs/external-plugin-guide.md)(唯一权威接入文档,不再双份维护)。
|
||||
- **文件/文件夹图标**:回退链是「具体 `names`/`exts` 注册 → catch-all(`exts: []`)→ 宿主 `FileTypeIcon`」。插件**不持有**扩展名表、图标数据或图标 chunk(#429 的 563 条数据与 `fileIconTheme` 开关在 rc.2 适配时删除,因为宿主已导出官方图形)。**语义雷区**:宿主分类器覆盖任意路径,所以注册 `exts: []` 会接管所有未具体命中的行——只想补几个类型的插件必须用 `exts`/`names`。
|
||||
- **内置 tab 图标**:六个内置类型与 diff 视图的 glyph 集中在 `src/client/builtins/tab-icons.tsx`(颜色在 `tab-icons.module.css`,全部 `--dsw-alias-*`),三处消费面自动同步:底部工作台 tab 条、原生指南胶囊、原生 tab 芯片。**原生芯片的图标是插件自己画的**——宿主的 `SidebarRightTabDefinition` 没有 icon 字段,但 `sidebar.right.pane.tab.title` 槽就是芯片内容(`NativeTabTitle` 渲染 `[glyph][title]`,glyph `aria-hidden` 以免改可访问名)。
|
||||
- **内置 tab 图标**:五个内置类型(文件 / 文件变动 / 任务管理 / 侧边对话 / diff)的 glyph 集中在 `src/client/builtins/tab-icons.tsx`(颜色在 `tab-icons.module.css`,全部 `--dsw-alias-*`),三处消费面自动同步:底部工作台 tab 条、原生指南胶囊、原生 tab 芯片。**原生芯片的图标是插件自己画的**——宿主的 `SidebarRightTabDefinition` 没有 icon 字段,但 `sidebar.right.pane.tab.title` 槽就是芯片内容(`NativeTabTitle` 渲染 `[glyph][title]`,glyph `aria-hidden` 以免改可访问名)。
|
||||
|
||||
---
|
||||
|
||||
@@ -71,4 +89,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`(注册表与内置清单:7 tab + 6 viewer)/ `market-manifest.spec.ts`(市场约束)/ `e2e-host-protocol.spec.ts`(RPC 双协议)/ `native-surface.spec.ts`(原生右侧栏承载面)/ `theme.spec.ts`(皮肤契约)/ `plugin-list.spec.ts`(推荐插件目录)/ `fs-search.spec.ts`(host 文件名搜索)。
|
||||
- **关键测试守护**:`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 文件名搜索)。
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# dsh-better-sidebar
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **已适配 DSH 原生侧边栏 API**(v0.19.0 起,DSH `0.1.5-rc.1+`):右列就是 DSH 自己的右侧栏——插件的每个 tab 类型与 tab 体通过 `ctx.sidebarRightTabs` / `ctx.sidebarRight` 注册与打开,聊天里的文件打开统一走 `ctx.sidebarRight.openResource('dsh-resource://file/…')`,插件**不再自绘右侧面板**(旧的浮窗能力同步移除)。自绘的底部工作台与开放给其他插件的 `ctx.betterSidebar` 服务保持不变,接入方式见[插件接入指南](docs/external-plugin-guide.md)。
|
||||
> **已适配 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**。
|
||||
|
||||
<!-- Hero -->
|
||||
<div align="center">
|
||||
@@ -12,9 +14,9 @@
|
||||
<a href="https://github.com/omdsh-dev/DSH-better-sidebar/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/omdsh-dev/DSH-better-sidebar" /></a>
|
||||
<a href="https://opensource.org/licenses/MIT"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-yellow.svg" /></a>
|
||||
<a href="https://dshfind.com/zh/plugins/omdsh-dev/DSH-better-sidebar?ref=badge"><img alt="dshfind" src="https://dshfind.com/api/badge/omdsh-dev/DSH-better-sidebar?lang=zh" /></a><br /><br />
|
||||
<a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="支持的 DSH 版本(v0.19.1 正式版):0.1.5-rc.1+(已在 0.1.5-rc.2 上验证)" src="https://img.shields.io/badge/DSH-0.1.5--rc.1%2B_%28verified_rc.2%29-4d6bfe" /></a>
|
||||
<a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="支持的 DSH 版本(v0.20.0 正式版):0.1.6-alpha.2+" src="https://img.shields.io/badge/DSH-0.1.6--alpha.2%2B-4d6bfe" /></a>
|
||||
<a href="https://github.com/topics/dsh-better-sidebar"><img alt="插件生态:GitHub topic dsh-better-sidebar" src="https://img.shields.io/badge/%E6%8F%92%E4%BB%B6%E7%94%9F%E6%80%81-topic%20dsh--better--sidebar-4d6bfe" /></a><br /><br />
|
||||
<img alt="文件管理" src="https://img.shields.io/badge/-文件管理-4d6bfe" /> <img alt="编辑预览" src="https://img.shields.io/badge/-编辑预览-4d6bfe" /> <img alt="内嵌浏览器" src="https://img.shields.io/badge/-内嵌浏览器-4d6bfe" /> <img alt="真实终端" src="https://img.shields.io/badge/-真实终端-4d6bfe" /> <img alt="文件变动" src="https://img.shields.io/badge/-文件变动-4d6bfe" /> <img alt="后台任务" src="https://img.shields.io/badge/-后台任务-4d6bfe" /> <img alt="侧边对话" src="https://img.shields.io/badge/-侧边对话-4d6bfe" /> <img alt="插件接入" src="https://img.shields.io/badge/-插件接入-4d6bfe" /><br /><br />
|
||||
<img alt="文件管理" src="https://img.shields.io/badge/-文件管理-4d6bfe" /> <img alt="编辑预览" src="https://img.shields.io/badge/-编辑预览-4d6bfe" /> <img alt="内置浏览器" src="https://img.shields.io/badge/-内置浏览器-4d6bfe" /> <img alt="文件变动" src="https://img.shields.io/badge/-文件变动-4d6bfe" /> <img alt="后台任务" src="https://img.shields.io/badge/-后台任务-4d6bfe" /> <img alt="侧边对话" src="https://img.shields.io/badge/-侧边对话-4d6bfe" /> <img alt="插件接入" src="https://img.shields.io/badge/-插件接入-4d6bfe" /><br /><br />
|
||||
<b>右侧栏 + 底部面板双工作台</b>,并把 <code>ctx.betterSidebar</code> 服务开放给所有插件——<br />
|
||||
通过 <code>registerTab</code> / <code>registerFileViewer</code> 注册新的侧边栏页面与文件预览器。
|
||||
</div>
|
||||
@@ -44,17 +46,16 @@
|
||||
## ✨ 功能一览
|
||||
|
||||
- **🗂️ 文件工作台**:资源管理器(懒加载目录树;软链接按目标类型展示——目录软链接可展开、失效链接标红;文件树与文件 tab 按扩展名显示图标——markdown / 图片 / PDF / 代码 / 配置 / 压缩包等各有 glyph,插件可经 `registerFileIcon` 注册自定义图标与目录图标)+ CodeMirror 编辑器;图片 / Markdown(含 Mermaid 图表,strict 安全渲染 + 点击放大;README 级内嵌 HTML——徽章墙 / `<details>` 折叠 / 表格内联标签经 DOMPurify 消毒真实渲染;浮动目录大纲一键跳转)/ HTML / PDF
|
||||
- **🌐 内嵌浏览器**:多开网页 tab,后退 / 前进 / 刷新;内容运行在沙箱 iframe;外链默认按协议分流——HTTP 在侧边栏打开、HTTPS 走系统浏览器(设置页可分别调整)
|
||||
- **💻 真实终端**:xterm.js + node-pty 真实 shell,断线重连回放;可选为模型注入 `terminal_*` 工具
|
||||
- **🌐 内置浏览器 + 链接分流**:网页 tab 由 **DSH 内置**的 `ui-sidebar-browser` 提供(多开 / 后退前进刷新 / 沙箱 iframe);插件保留聊天与界面里的**外链接管**——HTTP 在侧边栏打开、HTTPS 走系统浏览器(设置页可分别调整),这是宿主自身不提供的按协议分流
|
||||
- **💻 终端(DSH 内置)**:右侧栏终端由 **DSH 内置**的 `ui-sidebar-terminal` 提供(shell 选择 / 重命名 / 断线重连 / 刷新后恢复 / 主题跟随)。插件不再自带 xterm + node-pty 实现
|
||||
- **📂 模型侧边栏打开(可选)**:全局设置开启后注入 `sidebar_open` 工具——模型可主动在侧边栏打开文件 / 文件夹(树以该目录为根)/ HTTP(S) 网页
|
||||
- **🌿 文件变动**: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 风格的侧边线程——继承主会话完整上下文(含进行中的回合与工具调用)独立运行,不进入主会话;线程内可持续追问,一键「保存为新会话」提升为顶层会话
|
||||
- **🖥️ 原生右侧栏 + 底部工作台**:右列交给 DSH 0.1.5 的原生右侧栏——插件把每个 tab 类型注册成原生 tab(文件打开走 `dsh-resource://file/**`,并接管内置「文件」页 / 文件树),插件自己只保留底部工作台(分栏 / 终端 / 随会话持久化),开合按钮挂在会话头右侧
|
||||
- **📌 固定终端**:右键终端 Tab 可「固定到工作区 / 固定到全局」——固定后切换会话不消失,在 TabBar 内联呈现(跨会话虚拟 Tab,点击就地激活,PTY 按 home 会话 id+tab 直连宿主 PTY,无需切回宿主会话);Agent 终端被 reconcile 移除时豁免保留
|
||||
- **🖥️ 原生右侧栏 + 底部工作台**:右列交给 DSH 原生右侧栏——插件把每个 tab 类型注册成原生 tab(文件打开走 `dsh-resource://file/**`,并接管内置「文件」页 / 文件树),插件自己只保留底部工作台(分栏 / 随会话持久化),开合按钮挂在会话头右侧
|
||||
- **🔁 会话隔离**:布局 / Tab / 面板按会话持久化,陈旧状态自动净化
|
||||
- **⚙️ 声明式设置**:设置页「侧边卡片」逐项独立开关,二级设置经齿轮弹窗
|
||||
- **⚡ 按需加载**:启动只拉 ~325KB 核心,终端 / 编辑器 / Mermaid 图表等重依赖用到才按需拉取([设计文档](docs/plans/2026-08-12-lazy-chunks-design.md))
|
||||
- **⚡ 按需加载**:启动只拉 ~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)。
|
||||
@@ -64,30 +65,28 @@
|
||||
**前置**:已装好 DSH(`dsh web` 能正常运行),Node.js ≥ 20、pnpm ≥ 10。
|
||||
|
||||
**支持的 DSH 版本**:
|
||||
<a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="支持的 DSH 版本(v0.19.1 正式版):0.1.5-rc.1+(已在 0.1.5-rc.2 上验证)" src="https://img.shields.io/badge/DSH-0.1.5--rc.1%2B_%28verified_rc.2%29-4d6bfe" /></a>
|
||||
<a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="支持的 DSH 版本(v0.20.0 正式版):0.1.6-alpha.2+" src="https://img.shields.io/badge/DSH-0.1.6--alpha.2%2B-4d6bfe" /></a>
|
||||
|
||||
> 📌 **正式版**:`v0.19.0` 起适配 DSH **0.1.5-rc.1+**(npm dist-tag `latest`;`v0.19.1` 已在 **0.1.5-rc.2** 上完成真机挂载验证,rc.1 用户无需升级即可用本版——peer 下限仍是 `^0.1.5-rc.1`)。仍停在 DSH 0.1.5-alpha.2 的用户请固定安装 `dsh-better-sidebar@0.19.0-alpha.1`;0.1.2-rc.1 稳定线用户继续用 `dsh-better-sidebar@0.18.x`;DSH ≤ 0.1.1-rc.2 请用 `dsh-better-sidebar@0.17.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`)。
|
||||
|
||||
```sh
|
||||
dsh plugin --profile web add dsh-better-sidebar@latest # 首次会因 pnpm 11 拦截 node-pty 构建脚本而失败(依赖已写入)
|
||||
cd ~/.dsh/profiles/web && pnpm approve-builds --all # 放行构建脚本(自动重跑安装)
|
||||
dsh plugin --profile web add dsh-better-sidebar@latest # 重跑即成功
|
||||
dsh plugin --profile web add dsh-better-sidebar@latest
|
||||
```
|
||||
|
||||
> v0.20.0 起插件不再依赖任何需要构建脚本的包(`node-pty` 已随自研终端删除),所以安装**一步到位**,不再需要 `pnpm approve-builds` 那轮重跑。DSH 0.1.6 也自带 Web 侧的 **Plugins 页面**(`ui-plugin-manager`)与 `plugin_manager` 工具,装完后可以直接在那里启停。
|
||||
|
||||
装完**硬刷新浏览器**(Cmd/Ctrl+Shift+R)即可看到侧边栏(DSH 对 client 改动热加载,无需重启;仅 host 半更新时需要重启)。
|
||||
|
||||
**方式二:让 DSH 自己装**——把下面这段提示词发给任意一个 DSH 会话:
|
||||
|
||||
```text
|
||||
帮我安装 dsh-better-sidebar 插件(DSH 侧边栏工作台),步骤:
|
||||
1. 执行 dsh plugin --profile web add dsh-better-sidebar@latest(首次会被 pnpm 11 拦截 node-pty 构建脚本而失败,属正常)
|
||||
2. 在 ~/.dsh/profiles/web 下执行 pnpm approve-builds --all(放行构建脚本,会自动重跑安装)
|
||||
3. 再次执行 dsh plugin --profile web add dsh-better-sidebar@latest
|
||||
4. 完成后提醒我硬刷新浏览器(Cmd/Ctrl+Shift+R)
|
||||
1. 执行 dsh plugin --profile web add dsh-better-sidebar@latest(v0.20.0 起一步到位,无需构建脚本放行)
|
||||
2. 完成后提醒我硬刷新浏览器(Cmd/Ctrl+Shift+R)
|
||||
遇到报错先查 https://github.com/omdsh-dev/DSH-better-sidebar README 的常见问题表。
|
||||
```
|
||||
|
||||
**方式三:一键脚本**——克隆本仓库后执行 `bash scripts/install.sh`(macOS / Linux / Windows Git Bash;Windows 原生环境用 `install.ps1`;`-h` 查看参数),自动完成 add → 放行构建脚本 → 重跑安装。
|
||||
**方式三:一键脚本**——克隆本仓库后执行 `bash scripts/install.sh`(macOS / Linux / Windows Git Bash;Windows 原生环境用 `install.ps1`;`-h` 查看参数),自动完成安装 + bundle 注册(含幂等清理旧的手动挂载行)。
|
||||
|
||||
<details>
|
||||
<summary><b>更新</b></summary>
|
||||
@@ -105,12 +104,11 @@ dsh plugin --profile web add dsh-better-sidebar@latest
|
||||
|
||||
| 现象 | 原因与解决 |
|
||||
|---|---|
|
||||
| 报 `Ignored build scripts` | pnpm 11 拦截构建脚本。在 profile 目录(`~/.dsh/profiles/web`)跑 `pnpm approve-builds --all`。 |
|
||||
| 报 `Ignored build scripts` | pnpm 11 拦截了某个传递依赖的构建脚本。在 profile 目录(`~/.dsh/profiles/web`)跑 `pnpm approve-builds` 按提示放行——v0.20.0 起本插件自身不再有构建脚本依赖。 |
|
||||
| 报 `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` 之前。 |
|
||||
| Windows 下终端无法使用 | `node-pty` 依赖预编译二进制;若当前 Node 版本没有对应产物,需装编译工具链(VS Build Tools)。主流 Node 版本一般已有预编译。 |
|
||||
| 终端提示「node-pty 加载失败」 | `node-pty` 安装缺失/损坏(如 pnpm 拦截了构建脚本)。终端横幅会给出修复命令:复制到 DSH 所在环境的终端/cmd 执行(在 `~/.dsh/profiles/web` 下 `pnpm approve-builds --all && pnpm rebuild node-pty`),完成后重启 DSH 并点重试。插件与 DSH 核心使用同一 `node-pty@^1.1.0`,修复后两者同步恢复。 |
|
||||
| 终端无法使用 / 提示 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`。 |
|
||||
|
||||
</details>
|
||||
@@ -166,8 +164,8 @@ dsh registry enable dsh-external/dsh-better-sidebar
|
||||
|---|---|
|
||||
| **🗂️ 文件工作台:资源管理器**<br/><sub>支持两种格式的资源管理器:内嵌在文件预览中 / 独立显示文件树。懒加载目录树、软链接按目标类型展示(目录软链接可展开、失效链接标红)、全局文件名搜索、上传文件/文件夹与拖放上传、右键菜单(在新 Tab 打开 / 在侧边打开 / 复制路径)、悬浮 `@文件` 一键引用进输入框。</sub><br/><div align="center"><img width="420" alt="文件资源管理器" src="https://github.com/user-attachments/assets/a410bfd2-a8ba-43e6-873e-22417756e94d" /></div> | **📝 Markdown · 图片 · PDF 内联预览**<br/><sub>Markdown 预览支持 **Mermaid 图表**(`securityLevel: 'strict'` 安全渲染 + 二次清洗;点击图表弹窗放大、滚轮缩放、拖拽平移)、**README 级内嵌 HTML**(徽章墙 `<div align=center>`、`<details>` 折叠块内嵌 markdown、表格单元格内联标签——DOMPurify 白名单消毒真实渲染,`<script>` 等活性内容剥除,本地图片经会话媒体路由重写)与**浮动目录大纲**(≥3 标题出现,点击平滑跳转、自动展开折叠块);图片 / PDF 走媒体路由内联展示;Office 三件套由生态插件补齐。</sub><br/><div align="center"><img width="420" alt="Markdown + Mermaid 预览" src="https://github.com/user-attachments/assets/fe0e5182-55bb-45cc-b98b-a2877c2bdd38" /></div> |
|
||||
| **🖥️ CodeMirror 代码编辑器**<br/><div align="center"><img width="420" alt="CodeMirror 代码编辑器" src="https://github.com/user-attachments/assets/b44b488e-568c-4ee0-b96c-e9c906598a77" /></div> | **🖼️ 图片内联预览**<br/><div align="center"><img width="420" alt="图片内联预览" src="https://github.com/user-attachments/assets/f9a58c30-5b7a-48b5-9e22-37d7e071f593" /></div> |
|
||||
| **💻 真实终端**<br/><sub>xterm.js + node-pty 真实 shell(不是模拟器):断线重连 transcript 回放、shell / shellArgs 可配置(设置页或 `cordis.patch.yml`)、可选为模型注入 `terminal_*` 工具(agent 可直接开终端跑命令)。</sub><br/><div align="center"><img width="420" alt="真实终端" src="https://github.com/user-attachments/assets/0dad6ad3-ff3f-4b5a-86d2-f832ce65323e" /></div> | **🌿 文件变动:Git 视角 + 本轮文件**<br/><sub>双视角合一:**Git 视角**保留完整源代码管理(暂存 / 取消暂存 / 提交(`Ctrl+Enter`)/ 还原、历史、worktree 与子仓库选择);**本轮文件视角**实时折叠会话事件日志,记录模型读 / 写 / 编辑的每个文件(按文件分组、按类型筛选、操作数角标)。点击任意改动在底部**可拖拽预览面板**查看统一 diff——删红 / 增绿 / 改蓝配对 + 行内字符级高亮 + 语法着色 + 上下文折叠——也可一键展开为 VSCode 式独立 diff tab(同一渲染栈)。</sub><br/><div align="center"><img width="420" alt="文件变动" src="https://github.com/user-attachments/assets/e7fc1220-305f-4bca-8583-e77ab4f4fa78" /></div> |
|
||||
| **🌐 内嵌浏览器**<br/><sub>多开网页 tab:后退 / 前进 / 刷新 / 地址栏;内容运行在**不透明源沙箱 iframe**(界面实时显示沙箱状态,可按页面临时解锁);聊天里的外链点击可被接管到侧边栏打开(按协议分流,可配)。</sub><br/><div align="center"><img width="420" alt="内嵌浏览器" src="https://github.com/user-attachments/assets/9bc6b65a-64fc-4942-a685-76e391e55606" /></div> | **🧩 任务页:子代理拓扑 + 后台任务**<br/><sub>子代理树实时拓扑(运行状态、批量实时预览)+ 后台任务清单(退出码 / 实时输出 / 强制终止);新子代理 / 新任务可自动激活任务页,宽屏同时展开侧边栏,窄屏不强制展开全屏抽屉(可关)。</sub><br/><div align="center"><img width="420" alt="任务页:子代理拓扑" src="https://github.com/user-attachments/assets/dcd8ed2f-59fa-405b-937b-2d250f5034dd" /></div> |
|
||||
| **💻 终端(由 DSH 内置提供)**<br/><sub>v0.20.0 起右侧栏终端由 DSH 自己的 `ui-sidebar-terminal` 提供:shell 选择、双击重命名、断线重连、刷新后恢复、主题与对比度跟随。插件不再自带 xterm + node-pty 实现。<br/><br/>⚠️ **模型侧提示**:插件原来自带的 8 个 `terminal_*` 工具(默认关)是模型唯一的**跨调用持久**终端;上游等价物 `@deepseek-ai/dsh-tool-terminal` 未被任何内置 bundle 默认挂载,若你需要该能力,请在 profile 的 `cordis.patch.yml` 里自行插入一行 `tool-terminal`。</sub><br/><div align="center"><img width="420" alt="真实终端" src="https://github.com/user-attachments/assets/0dad6ad3-ff3f-4b5a-86d2-f832ce65323e" /></div> | **🌿 文件变动:Git 视角 + 本轮文件**<br/><sub>双视角合一:**Git 视角**保留完整源代码管理(暂存 / 取消暂存 / 提交(`Ctrl+Enter`)/ 还原、历史、worktree 与子仓库选择);**本轮文件视角**实时折叠会话事件日志,记录模型读 / 写 / 编辑的每个文件(按文件分组、按类型筛选、操作数角标)。点击任意改动在底部**可拖拽预览面板**查看统一 diff——删红 / 增绿 / 改蓝配对 + 行内字符级高亮 + 语法着色 + 上下文折叠——也可一键展开为 VSCode 式独立 diff tab(同一渲染栈)。</sub><br/><div align="center"><img width="420" alt="文件变动" src="https://github.com/user-attachments/assets/e7fc1220-305f-4bca-8583-e77ab4f4fa78" /></div> |
|
||||
| **🌐 浏览器(页面由 DSH 内置提供)**<br/><sub>网页 tab 由 DSH 自己的 `ui-sidebar-browser` 提供(多开 / 后退前进刷新 / 地址栏 / 沙箱 iframe)。插件保留宿主没有的那一半:聊天与界面里的**外链接管**(按协议分流,HTTP 进侧边栏、HTTPS 交系统浏览器,可分别配置;Ctrl/Cmd 点击始终放行)。</sub><br/><div align="center"><img width="420" alt="内嵌浏览器" src="https://github.com/user-attachments/assets/9bc6b65a-64fc-4942-a685-76e391e55606" /></div> | **🧩 任务页:子代理拓扑 + 后台任务**<br/><sub>子代理树实时拓扑(运行状态、批量实时预览)+ 后台任务清单(退出码 / 实时输出 / 强制终止);新子代理 / 新任务可自动激活任务页,宽屏同时展开侧边栏,窄屏不强制展开全屏抽屉(可关)。</sub><br/><div align="center"><img width="420" alt="任务页:子代理拓扑" src="https://github.com/user-attachments/assets/dcd8ed2f-59fa-405b-937b-2d250f5034dd" /></div> |
|
||||
| **💬 侧边对话(beta)**<br/><sub>Codex 风格侧边线程:**每个对话一个独立 Tab**;线程继承主会话完整上下文(含进行中回合,以 interrupted 诚实冻结)独立运行,不污染主会话;可持续追问、重启冷恢复;一键「保存为新会话」提升为顶层会话。</sub><br/><div align="center"><img width="420" alt="侧边对话(beta)" src="https://github.com/user-attachments/assets/3a338c36-f5de-4000-95f3-4b1cd04f60fc" /></div> | **🖥️ DSH 原生右侧栏 + 插件底部工作台**<br/><sub>右列是 DSH 自己的右侧栏:插件把每个 tab 类型注册成原生 tab(含接管内置「文件」页与文件预览),聊天里的文件点击直接落到原生栏;插件自有底部面板可与其同时展开,拖 Tab 到分栏边缘**拆分**、拖到中间**合并**,高度拖上缘调节;开合按钮在会话头右侧。</sub><br/><div align="center"><img width="420" alt="双工作台(右侧栏 + 底部面板)" src="https://github.com/user-attachments/assets/dfdb875e-a1a8-4d4b-8340-353736b1708f" /></div> |
|
||||
| **⚙️ 声明式设置**<br/><sub>设置页「侧边卡片」分区:每个 tab / 预览器一张小卡片,独立开关(高亮启用态 + 品牌开关滑块);二级设置经卡片底部「功能设置」条弹窗(开关 / 文本 / 数字 / 下拉);插件自有设置持久化在 `pluginSettings`。</sub><br/><div align="center"><img width="420" alt="声明式设置:侧边卡片" src="https://github.com/user-attachments/assets/0800ca64-621e-48da-b7df-aecfddc3ec29" /></div> | **📱 移动端**<br/><sub>窄屏(<768px)自动切换为全宽抽屉:底栏 tab 一次性并入右侧栏,触屏拖拽可调。</sub><br/><div align="center"><img width="360" alt="移动端全宽抽屉" src="https://github.com/user-attachments/assets/a82ba78a-f4cf-4d85-80e8-050a05beb144" /></div> |
|
||||
|
||||
@@ -263,7 +261,18 @@ GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sidebar
|
||||
|
||||
## 🆕 最近更新
|
||||
|
||||
**支持的 DSH 版本**:<a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="支持的 DSH 版本(v0.19.1 正式版):0.1.5-rc.1+(已在 0.1.5-rc.2 上验证)" src="https://img.shields.io/badge/DSH-0.1.5--rc.1%2B_%28verified_rc.2%29-4d6bfe" /></a> · 完整发布历史见 [Releases](https://github.com/omdsh-dev/DSH-better-sidebar/releases)
|
||||
**支持的 DSH 版本**:<a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="支持的 DSH 版本(v0.20.0 正式版):0.1.6-alpha.2+" src="https://img.shields.io/badge/DSH-0.1.6--alpha.2%2B-4d6bfe" /></a> · 完整发布历史见 [Releases](https://github.com/omdsh-dev/DSH-better-sidebar/releases)
|
||||
|
||||
### v0.20.0
|
||||
|
||||
> 📌 **正式版**(npm `latest`,无 prerelease 后缀):仅支持 **DSH 0.1.6-alpha.2+**(peer 下限 `^0.1.6-alpha.2`,CI 钉 `@deepseek-ai/dsh@0.1.6-alpha.2`)。**这是一次破坏性变更版**:0.1.5-rc.* 用户请固定 `dsh-better-sidebar@0.19.1`。适配记录见 [docs/plans/2026-09-21-dsh-0.1.6-alpha.2-adaptation.md](docs/plans/2026-09-21-dsh-0.1.6-alpha.2-adaptation.md)。
|
||||
|
||||
- 🖥️ **终端交给 DSH 内置**:DSH 0.1.6 自带右列终端(`ui-sidebar-terminal`),所以插件删掉了整套自研终端——`pty-manager` / `agent-pty` / `pty-deps` / 8 个 `terminal_*` 工具 / xterm 视图与字体链接 / 跨会话固定终端,以及 `node-pty` 依赖、它的 `allowBuilds` 放行与安装脚本的 `-Repair` 模式。**注意**:模型因而失去跨调用持久的终端(只剩一次性 `bash` / `pwsh`);上游 `@deepseek-ai/dsh-tool-terminal` 未被内置 bundle 挂载,需要时请自行在 profile 里启用。
|
||||
- 🌐 **浏览器视图让给内置**:插件删掉 `BrowserView` / 沙箱状态条 / 嵌入性探测(含宿主 `browser.probe` 路由)与两个只配置自家 iframe 的设置项;**保留**聊天与界面的外链接管(按协议分流是宿主没有的能力),目标改为 DSH 内置的 `browser` tab 类型。
|
||||
- 🧩 **产物行交回宿主**:DSH 0.1.6 把 `conversation.chat.turnTail` 从 chain 改成 list(上游有意让多个插件**追加**而非互相替换),插件原来的接管只能与内置产物卡重复,因此整体删除;点击行为不变(内置走宿主 `openFile` → 本插件编辑器)。代价是失去轮尾那一个「在文件树中显示」入口(树内右键仍在)。
|
||||
- 🐛 **修复两个静默破坏**:`SidebarRightGuideEntry.id` 变成必填后,缺 id 会让**整个原生承载面静默空掉**(抛在 `ctx.inject` 回调里被吞);`turnTail` 改 list 后原注册直接抛错、产物行失效。两处都已修好并加测试守护,同时补了注册失败的上报口。
|
||||
- 🎨 **空白面板卡片对齐内置卡风格**:`.paneCard` 改用 DSH 指南胶囊那套配方(0.5px l4 描边 / 24px 圆角 / layer-1 填充 / 56px 下限 / 14×20 内边距),并加测试守护其几何、令牌化配色,以及不复制上游那套**未定义**的 `--dsw-alias-bg-l1/-l2` 拼写。
|
||||
- 📐 **基线**:`@deepseek-ai/dsh-*` 全部钉 `0.1.6-alpha.2`,`dsh-code-runtime` 整包消失已移除,`diff` / `simple-icons` 作为 primitives 的新裸 import 提升进 devDependencies;`ui-primitives` 本版三处破坏(`IconSendOutline16` 下线、`TerminalBlockLabels.noExitCode` 新增、`ConnectionIndicator.reconnectLabel` 移除)同步适配。
|
||||
|
||||
### v0.19.1
|
||||
|
||||
@@ -645,11 +654,11 @@ make clean # 清理 lib/、*.tgz、playwright-report/、test-results/
|
||||
- Office 三件套预览(.docx/.xlsx/.pptx)已移至「推荐插件」(Office 预览插件,见设置页「添加插件」弹窗);未安装时此类文件走代码/下载查看兜底
|
||||
- 浏览器沙箱无登录态/第三方 Cookie 受限,部分站点登录需走弹窗;被 `X-Frame-Options`/`frame-ancestors` 拒绝嵌入的站点(如 arxiv.org)显示原因面板(含「在浏览器中打开」);iframe 内部跳转不进后退栈
|
||||
- HTML 预览渲染的是已保存文件(不反映未保存草稿)
|
||||
- 移动端(<768px)无底部面板:进入窄屏时其标签页一次性并入右侧栏(迁移后回桌面仍保留在右侧栏),桌面端的底部面板只在宽视口下可用;移动端底部首展自动开终端不触发。未选中会话时,点按弱化开关会显示选择会话提示;选中会话后开关打开全宽抽屉
|
||||
- 移动端(<768px)无底部面板:进入窄屏时其标签页一次性并入右侧栏(迁移后回桌面仍保留在右侧栏),桌面端的底部面板只在宽视口下可用。未选中会话时,点按弱化开关会显示选择会话提示;选中会话后开关打开全宽抽屉
|
||||
|
||||
## 🖥️ 平台支持
|
||||
|
||||
Windows / Linux / macOS 三平台适配(macOS 日常验证;其余经单元测试覆盖);`node-pty` 优先预编译二进制,失败需编译工具链(Windows VS Build Tools / Linux make+g+++python3 / macOS Xcode CLT)。
|
||||
Windows / Linux / macOS 三平台适配(macOS 日常验证;其余经单元测试覆盖)。v0.20.0 起插件不再包含原生依赖(终端与 `node-pty` 已交还 DSH 自身),构建只需 Node + pnpm,无需编译工具链。
|
||||
|
||||
## 🤝 参与贡献
|
||||
|
||||
|
||||
+35
-26
@@ -1,7 +1,9 @@
|
||||
# dsh-better-sidebar
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Built on DSH's native sidebar API** (since v0.19.0, DSH `0.1.5-rc.1+`): the right column *is* DSH's own sidebar — every plugin tab type and tab body registers and opens through `ctx.sidebarRightTabs` / `ctx.sidebarRight`, every file open from the chat goes through `ctx.sidebarRight.openResource('dsh-resource://file/…')`, and the plugin **no longer draws a right panel of its own** (the old free-window capability is gone with it). The self-drawn bottom workbench and the `ctx.betterSidebar` service other plugins register against are unchanged — see the [plugin integration guide](docs/external-plugin-guide.md).
|
||||
> **Built on DSH's native sidebar API** (since v0.19.0): the right column *is* DSH's own sidebar — every plugin tab type and tab body registers and opens through `ctx.sidebarRightTabs` / `ctx.sidebarRight`, every file open from the chat goes through `ctx.sidebarRight.openResource('dsh-resource://file/…')`, and the plugin **no longer draws a right panel of its own** (the old free-window capability is gone with it). The self-drawn bottom workbench and the `ctx.betterSidebar` service other plugins register against are unchanged — see the [plugin integration guide](docs/external-plugin-guide.md).
|
||||
>
|
||||
> **Since v0.20.0 the host support floor is DSH `0.1.6-alpha.2`**: that DSH version ships its own right-Sidebar **terminal** and **browser**, so the plugin **deleted its own terminal** (the whole xterm + node-pty stack) and **handed the browser view to the built-in**, keeping only the per-protocol link takeover. Hosts on 0.1.5-rc.* or earlier should stay on **v0.19.1**.
|
||||
|
||||
<!-- Hero -->
|
||||
<div align="center">
|
||||
@@ -12,9 +14,9 @@
|
||||
<a href="https://github.com/omdsh-dev/DSH-better-sidebar/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/omdsh-dev/DSH-better-sidebar" /></a>
|
||||
<a href="https://opensource.org/licenses/MIT"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-yellow.svg" /></a>
|
||||
<a href="https://dshfind.com/en/plugins/omdsh-dev/DSH-better-sidebar?ref=badge"><img alt="dshfind" src="https://dshfind.com/api/badge/omdsh-dev/DSH-better-sidebar?lang=en" /></a><br /><br />
|
||||
<a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="Supported DSH versions (v0.19.1): 0.1.5-rc.1+ (verified on rc.2)" src="https://img.shields.io/badge/DSH-0.1.5--rc.1%2B_%28verified_rc.2%29-4d6bfe" /></a>
|
||||
<a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="Supported DSH versions (v0.20.0): 0.1.6-alpha.2+" src="https://img.shields.io/badge/DSH-0.1.6--alpha.2%2B-4d6bfe" /></a>
|
||||
<a href="https://github.com/topics/dsh-better-sidebar"><img alt="Plugin ecosystem: GitHub topic dsh-better-sidebar" src="https://img.shields.io/badge/plugin%20ecosystem-topic%20dsh--better--sidebar-4d6bfe" /></a><br /><br />
|
||||
<img alt="File management" src="https://img.shields.io/badge/-File%20management-4d6bfe" /> <img alt="Edit & preview" src="https://img.shields.io/badge/-Edit%20%26%20preview-4d6bfe" /> <img alt="Embedded browser" src="https://img.shields.io/badge/-Embedded%20browser-4d6bfe" /> <img alt="Real terminal" src="https://img.shields.io/badge/-Real%20terminal-4d6bfe" /> <img alt="Changes" src="https://img.shields.io/badge/-Changes-4d6bfe" /> <img alt="Background tasks" src="https://img.shields.io/badge/-Background%20tasks-4d6bfe" /> <img alt="Side Chat" src="https://img.shields.io/badge/-Side%20Chat-4d6bfe" /> <img alt="Plugin integration" src="https://img.shields.io/badge/-Plugin%20integration-4d6bfe" /><br /><br />
|
||||
<img alt="File management" src="https://img.shields.io/badge/-File%20management-4d6bfe" /> <img alt="Edit & preview" src="https://img.shields.io/badge/-Edit%20%26%20preview-4d6bfe" /> <img alt="Built-in browser" src="https://img.shields.io/badge/-Built-in%20browser-4d6bfe" /> <img alt="Changes" src="https://img.shields.io/badge/-Changes-4d6bfe" /> <img alt="Background tasks" src="https://img.shields.io/badge/-Background%20tasks-4d6bfe" /> <img alt="Side Chat" src="https://img.shields.io/badge/-Side%20Chat-4d6bfe" /> <img alt="Plugin integration" src="https://img.shields.io/badge/-Plugin%20integration-4d6bfe" /><br /><br />
|
||||
<b>A dual workbench (right sidebar + bottom panel)</b> that opens its <code>ctx.betterSidebar</code> service to every plugin —<br />
|
||||
register new sidebar pages and file viewers via <code>registerTab</code> / <code>registerFileViewer</code>.
|
||||
</div>
|
||||
@@ -44,17 +46,16 @@
|
||||
## ✨ Features
|
||||
|
||||
- **🗂️ File Workbench**: file explorer (lazy-loading tree; symlinks show their target kind — directory links expand, dangling links flagged) + CodeMirror editor; inline preview for images / Markdown (incl. Mermaid diagrams, strict-mode safe rendering + click-to-zoom) / HTML / PDF
|
||||
- **🌐 Embedded Browser**: multiple web tabs with back / forward / refresh; content runs in a sandboxed iframe; external links are routed by protocol by default — HTTP opens in the sidebar, HTTPS goes to the system browser (both adjustable in settings)
|
||||
- **💻 Real Terminal**: xterm.js + node-pty real shell, reconnect with transcript replay; optionally injects `terminal_*` tools for the model
|
||||
- **🌐 Built-in browser + link routing**: web tabs are provided by DSH's own `ui-sidebar-browser` (multiple tabs, back / forward / reload, sandboxed iframe); the plugin keeps the **external-link takeover** in the chat and UI — HTTP opens in the sidebar, HTTPS goes to the system browser (both adjustable in the settings page), the protocol-based split the host itself does not provide
|
||||
- **💻 Terminal (provided by DSH)**: the right-Sidebar terminal is provided by DSH's own `ui-sidebar-terminal` (shell picker, rename, reconnect, restore after reload, theme following). The plugin no longer ships xterm + node-pty
|
||||
- **📂 Model-driven sidebar opens (opt-in)**: with the global setting on, the `sidebar_open` tool lets the model actively open files / folders (tree rooted there) / HTTP(S) pages in the sidebar
|
||||
- **🌿 Changes**: one tab, two lenses — **Git** (real diff / history / stage·commit·revert / worktree & child-repo selection) and **This Session** (live tracking of every file the model reads / writes / edits, grouped by file with kind filters); a unified diff renderer (mod pairing + intra-line character highlights + syntax coloring incl. mjs/cjs/mts/cts, CSS/SCSS/Less, HTML/XML/SVG/Vue, GraphQL, JSONC/JSON5 + context folding), a draggable bottom preview pane, and one-click expansion into a dedicated diff tab; `.md` ops (read / write / edit) offer a **reading-mode** toggle in the preview header — the shared MarkdownText renders GFM tables / task lists / strikethrough / footnotes / math, with local images rewritten through the /sidebar/file media route; documents with ```mermaid fences render through the editor's lazy mermaid renderer (click-to-zoom / pan diagrams); **secret redaction** — credential-shaped paths mask whole files and ordinary files mask secret-shaped values (api_key: / Bearer / sk- / AKIA / ghp_ / PEM …, field names kept), on by default with a one-click preview-pane toggle (persisted in localStorage), display-only — session data untouched. Known edge: an unquoted mermaid label containing a redacted secret breaks the diagram (falls back to source); workaround: quote the label. `.html` ops (read / write / edit) gain a **Render** toggle in the preview head — the editor's `/sidebar/html` route iframe, with relative assets resolving inside the route and segmented reads rendering the full document; always sandboxed (opaque origin + CSP header, no escape hatch). `.pdf` ops (read / write / edit) gain the same **Render** toggle — the editor's PDF preview reused verbatim (media-route bytes + explicit Blob, the browser's native viewer inline, with a download fallback)
|
||||
- **🧩 Background Tasks**: agent topology + background tasks (exit codes / live output / force-kill)
|
||||
- **💬 Side Chat (beta)**: Codex-style side threads — the child inherits the parent's FULL context (completed turns + the pending question + the in-progress turn's assistant output and tool activity, honestly frozen as "interrupted") and runs independently without entering the main conversation; threads support continuous follow-ups (auto-resumed after a DSH restart) and one-click "Save as new session" promotion to a top-level session
|
||||
- **🖥️ Native right sidebar + plugin bottom workbench**: the right column belongs to DSH 0.1.5's own sidebar — the plugin registers every tab type as a native tab (file opens go through `dsh-resource://file/**`, and the built-in Files page / file tree is taken over), keeping only its own bottom workbench (split panes / terminals / per-session persistence) whose toggle sits in the session header
|
||||
- **📌 Pinned Terminals**: right-click a terminal tab to "Pin to Workspace / Pin Globally" — pinned terminals survive session switches and surface inline in the TabBar as virtual tabs (click activates in-place, PTY connects directly to the home session's PTY via WS, no session jump needed); agent terminals exempted from reconcile removal
|
||||
- **🖥️ Native right sidebar + plugin bottom workbench**: the right column belongs to DSH's native right sidebar — the plugin registers every tab type as a native tab (file opens go through `dsh-resource://file/**`, and the built-in Files page / file tree is taken over), keeping only its own bottom workbench (split panes / session persistence) whose toggle sits in the session header
|
||||
- **🔁 Session Isolation**: layout / tabs / panels persisted per session, stale state auto-purged
|
||||
- **⚙️ Declarative Settings**: per-item toggles in the "Side Cards" settings section, secondary settings via the gear dialog
|
||||
- **⚡ On-demand Loading**: only ~325KB core at startup; heavy deps (terminal / editor / mermaid diagrams) load on demand ([design](docs/plans/2026-08-12-lazy-chunks-design.md))
|
||||
- **⚡ On-demand Loading**: only ~325KB core at startup; heavy deps (editor / mermaid diagrams / third-language dictionaries) load on demand ([design](docs/plans/2026-08-12-lazy-chunks-design.md))
|
||||
- **🌏 i18n**: UI text follows DSH's language (zh / en) with live switching; with the optional `@huanlin/dsh-plugin-better-locale` peer, 19 third-language overlays (ja / de / fr / …) are available
|
||||
|
||||
> 🔌 **Core principle**: service-first — the 8 built-in tabs + 6 viewers register through the same `ctx.betterSidebar` API as third-party plugins, with fully equal capabilities; anything the ecosystem can provide better is delegated to ecosystem plugins (**28+ ecosystem plugins** already — see "🌐 Plugin Ecosystem" below). See "🔌 Service API" and the [external plugin guide](./docs/external-plugin-guide.md).
|
||||
@@ -64,30 +65,28 @@
|
||||
**Prerequisites**: DSH installed (`dsh web` boots), Node.js ≥ 20, pnpm ≥ 10.
|
||||
|
||||
**Supported DSH versions**:
|
||||
<a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="Supported DSH versions (v0.19.1): 0.1.5-rc.1+ (verified on rc.2)" src="https://img.shields.io/badge/DSH-0.1.5--rc.1%2B_%28verified_rc.2%29-4d6bfe" /></a>
|
||||
<a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="Supported DSH versions (v0.20.0): 0.1.6-alpha.2+" src="https://img.shields.io/badge/DSH-0.1.6--alpha.2%2B-4d6bfe" /></a>
|
||||
|
||||
> 📌 **Stable release**: starting with `v0.19.0` the plugin targets DSH **0.1.5-rc.1+** (npm dist-tag `latest`; `v0.19.1` is verified end-to-end against **0.1.5-rc.2**, so rc.1 hosts need no DSH upgrade — the peer floor stays `^0.1.5-rc.1`). Hosts still on DSH 0.1.5-alpha.2 should stay pinned to `dsh-better-sidebar@0.19.0-alpha.1`; the 0.1.2-rc.1 stable line keeps using `dsh-better-sidebar@0.18.x`; DSH ≤ 0.1.1-rc.2 should use `dsh-better-sidebar@0.17.1`.
|
||||
> 📌 **Stable release**: starting with `v0.20.0` the plugin targets DSH **0.1.6-alpha.2+** (npm dist-tag `latest`; peer floor `^0.1.6-alpha.2`). **The floor had to move**: semver's prerelease rule means `^0.1.5-rc.1` can never match any `0.1.6` prerelease. Hosts still on DSH 0.1.5-rc.* (including the 0.1.5-rc.2 that is npm's `latest`) should pin **`dsh-better-sidebar@0.19.1`** — 0.1.6 carries two genuinely breaking contracts and this version writes no runtime compatibility layer; DSH 0.1.5-alpha.2 and earlier should likewise stay on their old versions (`0.19.0-alpha.1` / `0.18.x` / `0.17.1`).
|
||||
|
||||
```sh
|
||||
dsh plugin --profile web add dsh-better-sidebar@latest # first run fails: pnpm 11 blocks node-pty build scripts (the dependency is still written)
|
||||
cd ~/.dsh/profiles/web && pnpm approve-builds --all # allow the build scripts (re-runs the install automatically)
|
||||
dsh plugin --profile web add dsh-better-sidebar@latest # re-run succeeds
|
||||
dsh plugin --profile web add dsh-better-sidebar@latest
|
||||
```
|
||||
|
||||
> Since v0.20.0 the plugin depends on no package that needs a build script (`node-pty` left with the plugin's own terminal), so installing is **one step** — the `pnpm approve-builds` re-run is no longer part of it. DSH 0.1.6 also ships a Web **Plugins page** (`ui-plugin-manager`) plus the `plugin_manager` tool, so you can enable / disable it right there once installed.
|
||||
|
||||
Then **hard-refresh the browser** (Cmd/Ctrl+Shift+R) to see the sidebar (DSH hot-reloads client changes; only host-half updates need a restart).
|
||||
|
||||
**Or let DSH install it for you** — paste this prompt into any DSH session:
|
||||
|
||||
```text
|
||||
Install the dsh-better-sidebar plugin (a sidebar workbench for DSH):
|
||||
1. Run: dsh plugin --profile web add dsh-better-sidebar@latest (the first run fails because pnpm 11 blocks node-pty build scripts — that's expected)
|
||||
2. In ~/.dsh/profiles/web run: pnpm approve-builds --all (allows the build scripts and re-runs the install)
|
||||
3. Run the add command again: dsh plugin --profile web add dsh-better-sidebar@latest
|
||||
4. When done, remind me to hard-refresh the browser (Cmd/Ctrl+Shift+R)
|
||||
1. Run: dsh plugin --profile web add dsh-better-sidebar@latest (one step since v0.20.0 — no build-script approval needed)
|
||||
2. When done, remind me to hard-refresh the browser (Cmd/Ctrl+Shift+R)
|
||||
If anything fails, check the troubleshooting table in the README at https://github.com/omdsh-dev/DSH-better-sidebar
|
||||
```
|
||||
|
||||
**Option 3: one-shot script** — from a clone of this repo, run `bash scripts/install.sh` (macOS / Linux / Windows Git Bash; native Windows uses `install.ps1`; `-h` for options) — it automates add → approve-builds → re-run.
|
||||
**Option 3: one-shot script** — from a clone of this repo, run `bash scripts/install.sh` (macOS / Linux / Windows Git Bash; native Windows uses `install.ps1`; `-h` for options) — it installs and registers the bundle in one go (including idempotent cleanup of an old manual mount row).
|
||||
|
||||
<details>
|
||||
<summary><b>Updating</b></summary>
|
||||
@@ -105,12 +104,11 @@ or bump the version in `~/.dsh/profiles/web/package.json` (e.g. `"^0.16.1"`) and
|
||||
|
||||
| Symptom | Cause & fix |
|
||||
|---|---|
|
||||
| `Ignored build scripts` | pnpm 11 blocked build scripts. Run `pnpm approve-builds --all` in the profile directory (`~/.dsh/profiles/web`). |
|
||||
| `Ignored build scripts` | pnpm 11 blocked a **transitive** dependency's build script. Run `pnpm approve-builds` (no `--all`) in the profile directory (`~/.dsh/profiles/web`) and follow its prompts — since v0.20.0 the plugin itself has no build-script dependency. |
|
||||
| `minimum release age` / version `< 24h` | The release is younger than 24 hours. Wait, or re-run once (pnpm auto-adds `minimumReleaseAgeExclude`). |
|
||||
| "profile directory not found" | Run `dsh web` once so it initializes `~/.dsh/profiles/web`. |
|
||||
| Two sidebars on the page | Double-mount. Old hand-written line: `~/.dsh/profiles/web/cordis.patch.yml` still has `- insert: ... better-sidebar ...` — delete it (a same-id duplicate mount makes the loader fail loudly with `duplicate loader entry id`). When an aggregate bundle (e.g. `@linxin666/dsh-web-ui-all`) mounts this package under a **different** id, the plugin's own bundle patch backs off automatically since 0.13.x (it detects an already-enabled mount of the same package name and does not mount itself) — no manual fix needed; if it still double-mounts, make sure the aggregate bundle precedes `dsh-better-sidebar` in `dsh.profile.bundles`. |
|
||||
| Terminal fails on Windows | `node-pty` relies on prebuilt binaries; if none match your Node version, install a build toolchain (VS Build Tools). Mainstream Node versions are usually covered. |
|
||||
| Terminal shows "node-pty failed to load" | The `node-pty` install is missing or broken (e.g. pnpm skipped its build script). The terminal banner shows a repair command — copy it into a terminal/cmd on the DSH machine and run it (in `~/.dsh/profiles/web`: `pnpm approve-builds --all && pnpm rebuild node-pty`), then restart DSH and click Retry. The plugin and DSH core share the same `node-pty@^1.1.0`, so the repair restores both. |
|
||||
| Terminal unusable / shell fails to start | The terminal and `node-pty` now both come from **DSH's own `ui-sidebar-terminal`** (this plugin no longer ships one since v0.20.0). Consult DSH's own docs for problems; if the error mentions build scripts, see the row above. |
|
||||
| `dsh: command not found` | Install DSH first, or run `npx -y --package @deepseek-ai/dsh dsh plugin --profile web add dsh-better-sidebar@latest`. |
|
||||
|
||||
</details>
|
||||
@@ -166,8 +164,8 @@ Update: `git pull && pnpm install && pnpm build` → `node scripts/package-regis
|
||||
|---|---|
|
||||
| **🗂️ File Workbench: Explorer**<br/><sub>Two explorer modes: embedded in the file preview / standalone file tree. Lazy-loading directory tree, symlinks classified by target kind (directory links expand, dangling links flagged), global filename search, file/folder upload buttons plus drag-drop upload, context menu (open in new tab / open to the side / copy paths), and a hover `@file` button that references a file straight into the composer.</sub><br/><div align="center"><img width="420" alt="File explorer" src="https://github.com/user-attachments/assets/a410bfd2-a8ba-43e6-873e-22417756e94d" /></div> | **📝 Inline Preview: Markdown · Images · PDF**<br/><sub>The Markdown preview renders **Mermaid diagrams** (strict-mode safe rendering + a second sanitize pass; click a diagram for a zoom modal with wheel-zoom and drag-pan), **README-level inline HTML** (badge walls `<div align=center>`, `<details>` blocks nesting markdown, inline tags in table cells — DOMPurify-sanitized, `<script>` stripped, local media rewritten to the session media route) and a floating **table of contents** (appears with ≥3 headings, smooth-scroll jumping, auto-expanding folded blocks); images / PDFs display inline via the media route; the Office suite is covered by an ecosystem plugin.</sub><br/><div align="center"><img width="420" alt="Markdown + Mermaid preview" src="https://github.com/user-attachments/assets/fe0e5182-55bb-45cc-b98b-a2877c2bdd38" /></div> |
|
||||
| **🖥️ CodeMirror editor**<br/><div align="center"><img width="420" alt="CodeMirror editor" src="https://github.com/user-attachments/assets/b44b488e-568c-4ee0-b96c-e9c906598a77" /></div> | **🖼️ Inline image preview**<br/><div align="center"><img width="420" alt="Inline image preview" src="https://github.com/user-attachments/assets/f9a58c30-5b7a-48b5-9e22-37d7e071f593" /></div> |
|
||||
| **💻 Real Terminal**<br/><sub>xterm.js + node-pty real shell (not an emulator): transcript replay on reconnect, configurable shell / shellArgs (settings page or `cordis.patch.yml`), and optional `terminal_*` model tools so the agent can open terminals and run commands itself.</sub><br/><div align="center"><img width="420" alt="Real terminal" src="https://github.com/user-attachments/assets/0dad6ad3-ff3f-4b5a-86d2-f832ce65323e" /></div> | **🌿 Changes: Git lens + This-Session lens**<br/><sub>Two lenses on "what changed?": the **Git lens** keeps the full source-control surface (stage / unstage / commit (`Ctrl+Enter`) / revert, history, worktree and child-repo selectors); the **This Session** lens folds the session event log live, recording every file the model read / wrote / edited (grouped by file, kind-filtered, op-count badge). Clicking any change previews it in the **draggable bottom pane** with the unified diff — del red / add green / mod-blue pairing + intra-line character highlights + syntax coloring + context folding — or expands into a VSCode-style dedicated diff tab (same rendering stack).</sub><br/><div align="center"><img width="420" alt="Changes" src="https://github.com/user-attachments/assets/e7fc1220-305f-4bca-8583-e77ab4f4fa78" /></div> |
|
||||
| **🌐 Embedded Browser**<br/><sub>Multiple web tabs with back / forward / reload / address bar; content runs in an **opaque-origin sandboxed iframe** (live sandbox status in the UI, per-page temporary unlock available); external-link clicks in the chat can be taken over into the sidebar (protocol-based routing, configurable).</sub><br/><div align="center"><img width="420" alt="Embedded browser" src="https://github.com/user-attachments/assets/9bc6b65a-64fc-4942-a685-76e391e55606" /></div> | **🧩 Tasks: Agent Topology + Background Jobs**<br/><sub>Live subagent-tree topology (run states, batched live previews) plus the background-jobs list (exit codes / live output / force-kill); new subagents / jobs can auto-activate the Tasks page, expanding the sidebar on wide viewports without forcing narrow full-screen drawers open (configurable).</sub><br/><div align="center"><img width="420" alt="Tasks: subagent topology" src="https://github.com/user-attachments/assets/dcd8ed2f-59fa-405b-937b-2d250f5034dd" /></div> |
|
||||
| **💻 Terminal (provided by DSH)**<br/><sub>Starting with v0.20.0 the right-Sidebar terminal is provided by DSH's own `ui-sidebar-terminal`: shell picker, double-click rename, reconnect, restore after reload, theme and contrast following. The plugin no longer ships xterm + node-pty.<br/><br/>⚠️ **Model-side caveat**: the plugin's own 8 `terminal_*` tools (off by default) were the model's only **cross-call persistent** terminal; the upstream equivalent `@deepseek-ai/dsh-tool-terminal` is not mounted by any shipped bundle, so if you need that capability, add a `tool-terminal` row to your profile's `cordis.patch.yml` yourself.</sub><br/><div align="center"><img width="420" alt="Terminal" src="https://github.com/user-attachments/assets/0dad6ad3-ff3f-4b5a-86d2-f832ce65323e" /></div> | **🌿 Changes: Git lens + This-Session lens**<br/><sub>Two lenses on "what changed?": the **Git lens** keeps the full source-control surface (stage / unstage / commit (`Ctrl+Enter`) / revert, history, worktree and child-repo selectors); the **This Session** lens folds the session event log live, recording every file the model read / wrote / edited (grouped by file, kind-filtered, op-count badge). Clicking any change previews it in the **draggable bottom pane** with the unified diff — del red / add green / mod-blue pairing + intra-line character highlights + syntax coloring + context folding — or expands into a VSCode-style dedicated diff tab (same rendering stack).</sub><br/><div align="center"><img width="420" alt="Changes" src="https://github.com/user-attachments/assets/e7fc1220-305f-4bca-8583-e77ab4f4fa78" /></div> |
|
||||
| **🌐 Browser (pages provided by DSH)**<br/><sub>Web tabs are provided by DSH's own `ui-sidebar-browser` (multiple tabs / back-forward-reload / address bar / sandboxed iframe). The plugin keeps the half the host does not provide: the **external-link takeover** in the chat and UI (protocol-based routing — HTTP into the sidebar, HTTPS to the system browser, each configurable; Ctrl/Cmd-clicks always pass through).</sub><br/><div align="center"><img width="420" alt="Browser" src="https://github.com/user-attachments/assets/9bc6b65a-64fc-4942-a685-76e391e55606" /></div> | **🧩 Tasks: Agent Topology + Background Jobs**<br/><sub>Live subagent-tree topology (run states, batched live previews) plus the background-jobs list (exit codes / live output / force-kill); new subagents / jobs can auto-activate the Tasks page, expanding the sidebar on wide viewports without forcing narrow full-screen drawers open (configurable).</sub><br/><div align="center"><img width="420" alt="Tasks: subagent topology" src="https://github.com/user-attachments/assets/dcd8ed2f-59fa-405b-937b-2d250f5034dd" /></div> |
|
||||
| **💬 Side Chat (beta)**<br/><sub>Codex-style side threads: **one independent tab per conversation**; the thread inherits the parent's full context (including the in-progress turn, honestly frozen as "interrupted") and runs independently without polluting the main session; follow-ups survive restarts; one click promotes the thread to a top-level session.</sub><br/><div align="center"><img width="420" alt="Side Chat (beta)" src="https://github.com/user-attachments/assets/3a338c36-f5de-4000-95f3-4b1cd04f60fc" /></div> | **🖥️ DSH's native right sidebar + plugin bottom workbench**<br/><sub>The right column is DSH's own sidebar: the plugin registers every tab type as a native tab (including the built-in Files page and file previews), so clicking a file in the chat lands there directly; the plugin's own bottom panel can stay open alongside it — drag a tab to a pane edge to **split**, to the middle to **merge**, drag the top edge to resize; the toggle lives in the session header.</sub><br/><div align="center"><img width="420" alt="Dual workbench (right sidebar + bottom panel)" src="https://github.com/user-attachments/assets/dfdb875e-a1a8-4d4b-8340-353736b1708f" /></div> |
|
||||
| **⚙️ Declarative Settings**<br/><sub>The "Side card" section in DSH settings: one small card per tab / viewer with an independent toggle (highlighted enabled state + brand switch); secondary settings open from the "Feature settings" strip at the card bottom (switch / text / number / select rows); plugin-owned settings persist under `pluginSettings`.</sub><br/><div align="center"><img width="420" alt="Declarative settings: side cards" src="https://github.com/user-attachments/assets/0800ca64-621e-48da-b7df-aecfddc3ec29" /></div> | **📱 Mobile**<br/><sub>On narrow screens (<768px) the panels become a full-width drawer: bottom-panel tabs merge into the sidebar once, with touch-friendly dragging.</sub><br/><div align="center"><img width="360" alt="Mobile full-width drawer" src="https://github.com/user-attachments/assets/a82ba78a-f4cf-4d85-80e8-050a05beb144" /></div> |
|
||||
|
||||
@@ -267,7 +265,18 @@ The GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sid
|
||||
<a href="https://github.com/user-attachments/assets/946f7028-4967-461e-a750-d1b5056b62d0"><img width="33%" alt="Service API base screenshot" src="https://github.com/user-attachments/assets/946f7028-4967-461e-a750-d1b5056b62d0" /></a>
|
||||
</div>
|
||||
|
||||
**Supported DSH versions**: <a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="Supported DSH versions (v0.19.1): 0.1.5-rc.1+ (verified on rc.2)" src="https://img.shields.io/badge/DSH-0.1.5--rc.1%2B_%28verified_rc.2%29-4d6bfe" /></a> · full release history on the [Releases](https://github.com/omdsh-dev/DSH-better-sidebar/releases) page
|
||||
**Supported DSH versions**: <a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="Supported DSH versions (v0.20.0): 0.1.6-alpha.2+" src="https://img.shields.io/badge/DSH-0.1.6--alpha.2%2B-4d6bfe" /></a> · full release history on the [Releases](https://github.com/omdsh-dev/DSH-better-sidebar/releases) page
|
||||
|
||||
### v0.20.0
|
||||
|
||||
> 📌 **Stable release** (npm `latest`, no prerelease suffix): supports **DSH 0.1.6-alpha.2+** only (peer floor `^0.1.6-alpha.2`, CI pins `@deepseek-ai/dsh@0.1.6-alpha.2`). **This is a breaking-change release**: hosts on 0.1.5-rc.* should pin `dsh-better-sidebar@0.19.1`. See [docs/plans/2026-09-21-dsh-0.1.6-alpha.2-adaptation.md](docs/plans/2026-09-21-dsh-0.1.6-alpha.2-adaptation.md) for the adaptation record.
|
||||
|
||||
- 🖥️ **Terminal handed to DSH's built-in**: DSH 0.1.6 ships its own right-column terminal (`ui-sidebar-terminal`), so the plugin deleted its whole terminal stack — `pty-manager` / `agent-pty` / `pty-deps` / the 8 `terminal_*` tools / the xterm view and font-linking / cross-session terminal pinning — along with the `node-pty` dependency, its `allowBuilds` grant and the installer's `-Repair` mode. **Note**: the model therefore loses a cross-call persistent terminal (only one-shot `bash` / `pwsh` remain); the upstream `@deepseek-ai/dsh-tool-terminal` is not mounted by any shipped bundle, so enable it yourself in the profile when you need it.
|
||||
- 🌐 **Browser view handed to the built-in**: the plugin deleted `BrowserView` / the sandbox status bar / the embeddability probe (including the host's `browser.probe` route) and the two settings that only configured its own iframe; it **keeps** the external-link takeover in the chat and UI (protocol-based routing is the capability the host does not have), now targeting DSH's built-in `browser` tab type.
|
||||
- 🧩 **The produced-files row goes back to the host**: DSH 0.1.6 turned `conversation.chat.turnTail` from a chain into a list (upstream deliberately lets several plugins **append** rather than replace each other), so the plugin's takeover could only duplicate the built-in deliverables card and was removed wholesale; click behaviour is unchanged (the built-in goes through the host's `openFile` → this plugin's editor). The cost is the one "reveal in the file tree" entry at the turn tail (the in-tree context menu remains).
|
||||
- 🐛 **Two silent breakages fixed**: once `SidebarRightGuideEntry.id` became required, a missing id left the **whole native surface silently empty** (the throw happens inside an `ctx.inject` callback and is swallowed); once `turnTail` became a list the old registration threw outright and the row died. Both are fixed and guarded by tests, with a registration-failure reporting hook added alongside.
|
||||
- 🎨 **The empty-pane card now matches the built-in card recipe**: `.paneCard` adopts DSH's guide-capsule recipe (0.5px l4 border / 24px radius / layer-1 fill / 56px minimum / 14×20 padding), with tests guarding its geometry, its tokenized colours, and that it does not copy upstream's **undefined** `--dsw-alias-bg-l1/-l2` spellings.
|
||||
- 📐 **Baseline**: every `@deepseek-ai/dsh-*` package is pinned to `0.1.6-alpha.2`, the vanished `dsh-code-runtime` package is removed, `diff` / `simple-icons` are hoisted into devDependencies as primitives' new bare imports, and `ui-primitives`' three breakages this release (`IconSendOutline16` gone, `TerminalBlockLabels.noExitCode` added, `ConnectionIndicator.reconnectLabel` removed) are adapted.
|
||||
|
||||
### v0.19.1
|
||||
|
||||
@@ -641,11 +650,11 @@ make clean # remove lib/, *.tgz, playwright-report/, test-results/
|
||||
- Office-suite preview (.docx/.xlsx/.pptx) moved to the recommended office plugin (see the "Add plugins" modals in settings); without it these files fall through to the code/download fallbacks
|
||||
- Browser sandbox has no login state / third-party cookies are restricted; some sites need popup login; sites that refuse embedding via `X-Frame-Options`/`frame-ancestors` (e.g. arxiv.org) show a reason panel (with "Open in browser"); in-iframe navigation does not enter the back stack
|
||||
- HTML preview renders the saved file (not unsaved drafts)
|
||||
- No bottom panel on mobile (<768px): on narrow screens its tabs merge into the right sidebar once (after migrating back to desktop they stay in the right sidebar); the desktop bottom panel is only available on wide viewports; auto-open terminal on first bottom-panel expand does not trigger on mobile. Without a selected session, tapping the subdued toggle shows the select-session message; with a selected session, it opens the full-width drawer
|
||||
- No bottom panel on mobile (<768px): on narrow screens its tabs merge into the right sidebar once (after migrating back to desktop they stay in the right sidebar); the desktop bottom panel is only available on wide viewports. Without a selected session, tapping the subdued toggle shows the select-session message; with a selected session, it opens the full-width drawer
|
||||
|
||||
## 🖥️ Platform Support
|
||||
|
||||
Windows / Linux / macOS (macOS validated daily; the rest covered by unit tests); `node-pty` prefers prebuilt binaries, otherwise a build toolchain is required (Windows VS Build Tools / Linux make+g+++python3 / macOS Xcode CLT).
|
||||
Windows / Linux / macOS (macOS validated daily; the rest covered by unit tests). Since v0.20.0 the plugin carries no native dependencies (the terminal and `node-pty` went back to DSH itself), so building needs only Node + pnpm, with no compiler toolchain.
|
||||
|
||||
## 💬 Community
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> 面向 **消费插件开发者**:如何让你的插件向 better-sidebar 注册新的侧边栏页面(tab)和文件类型预览器。
|
||||
>
|
||||
> 适用版本:**v0.4.0+**(`ctx.betterSidebar` 服务);声明式设置 **v0.4.1+**;text/number 设置行 **v0.11.0+**;badge/生命周期/定向打开/插件设置/版本探测 **v0.12.0+**;select 设置行(`settingSelect`)与外链认领(`urlTarget`)**v0.13.0+**;统一 `@deepseek-ai/cordis` 类型基底 **v0.15.2+**;终端固定(pin)**v0.17.0+**。当前版本 **v0.19.1**(正式版,npm `latest`,仅支持 DSH **0.1.5-rc.1+**,已在 **0.1.5-rc.2** 上完成真机挂载验证;**0.1.5-alpha.2 及更早不再支持**——alpha.2 用户请用 v0.19.0-alpha.1,那是它的最后一版;0.1.2-rc.1 稳定线请用 v0.18.x)。**v0.19.0 移除了自绘右侧面板与自由窗口**(见 §0、§11)。
|
||||
> 适用版本:**v0.4.0+**(`ctx.betterSidebar` 服务);声明式设置 **v0.4.1+**;text/number 设置行 **v0.11.0+**;badge/生命周期/定向打开/插件设置/版本探测 **v0.12.0+**;select 设置行(`settingSelect`)与外链认领(`urlTarget`)**v0.13.0+**;统一 `@deepseek-ai/cordis` 类型基底 **v0.15.2+**。当前版本 **v0.20.0**(正式版,npm `latest`,仅支持 DSH **0.1.6-alpha.2+**)。**v0.19.0 移除了自绘右侧面板与自由窗口**(见 §0、§11);**v0.20.0 移除了插件自带的终端与浏览器视图**(宿主 0.1.6 自带两者,见 §4.4)、**移除了终端固定(pin)**(终端本身不再由本插件提供),并**删除了轮尾产物行接管**(DSH 0.1.6 把 `conversation.chat.turnTail` 从 chain 改成只能追加的 list,替换语义不复存在)。
|
||||
> 权威代码:`src/client/service.ts`(服务实现)、`src/client/builtins/`(内置 8 tab + 6 viewer 参考实现)、`lib/types/client/service.d.ts`(类型声明)。
|
||||
> 仓库开发规则(硬约束 / CI / 发版)见 [AGENTS.md](../AGENTS.md)。
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
## 0. 承载面:DSH 原生右侧栏 + 插件底部工作台(v0.19.0-alpha.0 起)
|
||||
|
||||
从 v0.19.0-alpha.0 起,**右列完全属于 DSH**:你的 tab 渲染在 **DSH 自己的右侧栏**里(`ctx.sidebarRight` / `ctx.sidebarRightTabs`),插件把每个 `TabDescriptor` 注册成原生 tab 类型(`kind = descriptor.id`)+ 一个原生 tab 体。插件自己只保留**底部工作台**(分栏树、终端、会话内持久化)。对你的接入代码**没有影响**——仍然只调用 `ctx.betterSidebar`:
|
||||
从 v0.19.0-alpha.0 起,**右列完全属于 DSH**:你的 tab 渲染在 **DSH 自己的右侧栏**里(`ctx.sidebarRight` / `ctx.sidebarRightTabs`),插件把每个 `TabDescriptor` 注册成原生 tab 类型(`kind = descriptor.id`)+ 一个原生 tab 体。插件自己只保留**底部工作台**(分栏树、会话内持久化;**v0.20.0 起其中不再有终端**——宿主 0.1.6 的右侧栏终端取代了它)。对你的接入代码**没有影响**——仍然只调用 `ctx.betterSidebar`:
|
||||
|
||||
- `registerTab` / `registerFileViewer` 签名不变;
|
||||
- `openTab` / `openFile` 默认落到原生右侧栏;新增可选 `OpenTabSeed.target`(`'right'` 默认 / `'bottom'` 落插件的底部工作台);
|
||||
@@ -21,7 +21,7 @@
|
||||
| 事项 | 说明 |
|
||||
|---|---|
|
||||
| 生命周期回调 | 原生面只有「一次打开」,不区分新建/聚焦,因此只触发 `onOpen`(`onActivate` 仅在插件自己的底部工作台里触发) |
|
||||
| 去重 | 原生按 `(kind, 地址)` 去重:有 `createTab` 的类型每次新开一个 tab(terminal / browser / sidechat / diff),其余聚焦已有 tab;`dedupeKey` 的自定义语义不参与原生面 |
|
||||
| 去重 | 原生按 `(kind, 地址)` 去重:有 `createTab` 的类型每次新开一个 tab(sidechat / diff),其余聚焦已有 tab;`dedupeKey` 的自定义语义不参与原生面 |
|
||||
| 布局持久化 | 原生栏的布局**只在内存**(刷新后回到折叠默认),插件自己的底部工作台仍然持久化 |
|
||||
| 跨会话打开 | 目标会话的右侧栏 store 未挂载时,打开会排队到该会话上屏后重放 |
|
||||
| 内置类型接管 | 插件的 `editor` 类型以 `extension` 优先级认领 `dsh-resource://file/**`(压过内置 `ui-sidebar-documentpreview` 的 `text` 预览——即 `fallback` 带),并接管内置 `files` 页面 kind(`openTab('files')` 打开插件的文件树);插件卸载/禁用时内置实现自动复位 |
|
||||
@@ -42,7 +42,7 @@ better-sidebar 从 v0.4.0 起把自己改造成一个**注册表服务**:
|
||||
- **新页面(tab)**:注册一种新的侧边栏 tab 类型,出现在侧边栏 `+` 菜单里,用户点击后在自己的分栏里打开你的 React 页面;
|
||||
- **文件预览器(file viewer)**:注册一种文件类型预览器,让用户在侧边栏打开文件时走你的渲染组件(覆盖或补充内置的 image/pdf/code 等)。
|
||||
|
||||
内置的 7 个 tab(editor / git——「文件变动」统一 tab(Git 视角 + 本轮文件视角)/ subagent / sidechat / terminal / browser / diff)和 6 个 viewer(image / pdf / markdown / html / code / binary-download)**自己也是通过同一套 API 注册的**(吃自己的狗粮),所以外部插件的能力与内置功能完全对等。
|
||||
内置的 5 个 tab(editor / git——「文件变动」统一 tab(Git 视角 + 本轮文件视角)/ subagent / sidechat / diff)和 6 个 viewer(image / pdf / markdown / html / code / binary-download)**自己也是通过同一套 API 注册的**(吃自己的狗粮),所以外部插件的能力与内置功能完全对等。**v0.20.0 起不再内置 terminal / browser 两个 tab 类型**:DSH 0.1.6 自带右列终端(`ui-sidebar-terminal`、kind `terminal`)与浏览器(`ui-sidebar-browser`、kind `browser`),插件的同名类型会让读者看到两份实现。
|
||||
|
||||
关键机制一句话:better-sidebar 的 client half 在 `apply()` 开头执行 `ctx.provide('betterSidebar', service)`(`src/client/index.tsx`),消费插件在 `inject` 里声明 `'betterSidebar'`,Cordis 保证服务就绪后才激活你的插件,然后你调用 `ctx.betterSidebar.registerTab(...)` / `registerFileViewer(...)` 完成注册,返回的 disposer 由 Cordis fiber 在卸载(HMR / 禁用)时自动调用。
|
||||
|
||||
@@ -192,11 +192,11 @@ interface TabDescriptor {
|
||||
description?: string | (() => string)
|
||||
/** 图标:ReactNode 或 (size: number) => ReactNode(不声明时宿主补一个方块占位) */
|
||||
icon?: ReactNode | ((size: number) => ReactNode)
|
||||
/** + 菜单排序(升序);默认 100。内置:editor=10, git=20, subagent=30, sidechat=35, terminal=40, browser=50 */
|
||||
/** + 菜单排序(升序);默认 100。内置:editor=10, git=20, subagent=30, sidechat=35 */
|
||||
order?: number
|
||||
/** 从 + 菜单隐藏(editor/diff 用:由其他流程触发打开,不在菜单里) */
|
||||
hidden?: boolean
|
||||
/** + 菜单禁用判定(如 terminal 配额满)。返回 false 只影响菜单 disabled,不拦截 openTab(只有设置页禁用开关会)。 */
|
||||
/** + 菜单禁用判定(用量配额一类)。返回 false 只影响菜单 disabled,不拦截 openTab(只有设置页禁用开关会)。 */
|
||||
available?: (ctx: Context, scope: SessionScope, state: SidebarState) => boolean
|
||||
/**
|
||||
* 单实例语法糖:`single: true` ≡ `dedupeKey: () => id`(打开时聚焦既有
|
||||
@@ -212,7 +212,7 @@ interface TabDescriptor {
|
||||
dedupeKey?: (tab: SidebarTab) => string | undefined
|
||||
/**
|
||||
* 自定义 tab 创建(minting SidebarTab + 状态 patch)。
|
||||
* 返回 null 拒绝创建。terminal 用它生成 terminal:<n> id 并递增 nextTerminal。
|
||||
* 返回 null 拒绝创建。sidechat 用它实现「一个线程一个 tab」。
|
||||
* 省略时用默认 { id, type, title } + seed 里的 path/diff。
|
||||
*/
|
||||
createTab?: (state: SidebarState) => { tab: SidebarTab; patch?: Partial<SidebarState> } | null
|
||||
@@ -220,7 +220,8 @@ interface TabDescriptor {
|
||||
* 外链认领(v0.13.0+,`features.includes('urlTarget')` gate):外链被接管
|
||||
* (`browserInterceptLinks` 总闸 + 协议开关均开)时,第一个 urlTarget 命中且未禁用的
|
||||
* 类型以 openTab({ type, url, title: hostname }) 打开,URL 预填 tab.path。先到先得;
|
||||
* 谓词抛错被吞。内置 browser 不声明、永远隐式兜底。
|
||||
* 谓词抛错被吞。v0.19.1 及更早内置 browser 隐式兜底;**v0.20.0 起本插件不再有
|
||||
* browser 类型**,无可认领的默认目标时接管直接放行给系统浏览器。
|
||||
* 多 URL 并存需 createTab 铸造 per-URL id,否则二次点击被 id 安全网聚焦、不覆写 path。
|
||||
*/
|
||||
urlTarget?: (url: URL) => boolean
|
||||
@@ -240,8 +241,8 @@ interface TabDescriptor {
|
||||
* - onOpen:openTab 真正**新建** tab 后(dedupe/id 安全网聚焦不算打开);
|
||||
* - onActivate:tab 被聚焦时(dedupe 聚焦、id 安全网聚焦、tab 栏点击激活);
|
||||
* - onClose:closeTab 关闭 tab 后。
|
||||
* 内置专属流程(diff 拆分放置、agent 终端 reconcile)直接改 state,不触发
|
||||
* 回调——但它们只作用于内置类型(diff/terminal),外部插件的 tab 永远走
|
||||
* 内置专属流程(diff 拆分放置、sidechat 线程重开)直接改 state,不触发
|
||||
* 回调——但它们只作用于内置类型(diff / sidechat),外部插件的 tab 永远走
|
||||
* service 路径。回调抛错只 console.error,绝不打断打开/关闭流程。
|
||||
* openTab 回调 scope 携带调用者传入的 { sessionId, cwd? };
|
||||
* closeTab/activateTab 仅显式传 scope 时带 cwd。
|
||||
@@ -356,7 +357,7 @@ ctx.effect(() =>
|
||||
)
|
||||
```
|
||||
|
||||
**自定义创建**(mint 自增 id,terminal 内置页同款):
|
||||
**自定义创建**(mint 自增 id):
|
||||
|
||||
```ts
|
||||
ctx.effect(() =>
|
||||
@@ -365,8 +366,8 @@ ctx.effect(() =>
|
||||
title: 'Console',
|
||||
order: 80,
|
||||
createTab: (state) => ({
|
||||
tab: { id: `console:${state.nextTerminal}`, type: 'my-plugin:console', title: `Console ${state.nextTerminal}` },
|
||||
patch: { nextTerminal: state.nextTerminal + 1 }, // 借内置计数器;也可自建 state 字段
|
||||
tab: { id: `console:${state.nextBrowser}`, type: 'my-plugin:console', title: `Console ${state.nextBrowser}` },
|
||||
patch: { nextBrowser: state.nextBrowser + 1 }, // 借内置计数器;也可自建 state 字段
|
||||
}),
|
||||
component: ({ tab, scope }) => <ConsoleView tabId={tab.id} sessionId={scope.sessionId} />,
|
||||
})
|
||||
@@ -400,12 +401,19 @@ ctx.effect(() => {
|
||||
| `git` | 20 | 是 | 是(本轮文件操作数) | 「文件变动」统一 tab(id 保留 `git` 以兼容持久化布局):**Git 视角**(原 Git 面板:staged/unstaged / 提交 / 历史 / worktree·子仓库选择)+ **本轮文件视角**(原 file-trace:模型读/写/编辑实时折叠,按文件分组、类型筛选);会话事件经插件自有宿主路由 `changes.ops` 供给(live 日志优先、冷会话回放持久化记录,`afterSeq` 增量),badge 读 tab 轮询写入的同步缓存。两视角共用底部可拖拽预览面板(`tab.meta.lens/previewH` 持久化),diff 渲染统一走 `src/client/diff/`(`DiffRows`/`DiffFiles`:mod 配对 + 行内高亮 + 语法着色 + 上下文折叠);Git 目标可展开为独立 diff tab(落进工作台的 diff 分栏) |
|
||||
| `subagent` | 30 | 是 | 否 | 子代理拓扑 |
|
||||
| `sidechat` | 35 | 否(`sidechat:<uuid>`,按 `meta.threadId` 去重) | 否 | 侧边对话(每对话一 Tab):打开即建空线程(首条消息赢得标签并同步标题);线程 = 插件自建子会话(种子继承父会话上下文,进行中回合以 `interrupted` 闭合;种子带合法 `subagent/descriptor`,SubagentView 按 `Side: ` 前缀过滤),`origin:'subagent'` 隐藏于主列表;走 `/sidebar/api/sidechat.*` 路由;头部菜单切换/重开(`parkSidechatReopen` + 确定性 id),关 Tab 释放 live agent;重开经 `collectOwnEvents` 回源到种子边界;「保存为新会话」= `session.fork`(`this` 敏感)。[设计文档](plans/2026-08-20-sidechat-tab-design.md) |
|
||||
| `terminal` | 40 | 否(`terminal:<n>`) | 否 | 终端。v0.17.0+ 右键「固定到工作区/全局」:跨会话不消失,TabBar 内联虚拟 Tab(`pinned:<homeSessionId>:<tabId>`),就地按 home scope 连 PTY;global 全会话可见、workspace 仅同 cwd;`tab.pin = { scope, homeCwd? }` 随会话持久化,渲染期解析(`collectPinnedTabs` → `createPinnedVirtualTab` → `injectPinnedIntoTree`) |
|
||||
| `browser` | 50 | 否(`browser:<n>`) | 否 | 内嵌浏览器(沙箱 iframe,可设置关沙箱) |
|
||||
| `diff` | -1 | 否(按 id 去重) | 是 | 差异查看(changes tab 的预览面板「展开为独立页签」触发,同一渲染栈) |
|
||||
|
||||
你的 `id` 不可与上述重复,否则 `registerTab` 抛 `"tab type \"X\" already registered"`。
|
||||
|
||||
**v0.20.0 删除的类型(迁移提示)**:
|
||||
|
||||
| 原 id | 现状 | 你该怎么做 |
|
||||
|---|---|---|
|
||||
| `terminal` | 宿主自带 `kind: 'terminal'`(`@deepseek-ai/dsh-client-ui-sidebar-terminal`) | 不要再注册同名 kind。插件侧 API(`api.ptyClose` / `agentPtyClose` / `agentSkipWait` / `terminalDeps` / `shellGet`)与 `SidebarTab.pin` / `SidebarState.nextTerminal` / `agentWaits` / `isAgentTabId` / `agentUuidOf` / `reconcileAgentTerminals` / `mirrorAgentWaits` / `setTabPin` 全部**已删除** |
|
||||
| `browser` | 宿主自带 `kind: 'browser'`(`@deepseek-ai/dsh-client-ui-sidebar-browser`) | 要打开网页请走宿主:`ctx.sidebarRight.openTab('browser', { params: { url } })`(宿主未装该包时 `openTab` 会抛,务必 try/catch 兜底)。插件侧的 `api.browserProbe` / `SidebarPrefs.browserNoSandbox` / `browserAllowedLoopback` 已删除;`browserInterceptLinks` / `browserInterceptHttp` / `browserInterceptHttps` **保留**(链接接管仍在,`urlTarget` 机制不变) |
|
||||
|
||||
**同时删除的还有**:轮尾产物行接管。DSH 0.1.6 把 `conversation.chat.turnTail` 从 `chain` 改成 `list`(list 槽要求 `options.id`,且只能**追加**,不能替换宿主的产物行),插件因此整体移除了 `registerTurnTailInterception` 与 `selectProducedFiles`。要往那个位置加东西,按 list 契约注册 `{ name: 'conversation.chat.turnTail', id: '<你的 id>', order }`;`openSidebarFile` 作为通用打开工具留在 `dsh-better-sidebar/src/client/sidebar-file.ts`(`resolveSidebarPath` 移到 `src/client/paths.ts`)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 文件预览器(FileViewer)注册 API
|
||||
@@ -811,7 +819,7 @@ ctx.effect(() =>
|
||||
- 展示:小卡片网格(图标 + 标题 + 类型 id),**高亮 = 启用**,勾选徽标钉在卡片最右端;viewer 卡片额外显示扩展名。
|
||||
- 持久化:开关写入 `SidebarPrefs.tabsEnabled / viewersEnabled`(开放 map,**缺省 = 启用**,显式 `false` 才禁用)。
|
||||
- 关闭语义:tab 从 `+` 菜单消失、`openTab` 拒绝新开(`console.warn`)、派生流程(子代理自动展开、agent 终端自动补 tab)停止,**已打开的 tab 保留**;viewer 被 `matchFileViewer` 跳过,文件落到下一个匹配。
|
||||
- `settings.toggles`(可选):在卡片行下追加**嵌套设置行**(仅父级启用时显示),绑定 `SidebarPrefs` 字段;通过卡片底部「功能设置」条在原生弹窗中编辑。行控件形状见 §4.1 的 `SettingRow`:`type: 'switch' | 'text' | 'number'`(v0.11.0+;text/number 行 blur/Enter 提交,number 行按 min/max 钳制,unit 渲染单位后缀)与 `type: 'select'`(v0.13.0+;`options` 支持 value/title/desc/icon,`multi` 多选存数组并按 options 顺序提交;任一项带 icon 时渲染大图标选项卡)。内置示例:subagent tab 的 `autoOpenSubagent`、terminal tab 的 `agentTerminalTools` + 自定义字体行、editor tab 的 `editorExplorer` 图标化下拉与 `workspaceFence` 开关(工作区路径围栏,见 §8.1)。
|
||||
- `settings.toggles`(可选):在卡片行下追加**嵌套设置行**(仅父级启用时显示),绑定 `SidebarPrefs` 字段;通过卡片底部「功能设置」条在原生弹窗中编辑。行控件形状见 §4.1 的 `SettingRow`:`type: 'switch' | 'text' | 'number'`(v0.11.0+;text/number 行 blur/Enter 提交,number 行按 min/max 钳制,unit 渲染单位后缀)与 `type: 'select'`(v0.13.0+;`options` 支持 value/title/desc/icon,`multi` 多选存数组并按 options 顺序提交;任一项带 icon 时渲染大图标选项卡)。内置示例:subagent tab 的 `autoOpenSubagent`、editor tab 的 `editorExplorer` 图标化下拉与 `workspaceFence` 开关(工作区路径围栏,见 §8.1)。
|
||||
- `settings.pluginToggles`(可选,v0.12.0+):**插件自有设置行**,行控件与 toggles 相同,但 key 是插件局部的——持久化在 prefs 文档的 `pluginSettings[<descriptor id>]`(开放 map,无需宿主 schema 字段)。tab 与 viewer 都可用(v0.12.0 起 viewer 卡片也有设置条)。
|
||||
- `settings.render`(可选,v0.12.0+):**自定义设置面板**——追加渲染在行列表之后,可单独存在。props 含 store/service/prefs、本 descriptor 的 `pluginSettings` blob、`updatePluginSetting(key, value)` 与 `close()`;抛错会被吞掉并显示内联错误。
|
||||
|
||||
@@ -851,7 +859,7 @@ ctx.effect(() =>
|
||||
)
|
||||
```
|
||||
|
||||
> ⚠️ **`toggles` 的 key 必须是宿主 PrefsSchema 的字段**(内置键:`autoOpenSubagent` / `agentTerminalTools` / `agentOpenTools` / `terminalFontFamily` / `terminalFontSize` / `editorExplorer` / `workspaceFence` / `htmlViewerNoSandbox` / `htmlViewerDefaultUnsafe` / `browserNoSandbox` / `browserInterceptLinks` / `browserInterceptHttp` / `browserInterceptHttps` / `changesDiffFloat`)。**v0.12.0 起设置 seam 已开放**:你自己的设置走 `pluginToggles`(声明式行)或 `render`(自定义面板),值持久化在 `pluginSettings[id]`——不再需要宿主 schema 字段,也不再被 seam 丢弃。值须 JSON 可序列化(行控件只产出 string/number/boolean;自定义面板自行负责)。
|
||||
> ⚠️ **`toggles` 的 key 必须是宿主 PrefsSchema 的字段**(v0.20.0 起的内置键:`autoOpenSubagent` / `autoOpenJobs` / `agentOpenTools` / `editorExplorer` / `workspaceFence` / `titleBarScheme` / `titleBarPresetId` / `customCss` / `titleBarCompat` / `titleBarStripPx` / `htmlViewerNoSandbox` / `htmlViewerDefaultUnsafe` / `browserInterceptLinks` / `browserInterceptHttp` / `browserInterceptHttps` / `tabsEnabled` / `viewersEnabled`;**已删除**:`agentTerminalTools` / `terminalShell` / `terminalShellArgs` / `terminalFontFamily` / `terminalFontSize` / `bottomPanelAutoTerminal` / `browserNoSandbox` / `browserAllowedLoopback`)。**v0.12.0 起设置 seam 已开放**:你自己的设置走 `pluginToggles`(声明式行)或 `render`(自定义面板),值持久化在 `pluginSettings[id]`——不再需要宿主 schema 字段,也不再被 seam 丢弃。值须 JSON 可序列化(行控件只产出 string/number/boolean;自定义面板自行负责)。
|
||||
|
||||
### 8.1 内置键 `workspaceFence`(工作区路径围栏)
|
||||
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
# DSH 0.1.6-alpha.2 适配(v0.20.0 基线)
|
||||
|
||||
日期:2026-09-21 分支:`feat/dsh-0.1.6-alpha.2`
|
||||
|
||||
## 背景
|
||||
|
||||
DSH `dsh-v0.1.5-rc.2..dsh-v0.1.6-alpha.2`(上游 **1687 个 commit**,中间还有 `dsh-v0.1.6-alpha.1`;`git diff --shortstat` = 5420 个变更文件 / +878 484 / −63 637)是 0.1.6 线的第二个预发布版,也是本插件自 rc.1 以来第一次遇到**真实破坏**的 delta。两件关键的事实构成这版全部工作:
|
||||
|
||||
1. **两条插件面向的硬契约收紧,且都是静默失败**(§上游 delta 1/2);三条 `ui-primitives` 破坏是编译期可见的。
|
||||
2. **宿主内置了插件原本自研的两个面**(右列终端、右列浏览器)。继续保留就是同一面存在两套实现,并且遮蔽产品正在开发中的宿主类型。
|
||||
|
||||
本计划记录该 delta、逐项结论与子系统级落点;上一版记录见 [2026-09-10-dsh-0.1.5-rc.2-adaptation.md](./2026-09-10-dsh-0.1.5-rc.2-adaptation.md),仓库级适配要点同步到 [AGENTS.md](../../AGENTS.md) §3(本版整体重写,是该节的权威摘要)。
|
||||
|
||||
本版由三条 commit 组成(`git log --oneline -4`,`git show --stat <sha>`):
|
||||
|
||||
| # | commit | 主题 | 规模 |
|
||||
|---|---|---|---|
|
||||
| 1 | `58ba17d` | `fix(native): meet DSH 0.1.6-alpha.2 guide-id and turn-tail contracts` | 17 files,+135 / −611 |
|
||||
| 2 | `d608998` | `chore(baseline): target DSH 0.1.6-alpha.2 and drop 0.1.5-rc support` | 27 files,+448 / −350 |
|
||||
| 3 | `5912274` | `feat: yield the terminal and browser to DSH 0.1.6's own sidebar types` | 79 files,+381 / −9 464 |
|
||||
|
||||
## 上游 delta(rc.2 → alpha.2,影响本插件的部分)
|
||||
|
||||
所有 alpha.2 结论都用 tag 取证(`git -C /tmp/dsh-0162 diff dsh-v0.1.5-rc.2 dsh-v0.1.6-alpha.2 -- <path>` / `git show dsh-v0.1.6-alpha.2:<path>`),不引用工作树、不引用记忆。
|
||||
|
||||
| # | 上游变更 | 上游文件(alpha.2) | 对插件的影响 |
|
||||
|---|---|---|---|
|
||||
| 1 | `SidebarRightGuideEntry.id` 变**必填**且同一 provider 内唯一;注册时 `new Set(entries.map(e => e.id)).size !== entries.length` 抛 `duplicate guide entry id` | `packages/client/ui-sidebar-right/src/client/tab-registry.ts`(抛点 `:251`,接口定义 `:61` 起;单测 `tests/tab-registry.client.spec.ts:302`) | **破坏(静默)**:插件两个 guide 条目都在 `undefined` 上相撞 → `register` 抛错 → 抛点在本插件 `ctx.inject(['sidebarRightTabs'], cb)` 的**回调体内**被 cordis 吞掉 → 整个原生承载面空掉(指南无条目、`openTab` 全拒)。→ §决策 6 |
|
||||
| 2 | `conversation.chat.turnTail` 由 `chain` 改 `list`;list 槽要求 `options.id`,缺则抛 `list slot "…" requires options.id` | `packages/client/ui-chat/src/client/contract/slots.ts:213`;抛点在 `packages/client/ui-slots/src/index.ts:1230` | **破坏(静默)**:插件传 `select` 直接抛(同样被吞)。语义也变了——chain 的 `select` 让一个条目**替换**宿主产物行,list 只能**追加**。→ §决策 5 |
|
||||
| 3 | `ui-primitives` 三处导出面变化:`IconSendOutline16` 不再导出(只剩 `IconSendOutline14`);`TerminalBlockLabels` 新增**必填** `noExitCode`;`ConnectionIndicator` 删掉 `reconnectLabel` | `packages/client/ui-primitives/src/icons/index.tsx`(`IconSendOutline16` 在 rc.2 有、alpha.2 无)、`src/**`(`git diff … -- packages/client/ui-primitives/src` 里 `−reconnectLabel` / `+noExitCode`) | **破坏(编译期)**:适配见 §改动清单「基线」 |
|
||||
| 4 | 宿主自带右列终端:`ui-sidebar-terminal` + `api-terminal-controller`;模型侧等价物 `packages/terminal/tool-terminal`(包名 `@deepseek-ai/dsh-tool-terminal`)**未被任何 shipped bundle 挂载** | `packages/client/ui-sidebar-terminal/**`、`packages/api/terminal-controller/**`、`packages/terminal/tool-terminal/**` | **让位** → 插件整套 PTY 栈删除。证据:`git grep -rn tool-terminal -- packages/bundle/` 在 alpha.2 只有 **1 处命中且是注释**(`packages/bundle/web-app/cordis.patch.yml:404`,讨论后台 job 注册表的生产者),没有任何 bundle 行挂载该包 |
|
||||
| 5 | 宿主自带 `browser` kind,且 `ui-chat` 已内置把正文 http(s) 链接委派给它 | `packages/client/ui-sidebar-browser/**`;`packages/client/ui-chat/src/client/apply.ts:147`(`ctx.get('sidebarRightTabs')?.get('browser')` 守卫后 `ctx.sidebarRight.openTab('browser', { params: { url } })`) | **让位** → 插件 `browser` tab 类型与宿主 `browser.probe` 路由删除;DOM 层链接接管保留(宿主不做按协议分流) |
|
||||
| 6 | 原生右列布局**现在持久化**:`localStorage` 的 `dsh.sidebar-right.v1.<sessionId>`(zod 校验,tab 体渲染前恢复) | `packages/client/ui-sidebar-right/src/client/persistence.ts`(`sidebarPersistence = 'dsh.sidebar-right.v1'`)、`README.zh.md:72` | §3.10 里「原生布局只在内存,不做持久化」的旧说法对 0.1.6 起**已失效**;插件不接入、只用现成面 |
|
||||
| 7 | `sidebar-right` 新增面:`SidebarRightTabDefinition.multiple?`、`SidebarRightPlacement.preferNewPane?`、class 上的 `openTabs` / `tabsIn(sessionId)` / `registerCloseHandler(kind, handler)`(**不在** `ISidebarRight` 接口里)、keyed 槽 `sidebar.right.tab.guide.entry` | `tab-registry.ts:103`、`contract/slots.ts:117`、`service.ts:218/228/261/387` | 插件只用到 `multiple`;其余仅保证不破坏 |
|
||||
| 8 | 官方管控面:新增 `packages/boot/plugin-manager`(`ctx.pluginManager` + `plugin_manager` 工具)与 `packages/client/ui-plugin-manager`(Web Plugins 页);`dsh.profile.patchReload` 在 alpha.2 已从**代码与文档整体消失**(rc.2 由 `apps/cli/src/plugin.ts` / `profile-boot.ts` 读取 + 4 份 `apps/cli/**` 文档 10 处说明;alpha.2 在 `packages/` + `apps/` 上 **0 命中**,只剩 `.agents/notes` 的陈旧文本);HMR 归 `packages/boot/hmr` | 同上 | **零额外声明**:两者都会接管任何已声明 `dsh.bundle.patch` 的包(本插件 `package.json` 的 `dsh.bundle.patch` = `./cordis.patch.yml`),本插件无需适配。`patchReload` 本插件**从未消费**(`grep -rn patchReload src tests scripts` 零命中),因此删除对本插件零影响 |
|
||||
| 9 | 依赖形状:`ui-primitives` 仍**不声明** `dependencies`(`peerDependencies` 只有 `@deepseek-ai/cordis`),bundle 新增裸 import `diff`(`src/DiffBlock.tsx:3`)与 `simple-icons`(`src/SiteGlyph.tsx:44`,全新 3 万余品牌图标集);`packages/code-runtime` **整包消失**(rc.2 还在,npm 上停在 0.1.5-alpha.2) | `packages/client/ui-primitives/package.json`、`packages/client/ui-primitives/src/*` | devDependencies 提升 `diff` / `simple-icons`;移除 `@deepseek-ai/dsh-code-runtime` |
|
||||
|
||||
**明确未变(不必再核)**:
|
||||
|
||||
- 指南「列出的条目 ≤ 4 条才渲染说明」的规则仍在:`MAX_DESCRIBED_ENTRIES = 4`(`packages/client/ui-sidebar-right/src/client/tabs/guide/GuideBody.tsx:40`)。
|
||||
- 一次性 token 鉴权、斜杠 RPC 方言、`MarkdownText` labels 嵌套契约、会话事件模型(`assistant/assistant-stream` 瞬时帧):全部沿用 0.1.5 线结论,本版零改动。
|
||||
- 插件的承载面用法不变:仍由 `ctx.inject(['sidebarRightTabs'], cb)` 驱动类型注册(**不能**等槽声明),仍只经 `ctx.sidebarRight.openTab/openResource/close` 与具体类上的 `openTabIn/…` 操作。
|
||||
- 宿主仍有 chain 槽(如 `conversation.composer`、`sidebar.right.tab.guide`),但本插件**从未**注册它们——插件的槽面只有三处:`conversation.session.header.utilities`(底部工作台开关)、`settings.section`、原生 `sidebar.right.pane.tab` / `.title`;唯一用过的「整行接管」型贡献(轮尾产物行)随 `turnTail` 改 list 而删除(#2)。
|
||||
- `dsh.plugin.json` **不是** DSH 契约:`git grep -n 'dsh\.plugin\.json'` 在 `dsh-v0.1.5-rc.2` 与 `dsh-v0.1.6-alpha.2` 上**均 0 命中**。它只服务第三方 registry(社区市场)通道,`lib/client-registry.js` 同理。
|
||||
|
||||
## 决策
|
||||
|
||||
1. **peer 下限必须动:`^0.1.5-rc.1` → `^0.1.6-alpha.2`**。semver 的预发布规则要求比较子与候选版本同 `[major,minor,patch]` 元组,`^0.1.5-rc.1` 在数学上**永远容纳不下任何 `0.1.6-*` 预发布版**。实测(用仓库树内的真实 `semver@7.8.5`):
|
||||
|
||||
| range \ candidate | `0.1.5-rc.2` | `0.1.6-alpha.1` | `0.1.6-alpha.2` | `0.1.6-alpha.3` | `0.1.6-rc.1` |
|
||||
|---|---|---|---|---|---|
|
||||
| `^0.1.5-rc.1` | true | false | false | false | false |
|
||||
| `^0.1.5-rc.2` | true | false | false | false | false |
|
||||
| `*`(裸) | false | false | false | false | false |
|
||||
| `^0.1.6-alpha.2` | false | false | **true** | **true** | **true** |
|
||||
|
||||
即:0.1.5 的下限不是「保守」而是**结构上不可能**容纳 0.1.6 预发布;`^0.1.6-alpha.2` 天然容纳 0.1.6 的 alpha/rc 全线并排除 0.1.5。
|
||||
2. **支持线取舍:0.1.5-rc.\* 用户停留在 v0.19.1**。rc.2 → alpha.2 有真实破坏(#1/#2),按本仓库先例(v0.19.0 抬到 0.1.5-rc.1 时直接桥掉 0.1.2 线)**不写运行时兼容层**;0.1.5-alpha.2 及更早同样停留在旧版。`engines.dsh` 与 peer 下限同源(同一个支持范围,两处不能分叉)。
|
||||
3. **终端整体让位**:删除整套 PTY 栈(见改动清单),宿主 `ui-sidebar-terminal` 成为唯一提供者。**接受模型侧代价并写进 README**:插件原有的 8 个 `terminal_*` 工具(`terminal_create/list/send/read/wait/resize/signal/close`,默认关)是模型唯一的**跨调用持久**终端,而上游等价物 `@deepseek-ai/dsh-tool-terminal` 未被任何 shipped bundle 默认挂载,删除后模型只剩一次性 `bash` / `pwsh`——这是「不维护第二套 PTY」的既定代价,不是意外,所以不补自家的替代实现。
|
||||
4. **浏览器让位,但保留 DOM 层链接接管**。宿主不做按协议分流(https 默认交系统浏览器),那是插件唯一还能提供的能力,因此 `src/client/link-intercept.ts` 与已发布的 `urlTarget` 机制(v0.13.0+,第三方消费插件按它声明)不动;接管目标改指**宿主的** `browser` kind(`ctx.sidebarRight.openTab('browser', { params: { url } })`),并保留 `window.open` 兜底——宿主没装该包时不至于点了没反应。
|
||||
5. **轮尾接管整体删除**。list 只能追加,插件再注册一行只会与宿主的产物卡**重复**;上游是有意选 list(「An additive list lets both plugins contribute without either knowing the other's data or rendering」,`.agents/notes/implemented/feature/2026-09-17-persistent-plan-cards.md:27`)。产物行交回宿主,它开文件走宿主 `openFile` → 本插件的 editor,**行为不变**。文件打开的那一半(`sidebar-file.ts`)与路径助手(`paths.ts` 的 `resolveSidebarPath`)保留,因为文件变动 tab 与编辑器宿主仍在用。
|
||||
6. **guide 条目 id 作为类型契约落进插件侧**:两个条目各自带 `id`(descriptor id / `'files'`),并加测试守护「每个 guide 条目 id 非空且互不重复」。**不做运行时兜底**(自动补 id 会把契约破坏变成永久沉默,正是这次最难查的形态)。
|
||||
7. **加一条可见的失败接缝**:`registerNativeSurface` 新增可选 `reportFailure(phase, error)`,接到 `src/client/index.tsx` 的 `fail()` 可见诊断条;单个 descriptor 注册失败不再带走其余注册。理由就是这次的症状——整个原生承载面静默空掉,除了一行缺失的指南之外没有任何提示。
|
||||
8. **卡片采用宿主胶囊配方,但字重走令牌阶梯**:几何/描边/填充/圆角照抄宿主 `GuideBody.module.css` 的 `.entry`(0.5px `--dsw-alias-border-l4` / 24px 圆角 / `--dsw-alias-bg-layer-1` / `min-height: 56px` / `padding: 14px 20px`),标签字号**不抄**宿主那里的裸 `font-size: 15px`(它背后没有令牌),改用阶梯最近一档 `--dsw-font-s-strong-14`(14px medium)。皮肤契约要求插件侧零豁免面(AGENTS.md §5)。
|
||||
9. **不接入本版新增的宿主面**:`openTabs` / `tabsIn` / `registerCloseHandler` / `preferNewPane` / keyed `guide.entry` 槽 / 全局面板——插件只用到 `multiple` 与既有面。
|
||||
|
||||
## 改动清单(子系统级)
|
||||
|
||||
| 子系统 | 文件 | 变更 |
|
||||
|---|---|---|
|
||||
| 基线 | `package.json` | `version` → `0.20.0`;15 个 `@deepseek-ai/*` peer 下限 `^0.1.5-rc.1` → `^0.1.6-alpha.2`;devDependencies 同步钉 `0.1.6-alpha.2`;移除 `@deepseek-ai/dsh-code-runtime`(上游整包消失);新增 `diff` `^9.0.0`、`simple-icons` `^16.32.0`(`ui-primitives` 裸 import,不走 devDependencies 就 `Cannot find package`) |
|
||||
| 基线 | `dsh.plugin.json` | `version` → `0.20.0`;`engines.dsh` → `>=0.1.6-alpha.2` |
|
||||
| 基线 | `pnpm-lock.yaml` | 锁文件对齐 alpha.2(646 行变动) |
|
||||
| 基线 | `.github/workflows/ci.yml` | mount 车道钉 `@deepseek-ai/dsh@0.1.6-alpha.2`;注释改为「alpha.2 是 npm 的 `alpha` dist-tag,`latest` 仍是 0.1.5 线」 |
|
||||
| 基线 | `scripts/e2e-common.sh` | 回退 npx **显式钉版**:新增 `DSH_NPX_SPEC`(缺省 `@deepseek-ai/dsh@0.1.6-alpha.2`)——不钉就会解析 `latest`,而 `latest` 仍是插件已不支持的 0.1.5 线 |
|
||||
| 基线 | `src/client/service.ts` | `SIDEBAR_SERVICE_VERSION` → `0.20.0`(`tests/service.spec.ts` 断言它 === `package.json` 的 version) |
|
||||
| 基线 | `src/client/SideChatView.tsx` | `IconSendOutline16` → `IconSendOutline14 size={16}`(保住原 16px 视觉尺寸);`TerminalBlockLabels` 补必填 `noExitCode: t('sideChatBlockNoExitCode')`;`ConnectionIndicator` 去掉 `reconnectLabel` |
|
||||
| 基线 | `src/client/locales.ts` + 19 份 `locales-*.ts`(共 20 份词典) | 新增 `sideChatBlockNoExitCode`(zh「已结束(无退出码)」/ en「Ended without an exit code」+ 各语言);`sideChatConnDisconnected` 改口径为「连接已断开,点击立即重连」——断线标签现在**自身**命名重试动作,跟随宿主文案 |
|
||||
| Tab 契约 | `src/client/native/index.ts` | 两个 guide 条目各带 `id`(descriptor id / 接管条目 `'files'`);`NativeSurfaceDeps` 新增可选 `reportFailure?: (phase: string, error: unknown) => void`,wrapper 内 try/catch 上报(单个 descriptor 失败不再带走其余注册) |
|
||||
| Tab 契约 | `src/client/index.tsx` | `fail()` 诊断条上移到 `apply()` 前部,并以 `reportFailure: (phase, error) => fail(\`native ${phase}\`, error)` 注入 `registerNativeSurface` |
|
||||
| Tab 契约 | `tests/native-surface.spec.ts` | +15 行:遍历注册到的全部 guide 条目,断言每个 `id` 是 string、总数 > 0、`new Set(ids).size === ids.length` |
|
||||
| 轮尾 | `src/client/intercept.tsx`(−131)、`src/client/produced-files.ts`(−120)、`tests/turn-tail-intercept.spec.ts`(−200)、`tests/produced-files.spec.ts`(−56) | 轮尾接管整体删除;`src/client/sidebar.module.css` 的接管样式随之删(−46) |
|
||||
| 轮尾 | `src/client/sidebar-file.ts`(新增 30)、`src/client/paths.ts`(+13) | 文件打开的那一半存活(文件变动 tab 与编辑器宿主仍用);`resolveSidebarPath` 移进同胞路径助手模块(绝对路径判定镜像宿主:POSIX 根 / 盘符 / UNC 都不拼 cwd) |
|
||||
| 终端 | 删除 `src/pty-manager.ts`(−548)、`src/agent-pty.ts`(−722)、`src/pty-deps.ts`(−240)、`src/tools.ts`(−500)、`src/client/TerminalView.tsx`(−477)、`src/client/TerminalWaitBanner.tsx`、`src/client/terminal-font.ts`(−255)、`src/client/terminal-links.ts`(−194)、`src/client/chunks/terminal.tsx`、`src/client/pinned.ts`(−177)、`src/client/sidebar/use-pinned-tabs.ts`(−147) | 整套 PTY 栈、xterm 视图、字体/链接助手、chunk 入口与跨会话固定终端全部下线 |
|
||||
| 终端 | `src/index.ts`(+14 / −560)、`src/config.ts`(−48)、`src/prefs-shared.ts`、`src/client/prefs.ts`(−26)、`src/client/api.ts`(−40)、`src/client/state.ts`(+11 / −244)、`src/client/TabBar.tsx`(+20 / −76)、`src/client/Sidebar.tsx`(+16 / −91)、`src/client/icons.tsx`(−11) | 终端设置项(`terminalShell` / `terminalShellArgs` / `terminalFontFamily` / `terminalFontSize` / `agentTerminalTools` / `bottomPanelAutoTerminal`)、宿主路由 `terminal.deps`、`clampTerminalFontSize`,以及被删特性留下的死状态(`SidebarTab.pin` / `nextTerminal` / `agentWaits` / `bottomOpenedOnce` / agent-tab helpers / pinned-tab memo key)一并清除 |
|
||||
| 终端 | `package.json` | `node-pty` `^1.1.0` 离开 `dependencies`;`@xterm/xterm`、`@xterm/addon-fit` 离开 devDependencies;`files` 去掉 `lib/client-terminal.js` |
|
||||
| 终端 | `pnpm-workspace.yaml` | 原 `allowBuilds: {node-pty: true, protobufjs: true}` 收缩(**块未整块删除**,见「收尾偏差」2) |
|
||||
| 终端 | `tsdown.config.ts`、`src/bundle-route.ts`、`src/client/chunk-loader.ts`、`scripts/package-registry.mjs`、`package.json` 的 `files`、`tests/{bundle-route,chunk-loader,chunk-artifact,manifest-consistency,lazy-chunk}.spec.*` | chunk 名由 `['editor','mermaid','locale','terminal']` 收敛为 `['editor','mermaid','locale']`(AGENTS.md §5 列出的镜像处全部同步) |
|
||||
| 终端 | 删除 13 个 spec | `agent-pty` / `agent-terminal-reconcile` / `agent-wait-badge` / `bottom-auto-terminal` / `open-when-sized` / `pinned` / `pty-deps` / `pty-helpers` / `terminal-deps-banner` / `terminal-font` / `terminal-links` / `terminal-wait-banner` / `tools` |
|
||||
| 浏览器 | 删除 `src/client/BrowserView.tsx`(−289)、`src/client/browser.ts`(−167)、`src/browser-probe.ts`(−26)、`tests/browser.spec.ts`、`tests/browser-probe.spec.ts`;`src/client/builtins/tabs.tsx`(+13 / −175)删 `browser` descriptor | 插件 `browser` tab 类型下线,宿主 `browser` kind 独占嵌入页(两条 commit 合计删 15 个 spec:终端 13 + 浏览器 2) |
|
||||
| 浏览器 | `src/index.ts`(删宿主 `browser.probe` 路由)、`src/prefs-shared.ts` / `src/client/prefs.ts`(删 `browserNoSandbox` / `browserAllowedLoopback`)、`src/config.ts` | 只配置插件自家 iframe 的两个设置项随它们喂养的宿主路由一并删除(宿主 `browser` kind 不进插件注册表) |
|
||||
| 浏览器 | `src/client/link-intercept.ts`、`src/client/sidebar/use-host-feeds.ts:128` | 保留 DOM 层 http(s) 接管,目标改宿主 `browser` kind(`ctx.sidebarRight.openTab('browser', { params: { url } })` + `window.open` 兜底);`urlTarget` 机制与匹配顺序(`matchUrlTarget`)不变 |
|
||||
| 卡片 | `src/client/sidebar.module.css` `.paneCard` | 采用宿主 guide 胶囊配方:`min-height: 56px` / `padding: 14px 20px` / `border: 0.5px solid var(--dsw-alias-border-l4)` / `border-radius: 24px` / `background: var(--dsw-alias-bg-layer-1)` / `font: var(--dsw-font-s-strong-14)` / hover 用 `--dsw-alias-interactive-bg-hover` |
|
||||
| 卡片 | `tests/theme.spec.ts`(+23) | 守护 `.paneCard` 的几何与「每条 paint 都解析到 `var(--dsw-`」,并断言**不得**出现上游专有拼写 `--dsw-alias-bg-l1` / `--dsw-alias-bg-l2`(证据:alpha.2 全树 4 处声明、**0 处定义**,全在 `packages/client/ui-sidebar-terminal/src/client/*.module.css`,故上游那 4 条 `background` 实际被丢弃) |
|
||||
| 挂载冒烟 | `tests/e2e/mount.e2e.ts` | 指南 sweep 拆成「插件自有 4 条(files / git / subagent / sidechat)+ 宿主自有 2 条(terminal / browser,各**恰好 1 条**——多 1 条即插件又在遮蔽宿主)」;说明表断言从「关 3 个 tab 类型」改「关 2 个」(宿主终端条目已不在插件注册表内) |
|
||||
| 聚合冒烟 | `scripts/e2e-aggregate-mount.sh` | 真方法哨兵从已删的 `terminal.deps` 改为 `settings.get`(理由写进脚本注释:`/sidebar/api` 是插件自己 fence 的 prefix 路由,未注册方法统统 404,`{"ok":true}` 只可能来自真实注册的 handler) |
|
||||
| 文档 | `AGENTS.md` §3 | 重写为「DSH 0.1.6-alpha.2 适配要点(0.1.6-alpha.2+ 基线)」:支持线 + 两条硬契约 + 两处让位 + 「本版还需知道的」9 条 |
|
||||
| 文档 | `README.md` | v0.20.0 段落、终端/浏览器「交还宿主」说明、支持版本徽章;终端卡片内写明模型侧代价与自行插入 `tool-terminal` 行的指引 |
|
||||
| 文档 | `docs/plans/2026-09-21-dsh-0.1.6-alpha.2-adaptation.md` | 本计划 |
|
||||
|
||||
### 收尾偏差(核对实际文件后修正的三处口径)
|
||||
|
||||
这三处不是计划项,而是**核对文件时发现的「提交信息与产物不符」**;写在这里以免被当作已完成,也给下一版留证据:
|
||||
|
||||
1. **安装脚本的 `-Repair` 模式**:`5912274` 的提交信息与 `README.md` 都称它已随终端删除,但三条 commit **都没有触碰这两个文件**——`git diff --stat HEAD~3..HEAD -- scripts/` 只有 `e2e-aggregate-mount.sh` / `e2e-common.sh` / `package-registry.mjs` 三个。`scripts/install.sh`(`--repair`,改动前行号 15/19/54/57/75/82/180)与 `scripts/install.ps1`(`-Repair`,改动前行号 22/26/51/175)当时**完整保留**了 node-pty 修复流程与 issue #140 文案。**该偏差已在本分支工作树修掉**(两个安装脚本 + `e2e-common.sh` 里 scratch profile 的 `allowBuilds` 块 + `package-registry.mjs` 的 node-pty 注释),截至本文档写作时**尚未提交**(见「落地」)。
|
||||
2. **`pnpm-workspace.yaml` 的 `allowBuilds` 块仍未收干净**:现状是 `node-pty: set this to true or false`(pnpm 自己写的待决占位行);原条目 `node-pty: true` 被换成该占位、`protobufjs: true` 被移除。块本身仍在,且仍以**已删除的依赖名**出现——**本版未处理**。
|
||||
3. **`browserNoSandboxWarning` 成了死键**:20 份 `src/client/locales*.ts`(词典 `locales.ts` + 19 份第三方语言)里仍在,而它描述的 `browserNoSandbox` 设置项已删,代码侧除词典外零消费——**本版未处理**。
|
||||
|
||||
## 验证
|
||||
|
||||
- **门禁(本版分支 HEAD `5912274`,macOS arm64)**:`pnpm install --registry=https://registry.npmjs.org`(**必须显式指定 registry**——机器默认的 npmmirror 源滞后,对 `@deepseek-ai/dsh-code-runtime` 报 404);`pnpm peers check` = `No peer dependency issues found`;`pnpm typecheck` 干净;`pnpm lint` 干净;`pnpm check:consumer-types` OK;`pnpm test` = **110 files / 1085 passed / 9 skipped**;`pnpm build` 恰好产出三个 chunk(`client-editor.js` / `client-mermaid.js` / `client-locale.js`)。
|
||||
- **写本文档时复核过的两条**:`pnpm peers check` = `No peer dependency issues found`、`pnpm test` = **110 files / 1085 passed / 9 skipped**,与上条一致(复核时工作树只有文档改动,测试文件与 HEAD 一致,因此数字对应 HEAD 的测试面;此后工作树又改动了 `tests/theme.spec.ts` 等收尾文件,**未重跑**)。`typecheck` / `lint` / `check:consumer-types` / `build` **未重跑**(本节记录的是门禁那一次的结果)。
|
||||
- **真机挂载冒烟(DSH 0.1.6-alpha.2):未执行**。`pnpm test:mount` 与 `pnpm test:mount:aggregate` 在本版分支上**没有跑过**——这是本版最大的未验证面:指南「插件 4 条 + 宿主 terminal/browser 各恰好 1 条」、聚合哨兵改 `settings.get`、卡片新几何,目前只有单测与 e2e 代码,没有真机结论。
|
||||
- **3080 实例 GUI 人工验证:未执行**。
|
||||
- **上游事实的取证方式**:本文每条 alpha.2 结论都来自 tag(`git -C /tmp/dsh-0162 diff dsh-v0.1.5-rc.2 dsh-v0.1.6-alpha.2 -- <path>` / `git show dsh-v0.1.6-alpha.2:<path>`),关键抛点原文为 `sidebarRight: duplicate guide entry id in "<id>"`(`tab-registry.ts:251`)与 `list slot "<name>" requires options.id`(`ui-slots/src/index.ts:1230`)。semver 表用仓库树内的 `semver@7.8.5` 实测。
|
||||
- **未验证的推断**(明确标注,不作为结论):「模型只剩一次性 `bash` / `pwsh`」是按两条**已验证**事实推出的——① `@deepseek-ai/dsh-tool-terminal` 未被任何 shipped bundle 挂载(`packages/bundle/` 零挂载行);② 插件删除的 `src/tools.ts` 确实注册了 8 个 `terminal_*` 工具。**没有**在真机上对模型可见工具清单做过实测。
|
||||
|
||||
## 落地
|
||||
|
||||
- 分支:`feat/dsh-0.1.6-alpha.2`,三条 commit(HEAD `5912274`)之上还有一批**未提交**的收尾改动:`AGENTS.md` §3 重写、`README.md` / `README_EN.md`、`docs/external-plugin-guide.md`、`scripts/install.sh` / `install.ps1`(删 `-Repair` / `--repair`)、`scripts/e2e-common.sh` / `e2e-aggregate-mount.sh` / `package-registry.mjs`(node-pty 残迹)、`tests/theme.spec.ts`(卡片守护加强,见「收尾偏差」1)。
|
||||
- 其余(PR、真机挂载冒烟、3080 GUI 验证、发版结论):**待回填**——本节不预填未经执行的结论。
|
||||
|
||||
## 不做
|
||||
|
||||
- 不为 0.1.5-rc.\* 写运行时/类型层兼容层(本版是**有意**的支持线断裂,与 v0.19.0 桥掉 0.1.2 线同一先例)。
|
||||
- 不把 peer 下限留在 `^0.1.5-rc.1` 或写成「宽松范围」——它在数学上容不下任何 0.1.6 预发布版(§决策 1 的表),留着只会让消费方装上一个必然崩的宿主。
|
||||
- **不补自家的终端替代实现**(不上第二套 PTY、不为模型侧持久终端另造工具);上游 `tool-terminal` 未被 bundle 挂载是上游的取舍,README 给指引即可。
|
||||
- 不恢复被删的轮尾产物行:list 槽只能追加,重新注册只会与宿主产物卡重复。
|
||||
- 不接入本版新增宿主面(`openTabs` / `tabsIn` / `registerCloseHandler` / `preferNewPane` / keyed `guide.entry` 槽 / 全局面板)。
|
||||
- 不改 DSH 源码(仓库硬约束 §1);不改 `release.yml`(0.20.0 无 prerelease 后缀 → 自动发 `latest`)。
|
||||
@@ -1,9 +1,6 @@
|
||||
packages:
|
||||
- .
|
||||
|
||||
allowBuilds:
|
||||
node-pty: set this to true or false
|
||||
|
||||
# The @deepseek-ai/* packages declare their dsh-* siblings as peerDependencies
|
||||
# (the DSH host bundle provides them all at mount time). This repo installs
|
||||
# every peer the dev tree needs as a direct devDependency pinned to the
|
||||
|
||||
@@ -74,7 +74,7 @@ FIXTURE_TGZ="$(ls "$TMP_DIR"/*.tgz 2>/dev/null | head -1 || true)"
|
||||
[ -n "$FIXTURE_TGZ" ] || die "fixture 打包失败"
|
||||
|
||||
# ── 引导 scratch profile(web 模板)────────────────────────────────────────
|
||||
# 三件套 heredoc 及 pnpm-workspace 的 allowBuilds / minimumReleaseAgeExclude
|
||||
# 三件套 heredoc 及 pnpm-workspace 的 minimumReleaseAgeExclude
|
||||
# 理由见 e2e-common.sh 的 e2e_write_profile。
|
||||
PROFILE_DIR="$DSH_HOME/profiles/web"
|
||||
e2e_write_profile "$PROFILE_DIR"
|
||||
|
||||
+24
-8
@@ -46,6 +46,7 @@ e2e_require_cmd() {
|
||||
# 个插件已不支持的宿主上。钉版必须与 package.json 的 peer 下限同步。
|
||||
DSH_NPX_SPEC="${DSH_NPX_SPEC:-@deepseek-ai/dsh@0.1.6-alpha.2}"
|
||||
e2e_resolve_dsh_cmd() {
|
||||
local explicit="${DSH_CMD}"
|
||||
if ! command -v "$DSH_CMD" >/dev/null 2>&1; then
|
||||
if command -v npx >/dev/null 2>&1; then
|
||||
say "PATH 上无 ${DSH_CMD},回退 npx -y --package ${DSH_NPX_SPEC}"
|
||||
@@ -54,6 +55,26 @@ e2e_resolve_dsh_cmd() {
|
||||
die "未找到 $DSH_CMD 或 npx;请先安装 DSH CLI(npm i -g @deepseek-ai/dsh)或用 DSH_CMD 指定"
|
||||
fi
|
||||
fi
|
||||
e2e_check_dsh_version "$explicit"
|
||||
}
|
||||
|
||||
# 解析出的 CLI 版本必须与 peer 下限同一条支持线。这不是洁癖:宿主只比
|
||||
# 0.1.6-alpha.2 早一个预发布(alpha.1)时,`dsh plugin add` 的 bundle 协调
|
||||
# 会静默不写 `dsh.profile.bundles`——CLI 退出码仍是 0,lane 却挂在「挂载未
|
||||
# 注册」上,排查方向完全被带偏(本机 PATH 上的 dsh 恰好就是 alpha.1,实测)。
|
||||
# DSH_CMD 显式给出时不拦(调用方明确知道自己在挂什么),只告警。
|
||||
e2e_check_dsh_version() {
|
||||
local explicit="$1" actual expected
|
||||
expected="${DSH_EXPECT_VERSION:-0.1.6-alpha.2}"
|
||||
# `dsh --version` 冷启动要走 npx,给足预算;拿不到版本就只告警。
|
||||
actual="$($DSH_CMD --version 2>/dev/null | tr -d '[:space:]' | head -c 64 || true)"
|
||||
case "$actual" in
|
||||
"$expected"|"$expected"*) return 0 ;;
|
||||
esac
|
||||
if [ "$explicit" = "dsh" ] && [ -n "$actual" ]; then
|
||||
die "PATH 上的 dsh 是 ${actual},本仓库的基线是 ${expected}(peer 下限同一支持线)。请改用 DSH_CMD='npx -y --package @deepseek-ai/dsh@${expected} dsh',或安装该版本。"
|
||||
fi
|
||||
warn "DSH_CMD 版本为 ${actual:-未知},基线是 ${expected}——真机证据落在非基线宿主上。"
|
||||
}
|
||||
|
||||
# ── tarball 解析 ─────────────────────────────────────────────────────────────
|
||||
@@ -108,10 +129,9 @@ e2e_cleanup() {
|
||||
|
||||
# ── scratch profile 三件套 ───────────────────────────────────────────────────
|
||||
# 引导 scratch profile(web 模板,镜像 dsh initProfile);先写
|
||||
# pnpm-workspace.yaml 的 allowBuilds / minimumReleaseAgeExclude,避免 pnpm 11
|
||||
# strict-dep-builds 拦截 node-pty/protobufjs 或拒绝 <24h 新版本——同 install.sh。
|
||||
# @deepseek-ai/* 通配与仓库根 pnpm-workspace.yaml 同策:钉的 DSH alpha 常在
|
||||
# 发布后 24h 内跑 lane,没有豁免会被 minimumReleaseAge 直接拒装。
|
||||
# pnpm-workspace.yaml 的 minimumReleaseAgeExclude,避免 pnpm 11 拒绝 <24h 新版本
|
||||
# ——同 install.sh。@deepseek-ai/* 通配与仓库根 pnpm-workspace.yaml 同策:钉的
|
||||
# DSH alpha 常在发布后 24h 内跑 lane,没有豁免会被 minimumReleaseAge 直接拒装。
|
||||
e2e_write_profile() {
|
||||
mkdir -p "$1"
|
||||
cat > "$1/package.json" <<EOF
|
||||
@@ -134,10 +154,6 @@ packages:
|
||||
nodeLinker: hoisted
|
||||
autoInstallPeers: false
|
||||
|
||||
allowBuilds:
|
||||
node-pty: true
|
||||
protobufjs: true
|
||||
|
||||
minimumReleaseAgeExclude:
|
||||
- dsh-better-sidebar
|
||||
- '@deepseek-ai/*'
|
||||
|
||||
+6
-49
@@ -18,14 +18,9 @@
|
||||
# & ([scriptblock]::Create($script)) -Version 0.10.2 -Restart
|
||||
# # 本地保存后运行
|
||||
# powershell -ExecutionPolicy Bypass -File install.ps1 -Version 0.10.2 -DryRun
|
||||
# # 修复 node-pty 依赖(终端提示「node-pty 加载失败」时用,见 issue #140)
|
||||
# powershell -ExecutionPolicy Bypass -File install.ps1 -Repair
|
||||
#
|
||||
# 参数:
|
||||
# -Version npm 版本号/范围,缺省 latest(自动解析为最新)。
|
||||
# -Repair 修复模式:不重装插件,只确保 profile 的 pnpm-workspace.yaml
|
||||
# 放行 node-pty 构建脚本,然后重跑 pnpm install + pnpm rebuild
|
||||
# node-pty。
|
||||
# -Profile 目标 profile 名(缺省 web);安装与修复模式均适用。
|
||||
# -Restart 装完后尝试 `pm2 restart dsh-web`(无 pm2 时仅提示)。会断开当前页面会话。
|
||||
# -DryRun 只打印将要执行的操作,不写任何文件。
|
||||
@@ -36,9 +31,6 @@
|
||||
# DSH_CMD 默认优先 PATH 上的 dsh,缺省回退 npx -y --package @deepseek-ai/dsh
|
||||
#
|
||||
# 说明:
|
||||
# - pnpm 11 的 strict-dep-builds 会拦截 node-pty/protobufjs 的构建脚本并使
|
||||
# `dsh plugin add` 非零退出。脚本会先把这两个构建许可写进 profile 的
|
||||
# pnpm-workspace.yaml(幂等),保证 CLI 一步成功。
|
||||
# - pnpm 11 的 minimumReleaseAge 会拒绝发布 <24h 的新版本。脚本会预写
|
||||
# minimumReleaseAgeExclude(幂等),放行本插件,避免"重跑一次才成功"。
|
||||
# - 老版本(<0.10.2)用手动挂载行,bundle 通道激活后需移除,否则双挂载。
|
||||
@@ -48,7 +40,6 @@ param(
|
||||
[string]$Version = '',
|
||||
[switch]$Restart,
|
||||
[switch]$DryRun,
|
||||
[switch]$Repair,
|
||||
[string]$Profile = 'web'
|
||||
)
|
||||
|
||||
@@ -99,23 +90,13 @@ function Get-DshCli {
|
||||
}
|
||||
|
||||
# 步骤 1(安装与修复共用):预写 workspace 设置(幂等),保证 pnpm 不拦截
|
||||
# node-pty/protobufjs 构建脚本、放行本插件新版本
|
||||
# 放行本插件新版本(0.20.0 起插件不再依赖需要构建脚本的包,allowBuilds 无需再动)
|
||||
function Ensure-WorkspaceSettings {
|
||||
$wsScript = @'
|
||||
const fs = require("fs");
|
||||
const p = process.argv[2];
|
||||
let t = fs.readFileSync(p, "utf8");
|
||||
const before = t;
|
||||
t = t.replace(/^(\s*)(node-pty|protobufjs):.*$/gm, "$1$2: true");
|
||||
if (!/^\s*allowBuilds:\s*$/m.test(t)) {
|
||||
t += "\nallowBuilds:\n node-pty: true\n protobufjs: true\n";
|
||||
} else {
|
||||
for (const k of ["node-pty", "protobufjs"]) {
|
||||
if (!new RegExp("^\\s*" + k + ":\\s*true\\s*$", "m").test(t)) {
|
||||
t = t.replace(/^(\s*allowBuilds:\s*)$/m, "$1\n " + k + ": true");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!/^\s*-\s+dsh-better-sidebar\s*$/m.test(t)) {
|
||||
if (/^\s*minimumReleaseAgeExclude:\s*$/m.test(t)) {
|
||||
t = t.replace(/^(\s*minimumReleaseAgeExclude:\s*)$/m, "$1\n - dsh-better-sidebar");
|
||||
@@ -137,7 +118,7 @@ console.log(t === before ? "unchanged" : "updated");
|
||||
$wsResult = (($wsOut | Out-String)).Trim()
|
||||
if ($wsCode -ne 0) { Die "处理 $WS_YML 失败(node 退出码 $wsCode):$wsResult" }
|
||||
if ($wsResult -eq 'updated') {
|
||||
Say "已确保 $WS_YML:allowBuilds(node-pty/protobufjs: true)+ minimumReleaseAgeExclude($PKG)"
|
||||
Say "已确保 $WS_YML:minimumReleaseAgeExclude($PKG)"
|
||||
} else {
|
||||
Say 'workspace 设置已就绪,跳过'
|
||||
}
|
||||
@@ -169,30 +150,6 @@ if (-not (Test-Path $WS_YML)) {
|
||||
Die "找不到 $WS_YML(请先初始化 $Profile profile)"
|
||||
}
|
||||
|
||||
# ── 修复模式(issue #140):不重装插件,只修复 node-pty 依赖 ────────────
|
||||
# 终端提示「node-pty 加载失败」时运行:确保 allowBuilds 后重跑
|
||||
# pnpm install + pnpm rebuild node-pty(重放被 pnpm 11 拦截的构建脚本)。
|
||||
if ($Repair) {
|
||||
if ($DryRun) {
|
||||
Say "[dry-run] 修复:确保 $WS_YML 含 allowBuilds(node-pty: true)"
|
||||
Say "[dry-run] 修复:cd $PROFILE_DIR; pnpm install; pnpm rebuild node-pty"
|
||||
exit 0
|
||||
}
|
||||
Say "修复模式:重装 node-pty(profile: $Profile,$PROFILE_DIR)..."
|
||||
Ensure-WorkspaceSettings
|
||||
Push-Location $PROFILE_DIR
|
||||
try {
|
||||
pnpm install
|
||||
if ($LASTEXITCODE -ne 0) { Die "pnpm install 失败(退出码 $LASTEXITCODE)。请确认 pnpm 在 PATH 上、网络可用,然后重试。" }
|
||||
pnpm rebuild node-pty
|
||||
if ($LASTEXITCODE -ne 0) { Die "pnpm rebuild node-pty 失败(退出码 $LASTEXITCODE)。请确认 pnpm 在 PATH 上,然后重试。" }
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
Say '修复完成:node-pty 已重装(与 DSH 核心保持同一版本)。请重启 DSH 后重试终端。'
|
||||
exit 0
|
||||
}
|
||||
|
||||
$SPEC = Resolve-Spec $Version
|
||||
$CLI = Get-DshCli
|
||||
if (-not $CLI) {
|
||||
@@ -201,7 +158,7 @@ if (-not $CLI) {
|
||||
Say "目标:$CLI plugin --profile $Profile add $PKG@$SPEC(profile: $PROFILE_DIR)"
|
||||
|
||||
if ($DryRun) {
|
||||
Say "[dry-run] 步骤 1:确保 $WS_YML 含 allowBuilds(node-pty/protobufjs: true)与 minimumReleaseAgeExclude($PKG)"
|
||||
Say "[dry-run] 步骤 1:确保 $WS_YML 含 minimumReleaseAgeExclude($PKG)"
|
||||
Say "[dry-run] 步骤 2:执行 $CLI plugin --profile $Profile add $PKG@$SPEC(安装 + bundle 自动注册)"
|
||||
Say "[dry-run] 步骤 3:校验 dsh.profile.bundles 含 $PKG"
|
||||
Say "[dry-run] 步骤 4:幂等移除 $PATCH_YML 里旧的 better-sidebar 手动挂载行(避免双挂载)"
|
||||
@@ -209,7 +166,7 @@ if ($DryRun) {
|
||||
exit 0
|
||||
}
|
||||
|
||||
# 步骤 1:预写 workspace 设置(幂等),保证 pnpm 不拦截构建、放行本插件新版本
|
||||
# 步骤 1:预写 workspace 设置(幂等),放行本插件新版本
|
||||
Ensure-WorkspaceSettings
|
||||
|
||||
# 步骤 2:官方 CLI 安装 + bundle 自动注册(含挂载)
|
||||
@@ -223,7 +180,7 @@ $addOut = & $CLI @cliArgs 2>&1
|
||||
$addCode = $LASTEXITCODE
|
||||
$addOut | ForEach-Object { $_ }
|
||||
if ($addCode -ne 0) {
|
||||
Warn 'dsh plugin add 失败。已预写 allowBuilds 与 minimumReleaseAgeExclude,仍失败的可能原因:'
|
||||
Warn 'dsh plugin add 失败。已预写 minimumReleaseAgeExclude,仍失败的可能原因:'
|
||||
Warn ' - 网络/登录问题:npm registry 不可达或需要登录。'
|
||||
Warn " - 依赖安装冲突:可手动重试 cd $PROFILE_DIR; pnpm install。"
|
||||
exit 1
|
||||
@@ -234,7 +191,7 @@ $pkgJson = Get-Content -Raw (Join-Path $PROFILE_DIR 'package.json') | ConvertFro
|
||||
$bundles = $pkgJson.dsh.profile.bundles
|
||||
if ($bundles -notcontains $PKG) {
|
||||
Warn 'dsh-better-sidebar 未出现在 dsh.profile.bundles 中——挂载未注册。'
|
||||
Warn "若上面的 pnpm 输出提示 ignored build scripts,请确认 $WS_YML 的 allowBuilds 后重跑本脚本。"
|
||||
Warn "若上面的 pnpm 输出提示 ignored build scripts,请确认 $WS_YML 的 allowBuilds 后重跑本脚本(0.20.0 起本插件自身不再有构建脚本依赖)。"
|
||||
exit 1
|
||||
}
|
||||
Say "bundle 已注册:dsh.profile.bundles 包含 $PKG(下次启动自动挂载)"
|
||||
|
||||
+8
-48
@@ -12,14 +12,10 @@
|
||||
#
|
||||
# 用法:
|
||||
# bash scripts/install.sh [版本] [--restart] [--dry-run]
|
||||
# bash scripts/install.sh --repair [--profile <名>] [--dry-run]
|
||||
#
|
||||
# 版本 npm 版本号/范围,缺省为 latest(自动解析为 ^<最新>)。
|
||||
# 示例:0.10.2、^0.10.2、~0.10.2、latest
|
||||
# --repair 修复模式:不重装插件,只确保 profile 的 pnpm-workspace.yaml
|
||||
# 放行 node-pty 构建脚本,然后重跑 pnpm install + pnpm rebuild
|
||||
# node-pty(终端提示「node-pty 加载失败」时用它,见 issue #140)。
|
||||
# --profile 目标 profile 名(缺省 web);安装与修复模式均适用。
|
||||
# --profile 目标 profile 名(缺省 web)。
|
||||
# --restart 装完后尝试 `pm2 restart dsh-web`(无 pm2 时仅打印提示)。
|
||||
# 注意:重启会断开当前 DSH 页面会话,默认不自动重启。
|
||||
# --dry-run 只打印将要执行的操作,不写任何文件。
|
||||
@@ -31,9 +27,6 @@
|
||||
# DSH_CMD 默认优先用 PATH 上的 `dsh`,缺省回退 npx -y --package @deepseek-ai/dsh
|
||||
#
|
||||
# 说明:
|
||||
# - pnpm 11 的 strict-dep-builds 会拦截 node-pty/protobufjs 的构建脚本并使
|
||||
# `dsh plugin add` 非零退出(bundle 协调因此被跳过)。脚本会先把这两个
|
||||
# 构建许可写进 profile 的 pnpm-workspace.yaml(幂等),保证 CLI 一步成功。
|
||||
# - pnpm 11 的 minimumReleaseAge 会拒绝发布 <24h 的新版本。脚本会预写
|
||||
# minimumReleaseAgeExclude(幂等),放行本插件,避免"重跑一次才成功"。
|
||||
# - 老版本(<0.10.2)用手动挂载行,bundle 通道激活后需移除,否则双挂载
|
||||
@@ -47,14 +40,12 @@ set -euo pipefail
|
||||
for arg in "$@"; do
|
||||
if [ "$arg" = "-h" ] || [ "$arg" = "--help" ]; then
|
||||
cat <<'EOF'
|
||||
dsh-better-sidebar 一键安装 / 依赖修复脚本
|
||||
dsh-better-sidebar 一键安装脚本
|
||||
|
||||
用法:
|
||||
bash scripts/install.sh [版本] [--restart] [--dry-run] [--profile <名>]
|
||||
bash scripts/install.sh --repair [--profile <名>] [--dry-run]
|
||||
|
||||
版本 npm 版本号/范围,缺省 latest(自动解析为最新)。示例:0.10.2、^0.10.2、latest
|
||||
--repair 修复模式:确保 profile 放行 node-pty 构建脚本并重装 node-pty(终端提示依赖加载失败时用)
|
||||
--profile 目标 profile 名(缺省 web)
|
||||
--restart 装完后尝试 `pm2 restart dsh-web`(无 pm2 时仅提示)
|
||||
--dry-run 只打印将要执行的操作,不写任何文件
|
||||
@@ -72,14 +63,12 @@ DSH_CMD="${DSH_CMD:-dsh}"
|
||||
|
||||
RESTART=false
|
||||
DRY_RUN=false
|
||||
REPAIR=false
|
||||
VERSION_SPEC=""
|
||||
PROFILE_NAME="web"
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--restart) RESTART=true ;;
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
--repair) REPAIR=true ;;
|
||||
--profile)
|
||||
if [ $# -lt 2 ]; then echo "--profile 需要一个 profile 名(如 web)" >&2; exit 2; fi
|
||||
PROFILE_NAME="$2"; shift ;;
|
||||
@@ -98,25 +87,14 @@ say() { printf '\033[32m[install]\033[0m %s\n' "$*"; }
|
||||
warn() { printf '\033[33m[warn]\033[0m %s\n' "$*" >&2; }
|
||||
die() { printf '\033[31m[error]\033[0m %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
# 步骤 1(安装与修复共用):预写 workspace 设置(幂等),保证 pnpm 不拦截
|
||||
# node-pty/protobufjs 构建脚本、放行本插件新版本
|
||||
# 步骤 1:预写 workspace 设置(幂等),放行本插件新版本
|
||||
# (0.20.0 起插件不再依赖任何需要构建脚本的包,allowBuilds 无需再动)
|
||||
ensure_workspace_settings() {
|
||||
WS_RESULT="$(node -e '
|
||||
const fs = require("fs");
|
||||
const p = process.argv[1];
|
||||
let t = fs.readFileSync(p, "utf8");
|
||||
const before = t;
|
||||
// allowBuilds:把 node-pty/protobufjs 归位为 true
|
||||
t = t.replace(/^(\s*)(node-pty|protobufjs):.*$/gm, "$1$2: true");
|
||||
if (!/^\s*allowBuilds:\s*$/m.test(t)) {
|
||||
t += "\nallowBuilds:\n node-pty: true\n protobufjs: true\n";
|
||||
} else {
|
||||
for (const k of ["node-pty", "protobufjs"]) {
|
||||
if (!new RegExp("^\\s*" + k + ":\\s*true\\s*$", "m").test(t)) {
|
||||
t = t.replace(/^(\s*allowBuilds:\s*)$/m, "$1\n " + k + ": true");
|
||||
}
|
||||
}
|
||||
}
|
||||
// minimumReleaseAgeExclude:放行本插件(版本无关),避免 <24h 新版本被拒
|
||||
if (!/^\s*-\s+dsh-better-sidebar\s*$/m.test(t)) {
|
||||
if (/^\s*minimumReleaseAgeExclude:\s*$/m.test(t)) {
|
||||
@@ -129,7 +107,7 @@ if (t !== before) fs.writeFileSync(p, t);
|
||||
console.log(t === before ? "unchanged" : "updated");
|
||||
' "$WS_YML")"
|
||||
[ "$WS_RESULT" = "updated" ] \
|
||||
&& say "已确保 ${WS_YML}:allowBuilds(node-pty/protobufjs: true)+ minimumReleaseAgeExclude(${PKG})" \
|
||||
&& say "已确保 ${WS_YML}:minimumReleaseAgeExclude(${PKG})" \
|
||||
|| say "workspace 设置已就绪,跳过"
|
||||
}
|
||||
|
||||
@@ -174,30 +152,12 @@ command -v node >/dev/null 2>&1 || die "未找到 node(DSH 运行需要 Node.j
|
||||
[ -d "$PROFILE_DIR" ] || die "找不到 profile 目录:${PROFILE_DIR}(请先安装并运行过一次 dsh web)"
|
||||
[ -f "$WS_YML" ] || die "找不到 ${WS_YML}(请先初始化 ${PROFILE_NAME} profile)"
|
||||
|
||||
# ── 修复模式(issue #140):不重装插件,只修复 node-pty 依赖 ────────────
|
||||
# 终端提示「node-pty 加载失败」时运行:确保 allowBuilds 后重跑
|
||||
# pnpm install + pnpm rebuild node-pty(重放被 pnpm 11 拦截的构建脚本)。
|
||||
if [ "$REPAIR" = true ]; then
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
say "[dry-run] 修复:确保 $WS_YML 含 allowBuilds(node-pty: true)"
|
||||
say "[dry-run] 修复:cd $PROFILE_DIR && pnpm install && pnpm rebuild node-pty"
|
||||
exit 0
|
||||
fi
|
||||
say "修复模式:重装 node-pty(profile: ${PROFILE_NAME},${PROFILE_DIR})..."
|
||||
ensure_workspace_settings
|
||||
if ! (cd "$PROFILE_DIR" && pnpm install && pnpm rebuild node-pty); then
|
||||
die "修复失败:pnpm install / pnpm rebuild node-pty 非零退出。请确认 pnpm 在 PATH 上、网络可用,然后重试。"
|
||||
fi
|
||||
say "修复完成:node-pty 已重装(与 DSH 核心保持同一版本)。请重启 DSH 后重试终端。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SPEC="$(resolve_spec "$VERSION_SPEC")"
|
||||
CLI="$(dsh_cli)"
|
||||
say "目标:$CLI plugin --profile $PROFILE_NAME add $PKG@${SPEC}(profile: ${PROFILE_DIR})"
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
say "[dry-run] 步骤 1:确保 $WS_YML 含 allowBuilds(node-pty/protobufjs: true)与 minimumReleaseAgeExclude(${PKG})"
|
||||
say "[dry-run] 步骤 1:确保 $WS_YML 含 minimumReleaseAgeExclude(${PKG})"
|
||||
say "[dry-run] 步骤 2:执行 $CLI plugin --profile $PROFILE_NAME add $PKG@${SPEC}(安装 + bundle 自动注册)"
|
||||
say "[dry-run] 步骤 3:校验 dsh.profile.bundles 含 $PKG"
|
||||
say "[dry-run] 步骤 4:幂等移除 $PATCH_YML 里旧的 better-sidebar 手动挂载行(避免双挂载)"
|
||||
@@ -211,7 +171,7 @@ ensure_workspace_settings
|
||||
# 步骤 2:官方 CLI 安装 + bundle 自动注册(含挂载)
|
||||
say "执行 $CLI plugin --profile $PROFILE_NAME add $PKG@$SPEC ..."
|
||||
if ! $CLI plugin --profile "$PROFILE_NAME" add "$PKG@$SPEC" 2>&1 | tail -n +1; then
|
||||
warn "dsh plugin add 失败。已预写 allowBuilds 与 minimumReleaseAgeExclude,仍失败的可能原因:"
|
||||
warn "dsh plugin add 失败。已预写 minimumReleaseAgeExclude,仍失败的可能原因:"
|
||||
warn " - 网络/登录问题:npm registry 不可达或需要登录。"
|
||||
warn " - 依赖安装冲突:可手动重试 cd $PROFILE_DIR && pnpm install。"
|
||||
exit 1
|
||||
@@ -225,7 +185,7 @@ if ! node -e '
|
||||
process.exit(bundles.includes(process.argv[2]) ? 0 : 1);
|
||||
' "$PROFILE_DIR/package.json" "$PKG"; then
|
||||
warn "dsh-better-sidebar 未出现在 dsh.profile.bundles 中——挂载未注册。"
|
||||
warn "若上面的 pnpm 输出提示 ignored build scripts,请确认 $WS_YML 的 allowBuilds 后重跑本脚本。"
|
||||
warn "若上面的 pnpm 输出提示 ignored build scripts,请确认 $WS_YML 的 allowBuilds 后重跑本脚本(0.20.0 起本插件自身不再有构建脚本依赖)。"
|
||||
exit 1
|
||||
fi
|
||||
say "bundle 已注册:dsh.profile.bundles 包含 ${PKG}(下次启动自动挂载)"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
*
|
||||
* The registry install copies the whole source directory (`cp -r`), so
|
||||
* installing the repo root would drag in `node_modules/` (machine-local
|
||||
* `link:` devDependency symlinks, node-pty binaries) and `.git/`. This
|
||||
* `link:` devDependency symlinks) and `.git/`. This
|
||||
* staging directory carries exactly the files the manifest references plus
|
||||
* the docs — same layout as the dsh-subagent-tree `registry/` pattern. It is
|
||||
* gitignored and rebuilt from scratch on every run.
|
||||
|
||||
@@ -296,7 +296,6 @@ export const ar: Record<string, string> = {
|
||||
browserBlockedScheme: 'محظور: يُسمح فقط بعناوين URL من نوع http/https',
|
||||
browserBlockedLoopback: 'محظور: لا يمكن تصفّح العناوين المحلية والداخلية هنا',
|
||||
browserInvalid: 'عنوان URL غير صالح',
|
||||
browserNoSandboxWarning: 'الحماية معطّلة: الصفحة الحالية تعمل بكامل صلاحيات الواجهة (يمكن إعادة تفعيلها في الإعدادات)',
|
||||
htmlNoSandboxWarning: 'الحماية معطّلة: يعمل هذا HTML بنفس أصل الواجهة — يمكنه قراءة ملفات الجلسة وواجهات API الداخلية (يمكن إعادة تفعيلها في الإعدادات)',
|
||||
sandboxStatusOn: 'وضع الحماية: مفعّل · لا يمكن للصفحات الوصول إلى بيانات الواجهة أو الملفات المحلية؛ قد لا تعمل تسجيلات الدخول وملفات الارتباط التابعة لجهات خارجية',
|
||||
sandboxUnlock: 'تعطيل مؤقت (غير آمن)',
|
||||
|
||||
@@ -281,7 +281,6 @@ export const de: Record<string, string> = {
|
||||
browserBlockedScheme: 'Blockiert: nur http/https-Links sind erlaubt',
|
||||
browserBlockedLoopback: 'Blockiert: lokale und interne Adressen können hier nicht aufgerufen werden',
|
||||
browserInvalid: 'Ungültige URL',
|
||||
browserNoSandboxWarning: 'Sandbox aus: Die aktuelle Seite läuft mit vollen Benutzeroberflächen-Rechten (in den Einstellungen wieder aktivierbar)',
|
||||
htmlNoSandboxWarning: 'Sandbox aus: Dieses HTML läuft mit vollen Benutzeroberflächen-Rechten (in den Einstellungen wieder aktivierbar)',
|
||||
sandboxStatusOn: 'Sandbox-Modus: aktiviert · Seiten können weder auf die Daten der Oberfläche noch auf lokale Dateien zugreifen; Anmeldungen und Drittanbieter-Cookies funktionieren möglicherweise nicht',
|
||||
sandboxUnlock: 'Vorübergehend deaktivieren (unsicher)',
|
||||
|
||||
@@ -288,7 +288,6 @@ export const fr: Record<string, string> = {
|
||||
browserBlockedScheme: 'Bloqué : seuls les liens http/https sont pris en charge',
|
||||
browserBlockedLoopback: 'Bloqué : l’accès aux adresses locales ou internes n’est pas autorisé dans le navigateur',
|
||||
browserInvalid: 'Adresse invalide',
|
||||
browserNoSandboxWarning: 'Bac à sable désactivé : la page actuelle partage l’origine de l’interface et dispose de l’ensemble des droits de session (restauration possible dans les réglages)',
|
||||
htmlNoSandboxWarning: 'Bac à sable désactivé : ce HTML partage l’origine de l’interface, peut lire les fichiers de session et les interfaces internes (restauration possible dans les réglages)',
|
||||
sandboxStatusOn: 'Mode bac à sable : activé · la page ne peut pas accéder aux données de l’interface ni aux fichiers locaux ; l’état de connexion et les cookies tiers peuvent être indisponibles',
|
||||
sandboxUnlock: 'Déverrouiller temporairement (non sécurisé)',
|
||||
|
||||
@@ -295,7 +295,6 @@ export const hi: Record<string, string> = {
|
||||
browserBlockedScheme: 'रोका गया: केवल http/https URL अनुमत हैं',
|
||||
browserBlockedLoopback: 'रोका गया: स्थानीय और आंतरिक पते यहाँ ब्राउज़ नहीं किए जा सकते',
|
||||
browserInvalid: 'अमान्य URL',
|
||||
browserNoSandboxWarning: 'सैंडबॉक्स बंद: वर्तमान पृष्ठ GUI विशेषाधिकार के साथ चलता है (सेटिंग्स में पुनः सक्षम करें)',
|
||||
htmlNoSandboxWarning: 'सैंडबॉक्स बंद: यह HTML GUI विशेषाधिकार के साथ चलता है (सेटिंग्स में पुनः सक्षम करें)',
|
||||
sandboxStatusOn: 'सैंडबॉक्स मोड: चालू · पृष्ठ GUI के डेटा या स्थानीय फ़ाइलों तक नहीं पहुँच सकते; लॉगिन और थर्ड-पार्टी कुकीज़ काम नहीं कर सकतीं',
|
||||
sandboxUnlock: 'अस्थायी रूप से अक्षम (असुरक्षित)',
|
||||
|
||||
@@ -293,7 +293,6 @@ export const id: Record<string, string> = {
|
||||
browserBlockedScheme: 'Diblokir: hanya URL http/https yang diizinkan',
|
||||
browserBlockedLoopback: 'Diblokir: alamat lokal dan internal tidak dapat dijelajahi di sini',
|
||||
browserInvalid: 'URL tidak valid',
|
||||
browserNoSandboxWarning: 'Sandbox mati: halaman saat ini berjalan dengan hak penuh GUI (aktifkan kembali di pengaturan)',
|
||||
htmlNoSandboxWarning: 'Sandbox mati: HTML ini berjalan dengan hak penuh GUI (aktifkan kembali di pengaturan)',
|
||||
sandboxStatusOn: 'Mode sandbox: aktif · halaman tidak dapat mengakses data GUI atau berkas lokal; login dan cookie pihak ketiga mungkin tidak berfungsi',
|
||||
sandboxUnlock: 'Nonaktifkan sementara (tidak aman)',
|
||||
|
||||
@@ -286,7 +286,6 @@ export const it: Record<string, string> = {
|
||||
browserBlockedScheme: 'Bloccato: sono ammessi solo URL http/https',
|
||||
browserBlockedLoopback: 'Bloccato: gli indirizzi locali e interni non possono essere navigati qui',
|
||||
browserInvalid: 'URL non valido',
|
||||
browserNoSandboxWarning: 'Sandbox disattivata: la pagina attuale viene eseguita con i privilegi completi dell’interfaccia (può riattivarla nelle impostazioni)',
|
||||
htmlNoSandboxWarning: 'Sandbox disattivata: questo HTML viene eseguito con la stessa origine dell’interfaccia e può leggere i file di sessione e le API interne (può riattivarla nelle impostazioni)',
|
||||
sandboxStatusOn: 'Modalità sandbox: attiva · le pagine non possono accedere ai dati dell’interfaccia o ai file locali; i login e i cookie di terze parti potrebbero non funzionare',
|
||||
sandboxUnlock: 'Disattiva temporaneamente (non sicuro)',
|
||||
|
||||
@@ -295,7 +295,6 @@ export const ja: Record<string, string> = {
|
||||
browserBlockedScheme: 'ブロック:http/https リンクのみ許可',
|
||||
browserBlockedLoopback: 'ブロック:ローカルや内部アドレスはブラウズできません',
|
||||
browserInvalid: '無効な URL',
|
||||
browserNoSandboxWarning: 'サンドボックスオフ:現在のページは GUI と同一オリジンで、完全な会話権限を持ちます(設定で復元可)',
|
||||
htmlNoSandboxWarning: 'サンドボックスオフ:この HTML は GUI と同一オリジンで、会話ファイルや内部 API を読み取れます(設定で復元可)',
|
||||
sandboxStatusOn: 'サンドボックスモード:オン ・ ページは GUI のデータやローカルファイルにアクセス不可。ログイン状態とサードパーティ Cookie は利用できない場合あり',
|
||||
sandboxUnlock: '一時的に解除(非安全)',
|
||||
|
||||
@@ -287,7 +287,6 @@ export const ko: Record<string, string> = {
|
||||
browserBlockedScheme: '차단됨: http/https 링크만 지원합니다',
|
||||
browserBlockedLoopback: '차단됨: 브라우저에서 로컬 또는 내부 주소에 접근할 수 없습니다',
|
||||
browserInvalid: '잘못된 URL',
|
||||
browserNoSandboxWarning: '샌드박스가 꺼짐: 현재 페이지가 인터페이스와 동일 출처로 전체 세션 권한을 가집니다(설정에서 복원 가능)',
|
||||
htmlNoSandboxWarning: '샌드박스가 꺼짐: 이 HTML이 인터페이스와 동일 출처로 세션 파일과 내부 인터페이스를 읽을 수 있습니다(설정에서 복원 가능)',
|
||||
sandboxStatusOn: '샌드박스 모드: 사용 중 · 페이지가 인터페이스 데이터와 로컬 파일에 접근할 수 없으며, 로그인 상태와 타사 Cookie는 동작하지 않을 수 있습니다',
|
||||
sandboxUnlock: '임시 해제(안전하지 않음)',
|
||||
|
||||
@@ -293,7 +293,6 @@ export const nl: Record<string, string> = {
|
||||
browserBlockedScheme: 'Geblokkeerd: alleen http/https-URL\'s zijn toegestaan',
|
||||
browserBlockedLoopback: 'Geblokkeerd: lokale en interne adressen kunnen hier niet worden bezocht',
|
||||
browserInvalid: 'Ongeldige URL',
|
||||
browserNoSandboxWarning: 'Sandbox uit: de huidige pagina draait met volledige GUI-privileges (herinschakelen in instellingen)',
|
||||
htmlNoSandboxWarning: 'Sandbox uit: deze HTML draait met volledige GUI-privileges (herinschakelen in instellingen)',
|
||||
sandboxStatusOn: 'Sandbox-modus: aan · pagina\'s kunnen de GUI-gegevens of lokale bestanden niet benaderen; logins en third-party cookies werken mogelijk niet',
|
||||
sandboxUnlock: 'Tijdelijk uitschakelen (onveilig)',
|
||||
|
||||
@@ -297,7 +297,6 @@ export const pl: Record<string, string> = {
|
||||
browserBlockedScheme: 'Zablokowano: dozwolone są tylko adresy URL http/https',
|
||||
browserBlockedLoopback: 'Zablokowano: nie można tu przeglądać adresów lokalnych i wewnętrznych',
|
||||
browserInvalid: 'Nieprawidłowy adres URL',
|
||||
browserNoSandboxWarning: 'Piaskownica wyłączona: bieżąca strona działa z pełnymi uprawnieniami interfejsu (można przywrócić w ustawieniach)',
|
||||
htmlNoSandboxWarning: 'Piaskownica wyłączona: ten HTML działa z pełnymi uprawnieniami interfejsu (można przywrócić w ustawieniach)',
|
||||
sandboxStatusOn: 'Tryb piaskownicy: włączony · strony nie mogą dostępować danych interfejsu ani plików lokalnych; logowania i ciasteczka firm trzecich mogą nie działać',
|
||||
sandboxUnlock: 'Wyłącz tymczasowo (niebezpieczne)',
|
||||
|
||||
@@ -278,7 +278,6 @@ export const pt: Record<string, string> = {
|
||||
browserBlockedScheme: 'Bloqueado: apenas URLs http/https são permitidas',
|
||||
browserBlockedLoopback: 'Bloqueado: endereços locais e internos não podem ser navegados aqui',
|
||||
browserInvalid: 'URL inválida',
|
||||
browserNoSandboxWarning: 'Sandbox desativado: a página atual roda com privilégios completos da interface (reative nas configurações)',
|
||||
htmlNoSandboxWarning: 'Sandbox desativado: este HTML roda com privilégios completos da interface (reative nas configurações)',
|
||||
sandboxStatusOn: 'Modo sandbox: ativado · as páginas não podem acessar os dados da interface nem arquivos locais; logins e cookies de terceiros podem não funcionar',
|
||||
sandboxUnlock: 'Desativar temporariamente (inseguro)',
|
||||
|
||||
@@ -292,7 +292,6 @@ export const ru: Record<string, string> = {
|
||||
browserBlockedScheme: 'Заблокировано: разрешены только ссылки http/https',
|
||||
browserBlockedLoopback: 'Заблокировано: локальные и внутренние адреса недоступны для просмотра здесь',
|
||||
browserInvalid: 'Неверный URL',
|
||||
browserNoSandboxWarning: 'Песочница выключена: текущая страница работает с полными привилегиями интерфейса (можно включить заново в настройках)',
|
||||
htmlNoSandboxWarning: 'Песочница выключена: этот HTML работает с полными привилегиями интерфейса и может читать файлы сессии и внутренние API (можно включить заново в настройках)',
|
||||
sandboxStatusOn: 'Режим песочницы: включён · страницы не имеют доступа к данным интерфейса и локальным файлам; вход и сторонние cookie могут не работать',
|
||||
sandboxUnlock: 'Временно отключить (небезопасно)',
|
||||
|
||||
@@ -278,7 +278,6 @@ export const sv: Record<string, string> = {
|
||||
browserBlockedScheme: 'Blockerat: endast http/https-URL:er är tillåtna',
|
||||
browserBlockedLoopback: 'Blockerat: lokala och interna adresser kan inte surfas här',
|
||||
browserInvalid: 'Ogiltig URL',
|
||||
browserNoSandboxWarning: 'Sandlåda av: den aktuella sidan kör med fullständiga GUI-privilegier (återaktivera i inställningarna)',
|
||||
htmlNoSandboxWarning: 'Sandlåda av: denna HTML kör med fullständiga GUI-privilegier (återaktivera i inställningarna)',
|
||||
sandboxStatusOn: 'Sandlådeläge: på · sidor kan inte komma åt GUI:ts data eller lokala filer; inloggningar och tredjepartscookies kanske inte fungerar',
|
||||
sandboxUnlock: 'Avaktivera tillfälligt (osäkert)',
|
||||
|
||||
@@ -295,7 +295,6 @@ export const th: Record<string, string> = {
|
||||
browserBlockedScheme: 'ถูกบล็อก: อนุญาตเฉพาะ URL http/https เท่านั้น',
|
||||
browserBlockedLoopback: 'ถูกบล็อก: ไม่สามารถเรียกดูที่อยู่ภายในเครื่องและภายในได้ที่นี่',
|
||||
browserInvalid: 'URL ไม่ถูกต้อง',
|
||||
browserNoSandboxWarning: 'แซนด์บ็อกซ์ปิด: หน้าปัจจุบันทำงานด้วยสิทธิ์ GUI เต็มรูปแบบ (เปิดใช้ใหม่ได้ในการตั้งค่า)',
|
||||
htmlNoSandboxWarning: 'แซนด์บ็อกซ์ปิด: HTML นี้ทำงานด้วยสิทธิ์ GUI เต็มรูปแบบ (เปิดใช้ใหม่ได้ในการตั้งค่า)',
|
||||
sandboxStatusOn: 'โหมดแซนด์บ็อกซ์: เปิด · หน้าเว็บไม่สามารถเข้าถึงข้อมูลหรือไฟล์ภายในของ GUI ได้; การล็อกอินและคุกกี้ของบุคคลที่สามอาจไม่ทำงาน',
|
||||
sandboxUnlock: 'ปิดใช้ชั่วคราว (ไม่ปลอดภัย)',
|
||||
|
||||
@@ -295,7 +295,6 @@ export const tr: Record<string, string> = {
|
||||
browserBlockedScheme: 'Engellendi: yalnızca http/https URL’lerine izin verilir',
|
||||
browserBlockedLoopback: 'Engellendi: yerel ve iç adresler burada gezilemez',
|
||||
browserInvalid: 'Geçersiz URL',
|
||||
browserNoSandboxWarning: 'Kum kapalı: geçerli sayfa tam arayüz ayrıcalıklarıyla çalışır (ayarlardan yeniden etkinleştirin)',
|
||||
htmlNoSandboxWarning: 'Kum kapalı: bu HTML tam arayüz ayrıcalıklarıyla çalışır (ayarlardan yeniden etkinleştirin)',
|
||||
sandboxStatusOn: 'Kum modu: açık · sayfalar arayüzün verilerine veya yerel dosyalara erişemez; oturum açmalar ve üçüncü taraf çerezler çalışmayabilir',
|
||||
sandboxUnlock: 'Geçici olarak devre dışı bırak (güvenli değil)',
|
||||
|
||||
@@ -295,7 +295,6 @@ export const vi: Record<string, string> = {
|
||||
browserBlockedScheme: 'Đã chặn: chỉ cho phép link http/https',
|
||||
browserBlockedLoopback: 'Đã chặn: không cho phép truy cập địa chỉ cục bộ hoặc nội bộ trong trình duyệt',
|
||||
browserInvalid: 'URL không hợp lệ',
|
||||
browserNoSandboxWarning: 'Sandbox đã tắt: trang hiện tại cùng nguồn với UI, có toàn quyền phiên (có thể bật lại trong cài đặt)',
|
||||
htmlNoSandboxWarning: 'Sandbox đã tắt: HTML này cùng nguồn với UI, có thể đọc tệp phiên và API nội bộ (có thể bật lại trong cài đặt)',
|
||||
sandboxStatusOn: 'Chế độ sandbox: đã bật · trang không thể truy cập dữ liệu UI và tệp cục bộ, đăng nhập và cookie bên thứ ba có thể không hoạt động',
|
||||
sandboxUnlock: 'Mở khóa tạm (không an toàn)',
|
||||
|
||||
@@ -310,7 +310,6 @@ export const zhHK: Record<string, string> = {
|
||||
browserBlockedScheme: '已封鎖:僅支援 http/https 連結',
|
||||
browserBlockedLoopback: '已封鎖:不允許在瀏覽器中存取本機或內部位址',
|
||||
browserInvalid: '無效的網址',
|
||||
browserNoSandboxWarning: '沙箱已關閉:目前頁面與介面同源,擁有完整工作階段權限(可在設定中恢復)',
|
||||
htmlNoSandboxWarning: '沙箱已關閉:此 HTML 與介面同源,可讀取工作階段檔案與內部介面(可在設定中恢復)',
|
||||
sandboxStatusOn: '沙箱模式:已啟用 · 頁面無法存取介面資料與本地檔案,登入態與第三方 Cookie 可能不可用',
|
||||
sandboxUnlock: '臨時解鎖(不安全)',
|
||||
|
||||
@@ -310,7 +310,6 @@ export const zhMO: Record<string, string> = {
|
||||
browserBlockedScheme: '已封鎖:僅支援 http/https 連結',
|
||||
browserBlockedLoopback: '已封鎖:不允許在瀏覽器中存取本機或內部位址',
|
||||
browserInvalid: '無效的網址',
|
||||
browserNoSandboxWarning: '沙箱已關閉:目前頁面與介面同源,擁有完整工作階段權限(可在設定中恢復)',
|
||||
htmlNoSandboxWarning: '沙箱已關閉:此 HTML 與介面同源,可讀取工作階段檔案與內部介面(可在設定中恢復)',
|
||||
sandboxStatusOn: '沙箱模式:已啟用 · 頁面無法存取介面資料與本地檔案,登入態與第三方 Cookie 可能不可用',
|
||||
sandboxUnlock: '臨時解鎖(不安全)',
|
||||
|
||||
@@ -310,7 +310,6 @@ export const zhTW: Record<string, string> = {
|
||||
browserBlockedScheme: '已封鎖:僅支援 http/https 連結',
|
||||
browserBlockedLoopback: '已封鎖:不允許在瀏覽器中存取本機或內部位址',
|
||||
browserInvalid: '無效的網址',
|
||||
browserNoSandboxWarning: '沙箱已關閉:目前頁面與介面同源,擁有完整工作階段權限(可在設定中恢復)',
|
||||
htmlNoSandboxWarning: '沙箱已關閉:此 HTML 與介面同源,可讀取工作階段檔案與內部介面(可在設定中恢復)',
|
||||
sandboxStatusOn: '沙箱模式:已啟用 · 頁面無法存取介面資料與本地檔案,登入態與第三方 Cookie 可能不可用',
|
||||
sandboxUnlock: '臨時解鎖(不安全)',
|
||||
|
||||
@@ -307,7 +307,6 @@ export const zh = {
|
||||
browserBlockedScheme: '已阻止:仅支持 http/https 链接',
|
||||
browserBlockedLoopback: '已阻止:不允许在浏览器中访问本机或内部地址',
|
||||
browserInvalid: '无效的网址',
|
||||
browserNoSandboxWarning: '沙箱已关闭:当前页面与界面同源,拥有完整会话权限(可在设置中恢复)',
|
||||
htmlNoSandboxWarning: '沙箱已关闭:此 HTML 与界面同源,可读取会话文件与内部接口(可在设置中恢复)',
|
||||
sandboxStatusOn: '沙箱模式:已启用 · 页面无法访问界面数据与本地文件,登录态与第三方 Cookie 可能不可用',
|
||||
sandboxUnlock: '临时解锁(不安全)',
|
||||
@@ -750,7 +749,6 @@ export const en: Record<keyof typeof zh, string> = {
|
||||
browserBlockedScheme: 'Blocked: only http/https URLs are allowed',
|
||||
browserBlockedLoopback: 'Blocked: local and internal addresses cannot be browsed here',
|
||||
browserInvalid: 'Invalid URL',
|
||||
browserNoSandboxWarning: 'Sandbox off: the current page runs with full GUI privileges (re-enable in settings)',
|
||||
htmlNoSandboxWarning: 'Sandbox off: this HTML runs with full GUI privileges (re-enable in settings)',
|
||||
sandboxStatusOn: 'Sandbox mode: on · pages cannot access the GUI\'s data or local files; logins and third-party cookies may not work',
|
||||
sandboxUnlock: 'Temporarily disable (unsafe)',
|
||||
|
||||
+16
-12
@@ -127,21 +127,25 @@ describe('skin contract: the plugin owns no color of its own', () => {
|
||||
// paint. A literal here would drift away from the host under every skin.
|
||||
const styles = readFileSync(resolve(ROOT, 'src/client/sidebar.module.css'), 'utf8')
|
||||
const block = /\.paneCard \{([\s\S]*?)\n\}/.exec(styles)?.[1]
|
||||
expect(block, 'the .paneCard rule must exist').toBeDefined()
|
||||
expect(block!).toContain('border: 0.5px solid var(--dsw-alias-border-l4)')
|
||||
expect(block!).toContain('border-radius: 24px')
|
||||
expect(block!).toContain('background: var(--dsw-alias-bg-layer-1)')
|
||||
expect(block!).toContain('min-height: 56px')
|
||||
expect(block!).toContain('padding: 14px 20px')
|
||||
if (block === undefined) throw new Error('the .paneCard rule must exist in src/client/sidebar.module.css')
|
||||
expect(block).toContain('border: 0.5px solid var(--dsw-alias-border-l4)')
|
||||
expect(block).toContain('border-radius: 24px')
|
||||
expect(block).toContain('background: var(--dsw-alias-bg-layer-1)')
|
||||
expect(block).toContain('min-height: 56px')
|
||||
expect(block).toContain('padding: 14px 20px')
|
||||
// No paint of its own: every color-ish declaration resolves to a token,
|
||||
// and the upstream-only spellings this repo must not copy are absent.
|
||||
for (const declaration of block!.matchAll(/(?:^|\n)\s*(?:background|border|color|font):\s*([^;]+);/g)) {
|
||||
const value = declaration[1]
|
||||
if (!/var\(--dsw-/.test(value)) continue
|
||||
expect(value, declaration[0]).toContain('var(--dsw-')
|
||||
for (const declaration of block.matchAll(/(?:^|\n)\s*(?:background|border|color|font):\s*([^;]*);/g)) {
|
||||
const value = declaration[1] ?? ''
|
||||
// A literal here (hex, rgba, a named colour) would drift from the host
|
||||
// under every skin, so any keyword whose value is not a token fails.
|
||||
expect(
|
||||
value === '' || value.includes('var(--dsw-'),
|
||||
`${declaration[0].trim()} must paint through a --dsw-* token (got: ${value})`,
|
||||
).toBe(true)
|
||||
}
|
||||
expect(block!).not.toContain('--dsw-alias-bg-l1')
|
||||
expect(block!).not.toContain('--dsw-alias-bg-l2')
|
||||
expect(block).not.toContain('--dsw-alias-bg-l1')
|
||||
expect(block).not.toContain('--dsw-alias-bg-l2')
|
||||
})
|
||||
|
||||
it('no icon dataset is shipped as a lazy chunk', () => {
|
||||
|
||||
Reference in New Issue
Block a user