Compare commits

...
22 Commits
Author SHA1 Message Date
figmar 0aafa94c4d chore(gen1): 第一代版本封板 — DSH 2.0.17 兼容 + settings API 迁移 + React #130 修复
- peerDeps 升级到 0.2.0-rc.2
- settings API 迁移: register() → describe() + settings/document-updated
- turnTail slots.register 补 id 字段
- EditorHost viewer.component undefined guard
- 56/56 测试全绿, typecheck/build 通过

tag: v1.2.0-gen1
2026-10-11 16:52:48 +08:00
figmar df6f99c4a3 chore(peer-deps): bump to 0.2.0-rc.2 for DSH 2.0.17 compatibility
PeerDependencies changed from ^0.1.5-rc.1 to ^0.2.0-rc.2 to match
DSH Desktop 2.0.17's built-in dsh packages (Harness v0.2.0-rc.2).

Rollback anchor: git revert this commit to restore 0.1.5-rc.1 peerDeps.

Context: DSH 2.0.17 release notes state '新版内核会检查插件声明的
兼容版本,不兼容的插件可能被跳过'. This is Plan D (force-install
via peerDeps bump) — if runtime API breakage occurs, fall back to
Plan A (rebase onto upstream dsh-better-sidebar 0.25.0).
2026-10-11 14:44:51 +08:00
figmar 40a05af9ba docs(wiki): 01 需求分析改口径——对比官方原生(不再把上游操作面单列一层)
CI / ci (push) Canceled after 0s
CI / ci-windows (push) Canceled after 0s
CI / plugin-mount (push) Canceled after 0s
用户纠正:editor/git/subagent/sidechat/terminal/browser 也是 apes-sidebar
打包进来的,对外是一个完整需求整体,对比的另一端只能是 DSH 官方原生。

- 用官方包自带 README 取证:原生右列 = 容器(sidebar-right) + 唯一页「文件」
  (sidebar-files, 只读) + 唯一查看器(documentpreview, kind=text, 纯文本兜底)
- 新增官方原生做不到的九件事表,作为 user 处境的 before
- 洞察 0 改为「官方假设你只是看,而真实使用是在这儿干活」(只读→可写)
- 新增洞察 6:官方只有一个入口→罗盘不出现;入口一多罗盘才登场(单一工具→工作台)
- 附录补官方证据引文;上游/fork 的区别降为对内维护说明
2026-10-04 02:02:55 +08:00
figmar 2b88c85ea7 docs(wiki): 01-需求分析 改为「原生 vs 上游 vs 本 fork」对比式需求分析
CI / ci (push) Canceled after 0s
CI / ci-windows (push) Canceled after 0s
CI / plugin-mount (push) Canceled after 0s
把用户贴的 10 行 tab 表按供给来源拆成三层,逐行追问没有它时他会怎么办:
- DSH 原生 = 右列容器 + 「文件」页 + documentpreview 文本兜底(证据 external-plugin-guide.md)
- 上游 = 六个操作面(文件/变动/子代理/侧边对话/终端/浏览器)
- 本 fork = 三个态势面(agents/apps/profile)

新增 7 条需求变化洞察(操作面→态势面、零切换棘轮、提问副作用归零、
看得见→敢放手、心智模型外化、价值随并行度放大、增量成本≈0),
U1-U6 收敛为由分析推出的需求表;实现/根因只留附录索引。
2026-10-04 02:00:25 +08:00
figmar 2224991047 docs(wiki): 需求分析改为使用者视角重写
CI / ci (push) Canceled after 0s
CI / ci-windows (push) Canceled after 0s
CI / plugin-mount (push) Canceled after 0s
原稿按「现象/根因(含证据)/需求/验收标准」组织,实质是在讲我们怎么修的;
现在从「用户为什么会需要」写起:谁在用 → 他会怎么说 → 为什么难受 → 他想要什么
→ 怎样算满意(全部是用户能亲手点到、亲眼看到的行为)。

- 五条实现导向的需求 → U1~U6 用户问题(U5 拆成两套侧边栏 / 弹窗被切)
- 新增:不做什么(省得期待错位)、已知的暂时满足不了(应用层 feed、完整子代理树、
  沙箱态相对资源),如实交代边界
- 新增:验收总表(动作 → 应看到)与优先级/状态表
- 实现机制与提交号全部移入附录 A「根因档案」索引,指向 02/03/05/06
2026-10-04 01:55:35 +08:00
figmar 63bcb47ed0 docs(wiki): 建立项目百科(需求分析 → 架构 → 功能 → 部署 → 运维 → 排障 → 开发与测试)
CI / ci (push) Canceled after 0s
CI / ci-windows (push) Canceled after 0s
CI / plugin-mount (push) Canceled after 0s
Gitea 的 wiki 此前是空的。本次把百科的**源文件版本化进仓库**(docs/wiki/),
再用脚本发布到 wiki——文档因此与代码同源、可 review、可回溯,而不是只存在于网页上。

八页(Home 作为导航与项目速览):

  Home          项目速览 / 两条版本线的宿主兼容性差异 / 文档地图 / 谱系说明
  01-需求分析    五条核心需求(现象·根因·验收标准·状态)+ 非目标与约束 + 需求→实现→锚点
  02-系统架构    host/client 分层与产物、三层接口面、面板宿主层叠契约与 isolate 陷阱、关键不变量
  03-功能说明    面板/预览器总表、信息层三 tab 数据来源、HTML 预览交付方式、i18n 与令牌、配置面
  04-部署指南    宿主版本矩阵与选线决策、profile 接线(file: + bundles)、构建、验收清单、卸载与切换
  05-运维手册    分支/tag 策略与回滚剧本、发布纪律(版本三元组/非确定性产物)、巡检、备份铁律、诊断
  06-故障排查    8 类症状 → 根因 → 处置(含本会话实机定位过的全部案例)+ 定位手法
  07-开发与测试  工程约定、目录、门禁基线、测试分层与契约 spec、扩展点、测量式 re-base 方法

脚本 scripts/push-wiki.sh:幂等发布(POST 新建 / PATCH 覆盖),凭据走 git credential helper
(临时 netrc,600,退出即删),不落盘、不进 argv。已实测:8 页全部发布成功,复推为更新。

内容纪律:只写**本次会话实证过**的事实(提交号、行数、层级取值、产物大小、宿主版本矩阵、
静默禁用机制、re-base 步骤),未验证的部分明确标注为待验证或已知限制;并如实说明本仓库
历代承载过 v1 / v2 / 当前 fork 三代实现,本百科描述的是 main 上的 fork 线。
2026-10-04 01:19:31 +08:00
figmar 4422001c5e fix(panels): mount the panel host inside the shell's framed content viewport
CI / ci (push) Canceled after 0s
CI / ci-windows (push) Canceled after 0s
CI / plugin-mount (push) Canceled after 0s
On DSH Desktop the plugin's panel layer covered host modals — the community
market dialog (dsh-community-market, which renders into the shell's own
`shell.overlay` slot) had its lower-left corner clipped by our workbench.

The fence is not a z-index mistake on either side. The shell's framed modes do:

  const contentViewport = document.getElementById('root')
  document.body.dataset.dshDesktopMode = environment.mode
  /* …and tags #root with data-dsh-desktop-content-viewport */

with

  body:is([data-dsh-desktop-mode="compatibility"], […"extended"]) [data-dsh-desktop-content-viewport] {
    isolation: isolate;
  }

`isolation: isolate` makes #root a stacking context (z-index: auto) wrapping the
whole app INCLUDING the shell's overlay layer (`.dshDesktopOverlay`,
z-index: 1000) that hosts plugin overlays. Our host was appended to
document.body — OUTSIDE that context — and because it is a positioned element
with z-index 25 it painted above the entire context, so the market's 1000 never
competed. The shell's overlay layer is the boundary plugins are meant to sit
under, so the fix is to join the layer, not to renumber ours: panelHostParent()
mounts the host into #root when the shell framed one (body otherwise, i.e. the
plain web deployment keeps today's behaviour), where the documented 25 keeps
its meaning — above the app frame (z-index auto), below the overlay layer.

The re-anchor guard now re-appends into the resolved parent instead of always
`document.body`, and also watches `data-dsh-desktop-mode` on <body> (the same
shell effect that stamps the viewport) so a shell that frames #root after our
mount still gets the host moved.

Verified: typecheck clean; build clean; 1367 passed / 9 skipped (131 files,
+3 parenting specs). Revert is one line per call site if the shell's overlay
layer turns out to sit below our panels for a reason not visible in the CSS.
2026-10-04 00:08:13 +08:00
figmar 4c242acf7b fix(html-preview): deliver the document through srcdoc so a sandboxed frame can load it
CI / ci (push) Canceled after 0s
CI / ci-windows (push) Canceled after 0s
CI / plugin-mount (push) Canceled after 0s
The preview showed nothing but the bare word `forbidden` in DSH Desktop. The
cause is not this plugin's routes but the host, one layer above them:
`DesktopWebServer` wraps EVERY route with `permits()`, and in the default
isolated/扩展 mode `ordinaryBrowserEnabled` is false, so a request is served
only when it carries the Electron renderer capability
(`x-dsh-desktop-renderer`). That header is attached solely to MAIN-frame and
SAME-ORIGIN-subframe traffic (`requestComesFromRendererOrigin`), and a frame
sandboxed WITHOUT allow-same-origin is an opaque origin at request time — so
the preview document request could never be served, while every other sidebar
route (all main-frame) worked fine.

The fix keeps the sandbox boundary exactly where it was and moves the delivery:
the document is fetched by the MAIN frame — which the host does trust — and
handed to the frame through `srcdoc`. New shared component HtmlPreviewFrame
owns that path plus the sandbox tokens for BOTH surfaces (the editor viewer and
the changes tab's op render preview, which had no escape hatch at all), so the
two can no longer drift; a failed fetch falls back to handing the frame the
route URL, leaving the route's own answer visible instead of a blank pane.

`withPreviewBase` injects `<base href="<the file's route directory>">`: a
srcdoc document has no URL of its own, so without it `./style.css` would
resolve against the GUI page. A document that declares its own base is left
alone.

Two consequences, both deliberate:
- sandboxed: the frame stays opaque, so a self-contained document previews
  completely while a multi-file one shows its markup with the relative assets
  missing (those requests are opaque-origin too — inlining them is a separate
  job);
- unlocked (the warned per-surface unlock, or the setting): the srcdoc frame
  inherits the GUI origin — which is what that escape hatch already meant — and
  there relative references resolve and load like any other same-origin
  request, so multi-file previews work in that mode.

Verified: typecheck clean; build clean; 1364 passed / 9 skipped (130 files,
+13: the pure base-rewrite spec, the delivery spec, and the updated
sandbox-contract assertions, which now pin srcdoc + no route src instead of
the old route-src shape).
2026-10-03 23:15:44 +08:00
figmar e5d34ac89f merge: sync fork onto upstream v0.19.1 — one sidebar instead of two
CI / ci (push) Canceled after 0s
CI / ci-windows (push) Canceled after 0s
CI / plugin-mount (push) Canceled after 0s
Brings 5a7e213 (the v0.19.1 re-base) onto main. The merge result is deliberately
the sync branch's tree verbatim: every file both sides touched is upstream
v0.19.1 + our rename, and the v0.18.0-era content main still carries for those
files (including the two package.json.bak.* scratch backups, FreeWindow, the
hand-written openpath interception and the v0.18.0 service surface) is
superseded rather than textually merged — a hunk-level merge of a 135-commit
upstream jump would be a guess, not an integration.

Net effect on main: the fork now runs on the base that registers its content
into DSH's OWN right sidebar, so the duplicate second sidebar and its duplicate
toggle cluster are gone. The fork's own contribution is unchanged and intact:
the apes:* tabs (agents / apps / profile) in src/client/apes/, the switch.sh
two-line switcher, and the fork identity (1.2.0).

Tree verified identical to sync/v0.19.1 (git diff --quiet) before this commit.
2026-10-03 22:51:27 +08:00
figmar 5a7e2139a0 chore(sync): re-base the fork onto upstream dsh-better-sidebar v0.19.1
The fork was pinned at upstream v0.18.0. That is the entire reason the window
ran TWO sidebars and TWO near-identical panel toggles: DSH 2.0.10 ships its own
right sidebar (@deepseek-ai/dsh-client-ui-sidebar-right), and upstream only
adopted it in v0.19.0-alpha.1 — which arrived after our fork point. v0.19.1 is
also the one upstream line whose support range actually matches our host
(engines.dsh >=0.1.5-rc.1, 14 DSH peers ^0.1.5-rc.1, host = 0.1.5-rc.2), so it
is the only tag the host's startup precheck accepts.

What upstream v0.18.0 → v0.19.1 brings
- src/client/native/{index,surface,tab-adapter}.tsx: every tab the plugin can
  draw becomes an OFFICIAL native tab type + body, and an external plugin's
  descriptor registered later gets its native type too. This is what retires
  the fork's own right panel — and with it the duplicate toggle cluster
  (`toggleCluster` is gone from v0.19.1).
- native tab-chip icons via the `sidebar.right.pane.tab.title` seat, coloured
  built-in tab icons, file/folder glyphs from DSH's own `FileTypeIcon`.
- guide descriptions (DSH renders them while the guide lists <= 4 entries),
  tab bodies that fill the panel, and the 0.1.5-rc.2 adaptation work we were
  carrying by hand (peer/devDep pinning, primitives' transitive devDeps).

How the sync was done
Rebuilt the tree from the v0.19.1 tag rather than resolving 53 conflicted
files: our v0.18.0 delta was measured first and is only (a) one mechanical
rename `dsh-better-sidebar` -> `dsh-apes-sidebar` and (b) ten real edits. So:
tag tree + rename (scoped: code/config/scripts/tests/README, while the 46
historical docs/plans records and switch.sh's upstream package name stay
untouched) + re-applied deltas + our apes module.

Fork deltas re-applied on the new base
- src/client/apes/ (icons / locales / tabs): the three apes:* information-layer
  tabs (agents / apps / profile), registered through the same BetterSidebarService
  as the builtins — which is exactly why they now ride upstream's native adapter
  into the official sidebar for free.
- identity: package.json (name/version 1.2.0/description/forkedFrom), dsh.plugin.json
  (id/version), SIDEBAR_SERVICE_VERSION 1.2.0, README fork notice,
  docs/external-plugin-guide.md.
- scripts/switch.sh + the pnpm switch* scripts (apes <-> upstream two-line switch).
- tests/apes-tabs.spec.ts (unescaped: it starts from the v0.19.1 base).

Dropped
- the Phase A native-registration spike (`apes/native-tabs.tsx` + its spec): it
  re-derived by hand exactly what upstream's native layer already does — the
  hand-written bridge is dead weight now, and v0.19.1's own e2e/jsdom lanes
  cover the same seat contract far better.
- the unused apesProfileGuideDesc key and the two package.json.bak.* backups.

Verified on the new base: typecheck clean; build clean; 1351 passed / 9 skipped
(128 files), up from 1255 on the old base.
2026-10-03 22:51:09 +08:00
figmar 96cc334143 spike(A): publish apes:profile as an official right-sidebar tab type
Phase A spike of the C8 root fix: instead of only rendering in the fork's
own shell, apes:profile now also registers with DSH's OFFICIAL right
sidebar through its documented two-stage extension protocol:

  1. ctx.sidebarRightTabs.register({ id, kind, priority:'extension',
     title, guide })  — the type declaration
  2. ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register(
     { name:'sidebar.right.pane.tab', key: id }, Body))  — the keyed body

The registry is reached through ctx.inject(['sidebarRightTabs'], ...) (the
same pattern intercept.tsx uses for the async remote.session namespace), so
activation order cannot strand the bridge and a deployment without the
official sidebar stays a silent no-op — the fork's own shell is untouched.

The body reuses the existing ApesProfileView (content is shared, never
duplicated); only the shell-scope binding differs. Guide entry order 70
keeps the apes trio's position in the official entry list.

Verified: typecheck clean; build clean; 1255 passed / 9 skipped (120 files,
6 new bridge specs pinning the two-stage contract, both no-op paths, and the
HMR-safe unwind).

Local commit only — not pushed until the in-app spike is confirmed.
2026-10-03 20:42:13 +08:00
figmar 9ea9e9b9fa fix(c8): match native icon family — outline glyphs at native scale
CI / ci (push) Canceled after 0s
CI / ci-windows (push) Canceled after 0s
CI / plugin-mount (push) Canceled after 0s
The apes tab glyphs (智能体/应用/画像) were fill-only paths spanning only
10-12 of the 16 viewBox units, so they rendered visibly smaller and lighter
than the DSH native glyphs (which fill ~92-96% of the box) in the + new-tab
menu and the right-panel tile grid.

Rewrite them in this repo's own outline recipe (src/client/icons.tsx): 16x16
viewBox, artwork on 1.5-14.5, 1.5px currentColor stroke, round caps/joins.
Renamed IconAgentFill16/IconGaugeFill16/IconUserFill16 ->
IconAgentOutline16/IconAppsOutline16/IconUserOutline16.
2026-10-03 20:05:45 +08:00
figmar 2f3944a8bd fix(c8): fill-style icons — visual unity with native tabs
CI / ci (push) Canceled after 0s
CI / ci-windows (push) Canceled after 0s
CI / plugin-mount (push) Canceled after 0s
Root cause: DSH's @deepseek-ai/dsh-client-ui-primitives only ships
outline variants (mask+stroke) for agent/gauge/user icons, while native
tabs use fill:currentColor paths (solid, heavy visual weight). This made
the apes tabs look 'lighter' than the official ones in the + menu.

Fix: custom fill-style SVG icons in src/client/apes/icons.tsx
(IconAgentFill16, IconGaugeFill16, IconUserFill16) — matching the
fill:currentColor path style of DSH native tabs (IconCodeOutline16,
IconFolderOpen16, etc.).

Also: order 40/45/50 → 60/65/70 (avoid clash with terminal=40,
browser=50 in the native tab order).
2026-10-03 19:31:00 +08:00
figmar fbd183e93f fix(c8): order 60/65/70 — 不与原生 terminal(40)/browser(50) 冲突
CI / ci (push) Canceled after 0s
CI / ci-windows (push) Canceled after 0s
CI / plugin-mount (push) Canceled after 0s
2026-10-03 19:20:20 +08:00
figmar cebc21aef2 fix(c8): sidebar entry unity — DSH Icon*16 + order 40-50
Root cause of the visual split:
1. Icon system mismatch: apes used react-icons (FiCpu/FiGrid/FiUser,
   static 14px ReactNode) while native builtins use DSH's Icon*16
   components (function form, host-controlled size). Two different
   icon libraries → inconsistent visual style.
2. Order 901-903 pushed apes tabs far after native builtins (10-35),
   creating two visually separate groups in the tab strip.

Fix:
- icon: react-icons Fi* → @deepseek-ai/dsh-client-ui-primitives
  Icon*16 (function form, same as builtins):
    agents  → IconAgentPresetOutline16
    apps    → IconGaugeOutline16
    profile → IconUserOutline16
- order: 901/902/903 → 40/45/50 (immediately after native builtins)
- SIDEBAR_SERVICE_VERSION + dsh.plugin.json version → 1.1.0
  (lockstep with package.json, required by manifest tests)

Gate: typecheck ✓ / build ✓ / test 119 files · 1249 passed · 9 skipped · 0 failed ✓
2026-09-16 02:44:35 +08:00
figmar aa28c1fbc7 feat(v1.1.0): adapt to DSH 2.0.10 (0.1.5-rc.2), drop apes:tasks, DSW token styles
C1-C5 env repair:
- align all @deepseek-ai/dsh-* peer+devDeps to 0.1.5-rc.2 (DSH 2.0.10 ships it)
- fix 0.1.5-rc.2 npm packaging defect: 15 primitives runtime deps (anser,
  shiki, katex, marked, ... ) were mis-declared as devDeps in the published
  package -> add to local devDependencies so vitest can resolve them
- fix sidechat-seed contract: dsh-session@0.1.5 assertAssistantSettlementShape
  now requires a top-level data.stream: Array on assistant messages -> seed
  helper emits stream: []

C6 slim tabs:
- remove apes:tasks (overlaps the official DSH session tree in the native
  sidebar); keep apes:agents / apes:apps / apes:profile
- clean dead code: ApesTasksView + job helpers import + apes i18n dead keys
  (tasks/status/duration) and the unused menu-title keys

C7 style align:
- apes view inline colors -> DSW alias tokens (--dsw-alias-bg/border/label/
  state-success/interactive) with literal fallbacks; geometry (font/spacing/
  radius) stays inline (DSH exposes no --ds-* geometry tokens to plugin panels)

gate: typecheck ok / build ok / test 119 files · 1249 passed
2026-09-16 01:57:46 +08:00
figmar 34e86141bd feat(scripts): switch.sh — apes↔upstream 两线对称切换
CI / ci (push) Canceled after 0s
CI / ci-windows (push) Canceled after 0s
CI / plugin-mount (push) Canceled after 0s
- 同一 profile 内二选一(双装不可行: 同 cordis entry id 冲突)
- 探测当前线 → 移除对手线依赖+bundle → 写目标线 → pnpm install → 校验 bundle
- 幂等可重复跑; --status 只读; --dry-run 不写文件; --profile 指定 profile
- aps 线用 file: 本地源码, upstream 线用 registry latest/指定版本
- 修复 pnpm spec 双 @ bug (值不含包名, pnpm 从 key 取包名)
- 双向真实切换验证通过 (aps→upstream→aps, 临时 profile 副本, EXIT=0)
- package.json 加 switch/switch:status/switch:aps/switch:upstream 入口
- switch.sh 加入 files (发布时带上)
2026-09-07 02:25:15 +08:00
figmar c0441d3a7e docs: README 补 v1.0.0 fork 版本更新说明 (4 个 apes tab + 数据接入)
CI / ci (push) Canceled after 0s
CI / ci-windows (push) Canceled after 0s
CI / plugin-mount (push) Canceled after 0s
2026-09-07 02:02:55 +08:00
figmar 427266a191 feat(apes): Phase 2 数据接入 — 4 个信息层 tab 接真实会话数据
CI / ci (push) Canceled after 0s
CI / ci-windows (push) Canceled after 0s
CI / plugin-mount (push) Canceled after 0s
- apes:tasks  接 jobsBySession + running sessions(live 任务列表:
  label/status/kind/耗时 + owner 归属,复用 collectTreeJobs/orderJobs)
- apes:agents 接 subagent 拓扑(rootAncestor + subagentsByParent 目录,
  parent→children activity running/inactive, 降级用 byId origin 过滤)
- apes:profile 接 byId[current](当前会话画像: title/cwd/running + agent 状态)
- apes:apps    展示 cwd/workspace 信息(无 DSH host feed, 保留说明)

数据源: ctx.sessions.list (useSyncExternalStore, uSES 安全订阅)
复用 pure helper: subagent-lineage.ts (rootAncestor/isSideThreadSummary)
  + subagent-jobs.ts (collectTreeJobs/orderJobs/isJobLive/jobDotState)
UI 独立内联样式, 不耦合 builtin CSS module / StateDot (保持 apes/ 隔离)

locales: 加 status/duration/empty/workspace 文案 (zh+en, 含 {param} 插值)
gate: 1250 passed | typecheck exit 0 | tsdown build complete
2026-09-07 01:45:39 +08:00
figmar a7bcdef21b feat(apes): Phase 1b — 补 apes:apps 信息层 tab (4 tab 齐活)
CI / ci (push) Canceled after 0s
CI / ci-windows (push) Canceled after 0s
CI / plugin-mount (push) Canceled after 0s
- tabs.tsx: +FiGrid icon, +AppsView placeholder, +apes:apps descriptor (order 902, profile 推到 903)
- locales.ts: +apesApps/apesAppsTitle/apesAppsHint zh/en 4 key
- tests: 3→4 tab 断言 (id 列表/contract/order 严格递增/disposer/i18n zh+en)

4 个 apes:* tab: tasks(900) / agents(901) / apps(902) / profile(903)
gate: typecheck + build + test 1250 passed / 9 skipped / 0 failed (无回归)
2026-09-06 21:32:49 +08:00
figmar 257b8b4eea feat(apes): Phase 1 — 注册 3 个 apes:* 信息层 tab (tasks/agents/profile)
- 独立 src/client/apes/ 模块 (D1=B), index.tsx 仅 3 处最小改动
- 3 个静态占位 tab (react-icons FiList/FiCpu/FiUser), order 900+ 排在上游 builtins 之后
- 隔离 i18n 命名空间 'apes' (不碰上游 betterSidebar 字典), zh/en 双语
- index.tsx: attachApesLocale + 字典注册 + registerApesTabs effect
- tests/apes-tabs.spec.ts: 9 测试 (注册契约 + disposer + i18n zh/en/兜底)

gate: typecheck + build + test 1250 passed / 9 skipped / 0 failed

apps tab 延后 Phase 1b, messages 已砍 (无 DSH message-stream API)
2026-09-06 21:10:13 +08:00
figmar 2a7e030b3b chore: fork dsh-better-sidebar v0.18.0 as dsh-apes-sidebar v1.0.0
- Rename dsh-better-sidebar → dsh-apes-sidebar across 74 files (262 occurrences)
- Bump version 0.18.0 → 1.0.0 (package.json + dsh.plugin.json + SIDEBAR_SERVICE_VERSION)
- Add forkedFrom metadata to package.json (upstream pin: commit 9e1a034)
- Add fork notice banner to README.md (MIT attribution preserved)
- Keep original MIT copyright (c) 2026 dsh-external
- Full gate green: typecheck + build (6 bundles) + test (1241 passed / 9 skipped)
2026-09-06 18:09:09 +08:00
109 changed files with 3675 additions and 1102 deletions
+3 -3
View File
@@ -16,7 +16,7 @@ body:
options:
- label: 我已搜索现有 issue,确认没有**完全相同**的条目(只有完全一致才算重复)| I searched existing issues and confirmed there is no **exact duplicate** (only exactly identical issues count as duplicates)
required: true
- label: 我已确认使用最新版本 dsh-better-sidebar / I confirmed I am on the latest dsh-better-sidebar version
- label: 我已确认使用最新版本 dsh-apes-sidebar / I confirmed I am on the latest dsh-apes-sidebar version
required: false
- type: dropdown
@@ -58,8 +58,8 @@ body:
id: plugin-version
attributes:
label: 插件版本 / Plugin version
description: dsh-better-sidebar 的版本号 | The dsh-better-sidebar version
placeholder: "如 0.13.1(`dsh plugin --profile web add dsh-better-sidebar@latest` 安装的版本)"
description: dsh-apes-sidebar 的版本号 | The dsh-apes-sidebar version
placeholder: "如 0.13.1(`dsh plugin --profile web add dsh-apes-sidebar@latest` 安装的版本)"
validations:
required: false
+3 -3
View File
@@ -35,7 +35,7 @@ jobs:
# config; the runner has none by default.
- name: Configure git identity for tests
run: |
git config --global user.name "dsh-better-sidebar-ci"
git config --global user.name "dsh-apes-sidebar-ci"
git config --global user.email "ci@dsh.invalid"
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -84,7 +84,7 @@ jobs:
# identity and rely on ambient config, which the runner lacks.
- name: Configure git identity for tests
run: |
git config --global user.name "dsh-better-sidebar-ci"
git config --global user.name "dsh-apes-sidebar-ci"
git config --global user.email "ci@dsh.invalid"
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -188,7 +188,7 @@ jobs:
run: pnpm test:mount
# Aggregate double-mount regression: a fixture aggregate bundle mounts
# dsh-better-sidebar under its own entry id BEFORE the plugin's bundle
# dsh-apes-sidebar under its own entry id BEFORE the plugin's bundle
# row; the plugin must back off (no duplicate /sidebar/api, boot OK).
# Uses the pinned DSH CLI installed above.
- name: Aggregate double-mount regression
+3 -3
View File
@@ -1,6 +1,6 @@
name: Release
# Publishes `dsh-better-sidebar` to npm via npm Trusted Publishing (OIDC) —
# Publishes `dsh-apes-sidebar` to npm via npm Trusted Publishing (OIDC) —
# no NPM_TOKEN secret required, provenance is attached to the tarball.
#
# Trigger: a GitHub Release is published (tag vX.Y.Z). The tag MUST match
@@ -12,7 +12,7 @@ name: Release
# supports the stable DSH line); stable versions publish as `latest`.
#
# One-time npm-side setup (manual, on npmjs.com):
# 1. package `dsh-better-sidebar` -> Settings -> Publishing access
# 1. package `dsh-apes-sidebar` -> Settings -> Publishing access
# -> Trusted Publishers -> "Add Trusted Publisher".
# 2. Provider: GitHub Actions
# Organization or user: omdsh-dev
@@ -66,7 +66,7 @@ jobs:
# (see tests/smoke.spec.ts); the runner has none by default.
- name: Configure git identity for tests
run: |
git config --global user.name "dsh-better-sidebar-ci"
git config --global user.name "dsh-apes-sidebar-ci"
git config --global user.email "ci@dsh.invalid"
- name: Install dependencies
+4 -4
View File
@@ -1,4 +1,4 @@
# dsh-better-sidebar 仓库规则(AGENTS)
# dsh-apes-sidebar 仓库规则(AGENTS)
> 本文只含**项目全局开发规则**(面向贡献者与 agent)。
> 消费插件接入 API 全参考(`ctx.betterSidebar` 服务、TabDescriptor / FileViewerDescriptor 全字段、声明式设置、原生右侧栏承载面、皮肤契约等)→ [docs/external-plugin-guide.md](docs/external-plugin-guide.md);逐特性设计史(含实施偏差记录)→ [docs/plans/](docs/plans/)。
@@ -17,7 +17,7 @@
## 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-apes-sidebar]` 挂载、无错误条/pageerror/console 错误,展开 DSH 原生右侧栏后经其 guide 页逐个打开插件 tab 类型(含终端懒加载 chunk),再经插件文件树(原生 `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 整体替换默认值)。
@@ -33,12 +33,12 @@
2. **Remote gateway 斜杠 RPC(唯一方言)**:`POST /api/workspace/create`,payload 恰为 `{args: {...}}`,**args 按控制器 TS 参数名包装**(`workspace/create`、`session/create` → `{args:{request:{...}}}`;`session/list` 参数名 `_request` **不可省略**——`{}` 也被拒 `args fields do not match the descriptor`);envelope `method` 与路径一致,点分路径 404。请求由 `tests/e2e/host-protocol.ts` 的 `rpcAttempt` 构造、`tests/e2e-host-protocol.spec.ts` 锁定;要调新方法先在真机验参数名再进 `RPC_ARGS_KEY`。
3. **`MarkdownText` labels 嵌套契约**:必填 `labels: { code: { copyLabel, copiedLabel }, footnotes }`(漏传回退硬编码中文)。四个渲染点(mermaid.tsx / MarkdownHtml.tsx / TextEditor.tsx / SideChatView.tsx)统一走 `src/client/markdown-labels.tsx` 的 `markdownTextProps()`。
4. **侧边对话转录走自有路由,不碰客户端宿主 RPC**:`ctx.connection.api`(含 `sessions.history`)在 alpha.1 整体移除,继任 `session/follow|page` 又对 `origin:'subagent'` 会话强制 subagent 地址(普通 `{kind:'session'}` 被 `agent-busy` 拒)且分页 `throughSeq` 不得超当前游标。转录因此由 **`sidechat.events`** 插件路由供给(`src/sidechat-routes.ts`:live 读 `agent.session.snapshotEvents()`、冷读 `sessionPersistence.inspect`,服务端 `session/end-seed` 切割 + `afterSeq` 增量);`ctx.connection` 镜像与 inject 已删。**0.1.5 起实时增量不在日志里**:`assistant/chunk` 事件被删除,进行中的模型增量改由 `agent/assistant-stream` 瞬时帧发布(`start` / `chunk` / `end`,不入会话日志),结算时才落 `assistant/message`(内嵌 `stream`)或 `assistant/attempt`(失败尝试,内嵌 `stream`)。插件在宿主侧 `src/assistant-live.ts` 折叠这些帧成有界缓冲,`sidechat.events` 的 `live` 字段按「当前 attempt 全量、每次轮询替换」下发(不是增量),转录映射与继承快照都读它。设计见 [docs/plans/2026-08-20-sidechat-tab-design.md](docs/plans/2026-08-20-sidechat-tab-design.md) §10。
5. **`dsh-settings` 无运行时 `settingsNamespace`**:命名空间合法性校验转为编译期模板字面量 `SettingsNamespaceInput`(小写字母开头 + `[a-z0-9-]` 尾部),`'dsh-better-sidebar'` 字面量直接过——宿主侧直接传常量(`src/index.ts` 的 settings inject)。
5. **`dsh-settings` 无运行时 `settingsNamespace`**:命名空间合法性校验转为编译期模板字面量 `SettingsNamespaceInput`(小写字母开头 + `[a-z0-9-]` 尾部),`'dsh-apes-sidebar'` 字面量直接过——宿主侧直接传常量(`src/index.ts` 的 settings inject)。
6. **`dsh-subagent` 的 `SUBAGENT_DESCRIPTOR_VERSION` 2 → 3**:sidechat 种子的 `subagent/descriptor` 版本由宿主包盖章,插件不硬编码;测试断言跟随常量(`tests/sidechat-routes.spec.ts`),勿钉字面量。
7. **`@deepseek-ai/dsh-client-runtime` 包已消亡**(继任 seed 是裸名 `dsh-client-store`,无 `/client` 子路径):peerDependencies、devDependencies、`dsh.client.inject`、chunk externals 白名单(`src/client/chunk-loader.ts` / `tsdown.config.ts` / `tests/chunk-loader.spec.ts` / `tests/manifest-consistency.spec.ts` 四处同步)均已无该条目。
8. **e2e scratch profile 的 `minimumReleaseAgeExclude` 含 `'@deepseek-ai/*'`**(`scripts/e2e-mount.sh` / `e2e-aggregate-mount.sh`,与仓库根 `pnpm-workspace.yaml` 同策):alpha 版本常在发布后 24h 内跑 lane,pnpm 11 的 `minimumReleaseAge` 默认会拒装新鲜包。
9. **插件开发树的 dsh-* 传递 peer 需提升为 devDependencies**(alpha.3 首见):`dsh-subagent` 等 npm 包把 `dsh-attachment` 等 dsh-* 姊妹包全部声明为 peerDependencies(由宿主 bundle 树统一提供,宿主侧无此问题),插件仓库若只直接依赖其中一部分,其余 peer 在 pnpm 下会解析到树上残留的旧版——如 `dsh-attachment@0.1.1-rc.1` 缺 `admitPromptContent` 导出、`dsh-subagent` 产物 import 它时测试加载即崩。因此 devDependencies 需涵盖 dev 树实际触达的全部 peer(attachment / code-runtime / scope / session-projection / system-prompt / user-approval / util-time 七个即为此提升,与直接依赖同款精确钉版);`@deepseek-ai/cordis` peer 自 alpha.3 起要求 `^4.0.2`(上游全线 peer 已升)。适配新 alpha 版本时先跑 `pnpm peers check`,把新失配的传递 peer 一并提升进 devDependencies。例外:**`dsh-client-locale` 的 peer/devDep 允许落后于基线**(alpha.5 时上游停在 0.1.2-alpha.3 未发新版)——peer 下限 `^0.1.2-alpha.3` 天然容纳同 tuple 的 alpha.5 运行时,此时保持旧钉版并在 `pnpm-workspace.yaml` 注明,待上游发版再追平;rc.1 起上游恢复发版,该包回到与 DSH 同 tuple(0.1.2-rc.1 → 0.1.5-rc.1,peer 下限 `^0.1.5-rc.1`),该例外消除。**alpha.2 起还有一类:上游包自身的 `dependencies` 丢失**——`@deepseek-ai/dsh-client-ui-primitives@0.1.5-alpha.2` 的 manifest 不再声明任何 `dependencies`(alpha.1 声明了 19 个),但 `lib/*.js` 仍裸 import `anser` / `shiki` / `@shikijs/langs/*` / `mdast-util-*` / `micromark-*` / `katex`。宿主由预构建前端 bundle 满足,独立安装的 dev/test 树不会——vitest 一碰 primitives 就 `Cannot find package 'anser'`。此时把 alpha.1 声明的同一组版本提升进 devDependencies(`clsx` 已在 dependencies 无需重复)。**rc.1 复查:`@deepseek-ai/dsh-client-ui-primitives@0.1.5-rc.1` 仍不声明任何 `dependencies`,bundle 仍裸 import 同一组包**(rc.2 同:只把 `CodeFileIcon` 的 SVG 数据拆进了新文件,无 `dependencies`)——这组提升不得回退,`pnpm peers check` / 单测一旦报 `Cannot find package 'anser'` 即是有人回退了。
10. **右侧栏是 DSH 原生栏,插件只提供 tab 类型**:聊天里一切文件打开(工具行 / 产物行 / 正文提及 / 行内代码路径)统一走 `ctx.sidebarRight.openResource(fileAddressFor(sessionId, cwd, path))`(`packages/client/ui-chat/src/client/apply.ts` 是唯一调用点),`remote.session.openWorkspacePath` 在 0.1.5 客户端已无调用者,插件的 openpath 拦截随之删除。插件的每个 `TabDescriptor` 注册成原生 tab 类型(`kind = descriptor.id`,`extension` 带)+ 原生 tab 体(`sidebar.right.pane.tab` keyed 槽,key = `dsh-better-sidebar:<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-apes-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` 时由宿主补方块占位。
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`。
---
+1 -1
View File
@@ -1,4 +1,4 @@
# dsh-better-sidebar 命令面薄封装:目标仅转发 package.json scripts(唯一事实源),
# dsh-apes-sidebar 命令面薄封装:目标仅转发 package.json scripts(唯一事实源),
# 新增/改名 script 不需要动这里;本文件只补目标发现(help)与 CI 门禁聚合
# (check / mount / mount-aggregate / registry)。
+52 -37
View File
@@ -1,4 +1,19 @@
# dsh-better-sidebar
# dsh-apes-sidebar
> [!NOTE]
> **Fork notice** — 本项目是 [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) 的 fork,
> 基线 **upstream v0.19.1**(commit `8753096`,MIT)。
>
> - **上游 v0.18.0 → v0.19.1 的关键变化**:v0.19.0-alpha.1 起,插件的内容**注册进 DSH 原生右侧栏**
> (`src/client/native/`)——不再自绘第二套右侧面板与开关按钮。这正是本 fork 此前"窗口里有两套侧边栏
> 与两套开关"的根因;升到 v0.19.1 后**只剩一套**。
> - **本 fork 的增量**:`src/client/apes/` 下的 3 个信息层 tab(`apes:agents` 智能体 / `apes:apps` 应用 /
> `apes:profile` 画像),按 DSH 标准 `TabDescriptor` 接口注册——因此它们会自动获得上游原生适配层
> 的全部待遇(进原生 guide、tab 芯片图标、开合与分栏)。
> - 版本线独立于上游:`1.2.0`(fork)。上游历史版本见其 CHANGELOG。
> - **📚 项目百科**(需求分析 → 系统架构 → 功能说明 → 部署指南 → 运维手册 → 故障排查 → 开发与测试):
> <https://gitea.apescale.com/figmar/apes-sidebar/wiki> 源文件在仓库 `docs/wiki/`,
> 改完用 `bash scripts/push-wiki.sh` 一条命令发布。
> [!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)。
@@ -6,14 +21,14 @@
<!-- Hero -->
<div align="center">
<b style="font-size: 1.15em;">一个服务化的侧边栏框架,一套开箱即用的完整工作台</b><br /><br />
<a href="https://www.npmjs.com/package/dsh-better-sidebar"><img alt="npm version" src="https://img.shields.io/npm/v/dsh-better-sidebar" /></a>
<a href="https://www.npmjs.com/package/dsh-better-sidebar"><img alt="npm downloads" src="https://img.shields.io/npm/dm/dsh-better-sidebar" /></a>
<a href="https://www.npmjs.com/package/dsh-apes-sidebar"><img alt="npm version" src="https://img.shields.io/npm/v/dsh-apes-sidebar" /></a>
<a href="https://www.npmjs.com/package/dsh-apes-sidebar"><img alt="npm downloads" src="https://img.shields.io/npm/dm/dsh-apes-sidebar" /></a>
<a href="https://github.com/omdsh-dev/DSH-better-sidebar/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/omdsh-dev/DSH-better-sidebar/actions/workflows/ci.yml/badge.svg" /></a>
<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://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 />
<a href="https://github.com/topics/dsh-apes-sidebar"><img alt="插件生态:GitHub topic dsh-apes-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 />
<b>右侧栏 + 底部面板双工作台</b>,并把 <code>ctx.betterSidebar</code> 服务开放给所有插件——<br />
通过 <code>registerTab</code> / <code>registerFileViewer</code> 注册新的侧边栏页面与文件预览器。
@@ -24,7 +39,7 @@
</div>
<div align="center">
<img alt="dsh-better-sidebar 工作台截图" src="https://github.com/user-attachments/assets/991c4b70-d45a-461f-a8c8-0a28b4218e60" />
<img alt="dsh-apes-sidebar 工作台截图" src="https://github.com/user-attachments/assets/991c4b70-d45a-461f-a8c8-0a28b4218e60" />
<video src="https://github.com/user-attachments/assets/23187822-047e-45cc-b480-fe997bd55b86" muted autoplay loop playsinline controls width="100%"></video>
</div>
@@ -66,12 +81,12 @@
**支持的 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>
> 📌 **正式版**:`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.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-apes-sidebar@0.19.0-alpha.1`;0.1.2-rc.1 稳定线用户继续用 `dsh-apes-sidebar@0.18.x`;DSH ≤ 0.1.1-rc.2 请用 `dsh-apes-sidebar@0.17.1`。
```sh
dsh plugin --profile web add dsh-better-sidebar@latest # 首次会因 pnpm 11 拦截 node-pty 构建脚本而失败(依赖已写入)
dsh plugin --profile web add dsh-apes-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-apes-sidebar@latest # 重跑即成功
```
装完**硬刷新浏览器**(Cmd/Ctrl+Shift+R)即可看到侧边栏(DSH 对 client 改动热加载,无需重启;仅 host 半更新时需要重启)。
@@ -79,10 +94,10 @@ dsh plugin --profile web add dsh-better-sidebar@latest # 重跑即成功
**方式二:让 DSH 自己装**——把下面这段提示词发给任意一个 DSH 会话:
```text
帮我安装 dsh-better-sidebar 插件(DSH 侧边栏工作台),步骤:
1. 执行 dsh plugin --profile web add dsh-better-sidebar@latest(首次会被 pnpm 11 拦截 node-pty 构建脚本而失败,属正常)
帮我安装 dsh-apes-sidebar 插件(DSH 侧边栏工作台),步骤:
1. 执行 dsh plugin --profile web add dsh-apes-sidebar@latest(首次会被 pnpm 11 拦截 node-pty 构建脚本而失败,属正常)
2. 在 ~/.dsh/profiles/web 下执行 pnpm approve-builds --all(放行构建脚本,会自动重跑安装)
3. 再次执行 dsh plugin --profile web add dsh-better-sidebar@latest
3. 再次执行 dsh plugin --profile web add dsh-apes-sidebar@latest
4. 完成后提醒我硬刷新浏览器(Cmd/Ctrl+Shift+R)
遇到报错先查 https://github.com/omdsh-dev/DSH-better-sidebar README 的常见问题表。
```
@@ -93,7 +108,7 @@ dsh plugin --profile web add dsh-better-sidebar@latest # 重跑即成功
<summary><b>更新</b></summary>
```sh
dsh plugin --profile web add dsh-better-sidebar@latest
dsh plugin --profile web add dsh-apes-sidebar@latest
```
也可把 `~/.dsh/profiles/web/package.json` 里的版本号改高后 `pnpm install`。改完**硬刷新浏览器**(Cmd/Ctrl+Shift+R)即可(client 改动无需重启 DSH)。
@@ -108,10 +123,10 @@ dsh plugin --profile web add dsh-better-sidebar@latest
| 报 `Ignored build scripts` | pnpm 11 拦截构建脚本。在 profile 目录(`~/.dsh/profiles/web`)跑 `pnpm approve-builds --all`。 |
| 报 `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` 之前。 |
| 页面出现**两个侧边栏** | 双挂载。旧的手动挂载行:`~/.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-apes-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`,修复后两者同步恢复。 |
| 提示 `dsh: command not found` | 先安装 DSH;或直接用 `npx -y --package @deepseek-ai/dsh dsh plugin --profile web add dsh-better-sidebar@latest`。 |
| 提示 `dsh: command not found` | 先安装 DSH;或直接用 `npx -y --package @deepseek-ai/dsh dsh plugin --profile web add dsh-apes-sidebar@latest`。 |
</details>
@@ -123,11 +138,11 @@ dsh plugin --profile web add dsh-better-sidebar@latest
```text
1. git clone https://github.com/omdsh-dev/DSH-better-sidebar.git ~/Code/DSH-better-sidebar
cd ~/Code/DSH-better-sidebar && pnpm install && pnpm build
2. ~/.dsh/profiles/web/package.json 的 dependencies 写 "dsh-better-sidebar": "link:<克隆目录绝对路径>"
2. ~/.dsh/profiles/web/package.json 的 dependencies 写 "dsh-apes-sidebar": "link:<克隆目录绝对路径>"
3. ~/.dsh/profiles/web/cordis.patch.yml 追加挂载行(需要指定终端 shell 时,在行内加 `config.shell`;`config.shellArgs` 可带参启动,非空时替换默认的 `-l`。不填则自动解析 `$SHELL` / 登录 shell / powershell.exe):
- insert:
- id: better-sidebar
name: 'dsh-better-sidebar'
name: 'dsh-apes-sidebar'
config:
shell: /bin/zsh
shellArgs:
@@ -137,7 +152,7 @@ dsh plugin --profile web add dsh-better-sidebar@latest
5. 硬刷新浏览器(Cmd/Ctrl+Shift+R)即可看到效果(client 改动无需重启 DSH;host 半改动才需重启)
```
更新:`git pull && pnpm install && pnpm build` → 硬刷新浏览器即可(client 改动热加载生效,无需重启 DSH;host 半改动才需重启)。切回 npm 通道时,把依赖改回 `"dsh-better-sidebar": "^0.16.1"` 再 `pnpm install`。
更新:`git pull && pnpm install && pnpm build` → 硬刷新浏览器即可(client 改动热加载生效,无需重启 DSH;host 半改动才需重启)。切回 npm 通道时,把依赖改回 `"dsh-apes-sidebar": "^0.16.1"` 再 `pnpm install`。
</details>
@@ -151,7 +166,7 @@ git clone https://github.com/omdsh-dev/DSH-better-sidebar.git && cd DSH-better-s
pnpm install && pnpm build
node scripts/package-registry.mjs # 组装 registry/ 暂存(含清单 + 产物 + README,不入库)
dsh registry install ./registry # 安装(默认禁用)
dsh registry enable dsh-external/dsh-better-sidebar
dsh registry enable dsh-external/dsh-apes-sidebar
```
更新:`git pull && pnpm install && pnpm build` → `node scripts/package-registry.mjs` → `dsh registry uninstall/install/enable`。切换通道前先移除另一通道的挂载。
@@ -176,7 +191,7 @@ dsh registry enable dsh-external/dsh-better-sidebar
`ctx.betterSidebar` 服务向所有插件开放两个扩展点:**`registerTab`(注册侧边栏页面)** 与 **`registerFileViewer`(注册文件预览器)**。内置的 8 tab + 6 viewer 与第三方插件走同一套 API,能力完全对等。
```ts
import type {} from 'dsh-better-sidebar' // 触发 ctx.betterSidebar 类型合并
import type {} from 'dsh-apes-sidebar' // 触发 ctx.betterSidebar 类型合并
export const inject = ['betterSidebar']
export function apply(ctx: Context) {
ctx.effect(() => ctx.betterSidebar.registerTab({
@@ -190,7 +205,7 @@ export function apply(ctx: Context) {
}
```
GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sidebar) 下已有 **28+ 生态插件**(持续增长中):
GitHub topic [`dsh-apes-sidebar`](https://github.com/topics/dsh-apes-sidebar) 下已有 **28+ 生态插件**(持续增长中):
<div align="center">
<a href="https://github.com/user-attachments/assets/d4385b7e-aab4-425d-a5c4-2da5da81a34e"><img width="66%" alt="设置页「添加插件」弹窗:推荐插件目录 + 一键复制安装命令" src="https://github.com/user-attachments/assets/d4385b7e-aab4-425d-a5c4-2da5da81a34e" /></a><br />
@@ -213,7 +228,7 @@ GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sidebar
| [GULI-lab/DSH-element-source](https://github.com/GULI-lab/DSH-element-source) | <img alt="stars" src="https://img.shields.io/github/stars/GULI-lab/DSH-element-source?style=flat&color=4d6bfe" /> | 点击页面任意 UI 元素直达 Vue / React / Svelte / Angular 源码并送入会话 |
| [Lzh3070/dsh-file-review-tab](https://github.com/Lzh3070/dsh-file-review-tab) | <img alt="stars" src="https://img.shields.io/github/stars/Lzh3070/dsh-file-review-tab?style=flat&color=4d6bfe" /> | 文件改动审查页:行级红绿 diff + 撤销 + chat 行深链 |
| [yq04/dsh-git-remotes](https://github.com/yq04/dsh-git-remotes) | <img alt="stars" src="https://img.shields.io/github/stars/yq04/dsh-git-remotes?style=flat&color=4d6bfe" /> | Git 远程页:分支 / 上游 / ahead-behind,fetch 可 prune、ff-only pull、确认后 push |
| [ztyhehe/dsh-better-sidebar-svn](https://github.com/ztyhehe/dsh-better-sidebar-svn) | <img alt="stars" src="https://img.shields.io/github/stars/ztyhehe/dsh-better-sidebar-svn?style=flat&color=4d6bfe" /> | SVN 源码管理页:status / diff / log / commit / update / revert / 冲突解决,与内置 Git 面板对称 |
| [ztyhehe/dsh-apes-sidebar-svn](https://github.com/ztyhehe/dsh-apes-sidebar-svn) | <img alt="stars" src="https://img.shields.io/github/stars/ztyhehe/dsh-apes-sidebar-svn?style=flat&color=4d6bfe" /> | SVN 源码管理页:status / diff / log / commit / update / revert / 冲突解决,与内置 Git 面板对称 |
| [Melody-max114/dsh-excel-panel](https://github.com/Melody-max114/dsh-excel-panel) | <img alt="stars" src="https://img.shields.io/github/stars/Melody-max114/dsh-excel-panel?style=flat&color=4d6bfe" /> | Excel 编辑页:xlsx 预览 / 编辑、公式实时计算、合并单元格、保存回原文件 |
| [v587d/dsh-anysearch-refs](https://github.com/v587d/dsh-anysearch-refs) | <img alt="stars" src="https://img.shields.io/github/stars/v587d/dsh-anysearch-refs?style=flat&color=4d6bfe" /> | AnySearch 搜索结果引用卡片:搜索词、来源摘要、关键词高亮 |
| [mlosun/dsh-docs-panel](https://github.com/mlosun/dsh-docs-panel) | <img alt="stars" src="https://img.shields.io/github/stars/mlosun/dsh-docs-panel?style=flat&color=4d6bfe" /> | 全局文档面板:随身 Markdown 笔记,任何工作区随时可读 |
@@ -226,7 +241,7 @@ GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sidebar
| [yq04/dsh-turn-review](https://github.com/yq04/dsh-turn-review) | <img alt="stars" src="https://img.shields.io/github/stars/yq04/dsh-turn-review?style=flat&color=4d6bfe" /> | 本轮审查:逐回合审查 agent 改动 |
| [Ghz114514/dsh-refpics](https://github.com/Ghz114514/dsh-refpics) | <img alt="stars" src="https://img.shields.io/github/stars/Ghz114514/dsh-refpics?style=flat&color=4d6bfe" /> | Pinterest 风格参考图搜索:瀑布流、侧栏画板、下载与 Eagle 收藏 |
| [yzlin499/dsh-yzlin499-easy-plugins](https://github.com/yzlin499/dsh-yzlin499-easy-plugins) | <img alt="stars" src="https://img.shields.io/github/stars/yzlin499/dsh-yzlin499-easy-plugins?style=flat&color=4d6bfe" /> | 实用小工具集(毛坯房 DSH 友好) |
| [dong-victor/dsh-better-sidebar-starter](https://github.com/dong-victor/dsh-better-sidebar-starter) | <img alt="stars" src="https://img.shields.io/github/stars/dong-victor/dsh-better-sidebar-starter?style=flat&color=4d6bfe" /> | 运行配置页:IDEA 式 Run/Debug 配置(npm / springboot / python / custom)——一键启动、历史保存、WebSocket 实时日志(ANSI 彩色)、多实例并行、进程树跨平台杀死 |
| [dong-victor/dsh-apes-sidebar-starter](https://github.com/dong-victor/dsh-apes-sidebar-starter) | <img alt="stars" src="https://img.shields.io/github/stars/dong-victor/dsh-apes-sidebar-starter?style=flat&color=4d6bfe" /> | 运行配置页:IDEA 式 Run/Debug 配置(npm / springboot / python / custom)——一键启动、历史保存、WebSocket 实时日志(ANSI 彩色)、多实例并行、进程树跨平台杀死 |
| [baosfeng/my-dsh-plugins](https://github.com/baosfeng/my-dsh-plugins) | <img alt="stars" src="https://img.shields.io/github/stars/baosfeng/my-dsh-plugins?style=flat&color=4d6bfe" /> | 个人多插件合集(`dsh-file-activity`):侧边栏文件活动页——记录文件读取 / 新增 / 修改历史与统计,按文件夹平铺,点击用原生预览打开 |
| [Hoemr/dsh-better-overleaf](https://github.com/Hoemr/dsh-better-overleaf) | <img alt="stars" src="https://img.shields.io/github/stars/Hoemr/dsh-better-overleaf?style=flat&color=4d6bfe" /> | Overleaf 标签页:直连 CDP 浏览器登录(支持第三方 Chromium)、项目切换、工作区下 overleaf/ 目录本地 git 镜像与双向同步 |
@@ -241,7 +256,7 @@ GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sidebar
|---|---|---|
| [HuanLinOTO/dsh-plugin-better-sidebar-plugin-office](https://github.com/HuanLinOTO/dsh-plugin-better-sidebar-plugin-office) | <img alt="stars" src="https://img.shields.io/github/stars/HuanLinOTO/dsh-plugin-better-sidebar-plugin-office?style=flat&color=4d6bfe" /> | Office 三件套预览(.docx / .xlsx / .pptx),独立 bundle 瘦身主体(官方推荐目录收录) |
| [zemul/dsh-video-preview](https://github.com/zemul/dsh-video-preview) | <img alt="stars" src="https://img.shields.io/github/stars/zemul/dsh-video-preview?style=flat&color=4d6bfe" /> | 视频内联预览:.mp4 / .webm / .mov / .mkv / .avi,自带 /video 路由支持 HTTP Range 拖进度条 |
| [dong-victor/dsh-better-sidebar-jupyter](https://github.com/dong-victor/dsh-better-sidebar-jupyter) | <img alt="stars" src="https://img.shields.io/github/stars/dong-victor/dsh-better-sidebar-jupyter?style=flat&color=4d6bfe" /> | `.ipynb` 可运行 Notebook 视图:懒启动 Python kernel、流式输出、保存回写 |
| [dong-victor/dsh-apes-sidebar-jupyter](https://github.com/dong-victor/dsh-apes-sidebar-jupyter) | <img alt="stars" src="https://img.shields.io/github/stars/dong-victor/dsh-apes-sidebar-jupyter?style=flat&color=4d6bfe" /> | `.ipynb` 可运行 Notebook 视图:懒启动 Python kernel、流式输出、保存回写 |
</details>
@@ -252,14 +267,14 @@ GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sidebar
| 插件 | ⭐ | 简介 |
|---|---|---|
| [eg-bole/dsh-better-sidebar-icons](https://github.com/eg-bole/dsh-better-sidebar-icons) | <img alt="stars" src="https://img.shields.io/github/stars/eg-bole/dsh-better-sidebar-icons?style=flat&color=4d6bfe" /> | VSCode 风格文件 / 文件夹图标主题:文件树与编辑器 Tab 换上熟悉的开发环境图标(vscode-icons 移植,纯 DOM 覆盖零侵入,安装 / 卸载零残留) |
| [dong-victor/dsh-better-sidebar-terminal-plus](https://github.com/dong-victor/dsh-better-sidebar-terminal-plus) | <img alt="stars" src="https://img.shields.io/github/stars/dong-victor/dsh-better-sidebar-terminal-plus?style=flat&color=4d6bfe" /> | 终端增强:内嵌 Nerd Font 图标字体、修复 xterm 图标渲染、稳定终端 cwd |
| [eg-bole/dsh-apes-sidebar-icons](https://github.com/eg-bole/dsh-apes-sidebar-icons) | <img alt="stars" src="https://img.shields.io/github/stars/eg-bole/dsh-apes-sidebar-icons?style=flat&color=4d6bfe" /> | VSCode 风格文件 / 文件夹图标主题:文件树与编辑器 Tab 换上熟悉的开发环境图标(vscode-icons 移植,纯 DOM 覆盖零侵入,安装 / 卸载零残留) |
| [dong-victor/dsh-apes-sidebar-terminal-plus](https://github.com/dong-victor/dsh-apes-sidebar-terminal-plus) | <img alt="stars" src="https://img.shields.io/github/stars/dong-victor/dsh-apes-sidebar-terminal-plus?style=flat&color=4d6bfe" /> | 终端增强:内嵌 Nerd Font 图标字体、修复 xterm 图标渲染、稳定终端 cwd |
| [Max-Null/dsh-sidebar-preview-select](https://github.com/Max-Null/dsh-sidebar-preview-select) | <img alt="stars" src="https://img.shields.io/github/stars/Max-Null/dsh-sidebar-preview-select?style=flat&color=4d6bfe" /> | 预览划选增强:侧边栏预览里划选文本 → 浮动「发送到会话」 |
| [Hoemr/dsh-quicklook](https://github.com/Hoemr/dsh-quicklook) | <img alt="stars" src="https://img.shields.io/github/stars/Hoemr/dsh-quicklook?style=flat&color=4d6bfe" /> | QuickLook 式空格预览:活动文件标签页按 Space 全尺寸查看图片 / PDF / 文本,Space 或 Esc 关闭 |
</details>
> 📣 **上架你的插件**:给仓库打上 `dsh-better-sidebar` topic 即出现在 [topic 页](https://github.com/topics/dsh-better-sidebar);再向 [`src/client/plugins-tabs.ts`](./src/client/plugins-tabs.ts) / [`src/client/plugins-viewers.ts`](./src/client/plugins-viewers.ts) 提一条 `PluginEntry` PR,即可进入设置页内置推荐目录(数据完整性由 `tests/plugin-list.spec.ts` 守护)。
> 📣 **上架你的插件**:给仓库打上 `dsh-apes-sidebar` topic 即出现在 [topic 页](https://github.com/topics/dsh-apes-sidebar);再向 [`src/client/plugins-tabs.ts`](./src/client/plugins-tabs.ts) / [`src/client/plugins-viewers.ts`](./src/client/plugins-viewers.ts) 提一条 `PluginEntry` PR,即可进入设置页内置推荐目录(数据完整性由 `tests/plugin-list.spec.ts` 守护)。
## 🆕 最近更新
@@ -299,7 +314,7 @@ GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sidebar
### v0.19.0-alpha.1
> 🧪 **alpha 通道**(npm dist-tag `alpha`,安装 `dsh-better-sidebar@alpha`):本版仅支持 **DSH 0.1.5-alpha.2+**(peer 下限 `^0.1.5-alpha.2`,CI 钉 `@deepseek-ai/dsh@0.1.5-alpha.2`)。0.1.5-alpha.1 请继续用 **v0.19.0-alpha.0**;0.1.2-rc.1 稳定线用 **v0.18.1**(npm `latest`)。
> 🧪 **alpha 通道**(npm dist-tag `alpha`,安装 `dsh-apes-sidebar@alpha`):本版仅支持 **DSH 0.1.5-alpha.2+**(peer 下限 `^0.1.5-alpha.2`,CI 钉 `@deepseek-ai/dsh@0.1.5-alpha.2`)。0.1.5-alpha.1 请继续用 **v0.19.0-alpha.0**;0.1.2-rc.1 稳定线用 **v0.18.1**(npm `latest`)。
**✨ 新功能**
@@ -317,7 +332,7 @@ GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sidebar
### v0.19.0-alpha.0
> 🧪 **alpha 通道**(npm dist-tag `alpha`,安装 `dsh-better-sidebar@alpha`):本版仅支持 **DSH 0.1.5-alpha.1+**(peer 下限 `^0.1.5-alpha.1`,CI 钉 `@deepseek-ai/dsh@0.1.5-alpha.1`)。0.1.2-rc.1 稳定线请继续用 **v0.18.1**(npm `latest`)。
> 🧪 **alpha 通道**(npm dist-tag `alpha`,安装 `dsh-apes-sidebar@alpha`):本版仅支持 **DSH 0.1.5-alpha.1+**(peer 下限 `^0.1.5-alpha.1`,CI 钉 `@deepseek-ai/dsh@0.1.5-alpha.1`)。0.1.2-rc.1 稳定线请继续用 **v0.18.1**(npm `latest`)。
**✨ 新功能**
@@ -361,7 +376,7 @@ GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sidebar
### v0.18.0
> 📌 **正式版**(npm `latest`):本版仅支持 **DSH 0.1.2-rc.1+**(peer 下限 `^0.1.2-rc.1`);不再支持 0.1.0-rc.8 ~ 0.1.1-rc.2——DSH stable 用户请固定安装 `dsh-better-sidebar@0.17.1`,停留在 0.1.2-alpha.x 的宿主继续用 `dsh-better-sidebar@alpha`(v0.18.0-alpha.0)。
> 📌 **正式版**(npm `latest`):本版仅支持 **DSH 0.1.2-rc.1+**(peer 下限 `^0.1.2-rc.1`);不再支持 0.1.0-rc.8 ~ 0.1.1-rc.2——DSH stable 用户请固定安装 `dsh-apes-sidebar@0.17.1`,停留在 0.1.2-alpha.x 的宿主继续用 `dsh-apes-sidebar@alpha`(v0.18.0-alpha.0)。
**✨ 新功能**
@@ -386,7 +401,7 @@ GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sidebar
**🌐 生态收录**
- 新收录 10+ 插件:dsh-better-sidebar-icons(#441)、dsh-sidenote(#451,原 dsh-sidechat #470)、dsh-github-workbench(#410)、dsh-bilingual-reader(#379)、dsh-server-deck(#413)、dsh-md-export(#405)、dsh-code-nav(#404)、dsh-suhuang-scroll(#392)、dsh-better-overleaf(#370)等(均含 18+ 语言 i18n 补齐)
- 新收录 10+ 插件:dsh-apes-sidebar-icons(#441)、dsh-sidenote(#451,原 dsh-sidechat #470)、dsh-github-workbench(#410)、dsh-bilingual-reader(#379)、dsh-server-deck(#413)、dsh-md-export(#405)、dsh-code-nav(#404)、dsh-suhuang-scroll(#392)、dsh-better-overleaf(#370)等(均含 18+ 语言 i18n 补齐)
<details>
<summary><b>历史版本(v0.12.0 – v0.15.2)</b></summary>
@@ -399,13 +414,13 @@ GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sidebar
### v0.18.1-alpha.0
> 🧪 **alpha 通道**:本版仅支持 **DSH 0.1.2-alpha.x**(peer 下限 `^0.1.2-alpha.3`,npm dist-tag `alpha`,安装 `dsh-better-sidebar@alpha`)。该版本号未单独发布,内容已并入 **v0.18.0** 正式版。
> 🧪 **alpha 通道**:本版仅支持 **DSH 0.1.2-alpha.x**(peer 下限 `^0.1.2-alpha.3`,npm dist-tag `alpha`,安装 `dsh-apes-sidebar@alpha`)。该版本号未单独发布,内容已并入 **v0.18.0** 正式版。
- 🔗 **适配 DSH 0.1.2-alpha.3(npm 已发布,`alpha` dist-tag)**:CI 挂载门禁钉版、`dsh.plugin.json` engines 下限与 `@deepseek-ai/*` peer / devDependencies 基线升至 0.1.2-alpha.3(真机挂载冒烟 14/14 验证)。逐点核查了 alpha.2 → alpha.3 全部 117 个 commit:插件依赖的宿主契约(token 鉴权、斜杠 RPC、`MarkdownText` labels、`sidechat.events` 所依赖的事件流与持久化 API、`SettingsNamespaceInput`、`SUBAGENT_DESCRIPTOR_VERSION`(仍为 3)、`dsh-client-store`、profile 加载器、node-pty 钉版)均无变化,无需代码适配;alpha.3 的破坏性改动(`BeginSubmissionInput.mode` 必填、subagent 错误码改名 `attachment-invalid`、SQLite 持久化后端移除、投影 change feed 收紧为 identity-gated)经核实均不触及本插件。
### v0.18.0-alpha.0
> 🧪 **alpha 通道**:本版仅支持 **DSH 0.1.2-alpha.x**(peer 下限 `^0.1.2-alpha.2`,npm dist-tag `alpha`,安装 `dsh-better-sidebar@alpha`);不再支持 0.1.0-rc.8 ~ 0.1.1-rc.2——stable DSH 用户请用 v0.17.1(npm `latest`)。
> 🧪 **alpha 通道**:本版仅支持 **DSH 0.1.2-alpha.x**(peer 下限 `^0.1.2-alpha.2`,npm dist-tag `alpha`,安装 `dsh-apes-sidebar@alpha`);不再支持 0.1.0-rc.8 ~ 0.1.1-rc.2——stable DSH 用户请用 v0.17.1(npm `latest`)。
- 🔗 **适配 DSH 0.1.2-alpha.2(npm 已发布,`alpha` dist-tag)**:CI 挂载门禁钉版与 `@deepseek-ai/*` devDependencies 基线升至 0.1.2-alpha.2(真机挂载冒烟 14/14 验证)。适配点:`dsh-settings` 移除运行时导出 `settingsNamespace`(命名空间改为编译期校验,宿主侧直接传常量);`dsh-subagent` 描述符版本 2→3(由宿主包盖章,测试断言跟随 `SUBAGENT_DESCRIPTOR_VERSION` 常量);`SessionEvent.ignorable` 恢复与 Remote 网关 `RemoteError` 封装经核实对本插件无破坏。
- 🐛 **修复 DSH 0.1.2-alpha.1+ 上侧边对话转录空白**:转录轮询此前仍走 alpha.1 已移除的 `ctx.connection.api`(错误被静默吞掉,tab 永远渲染空转录),现改由插件自有 `sidechat.events` 路由供给(活线程读内存事件日志、冷线程读会话持久化,`afterSeq` 增量拉取,[sidechat-routes.ts](./src/sidechat-routes.ts))。
@@ -443,7 +458,7 @@ GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sidebar
**🐛 修复**
- 🛒 **DSH 市场受管安装兼容**([#338](https://github.com/omdsh-dev/DSH-better-sidebar/pull/338)):移除 `peerDependencies` 里的公开版 `cordis`(市场预览硬拒依赖字段出现 `cordis`,optional 无效)——npm 包满足 [dsh-community-market 安装规范](https://github.com/anywhere-labs/deepseek-harness-desktop/blob/master/dsh-community-market/docs/install-and-uninstall.zh.md),dshfind / 1024Store 目录里的条目重新获得 `repository_backlink` 验证目标,可直接从 Desktop 市场受管安装
- 🔤 **类型基底迁移到 `@deepseek-ai/cordis`**([#338](https://github.com/omdsh-dev/DSH-better-sidebar/pull/338)):`Context` = 真实 vendored cordis Context 与结构化服务面的**交集**,`ctx.betterSidebar` 类型合并改挂 `@deepseek-ai/cordis`,公开版 cordis 不再被依赖。**消费者迁移**:`import type { Context } from 'cordis'` 改为 `import type { Context } from '@deepseek-ai/cordis'`(`import type {} from 'dsh-better-sidebar'` 的类型合并方式不变);未使用该导入的插件无影响
- 🔤 **类型基底迁移到 `@deepseek-ai/cordis`**([#338](https://github.com/omdsh-dev/DSH-better-sidebar/pull/338)):`Context` = 真实 vendored cordis Context 与结构化服务面的**交集**,`ctx.betterSidebar` 类型合并改挂 `@deepseek-ai/cordis`,公开版 cordis 不再被依赖。**消费者迁移**:`import type { Context } from 'cordis'` 改为 `import type { Context } from '@deepseek-ai/cordis'`(`import type {} from 'dsh-apes-sidebar'` 的类型合并方式不变);未使用该导入的插件无影响
- 🧩 **插件树内 `ctx.betterSidebar` 读取全面修复**([#357](https://github.com/omdsh-dev/DSH-better-sidebar/pull/357),修复 [#356](https://github.com/omdsh-dev/DSH-better-sidebar/issues/356)):npm 安装的 DSH 0.1.1-rc.x(web bundle)下侧边栏页面每次加载即崩(`cannot get property "betterSidebar" without inject`)——26 处内部直读 `ctx.betterSidebar` 改走 `ctx.get('betterSidebar')`(root reflect store 解析,不受 fiber 链影响);外部消费者 `inject: ['betterSidebar'] + ctx.betterSidebar` 契约不变
- 🔐 **文件 API 会话工作区边界**([#345](https://github.com/omdsh-dev/DSH-better-sidebar/pull/345),修复 [#328](https://github.com/omdsh-dev/DSH-better-sidebar/issues/328)):`fs.tree / fs.read / fs.write` 的 workspace 越界访问修复;媒体、HTML 预览与上传统一 real-path 符号链接校验;新增绝对路径 / 符号链接 / 上传 / 嵌套 Git 会话回归测试
- 🪟 **面板宿主层级与视口裁剪**([#330](https://github.com/omdsh-dev/DSH-better-sidebar/pull/330) [#278](https://github.com/omdsh-dev/DSH-better-sidebar/pull/278),修复 [#277](https://github.com/omdsh-dev/DSH-better-sidebar/issues/277)):面板宿主层 z-index 40→25——低于 DSH cordis 动态插件面板 30,工作台不再遮挡 cordis 清单 / 审批面(AppFrame 20 之上、100+ 浮层之下);宿主 `overflow: hidden` 裁剪视口边缘,收起的面板不再把文档撑出双向滚动(实测 `scrollWidth` 2289→1672 / `scrollHeight` 1280→1032,任意皮肤)
@@ -605,9 +620,9 @@ GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sidebar
### ➕ 添加插件(推荐插件目录)
设置页「侧边卡片」两个网格末尾的**虚线卡片**分别打开 Tab / 预览插件弹窗:声明扩展点、「**在 GitHub 上浏览更多插件**」按钮([GitHub topic `dsh-better-sidebar`](https://github.com/topics/dsh-better-sidebar))、推荐插件目录(名字 / 仓库 / 简介 / 安装脚本),每个条目「**跳转**」直达仓库、「**复制**」把安装命令写入剪贴板。
设置页「侧边卡片」两个网格末尾的**虚线卡片**分别打开 Tab / 预览插件弹窗:声明扩展点、「**在 GitHub 上浏览更多插件**」按钮([GitHub topic `dsh-apes-sidebar`](https://github.com/topics/dsh-apes-sidebar))、推荐插件目录(名字 / 仓库 / 简介 / 安装脚本),每个条目「**跳转**」直达仓库、「**复制**」把安装命令写入剪贴板。
**收录新插件**:向 [`src/client/plugins-tabs.ts`](./src/client/plugins-tabs.ts)(Tab 注册)或 [`src/client/plugins-viewers.ts`](./src/client/plugins-viewers.ts)(文件预览注册)追加一条 `PluginEntry`,并把仓库打上 `dsh-better-sidebar` topic;数据完整性由 `tests/plugin-list.spec.ts` 守护。
**收录新插件**:向 [`src/client/plugins-tabs.ts`](./src/client/plugins-tabs.ts)(Tab 注册)或 [`src/client/plugins-viewers.ts`](./src/client/plugins-viewers.ts)(文件预览注册)追加一条 `PluginEntry`,并把仓库打上 `dsh-apes-sidebar` topic;数据完整性由 `tests/plugin-list.spec.ts` 守护。
## 🛠️ 开发与构建
@@ -654,7 +669,7 @@ Windows / Linux / macOS 三平台适配(macOS 日常验证;其余经单元
## 🤝 参与贡献
- **代码改动走 PR**:`feat/*` / `fix/*` 分支开发 → `gh pr create`;纯文档改动可直接推 main
- **收录生态插件**:给仓库打 `dsh-better-sidebar` topic + 向 [`src/client/plugins-tabs.ts`](./src/client/plugins-tabs.ts) / [`plugins-viewers.ts`](./src/client/plugins-viewers.ts) 提 PR
- **收录生态插件**:给仓库打 `dsh-apes-sidebar` topic + 向 [`src/client/plugins-tabs.ts`](./src/client/plugins-tabs.ts) / [`plugins-viewers.ts`](./src/client/plugins-viewers.ts) 提 PR
- **提交前自检**:`pnpm typecheck && pnpm build && pnpm test`(或 `make check` 一键聚合;CI 另有 npm 打包 → 真实挂载 → 无头渲染门禁 `pnpm test:mount`,及聚合双挂载回归 `pnpm test:mount:aggregate`)
- 仓库工作规范见 [`AGENTS.md`](./AGENTS.md)(含仓库硬约束与 CI 说明)
@@ -685,5 +700,5 @@ Windows / Linux / macOS 三平台适配(macOS 日常验证;其余经单元
---
<div align="center">
<sub>MIT License · Built for the <a href="https://github.com/deepseek-ai/deepseek-harness">DeepSeek Harness</a> ecosystem · 在 <a href="https://github.com/topics/dsh-better-sidebar">topic dsh-better-sidebar</a> 发现更多生态插件</sub>
<sub>MIT License · Built for the <a href="https://github.com/deepseek-ai/deepseek-harness">DeepSeek Harness</a> ecosystem · 在 <a href="https://github.com/topics/dsh-apes-sidebar">topic dsh-apes-sidebar</a> 发现更多生态插件</sub>
</div>
+36 -36
View File
@@ -1,4 +1,4 @@
# dsh-better-sidebar
# dsh-apes-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).
@@ -6,14 +6,14 @@
<!-- Hero -->
<div align="center">
<b style="font-size: 1.15em;">A service-oriented sidebar framework, and a complete workbench out of the box</b><br /><br />
<a href="https://www.npmjs.com/package/dsh-better-sidebar"><img alt="npm version" src="https://img.shields.io/npm/v/dsh-better-sidebar" /></a>
<a href="https://www.npmjs.com/package/dsh-better-sidebar"><img alt="npm downloads" src="https://img.shields.io/npm/dm/dsh-better-sidebar" /></a>
<a href="https://www.npmjs.com/package/dsh-apes-sidebar"><img alt="npm version" src="https://img.shields.io/npm/v/dsh-apes-sidebar" /></a>
<a href="https://www.npmjs.com/package/dsh-apes-sidebar"><img alt="npm downloads" src="https://img.shields.io/npm/dm/dsh-apes-sidebar" /></a>
<a href="https://github.com/omdsh-dev/DSH-better-sidebar/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/omdsh-dev/DSH-better-sidebar/actions/workflows/ci.yml/badge.svg" /></a>
<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://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 />
<a href="https://github.com/topics/dsh-apes-sidebar"><img alt="Plugin ecosystem: GitHub topic dsh-apes-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 &amp; 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 />
<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>.
@@ -25,7 +25,7 @@
<div align="center">
<video src="https://github.com/user-attachments/assets/23187822-047e-45cc-b480-fe997bd55b86" muted autoplay loop playsinline controls width="100%"></video>
<img alt="dsh-better-sidebar workbench" src="https://github.com/user-attachments/assets/dfdb875e-a1a8-4d4b-8340-353736b1708f" />
<img alt="dsh-apes-sidebar workbench" src="https://github.com/user-attachments/assets/dfdb875e-a1a8-4d4b-8340-353736b1708f" />
</div>
## 📑 Contents
@@ -66,12 +66,12 @@
**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>
> 📌 **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.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-apes-sidebar@0.19.0-alpha.1`; the 0.1.2-rc.1 stable line keeps using `dsh-apes-sidebar@0.18.x`; DSH ≤ 0.1.1-rc.2 should use `dsh-apes-sidebar@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)
dsh plugin --profile web add dsh-apes-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-apes-sidebar@latest # re-run succeeds
```
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).
@@ -79,10 +79,10 @@ Then **hard-refresh the browser** (Cmd/Ctrl+Shift+R) to see the sidebar (DSH hot
**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)
Install the dsh-apes-sidebar plugin (a sidebar workbench for DSH):
1. Run: dsh plugin --profile web add dsh-apes-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
3. Run the add command again: dsh plugin --profile web add dsh-apes-sidebar@latest
4. 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
```
@@ -93,7 +93,7 @@ If anything fails, check the troubleshooting table in the README at https://gith
<summary><b>Updating</b></summary>
```sh
dsh plugin --profile web add dsh-better-sidebar@latest
dsh plugin --profile web add dsh-apes-sidebar@latest
```
or bump the version in `~/.dsh/profiles/web/package.json` (e.g. `"^0.16.1"`) and run `pnpm install`. Then hard-refresh the browser (Cmd/Ctrl+Shift+R) — client changes do not need a DSH restart.
@@ -108,10 +108,10 @@ or bump the version in `~/.dsh/profiles/web/package.json` (e.g. `"^0.16.1"`) and
| `Ignored build scripts` | pnpm 11 blocked build scripts. Run `pnpm approve-builds --all` in the profile directory (`~/.dsh/profiles/web`). |
| `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`. |
| 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-apes-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. |
| `dsh: command not found` | Install DSH first, or run `npx -y --package @deepseek-ai/dsh dsh plugin --profile web add dsh-better-sidebar@latest`. |
| `dsh: command not found` | Install DSH first, or run `npx -y --package @deepseek-ai/dsh dsh plugin --profile web add dsh-apes-sidebar@latest`. |
</details>
@@ -123,11 +123,11 @@ To debug local changes or track the dev branch, point the dependency at a local
```text
1. git clone https://github.com/omdsh-dev/DSH-better-sidebar.git ~/Code/DSH-better-sidebar
cd ~/Code/DSH-better-sidebar && pnpm install && pnpm build
2. In ~/.dsh/profiles/web/package.json dependencies write "dsh-better-sidebar": "link:<absolute path of the clone>"
2. In ~/.dsh/profiles/web/package.json dependencies write "dsh-apes-sidebar": "link:<absolute path of the clone>"
3. Append this mount line to ~/.dsh/profiles/web/cordis.patch.yml (to pick the terminal shell, add `config.shell`; `config.shellArgs` starts it with explicit args — when non-empty they replace the default `-l`. When omitted the host resolves `$SHELL` / the login shell / powershell.exe):
- insert:
- id: better-sidebar
name: 'dsh-better-sidebar'
name: 'dsh-apes-sidebar'
config:
shell: /bin/zsh
shellArgs:
@@ -137,7 +137,7 @@ To debug local changes or track the dev branch, point the dependency at a local
5. Restart DSH and hard-refresh
```
Update: `git pull && pnpm install && pnpm build` → just hard-refresh the browser (client changes hot-reload; only host-half changes need a DSH restart). To switch back to the npm channel, restore `"dsh-better-sidebar": "^0.16.1"` and re-run `pnpm install`.
Update: `git pull && pnpm install && pnpm build` → just hard-refresh the browser (client changes hot-reload; only host-half changes need a DSH restart). To switch back to the npm channel, restore `"dsh-apes-sidebar": "^0.16.1"` and re-run `pnpm install`.
</details>
@@ -151,7 +151,7 @@ git clone https://github.com/omdsh-dev/DSH-better-sidebar.git && cd DSH-better-s
pnpm install && pnpm build
node scripts/package-registry.mjs # assemble the registry/ staging (manifest + artifacts + README, not committed)
dsh registry install ./registry # install (disabled by default)
dsh registry enable dsh-external/dsh-better-sidebar
dsh registry enable dsh-external/dsh-apes-sidebar
```
Update: `git pull && pnpm install && pnpm build` → `node scripts/package-registry.mjs` → `dsh registry uninstall/install/enable`. Remove the other channel's mount before switching.
@@ -176,7 +176,7 @@ Update: `git pull && pnpm install && pnpm build` → `node scripts/package-regis
The `ctx.betterSidebar` service opens two extension points to every plugin: **`registerTab` (sidebar pages)** and **`registerFileViewer` (file previewers)**. The 8 built-in tabs + 6 viewers register through the exact same API — fully equal capabilities.
```ts
import type {} from 'dsh-better-sidebar' // triggers the ctx.betterSidebar type merge
import type {} from 'dsh-apes-sidebar' // triggers the ctx.betterSidebar type merge
export const inject = ['betterSidebar']
export function apply(ctx: Context) {
ctx.effect(() => ctx.betterSidebar.registerTab({
@@ -190,7 +190,7 @@ export function apply(ctx: Context) {
}
```
The GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sidebar) already hosts **28+ ecosystem plugins** (and growing):
The GitHub topic [`dsh-apes-sidebar`](https://github.com/topics/dsh-apes-sidebar) already hosts **28+ ecosystem plugins** (and growing):
<div align="center">
<a href="https://github.com/user-attachments/assets/d4385b7e-aab4-425d-a5c4-2da5da81a34e"><img width="66%" alt="The built-in Add Plugins modal: recommended catalog + one-click install command" src="https://github.com/user-attachments/assets/d4385b7e-aab4-425d-a5c4-2da5da81a34e" /></a><br />
@@ -213,7 +213,7 @@ The GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sid
| [GULI-lab/DSH-element-source](https://github.com/GULI-lab/DSH-element-source) | <img alt="stars" src="https://img.shields.io/github/stars/GULI-lab/DSH-element-source?style=flat&color=4d6bfe" /> | Click any UI element on your dev page to jump to its Vue / React / Svelte / Angular source, straight into the chat |
| [Lzh3070/dsh-file-review-tab](https://github.com/Lzh3070/dsh-file-review-tab) | <img alt="stars" src="https://img.shields.io/github/stars/Lzh3070/dsh-file-review-tab?style=flat&color=4d6bfe" /> | File-change review tab: line-level red/green diffs + undo + chat-line deep links |
| [yq04/dsh-git-remotes](https://github.com/yq04/dsh-git-remotes) | <img alt="stars" src="https://img.shields.io/github/stars/yq04/dsh-git-remotes?style=flat&color=4d6bfe" /> | Git remotes tab: branches / upstream / ahead-behind, fetch with prune, ff-only pull, confirm-before-push |
| [ztyhehe/dsh-better-sidebar-svn](https://github.com/ztyhehe/dsh-better-sidebar-svn) | <img alt="stars" src="https://img.shields.io/github/stars/ztyhehe/dsh-better-sidebar-svn?style=flat&color=4d6bfe" /> | SVN source-control tab: status / diff / log / commit / update / revert / conflict resolution — symmetric to the built-in Git panel |
| [ztyhehe/dsh-apes-sidebar-svn](https://github.com/ztyhehe/dsh-apes-sidebar-svn) | <img alt="stars" src="https://img.shields.io/github/stars/ztyhehe/dsh-apes-sidebar-svn?style=flat&color=4d6bfe" /> | SVN source-control tab: status / diff / log / commit / update / revert / conflict resolution — symmetric to the built-in Git panel |
| [Melody-max114/dsh-excel-panel](https://github.com/Melody-max114/dsh-excel-panel) | <img alt="stars" src="https://img.shields.io/github/stars/Melody-max114/dsh-excel-panel?style=flat&color=4d6bfe" /> | Excel editing: xlsx preview/edit, live formula evaluation, merged cells, save back to the original file |
| [v587d/dsh-anysearch-refs](https://github.com/v587d/dsh-anysearch-refs) | <img alt="stars" src="https://img.shields.io/github/stars/v587d/dsh-anysearch-refs?style=flat&color=4d6bfe" /> | AnySearch results as sidebar cards: query, source snippets, highlighted keywords |
| [mlosun/dsh-docs-panel](https://github.com/mlosun/dsh-docs-panel) | <img alt="stars" src="https://img.shields.io/github/stars/mlosun/dsh-docs-panel?style=flat&color=4d6bfe" /> | Global docs panel: portable Markdown notes, readable from any workspace |
@@ -226,7 +226,7 @@ The GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sid
| [yq04/dsh-turn-review](https://github.com/yq04/dsh-turn-review) | <img alt="stars" src="https://img.shields.io/github/stars/yq04/dsh-turn-review?style=flat&color=4d6bfe" /> | Turn review: review agent changes turn by turn |
| [Ghz114514/dsh-refpics](https://github.com/Ghz114514/dsh-refpics) | <img alt="stars" src="https://img.shields.io/github/stars/Ghz114514/dsh-refpics?style=flat&color=4d6bfe" /> | Pinterest-style reference-image search: masonry wall, sidebar board, downloads, save-to-Eagle |
| [yzlin499/dsh-yzlin499-easy-plugins](https://github.com/yzlin499/dsh-yzlin499-easy-plugins) | <img alt="stars" src="https://img.shields.io/github/stars/yzlin499/dsh-yzlin499-easy-plugins?style=flat&color=4d6bfe" /> | A handy utility bundle for a bare-bones DSH |
| [dong-victor/dsh-better-sidebar-starter](https://github.com/dong-victor/dsh-better-sidebar-starter) | <img alt="stars" src="https://img.shields.io/github/stars/dong-victor/dsh-better-sidebar-starter?style=flat&color=4d6bfe" /> | Run-configurations tab: IDEA-style Run/Debug configs (npm / springboot / python / custom) — one-click launch, history, WebSocket live logs (ANSI colors), parallel instances, cross-platform process-tree kill |
| [dong-victor/dsh-apes-sidebar-starter](https://github.com/dong-victor/dsh-apes-sidebar-starter) | <img alt="stars" src="https://img.shields.io/github/stars/dong-victor/dsh-apes-sidebar-starter?style=flat&color=4d6bfe" /> | Run-configurations tab: IDEA-style Run/Debug configs (npm / springboot / python / custom) — one-click launch, history, WebSocket live logs (ANSI colors), parallel instances, cross-platform process-tree kill |
| [baosfeng/my-dsh-plugins](https://github.com/baosfeng/my-dsh-plugins) | <img alt="stars" src="https://img.shields.io/github/stars/baosfeng/my-dsh-plugins?style=flat&color=4d6bfe" /> | Personal multi-plugin collection (`dsh-file-activity`): a sidebar file-activity tab recording read / added / modified history and stats, flat-browsed by folder, opened with the native preview |
| [Hoemr/dsh-better-overleaf](https://github.com/Hoemr/dsh-better-overleaf) | <img alt="stars" src="https://img.shields.io/github/stars/Hoemr/dsh-better-overleaf?style=flat&color=4d6bfe" /> | Overleaf tab: direct-CDP browser login (third-party Chromium supported), project switching, local git mirrors under the workspace with two-way sync |
@@ -241,7 +241,7 @@ The GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sid
|---|---|---|
| [HuanLinOTO/dsh-plugin-better-sidebar-plugin-office](https://github.com/HuanLinOTO/dsh-plugin-better-sidebar-plugin-office) | <img alt="stars" src="https://img.shields.io/github/stars/HuanLinOTO/dsh-plugin-better-sidebar-plugin-office?style=flat&color=4d6bfe" /> | Office-suite preview (.docx / .xlsx / .pptx) as a separate bundle to slim the core (in the official recommended catalog) |
| [zemul/dsh-video-preview](https://github.com/zemul/dsh-video-preview) | <img alt="stars" src="https://img.shields.io/github/stars/zemul/dsh-video-preview?style=flat&color=4d6bfe" /> | Inline video preview: .mp4 / .webm / .mov / .mkv / .avi with a /video host route supporting HTTP Range scrubbing |
| [dong-victor/dsh-better-sidebar-jupyter](https://github.com/dong-victor/dsh-better-sidebar-jupyter) | <img alt="stars" src="https://img.shields.io/github/stars/dong-victor/dsh-better-sidebar-jupyter?style=flat&color=4d6bfe" /> | Runnable `.ipynb` notebook view: lazy-start Python kernel, streaming outputs, save-back |
| [dong-victor/dsh-apes-sidebar-jupyter](https://github.com/dong-victor/dsh-apes-sidebar-jupyter) | <img alt="stars" src="https://img.shields.io/github/stars/dong-victor/dsh-apes-sidebar-jupyter?style=flat&color=4d6bfe" /> | Runnable `.ipynb` notebook view: lazy-start Python kernel, streaming outputs, save-back |
</details>
@@ -252,13 +252,13 @@ The GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sid
| Plugin | ⭐ | Description |
|---|---|---|
| [dong-victor/dsh-better-sidebar-terminal-plus](https://github.com/dong-victor/dsh-better-sidebar-terminal-plus) | <img alt="stars" src="https://img.shields.io/github/stars/dong-victor/dsh-better-sidebar-terminal-plus?style=flat&color=4d6bfe" /> | Terminal enhancement: bundled Nerd Font icons, xterm glyph fixes, stable terminal cwd |
| [dong-victor/dsh-apes-sidebar-terminal-plus](https://github.com/dong-victor/dsh-apes-sidebar-terminal-plus) | <img alt="stars" src="https://img.shields.io/github/stars/dong-victor/dsh-apes-sidebar-terminal-plus?style=flat&color=4d6bfe" /> | Terminal enhancement: bundled Nerd Font icons, xterm glyph fixes, stable terminal cwd |
| [Max-Null/dsh-sidebar-preview-select](https://github.com/Max-Null/dsh-sidebar-preview-select) | <img alt="stars" src="https://img.shields.io/github/stars/Max-Null/dsh-sidebar-preview-select?style=flat&color=4d6bfe" /> | Preview selection boost: select text in any sidebar preview → floating "send to session" |
| [Hoemr/dsh-quicklook](https://github.com/Hoemr/dsh-quicklook) | <img alt="stars" src="https://img.shields.io/github/stars/Hoemr/dsh-quicklook?style=flat&color=4d6bfe" /> | QuickLook-style Space preview: press Space on the active file tab for a full-size image / PDF / text overlay; Space or Esc closes |
</details>
> 📣 **List your plugin**: tag your repo with the `dsh-better-sidebar` topic to appear on the [topic page](https://github.com/topics/dsh-better-sidebar); then PR one `PluginEntry` into [`src/client/plugins-tabs.ts`](./src/client/plugins-tabs.ts) / [`src/client/plugins-viewers.ts`](./src/client/plugins-viewers.ts) to join the built-in recommended catalog (data integrity is guarded by `tests/plugin-list.spec.ts`).
> 📣 **List your plugin**: tag your repo with the `dsh-apes-sidebar` topic to appear on the [topic page](https://github.com/topics/dsh-apes-sidebar); then PR one `PluginEntry` into [`src/client/plugins-tabs.ts`](./src/client/plugins-tabs.ts) / [`src/client/plugins-viewers.ts`](./src/client/plugins-viewers.ts) to join the built-in recommended catalog (data integrity is guarded by `tests/plugin-list.spec.ts`).
## 🆕 Recent Updates
@@ -303,7 +303,7 @@ The GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sid
### v0.19.0-alpha.1
> 🧪 **alpha channel** (npm dist-tag `alpha`, install `dsh-better-sidebar@alpha`): supports **DSH 0.1.5-alpha.2+** only (peer floor `^0.1.5-alpha.2`, CI pins `@deepseek-ai/dsh@0.1.5-alpha.2`). Stay on **v0.19.0-alpha.0** for 0.1.5-alpha.1; the 0.1.2-rc.1 stable line keeps using **v0.18.1** (npm `latest`).
> 🧪 **alpha channel** (npm dist-tag `alpha`, install `dsh-apes-sidebar@alpha`): supports **DSH 0.1.5-alpha.2+** only (peer floor `^0.1.5-alpha.2`, CI pins `@deepseek-ai/dsh@0.1.5-alpha.2`). Stay on **v0.19.0-alpha.0** for 0.1.5-alpha.1; the 0.1.2-rc.1 stable line keeps using **v0.18.1** (npm `latest`).
**✨ New**
@@ -321,7 +321,7 @@ The GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sid
### v0.19.0-alpha.0
> 🧪 **alpha channel** (npm dist-tag `alpha`, install `dsh-better-sidebar@alpha`): supports **DSH 0.1.5-alpha.1+** only (peer floor `^0.1.5-alpha.1`, CI pins `@deepseek-ai/dsh@0.1.5-alpha.1`). The 0.1.2-rc.1 stable line keeps using **v0.18.1** (npm `latest`).
> 🧪 **alpha channel** (npm dist-tag `alpha`, install `dsh-apes-sidebar@alpha`): supports **DSH 0.1.5-alpha.1+** only (peer floor `^0.1.5-alpha.1`, CI pins `@deepseek-ai/dsh@0.1.5-alpha.1`). The 0.1.2-rc.1 stable line keeps using **v0.18.1** (npm `latest`).
**✨ New**
@@ -360,7 +360,7 @@ The GitHub topic [`dsh-better-sidebar`](https://github.com/topics/dsh-better-sid
### v0.18.0
> 📌 **Stable release** (npm `latest`): this release targets **DSH 0.1.2-rc.1+ only** (peer floor `^0.1.2-rc.1`); 0.1.0-rc.8 ~ 0.1.1-rc.2 are not supported — stable-DSH users should stay pinned to `dsh-better-sidebar@0.17.1`, hosts on 0.1.2-alpha.x keep `dsh-better-sidebar@alpha` (v0.18.0-alpha.0).
> 📌 **Stable release** (npm `latest`): this release targets **DSH 0.1.2-rc.1+ only** (peer floor `^0.1.2-rc.1`); 0.1.0-rc.8 ~ 0.1.1-rc.2 are not supported — stable-DSH users should stay pinned to `dsh-apes-sidebar@0.17.1`, hosts on 0.1.2-alpha.x keep `dsh-apes-sidebar@alpha` (v0.18.0-alpha.0).
All changes since v0.17.1 (the two intermediate version numbers v0.18.1-alpha.0 / v0.19.0-alpha.0 were never published; their content is folded into this release):
@@ -391,7 +391,7 @@ All changes since v0.17.1 (the two intermediate version numbers v0.18.1-alpha.0
**🌐 Ecosystem**
- 10+ new curated plugins: dsh-better-sidebar-icons (#441), dsh-sidenote (#451, formerly dsh-sidechat #470), dsh-github-workbench (#410), dsh-bilingual-reader (#379), dsh-server-deck (#413), dsh-md-export (#405), dsh-code-nav (#404), dsh-suhuang-scroll (#392), dsh-better-overleaf (#370), and more (each with 18+ language i18n coverage)
- 10+ new curated plugins: dsh-apes-sidebar-icons (#441), dsh-sidenote (#451, formerly dsh-sidechat #470), dsh-github-workbench (#410), dsh-bilingual-reader (#379), dsh-server-deck (#413), dsh-md-export (#405), dsh-code-nav (#404), dsh-suhuang-scroll (#392), dsh-better-overleaf (#370), and more (each with 18+ language i18n coverage)
### v0.19.0-alpha.0 (0.1.2-alpha.5 adaptation, unpublished, folded into v0.18.0)
@@ -401,13 +401,13 @@ All changes since v0.17.1 (the two intermediate version numbers v0.18.1-alpha.0
### v0.18.1-alpha.0
> 🧪 **Alpha track**: this release targets **DSH 0.1.2-alpha.x only** (peer floor `^0.1.2-alpha.3`, npm dist-tag `alpha`, install `dsh-better-sidebar@alpha`). This version number was never published separately; its content is folded into the **v0.18.0** stable release.
> 🧪 **Alpha track**: this release targets **DSH 0.1.2-alpha.x only** (peer floor `^0.1.2-alpha.3`, npm dist-tag `alpha`, install `dsh-apes-sidebar@alpha`). This version number was never published separately; its content is folded into the **v0.18.0** stable release.
- 🔗 **Adapted to DSH 0.1.2-alpha.3 (published to npm, `alpha` dist-tag)**: the CI mount gate's pin, the `dsh.plugin.json` engines floor, and the `@deepseek-ai/*` peer / devDependencies baseline moved up to 0.1.2-alpha.3 (verified by a real-host mount smoke 14/14). All 117 commits between alpha.2 and alpha.3 were audited point by point: every host contract this plugin relies on (token auth, slash RPC, `MarkdownText` labels, the event-stream and persistence APIs behind `sidechat.events`, `SettingsNamespaceInput`, `SUBAGENT_DESCRIPTOR_VERSION` (still 3), `dsh-client-store`, the profile loader, the node-pty pin) is unchanged, so no code adaptation was needed; alpha.3's breaking changes (the required `BeginSubmissionInput.mode`, the renamed `attachment-invalid` subagent error, the removed SQLite persistence backend, the identity-gated projection change feed) were all verified not to touch this plugin.
### v0.18.0-alpha.0
> 🧪 **Alpha track**: this release targets **DSH 0.1.2-alpha.x only** (peer floor `^0.1.2-alpha.2`, npm dist-tag `alpha`, install `dsh-better-sidebar@alpha`); 0.1.0-rc.8 ~ 0.1.1-rc.2 are no longer supported — stable-DSH users should stay on v0.17.1 (npm `latest`).
> 🧪 **Alpha track**: this release targets **DSH 0.1.2-alpha.x only** (peer floor `^0.1.2-alpha.2`, npm dist-tag `alpha`, install `dsh-apes-sidebar@alpha`); 0.1.0-rc.8 ~ 0.1.1-rc.2 are no longer supported — stable-DSH users should stay on v0.17.1 (npm `latest`).
- 🔗 **Adapted to DSH 0.1.2-alpha.2 (published to npm, `alpha` dist-tag)**: the CI mount gate's pin and the `@deepseek-ai/*` devDependencies baseline moved up to it (verified by a real-host mount smoke 14/14). Adaptation points: `dsh-settings` dropped the runtime `settingsNamespace` export (namespaces are now validated at compile time — the host passes the constant directly); the `dsh-subagent` descriptor version went 2→3 (stamped by the host package; the test assertion follows the `SUBAGENT_DESCRIPTOR_VERSION` constant); the restored `SessionEvent.ignorable` and the Remote gateway's unified `RemoteError` wrapping were verified to not affect this plugin.
- 🐛 **Fixed blank Side Chat transcripts on DSH 0.1.2-alpha.1+**: transcript polling still called the `ctx.connection.api` face removed in alpha.1 (the error was silently swallowed, so the tab rendered an empty transcript forever). Transcripts now come from the plugin's own `sidechat.events` route (live threads read the in-memory event log, cold threads read session persistence, with `afterSeq` delta pulls, [sidechat-routes.ts](./src/sidechat-routes.ts)).
@@ -445,7 +445,7 @@ All changes since v0.15.2:
**🐛 Fixes**
- 🛒 **DSH marketplace managed-install compatibility** ([#338](https://github.com/omdsh-dev/DSH-better-sidebar/pull/338)): the public `cordis` entry was removed from `peerDependencies` (the market preview hard-rejects `cordis` in any dependency field — optional does not help), so the npm package satisfies the [dsh-community-market install rules](https://github.com/anywhere-labs/deepseek-harness-desktop/blob/master/dsh-community-market/docs/install-and-uninstall.zh.md) — catalog sources (dshfind / 1024Store) can re-issue the `repository_backlink` verified target and the plugin becomes installable through the Desktop market
- 🔤 **Type base migrated to `@deepseek-ai/cordis`** ([#338](https://github.com/omdsh-dev/DSH-better-sidebar/pull/338)): the declaration surface (`src/context-types.ts`) no longer depends on or restates the public `cordis` — `Context` is now an **intersection** of the real vendored cordis Context with the structural service faces, and the `ctx.betterSidebar` type merge lives on `@deepseek-ai/cordis`. **Consumer migration**: change `import type { Context } from 'cordis'` to `import type { Context } from '@deepseek-ai/cordis'` (the `import type {} from 'dsh-better-sidebar'` merge path is unchanged); plugins that never used that import are unaffected
- 🔤 **Type base migrated to `@deepseek-ai/cordis`** ([#338](https://github.com/omdsh-dev/DSH-better-sidebar/pull/338)): the declaration surface (`src/context-types.ts`) no longer depends on or restates the public `cordis` — `Context` is now an **intersection** of the real vendored cordis Context with the structural service faces, and the `ctx.betterSidebar` type merge lives on `@deepseek-ai/cordis`. **Consumer migration**: change `import type { Context } from 'cordis'` to `import type { Context } from '@deepseek-ai/cordis'` (the `import type {} from 'dsh-apes-sidebar'` merge path is unchanged); plugins that never used that import are unaffected
- 🧩 **All internal `ctx.betterSidebar` reads fixed** ([#357](https://github.com/omdsh-dev/DSH-better-sidebar/pull/357), fixes [#356](https://github.com/omdsh-dev/DSH-better-sidebar/issues/356)): on npm-installed DSH 0.1.1-rc.x (web bundle) the sidebar died on every page load (`cannot get property "betterSidebar" without inject`) — the 26 internal direct reads now go through `ctx.get('betterSidebar')` (root reflect-store resolution, immune to the fiber chain); external consumers keep `inject: ['betterSidebar'] + ctx.betterSidebar`
- 🔐 **Session-workspace boundary for file APIs** ([#345](https://github.com/omdsh-dev/DSH-better-sidebar/pull/345), fixes [#328](https://github.com/omdsh-dev/DSH-better-sidebar/issues/328)): fixed `fs.tree / fs.read / fs.write` workspace escapes; media, HTML preview and uploads share real-path symlink validation; regression tests for absolute paths, symlinks, uploads and nested Git sessions
- 🪟 **Panel-host layering and viewport clipping** ([#330](https://github.com/omdsh-dev/DSH-better-sidebar/pull/330) [#278](https://github.com/omdsh-dev/DSH-better-sidebar/pull/278), fixes [#277](https://github.com/omdsh-dev/DSH-better-sidebar/issues/277)): panel host z-index 40→25 — below the DSH cordis dynamic-plugin layer (30), so the workbench no longer covers cordis inventory/approval surfaces (still above AppFrame's 20 and below the 100+ float stack); `overflow: hidden` on the host clips at the viewport edge, so collapsed panels no longer stretch the document into bidirectional scrolling (measured `scrollWidth` 2289→1672 / `scrollHeight` 1280→1032, any skin)
@@ -601,9 +601,9 @@ Full integration docs (complete fields, matching algorithm, HMR pitfalls, declar
### ➕ Add Plugins (recommended plugin catalog)
The dashed cards at the end of the "Sidebar content" / "File viewers" grids in the "Side Cards" settings section open the **Add tab plugins** / **Add preview plugins** modals: each declares its open extension point, offers a "**Browse more plugins on GitHub**" button (the [GitHub topic `dsh-better-sidebar`](https://github.com/topics/dsh-better-sidebar)), and lists the recommended catalog (name / repo / description / install script) — "**Open**" jumps to the repo, "**Copy**" writes the install command to the clipboard.
The dashed cards at the end of the "Sidebar content" / "File viewers" grids in the "Side Cards" settings section open the **Add tab plugins** / **Add preview plugins** modals: each declares its open extension point, offers a "**Browse more plugins on GitHub**" button (the [GitHub topic `dsh-apes-sidebar`](https://github.com/topics/dsh-apes-sidebar)), and lists the recommended catalog (name / repo / description / install script) — "**Open**" jumps to the repo, "**Copy**" writes the install command to the clipboard.
**Curating a new plugin**: append a `PluginEntry` to [`src/client/plugins-tabs.ts`](./src/client/plugins-tabs.ts) (tab registrations) or [`src/client/plugins-viewers.ts`](./src/client/plugins-viewers.ts) (file-previewer registrations) and tag your repo with the `dsh-better-sidebar` topic; data integrity is guarded by `tests/plugin-list.spec.ts`.
**Curating a new plugin**: append a `PluginEntry` to [`src/client/plugins-tabs.ts`](./src/client/plugins-tabs.ts) (tab registrations) or [`src/client/plugins-viewers.ts`](./src/client/plugins-viewers.ts) (file-previewer registrations) and tag your repo with the `dsh-apes-sidebar` topic; data integrity is guarded by `tests/plugin-list.spec.ts`.
## 🛠️ Development & Build
@@ -663,7 +663,7 @@ WeChat / QQ group QR codes will live here. After uploading the QR images (drag t
## 🤝 Contributing
- **Code changes go through PRs**: develop on a `feat/*` / `fix/*` branch, then `gh pr create`; docs-only changes may be pushed to main directly
- **Curate an ecosystem plugin**: tag your repo with `dsh-better-sidebar` + PR a `PluginEntry` into [`src/client/plugins-tabs.ts`](./src/client/plugins-tabs.ts) / [`plugins-viewers.ts`](./src/client/plugins-viewers.ts)
- **Curate an ecosystem plugin**: tag your repo with `dsh-apes-sidebar` + PR a `PluginEntry` into [`src/client/plugins-tabs.ts`](./src/client/plugins-tabs.ts) / [`plugins-viewers.ts`](./src/client/plugins-viewers.ts)
- **Before submitting**: `pnpm typecheck && pnpm build && pnpm test` (or `make check` for the one-shot aggregate; CI additionally gates on npm-pack → real-mount → headless-render via `pnpm test:mount`, plus the aggregate double-mount regression `pnpm test:mount:aggregate`)
- See [`AGENTS.md`](./AGENTS.md) for the repository rules (hard constraints, CI lanes, release flow)
@@ -694,5 +694,5 @@ Thanks to everyone who contributed:
---
<div align="center">
<sub>MIT License · Built for the <a href="https://github.com/deepseek-ai/deepseek-harness">DeepSeek Harness</a> ecosystem · discover more on the <a href="https://github.com/topics/dsh-better-sidebar">dsh-better-sidebar topic</a></sub>
<sub>MIT License · Built for the <a href="https://github.com/deepseek-ai/deepseek-harness">DeepSeek Harness</a> ecosystem · discover more on the <a href="https://github.com/topics/dsh-apes-sidebar">dsh-apes-sidebar topic</a></sub>
</div>
+7 -7
View File
@@ -1,12 +1,12 @@
# dsh-better-sidebar bundle patch
# dsh-apes-sidebar bundle patch
#
# This file is the `dsh.bundle.patch` layer of the published npm package: when
# the plugin is installed through the official CLI —
#
# dsh plugin --profile <name> add dsh-better-sidebar@<version>
# dsh plugin --profile <name> add dsh-apes-sidebar@<version>
#
# — the command reconciles `dsh.profile.bundles` against installed packages
# and, seeing this declaration, appends `dsh-better-sidebar` to the bundle
# and, seeing this declaration, appends `dsh-apes-sidebar` to the bundle
# stack. The profile boot then merges THIS patch (a single `insert` of the
# plugin row) exactly like the manual cordis.patch.yml mount line used
# before. No profile file edits needed — one command installs and mounts.
@@ -23,7 +23,7 @@
#
# - insert:
# - id: better-sidebar
# name: 'dsh-better-sidebar'
# name: 'dsh-apes-sidebar'
# config:
# shell: /bin/zsh
# shellArgs:
@@ -35,7 +35,7 @@
# `web-ui-better-sidebar`). Two mounts register /sidebar/api twice and fail the
# whole plugin tree at boot ("duplicate prefix route"). The `!!js` disabled
# expression backs THIS row off when another *enabled* entry already mounts
# `dsh-better-sidebar`; the aggregate instance then owns the sidebar. The
# `dsh-apes-sidebar`; the aggregate instance then owns the sidebar. The
# loader evaluates it when this row is processed (entry-list order, only rows
# before this one are visible), so the aggregate bundle must precede this one
# in `dsh.profile.bundles` — the standard install order, since `dsh plugin add`
@@ -45,5 +45,5 @@
# fail loudly at the loader as before.
- insert:
- id: better-sidebar
name: 'dsh-better-sidebar'
disabled: !!js "[...ctx.loader.entries()].some((e) => e.options.name === 'dsh-better-sidebar' && e.options.id !== 'better-sidebar' && !e.disabled)"
name: 'dsh-apes-sidebar'
disabled: !!js "[...ctx.loader.entries()].some((e) => e.options.name === 'dsh-apes-sidebar' && e.options.id !== 'better-sidebar' && !e.disabled)"
+19 -19
View File
@@ -1,4 +1,4 @@
# 外部插件接入指南:基于 dsh-better-sidebar 实现新页面
# 外部插件接入指南:基于 dsh-apes-sidebar 实现新页面
> 面向 **消费插件开发者**:如何让你的插件向 better-sidebar 注册新的侧边栏页面(tab)和文件类型预览器。
>
@@ -56,7 +56,7 @@ better-sidebar 从 v0.4.0 起把自己改造成一个**注册表服务**:
DSH 运行时和生态的类型正主是 vendored `@deepseek-ai/cordis`。你的插件解析到它的 `Context` 时,`ctx.betterSidebar` 不会自动出现——由 better-sidebar 用 `declare module '@deepseek-ai/cordis'` 补上:
```ts
import type {} from 'dsh-better-sidebar' // 触发 declare module '@deepseek-ai/cordis' 类型合并
import type {} from 'dsh-apes-sidebar' // 触发 declare module '@deepseek-ai/cordis' 类型合并
```
这个 **type-only import** 在编译时被擦除,不产生任何运行时依赖,也不会触发构建纯度门(见 §10)。
@@ -68,15 +68,15 @@ import type {} from 'dsh-better-sidebar' // 触发 declare module '@deepseek-ai
"name": "my-plugin",
"peerDependencies": {
"@deepseek-ai/cordis": "^4.0.1",
"dsh-better-sidebar": "workspace:*"
"dsh-apes-sidebar": "workspace:*"
},
"peerDependenciesMeta": {
"dsh-better-sidebar": { "optional": true }
"dsh-apes-sidebar": { "optional": true }
}
}
```
- `dsh-better-sidebar` 必须是 **peerDependency**(不是 dependency),避免两份实例;
- `dsh-apes-sidebar` 必须是 **peerDependency**(不是 dependency),避免两份实例;
- `optional: true`:better-sidebar 未安装时你的插件照常加载,注册代码因为 `ctx.betterSidebar` 为 undefined 而安全跳过。
> ⚠️ **计划上架 DSH 插件市场**的插件还须遵守市场 manifest 约束(`dependencies`/`peerDependencies`/`optionalDependencies` 一律不得出现 `cordis`、`scripts` 不得含 install 类钩子),详见 [AGENTS.md](../AGENTS.md) 硬约束一节。
@@ -92,10 +92,10 @@ import type {
FileViewerDescriptor,
FileViewerProps,
FileFetchStrategy,
} from 'dsh-better-sidebar'
} from 'dsh-apes-sidebar'
// 方式二:子路径(与主入口等价)
import type { TabDescriptor } from 'dsh-better-sidebar/client/service' // 别名 ./client/api
import type { TabDescriptor } from 'dsh-apes-sidebar/client/service' // 别名 ./client/api
```
v0.12.0 起,服务模块还 re-export 了完整的状态词汇表,消费者可以直接命名(不再只能靠推断):
@@ -104,10 +104,10 @@ v0.12.0 起,服务模块还 re-export 了完整的状态词汇表,消费者
import type {
SidebarTab, SidebarState, SidebarStore, SidebarSnapshot, SidebarDiffRef, TabType,
SessionScope, SidebarPrefs, OpenTabSeed, SidebarSettingsRenderProps,
} from 'dsh-better-sidebar/client/service'
} from 'dsh-apes-sidebar/client/service'
```
> 💡 **类型合并触发路径**:`import type {} from 'dsh-better-sidebar/client/service'` 同样会加载 `Context` 的 augmentation(`declare module '@deepseek-ai/cordis'` 在 context-types.d.ts 中)——**纯浏览器侧插件建议走 `client/service` 路径**,避免拉进宿主半的 Node 类型图(主入口 `dsh-better-sidebar` 的声明面含宿主代码;宿主消费者本就处于 Node 环境则无所谓)。client 可达声明图(`client/*` + context-types + html-route + prefs-shared)自 v0.12.0 起**零 Node 依赖**(`scripts/check-consumer-types.sh` 守护),没有 `@types/node`、`skipLibCheck: false` 也能编译。
> 💡 **类型合并触发路径**:`import type {} from 'dsh-apes-sidebar/client/service'` 同样会加载 `Context` 的 augmentation(`declare module '@deepseek-ai/cordis'` 在 context-types.d.ts 中)——**纯浏览器侧插件建议走 `client/service` 路径**,避免拉进宿主半的 Node 类型图(主入口 `dsh-apes-sidebar` 的声明面含宿主代码;宿主消费者本就处于 Node 环境则无所谓)。client 可达声明图(`client/*` + context-types + html-route + prefs-shared)自 v0.12.0 起**零 Node 依赖**(`scripts/check-consumer-types.sh` 守护),没有 `@types/node`、`skipLibCheck: false` 也能编译。
---
@@ -115,7 +115,7 @@ import type {
```ts
// my-plugin/src/client/index.ts
import type {} from 'dsh-better-sidebar' // 触发 ctx.betterSidebar 类型合并
import type {} from 'dsh-apes-sidebar' // 触发 ctx.betterSidebar 类型合并
import type { Context } from '@deepseek-ai/cordis'
export const inject = ['betterSidebar', 'slots'] // 声明服务依赖(slots 可选,按需)
@@ -866,13 +866,13 @@ ctx.effect(() =>
| 陷阱 | 说明 |
|---|---|
| **构建纯度门** | client bundle 禁止 value-import `@dsh-external/*` 或非白名单的 `@deepseek-ai/*`(`tsdown.config.ts` 的 `dsh-client-bundle-purity` 插件);**类型 `import type {}` 会被擦除,不触发门禁**——类型可自由共享,运行时符号不行。所有跨插件交互走 `ctx.betterSidebar` 方法调用 |
| **统一 cordis 分支(v0.15.2+)** | 类型基底与 augmentation 全部在 DSH 运行时正主 `@deepseek-ai/cordis` 上(`src/context-types.ts`:真实 cordis `Context` 与结构化服务面做**交集**,不再重述 `declare module 'cordis'`——host/client 包对同名成员声明不同会 TS2717)。消费者 `import type {} from 'dsh-better-sidebar'` 即拿到 `ctx.betterSidebar`,或直接 `import type { Context } from 'dsh-better-sidebar'`。公开版 `cordis` 不再被依赖 |
| **统一 cordis 分支(v0.15.2+)** | 类型基底与 augmentation 全部在 DSH 运行时正主 `@deepseek-ai/cordis` 上(`src/context-types.ts`:真实 cordis `Context` 与结构化服务面做**交集**,不再重述 `declare module 'cordis'`——host/client 包对同名成员声明不同会 TS2717)。消费者 `import type {} from 'dsh-apes-sidebar'` 即拿到 `ctx.betterSidebar`,或直接 `import type { Context } from 'dsh-apes-sidebar'`。公开版 `cordis` 不再被依赖 |
| **ModuleLoader 不跨插件** | 运行时 `require()` 虽支持跨 bundle,但被构建门挡;所有交互走 `ctx.betterSidebar` 方法调用 |
| **host 半无此服务** | `ctx.betterSidebar` 只在 client 侧存在;host 半需要 better-sidebar 数据走 `/sidebar/api/*` HTTP 路由 |
| **portal 限制** | 整面板 slot 由 ui-layout 独占,外部 tab 只能进入 better-sidebar 的 portal 内部,无法全屏替换整个面板 |
| **id 冲突** | `registerTab` / `registerFileViewer` 对重复 id 抛错;建议用包前缀(`my-plugin:xxx`) |
| **不要 value-import `dsh-better-sidebar`** | 即使是 `./client/service` 子路径,运行时落点也是 client bundle;只做 type-only import |
| **client 声明图零 Node 依赖(v0.12.0+)** | `dsh-better-sidebar/client/service` 的可达声明面(含 `Context`)不引用 `node:*` / `Buffer`——纯浏览器侧插件无需 `@types/node`,`skipLibCheck: false` 也能编译(`scripts/check-consumer-types.sh` 守护;主入口含宿主声明,宿主消费者本就处于 Node 环境) |
| **不要 value-import `dsh-apes-sidebar`** | 即使是 `./client/service` 子路径,运行时落点也是 client bundle;只做 type-only import |
| **client 声明图零 Node 依赖(v0.12.0+)** | `dsh-apes-sidebar/client/service` 的可达声明面(含 `Context`)不引用 `node:*` / `Buffer`——纯浏览器侧插件无需 `@types/node`,`skipLibCheck: false` 也能编译(`scripts/check-consumer-types.sh` 守护;主入口含宿主声明,宿主消费者本就处于 Node 环境) |
| **家族右面板互斥(v0.13.0+)** | `aionui-panel` 设置的 `rightPanel` 解析为 `'aionui-panel'` 时整个侧边栏不挂载(`settings.get` 返回 `externalDisable: true`,挂载门 + 接管停用;`settings/document-updated` 推送实时生效,无 `remote` 服务回退启动时判定)。未安装 aionui 不受影响 |
| **i18n 跟随** | 文案跟随 DSH `ctx.locale`(词典在 `betterSidebar` 命名空间;Host-backed `locale.preference` 优先于浏览器语言并实时切换;缺失回退浏览器)。消费插件**不要**依赖内部 `t()`——标题传字符串或 `() => string` |
| **第三语言覆盖(ja 等)** | 可选 peer `@huanlin/dsh-plugin-better-locale`(optional)提供 ja/ko 覆盖,**借用 DSH 英文槽位**(仅 DSH=en 时生效,zh 下惰性)。经 `ctx.get('betterLocale')` 注入 `t()`;未安装整段 no-op |
@@ -907,7 +907,7 @@ ctx.effect(() =>
- **面板表面**:右/底面板背景 = `var(--dsw-alias-bg-layer-1)`。**绝不消费 `--dsw-specific-sidebar-fill`**(宿主左导航专属,皮肤按左导航语义覆盖它,面板消费会失去填充)。换面板表面 = 覆写 `--dsw-alias-bg-layer-1`。
- **终端/编辑器表面**:`effectiveTokenValue` 读 `--dsw-alias-bg-base`——`transparent` 与 alpha < 0.9 的半透明值回退不透明底色(文字不叠背景画,issue #90);≥ 0.9 放行。
- **根锚点**:宿主 div 带 `data-dsh-better-sidebar`(append 到 body);其内**面板宿主层** `[data-dsh-panel-host]`(`fixed; inset:0; z-25; pointer-events:none; overflow:hidden+clip`,v0.13.1+),面板/开关簇 absolute 定位,免疫中间层 transform 劫持;页面级 transform 触发 `data-dsh-panel-host-degraded` 降级。`overflow` 级联是**契约**(`hidden` 兜底 + `clip` 收尾,`tests/panel-host-css.spec.ts` 守护):`hidden` 盒子仍是滚动容器,脚本滚动或浏览器 scroll-into-view 修正(焦点移入视口外区域、嵌套 iframe/工作台加载时抢焦点、面板滑出动画中 focus() 落点)会沿最近可滚祖先滚走整层——面板与开关簇集体偏离视口角(computed left/right 仍"正确",偏移藏在盒子自身 scroll offset 里);`clip` 裁剪语义相同但不产生滚动盒,任何路径都滚不动这层。皮肤作用域覆盖限定在 `[data-dsh-better-sidebar]` 内。
- **根锚点**:宿主 div 带 `data-dsh-apes-sidebar`(append 到 body);其内**面板宿主层** `[data-dsh-panel-host]`(`fixed; inset:0; z-25; pointer-events:none; overflow:hidden+clip`,v0.13.1+),面板/开关簇 absolute 定位,免疫中间层 transform 劫持;页面级 transform 触发 `data-dsh-panel-host-degraded` 降级。`overflow` 级联是**契约**(`hidden` 兜底 + `clip` 收尾,`tests/panel-host-css.spec.ts` 守护):`hidden` 盒子仍是滚动容器,脚本滚动或浏览器 scroll-into-view 修正(焦点移入视口外区域、嵌套 iframe/工作台加载时抢焦点、面板滑出动画中 focus() 落点)会沿最近可滚祖先滚走整层——面板与开关簇集体偏离视口角(computed left/right 仍"正确",偏移藏在盒子自身 scroll offset 里);`clip` 裁剪语义相同但不产生滚动盒,任何路径都滚不动这层。皮肤作用域覆盖限定在 `[data-dsh-apes-sidebar]` 内。
- **布局变量**(`<html>` 上,面板打开时有效):`--dsh-sidebar-width` / `--dsh-sidebar-height`。右面板宽度 = AppFrame 的 `padding-right` 预留(新版 `#root [data-dsh-frame]` / rc.8 `#root > [data-slot="root"] > div` 双锚点),AppFrame border box 保持完整桌面视口宽度(Harness 以此判定桌面/窄屏布局,避免插件面板展开误入窄屏);AppFrame 的 details 拖拽手柄按同一变量向左平移贴合列边缘。底部面板仍走 centerCol `margin-bottom`;centerCol 锚点 = **JS 标注**(禁止 `nth-child`):侧栏 shell 的定位器给测得的 centerCol 节点打 `[data-dsh-center-col]` 标签(`Sidebar.tsx` locate,节点更换/HMR 时随 ref 迁移),`layout.css` 用 `#root [data-dsh-center-col]` 选中(`drag-layout.e2e.ts` 断言恰一节点且为对话槽宿主的父级——alpha.2 起 shell 把 `#root [data-slot="main.conversation"]` 解析进列并跳过 `display: contents` 祖先(`center-column.ts` 的 `CENTER_COLUMN_SELECTOR` 同时认 `main.conversation` 与 alpha.1 的 `conversation`),定位器从槽宿主向上取第一个非 `contents` 的祖先;frame 宽度与桌面 Session Log 由 `desktop-layout.e2e.ts` 断言)。
- **桌面信号与标题栏**(v0.14.1+ 四方案模型 `SidebarPrefs.titleBarScheme`,唯一决策点 `src/client/titlebar-strip.ts` 纯函数):
- 壳信号(只读,不自动触发修改):URL `dsh-desktop-mode` / `dsh-desktop-platform` / 可选 `dsh-desktop-titlebar-inset`(0–120 clamp)。
@@ -920,7 +920,7 @@ ctx.effect(() =>
### 12.2 注意事项
- 类名是 CSS Modules 哈希,**不是契约**;精确命中用 `[data-dsh-better-sidebar]` + 子串类名(`[class*='panel']`)或 DOM 结构。
- 类名是 CSS Modules 哈希,**不是契约**;精确命中用 `[data-dsh-apes-sidebar]` + 子串类名(`[class*='panel']`)或 DOM 结构。
- 改动本契约必须同步本文档、设计文档与 `tests/theme.spec.ts`。
---
@@ -942,11 +942,11 @@ ctx.effect(() =>
},
"peerDependencies": {
"@deepseek-ai/cordis": "^4.0.1",
"dsh-better-sidebar": "workspace:*",
"dsh-apes-sidebar": "workspace:*",
"react": "^18.2.0"
},
"peerDependenciesMeta": {
"dsh-better-sidebar": { "optional": true }
"dsh-apes-sidebar": { "optional": true }
}
}
```
@@ -955,7 +955,7 @@ ctx.effect(() =>
```tsx
import { createElement } from 'react'
import type {} from 'dsh-better-sidebar' // 触发 ctx.betterSidebar 类型合并
import type {} from 'dsh-apes-sidebar' // 触发 ctx.betterSidebar 类型合并
import type { Context } from '@deepseek-ai/cordis'
export const inject = ['betterSidebar']
@@ -1033,7 +1033,7 @@ better-sidebar 的内置 tab 和 viewer 就是参考实现("吃狗粮"),
- **接入方式**:可选软依赖——client half 本地重述最小服务契约(`registerTab`),未安装 better-sidebar 时注册静默跳过,插件原有表面不受影响;
- **注册内容**:`dsh-sentinel:watches` tab(order 60,单实例):全服务器监控表 + 最近触发历史;
- **类型处理**:未 value-import `dsh-better-sidebar`,构建零耦合;与 §2 的 `import type {}` 方案可互换;
- **类型处理**:未 value-import `dsh-apes-sidebar`,构建零耦合;与 §2 的 `import type {}` 方案可互换;
- **实测**:v0.3.0 起,真实 web profile 验证通过。
通过 `ctx.betterSidebar` 的三方插件 [dsh-sidebar-qa](https://github.com/ChenRuoT/dsh-sidebar-qa) —— 基于 better-sidebar 的划选提问:对话划选 → 右侧面板提问 → 同工作区独立追问会话(❓追问·主题);快速无思考模型压缩主对话上下文后与引文一起注入,不打断主对话;追问可嵌套、可继续、可归档。
+185
View File
@@ -0,0 +1,185 @@
# 01 · 需求分析:官方原生右列 vs 装了 apes-sidebar
> 本篇只回答一个问题:**一个在 DSH 里干活的人,从"官方原生"换到"装了这个包",他的需求和处境发生了什么变化?**
> 对比的两端只有两个:**官方原生的右侧栏**,和**装了 apes-sidebar 之后的右侧栏**(那个包是一个整体,九个 tab 一起算)。
> 机制与根因只在[附录 A](#附录-a--证据与实现索引)留索引,细节见 [02-系统架构](02-系统架构)、[06-故障排查](06-故障排查)。
## 一、先钉住"谁在用、他在哪儿干活"
在 DSH 里,**右侧栏是使用者一天里盯得最久的一块地方**。它不是"偶尔打开的设置页",而是"工作台 + 仪表盘"。
一个典型使用者(在 DSH 里做项目的人):
- 中间是对话主线——他和模型一起干活;
- 右边是这块地方——看文件、看改动、看子代理、顺手追问、跑命令、看预览;
- 他往往同时开着好几个会话、好几个项目。
这决定了本页所有需求的共同特征:**这些信息"看一眼就够,但要频繁看"。**
频繁看的东西必须常驻在视野里——不能要求他"跳出去看一眼,再跳回来"。
## 二、官方原生的右列:一个「只读的文件浏览器 + 预览器」
本机宿主(`dsh@0.1.5-rc.2`)里,官方真正放进右列的东西只有三件:
| 官方包 | 它给用户什么 | 官方原话 |
| --- | --- | --- |
| `dsh-client-ui-sidebar-right` | **容器**:每会话一块可停靠面板、两种形态(贴靠 / 全屏)、展开按钮在会话 header 角落、最多左右两格、拖拽与浮窗、tab 菜单、内置撤销历史 | "为每个会话持有一个停靠面……拥有导航控制器(`ctx.sidebarRight`)、tab 类型注册表" |
| `dsh-client-ui-sidebar-files` | **唯一的页**:「文件」——把会话工作区根目录画成一棵**只读**的树 | "把会话的工作区根目录画成一棵树,逐层经线上列出,并把文件打开到 Sidebar 里" |
| `dsh-client-ui-sidebar-documentpreview` | **唯一的查看器**(kind = `text`):预览 Markdown / 代码 / 图片 / PDF / HTML,认不出的扩展名走**纯文本兜底** | "在右侧 Sidebar 预览**可读文件**,无需另开 tab 即可切换已注册的渲染器" |
一个容易被忽略、但对"用户体验"影响很大的细节:
> 引导页(罗盘)**只在注册了两个以上入口时才出现**——官方随包只有一个入口(Files),
> 所以官方用户的右列**默认直接就是那棵文件树,罗盘从不露面**。
> (原文:"恰好一个入口时直接打开对应页面(随包组合中为 Files)"。)
**一句话概括官方原生的取向:它假设你只是想「看」。**
它给你树和预览,都是只读的;**它不假设你要在这块地方「干活」**。
于是,官方原生用户在右列里**做不到**这些事(每一条都是他能直接感知的缺失):
| 他想做的事 | 官方原生右列 | 他只能怎么办 |
| --- | --- | --- |
| 改一个文件 | ❌ 只有预览,没有可写的编辑器 | 跳出去,用外部编辑器打开 |
| 知道刚才改了哪些东西 | ❌ 没有变更清单、没有 diff | 切终端敲 `git status` / `git diff`,或另开 diff 工具 |
| 看某个子代理干了什么 | ❌ 右列里没有这个入口 | 翻会话树、翻聊天记录去认人 |
| 追问一句又不想弄脏主线 | ❌ 没有旁路对话 | 硬着头皮在主对话里问,或另开一个窗口 |
| 跑一条命令 | ❌ 没有终端 | 切到系统终端,还得先 `cd` 到对的目录 |
| 看页面 / HTML 出来什么样 | ❌ 没有内嵌浏览器 | 切浏览器标签,或手动打开产物文件 |
| 知道谁在替我干活、跑到哪了 | ❌ 完全没有 | 只能往上翻记录,翻到一半又忘了上下文 |
| 知道这摊活是在哪儿干的 | ❌ 界面不写 | 靠脑子记,或敲 `pwd` 确认 |
| 知道这个会话现在什么状态 | ❌ 界面不写 | 靠界面上的小细节猜 |
## 三、装了 apes-sidebar 之后:同一块地方,从「看」变成「干 + 心里有数」
apes-sidebar 把这九件事一次性放进右列。它们**是一个整体需求**:把右列从"文件浏览器"变成"工作台 + 仪表盘"。
| tab | 他真正想要的是什么 | 官方原生时他被迫怎么做 | 装了之后 | 需求被改变成 |
| --- | --- | --- | --- | --- |
| 文件(编辑器) | 看**和改**代码,而不丢掉当前上下文 | ⌘Tab 跳到外部编辑器;回来要重新找位置 | 右列里直接看、直接改 | 「看文件不该让我离开现场」 |
| 文件变动(git) | 刚才到底改了哪些东西 | 切终端敲命令,或另开 diff 工具 | 变更清单 + 单文件 diff,就在旁边 | 「改了什么必须当场看得见」——**这是信任的前提** |
| 子代理 | 进某个子代理,看它到底干了什么 | 翻会话树、翻记录去认人 | 右列里直接进入该子代理 | 「子代理是可进入的,不是只能干等的」 |
| 侧边对话 | 追问一句,但不想污染主线 | 硬着头皮在主对话里问,或另开窗口 | 旁路开一个小对话,两边互不干扰 | 「提问不该有副作用」——**性质变了** |
| 终端 | 跑一条命令 | 切到系统终端,常常还得先 `cd` | 右列里跑,工作目录天然就是对的 | 「命令的上下文应该跟着会话走」 |
| 浏览器 / 预览 | 看这个页面出来是什么样 | 切浏览器标签,或手动打开产物 | 右列内嵌,当场看 | 「产出物要能当场验收」 |
| **智能体** | 谁在替我干活、跑到哪了、哪个停了 | **基本没办法**——只能翻记录猜 | 主代理在最上,直系子代理一人一行,带运行/结束状态 | 「派了活之后,我要的是**扫一眼**,不是盯着守」(见 §四·洞察 4) |
| **应用** | 这摊活到底是在哪儿干的 | **基本没办法**——靠脑子记或敲 `pwd` | 打开就看到当前会话锚定的工作区 | 「界面应该告诉我我在哪,而不是要我自己记住」 |
| **画像** | 这个会话现在是什么状态 | 靠界面小细节猜(在跑?停了?哪一次?) | 标题 / 运行或空闲 / 工作区,写清楚 | 「离席回来,不用重建心智模型」 |
连**顺序**都在讲同一件事:`10…50` 是操作面(看/改/查/问/跑/验),`60…70` 是态势面(谁在干、在哪儿干、什么状态)。
先让"活能在这儿干完",再让"我心里有数"——这是这个包对用户的两级承诺。
## 四、深度分析:需求到底发生了什么变化
### 洞察 0 · 官方原生假设"你只是看",而真实使用是"在这儿干活"
这是整个需求空间的起点。官方原生不是做得不好,而是**取向不同**:只读预览是"查看器"的思路,
而使用者把它当"工位"用——他会在这里改一个文件、跑一条命令、瞄一眼改动。
**当一块地方被当成工位,只读就会立刻变成绕路。** 这就是这个包存在的全部理由。
### 洞察 1 · 从「只读」到「可写」:补的不是功能,是主权的归还
官方只给预览,意味着**每一个"改一下"都要离开这块地方**。装上之后,改、查改动、跑、验四件事都在原地完成。
用户省下的不是几次点击,而是**每次离开都要付出的"上下文重建"成本**——回来要重新找到刚才的位置、重新想起来自己在干嘛。
需求因此从「我要能看」变成「我要在这儿把活干完」。
### 洞察 2 · 「留在原地」会变成默认预期(棘轮效应)
一旦右列里能改文件、能跑命令、能看预览,他对"跳出去一下"的容忍度会**永久下降**。
他会开始说「为什么这个还要我切窗口?」——而这句话在他用官方原生时是说不出口的(因为原生本来就没有可切的东西)。
**这类需求只前进、不后退**,所以值得一次做对:它一旦被填好,就变成基础设施。
### 洞察 3 · 消除副作用之后,他会开始"问"(侧边对话)
侧边对话的价值不在"多一个对话框",而在**把提问的代价从"可能污染主线"降到"零"**。
需求的性质变了:以前他的问题是「要不要问?」(一个决策),现在变成「顺手问一下」(一个动作)。
**当一件事的代价趋近于零,用户的行为频率会跳一个数量级**——这是需求分析里最值得警惕、也最值得利用的一条规律。
### 洞察 4 · 看得见 → 敢放手(智能体 tab 的核心价值)
这是整个包里最值钱的一条。官方原生在这一点上是**彻底空白**:
- 官方没有子代理视图,也没有任何"队伍"概念 → 他派了活之后**只能等**,或者不停往上翻记录确认;
- 这个包的「子代理」让他能**进入**某一个;「智能体」让他**不必进去就看得到全局**——主代理 + 直系子代理,谁在跑、谁停了。
用户真正的痛点不是"我进不去子代理",而是**"我不敢派太多活"**:
因为看不见,他只能用"盯着"来对冲风险,而盯着就失去了并行的意义。
> **看得见 → 敢放手 → 真的并行 → 才配得上"多智能体"。**
> 需求从「我要能看见」升级为「我要能放心」。
### 洞察 5 · 心智模型从脑子里搬到界面上(应用 / 画像)
多项目、多会话的人切换时要重建一次"我这是在哪儿"——标题差不多的会话最容易看错,
而看错的代价是**写错文件**。官方原生在右列里一个字都不写这件事。
「应用」「画像」做的就是**把这份"我记得"外化成"界面写着"**。
需求的性质:从「我要记性好」变成「界面不该让我记」。
### 洞察 6 · "引导页"第一次有了意义:从单一工具到工作台
官方原生只有一个入口(Files),所以它**默认直接开文件树,罗盘界面根本不会出现**。
入口一多,罗盘才登场——用户第一次能"一屏看见这块地方能干什么"。
这是**用户层面的分水岭**:官方是"一个工具",装了之后是"一个工作台的目录页"。
(对照 §二那个细节:入口数决定了这块地方的形态。)
### 洞察 7 · 价值随"并行度"放大;而成本几乎为零
- 单会话、单项目的用户:「应用」「画像」几乎用不上,「智能体」也只有一个主代理;
- 一旦同时开几个会话、派几个子代理、换几个项目:这三个 tab 的边际价值**陡增**。
所以这套东西**不是人人需要,但接到的人离不开**。
同时它**不新增数据源、不新增进程、不引入新的失败面**——只是把宿主已经投影出来的信息换个视角呈现
(逐条列在 [03-功能说明](03-功能说明))。对使用者意味着:**低风险地换到确定性**,这是它最该守住的边界。
## 五、由以上分析推出的需求(U1–U6)
| # | 需求(用户口吻) | 怎么算他满意了 | 现状 |
| --- | --- | --- | --- |
| U1 | 「我要在这儿把活干完,别老让我跳出去」 | 改文件、查改动、跑命令、看预览、顺手追问,都在右列完成 | ✅ 操作面六个 tab |
| U2 | 「我要看得见谁在替我干活」 | 右列点一下就有队伍表;人数/状态一眼可辨;没有子代理时明确说"没有",不是空白 | ✅ 智能体 tab |
| U3 | 「我要知道我是在哪儿干的活、这会话什么状态」 | 打开就看到工作区与运行/空闲,不用敲 `pwd`;拿不到应用信息时如实说明 | ◑ 工作区/状态 ✅ · 应用层等宿主 |
| U4 | 「我点开 HTML 就该看得见」 | 自包含 HTML 完整显示,而不是一行 `forbidden`;**但不许为此拆掉沙箱** | ✅(多文件需我本人点「临时解锁」) |
| U5 | 「我的界面不许自己乱套」 | 右列只有一套侧边栏、一个开合入口;弹窗完整盖在面板之上、按钮点得到 | ✅ |
| U6 | 「我敢升级,也退得回去」 | 有"宿主版本 ↔ 插件版本"对照表;每步升级都有分支/tag 可回滚 | ✅ 两条版本线;新宿主线待实机验证 |
## 六、我们不做的事(省得期待错位)
- **不重造官方已有的东西。** 官方给了容器与文件树,我们在它里面长东西,不另起一套侧边栏。
- **不为"看得见"放宽安全边界。** 需要放宽必须由用户**本人显式点**「临时解锁」。
- **不做团队/房间/多智能体协作编排。** 那是宿主的另一类职责。
- **不承诺"装上立刻见效"。** 单会话用户可能几乎感觉不到差异(见洞察 7)。
## 七、已知的「暂时满足不了」(诚实交代)
| 他想要的 | 现状 | 为什么(一句话) | 何时会有 |
| --- | --- | --- | --- |
| 知道这摊活属于哪个**应用**、有哪些入口 | 「应用」只显示工作区 | 宿主目前没有向插件提供应用清单的通道 | 宿主提供 feed 后即可接上 |
| 一次摊开**完整**子代理树(子代理的子代理) | 只显示**直系** | 宿主投影只给一层 | 同上 |
| 沙箱状态下多文件 HTML 的相对资源 | 需点「临时解锁」 | 不透明源沙箱下子请求拿不到宿主信任头(安全边界的必然结果) | 不打算改——这是刻意保留的边界 |
> 这三条说明同一件事:**需求跑在宿主能力前面**。写出来不是免责,是让读的人知道
> "不能用"和"还没做"是两回事。
## 附录 A · 证据与实现索引
开发与排障看这里;其余读者可跳过。
**"官方原生到底有什么"的证据**(本机宿主 `dsh@0.1.5-rc.2`,官方包自带文档):
- 容器:`@deepseek-ai/dsh-client-ui-sidebar-right/README.zh.md` —— 停靠面、两种形态、扩展席位、引导页。
- 唯一的页:`@deepseek-ai/dsh-client-ui-sidebar-files/README.zh.md` —— "文件树 tab 类型……把文件打开到 Sidebar 里"。
- 唯一的查看器:`@deepseek-ai/dsh-client-ui-sidebar-documentpreview/README.zh.md` —— "Sidebar tab 的 kind 为 `text`",未知扩展名纯文本兜底。
- "官方只有一个入口 ⇒ 罗盘不出现":同上 sidebar-right 文档「引导页」一节。
**本包一侧的对应实现**(`editor`/`git`/`subagent`/`sidechat`/`terminal`/`browser` 为随包操作面;
`agents`/`apps`/`profile` 是本 fork 自增的态势面——对内维护用,对外它是一个包):
| 需求 | 机制(去哪看) | 关键证据 |
| --- | --- | --- |
| U5-a 右列只有一套侧边栏 | 每个 tab 注册成原生 tab 类型(`kind = descriptor.id`)渲染进官方右列;自绘面板已移除 | [02-系统架构](02-系统架构)、[05-运维手册](05-运维手册) |
| U5-b 弹窗不被面板切 | 宿主容器上的层叠上下文(`isolation`)导致;挂载点改挂 `#root` 而不再挂 `document.body` | [06-故障排查](06-故障排查) |
| U4 HTML 预览 | 主框架 `fetch` → `srcDoc`,**`sandbox` 属性未动**;失败回退路由 URL | [06-故障排查](06-故障排查) |
| 与官方 `text` 预览的关系 | 本包的编辑器类型以更高优先级接管 `dsh-resource://file/**`,卸载即恢复官方预览 | `docs/external-plugin-guide.md` §「内置类型接管」 |
| U6 版本线 | 宿主版本不匹配时宿主**静默禁用**整行(不报错、无日志);`^` 跨 minor 判 false | [04-部署指南](04-部署指南)、[06-故障排查](06-故障排查) |
<!-- 本页只讲"为什么需要"。实现细节、提交号、测试名一律留在 02–07 与附录索引里。 -->
+154
View File
@@ -0,0 +1,154 @@
# 02 · 系统架构
> 本篇说明插件的**分层、接口面、层叠契约与关键不变量**。
> 与源码一一对应,可直接当作改动前的「雷区地图」使用。
## 一、总体分层
```
┌──────────────────────────────────────────────────────────────────────────┐
│ DSH 宿主(Desktop Electron 壳 / Web profile) │
│ ├─ 原生右侧栏 ctx.sidebarRight / sidebarRightTabs(tab 类型注册表) │
│ ├─ 会话与任务 ctx.sessions.list(会话摘要、子代理、投影)、ctx.tools │
│ ├─ webServer 路由挂载点(插件用 /sidebar/* 前缀注册) │
│ └─ 桌面壳层 webserver 外层的请求围栏 + #root 的 content-viewport 标记 │
└───────────────┬──────────────────────────────────────────────────────────┘
│ ① 宿主 → 插件:cordis 服务注入(inject)
│ ② 插件 → 宿主:原生 tab / 资源打开 / 路由
┌───────────────┴──────────────────────────────────────────────────────────┐
│ 插件 HOST 半(Node,lib/index.js) │
│ · src/index.ts 路由注册(/sidebar/*)、信任围栏、会话/文件数据、HTML 交付 │
│ · src/trust-fence.ts 请求信任围栏(Host 头 / 令牌) │
│ · src/invariant.ts 依赖零的断言与 semver 工具(host/client 共用) │
└───────────────┬──────────────────────────────────────────────────────────┘
│ 浏览器侧模块(__ModuleLoader__ bundle,非 CJS)
┌───────────────┴──────────────────────────────────────────────────────────┐
│ 插件 CLIENT 半(浏览器,lib/client.js + 懒加载 chunk) │
│ · src/client/index.tsx 应用入口:装配 store/service/原生适配/信息层 │
│ · src/client/native/ 原生右侧栏适配(tab 记录、tab 体、tab 芯片标题) │
│ · src/client/builtins/ 内置面板:文件 / 文件变动 / 子代理 / 侧边对话 / │
│ 终端 / 浏览器 + 文件预览器 │
│ · src/client/sidebar/ 自绘底部工作台与开合控件(原生侧边栏之外的部分) │
│ · src/client/apes/ 信息层三 tab(本 fork 的增量) │
│ · src/client/service.ts ctx.betterSidebar 服务(供其他插件注册) │
└──────────────────────────────────────────────────────────────────────────┘
```
## 二、构建产物
| 产物 | 角色 | 备注 |
| --- | --- | --- |
| `lib/index.js` | host 半(ESM,Node) | 路由、围栏、数据面 |
| `lib/invariant.js` | 不变量面(零依赖) | host/client 都要 import,**不得**引入 Node 内置或宿主包 |
| `lib/client.js` | client 主 bundle | 浏览器 `window.__ModuleLoader__.load({...})` 形态,**不是** CJS/ESM,Node 里 `require` 会因 `window is not defined` 抛错 |
| `lib/client-registry.js` | 懒加载 chunk | 高开销能力(图标/预览库)按需加载 |
| `lib/client-editor.js` | 懒加载 chunk | 编辑器(CodeMirror 系) |
| `lib/client-locale.js` | 懒加载 chunk | 语言包 |
| `lib/client-mermaid.js` | 懒加载 chunk | Mermaid 渲染 |
| `lib/client-terminal.js` | 懒加载 chunk | 终端 |
| `lib/types/**/*.d.ts` | 类型声明 | 供消费方类型检查 |
> 懒加载动机:主 bundle 不应静态 import 重型预览库;chunk 通过宿主 `ctx.modules` 解析外部依赖
> (入口在 `src/client/index.tsx` 里先 `setChunkModuleSystem(ctx.modules)`)。
## 三、三层接口面
### 3.1 宿主 → 插件(cordis 注入)
```ts
// src/index.ts
export const name = 'dsh-apes-sidebar'
export const inject = ['webServer', 'sessions', 'webRuntime', 'tools']
export function apply(ctx: Context, config?: SidebarConfig): void
```
客户端侧读取的宿主面(结构化镜像,运行时由宿主提供):
`ctx.sessions.list`(`getSnapshot/subscribe`)、`ctx.sidebarRight`(原生右列与 `openResource`)、
`ctx.slots`、`ctx.settings`(挂载行 `config`)、`ctx.modules`(懒加载 chunk 的模块系统)。
### 3.2 插件 → 宿主
| 方向 | 手段 |
| --- | --- |
| 注册 tab 类型与 tab 体 | `src/client/native/registerNativeSurface()` → 原生右侧栏注册表 |
| 打开会话内文件 | `ctx.sidebarRight.openResource('dsh-resource://file/…')` |
| 暴露 HTTP 能力 | `/sidebar/` 前缀路由:`api` · `file` · `html` · `upload` · `ws/terminal` · `ws/agent-opens` · `ws/agent-terminals` |
### 3.3 插件 → 其他插件(对外服务)
`ctx.betterSidebar`(`src/client/service.ts`)对外开放 `registerTab` / `registerFileViewer` 等,
并按「能力清单」做前向兼容门控(能力只增不减)。对外契约见仓库内
[docs/external-plugin-guide.md](../blob/main/docs/external-plugin-guide.md)。
服务版本常量 `SIDEBAR_SERVICE_VERSION` 必须与 `package.json` / `dsh.plugin.json` 的版本三方一致
(有单元测试断言这一对)。
## 四、面板宿主与层叠契约(重点雷区)
插件的所有面板渲染在一个 **body 级(或壳层 viewport 级)的宿主层**里:
```css
[data-dsh-panel-host] { position: fixed; inset: 0; z-index: 25; pointer-events: none }
```
`z-index: 25` 不是随手取的,它要同时满足:**在 app 内容与壳层 overlay 之上、在宿主交给插件用的
overlay 槽之下**。完整取义:
| 层级 | 对象 |
| --- | --- |
| `z-index: auto` | app 的流内内容、`.dshDesktopFrame` |
| **25** | **本插件的面板宿主** |
| 30 | 宿主的动态插件面板(ui-cordis) |
| 100+ | app 的 overlay 栈(Toast / onBoarding 等) |
| 1000 | `.dshDesktopOverlay[data-shell-overlay]`(宿主给 `shell.overlay` 槽的容器,第三方弹窗在此) |
**陷阱(已修,`4422001`)**:DSH 桌面壳的有框模式会给上游 `#root` 打标
`data-dsh-desktop-content-viewport` 并施加 `isolation: isolate`,使 `#root` 成为一个**层叠上下文**。
宿主自己的 overlay 层就在这个上下文内;若插件宿主挂在 `document.body`(上下文之外),
它是「带正 z-index 的定位元素」,会画在整个上下文**之上**——把宿主 overlay 里的弹窗(如社区插件市场)盖住。
```ts
// src/client/desktop-env.ts —— 修法:挂进壳层的 content viewport
export function panelHostParent(doc: Document = document): HTMLElement {
return doc.querySelector<HTMLElement>('#root[data-dsh-desktop-content-viewport]') ?? doc.body
}
```
进入该上下文后,25 恢复原意(在 app frame 之上、在 overlay 层 1000 之下);无该标记的部署
(纯网页 / 未打标的壳)仍挂 `document.body`,行为不变。重新锚定守卫同时监听
`document.body` 的 `data-dsh-desktop-mode`(壳层打标与它是同一个 effect),因此壳层在插件挂载**之后**
才框住 `#root` 的情况也能被搬到正确位置。
## 五、数据流示例
**会话 → 信息层**
```
ctx.sessions.list.getSnapshot() → { byId, projectionsBySession? }
useSyncExternalStore(subscribe) (uSES 安全订阅)
├─ apes:agents → rootAncestor(byId, scope.sessionId) 找主代理
│ → projectionsBySession[rootId].values.subagentCatalog 取直系子代理
│ (无投影时回退 byId 里的 durable 子会话)
└─ apes:profile / apes:apps → byId[scope.sessionId](标题 / cwd / running)
```
**文件 → 预览**(HTML 为例)
```
编辑器/diff 面板 → /sidebar/html/<session>/<path> (路由负责路径围栏与体积上限)
主框架 fetch 取回文档文本 → 注入 <base href="<该文件的路由目录>">
→ iframe srcDoc + sandbox(不透明源)
取失败时回退为 iframe src=路由 URL,把路由的答复原样透出
```
## 六、关键不变量(改动前必读)
1. **host / client 拆分是结构性的**:client bundle 在浏览器运行,不得 import Node 内置;
`src/invariant.ts` 两侧共用,必须保持零依赖。
2. **`dsh-better-sidebar` 是 peer,永不打包**:一旦被内联,client 会带上第二份侧边栏注册表,
tab 注册进死实例,界面上永不出现。
3. **tab id 前缀归本 fork**:信息层用 `apes:*`;不得占用上游/宿主内部 id。
4. **HTML 预览的沙箱令牌不得放宽**(不加 `allow-same-origin` 等);交付方式只能是「主框架取文档 + `srcDoc`」。
5. **颜色只用宿主设计令牌**(`--dsw-alias-*`),不写硬编码色值;几何量(字号/间距/圆角)无公开令牌,内联即可。
6. **所有面向用户的字符串走 i18n**,`zh` / `en` 必须同时存在。
7. **变更后必须重新同步到 profile**:构建产物非字节确定性,且 profile 的 `file:` 依赖是复制语义,
不能假定旧副本可用(见 [04-部署指南](04-部署指南))。
+109
View File
@@ -0,0 +1,109 @@
# 03 · 功能说明
> 本篇是**功能清单与行为契约**:有哪些面板、数据从哪来、文案与配色怎么走、安全边界在哪、能配什么。
## 一、面板与标签页总表
「来源」列:**上游** = 来自 `dsh-better-sidebar`;**宿主** = DSH 自身提供并占用该槽位;**本 fork** = 本项目的增量。
| id | 名称 | order | 来源 | 数据 / 行为 |
| --- | --- | --- | --- | --- |
| `editor` | 文件 | 10 | 上游 | 文件树 + 编辑器(CodeMirror 系,懒加载 `client-editor`) |
| `git` | 文件变动 | 20 | 上游 | 工作区变更、单个 diff 视图 |
| `subagent` | 子代理 | 30 | 上游 | 会话的子代理视图 |
| `sidechat` | 侧边对话 | 35 | 上游 | 会话内旁路对话 |
| `terminal` | 终端 | 40 | 上游 | PTY 终端(懒加载 `client-terminal`;v0.24.1 线起由宿主内置终端承担) |
| `browser` | 浏览器 | 50 | 上游 | 内嵌浏览器/HTML 预览宿主 |
| `diff` | (内部) | -1 | 上游 | 非用户可见,供内部打开 diff |
| **`apes:agents`** | **智能体** | **60** | **本 fork** | 主代理 + 直系子代理拓扑 |
| **`apes:apps`** | **应用** | **65** | **本 fork** | 当前会话所属工作区(宿主暂无「应用」feed) |
| **`apes:profile`** | **画像** | **70** | **本 fork** | 当前会话摘要(标题 / cwd / 运行态) |
> order 10–50 是上游/宿主既定带宽,apos 信息层取 60/65/70 留出余量,避免与原生面板抢占位置。
## 二、信息层三 tab(本 fork 的增量)
三个 tab 全部是**单实例**(`single: true`),且都按 DSH 标准 `TabDescriptor` 注册,
因此自动获得原生侧边栏的全部待遇:进入原生 guide、tab 芯片带图标、可开合与分栏。
| tab | 展示内容 | 数据来源 | 空态 |
| --- | --- | --- | --- |
| `apes:agents` | 根(主代理)卡片 + 其直系子代理卡片;每行显示标签、`one-shot`/`continuable`、运行态圆点 | ① `ctx.sessions.list` 的 `byId`(durable 会话摘要);② 宿主的会话投影 `projectionsBySession[sessionId].values.subagentCatalog`(直系子代理清单) | 「当前会话没有子代理」提示 |
| `apes:apps` | 当前会话的工作区(cwd) | `byId[scope.sessionId].cwd`,回退 `scope.cwd` | 明确写出「宿主今日无应用 feed」 |
| `apes:profile` | 会话标题、运行/空闲、cwd | `byId[scope.sessionId]` | 「无 cwd」提示 |
**实现要点**:两个来源被归一化成同一种行形状 `{ id, label, mode }`,**活动态统一从 `byId` 读**,
于是「投影已就绪」与「仅有 durable 摘要」两种情况下渲染一致;这也是上游 0.1.6→0.1.7 会话 feed
迁移(`subagentsByParent` → `projectionsBySession`、`list.current` 删除)后的落地方式。
## 三、文件预览器
| id | 适用 | 说明 |
| --- | --- | --- |
| `image` | 图片 | 走媒体路由,受 `mediaLimit` 限制 |
| `pdf` | PDF | 内嵌查看 |
| `markdown` | Markdown | 渲染(Mermaid 走懒加载 chunk,可手动刷新) |
| `html` | HTML | **不透明源沙箱 iframe**,见下节 |
| `code` | 代码 | 高亮查看(编辑器懒加载) |
| `binary-download` | 二进制 | 只提供下载 |
### 3.1 HTML 预览的交付方式(重要)
```
路由 /sidebar/html/<session>/<path> ← 路径围栏 + 体积上限(readLimit)
│
├─ 主框架 fetch(唯一同时被宿主围栏信任、又能带上 Electron 头的角色)
│
├─ 注入 <base href="<该文件的路由目录>"> ← 让 ./style.css 等相对引用解析回本文件所在目录
│
└─ iframe srcDoc=<文档> + sandbox=HTML_IFRAME_SANDBOX
```
| 状态 | 自包含 HTML | 多文件 HTML(外链 CSS/JS/图片) |
| --- | --- | --- |
| 沙箱态(默认) | ✅ 完整可用 | 正文可用,**相对资源不可用**(iframe 源为 `null`,子请求同样被宿主围栏拒绝) |
| 临时解锁(无 sandbox 属性) | ✅ | ✅ **相对资源可用**(iframe 继承 GUI 源,成为普通同源请求) |
设计取舍:**沙箱令牌一个字符都不放宽**。多文件 HTML 想要完整效果需用户显式点「临时解锁」——
这是把「放宽边界」的决定权留给用户的显式动作,而不是插件默认降级。
## 四、安全边界(三层)
| 层 | 机制 | 说明 |
| --- | --- | --- |
| 预览内容 | `sandbox="allow-scripts allow-popups allow-downloads allow-modals"` | **不含 `allow-same-origin`**:预览文档运行在不透明源里。宿主路由另有 CSP `sandbox` 兜底 |
| 插件路由 | 请求信任围栏(`src/trust-fence.ts`) | 校验 Host 头/令牌,拒绝跨站或非授权来源对 `/sidebar/*` 的访问 |
| 桌面壳 | Electron 注入的 `x-dsh-desktop-renderer` 头 | 扩展模式下所有路由的前置围栏;只发给主框架与同源子框架。这正是「沙箱 iframe 发不出请求」的根源 |
## 五、i18n 与主题
- **语言包**:`src/client/locales.ts` + 19 个 `src/client/locales-*.ts` 覆盖文件;默认 `zh`,缺省回退 `en`。
- **本 fork 文案**:集中在 `src/client/apes/locales.ts`,每个 key 同时提供 `zh` / `en`。
- **硬性约定**:界面里**不得出现裸字符串**,一律 `t('key')`。
- **配色**:只用宿主设计令牌 `--dsw-alias-*`(bg / border / label / state / interactive 系列),
写法为 `var(--dsw-alias-xxx, <字面回退>)`,保证在非 DSH 环境(单元测试)也能渲染。
**无公开的 `--ds-*` 几何令牌**,字号/间距/圆角内联即可。
## 六、配置面(挂载行 `config`)
配置写在插件挂载行上(profile 的 `cordis.patch.yml` 或 bundle patch 的 insert 行),设置页写的也是它:
| 字段 | 默认 | 含义 |
| --- | --- | --- |
| `readLimit` | `512 KiB` | 单文件读取上限(超出截断并标记 `truncated`) |
| `mediaLimit` | `20 MiB` | 媒体(图片等)读取上限 |
| `uploadLimit` | `128 MiB` | 上传上限 |
| `listLimit` | `1000` | 目录列举条目上限 |
| `terminalsPerSession` | `3` | 每会话终端数上限 |
| `shell` / `shellArgs` | 自动解析 | 终端 shell 与其启动参数(`shellArgs` 非空时替换默认 `-l`) |
例:在挂载行里指定 shell
```yaml
- insert:
- id: better-sidebar
name: 'dsh-apes-sidebar'
config:
shell: /bin/zsh
shellArgs: ['--noprofile', '--no-rc']
```
+163
View File
@@ -0,0 +1,163 @@
# 04 · 部署指南
> 目标:在一台机器上把本插件挂进 DSH,并确认它**真的在跑**。
> 关键前提:**先确认宿主版本,再选版本线**——选错线的唯一症状是「静默失效」。
## 一、前置条件
| 项 | 要求 |
| --- | --- |
| Node | ≥ 20(`engines.node`);本机验证用 22.x |
| pnpm | 11.x(`pnpm install` 会自动触发 `prepare: tsdown` 构建) |
| DSH | 见下面的版本矩阵 |
| 终端可用性 | 仅 v0.19.1 线需要额外放行 `node-pty` 原生构建(见 §五) |
### 1.1 宿主版本矩阵(先读这张表)
| 版本线 | 分支 | 插件版本 | peer 声明 | 适用宿主 |
| --- | --- | --- | --- | --- |
| 旧宿主线 | `main` | 1.2.0 | `^0.1.5-rc.1` | DSH `0.1.5-rc.1+`(含 `0.1.5-rc.2`,即 **DSH Desktop 2.0.10** 内置版本) |
| 新宿主线 | `sync/v0.24.1` | 1.3.0 | `^0.2.0-rc.1` | DSH `0.2.0-rc.1+` |
**怎么查宿主版本**(扩展模式桌面版):
```bash
# 内置的 dsh 版本
grep -m1 '"version"' "/Applications/DSH Desktop.app/Contents/Resources/app/node_modules/@deepseek-ai/dsh/package.json"
# 或看 profile 里装的宿主包
grep -m1 -A2 '"@deepseek-ai/dsh"' ~/.dsh/profiles/desktop/package.json
```
**选线决策**
```
宿主 ≥ 0.2.0-rc.1 ? ── 是 ──▶ sync/v0.24.1(1.3.0)
└─ 否 ──▶ main(1.2.0)
```
> ⚠️ **peer 不匹配不会报错。** 宿主以 `semver.satisfies(host, peerRange, { includePrerelease: true })` 预检,
> caret 跨 minor 判 false 后**整行静默禁用**:不加载、无日志、界面上「像没装」。
> 因此「装完没反应」的第一件事永远是核对宿主版本 ↔ peer 范围。
## 二、安装方式(本 fork 未发布 npm 包)
`npm view dsh-apes-sidebar` 返回 404 —— **本 fork 不在 npm 上**(README 里的 npm 徽章/命令是沿用上游文档)。
实际部署使用「本地源码 + `file:` 链接依赖」:
### 步骤 1:构建
```bash
cd <插件仓库绝对路径>
git checkout main # 或 sync/v0.24.1(按宿主版本选)
pnpm install # 会顺带跑 prepare: tsdown
pnpm typecheck && pnpm build && pnpm test # 门禁三连
```
产物(`lib/`):`index.js` / `invariant.js`(host)+ `client.js` + 懒加载 chunk
(`client-registry` / `client-editor` / `client-locale` / `client-mermaid` / `client-terminal`)+ `types/`。
### 步骤 2:接进 profile
编辑 `~/.dsh/profiles/<profile>/package.json`(桌面版为 `desktop`,网页版通常为 `web`):
```jsonc
{
"dependencies": {
"dsh-apes-sidebar": "file:/绝对路径/dsh-apes-sidebar" // ← 必须是绝对路径
},
"dsh": {
"profile": {
"bundles": [
"…",
"dsh-apes-sidebar" // ← 加入 bundle 栈
]
}
}
}
```
> 也可以在 profile 自己的 `cordis.patch.yml` 里手写挂载行(老方式)。**两种方式不要同时用**:
> 会双挂载成两个 Node 半、两个侧边栏。包内自带的 `cordis.patch.yml` 是 `dsh.bundle.patch` 层,
> 由官方 CLI 安装时自动追加 bundle,并带「聚合包双挂载退让」守卫。
### 步骤 3:让 profile 重新安装依赖
```bash
cd ~/.dsh/profiles/<profile> && pnpm install
```
**为什么必须重装**:pnpm 对 `file:` 依赖是**复制**语义(不是软链)。源码 `pnpm build` 之后,
profile 的 `node_modules/dsh-apes-sidebar/lib` **仍是旧副本**——这就是「改了代码但界面没变」的头号原因。
DSH 启动时也会 reconcile profile 的 pnpm workspace,但不要假定它一定帮你刷新。
### 步骤 4:完整重启
**⌘Q 完全退出 DSH,再重新启动**(不是关窗口)。client 半有热加载,但 host 半与依赖变更必须走完整重启。
## 三、验收清单
重启后逐条核对(每条都对应一个可观察事实):
- [ ] **右列只有一套侧边栏**、一个开合控件(不再出现自绘的第二套开关)
- [ ] 原生 guide 里能看到本插件的 tab 芯片,且**带图标**
- [ ] 三个信息层 tab 存在:**智能体 / 应用 / 画像**(order 在原生面板之后)
- [ ] `apes:agents` 打开后:有主代理卡片;有子代理时列出直系子代理(标签 + 模式 + 运行态)
- [ ] 打开一个 HTML 文件:**沙箱态下自包含 HTML 能正常渲染**(不再是 `forbidden`)
- [ ] 打开「社区插件市场」:弹窗**完整盖在**本插件面板之上(面板不切弹窗角)
- [ ] 页面左下角**没有**红色诊断条(有的话说明 client 生命周期某处抛错)
- [ ] 开发者控制台**没有** `[dsh-apes-sidebar] … error` 前缀的错误
## 四、网页 profile 与桌面 profile 的差异
| 项 | 桌面版(DSH Desktop) | 网页版 |
| --- | --- | --- |
| profile 目录 | `~/.dsh/profiles/desktop` | 通常 `~/.dsh/profiles/web` |
| 外壳 | Electron 壳,有「扩展模式/兼容模式」与请求围栏 | 浏览器直连宿主端口 |
| 预览 iframe | 受渲染器围栏约束(因此必须走 `srcDoc` 交付) | 无该围栏,但仍受路由 CSP/沙箱约束 |
| 面板宿主层级 | 壳层会给 `#root` 打 content-viewport 标记 → 宿主挂进 `#root` | 无标记 → 宿主挂 `document.body` |
| 刷新方式 | ⌘Q 完整退出再启动 | 硬刷新(Cmd/Ctrl+Shift+R) |
## 五、v0.19.1 线特有:放行 node-pty 原生构建
`main`(上游 v0.19.1 基线)在 `package.json` 中仍有 `node-pty` 依赖;pnpm 11 默认**拦截依赖的构建脚本**,
于是首次安装会失败或终端不可用:
```bash
cd ~/.dsh/profiles/<profile>
pnpm approve-builds # 选中 node-pty,回车确认
pnpm rebuild node-pty # 必要时
```
`sync/v0.24.1`(1.3.0)**不含** `node-pty`(终端交由宿主内置承担),无需此步。
(`main` 与 `sync/v0.24.1` 的 `node-pty` 引用数:1 / 0。)
## 六、切换与卸载
同一条线只能装一个(两条线都注册 `ctx.betterSidebar`,entry id 冲突,第二个被丢弃)。
仓库自带切换脚本:
```bash
bash scripts/switch.sh --status # 只看当前是哪条线
bash scripts/switch.sh aps # 切到本 fork 线(本地 file: 安装)
bash scripts/switch.sh upstream # 切回上游 dsh-better-sidebar
bash scripts/switch.sh upstream 0.18.0 # 指定上游版本
bash scripts/switch.sh aps --profile web # 指定 profile
```
**完全卸载**:从 profile 的 `dependencies` 与 `dsh.profile.bundles` 中移除 `dsh-apes-sidebar`,
`pnpm install`,⌘Q 完整重启。若曾手写过 `cordis.patch.yml` 挂载行,一并删除(否则会留下悬空 entry)。
> 若同时装了聚合包(如 `@linxin666/dsh-web-ui-all`)且它内部也挂载了本包,
> 包内 bundle patch 的守卫会让**本行退让**;此时请确认聚合包在 bundle 栈中位于本行之前。
## 七、变更后的重新部署
```bash
# 插件仓库
pnpm typecheck && pnpm build && pnpm test
# profile
cd ~/.dsh/profiles/<profile> && pnpm install
# ⌘Q 退出 DSH 再启动
```
改完代码**必须**走完这三步:产物构建非字节确定性,profile 的 `file:` 副本也不会自动跟随。
+133
View File
@@ -0,0 +1,133 @@
# 05 · 运维手册
> 本篇是**日常运维、升级与回滚**的操作手册。所有命令都以「先留锚点、再动手、改完回读验证」为原则。
## 一、版本线与分支策略(当前远端实况)
| ref | 指向 | 含义 / 用途 |
| --- | --- | --- |
| `main` | `4422001` | **旧宿主线**:插件 1.2.0,上游基线 v0.19.1,peer `^0.1.5-rc.1`。本机 DSH Desktop 2.0.10 正在跑这条 |
| `sync/v0.24.1` | `e5bbef1` | **新宿主线**:插件 1.3.0,上游基线 v0.24.1,peer `^0.2.0-rc.1`。待 ≥0.2.0 宿主实机验证 |
| `backup/pre-sync-v0.19.1` | `96cc334` | v0.19.1 re-base **之前**的状态(旧 fork 三角色 tab 版) |
| `backup/pre-sync-v0.24.1` | `4422001` | v0.24.1 re-base 之前的 `main`(等价于当前 main,作为下次升级的锚点) |
| `tags/apes-v1.1.0-pre-sync` | `96cc334` | 与上面第一支 backup 同一提交,tag 形式的锚点 |
| `tags/apes-v1.2.0` | `4422001` | 当前已验收的 1.2.0 线 |
**纪律**:任何 re-base / 大改之前,先建 `backup/pre-sync-<新基线>` 分支 + `apes-v<版本>` tag,
并**推送**到远端(本地锚点在多人/多机场景等于不存在)。
## 二、回滚剧本
### 场景 A:某次提交引入回归(单点回退)
```bash
cd <插件仓库>
git log --oneline -5 # 定位可疑提交
git revert <sha> # 保留历史的回退(推荐)
# 或整体丢弃:git reset --hard <上一提交>
pnpm typecheck && pnpm build && pnpm test
cd ~/.dsh/profiles/<profile> && pnpm install # file: 是复制语义,必须重装
# ⌘Q 完全退出 DSH → 重启
```
### 场景 B:整条线回退(例如新线在真机上不可用)
```bash
git checkout main # 或 backup/pre-sync-v0.24.1
pnpm install && pnpm build
cd ~/.dsh/profiles/<profile> && pnpm install
# ⌘Q 重启
```
### 场景 C:profile 依赖/config 被改坏
```bash
cd ~/.dsh/profiles/<profile>
cp package.json package.json.bak.$(date +%s) # 先备份
bash <插件仓库>/scripts/switch.sh aps # 或 upstream,脚本会重写依赖与 bundle 行
pnpm install
# ⌘Q 重启
```
**回滚验收**:重启后按 [04-部署指南](04-部署指南) §三 的验收清单逐条核对(至少「右列一套侧边栏」
与「三个信息层 tab 存在」两条)。
## 三、发布 / 变更纪律
1. **版本三元组必须一致**:
- `package.json` 的 `version`
- `dsh.plugin.json` 的 `version`
- `src/client/service.ts` 的 `SIDEBAR_SERVICE_VERSION`
三者不一致会被单元测试(`tests/service.spec.ts`)直接拦下——**不要改其中一个而忘了另一个**。
2. **门禁三连必过**:`pnpm typecheck && pnpm build && pnpm test`。当前基线:`main` 131 个测试文件 /
1367 通过;`sync/v0.24.1` 141 个文件 / 1515 通过(另有 9 个 skip)。
3. **产物非字节确定性**:两次构建的字节数可能相同但 **sha 不同**。因此「改完代码 → 让 profile 用旧副本」
永远不成立;必须重新 `pnpm build` + 让 profile 重新安装。
4. **发布方式**:本 fork 未发布 npm 包,发布 = 推分支/tag + 目标机器 `git fetch && checkout && pnpm build`
+ profile 重装。发布前把 `README.md` 与 `docs/wiki/` 一并更新(wiki 见 §六)。
5. **不要顺手改无关文件**:re-base 时保持「只带自己的增量」,避免下次升级冲突面扩大。
## 四、日常巡检清单
| 频率 | 检查项 | 命令 / 判据 |
| --- | --- | --- |
| 每次部署后 | 版本三元组一致 | 见 §三.1 |
| 每次部署后 | 产物标记齐全 | `grep -c 'apes:agents' lib/client.js`(>0)、`grep -c 'dsh-desktop-content-viewport' lib/client.js`(>0) |
| 每次部署后 | profile 副本已刷新 | 比对 `lib/client.js` 与 `~/.dsh/profiles/<p>/node_modules/dsh-apes-sidebar/lib/client.js` 的 sha256 |
| 每次升级宿主/插件 | 宿主版本 ↔ peer 范围 | 见 [04-部署指南](04-部署指南) §1.1(不匹配 = 静默禁用) |
| 每月/大改前 | 锚点是否已推远端 | `git ls-remote <remote> \| grep -E 'heads/(main\|sync)\|tags/apes'` |
| 异常时 | 诊断条与控制台 | 页面左下角红条;控制台 `[dsh-apes-sidebar]` 前缀 |
## 五、备份与配置铁律
- **改任何 profile 配置前先备份**,改完**回读验证**(不要假定写入即生效):
```bash
cp ~/.dsh/profiles/<profile>/package.json{,.bak.$(date +%s)}
# 改完:
python3 -c "import json;json.load(open('$HOME/.dsh/profiles/<profile>/package.json'));print('JSON OK')"
```
- **DSH 应用配置的类型陷阱**:部分字段(如材质/透明度类的 `macosMaterial`)必须是**字符串**
(`"off"` / `"transparent"`)。若被写成 YAML 布尔(`false`),DSH 会启动进**恢复模式**。
改完务必回读校验类型:
```bash
python3 -c "import yaml;d=yaml.safe_load(open('$HOME/.dsh/settings.yaml'));v=d.get('macosMaterial');print(v, type(v).__name__)"
```
- **不要回滚/手工编辑** profile 的 `pnpm-lock.yaml`:由 pnpm 重建;手工改会导致解析错乱。
- **凭据不进仓库**:远端推送用系统凭据助手(`git credential fill`),仓库内不写 token。
## 六、百科(wiki)本身的维护
百科源文件版本化在仓库 `docs/wiki/`,通过脚本推送到 Gitea wiki:
```bash
bash scripts/push-wiki.sh # 推送/更新全部页面(幂等:有则 PATCH,无则 POST)
bash scripts/push-wiki.sh 05-运维手册 # 只推一页
```
凭据来自 `git credential fill`(与推送代码同一套,不落盘)。修改文档的推荐流程:
**改 `docs/wiki/*.md` → 提交到仓库 → 跑脚本推送**,这样 wiki 与代码同源可回溯。
## 七、诊断与日志
| 信号 | 含义 | 位置 |
| --- | --- | --- |
| 左下角红色诊断条 `[dsh-apes-sidebar] <phase> error: …` | client 生命周期某阶段抛错(mount / native / 懒加载等) | 页面左下角(`z-index: 2147483000`),同时 `console.error` 打前缀 |
| 控制台 `[dsh-apes-sidebar] …` | 同上,且带上具体阶段名 | 开发者工具 Console |
| 「插件像没装」且**无任何日志** | 宿主兼容性预检失败(peer 不匹配)→ **静默禁用** | 只能靠版本矩阵排查,见 [06-故障排查](06-故障排查) §1 |
| 界面正常但面板层级错乱 | 面板宿主挂载点/层叠上下文问题 | 见 [02-系统架构](02-系统架构) §四 |
## 八、性能与容量
| 项 | 观察值 | 说明 |
| --- | --- | --- |
| `lib/client.js` | ≈ 0.9 MB | 主 bundle,始终加载 |
| `lib/client-mermaid.js` | ≈ 7 MB(+12 MB map) | 懒加载,仅在需要渲染 Mermaid 时拉取 |
| `lib/client-editor.js` | ≈ 2 MB | 懒加载,仅打开编辑器时 |
| `lib/client-terminal.js` | ≈ 0.5 MB | 懒加载,仅打开终端时 |
| 全量测试 | 约 3–4 分钟 | 变更后按需跑全量,日常可只跑相关 spec |
主 bundle 不应静态引入上述重型库;新增重型依赖一律走 `src/client/chunks/`(懒加载 chunk)。
+208
View File
@@ -0,0 +1,208 @@
# 06 · 故障排查
> 每条都给出**症状 → 根因(机制)→ 处置 → 验证**。标注 ✅ 的条目是在真机上定位并验证过的案例。
## 速查表
| 症状 | 最可能根因 | 跳到 |
| --- | --- | --- |
| 装完「像没装」,界面无变化且**无任何日志** | peer 范围与宿主版本不匹配 → 静默禁用 | §1 |
| 页面出现**两个侧边栏** / 两套开合按钮 | 双挂载(两条线同装,或残留挂载行) | §2 |
| HTML 预览只显示 `forbidden` | 桌面壳渲染器围栏 + 不透明源 iframe | §3 |
| 插件面板盖住宿主/第三方弹窗(如插件市场) | 面板宿主挂在层叠上下文之外 | §4 |
| 终端打不开 / `node-pty` 加载失败 | pnpm 拦截原生构建(仅 v0.19.1 线) | §5 |
| 改了代码,界面没变 | 产物未重建 / profile 副本未刷新 / 浏览器缓存 | §6 |
| DSH 拒绝加载插件、entry pending | 插件声明(`dsh.plugin.json` / bundle patch)不合法 | §7 |
| DSH 启动进**恢复模式** | profile 配置被写成错误类型(如 YAML 布尔) | §8 |
---
## §1 装完「像没装」:静默禁用 ✅
**症状**:profile 里依赖装了、文件也在,但界面没有任何变化;日志里也没有插件相关报错。
**根因**:宿主启动时做兼容性预检,判定 `peerDependencies` 与自身版本是否满足:
```
semver.satisfies(hostVersion, peerRange, { includePrerelease: true })
```
caret 范围**跨 minor 必然为 false**(例如 `^0.1.5-rc.1` 对 `0.2.0`)。判定失败后宿主的处理是
**跳过这一行**——不抛错、不打日志、不加载。
**处置**:核对宿主版本,改用匹配的版本线(见 [04-部署指南](04-部署指南) §1.1)。
```bash
grep -m1 '"version"' "/Applications/DSH Desktop.app/Contents/Resources/app/node_modules/@deepseek-ai/dsh/package.json"
grep -n 'dsh-client-ui-sidebar-right' <插件仓库>/package.json # 看 peer 范围
```
**验证**:重启后右列出现插件 tab;`lib/client.js` 里能 grep 到 `apes:agents`。
**教训**:这一条没有日志可查,因此**部署前先看版本矩阵**是唯一可靠的预防手段。
## §2 两个侧边栏 / 两套开合按钮 ✅
**症状**:窗口里出现两套右侧面板与两组开合控件。
**根因**:两个方向都可能:
1. **旧版遗留**:v0.18.0 基线时插件自绘右侧面板与开关(`toggleCluster`)。升级后自绘部分应已移除。
2. **双挂载**:同一条线被挂两次(profile 自己的 `cordis.patch.yml` 里还留着旧挂载行,
与 bundle patch 同时生效),或者本 fork 线与上游线同时安装(两者都注册 `ctx.betterSidebar`,
entry id 冲突,第二个被丢弃)。
**处置**:
```bash
# 确认产物里已经没有自绘开关
grep -c toggleCluster <插件仓库>/lib/client.js # 期望 0
# 检查是否双挂载:profile 里是否同时存在 bundle 行 与 手写挂载行
grep -n "dsh-apes-sidebar\|dsh-better-sidebar" ~/.dsh/profiles/<profile>/cordis.patch.yml ~/.dsh/profiles/<profile>/package.json
```
- 有残留手写挂载行 → 删除它,只保留一条通道。
- 两条线同装 → `bash scripts/switch.sh aps`(或 `upstream`)二选一。
- 装了聚合包(如 `@linxin666/dsh-web-ui-all`)→ 确认聚合包在本包之前,让包内守卫生效。
**验证**:右列只剩一套面板与一个开合控件。
## §3 HTML 预览显示 `forbidden` ✅
**症状**:预览 HTML 文件时 iframe 里只有一行裸文本 `forbidden`;而「临时解锁」后又能看。
**根因(三层)**:
1. DSH Desktop 在**扩展模式**下给所有路由套了一层请求围栏:`ordinaryBrowserEnabled` 恒为 false,
不带 Electron 注入的 `x-dsh-desktop-renderer` 头的请求一律 `403 + "forbidden"`;
2. 该头只注入给 **mainFrame** 与**同源**子框架;
3. 预览 iframe 的 sandbox 令牌不含 `allow-same-origin`,请求时其源为 `null` → 拿不到头 → 403。
与「是否升级了插件基线」无关(v0.18.0 起行为一致)。
**处置**:插件已在 1.2.0+ 用 **`srcDoc` 交付**修复(主框架取文档 → 注入 `<base>` → 注入 iframe),
`main` 上对应提交 `4c242ac`。**不要**通过给 iframe 加 `allow-same-origin` 来"修"——那是安全降级。
**验证**:
- 沙箱态下自包含 HTML(含内联脚本)能正常渲染;
- 有外链资源的 HTML 在沙箱态下缺资源属**已知限制**,点「临时解锁」后应完整显示。
**排查命令**(确认宿主围栏在起作用):
```bash
lsof -nP -iTCP -sTCP:LISTEN | grep -i dsh # 找宿主端口(每次启动可能不同)
curl -s -o /dev/null -w '%{http_code}\n' "http://127.0.0.1:<端口>/" # 无渲染器头 → 403
```
## §4 插件面板盖住宿主/第三方弹窗 ✅
**症状**:打开「社区插件市场」(宿主自带 `dsh-community-market`)时,弹窗左下角被本插件的
文件面板整齐切掉一块。
**根因**:桌面壳的有框模式会给上游 `#root` 打标 `data-dsh-desktop-content-viewport` 并施加
`isolation: isolate`,使 `#root` 成为**层叠上下文**;宿主的 overlay 层(`.dshDesktopOverlay`,
`z-index: 1000`)位于其中。插件面板宿主若挂在 `document.body`(该上下文之外)且是定位元素,
就会画在整个上下文之上,把 overlay 里的弹窗盖住。
**处置**:已修(`4422001`)——面板宿主改为挂进 `#root[data-dsh-desktop-content-viewport]`,
无标记时回退 `document.body`。**不要**通过调大 `z-index` 解决(25 的取义见
[02-系统架构](02-系统架构) §四;乱调会破坏与宿主其他层级的关系)。
**验证**:插件市场弹窗完整盖住本插件面板,同时本插件面板仍在对话内容之上。
## §5 终端不可用 / `node-pty` 加载失败(仅 v0.19.1 线)✅
**症状**:终端面板报 shell 启动失败或提示 `node-pty` 加载失败;`pnpm install` 阶段报
"ignored build scripts"。
**根因**:pnpm 11 默认**拦截依赖的构建脚本**,`node-pty` 的原生二进制没被编译。
(`main` 依赖 `node-pty`;`sync/v0.24.1` 已移除该依赖,终端由宿主内置承担。)
**处置**:
```bash
cd ~/.dsh/profiles/<profile>
pnpm approve-builds # 选中 node-pty
pnpm rebuild node-pty # 必要时
# ⌘Q 完整退出 DSH 再启动
```
**验证**:终端能开出 shell;`node_modules/node-pty/build/Release/*.node` 存在。
## §6 改了代码,界面没变 ✅
**症状**:源码已改、`pnpm build` 也跑了,但界面行为仍是旧的。
**根因(三个叠加因素)**:
1. 产物**非字节确定性**,且 profile 的 `file:` 依赖是**复制**语义 → profile 里仍是旧副本;
2. `lib/client.js` 是浏览器的 `window.__ModuleLoader__.load({...})` bundle,**不是 CJS/ESM**,
在 Node 里 `require` 会因 `window is not defined` 报错——因此不能用「在 Node 里试跑」来验证前端改动;
3. host 半的改动必须重启进程(client 半有热加载,host 半没有)。
**处置**:
```bash
cd <插件仓库> && pnpm build
cd ~/.dsh/profiles/<profile> && pnpm install # 刷新 file: 副本
# ⌘Q 完整退出 DSH → 重启
```
**验证**:比对两边产物 sha:
```bash
shasum -a 256 <插件仓库>/lib/client.js \
~/.dsh/profiles/<profile>/node_modules/dsh-apes-sidebar/lib/client.js
```
## §7 DSH 拒绝加载插件 / entry pending
**症状**:插件目录存在,但宿主不加载,或 loader 报 entry pending / 恢复模式。
**根因**:插件的声明面不合法或不被接受,常见于:
- `dsh.plugin.json` 的 `engines.dsh` 与宿主不符;
- bundle patch / 挂载行的 `insert` 结构不合法(例如把声明写成宿主无法解析的形态);
- 同 id 重复挂载(`duplicate loader entry id` / `duplicate prefix route`)。
**处置**:
```bash
cat <插件仓库>/dsh.plugin.json # 检查 id / engines / main
cat <插件仓库>/cordis.patch.yml # 检查 insert 结构与守卫表达式
grep -n "cordis.patch\|dsh-apes-sidebar" ~/.dsh/profiles/<profile>/package.json
```
确保只有一条挂载通道、`name` 与实际包名一致(本 fork 为 `dsh-apes-sidebar`),
`engines.dsh` 与宿主版本相容。改完 `pnpm install` + **完整重启**。
**验证**:启动日志无 loader 报错;右列出现插件 tab。
## §8 DSH 启动进「恢复模式」
**症状**:DSH 启动后进入恢复/安全模式,插件与侧边栏都不正常。
**根因**:profile 或应用配置被写成**错误类型**。典型是材质/透明度字段要求**字符串**
(`"off"` / `"transparent"`),但被 YAML 序列化成了布尔 `false` —— 宿主按类型校验失败即进恢复模式。
**处置与验证**:
```bash
python3 -c "import yaml;d=yaml.safe_load(open('$HOME/.dsh/settings.yaml'));v=d.get('macosMaterial');print(repr(v), type(v).__name__)"
# 期望:'off' str(或 'transparent' str);若为 False bool → 改回字符串
```
改配置前**先备份**,改完**回读校验类型**(见 [05-运维手册](05-运维手册) §五)。
---
## 附:定位手法(本次会话验证有效)
1. **先按用户可见文案 grep 找归属**:`grep -rl "<界面上的中文短语>" <app>/lib <profile>/node_modules`。
若两边都搜不到,说明它来自**运行时下载/缓存**(例如市场插件),再按 `NSUserData` 缓存或
`desktop-market/state.json` 之类线索去找提供方。
2. **再读提供方自己的 CSS/DOM 结构**:拿到它声明的 z-index、定位方式与所在槽位
(如 `renderSlot("shell.overlay")`),据此判断是「它太高」还是「我们太高」。
3. **层叠问题先找 `isolation` / `transform` / `filter` / `contain`**:这些才是层叠上下文与包含块的真正来源,
比 z-index 数值更关键。
4. **像素证据要用原图量**:交给视觉模型的截图会被缩放(1920→1280),裁剪坐标 ≠ 屏幕坐标;
用 PIL 在原图上量边界、比对 RGB 剖面,再下结论。
+164
View File
@@ -0,0 +1,164 @@
# 07 · 开发与测试
> 面向改代码的人(与 agent)。**先读 [02-系统架构](02-系统架构) §六「关键不变量」**,
> 再动手改,能省掉大部分回退。
## 一、工程约定
| 项 | 约定 |
| --- | --- |
| 语言 | TypeScript **strict**(含 `noUncheckedIndexedAccess`)、ESM、导入写 `.ts` 扩展名 |
| 构建 | `tsdown`(rolldown),host/client 双产物;懒加载 chunk 走 `src/client/chunks/` |
| 包管理 | `pnpm`(11.x);`pnpm install` 会触发 `prepare` 构建 |
| 测试 | `vitest`;默认 node 环境,需要 DOM 的 spec 用 `// @vitest-environment jsdom` |
| 宿主 peer | `dsh-better-sidebar`(本仓库已改名)**永不打包**,必须留在 peer/neverBundle 一侧 |
| 命名 | 信息层 tab id 前缀 `apes:`;插件名 `dsh-apes-sidebar` |
| 颜色 | 只用 `--dsw-alias-*` 设计令牌(`var(--token, <回退>)`),不写死色值 |
## 二、目录结构(`main` 实况)
```
src/
index.ts HOST 入口:name / inject / apply(ctx, config),路由与围栏装配
invariant.ts 零依赖不变量面(host/client 共用)
context-types.ts cordis 服务面契约(ctx.sessions / sidebarRight / …)
trust-fence.ts 请求信任围栏(Host 头 / 令牌)
html-route.ts /sidebar/html 路由(路径围栏 + 体积上限)
config.ts 挂载行配置 schema 与默认值(readLimit / mediaLimit / …)
fs-*.ts git.ts … 文件系统、Git、会话、任务、终端等 host 能力
client/
index.tsx CLIENT 入口(apply):装配 store/service/native/apes
service.ts ctx.betterSidebar 服务实现(对外 API + 能力清单)
state.ts 侧边栏状态存储(store)
native/ 原生右侧栏适配(tab 记录、tab 体、tab 芯片标题)
builtins/ 内置面板:editor / git / subagent / sidechat / terminal / browser + 预览器
sidebar/ 自绘底部工作台与开合控件
apes/ 信息层三 tab(本 fork 增量:tabs.tsx / icons.tsx / locales.ts)
changes/ diff/ 文件变动与 diff 视图
chunks/ 懒加载 chunk:editor / locale / mermaid / terminal
locales*.ts i18n(`locales.ts` + 19 个语言覆盖文件)
*.module.css 组件样式(含 sidebar.module.css:面板宿主层契约)
tests/ vitest specs(131 个文件,含 e2e/ 的挂载脚本型用例)
scripts/ install / switch / e2e 挂载 / 发布辅助
docs/ external-plugin-guide.md(对外契约)+ plans/(上游历史设计)+ wiki/(本百科源文件)
```
## 三、门禁(提交前必跑)
```bash
pnpm typecheck # tsc --noEmit(strict)
pnpm build # rm -rf lib && tsc(d.ts) && tsdown
pnpm test # vitest run
```
当前基线(本次会话实测):`main` = 131 文件 / **1367 通过** + 9 skip;
`sync/v0.24.1` = 141 文件 / **1515 通过** + 9 skip。全量 test 约 3–4 分钟。
**任何一条不过就不要提交**;三条全绿才算完成。
## 四、测试体系(按层次)
| 层次 | 环境 | 手法 | 代表 spec |
| --- | --- | --- | --- |
| 宿主插件形状 / 对外契约 | node | 断言导出面与语义(含与 `package.json` 的版本一致性) | `smoke.spec.ts` · `plugin-shape.spec.ts` · `service.spec.ts` · `consumer-types.ts` |
| 原生侧边栏适配 | node | 用「类型为真、运行时为假」的 mock 注册表 | `native-surface.spec.ts` |
| 面板宿主与层叠契约 | node + jsdom | CSS 断言 + DOM 挂载点解析 | `panel-host-css.spec.ts` · `panel-host-parent.spec.ts` |
| 预览交付 | node + jsdom | 纯逻辑(`<base>` 注入)+ 客户端挂载(stub `fetch`,`createRoot` + `act`) | `html-preview.spec.ts` · `html-preview-frame.spec.tsx` |
| 沙箱契约 | node | 对渲染结果做**字符串断言**(服务端渲染把 `srcDoc` 输出为 camelCase `srcDoc=""`,与客户端设置的真实属性 `srcdoc` 不同 —— 断言方式随之不同) | `sandbox-views.spec.tsx` |
| 信息层 tab | node | mock `ctx.sessions.list` 与投影 | `apes-tabs.spec.ts` |
| 桌面环境探测 | node | stub 全局(URL stamp / preload 标记) | `desktop-env.spec.ts` |
约定:
- **不引入 jsdom 到默认环境**:需要 DOM 的 spec 在文件头写 `// @vitest-environment jsdom`。
- **不依赖真实 DSH 宿主**:需要真宿主的用例归到 `tests/e2e/`(脚本型挂载),不进单元门禁。
- 新增「契约」必须**同时新增 pin 它的 spec**(例如本文档提到的 25 层级、沙箱令牌、`srcDoc` 交付方式,
都有对应断言)。改契约 = 改代码 + 改 spec,两者必须一起提交。
## 五、扩展点(对外服务)
其他插件通过 `ctx.betterSidebar` 接入:
```ts
// 注册一个 tab 类型
await ctx.betterSidebar.registerTab(descriptor)
// 注册一个文件预览器
await ctx.betterSidebar.registerFileViewer(viewer)
```
- 服务按**能力清单**做前向兼容门控(能力只增不减),消费方应据此判断可用性而非版本号比较。
- 完整契约、字段表与示例见仓库内 [docs/external-plugin-guide.md](../blob/main/docs/external-plugin-guide.md)。
- 改服务面时:**只能新增**;删除或改语义需同步更新能力清单、指南与本百科。
## 六、上游同步:测量式 re-base(本项目的方法论)
**不 merge 上游**。上游每个版本差距可达数百文件,直接合并会引入无关变更并放大冲突面;
本项目改用「先测量、再重建、后补增量」:
### 步骤
**① 测出自己的增量(把改名归一化掉)**
```python
# 对每个变更文件:取出两边 blob,把改名归一化后比较
old = git('show', f'{base}:{path}'); new = git('show', f'HEAD:{path}')
rename_only = old.replace('dsh-better-sidebar', 'dsh-apes-sidebar') == new
```
→ 得到三张清单:**仅改名**(自动处理)/ **真改动** / **新增**。数量级参考:v0.19.1→v0.24.1 为
78 / 11 / 10。
**② 确定改名的精确范围(不要重新推断)**
```bash
# 仍保留上游名字的文件 = 有意保留(本仓库为 README.md、package.json、docs/plans/*)
comm -12 <(git grep -l 'dsh-better-sidebar' <旧基线> | cut -d: -f2 | sort) \
<(git grep -l 'dsh-better-sidebar' HEAD | cut -d: -f2 | sort)
```
其余文件只做子串替换 `dsh-better-sidebar` → `dsh-apes-sidebar`;**camelCase 服务名 `betterSidebar` 必须保留**。
**③ 造一个"改名归一化基座",让增量与改名解耦**
```bash
git checkout -B tmp/norm-<旧基线> <旧基线 tag>
# → 套同一口径改名 → commit
git diff tmp/norm-<旧基线> HEAD -- . ':(exclude)pnpm-lock.yaml' > /tmp/apes-delta.patch
# 校验:这条 diff 的文件清单必须恰好等于 ① 的「真改动 + 新增」
```
**④ 在新基线上重建 + 三点合并**
```bash
git checkout -B sync/<新基线 tag> <新基线 tag>
# → 套同一口径改名 → commit("rename only")
git apply -3 /tmp/apes-delta.patch # 冲突会以 <<<<<<< ours / >>>>>>> theirs 留存(命令本身不失败)
```
**⑤ 逐条判读冲突(只有五种形态)**
| 形态 | 判定 |
| --- | --- |
| 版本/身份 JSON | 取**新基线**侧,再改成本 fork 的版本;两侧各自新增的元数据取**并集**(依赖、files 条目、`forkedFrom` 块) |
| 上游搬走了某个 import | 取上游的新路径,保留本 fork 的新 import |
| 上游新环境里的注册块 | 保留本 fork 的块,放进上游的新上下文 |
| 同一函数两边都定义(上游上移/下移了它) | 删除本 fork 那份;用 `grep -c 'const <name>'` 断言只剩 1 处 |
| 长文档(README) | 整体取上游原文,再机械套上本 fork 的两处增量:顶部 fork notice + 包名替换(**跳过** `github.com/<上游 org>` 与徽章域名行) |
**⑥ 迁移检查(API 漂移)+ 门禁**
- 先**解完所有冲突标记再** `pnpm install`(`prepare: tsdown` 会因为语法错误直接失败),
并**删掉 lockfile 重新生成**(不要把旧 lockfile 带过来)。
- 逐条跑 `pnpm typecheck` 修类型漂移。经验:漂移通常集中在**本 fork 自己新增的模块**里。
上游会在 `context-types.ts` 的注释里写清迁移路径(例:0.1.6 的 `subagentsByParent`
→ 0.1.7 的 `projectionsBySession[id].values.subagentCatalog`,`list.current` 被删除),照注释做即可。
- 最后跑门禁三连,并且**用 `grep` 核对关键标记**(tab id、`srcDoc`、`dsh-desktop-content-viewport` 等)。
**⑦ 收尾(纪律)**
- 安全锚点:`backup/pre-sync-<新基线>` 分支 + `apes-v<版本>` tag,**推送远端**。
- **不要把新线同步进正在使用的 profile**:peer 范围可能已跨 minor,同步过去只会「静默失效」,
同时把本机环境弄脏。保持 profile 在已验证的线上。
## 七、提交与分支纪律
- 提交信息用 `type(scope): 摘要`(`feat` / `fix` / `chore` / `docs` / `refactor`),正文说明**为什么**与验证方式。
- 一个提交一件事;re-base 类改动拆成「rename only」+「增量移植」两个提交,便于回溯与回滚。
- 分支命名:`sync/<上游 tag>`(跟随上游)、`backup/pre-sync-<基线>`(锚点)、其余按需。
- 提交前:门禁三连 + 关键标记 grep + (若涉及部署)提醒 profile 重装与完整重启。
+82
View File
@@ -0,0 +1,82 @@
# apes-sidebar 项目百科
> DeepSeek Harness(DSH)的**服务化右侧边栏工作台** + **apes 信息层**。
> 本项目是 [`dsh-better-sidebar`](https://github.com/omdsh-dev/DSH-better-sidebar)(MIT)的 fork,
> 在其「文件 / 文件变动 / 任务 / 侧边对话 / 终端 / 浏览器」工作台之上,追加三个信息层标签页:
> **智能体(agents)/ 应用(apps)/ 画像(profile)**。
## 一、项目速览
| 项 | 值 |
| --- | --- |
| 仓库 | `figmar/apes-sidebar`(公开) |
| 默认分支 | `main` |
| 许可证 | MIT(fork 自 `omdsh-dev/DSH-better-sidebar`) |
| 语言 / 构建 | TypeScript(strict)· `tsdown`(rolldown)· `pnpm` |
| 宿主 | DSH Desktop(Electron 壳)或 DSH Web profile |
| 上游跟随方式 | 不合并上游提交,而是**测量式 re-base**(见 [07-开发与测试](07-开发与测试)) |
## 二、两条版本线(重要)
本仓库同时维护两条互相独立、**不能同时挂载**的版本线(两条线都注册 `ctx.betterSidebar`,
cordis entry id 冲突,第二个会被丢弃):
| 分支 | 版本 | 上游基线 | peer / 宿主要求 | 状态 |
| --- | --- | --- | --- | --- |
| **`main`** | 1.2.0 | `dsh-better-sidebar` **v0.19.1**(`8753096`) | `^0.1.5-rc.1`(DSH **0.1.5-rc.1+**) | 已在本机 DSH Desktop 2.0.10(`dsh 0.1.5-rc.2`)实机运行 |
| **`sync/v0.24.1`** | 1.3.0 | `dsh-better-sidebar` **v0.24.1**(`a2751cf`) | `^0.2.0-rc.1`(DSH **0.2.0-rc.1+**) | 构建与测试全绿,**待具备 0.2.0 宿主实机验证** |
> ⚠️ **宿主的兼容性预检是「静默禁用」,不报错、不加载、无日志。**
> 宿主用 `semver.satisfies(host, peerRange, { includePrerelease: true })` 判定;caret 范围跨 minor
> 不成立(`^0.1.5-rc.1` 在 `0.2.0` 宿主上为 false),于是整行被跳过。
> **现象 = 插件"好像没装"**。选线请先看宿主版本,见 [04-部署指南](04-部署指南)。
## 三、文档地图
| 页面 | 内容 |
| --- | --- |
| [01-需求分析](01-需求分析) | **官方原生 vs 装了本包**:官方右列只有「文件树 + 只读文本预览」(附官方包证据),九个 tab 是一个整体需求;7 条需求变化洞察、U1–U6、已知限制 |
| [02-系统架构](02-系统架构) | host/client 拆分、三层接口面、面板宿主层级契约、关键不变量 |
| [03-功能说明](03-功能说明) | 全部标签页与预览器、信息层数据来源、i18n、主题令牌、安全边界 |
| [04-部署指南](04-部署指南) | 环境矩阵、选线、profile 接线、构建、验收清单、卸载 |
| [05-运维手册](05-运维手册) | 版本线与分支策略、回滚剧本、发布纪律、巡检、备份铁律 |
| [06-故障排查](06-故障排查) | 症状 → 根因 → 处置(含全部实机验证过的案例) |
| [07-开发与测试](07-开发与测试) | 工程约定、目录、门禁、测试体系、扩展点、上游 re-base 方法 |
## 四、快速开始(TL;DR)
```bash
# 1) 构建(Node ≥ 20,pnpm)
pnpm install && pnpm typecheck && pnpm build && pnpm test
# 2) 接到 DSH profile(以桌面 profile 为例,使用 file: 链接依赖)
# 在 ~/.dsh/profiles/desktop/package.json 中:
# "dsh-apes-sidebar": "file:/绝对路径/dsh-apes-sidebar"
# 并在 dsh.profile.bundles 列表里加入 "dsh-apes-sidebar"
# 3) 让 profile 重新安装依赖,然后 ⌘Q 完整退出 DSH 再启动
```
完整步骤与坑见 [04-部署指南](04-部署指南)。
## 五、仓库内的事实源
| 位置 | 说明 |
| --- | --- |
| `README.md` | 面向用户的功能与安装说明(主体沿用上游文档,顶部有 fork notice) |
| `AGENTS.md` | 面向 agent/协作者的工程约定(构建、布局、硬性规则、测试约定)⚠️ 注意:本文件当前描述的是**另一套 from-scratch 实现**,与 `main` 的 fork 树不一致,以源码为准 |
| `docs/external-plugin-guide.md` | 插件接入指南:`ctx.betterSidebar.registerTab / registerFileViewer` 的对外契约 |
| `docs/plans/` | 上游历史设计文档(保持上游原样,未随 fork 改名) |
| `docs/wiki/` | **本百科的源文件**(Gitea wiki 页面的可版本化副本,由 `scripts/push-wiki.sh` 推送) |
| `scripts/switch.sh` | 两线切换脚本(`aps` ↔ `upstream`),含 `--status` |
## 六、谱系说明(如实交代)
`figmar/apes-sidebar` 这个仓库先后承载过同一产品族的三代实现:
1. **v1**:早期 JS bundle 版侧边栏插件(仓库描述里提到的「可配置显示名 / 预设 feyOS」即出自那一代);
2. **v2**:from-scratch 重写(`src/index.ts` + `src/client/index.tsx` 结构,五个 `apes:*` tab),
即 `AGENTS.md` 所描述的那一套,其产物不在当前 `main` 上;
3. **当前线(v0.19.1 / v0.24.1 fork)**:即 `main` 与 `sync/v0.24.1` 上的代码——本百科描述的对象。
若需要把本百科改写成描述 v2 重写版,请告知,源文件在 `docs/wiki/` 可直接替换。
+2 -2
View File
@@ -1,6 +1,6 @@
{
"id": "dsh-external/dsh-better-sidebar",
"version": "0.19.1",
"id": "dsh-external/dsh-apes-sidebar",
"version": "1.2.0",
"main": "./lib/index.js",
"description": "VSCode 风格右侧侧边栏:文件资源管理器 / 编辑器 / 终端 / Git / 浏览器,按会话隔离。暴露服务供其他插件注册侧边栏页面和文件预览器",
"engines": {
+38 -27
View File
@@ -1,7 +1,7 @@
{
"name": "dsh-better-sidebar",
"version": "0.19.1",
"description": "DSH web plugin: a VSCode-like right sidebar (explorer / editor / terminal / git / browser), isolated per conversation session. Exposes a service for other plugins to register sidebar tabs and file viewers.",
"name": "dsh-apes-sidebar",
"version": "1.2.0",
"description": "APE fork of dsh-better-sidebar (base v0.19.1): VSCode-like right sidebar (explorer / editor / terminal / git / browser) isolated per session, plus apes:* information-layer tabs (agents / apps / profile). Forked from omdsh-dev/DSH-better-sidebar under MIT.",
"type": "module",
"repository": {
"type": "git",
@@ -70,7 +70,8 @@
"cordis.patch.yml",
"README.md",
"README_EN.md",
"LICENSE"
"LICENSE",
"scripts/switch.sh"
],
"scripts": {
"build": "node -e \"require('node:fs').rmSync('lib',{recursive:true,force:true})\" && tsc -p tsconfig.build.json && tsdown",
@@ -85,25 +86,29 @@
"test:mount:aggregate": "bash scripts/e2e-aggregate-mount.sh",
"clean": "node -e \"require('node:fs').rmSync('lib',{recursive:true,force:true})\"",
"check:consumer-types": "bash scripts/check-consumer-types.sh",
"lint": "eslint ."
"lint": "eslint .",
"switch": "bash scripts/switch.sh",
"switch:status": "bash scripts/switch.sh --status",
"switch:aps": "bash scripts/switch.sh aps",
"switch:upstream": "bash scripts/switch.sh upstream"
},
"license": "MIT",
"peerDependencies": {
"@deepseek-ai/cordis": "^4.0.2",
"@deepseek-ai/dsh-agent": "^0.1.5-rc.1",
"@deepseek-ai/dsh-client-locale": "^0.1.5-rc.1",
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.1",
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.1",
"@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.1",
"@deepseek-ai/dsh-client-ui-sidebar-right": "^0.1.5-rc.1",
"@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.1",
"@deepseek-ai/dsh-host-webserver": "^0.1.5-rc.1",
"@deepseek-ai/dsh-invariants": "^0.1.5-rc.1",
"@deepseek-ai/dsh-llm": "^0.1.5-rc.1",
"@deepseek-ai/dsh-session": "^0.1.5-rc.1",
"@deepseek-ai/dsh-settings": "^0.1.5-rc.1",
"@deepseek-ai/dsh-subagent": "^0.1.5-rc.1",
"@deepseek-ai/dsh-tools": "^0.1.5-rc.1",
"@deepseek-ai/cordis": "^4.0.4",
"@deepseek-ai/dsh-agent": "^0.2.0-rc.2",
"@deepseek-ai/dsh-client-locale": "^0.2.0-rc.2",
"@deepseek-ai/dsh-client-ui-conversation": "^0.2.0-rc.2",
"@deepseek-ai/dsh-client-ui-primitives": "^0.2.0-rc.2",
"@deepseek-ai/dsh-client-ui-settings": "^0.2.0-rc.2",
"@deepseek-ai/dsh-client-ui-sidebar-right": "^0.2.0-rc.2",
"@deepseek-ai/dsh-client-ui-slots": "^0.2.0-rc.2",
"@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.2",
"@deepseek-ai/dsh-invariants": "^0.2.0-rc.2",
"@deepseek-ai/dsh-llm": "^0.2.0-rc.2",
"@deepseek-ai/dsh-session": "^0.2.0-rc.2",
"@deepseek-ai/dsh-settings": "^0.2.0-rc.2",
"@deepseek-ai/dsh-subagent": "^0.2.0-rc.2",
"@deepseek-ai/dsh-tools": "^0.2.0-rc.2",
"@huanlin/dsh-plugin-better-locale": "^0.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
@@ -173,6 +178,7 @@
"@deepseek-ai/dsh-tools": "0.1.5-rc.2",
"@deepseek-ai/dsh-user-approval": "0.1.5-rc.2",
"@deepseek-ai/dsh-util-time": "0.1.5-rc.2",
"@eslint/js": "^10.0.1",
"@huanlin/dsh-plugin-better-locale": "^0.1.0",
"@playwright/test": "^1.62.1",
"@shikijs/langs": "^4.3.1",
@@ -185,6 +191,9 @@
"@xterm/xterm": "^5.5.0",
"anser": "^2.3.5",
"cordis": "^4.0.0-rc.8",
"eslint": "^10.9.1",
"eslint-plugin-react-hooks": "^7.1.1",
"globals": "^17.12.0",
"jsdom": "^29.1.1",
"katex": "^0.16.47",
"lightningcss": "^1.32.0",
@@ -200,17 +209,19 @@
"micromark-util-sanitize-uri": "^2.0.1",
"micromark-util-symbol": "^2.0.1",
"micromark-util-types": "^2.0.2",
"shiki": "^4.3.1",
"react": "^18.2.0",
"react-dom": "18.2.0",
"shiki": "^4.3.1",
"tsdown": "^0.22.2",
"typescript": "^5.6.0",
"typescript-eslint": "^8.69.0",
"unrun": "^0.2.39",
"vitest": "^4.1.8",
"@eslint/js": "^10.0.1",
"eslint": "^10.9.1",
"eslint-plugin-react-hooks": "^7.1.1",
"globals": "^17.12.0",
"typescript-eslint": "^8.69.0"
"vitest": "^4.1.8"
},
"forkedFrom": {
"name": "dsh-better-sidebar",
"version": "0.19.1",
"commit": "8753096",
"url": "https://github.com/omdsh-dev/DSH-better-sidebar"
}
}
+617 -619
View File
File diff suppressed because it is too large Load Diff
+12 -12
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Consumer-facing type surface check: build a tiny standalone consumer that
# imports `dsh-better-sidebar/client/service` WITHOUT @types/node and with
# imports `dsh-apes-sidebar/client/service` WITHOUT @types/node and with
# skipLibCheck: false, then type-check it with the repo's tsc.
#
# This is the exact scenario the v0.12.0 API work cares about: the shipped
@@ -35,14 +35,14 @@ mkdir -p "$WORK/node_modules"
# (GetFinalPathNameByHandle on Windows) expands junctions, which bash-level
# resolution may leave untouched; node is guaranteed here (tsc runs below).
resolve_dir() { node -e 'process.stdout.write(require("node:fs").realpathSync.native(process.argv[1]))' "$1"; }
ln -s "$(resolve_dir "$(pwd)")" "$WORK/node_modules/dsh-better-sidebar"
ln -s "$(resolve_dir "$(pwd)")" "$WORK/node_modules/dsh-apes-sidebar"
# The vendored cordis base (and, through its realpath, cosmokit / standard-schema)
# must resolve so the consumer can type `Context` against the DSH runtime scope.
mkdir -p "$WORK/node_modules/@deepseek-ai"
ln -s "$(resolve_dir "$(pwd)/node_modules/@deepseek-ai/cordis")" "$WORK/node_modules/@deepseek-ai/cordis"
# Loud guard: both links must resolve to a package root, or tsc below would
# report misleading module-resolution errors.
for pkg_json in "$WORK/node_modules/dsh-better-sidebar/package.json" "$WORK/node_modules/@deepseek-ai/cordis/package.json"; do
for pkg_json in "$WORK/node_modules/dsh-apes-sidebar/package.json" "$WORK/node_modules/@deepseek-ai/cordis/package.json"; do
if [ ! -f "$pkg_json" ]; then
echo "[check-consumer-types] FAIL: $pkg_json does not resolve (broken link). Link layer:" >&2
ls -la "$WORK/node_modules" "$WORK/node_modules/@deepseek-ai" >&2 || true
@@ -51,14 +51,14 @@ for pkg_json in "$WORK/node_modules/dsh-better-sidebar/package.json" "$WORK/node
done
cat > "$WORK/check.ts" <<'EOF'
import { SIDEBAR_FEATURES, SIDEBAR_SERVICE_VERSION } from 'dsh-better-sidebar/client/service'
import type {} from 'dsh-better-sidebar/client/service'
import { SIDEBAR_FEATURES, SIDEBAR_SERVICE_VERSION } from 'dsh-apes-sidebar/client/service'
import type {} from 'dsh-apes-sidebar/client/service'
import type {
BetterSidebarService, FileViewerDescriptor, OpenTabSeed, SidebarSettingsRenderProps,
TabComponentProps, TabDescriptor,
} from 'dsh-better-sidebar/client/service'
import type { SessionScope, SidebarSnapshot, SidebarState, SidebarStore, SidebarTab } from 'dsh-better-sidebar/client/service'
import type { SidebarPrefs } from 'dsh-better-sidebar/client/service'
} from 'dsh-apes-sidebar/client/service'
import type { SessionScope, SidebarSnapshot, SidebarState, SidebarStore, SidebarTab } from 'dsh-apes-sidebar/client/service'
import type { SidebarPrefs } from 'dsh-apes-sidebar/client/service'
// The vendored-cordis path: `Context` from '@deepseek-ai/cordis' plus the
// side-effect type import above must expose `ctx.betterSidebar` — the consumer
// never needs to import this package's own Context type.
@@ -126,14 +126,14 @@ set -e
# Fail only on errors that mention OUR package / declarations / the fixture.
# Upstream noise (vendored cordis / cosmokit declaration warnings under the
# repo's pinned TS lib) lives anywhere under node_modules and is filtered.
if grep -E "dsh-better-sidebar|lib/types|check\.ts" "$WORK/strict.log" | grep -vE "node_modules/[^ ]*(cordis|cosmokit)"; then
echo "[check-consumer-types] FAIL: errors in the dsh-better-sidebar declaration surface:" >&2
grep -E "dsh-better-sidebar|lib/types|check\.ts" "$WORK/strict.log" >&2
if grep -E "dsh-apes-sidebar|lib/types|check\.ts" "$WORK/strict.log" | grep -vE "node_modules/[^ ]*(cordis|cosmokit)"; then
echo "[check-consumer-types] FAIL: errors in the dsh-apes-sidebar declaration surface:" >&2
grep -E "dsh-apes-sidebar|lib/types|check\.ts" "$WORK/strict.log" >&2
exit 1
fi
if [ "$STATUS" -ne 0 ]; then
echo "[check-consumer-types] pass 2 note: strict mode only reports upstream declaration noise:"
grep -v "dsh-better-sidebar\|lib/types\|check\.ts" "$WORK/strict.log" | head -5 || true
grep -v "dsh-apes-sidebar\|lib/types\|check\.ts" "$WORK/strict.log" | head -5 || true
fi
echo "[check-consumer-types] OK: the client/service declaration surface is node-free and self-contained."
+3 -3
View File
@@ -1,9 +1,9 @@
#!/usr/bin/env bash
# =============================================================================
# dsh-better-sidebar 聚合双挂载冒烟(CI + 本地):
# dsh-apes-sidebar 聚合双挂载冒烟(CI + 本地):
#
# 场景:一个聚合 bundle(tests/fixtures/aggregate-better-sidebar)先以独立
# 条目 id 挂载 dsh-better-sidebar,插件自身的 bundle patch 后到 —— 旧行为
# 条目 id 挂载 dsh-apes-sidebar,插件自身的 bundle patch 后到 —— 旧行为
# 两个实例都注册 /sidebar/api,整个插件树启动失败(duplicate prefix
# route);新行为插件行检测到已有启用中的同包名挂载后自动 disabled,由
# 聚合行接管 sidebar。
@@ -22,7 +22,7 @@
# 环境变量(均可省略):
# DSH_CMD dsh 命令;缺省 PATH 上的 `dsh`,回退 npx 拉官方包
# (同 e2e-mount.sh)
# TARBALL 插件 tarball;缺省仓库根 dsh-better-sidebar-*.tgz(须已 pack)
# TARBALL 插件 tarball;缺省仓库根 dsh-apes-sidebar-*.tgz(须已 pack)
# PORT 固定端口(默认 0 = OS 分配,从日志解析 URL)
# DSH_HOME_BASE scratch 根目录(默认系统临时目录)。脚本始终在其下新建
# 本调用拥有的独立子目录,只写入/删除该子目录;调用方提供
+3 -3
View File
@@ -57,9 +57,9 @@ e2e_resolve_dsh_cmd() {
# 过期产物上。未找到候选时返回非零,由调用方按各自文案 die。
e2e_resolve_tarball() {
if [ -z "${TARBALL:-}" ]; then
TARBALL="$(ls -t "$ROOT"/dsh-better-sidebar-*.tgz 2>/dev/null | head -1 || true)"
TARBALL="$(ls -t "$ROOT"/dsh-apes-sidebar-*.tgz 2>/dev/null | head -1 || true)"
local count
count="$(ls "$ROOT"/dsh-better-sidebar-*.tgz 2>/dev/null | wc -l | tr -d ' ' || true)"
count="$(ls "$ROOT"/dsh-apes-sidebar-*.tgz 2>/dev/null | wc -l | tr -d ' ' || true)"
[ "$count" -le 1 ] || warn "发现 $count 个 tarball,按 mtime 选用最新:$(basename "$TARBALL")(建议清理其余)"
fi
[ -n "$TARBALL" ] && [ -f "$TARBALL" ]
@@ -134,7 +134,7 @@ allowBuilds:
protobufjs: true
minimumReleaseAgeExclude:
- dsh-better-sidebar
- dsh-apes-sidebar
- '@deepseek-ai/*'
EOF
}
+7 -7
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# =============================================================================
# dsh-better-sidebar 挂载冒烟编排(CI + 本地):
# dsh-apes-sidebar 挂载冒烟编排(CI + 本地):
#
# 1. 用官方 CLI 把 npm 打包产物(tarball)真实挂载进一个全新 scratch
# profile(`dsh plugin --profile web add file:<tarball>`,触发
@@ -14,7 +14,7 @@
#
# 环境变量(均可省略):
# DSH_CMD dsh 命令;缺省 PATH 上的 `dsh`,回退 npx 拉官方包
# TARBALL 插件 tarball;缺省仓库根 dsh-better-sidebar-*.tgz(须已 pack)
# TARBALL 插件 tarball;缺省仓库根 dsh-apes-sidebar-*.tgz(须已 pack)
# PORT 固定端口(默认 0 = OS 分配,从日志解析 URL)
# DSH_HOME_BASE 覆盖 scratch 根目录(默认系统临时目录)。脚本始终在其下
# 新建本调用拥有的独立子目录,只写入/删除该子目录;调用方
@@ -44,7 +44,7 @@ e2e_require_cmd pnpm "dsh plugin 转发给 pnpm"
e2e_resolve_dsh_cmd
e2e_resolve_tarball || die "找不到 tarball(TARBALL 或 \$ROOT/dsh-better-sidebar-*.tgz)——先运行 pnpm build && pnpm pack"
e2e_resolve_tarball || die "找不到 tarball(TARBALL 或 \$ROOT/dsh-apes-sidebar-*.tgz)——先运行 pnpm build && pnpm pack"
TARBALL="$(cd "$(dirname "$TARBALL")" && pwd)/$(basename "$TARBALL")"
say "tarball: $TARBALL"
@@ -72,18 +72,18 @@ e2e_write_profile "$PROFILE_DIR"
say "执行 dsh plugin --profile web add file:$TARBALL ..."
$DSH_CMD plugin --profile web add "file:$TARBALL"
# 步骤 3:校验挂载生效(dsh.profile.bundles 含 dsh-better-sidebar)
# 步骤 3:校验挂载生效(dsh.profile.bundles 含 dsh-apes-sidebar)
if ! node -e '
const fs = require("fs");
const p = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
const bundles = p.dsh?.profile?.bundles ?? [];
process.exit(bundles.includes("dsh-better-sidebar") ? 0 : 1);
process.exit(bundles.includes("dsh-apes-sidebar") ? 0 : 1);
' "$PROFILE_DIR/package.json"; then
warn "dsh-better-sidebar 未出现在 dsh.profile.bundles 中——挂载未注册"
warn "dsh-apes-sidebar 未出现在 dsh.profile.bundles 中——挂载未注册"
cat "$PROFILE_DIR/package.json"
exit 1
fi
say "挂载已注册:dsh.profile.bundles 包含 dsh-better-sidebar"
say "挂载已注册:dsh.profile.bundles 包含 dsh-apes-sidebar"
# 步骤 4:启动 dsh web(--port 0 = OS 分配,避免端口冲突;keyless 可起)
say "启动 dsh web(port=${PORT})..."
+7 -7
View File
@@ -1,8 +1,8 @@
## =============================================================================
# dsh-better-sidebar 一键安装脚本(官方 CLI 方式,Windows PowerShell 5.1+ / pwsh)
# dsh-apes-sidebar 一键安装脚本(官方 CLI 方式,Windows PowerShell 5.1+ / pwsh)
#
# 通过 DSH 官方插件命令安装 npm 包并自动挂载:
# dsh plugin --profile web add dsh-better-sidebar@<version>
# dsh plugin --profile web add dsh-apes-sidebar@<version>
#
# 包内声明了 dsh.bundle.patch(cordis.patch.yml):CLI 的 bundle 协调会把它
# 自动加进 profile 的 dsh.profile.bundles,下次启动即挂载——无需手动写
@@ -52,7 +52,7 @@ param(
[string]$Profile = 'web'
)
$PKG = 'dsh-better-sidebar'
$PKG = 'dsh-apes-sidebar'
$REGISTRY = if ($env:REGISTRY) { $env:REGISTRY } else { 'https://registry.npmjs.org' }
# DSH_HOME:DSH_HOME 环境变量 > %USERPROFILE% > $HOME
@@ -116,11 +116,11 @@ if (!/^\s*allowBuilds:\s*$/m.test(t)) {
}
}
}
if (!/^\s*-\s+dsh-better-sidebar\s*$/m.test(t)) {
if (!/^\s*-\s+dsh-apes-sidebar\s*$/m.test(t)) {
if (/^\s*minimumReleaseAgeExclude:\s*$/m.test(t)) {
t = t.replace(/^(\s*minimumReleaseAgeExclude:\s*)$/m, "$1\n - dsh-better-sidebar");
t = t.replace(/^(\s*minimumReleaseAgeExclude:\s*)$/m, "$1\n - dsh-apes-sidebar");
} else {
t += "\nminimumReleaseAgeExclude:\n - dsh-better-sidebar\n";
t += "\nminimumReleaseAgeExclude:\n - dsh-apes-sidebar\n";
}
}
if (t !== before) fs.writeFileSync(p, t);
@@ -233,7 +233,7 @@ if ($addCode -ne 0) {
$pkgJson = Get-Content -Raw (Join-Path $PROFILE_DIR 'package.json') | ConvertFrom-Json
$bundles = $pkgJson.dsh.profile.bundles
if ($bundles -notcontains $PKG) {
Warn 'dsh-better-sidebar 未出现在 dsh.profile.bundles 中——挂载未注册。'
Warn 'dsh-apes-sidebar 未出现在 dsh.profile.bundles 中——挂载未注册。'
Warn "若上面的 pnpm 输出提示 ignored build scripts,请确认 $WS_YML 的 allowBuilds 后重跑本脚本。"
exit 1
}
+10 -10
View File
@@ -1,9 +1,9 @@
#!/usr/bin/env bash
# =============================================================================
# dsh-better-sidebar 一键安装脚本(官方 CLI 方式,macOS / Linux / Windows Git Bash)
# dsh-apes-sidebar 一键安装脚本(官方 CLI 方式,macOS / Linux / Windows Git Bash)
#
# 通过 DSH 官方插件命令安装 npm 包并自动挂载:
# dsh plugin --profile web add dsh-better-sidebar@<version>
# dsh plugin --profile web add dsh-apes-sidebar@<version>
#
# 包内声明了 dsh.bundle.patch(cordis.patch.yml):CLI 的 bundle 协调会把它
# 自动加进 profile 的 dsh.profile.bundles,下次启动即挂载——无需手动写
@@ -38,8 +38,8 @@
# minimumReleaseAgeExclude(幂等),放行本插件,避免"重跑一次才成功"。
# - 老版本(<0.10.2)用手动挂载行,bundle 通道激活后需移除,否则双挂载
# (Node 半挂两次、页面两个侧边栏)。脚本会幂等移除 better-sidebar 挂载行。
# - 回滚:dsh plugin --profile web remove dsh-better-sidebar,或把 profile 依赖
# 改回 "dsh-better-sidebar": "link:<路径>" 再 pnpm install。
# - 回滚:dsh plugin --profile web remove dsh-apes-sidebar,或把 profile 依赖
# 改回 "dsh-apes-sidebar": "link:<路径>" 再 pnpm install。
# =============================================================================
set -euo pipefail
@@ -47,7 +47,7 @@ set -euo pipefail
for arg in "$@"; do
if [ "$arg" = "-h" ] || [ "$arg" = "--help" ]; then
cat <<'EOF'
dsh-better-sidebar 一键安装 / 依赖修复脚本
dsh-apes-sidebar 一键安装 / 依赖修复脚本
用法:
bash scripts/install.sh [版本] [--restart] [--dry-run] [--profile <名>]
@@ -67,7 +67,7 @@ done
DSH_HOME="${DSH_HOME:-${HOME:-${USERPROFILE:-}}/.dsh}"
REGISTRY="${REGISTRY:-https://registry.npmjs.org}"
PKG="dsh-better-sidebar"
PKG="dsh-apes-sidebar"
DSH_CMD="${DSH_CMD:-dsh}"
RESTART=false
@@ -118,11 +118,11 @@ if (!/^\s*allowBuilds:\s*$/m.test(t)) {
}
}
// minimumReleaseAgeExclude:放行本插件(版本无关),避免 <24h 新版本被拒
if (!/^\s*-\s+dsh-better-sidebar\s*$/m.test(t)) {
if (!/^\s*-\s+dsh-apes-sidebar\s*$/m.test(t)) {
if (/^\s*minimumReleaseAgeExclude:\s*$/m.test(t)) {
t = t.replace(/^(\s*minimumReleaseAgeExclude:\s*)$/m, "$1\n - dsh-better-sidebar");
t = t.replace(/^(\s*minimumReleaseAgeExclude:\s*)$/m, "$1\n - dsh-apes-sidebar");
} else {
t += "\nminimumReleaseAgeExclude:\n - dsh-better-sidebar\n";
t += "\nminimumReleaseAgeExclude:\n - dsh-apes-sidebar\n";
}
}
if (t !== before) fs.writeFileSync(p, t);
@@ -224,7 +224,7 @@ if ! node -e '
const bundles = p.dsh?.profile?.bundles ?? [];
process.exit(bundles.includes(process.argv[2]) ? 0 : 1);
' "$PROFILE_DIR/package.json" "$PKG"; then
warn "dsh-better-sidebar 未出现在 dsh.profile.bundles 中——挂载未注册。"
warn "dsh-apes-sidebar 未出现在 dsh.profile.bundles 中——挂载未注册。"
warn "若上面的 pnpm 输出提示 ignored build scripts,请确认 $WS_YML 的 allowBuilds 后重跑本脚本。"
exit 1
fi
+2 -2
View File
@@ -6,7 +6,7 @@
* pnpm build
* node scripts/package-registry.mjs
* dsh registry install ./registry
* dsh registry enable dsh-external/dsh-better-sidebar
* dsh registry enable dsh-external/dsh-apes-sidebar
*
* The registry install copies the whole source directory (`cp -r`), so
* installing the repo root would drag in `node_modules/` (machine-local
@@ -54,4 +54,4 @@ for (const file of files) {
cpSync(source, target)
}
console.log(`registry/ assembled (${files.length} files)`)
console.log('next: dsh registry install ./registry && dsh registry enable dsh-external/dsh-better-sidebar')
console.log('next: dsh registry install ./registry && dsh registry enable dsh-external/dsh-apes-sidebar')
+113
View File
@@ -0,0 +1,113 @@
#!/usr/bin/env bash
# =============================================================================
# push-wiki.sh — 把 docs/wiki/*.md 推送/更新到本仓库的 Gitea wiki
#
# 为什么需要它:本项目的百科源文件**版本化在仓库里**(docs/wiki/),
# Gitea wiki 只是它的发布面。改文档 = 改仓库文件 + 跑本脚本,日志与回溯都在 git 里。
#
# 行为:
# · 幂等:页面不存在则 POST 新建,已存在则 PATCH 覆盖(标题 = 文件名去掉 .md)
# · 凭据来自 `git credential fill`(与推代码同一套),脚本自身不存任何 token
# · 凭据经临时 netrc 文件传给 curl(600 权限,退出即删),不进入 argv
#
# 用法:
# bash scripts/push-wiki.sh # 推送全部页面
# bash scripts/push-wiki.sh 05-运维手册 # 只推指定页面(按文件名/片段匹配)
# WIKI_REMOTE=origin bash scripts/push-wiki.sh # 指定 remote(默认 gitea,缺失则 origin)
# =============================================================================
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
WIKI_DIR="$ROOT/docs/wiki"
FILTER="${1:-}"
# ---- 解析 remote / owner / repo -------------------------------------------
pick_remote() {
for r in "${WIKI_REMOTE:-}" gitea origin; do
[ -z "$r" ] && continue
if git remote get-url "$r" >/dev/null 2>&1; then echo "$r"; return 0; fi
done
echo "无法找到可用的 git remote(试过: ${WIKI_REMOTE:-<空>} gitea origin)" >&2
return 1
}
REMOTE="$(pick_remote)"
URL="$(git remote get-url "$REMOTE")"
case "$URL" in
https://*) ;;
*) echo "本脚本只支持 https remote(当前: $URL)" >&2; exit 1 ;;
esac
HOST="$(printf '%s' "$URL" | sed -E 's#^https://([^/]+)/.*#\1#')"
SLUG="$(printf '%s' "$URL" | sed -E 's#^https://[^/]+/##; s#\.git$##')"
OWNER="${SLUG%%/*}"
REPO="${SLUG#*/}"
API="https://$HOST/api/v1/repos/$OWNER/$REPO/wiki"
echo "wiki 目标 : $OWNER/$REPO @ $HOST (remote: $REMOTE)"
echo "源目录 : docs/wiki"
echo
# ---- 凭据 -----------------------------------------------------------------
CRED="$(printf 'protocol=https\nhost=%s\n\n' "$HOST" | git credential fill 2>/dev/null || true)"
USER="$(printf '%s\n' "$CRED" | sed -n 's/^username=//p')"
PASS="$(printf '%s\n' "$CRED" | sed -n 's/^password=//p')"
if [ -z "$PASS" ]; then
echo "未从 git credential helper 取到 $HOST 的凭据(先确保 git push 能成功)" >&2
exit 1
fi
NETRC="$(mktemp)"
chmod 600 "$NETRC"
trap 'rm -f "$NETRC"' EXIT
printf 'machine %s\nlogin %s\npassword %s\n' "$HOST" "$USER" "$PASS" > "$NETRC"
json_body() { # $1 = file
python3 - "$1" <<'PY'
import base64, json, sys, pathlib
p = pathlib.Path(sys.argv[1])
print(json.dumps({
"title": p.stem,
"content_base64": base64.b64encode(p.read_text(encoding="utf-8").encode("utf-8")).decode("ascii"),
}, ensure_ascii=False))
PY
}
api() { # $1 = method, $2 = path, $3 = body(optional) -> 输出 HTTP 码
local method="$1" path="$2" body="${3:-}"
if [ -n "$body" ]; then
curl -s --netrc-file "$NETRC" -o /tmp/push-wiki-resp.json -w '%{http_code}' \
-X "$method" -H 'Content-Type: application/json' --data-binary "$body" "$API$path"
else
curl -s --netrc-file "$NETRC" -o /tmp/push-wiki-resp.json -w '%{http_code}' -X "$method" "$API$path"
fi
}
# ---- 逐页推送 -------------------------------------------------------------
shopt -s nullglob
files=("$WIKI_DIR"/*.md)
[ ${#files[@]} -gt 0 ] || { echo "docs/wiki 下没有 .md 文件" >&2; exit 1; }
ok=0; fail=0
for f in "${files[@]}"; do
title="$(basename "$f" .md)"
if [ -n "$FILTER" ] && [[ "$title" != *"$FILTER"* ]]; then continue; fi
body="$(json_body "$f")"
code="$(api POST /new "$body")"
if [ "$code" = "201" ]; then
echo " + $title (新建)"; ok=$((ok+1)); continue
fi
enc="$(python3 -c 'import sys,urllib.parse;print(urllib.parse.quote(sys.argv[1], safe=""))' "$title")"
code="$(api PATCH "/page/$enc" "$body")"
if [ "$code" = "200" ]; then
echo " ~ $title (更新)"; ok=$((ok+1))
else
echo " ! $title 失败(HTTP $code): $(head -c 200 /tmp/push-wiki-resp.json 2>/dev/null)" >&2
fail=$((fail+1))
fi
done
echo
echo "完成:成功 $ok 页,失败 $fail 页"
[ "$fail" -eq 0 ] || exit 1
echo "查看: https://$HOST/$OWNER/$REPO/wiki"
+294
View File
@@ -0,0 +1,294 @@
#!/usr/bin/env bash
# =============================================================================
# dsh-apes-sidebar 两线切换脚本(apes fork 线 ↔ upstream better-sidebar 线)
#
# 同一个 DSH profile 内**二选一**切换侧边栏插件——双装不可行(两条线都注册
# 进 `ctx.betterSidebar`,cordis entry id 冲突,第二个被丢弃)。切换 =
# 换 profile 依赖 + 换 dsh.profile.bundles + pnpm install + 提示重启。
#
# aps 本 fork 线(dsh-apes-sidebar,含 4 个 apes:* 信息层 tab)
# 依赖:dsh-apes-sidebar: <file: 本地路径>(本地开发)或
# dsh-apes-sidebar@<版本>(npm,发布后)
# upstream upstream 线(dsh-better-sidebar,官方 8 tab + 生态)
# 依赖:dsh-better-sidebar@<版本>(registry,默认 latest)
#
# 用法:
# bash scripts/switch.sh aps # 切到本 fork 线(本地 file: 安装)
# bash scripts/switch.sh upstream # 切到 upstream 线(registry 安装)
# bash scripts/switch.sh upstream 0.18.0 # 指定 upstream 版本
# bash scripts/switch.sh --status # 只看当前线,不改任何东西
#
# 通用 flag(两线均适用):
# --profile <名> 目标 profile(缺省 desktop)
# --dry-run 只打印将执行的操作,不写任何文件
# -h/--help 打印本帮助
#
# 环境(均可省略,脚本自动探测):
# DSH_HOME 默认 ~/.dsh
# REGISTRY 默认 https://registry.npmjs.org
# DSH_CMD 默认 PATH 上的 dsh,缺省 npx -y --package @deepseek-ai/dsh
# APS_SRC aps 线本地源码路径(缺省 = 本仓库根,即 file: 指向自己)
#
# 机制(幂等,可重复跑):
# 1. 探测当前线(依赖里哪个包在)
# 2. 与目标线相同 → 提示后直接退出(不重装)
# 3. 移除另一线的依赖 + 从 bundles 摘除另一线的 bundle
# 4. 写入目标线依赖 + 确保 workspace 放行 node-pty/protobufjs 构建
# 5. cd profile && pnpm install(reconcileProfilePnpmWorkspace 会重建
# node_modules——这是预期行为,两线不共存)
# 6. 确保 bundles 含目标线包名(bundle 通道挂载生效的判据)
# 7. 幂等移除 cordis.patch.yml 里对手线的手动挂载行(避免双挂载)
# 8. 提示重启 DSH
#
# 回滚:跑 `bash scripts/switch.sh <另一线>` 即可切回去。
# =============================================================================
set -euo pipefail
for arg in "$@"; do
if [ "$arg" = "-h" ] || [ "$arg" = "--help" ]; then
cat <<'EOF'
dsh-apes-sidebar 两线切换(同一 profile 内二选一)
用法:
bash scripts/switch.sh <aps|upstream> [版本]
bash scripts/switch.sh --status
aps 切到本 fork 线(dsh-apes-sidebar,4 个 apes:* tab)
upstream 切到 upstream 线(dsh-better-sidebar,官方)
<版本> 仅对 upstream 生效(缺省 latest)
--status 只看当前线,不改动
--profile 目标 profile(缺省 desktop)
--dry-run 只打印操作,不写文件
-h/--help 本帮助
环境变量:DSH_HOME、REGISTRY、DSH_CMD、APS_SRC(aps 本地源码路径)
EOF
exit 0
fi
done
DSH_HOME="${DSH_HOME:-${HOME:-${USERPROFILE:-}}/.dsh}"
REGISTRY="${REGISTRY:-https://registry.npmjs.org}"
DSH_CMD="${DSH_CMD:-dsh}"
# aps 线本地源码:缺省 = 本仓库根(脚本在 scripts/ 下,根 = 上一级)
APS_SRC="${APS_SRC:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
APS_PKG="dsh-apes-sidebar"
UP_PKG="dsh-better-sidebar"
DRY_RUN=false
STATUS_ONLY=false
TARGET=""
UP_VERSION=""
PROFILE_NAME="desktop"
while [ $# -gt 0 ]; do
case "$1" in
--status) STATUS_ONLY=true ;;
--dry-run) DRY_RUN=true ;;
--profile)
if [ $# -lt 2 ]; then echo "--profile 需要一个 profile 名(如 desktop)" >&2; exit 2; fi
PROFILE_NAME="$2"; shift ;;
aps|upstream) TARGET="$1" ;;
-h|--help) : ;;
-*) echo "未知参数: ${1}(用 -h 查看用法)" >&2; exit 2 ;;
*) UP_VERSION="$1" ;; # 版本参数(仅 upstream 用)
esac
shift
done
PROFILE_DIR="$DSH_HOME/profiles/$PROFILE_NAME"
PKG_JSON="$PROFILE_DIR/package.json"
WS_YML="$PROFILE_DIR/pnpm-workspace.yaml"
PATCH_YML="$PROFILE_DIR/cordis.patch.yml"
say() { printf '\033[32m[switch]\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; }
command -v node >/dev/null 2>&1 || die "未找到 node(DSH 运行需要 Node.js ≥ 20)。"
[ -d "$PROFILE_DIR" ] || die "找不到 profile 目录:${PROFILE_DIR}(请先初始化 ${PROFILE_NAME} profile)"
[ -f "$PKG_JSON" ] || die "找不到 ${PKG_JSON}(profile 未初始化)"
# ── 探测当前线(依赖里哪个包在)─────────────────────────────────────────
current_line() {
node -e '
const p = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
const deps = p.dependencies || {};
if (deps["dsh-apes-sidebar"]) console.log("aps");
else if (deps["dsh-better-sidebar"]) console.log("upstream");
else console.log("none");
' "$PKG_JSON"
}
if [ "$STATUS_ONLY" = true ]; then
CUR="$(current_line)"
say "profile: ${PROFILE_NAME} (${PROFILE_DIR})"
case "$CUR" in
aps) say "当前线:aps(${APS_PKG})" ;;
upstream) say "当前线:upstream(${UP_PKG})" ;;
none) say "当前线:无(两条线都未装)" ;;
esac
# 附带显示 bundles 里的 sidebar 条目
node -e '
const p = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
const b = (p.dsh && p.dsh.profile && p.dsh.profile.bundles) || [];
const side = b.filter(x => /sidebar/.test(x));
console.log("bundles 里的 sidebar 条目: " + (side.length ? side.join(", ") : "(无)"));
' "$PKG_JSON"
exit 0
fi
[ -n "$TARGET" ] || die "缺少目标线(aps | upstream)。用 --status 查看当前线。"
CUR="$(current_line)"
case "$TARGET" in
aps) T_PKG="$APS_PKG"; T_DEP="file:${APS_SRC}" ;;
upstream)
T_PKG="$UP_PKG"
# pnpm 从依赖 key 取包名,值只给版本 spec(不含包名,否则双 @)
case "${UP_VERSION:-latest}" in
latest) T_DEP="latest" ;;
*) T_DEP="$UP_VERSION" ;;
esac
;;
esac
[ "$CUR" = "$TARGET" ] && {
say "当前已是 ${TARGET} 线(${T_PKG}),无需切换。"
say "如需强制重装:先跑 --status 确认,再手动移除依赖后重跑。"
exit 0
}
OTHER_PKG=""
case "$TARGET" in
aps) OTHER_PKG="$UP_PKG" ;;
upstream) OTHER_PKG="$APS_PKG" ;;
esac
say "切换:${CUR} → ${TARGET}(profile: ${PROFILE_NAME})"
say "目标依赖:${T_DEP}"
if [ "$DRY_RUN" = true ]; then
say "[dry-run] 步骤 1:移除 ${OTHER_PKG:-(无)} 依赖 + 从 bundles 摘除 ${OTHER_PKG:-(无)}"
say "[dry-run] 步骤 2:写入 ${T_PKG} 依赖(${T_DEP})"
say "[dry-run] 步骤 3:确保 ${WS_YML} 放行 node-pty/protobufjs 构建"
say "[dry-run] 步骤 4:cd ${PROFILE_DIR} && pnpm install(重建 node_modules)"
say "[dry-run] 步骤 5:确保 bundles 含 ${T_PKG}"
say "[dry-run] 步骤 6:幂等移除 ${PATCH_YML} 里 ${OTHER_PKG:-对手线} 的手动挂载行"
say "[dry-run] 步骤 7:提示重启 DSH"
exit 0
fi
# ── 步骤 1+2+5 合并:一次 node 调用完成依赖 + bundles 的对称改写 ─────────
# 移除对手线依赖 + bundle,写入目标线依赖 + 确保目标 bundle 在列。幂等。
node -e '
const fs = require("fs");
const p = process.argv[1];
const keep = process.argv[2]; // 目标包名
const depSpec = process.argv[3]; // 目标依赖 spec(file:... 或 pkg@ver)
const remove = process.argv[4]; // 对手包名(可能为空串)
const t = JSON.parse(fs.readFileSync(p, "utf8"));
t.dependencies = t.dependencies || {};
// 移除对手线依赖
if (remove) { const had = t.dependencies[remove]; delete t.dependencies[remove]; if (had) console.log("removed dep: " + remove + "=" + had); }
// 写入目标线依赖
t.dependencies[keep] = depSpec;
// bundles:摘除对手线,确保目标线在列
const prof = (t.dsh && t.dsh.profile) || {};
const b = prof.bundles || [];
const nb = b.filter(x => x !== remove);
if (!nb.includes(keep)) nb.push(keep);
t.dsh = t.dsh || {}; t.dsh.profile = prof; prof.bundles = nb;
fs.writeFileSync(p, JSON.stringify(t, null, 2) + "\n");
console.log("deps now: " + JSON.stringify(t.dependencies));
console.log("bundles now: " + nb.join(", "));
' "$PKG_JSON" "$T_PKG" "$T_DEP" "$OTHER_PKG"
say "依赖 + bundles 已改写"
# ── 步骤 3:确保 workspace 放行 node-pty/protobufjs 构建(幂等)─────────
if [ -f "$WS_YML" ]; then
WS_RESULT="$(node -e '
const fs = require("fs");
const p = process.argv[1];
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 (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)" \
|| say "workspace 设置已就绪,跳过"
else
warn "找不到 ${WS_YML},跳过 workspace 预写(若 pnpm install 报 ignored build scripts 需手动补)"
fi
# ── 步骤 4:pnpm install(重建 node_modules——两线不共存,这是预期)──────
command -v pnpm >/dev/null 2>&1 || die "未找到 pnpm。请安装 pnpm 9+ 并加入 PATH。"
say "执行 cd ${PROFILE_DIR} && pnpm install(reconcileProfilePnpmWorkspace 会重建 node_modules)..."
if ! (cd "$PROFILE_DIR" && pnpm install); then
die "pnpm install 失败。请确认网络可用、依赖 spec 正确,然后重试。"
fi
# ── 步骤 5 校验:bundles 含目标包(挂载生效判据)────────────────────────
if ! node -e '
const p = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
const b = (p.dsh && p.dsh.profile && p.dsh.profile.bundles) || [];
process.exit(b.includes(process.argv[2]) ? 0 : 1);
' "$PKG_JSON" "$T_PKG"; then
warn "${T_PKG} 未出现在 dsh.profile.bundles——挂载未注册,请检查 ${PKG_JSON}。"
exit 1
fi
say "bundle 已注册:dsh.profile.bundles 含 ${T_PKG}(下次启动自动挂载)"
# ── 步骤 6:幂等移除对手线在 cordis.patch.yml 里的手动挂载行 ─────────────
if [ -f "$PATCH_YML" ]; then
MOUNT_RESULT="$(node -e '
const fs = require("fs");
const p = process.argv[1];
const removePkg = process.argv[2];
if (!removePkg) { console.log("none"); process.exit(0); }
const lines = fs.readFileSync(p, "utf8").split("\n");
const out = [];
let i = 0;
let removed = false;
while (i < lines.length) {
const line = lines[i];
if (/^[ \t]*- insert:\s*$/.test(line)) {
const block = [line];
let j = i + 1;
while (j < lines.length && lines[j].trim() !== "" && !/^- \s/.test(lines[j])) {
block.push(lines[j]);
j++;
}
if (block.some((l) => new RegExp("id:\\s*" + removePkg.replace(/[-/]/g, "\\$&") + "\\b").test(l))) {
while (out.length && /^[ \t]*#/.test(out[out.length - 1])) out.pop();
i = j;
removed = true;
continue;
}
}
out.push(line);
i++;
}
if (!removed) console.log("none");
else { const t = out.join("\n").replace(/\n{3,}/g, "\n\n"); fs.writeFileSync(p, t); console.log("removed"); }
' "$PATCH_YML" "$OTHER_PKG")"
[ "$MOUNT_RESULT" = "removed" ] && say "已从 ${PATCH_YML} 移除 ${OTHER_PKG} 的手动挂载行" \
|| say "无 ${OTHER_PKG} 手动挂载行,跳过"
else
say "无 ${PATCH_YML},跳过手动挂载行清理"
fi
# ── 步骤 7:提示重启 ────────────────────────────────────────────────────
say "切换完成:${CUR} → ${TARGET}(${T_PKG})"
say "下一步:重启 DSH 并硬刷新(Cmd/Ctrl+Shift+R)使新副本生效。"
+1 -1
View File
@@ -483,7 +483,7 @@ export function EditorHost(props: {
? <FenceErrorNotice store={store} onDisabled={() => { setReloadSeq(sequence => sequence + 1) }} />
: <div className={css.editorError}>{load.message}</div>)}
{!showEmpty && load.status === 'binary' && <BinaryDownload scope={scope} path={path} />}
{!showEmpty && load.status === 'ready' && createElement(load.viewer.component, {
{!showEmpty && load.status === 'ready' && load.viewer.component !== undefined && createElement(load.viewer.component, {
ctx, store, scope, path, title,
viewerId: load.viewer.id,
content: load.content,
+78
View File
@@ -0,0 +1,78 @@
/**
* The one HTML-preview frame both preview surfaces render.
*
* Why this is not a plain `src` to the `/sidebar/html` route: on DSH Desktop
* the host answers 403 `forbidden` to every request that does not carry the
* Electron renderer capability (`x-dsh-desktop-renderer`), and the shell
* attaches that header only to MAIN-frame and SAME-ORIGIN-subframe traffic
* (`requestComesFromRendererOrigin`). A frame sandboxed WITHOUT
* allow-same-origin is an opaque origin at request time, so its document
* request never carries the capability and the preview shows nothing but the
* bare refusal.
*
* So the document is fetched by the MAIN frame — which does carry it — and
* handed to the frame through `srcdoc`. The `sandbox` attribute stays exactly
* as before: the preview still runs in an opaque origin, and the route's own
* workspace fence still bounds what can be fetched.
*
* Two consequences worth knowing:
* - sandboxed: an opaque origin cannot make network requests either, so a
* self-contained document previews completely while a multi-file one shows
* its markup with the relative assets missing (inlining them is a separate
* job, not this component's);
* - unsandboxed (the warned per-surface unlock, or the setting): the srcdoc
* frame inherits the GUI's origin — which is precisely what that escape
* hatch already meant — and there its relative references resolve and load
* like any other same-origin request.
*
* A fetch that fails hands the frame the route URL instead, so the route's
* own answer (its JSON error, or the refusal) stays visible rather than being
* replaced by a blank pane.
*/
import { useEffect, useState, type ReactNode } from 'react'
import { HTML_IFRAME_SANDBOX, withPreviewBase } from './html-preview.ts'
/** One preview frame. */
export function HtmlPreviewFrame(props: {
/** The `/sidebar/html/<sessionId>/<path…>` route URL of the SAVED file. */
url: string
/** Whether the opaque-origin sandbox applies (the default for both surfaces). */
sandboxed: boolean
className?: string
title: string
}): ReactNode {
const { url, sandboxed, className, title } = props
const [doc, setDoc] = useState<string | null>(null)
const [failed, setFailed] = useState(false)
useEffect(() => {
const controller = new AbortController()
setDoc(null)
setFailed(false)
void fetch(url, { signal: controller.signal })
.then(async (response) => {
if (!response.ok) throw new Error(String(response.status))
return response.text()
})
.then((text) => { setDoc(text) })
.catch((error: unknown) => {
// An abort is this effect's own cleanup, not a failure.
if ((error as { name?: string }).name !== 'AbortError') setFailed(true)
})
return () => { controller.abort() }
}, [url])
// The delivery path depends only on what we have: the fetched markup, the
// route's own answer after a failed fetch, or an empty frame while loading.
const delivery = doc !== null
? { srcDoc: withPreviewBase(doc, url) }
: failed ? { src: url } : { srcDoc: '' }
return (
<iframe
{...delivery}
className={className}
sandbox={sandboxed ? HTML_IFRAME_SANDBOX : undefined}
referrerPolicy="no-referrer"
allow=""
title={title}
/>
)
}
+2 -2
View File
@@ -26,14 +26,14 @@ export class RenderBoundary extends Component<{ children?: ReactNode; className?
}
componentDidCatch(error: Error, info: ErrorInfo): void {
console.error('[dsh-better-sidebar] render error:', error, info.componentStack)
console.error('[dsh-apes-sidebar] render error:', error, info.componentStack)
}
render(): ReactNode {
if (this.state.error !== null) {
return (
<div className={this.props.className}>
<span>dsh-better-sidebar: {this.state.error}</span>
<span>dsh-apes-sidebar: {this.state.error}</span>
<button
type="button"
className={css.terminalRetry}
+1 -1
View File
@@ -665,7 +665,7 @@ export function Sidebar(props: { ctx: Context; store: SidebarStore }) {
try {
value = descriptor.badge(ctx, { sessionId, cwd }, state)
} catch (error) {
console.error('[dsh-better-sidebar] tab badge error:', error)
console.error('[dsh-apes-sidebar] tab badge error:', error)
return null
}
if (value === null || value === undefined || value === '') return null
+2 -2
View File
@@ -704,7 +704,7 @@ export function SubagentView(props: {
try {
sessions.openSubagent?.(address)
} catch (error) {
console.error('[dsh-better-sidebar] openSubagent failed:', error)
console.error('[dsh-apes-sidebar] openSubagent failed:', error)
}
}, [sessions, onOpenChild])
@@ -714,7 +714,7 @@ export function SubagentView(props: {
try {
sessions.open?.(rootId)
} catch (error) {
console.error('[dsh-better-sidebar] open session failed:', error)
console.error('[dsh-apes-sidebar] open session failed:', error)
}
}, [sessions, rootId])
+2 -2
View File
@@ -289,7 +289,7 @@ export function TerminalView(props: { scope: SessionScope; tabId: string; store:
failures += 1
if (failures >= FAILURE_LIMIT) {
const detail = event.reason !== '' ? ` (${event.code}: ${event.reason})` : ` (${event.code})`
console.error('[dsh-better-sidebar] terminal connection failed:', event.code, event.reason, url)
console.error('[dsh-apes-sidebar] terminal connection failed:', event.code, event.reason, url)
setFatal(`${t('terminalConnectFailed')}${detail}`)
return
}
@@ -359,7 +359,7 @@ export function TerminalView(props: { scope: SessionScope; tabId: string; store:
fit.fit()
sendResize()
} catch (error) {
console.error('[dsh-better-sidebar] xterm open failed:', error)
console.error('[dsh-apes-sidebar] xterm open failed:', error)
}
})
+8 -10
View File
@@ -36,7 +36,7 @@ import { LazyMermaidMarkdown, MarkdownDocument, type MarkdownHtmlMedia } from '.
import { MdToc } from './md-toc.tsx'
import { splitMermaidBlocks } from './mermaid-blocks.ts'
import { t } from './locales.ts'
import { HTML_IFRAME_SANDBOX } from './html-preview.ts'
import { HtmlPreviewFrame } from './HtmlPreviewFrame.tsx'
import type { EditorToolbarState, FileViewerProps } from './service.ts'
import css from './sidebar.module.css'
@@ -545,16 +545,14 @@ export function TextEditor(props: FileViewerProps) {
onUnlock={() => { setLocalUnlock(true) }}
onRestore={() => { setLocalUnlock(false) }}
/>
{/* Route-src (never srcdoc — a srcdoc frame inherits the parent
origin when unsandboxed; the route URL keeps the frame
cross-origin by construction). The preview shows the SAVED
file; the draft is only visible in edit mode. */}
<iframe
{/* The preview shows the SAVED file; the draft is only visible in
edit mode. HtmlPreviewFrame owns both the delivery path
(main-frame fetch -> srcdoc, which a sandboxed frame could not
reach on its own) and the sandbox tokens. */}
<HtmlPreviewFrame
className={css.editorHtml}
src={htmlUrl(scope, path)}
sandbox={htmlNoSandbox ? undefined : HTML_IFRAME_SANDBOX}
referrerPolicy="no-referrer"
allow=""
url={htmlUrl(scope, path)}
sandboxed={!htmlNoSandbox}
title={path}
/>
</>
+50
View File
@@ -0,0 +1,50 @@
/**
* Tab glyphs for the apes tabs (智能体 / 应用 / 画像).
*
* These follow the SAME recipe as this repo's own icon set in
* `src/client/icons.tsx` — the one the sidebar already uses for 终端 /
* 浏览器 / 文件变动: a 16×16 viewBox whose artwork spans 1.5–14.5, a 1.5px
* `currentColor` stroke, round caps/joins, `fill: none`.
*
* That recipe is what makes an icon sit at the native visual weight in the
* `+` new-tab menu and the right-panel tile grid: DSH's own glyphs fill
* ~92–96% of the box, so artwork padded any tighter renders visibly small
* next to them.
*
* (An earlier revision drew these as fill-only paths spanning only 10–12 of
* the 16 units — that is why they read as a separate, lighter family.)
*/
import type { ReactNode } from 'react'
/** Agent glyph: a rounded robot head with two fill eyes. */
export function IconAgentOutline16({ size = 16, className }: { size?: number; className?: string }): ReactNode {
return (
<svg width={size} height={size} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<rect x="1.5" y="3" width="13" height="11.5" rx="4.2" stroke="currentColor" strokeWidth="1.5" />
<circle cx="5.8" cy="8.6" r="1.25" fill="currentColor" />
<circle cx="10.2" cy="8.6" r="1.25" fill="currentColor" />
</svg>
)
}
/** Apps glyph: a 2×2 grid of rounded squares. */
export function IconAppsOutline16({ size = 16, className }: { size?: number; className?: string }): ReactNode {
return (
<svg width={size} height={size} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<rect x="1.5" y="1.5" width="5.5" height="5.5" rx="1.8" stroke="currentColor" strokeWidth="1.5" />
<rect x="9" y="1.5" width="5.5" height="5.5" rx="1.8" stroke="currentColor" strokeWidth="1.5" />
<rect x="1.5" y="9" width="5.5" height="5.5" rx="1.8" stroke="currentColor" strokeWidth="1.5" />
<rect x="9" y="9" width="5.5" height="5.5" rx="1.8" stroke="currentColor" strokeWidth="1.5" />
</svg>
)
}
/** Profile glyph: a head over shoulders. */
export function IconUserOutline16({ size = 16, className }: { size?: number; className?: string }): ReactNode {
return (
<svg width={size} height={size} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<circle cx="8" cy="4.9" r="3.4" stroke="currentColor" strokeWidth="1.5" />
<path d="M1.5 14.5c0-3.3 2.8-5.3 6.5-5.3s6.5 2 6.5 5.3" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
)
}
+43
View File
@@ -0,0 +1,43 @@
/**
* apes:* information-layer tabs — the fork's own contribution on top of the
* upstream dsh-better-sidebar. Three tabs ship (agents / apps / profile);
* `apes:tasks` was removed (C6) because it overlaps the official DSH session
* tree in the native sidebar, and `messages` is deferred (no DSH
* message-stream API yet — tracked separately).
*
* Isolated under `src/client/apes/` so the upstream `builtins/` tree stays
* untouched: cherry-picking upstream releases only has to keep the single
* `registerApesTabs(...)` call site in `index.tsx` (see D1 = module B).
*
* Every descriptor is registered through the same `BetterSidebarService`
* the builtins use (dogfood), so the apes tabs get the full sidebar
* lifecycle for free: + menu, enable/disable switches, badge pills,
* dedupe/focus, and HMR-safe disposal via the returned disposer.
*/
import type { Context } from '../../context-types.ts'
import type { BetterSidebarService } from '../service.ts'
import { apesTabs } from './tabs.tsx'
// Re-exported so index.tsx can register the apes dictionaries + attach the
// locale service in one import (keeps the apes module self-contained).
export { APES_LOCALE_NS, apesZh, apesEn, attachApesLocale } from './locales.ts'
/**
* Register the apes:* tabs with the sidebar service. Returns a disposer that
* unregisters all of them (cordis auto-invokes it on fiber disposal, so HMR
* re-activation re-registers cleanly — same contract as registerBuiltins).
*/
export function registerApesTabs(
_ctx: Context,
service: BetterSidebarService,
): () => void {
const disposers: (() => void)[] = []
for (const tab of apesTabs) {
disposers.push(service.registerTab(tab))
}
return () => {
for (const d of disposers) {
try { d() } catch { /* already disposed */ }
}
}
}
+87
View File
@@ -0,0 +1,87 @@
/**
* apes:* tab i18n — isolated from the upstream `betterSidebar` locale
* namespace (D1 = module B). The fork keeps its own `apes` namespace and
* its own `t()` so that adding apes keys never touches the upstream
* `locales.ts` (whose `CopyKey = keyof typeof zh` is closed over its own
* dicts). The `t()` mirrors the upstream implementation's resolution order
* (active-locale chain → key fallback) but reads the apes dicts; it is NOT
* wired to the better-locale override store in Phase 1 (those third-language
* overrides ride the lazy `locale` chunk that only covers `betterSidebar`
* entries — apes keys fall back to zh/en, which is acceptable for the
* placeholder round and gets the override integration in Phase 2).
*/
/** The apes locale namespace (registered with ctx.locale under this key). */
export const APES_LOCALE_NS = 'apes'
/** The zh dictionary (source of truth). */
export const apesZh = {
// View titles
apesAgentsTitle: '智能体',
apesAppsTitle: '应用',
apesProfileTitle: '画像',
// Agents view
apesAgentsEmpty: '当前会话没有子智能体',
apesAgentsRoot: '主智能体',
apesAgentRunning: '运行中',
apesAgentInactive: '空闲',
apesAgentOneShot: '一次性',
apesAgentContinuable: '可续',
// Profile view
apesProfileCurrent: '当前会话',
apesProfileCwd: '工作目录',
apesProfileNoCwd: '未知',
apesProfileRunning: '运行中',
apesProfileIdle: '空闲',
// Apps view
apesAppsNoFeed: '当前 DSH 版本未提供工作区应用数据源',
apesAppsWorkspace: '工作区',
} as const
/** The en dictionary (checked against zh — every zh key must appear here). */
export const apesEn: Record<keyof typeof apesZh, string> = {
apesAgentsTitle: 'Agents',
apesAppsTitle: 'Apps',
apesProfileTitle: 'Profile',
apesAgentsEmpty: 'No subagents in this session',
apesAgentsRoot: 'Main agent',
apesAgentRunning: 'running',
apesAgentInactive: 'idle',
apesAgentOneShot: 'one-shot',
apesAgentContinuable: 'continuable',
apesProfileCurrent: 'Current session',
apesProfileCwd: 'Working directory',
apesProfileNoCwd: 'unknown',
apesProfileRunning: 'running',
apesProfileIdle: 'idle',
apesAppsNoFeed: 'This DSH version exposes no workspace-app feed',
apesAppsWorkspace: 'Workspace',
}
export type ApesCopyKey = keyof typeof apesZh
/** Resolve the currently active DSH locale ('zh' | 'en' today). */
export function attachApesLocale(service: { getSnapshot(): { active: string } } | undefined): void {
apesLocaleService = service
}
let apesLocaleService: { getSnapshot(): { active: string } } | undefined
/**
* Translate an apes key. Resolution order: active-locale chain (zh dict when
* the DSH preference is zh, en dict otherwise) → key fallback (shows the key
* itself rather than `undefined`). Mirrors the upstream t() minus the
* better-locale override (Phase 2).
*/
export function t(key: ApesCopyKey, params?: Record<string, string | number>): string {
const active = apesLocaleService?.getSnapshot().active ?? 'zh'
const dict = active.toLowerCase().startsWith('zh') ? apesZh : apesEn
let text: string | undefined = dict[key]
if (text === undefined) text = key
if (params !== undefined) {
for (const [name, value] of Object.entries(params)) {
text = text.replaceAll(`{${name}}`, String(value))
}
}
return text
}
+237
View File
@@ -0,0 +1,237 @@
/**
* apes:* tab views (D2 = data wiring). Each tab reads live data from the
* sessions list feed (`ctx.sessions.list`) via `useSyncExternalStore` and
* reuses the fork's PURE lineage/job helpers — these are framework-free and
* live outside `builtins/`, so importing them does not couple the apes module
* to upstream UI and survives cherry-picks.
*
* Three information-layer tabs (C6: `apes:tasks` removed — it overlaps the
* official DSH session tree in the native sidebar):
* - apes:agents → the subagent topology rooted at the main agent of the
* current session (rootAncestor + per-parent catalogs).
* - apes:apps → no DSH host feed today: shows the workspace (cwd) of the
* current session, otherwise an honest "no feed" note.
* - apes:profile → the current session's summary row (title, cwd, running).
*
* Visual unity (C7): colors use the DSW design-token aliases
* (`--dsw-alias-*`) so the apes panels match the native DSH sidebar
* surface; the runtime defines these on :root. Font sizes / spacing /
* radius stay inline (DSH exposes no public `--ds-*` geometry alias for
* plugin panels — its own vendor bundles inline those too).
*
* Data sources (structural mirror — runtime provided by the DSH host):
* - `ctx.sessions.list.getSnapshot()` → { current, byId, subagentsByParent? };
* `subscribe(cb)` is uSES-safe.
* - `scope.sessionId` = the currently-open session.
*/
import { useCallback, useMemo, useState, type CSSProperties } from 'react'
import { useSyncExternalStore } from 'react'
import { IconAgentOutline16, IconAppsOutline16, IconUserOutline16 } from './icons.tsx'
import type { TabComponentProps, TabDescriptor } from '../service.ts'
import type {
SidebarSessionList,
SidebarSessionSummary,
SidebarSubagentCatalog,
SidebarSubagentChildEntry,
} from '../../context-types.ts'
import { rootAncestor, isSideThreadSummary } from '../subagent-lineage.ts'
import { t } from './locales.ts'
/**
* DSW design-token color aliases. The DSH runtime defines these on :root
* (same set the native dsh-client-ui-* vendor bundles consume). We reference
* them by name via `var(...)` with a literal fallback so the panels still
* render in a non-DSH context (unit harness) with a sane dark palette.
*/
const DSW = {
bgBase: 'var(--dsw-alias-bg-base, #1a1a1a)',
bgLayer1: 'var(--dsw-alias-bg-layer-1, #202020)',
borderL1: 'var(--dsw-alias-border-l1, #2a2a2a)',
borderL2: 'var(--dsw-alias-border-l2, #303030)',
labelPrimary: 'var(--dsw-alias-label-primary, #e8e8e8)',
labelSecondary: 'var(--dsw-alias-label-secondary, #a0a0a0)',
labelTertiary: 'var(--dsw-alias-label-tertiary, #7a7a7a)',
success: 'var(--dsw-alias-state-success-primary, #52c41a)',
interactiveHover: 'var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,0.06))',
} as const
/** Shared inline styles (KISS: one object, no CSS module). Colors via DSW. */
const S: Record<string, CSSProperties> = {
body: { padding: 12, display: 'flex', flexDirection: 'column', gap: 12 },
title: { margin: 0, fontSize: 14, fontWeight: 600, color: DSW.labelPrimary },
hint: { margin: 0, fontSize: 12, color: DSW.labelTertiary },
list: { display: 'flex', flexDirection: 'column', gap: 8 },
card: { border: `1px solid ${DSW.borderL1}`, borderRadius: 8, padding: 10, display: 'flex', flexDirection: 'column', gap: 6, background: DSW.bgLayer1 },
cardRow: { display: 'flex', alignItems: 'center', gap: 8 },
cardLabel: { fontSize: 13, fontWeight: 500, flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: DSW.labelPrimary },
cardSecondary: { fontSize: 11, color: DSW.labelSecondary, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
badge: { fontSize: 11, color: DSW.labelTertiary, flexShrink: 0 },
}
/** A small colored status dot (self-contained, not the builtin StateDot). */
function Dot(props: { color: string }) {
const style: CSSProperties = {
width: 8, height: 8, borderRadius: '50%', background: props.color, flexShrink: 0, display: 'inline-block',
}
return <span aria-hidden style={style} />
}
/** Subscribe to the sessions list feed (uSES-safe). Returns the live snapshot. */
function useSessionList(ctx: TabComponentProps['ctx']): SidebarSessionList {
const sessions = ctx.sessions
return useSyncExternalStore(
useMemo(() => (callback: () => void) => sessions.list.subscribe(callback), [sessions]),
useCallback(() => sessions.list.getSnapshot(), [sessions]),
)
}
/** One level of the subagent topology (rooted at the main agent). */
function agentChildren(
byId: Readonly<Record<string, SidebarSessionSummary>>,
parentSessionId: string,
): SidebarSessionSummary[] {
return Object.values(byId).filter(
s => s.origin === 'subagent' && s.parentId === parentSessionId && !isSideThreadSummary(s),
)
}
/** apes:agents — the subagent topology of the current session's tree. */
function ApesAgentsView(props: TabComponentProps) {
const list = useSessionList(props.ctx)
const { byId, subagentsByParent } = list
const rootId = useMemo(() => rootAncestor(byId, props.scope.sessionId), [byId, props.scope.sessionId])
const rootSummary = rootId === undefined ? undefined : byId[rootId]
const rootCatalog = rootId === undefined ? undefined : subagentsByParent?.[rootId]
// The topology: the main agent + its direct subagent children (one level,
// with per-child activity). A lazy catalog (subagentsByParent) is preferred
// for the child list when present; otherwise fall back to durable byId rows.
// (`.filter` does not narrow a union — narrow explicitly to child entries.)
const children: SidebarSubagentChildEntry[] = (rootCatalog?.entries ?? [])
.filter((e): e is SidebarSubagentChildEntry => e.kind === 'child')
.concat(rootCatalog === undefined
? agentChildren(byId, rootId ?? '').map<SidebarSubagentChildEntry>(s => ({
kind: 'child', id: s.id, mode: 'continuable',
activity: s.running === true ? 'running' : 'inactive',
label: s.displayTitle, hasChildren: agentChildren(byId, s.id).length > 0,
}))
: [])
return (
<div style={S.body}>
<h2 style={S.title}>{t('apesAgentsTitle')}</h2>
{rootSummary === undefined && children.length === 0
? <p style={S.hint}>{t('apesAgentsEmpty')}</p>
: (
<div style={S.list}>
{/* Root (main agent) node */}
{rootSummary !== undefined && (
<div style={S.card}>
<div style={S.cardRow}>
<Dot color={rootSummary.running === true ? DSW.success : DSW.labelTertiary} />
<span style={S.cardLabel} title={rootSummary.displayTitle}>{t('apesAgentsRoot')}: {rootSummary.displayTitle}</span>
</div>
<div style={S.cardRow}>
<span style={S.badge}>{rootSummary.running === true ? t('apesAgentRunning') : t('apesAgentInactive')}</span>
</div>
</div>
)}
{/* Direct subagent children */}
{children.map(entry => {
const summary = byId[entry.id]
const running = entry.activity === 'running'
const label = entry.label ?? summary?.displayTitle ?? entry.id
return (
<div key={entry.id} style={{ ...S.card, marginLeft: 14, borderLeft: `2px solid ${DSW.borderL2}` }}>
<div style={S.cardRow}>
<Dot color={running ? DSW.success : DSW.labelTertiary} />
<span style={S.cardLabel} title={label}>{label}</span>
</div>
<div style={S.cardRow}>
<span style={S.cardSecondary}>
{entry.mode === 'one-shot' ? t('apesAgentOneShot') : t('apesAgentContinuable')}
{' · '}
{running ? t('apesAgentRunning') : t('apesAgentInactive')}
</span>
</div>
</div>
)
})}
</div>
)}
</div>
)
}
/** apes:profile — the current session's identity (title, cwd, running state). */
export function ApesProfileView(props: TabComponentProps) {
const list = useSessionList(props.ctx)
const current = list.current
const summary = current === undefined ? undefined : list.byId[current]
const cwd = summary?.cwd ?? props.scope.cwd
return (
<div style={S.body}>
<h2 style={S.title}>{t('apesProfileTitle')}</h2>
{summary === undefined
? <p style={S.hint}>{t('apesProfileNoCwd')}</p>
: (
<div style={S.list}>
<div style={S.card}>
<div style={S.cardRow}>
<Dot color={summary.running === true ? DSW.success : DSW.labelTertiary} />
<span style={S.cardLabel} title={summary.displayTitle}>{t('apesProfileCurrent')}</span>
</div>
<div style={S.cardRow}>
<span style={S.badge}>{summary.running === true ? t('apesProfileRunning') : t('apesProfileIdle')}</span>
</div>
</div>
<div style={S.card}>
<div style={S.cardRow}>
<span style={S.cardLabel}>{summary.displayTitle}</span>
</div>
<div style={S.cardRow}>
<span style={S.cardSecondary}>{t('apesProfileCwd')}: {cwd ?? t('apesProfileNoCwd')}</span>
</div>
</div>
</div>
)}
</div>
)
}
/** apes:apps — no DSH host feed today; shows the current workspace (cwd). */
function ApesAppsView(props: TabComponentProps) {
const list = useSessionList(props.ctx)
const current = list.current
const summary = current === undefined ? undefined : list.byId[current]
const cwd = summary?.cwd ?? props.scope.cwd
return (
<div style={S.body}>
<h2 style={S.title}>{t('apesAppsTitle')}</h2>
<p style={S.hint}>{t('apesAppsNoFeed')}</p>
<div style={S.card}>
<div style={S.cardRow}>
<span style={S.cardLabel}>{t('apesAppsWorkspace')}</span>
</div>
<div style={S.cardRow}>
<span style={S.cardSecondary}>{cwd ?? t('apesProfileNoCwd')}</span>
</div>
</div>
</div>
)
}
/**
* The three apes:* tab descriptors (C6: `apes:tasks` removed — overlaps the
* official DSH session tree). Order 901-903: agents/apps/profile. Each is
* already shaped as `TabDescriptor` (component + single-instance + icon
* element + i18n title function) so `registerApesTabs` can hand them straight
* to `service.registerTab`. Information-layer tabs are single-instance.
*/
export const apesTabs: TabDescriptor[] = [
{ id: 'apes:agents', title: () => t('apesAgentsTitle'), icon: (size: number) => <IconAgentOutline16 size={size} />, order: 60, single: true, component: ApesAgentsView },
{ id: 'apes:apps', title: () => t('apesAppsTitle'), icon: (size: number) => <IconAppsOutline16 size={size} />, order: 65, single: true, component: ApesAppsView },
{ id: 'apes:profile', title: () => t('apesProfileTitle'), icon: (size: number) => <IconUserOutline16 size={size} />, order: 70, single: true, component: ApesProfileView },
]
+8 -9
View File
@@ -15,7 +15,7 @@ import { api, htmlUrl } from '../api.ts'
import { t } from '../locales.ts'
import { baseName } from '../paths.ts'
import { resolveSidebarPath } from '../produced-files.ts'
import { HTML_IFRAME_SANDBOX } from '../html-preview.ts'
import { HtmlPreviewFrame } from '../HtmlPreviewFrame.tsx'
import type { SidebarDiffRef, SidebarTab } from '../state.ts'
import { DiffRows, ReadRows } from '../diff/DiffRows.tsx'
import { PdfView } from '../PdfView.tsx'
@@ -66,19 +66,18 @@ function diffOf(op: FileOp, prior: string | undefined): readonly DiffRow[] {
return []
}
/** The render view of one html op target: the route-src iframe. Extracted
* (and exported) so the always-sandboxed contract is pinned directly by the
* sandbox spec — this surface has NO no-sandbox escape hatch. */
/** The render view of one html op target. Extracted (and exported) so the
* always-sandboxed contract is pinned directly by the sandbox spec — this
* surface has NO no-sandbox escape hatch, so it always takes the srcdoc
* delivery path. */
export function HtmlRenderPreview(props: { src: string; title: string }) {
return (
<div className={css.htmlPane}>
<iframe
<HtmlPreviewFrame
className={css.htmlFrame}
url={props.src}
sandboxed
title={props.title}
src={props.src}
sandbox={HTML_IFRAME_SANDBOX}
referrerPolicy="no-referrer"
allow=""
/>
</div>
)
+4 -4
View File
@@ -153,7 +153,7 @@ const defaultScriptLoader: ChunkScriptLoader = (src) => new Promise((resolve, re
}, { once: true })
el.addEventListener('error', () => {
el.remove()
reject(new Error(`[dsh-better-sidebar] chunk script ${src} failed to load`))
reject(new Error(`[dsh-apes-sidebar] chunk script ${src} failed to load`))
}, { once: true })
document.head.append(el)
})
@@ -191,7 +191,7 @@ async function buildExternalsRequire(modules: ChunkModuleSystem): Promise<(spec:
if (!table.has(spec)) {
// Single quotes: the bundle-consistency scan regexes for require("...")
// lexical calls — a double-quoted literal here would trip it.
throw new Error(`[dsh-better-sidebar] chunk require('${spec}') missed the module table`)
throw new Error(`[dsh-apes-sidebar] chunk require('${spec}') missed the module table`)
}
return table.get(spec)
}
@@ -248,12 +248,12 @@ export async function loadChunk(name: ChunkName): Promise<ChunkExports> {
if (test !== undefined) return test()
const modules = moduleSystem()
if (modules === undefined) {
throw new Error(`[dsh-better-sidebar] chunk "${name}": client module system unavailable`)
throw new Error(`[dsh-apes-sidebar] chunk "${name}": client module system unavailable`)
}
await scriptLoader(CHUNK_URL(name))
const factory = chunkRegistry()[name]
if (typeof factory !== 'function') {
throw new Error(`[dsh-better-sidebar] chunk "${name}" script did not register its factory`)
throw new Error(`[dsh-apes-sidebar] chunk "${name}" script did not register its factory`)
}
const require = await buildExternalsRequire(modules)
const exports = factory(require)
+1 -1
View File
@@ -1,7 +1,7 @@
/**
* Lazy chunk entry: the code/markdown/html text editor (CodeMirror 6 + the
* language packages). Built as `lib/client-editor.js` and registered under
* `dsh-better-sidebar/editor` — fetched only when a text file is first
* `dsh-apes-sidebar/editor` — fetched only when a text file is first
* opened (see chunk-loader.ts and docs/plans/2026-08-12-lazy-chunks-design.md).
* Never import this module from the core bundle: it pulls CodeMirror into
* the startup path.
+1 -1
View File
@@ -1,7 +1,7 @@
/**
* Lazy chunk entry: the interactive terminal (xterm + fit addon). Built as
* `lib/client-terminal.js` and registered under
* `dsh-better-sidebar/terminal` — fetched only when a terminal tab is first
* `dsh-apes-sidebar/terminal` — fetched only when a terminal tab is first
* opened (see chunk-loader.ts and docs/plans/2026-08-12-lazy-chunks-design.md).
* Never import this module from the core bundle: it pulls xterm (and its
* stylesheet) into the startup path.
+4 -4
View File
@@ -170,12 +170,12 @@ export function appendToDraft(ctx: Context, sessionId: string, text: string): bo
try {
const actx = ctx.sessions.scope(sessionId)
if (actx === undefined) {
console.warn('[dsh-better-sidebar] draft insert skipped: no session scope', sessionId)
console.warn('[dsh-apes-sidebar] draft insert skipped: no session scope', sessionId)
return false
}
const conversation = ctx.get('conversation') as SidebarConversation | undefined
if (conversation === undefined) {
console.warn('[dsh-better-sidebar] draft insert skipped: conversation service unavailable')
console.warn('[dsh-apes-sidebar] draft insert skipped: conversation service unavailable')
return false
}
const input = conversation.input.for(actx)
@@ -189,7 +189,7 @@ export function appendToDraft(ctx: Context, sessionId: string, text: string): bo
placeComposerCaretAfterInsert(next, caretAfter)
return true
} catch (error) {
console.warn('[dsh-better-sidebar] draft insert failed:', error)
console.warn('[dsh-apes-sidebar] draft insert failed:', error)
return false
}
}
@@ -250,7 +250,7 @@ export function insertFileReference(ctx: Context, sessionId: string, relativePat
const after = input.state.getSnapshot()
return after.draftRev !== before.draftRev
} catch (error) {
console.warn('[dsh-better-sidebar] file-reference insert failed:', error)
console.warn('[dsh-apes-sidebar] file-reference insert failed:', error)
return false
}
}
+21
View File
@@ -70,3 +70,24 @@ function parseTitlebarInset(raw: string | null): number {
export function resetDesktopEnvForTests(): void {
cached = undefined
}
/**
* The element the sidebar's panel host belongs in.
*
* The desktop shell stamps the upstream `#root` with
* `data-dsh-desktop-content-viewport` and gives it `isolation: isolate` — a
* stacking context around the whole app INCLUDING the shell's own overlay
* layer (`.dshDesktopOverlay`, z-index 1000), which is where a plugin's modal
* renders (the community market's, for one). A host parented to
* `document.body` sits OUTSIDE that context, so the panel layer's documented
* 25 outranks everything inside it and covered host modals; inside the
* viewport the same 25 keeps its meaning — above the app frame (z-index auto)
* and below the overlay layer (1000).
*
* Every other deployment (plain web, an unframed shell) keeps the body mount.
* @param doc - the document to resolve against.
* @returns the content viewport when the shell framed one, else the body.
*/
export function panelHostParent(doc: Document = document): HTMLElement {
return doc.querySelector<HTMLElement>('#root[data-dsh-desktop-content-viewport]') ?? doc.body
}
+46 -1
View File
@@ -1,7 +1,8 @@
/**
* Shared vocabulary of the sidebar's HTML preview surfaces (the editor's
* html viewer and the changes tab's op render preview): the sandbox tokens
* both preview iframes load with.
* both preview iframes load with, plus the document-base rewrite the srcdoc
* delivery path needs.
*/
/**
@@ -13,3 +14,47 @@
* render preview always stays sandboxed.
*/
export const HTML_IFRAME_SANDBOX = 'allow-scripts allow-popups allow-downloads allow-modals'
/** Whether a document already declares its own base URL. */
const HAS_BASE = /<base[\s/>]/i
/** Index just past the element a `<base>` must precede to land in <head>. */
function insertionPoint(html: string): number {
const head = /<head\b[^>]*>/i.exec(html)
if (head !== null) return head.index + head[0].length
const htmlTag = /<html\b[^>]*>/i.exec(html)
if (htmlTag !== null) return htmlTag.index + htmlTag[0].length
return 0
}
/**
* The directory part of a preview route URL, with its trailing slash.
* @param url - the `/sidebar/html/<sessionId>/<path…>` route URL.
* @returns the URL up to and including the last `/`.
*/
export function previewBaseOf(url: string): string {
const cut = url.lastIndexOf('/')
return cut < 0 ? url : url.slice(0, cut + 1)
}
/**
* Point a preview document's relative references back at its own route.
*
* A `srcdoc` document has no URL of its own — the browser resolves its
* relative references against the EMBEDDING page's URL, so `./style.css` in a
* previewed file would resolve to the GUI's origin and hit whatever route
* happens to match. Injecting `<base href="<the file's route directory>">`
* keeps relative resolution inside `/sidebar/html/…`, where the file's own
* siblings live. A document that declares its own `<base>` is left alone: the
* author said what the base is.
*
* @param html - the previewed document's markup.
* @param url - the route URL the document was fetched from.
* @returns the markup, with a base element injected when it had none.
*/
export function withPreviewBase(html: string, url: string): string {
if (HAS_BASE.test(html)) return html
const href = previewBaseOf(url).replace(/"/g, '&quot;')
const at = insertionPoint(html)
return `${html.slice(0, at)}<base href="${href}">${html.slice(at)}`
}
+56 -26
View File
@@ -1,5 +1,5 @@
/**
* Client half of dsh-better-sidebar: resolves the user's "Side card"
* Client half of dsh-apes-sidebar: resolves the user's "Side card"
* preferences through the plugin's own fenced settings route, mounts the
* right sidebar portal (inside an error boundary so a rendering failure
* shows an error strip instead of a blank panel), registers the turn-tail
@@ -15,6 +15,7 @@ import { allLeaves, createSidebarStore, isAgentTabId } from './state.ts'
import { createBetterSidebarService, matchUrlTarget } from './service.ts'
import { revalidateChunksOnReactivate, setChunkModuleSystem } from './chunk-loader.ts'
import { registerBuiltins } from './builtins/index.ts'
import { registerApesTabs, APES_LOCALE_NS, apesZh, apesEn, attachApesLocale } from './apes/index.ts'
import { Sidebar } from './Sidebar.tsx'
import { RenderBoundary } from './RenderBoundary.tsx'
import { registerTurnTailInterception } from './intercept.tsx'
@@ -26,6 +27,7 @@ import { registerLinkInterception } from './link-intercept.ts'
import { registerImeGuard } from './ime-guard.ts'
import { registerSettingsNavIcon } from './settings-nav-icon.ts'
import { loadBootDecision } from './prefs.ts'
import { panelHostParent } from './desktop-env.ts'
import { SideCardSection } from './SideCardSection.tsx'
import { api } from './api.ts'
import { LOCALE_NS, attachLocale, attachBetterLocale, t, zh, en } from './locales.ts'
@@ -61,11 +63,17 @@ export function apply(ctx: Context): void {
// plugin's dictionaries into the shared locale registry. The disposers
// run on fiber disposal, so re-activation (HMR) re-registers cleanly.
attachLocale(ctx.locale)
// The apes module has its own locale namespace (isolated from the upstream
// betterSidebar dicts): attach it so apes t() tracks the DSH active-locale
// preference, and register its zh/en dicts under `apes`.
attachApesLocale(ctx.locale)
ctx.effect(() => {
const offZh = ctx.locale.register(LOCALE_NS, 'zh', zh)
const offEn = ctx.locale.register(LOCALE_NS, 'en', en)
return () => { offZh(); offEn() }
}, 'dsh-better-sidebar: dictionaries')
const offApesZh = ctx.locale.register(APES_LOCALE_NS, 'zh', apesZh as Record<string, string>)
const offApesEn = ctx.locale.register(APES_LOCALE_NS, 'en', apesEn as Record<string, string>)
return () => { offZh(); offEn(); offApesZh(); offApesEn() }
}, 'dsh-apes-sidebar: dictionaries')
// Opt-in third-language support through @huanlin/dsh-plugin-better-locale.
// When that plugin is installed, it publishes `ctx.betterLocale` (the
@@ -129,7 +137,7 @@ export function apply(ctx: Context): void {
dispose?.()
attachBetterLocale(undefined)
}
}, 'dsh-better-sidebar: better-locale lazy integration')
}, 'dsh-apes-sidebar: better-locale lazy integration')
// One store instance per activation: production code creates it only here,
// then hands it to the mounted panel and closes over it in the slot
// registrations (the official createXXXStore() factory rule — no
@@ -150,17 +158,17 @@ export function apply(ctx: Context): void {
service.setSurface(nativeSurface)
ctx.effect(
() => registerNativeSurface({ ctx, store: sidebarStore, service, records: nativeRecords }),
'dsh-better-sidebar: native right-Sidebar registrations',
'dsh-apes-sidebar: native right-Sidebar registrations',
)
// The bottom workbench's expand/collapse button in DSH's session header
// (the header's corner seat belongs to the native sidebar's own control).
ctx.effect(
() => registerBottomToggle(ctx, sidebarStore),
'dsh-better-sidebar: bottom-workbench toggle',
'dsh-apes-sidebar: bottom-workbench toggle',
)
ctx.effect(
() => () => { nativeSurface.dispose(); service.setSurface(undefined) },
'dsh-better-sidebar: native right-Sidebar surface',
'dsh-apes-sidebar: native right-Sidebar surface',
)
// Terminal tab titles use the host's effective shell name (e.g. bash/zsh)
// instead of "Terminal 1". Start with a safe fallback and replace it as
@@ -186,7 +194,17 @@ export function apply(ctx: Context): void {
// fiber disposal (HMR-safe).
ctx.effect(
() => registerBuiltins(ctx, service, { terminalTitle: () => terminalTitle }),
'dsh-better-sidebar: register built-in tabs and viewers',
'dsh-apes-sidebar: register built-in tabs and viewers',
)
// The apes:* information-layer tabs (the fork's own contribution, isolated
// under src/client/apes). Registered through the SAME service as the
// builtins, so the fork's native adapter (`native/index.ts`) turns each one
// into an official right-sidebar tab type + body automatically — that is the
// entire reason this fork now runs ONE sidebar instead of two. The disposer
// runs on fiber disposal (HMR-safe).
ctx.effect(
() => registerApesTabs(ctx, service),
'dsh-apes-sidebar: register apes tabs',
)
// A failure anywhere in the client lifecycle must never take the app down
// silently: log with the plugin prefix and pin a visible diagnostic strip
@@ -197,7 +215,7 @@ export function apply(ctx: Context): void {
// byte-identical to the old hardcoded bar, and any `--dsw-alias-*` skin
// re-themes it (guide §12: no hardcoded colors).
const fail = (phase: string, error: unknown): void => {
console.error(`[dsh-better-sidebar] ${phase} error:`, error)
console.error(`[dsh-apes-sidebar] ${phase} error:`, error)
try {
const bar = document.createElement('div')
bar.style.cssText = 'position:fixed;left:8px;bottom:8px;z-index:2147483000;max-width:70vw;padding:8px 12px;'
@@ -205,7 +223,7 @@ export function apply(ctx: Context): void {
+ 'color:var(--dsw-alias-state-error-primary,#f2a1a1);'
+ 'background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-base,#1b1b22));'
+ 'border:1px solid var(--dsw-alias-state-error-primary,#f2a1a1);border-radius:8px;white-space:pre-wrap'
bar.textContent = `[dsh-better-sidebar] ${phase} error: ${error instanceof Error ? error.message : String(error)}`
bar.textContent = `[dsh-apes-sidebar] ${phase} error: ${error instanceof Error ? error.message : String(error)}`
document.body.appendChild(bar)
} catch {
// Nothing left to report with.
@@ -242,17 +260,26 @@ export function apply(ctx: Context): void {
host?.remove()
host = undefined
}
/** Re-attach the host if the page (a desktop shell wrapper, SPA
* navigation, …) ever removes it from <body>. Cheap: childList only,
* no subtree, no attribute filtering. */
/** Put the host in the layer it belongs to — re-attaching it if the
* page (a desktop shell wrapper, SPA navigation, …) removed it, and
* moving it if the shell framed the content viewport after our mount
* (see panelHostParent). */
const anchorHost = (): void => {
if (host === undefined) return
const parent = panelHostParent()
if (host.parentElement !== parent) parent.appendChild(host)
}
/** Watch for the host being removed, and for the shell stamping its
* framed mode onto <body> (the same effect tags #root as the content
* viewport, which is where the host must live). */
const guardAnchor = (): void => {
if (bodyObserver !== undefined) return
bodyObserver = new MutationObserver(() => {
if (host !== undefined && !document.body.contains(host)) {
document.body.appendChild(host)
}
bodyObserver = new MutationObserver(anchorHost)
bodyObserver.observe(document.body, {
childList: true,
attributes: true,
attributeFilter: ['data-dsh-desktop-mode'],
})
bodyObserver.observe(document.body, { childList: true })
}
/** One-shot geometry self-check: if the host page transforms
* <html>/<body> itself (exotic shells), a fixed panel host would
@@ -274,7 +301,7 @@ export function apply(ctx: Context): void {
return
}
layer.setAttribute('data-dsh-panel-host-degraded', '')
console.warn('[dsh-better-sidebar] panel host geometry mismatch — a page-level transform was detected; using degraded viewport sync')
console.warn('[dsh-apes-sidebar] panel host geometry mismatch — a page-level transform was detected; using degraded viewport sync')
// Track our own compensating translation so the loop judges the
// UNCORRECTED geometry: clearing degraded mode must wait for the
// ancestor transform to actually disappear — the frame right after
@@ -305,8 +332,11 @@ export function apply(ctx: Context): void {
if (mounted || disposed) return
try {
host = document.createElement('div')
host.setAttribute('data-dsh-better-sidebar', '')
document.body.appendChild(host)
host.setAttribute('data-dsh-apes-sidebar', '')
// The shell's framed content viewport when there is one (see
// panelHostParent): it is a stacking context, so a body-level host
// would paint above every overlay the shell hosts inside it.
panelHostParent().appendChild(host)
root = createRoot(host)
root.render(createElement(RenderBoundary, { className: css.boundaryError }, createElement(Sidebar, { ctx, store: sidebarStore })))
mounted = true
@@ -352,7 +382,7 @@ export function apply(ctx: Context): void {
offRemote?.()
unmount()
}
}, 'dsh-better-sidebar: sidebar mount')
}, 'dsh-apes-sidebar: sidebar mount')
ctx.effect(
() => {
@@ -363,7 +393,7 @@ export function apply(ctx: Context): void {
return () => {}
}
},
'dsh-better-sidebar: turn-tail interception',
'dsh-apes-sidebar: turn-tail interception',
)
ctx.effect(
@@ -407,7 +437,7 @@ export function apply(ctx: Context): void {
return () => {}
}
},
'dsh-better-sidebar: link interception',
'dsh-apes-sidebar: link interception',
)
// The IME guard: composition keys (candidate arrows, confirm, cancel)
@@ -427,7 +457,7 @@ export function apply(ctx: Context): void {
return () => {}
}
},
'dsh-better-sidebar: IME composition guard',
'dsh-apes-sidebar: IME composition guard',
)
// DSH 0.1.x does not yet carry an icon through the settings.section
@@ -437,7 +467,7 @@ export function apply(ctx: Context): void {
// the marker for HMR / plugin disable.
ctx.effect(
() => registerSettingsNavIcon(() => t('settingsNav')),
'dsh-better-sidebar: settings navigation icon',
'dsh-apes-sidebar: settings navigation icon',
)
// The "Side card" settings section: appears in the DSH Settings shell
+2 -1
View File
@@ -112,6 +112,7 @@ export function SidebarProducedFiles(props: {
export function registerTurnTailInterception(ctx: Context, store: SidebarStore): () => void {
return ctx.slots.inject('conversation.chat.turnTail', () => ctx.slots.register({
name: 'conversation.chat.turnTail',
id: 'dsh-apes-sidebar:turn-tail',
// Decline the takeover while the editor tab type is disabled in the side
// card settings: the produced-files row falls back to the default
// deliverables behavior instead of offering chips that cannot open. Also
@@ -122,7 +123,7 @@ export function registerTurnTailInterception(ctx: Context, store: SidebarStore):
return selectProducedFiles(owner)
},
priority: -1,
registrant: 'dsh-better-sidebar',
registrant: 'dsh-apes-sidebar',
inject: (sessionId: string) => ({
openInSidebar: (path: string) => { openSidebarFile(ctx, store, sessionId, path) },
onShowInFolder: (files: readonly string[]) => { revealInExplorer(ctx, store, sessionId, files) },
+1 -1
View File
@@ -217,7 +217,7 @@ export function languageForPath(path: string): Language | LanguageSupport | null
return FACTORIES[key]!()
} catch (error) {
// A broken factory degrades to plain text, never crashes the editor.
console.warn(`[dsh-better-sidebar] language factory "${key}" failed:`, error)
console.warn(`[dsh-apes-sidebar] language factory "${key}" failed:`, error)
return null
}
}
+2 -2
View File
@@ -60,11 +60,11 @@ body[data-dsh-sidebar-dragging] #root [data-dsh-center-col] {
only this plugin's localized row; render the requested Lucide
gallery-horizontal-end SVG as a currentColor mask so it follows the native
nav hover/active colors without changing the shell's 16px icon rhythm. */
[data-dsh-better-sidebar-settings-nav] > svg:first-child {
[data-dsh-apes-sidebar-settings-nav] > svg:first-child {
display: none;
}
[data-dsh-better-sidebar-settings-nav]::before {
[data-dsh-apes-sidebar-settings-nav]::before {
content: '';
flex: none;
width: 16px;
+1 -1
View File
@@ -37,7 +37,7 @@ function LazyChunkView<P extends object>({ chunk, pick, props }: LazyChunkViewPr
if (cancelled) return
const Comp = pick(mod)
if (Comp === undefined) {
setState({ status: 'error', message: `[dsh-better-sidebar] chunk "${chunk}" is missing its component` })
setState({ status: 'error', message: `[dsh-apes-sidebar] chunk "${chunk}" is missing its component` })
return
}
setState({ status: 'ready', Comp })
+2 -2
View File
@@ -404,7 +404,7 @@ export const ar: Record<string, string> = {
addPluginsViewerCardDesc: 'تسجيل معاينة نوع ملف',
addPluginsTabDesc: 'يمكن توسيع صفحات الشريط الجانبي (التبويبات) بواسطة الإضافات. تُسجَّل الإضافات عبر خدمة ctx.betterSidebar؛ النقر على «تثبيت» ينسخ أمر التثبيت — الصقه في طرفية حيث يقيم ملف DSH الشخصي وشغّله.',
addPluginsViewerDesc: 'يمكن توسيع عارضات الملفات بواسطة الإضافات. تُسجَّل الإضافات عبر خدمة ctx.betterSidebar؛ النقر على «تثبيت» ينسخ أمر التثبيت — الصقه في طرفية حيث يقيم ملف DSH الشخصي وشغّله.',
addPluginsBrowseMore: 'تصفّح المزيد من الإضافات على GitHub (topic: dsh-better-sidebar)',
addPluginsBrowseMore: 'تصفّح المزيد من الإضافات على GitHub (topic: dsh-apes-sidebar)',
addPluginsSearch: 'البحث باسم الإضافة / الوصف…',
addPluginsNoMatch: 'لا إضافات مطابقة',
addPluginsRecommended: 'إضافات موصى بها',
@@ -417,7 +417,7 @@ export const ar: Record<string, string> = {
pluginGithubWorkbenchDesc: 'تبويب GitHub Workbench: شجرة المستودع البعيد + Issues / Pull requests / Actions مع دعم كامل للكتابة — إنشاء Issue/PR، والتعليق، والتعديل، والإغلاق/إعادة الفتح، والدمج عبر squash·merge·rebase (بتأكيد قوي)، وإعادة تشغيل CI أو إلغائها؛ يعرض مبدّل المستودعات تلقائيًا المستودعات المتاحة ويتيح البحث في المستودعات العامة؛ وبدون better-sidebar يتحول تلقائيًا إلى لوحة جانبية مستقلة على اليمين',
pluginMdExportDesc: 'يضيف زر «تصدير» إلى شريط أدوات Markdown في better-sidebar: يعرض ملف .md الحالي بنقرة واحدة كملف HTML مستقل (الجداول/كتل الأكواد/مخططات Mermaid مدمجة، والتخطيط يتبع سمة المعاينة) ويحفظه بجوار الملف، أو يصدّره PDF عبر نافذة الطباعة',
pluginCodeNavDesc: 'تنقل معاينة الكود: يكتشف اللغة تلقائياً من نوع الملف ويبرز الصياغة، مخطط الرموز (تصفية الفئات/الطرق/المتغيرات + قفزة بنقرة واحدة)، والبحث داخل الملف (إبراز كل التطابقات، السابق/التالي، مطابقة حالة الأحرف) — يتولى معاينة ملفات الكود في محرر better-sidebar',
pluginSuhuangScrollDesc: 'يربط بيئة Suhuang Scroll المحلية بإعدادات DSH وbetter-sidebar لإعداد النموذج واختبار الاتصال والتحكم في التصحيح المتواصل؛ يتطلب Suhuang Scroll Runtime وdsh-better-sidebar',
pluginSuhuangScrollDesc: 'يربط بيئة Suhuang Scroll المحلية بإعدادات DSH وbetter-sidebar لإعداد النموذج واختبار الاتصال والتحكم في التصحيح المتواصل؛ يتطلب Suhuang Scroll Runtime وdsh-apes-sidebar',
pluginBetterOverleafDesc: 'تبويب Overleaf لـ better-sidebar: تسجيل دخول مباشر عبر متصفح CDP (يدعم Chromium من جهات أخرى)، قائمة المشاريع/التبديل، نسخ git محلية ضمن <workspace>/overleaf/، مزامنة git ثنائية الاتجاه مع تراجع إلى API للقراءة فقط، ومعاينة الملفات عبر مساحة عمل الشريط الجانبي',
pluginGitRemotesDesc: 'تبويب Git Remotes: فرع/أصل/أمام-خلف، fetch (مع prune اختياري)، ff-only pull، والدفع فقط بعد تأكيد داخل التبويب. لا يستبدل تبويب Git المدمج للإدراج/التثبيت، ولا يقدّم force-push أو أداة دفع آلي للنموذج',
pluginSentinelDesc: 'نظام إيقاظ وكيل يحرّكه تحقّق الشروط: مستشعرات ملف/عملية/منفذ/HTTP/أمر/webhook توقظ الجلسات الخاملة عند تحقّق الشرط؛ يُسجِّل تبويب «Sentinel» مع جدول مراقبة شامل للخادم',
+2 -2
View File
@@ -389,7 +389,7 @@ export const de: Record<string, string> = {
addPluginsViewerCardDesc: 'Eine Dateityp-Vorschau registrieren',
addPluginsTabDesc: 'Seitenleisten-Seiten (Tabs) können durch Plugins erweitert werden. Plugins registrieren sich über den ctx.betterSidebar-Dienst; ein Klick auf „Installieren“ kopiert den Installationsbefehl – fügen Sie ihn in einem Terminal ein, in dem Ihr DSH-Profil liegt, und führen Sie ihn aus.',
addPluginsViewerDesc: 'Datei-Viewer können durch Plugins erweitert werden. Plugins registrieren sich über den ctx.betterSidebar-Dienst; ein Klick auf „Installieren“ kopiert den Installationsbefehl – fügen Sie ihn in einem Terminal ein, in dem Ihr DSH-Profil liegt, und führen Sie ihn aus.',
addPluginsBrowseMore: 'Weitere Plugins auf GitHub durchsuchen (Topic: dsh-better-sidebar)',
addPluginsBrowseMore: 'Weitere Plugins auf GitHub durchsuchen (Topic: dsh-apes-sidebar)',
addPluginsSearch: 'Nach Plugin-Name oder -Beschreibung suchen…',
addPluginsNoMatch: 'Keine passenden Plugins',
addPluginsRecommended: 'Empfohlene Plugins',
@@ -402,7 +402,7 @@ export const de: Record<string, string> = {
pluginGithubWorkbenchDesc: '„GitHub Workbench“-Tab: Repository-Baum + Issues / Pull requests / Actions mit vollen Schreibrechten — Issue/PR erstellen, kommentieren, bearbeiten, schließen/wieder öffnen, squash·merge·rebase-Merge (mit starker Bestätigung), CI erneut ausführen/abbrechen; der Repo-Umschalter listet automatisch zugreifbare Repos auf und durchsucht öffentliche; ohne better-sidebar fällt er auf ein eigenständiges rechtes Panel zurück',
pluginMdExportDesc: 'Fügt einen „Export"-Button zur Markdown-Symbolleiste in better-sidebar hinzu: rendert die aktuelle .md per Klick in eigenständiges HTML (Tabellen/Codeblöcke/Mermaid-Diagramme inline, Layout folgt dem Vorschau-Theme) und speichert es neben der .md, oder exportiert über den Druckdialog als PDF',
pluginCodeNavDesc: 'Code-Vorschau-Navigation: erkennt die Sprache am Dateityp und hebt die Syntax hervor, Symbol-Gliederung (Filter für Klassen/Methoden/Variablen + Sprung mit einem Klick) und Suche in der Datei (alle Treffer hervorgehoben, vor/zurück, Groß-/Kleinschreibung) — übernimmt die Code-Dateivorschau im better-sidebar-Editor',
pluginSuhuangScrollDesc: 'Verbindet die lokale Suhuang Scroll Runtime mit den DSH-Einstellungen und better-sidebar für Modellkonfiguration, Verbindungstests und fortlaufende Bewertung; benötigt Suhuang Scroll Runtime und dsh-better-sidebar',
pluginSuhuangScrollDesc: 'Verbindet die lokale Suhuang Scroll Runtime mit den DSH-Einstellungen und better-sidebar für Modellkonfiguration, Verbindungstests und fortlaufende Bewertung; benötigt Suhuang Scroll Runtime und dsh-apes-sidebar',
pluginBetterOverleafDesc: 'Overleaf-Tab für better-sidebar: Login direkt über CDP-Browser (Chromium von Drittanbietern unterstützt), Projekliste/-wechsel, lokale git-Spiegel unter <workspace>/overleaf/, bidirektionaler git-Abgleich mit Read-only-API-Fallback und Dateivorschau über die Sidebar-Workbench',
pluginGitRemotesDesc: '„Git-Remotes“-Tab: Branches/Upstream/ahead-behind ansehen, fetch (optional prune), ff-only pull und push erst nach Bestätigung im Tab. Ersetzt nicht den eingebauten Stagen/Commit-Tab und bietet weder force-push noch automatisches Push-Werkzeug für das Modell',
pluginSentinelDesc: 'Bedingungsgesteuertes Wecksystem für Agenten: Datei-/Prozess-/Port-/HTTP-/Befehls-/Webhook-Sensoren wecken ruhende Sitzungen, wenn Bedingungen zutreffen; registriert einen „Sentinel“-Tab mit der serverweiten Überwachungstabelle',
+2 -2
View File
@@ -396,7 +396,7 @@ export const fr: Record<string, string> = {
addPluginsViewerCardDesc: 'Enregistrer un aperçu d’un type de fichier',
addPluginsTabDesc: 'Les pages de la barre latérale (onglets) peuvent être étendues par des plugins. Les plugins s’enregistrent via le service ctx.betterSidebar ; cliquez sur « Installer » pour copier la commande d’installation, collez-la dans un terminal de l’environnement DSH et exécutez-la.',
addPluginsViewerDesc: 'Les aperçus de fichiers peuvent être étendus par des plugins. Les plugins s’enregistrent via le service ctx.betterSidebar ; cliquez sur « Installer » pour copier la commande d’installation, collez-la dans un terminal de l’environnement DSH et exécutez-la.',
addPluginsBrowseMore: 'Parcourir plus de plugins sur GitHub (topic : dsh-better-sidebar)',
addPluginsBrowseMore: 'Parcourir plus de plugins sur GitHub (topic : dsh-apes-sidebar)',
addPluginsSearch: 'Rechercher par nom / description de plugin…',
addPluginsNoMatch: 'Aucun plugin correspondant',
addPluginsRecommended: 'Plugins recommandés',
@@ -409,7 +409,7 @@ export const fr: Record<string, string> = {
pluginGithubWorkbenchDesc: 'Onglet « GitHub Workbench » de better-sidebar : arborescence du dépôt distant + Issues / Pull requests / Actions avec prise en charge complète en écriture — création d’issue/PR, commentaire, édition, fermeture/réouverture, merge squash·merge·rebase (confirmation renforcée), relance/annulation de la CI ; le sélecteur de dépôt liste automatiquement les dépôts accessibles et recherche les dépôts publics ; sans better-sidebar, il bascule sur un panneau latéral droit autonome',
pluginMdExportDesc: 'Ajoute un bouton « Exporter » à la barre d’outils Markdown de better-sidebar : rend en un clic le .md courant en HTML autonome (tableaux/blocs de code/diagrammes Mermaid en ligne, mise en page fidèle au thème d’aperçu) et l’enregistre à côté du .md, ou exporte en PDF via la boîte de dialogue d’impression',
pluginCodeNavDesc: 'Navigation de prévisualisation de code : détecte le langage selon le type de fichier et colore la syntaxe, plan des symboles (filtres classes/méthodes/variables + saut en un clic) et recherche dans le fichier (toutes les correspondances surlignées, précédent/suivant, respect de la casse) — prend en charge l’aperçu des fichiers de code dans l’éditeur better-sidebar',
pluginSuhuangScrollDesc: 'Connecte le Runtime Suhuang Scroll local aux réglages DSH et à better-sidebar pour configurer le modèle, tester la connexion et contrôler la correction continue ; nécessite Suhuang Scroll Runtime et dsh-better-sidebar',
pluginSuhuangScrollDesc: 'Connecte le Runtime Suhuang Scroll local aux réglages DSH et à better-sidebar pour configurer le modèle, tester la connexion et contrôler la correction continue ; nécessite Suhuang Scroll Runtime et dsh-apes-sidebar',
pluginBetterOverleafDesc: 'Onglet Overleaf pour better-sidebar : connexion directe via navigateur CDP (Chromium tiers pris en charge), liste/sélection de projets, miroirs git locaux sous <workspace>/overleaf/, synchronisation git bidirectionnelle avec repli API en lecture seule, et aperçu des fichiers via l’atelier de la barre latérale',
pluginGitRemotesDesc: 'Onglet Git Remotes de better-sidebar : branches/amonts/ahead-behind, fetch (avec prune possible), pull en ff-only, push seulement après confirmation. Ne remplace pas la mise en scène/les commits du Git intégré et ne fournit pas de force-push ni de push automatique par le modèle',
pluginSentinelDesc: 'Système de réveil d’agent piloté par conditions : capteurs fichier/processus/port/HTTP/commande/webhook, les sessions en veille se réveillent automatiquement lorsque les conditions sont remplies ; enregistre l’onglet « Sentinel » affichant le tableau de surveillance global du serveur',
+2 -2
View File
@@ -403,7 +403,7 @@ export const hi: Record<string, string> = {
addPluginsViewerCardDesc: 'फ़ाइल-प्रकार पूर्वावलोकन पंजीकृत करें',
addPluginsTabDesc: 'साइडबार पृष्ठ (टैब) प्लगइन्स द्वारा विस्तारित हो सकते हैं। प्लगइन्स ctx.betterSidebar सेवा के माध्यम से पंजीकृत होते हैं; इंस्टॉल क्लिक करने पर इंस्टॉल कमांड कॉपी होता है — इसे उस टर्मिनल में पेस्ट करें जहाँ आपका DSH प्रोफ़ाइल है और चलाएँ।',
addPluginsViewerDesc: 'फ़ाइल पूर्वावलोकक प्लगइन्स द्वारा विस्तारित हो सकते हैं। प्लगइन्स ctx.betterSidebar सेवा के माध्यम से पंजीकृत होते हैं; इंस्टॉल क्लिक करने पर इंस्टॉल कमांड कॉपी होता है — इसे उस टर्मिनल में पेस्ट करें जहाँ आपका DSH प्रोफ़ाइल है और चलाएँ।',
addPluginsBrowseMore: 'GitHub पर और प्लगइन्स देखें (topic: dsh-better-sidebar)',
addPluginsBrowseMore: 'GitHub पर और प्लगइन्स देखें (topic: dsh-apes-sidebar)',
addPluginsSearch: 'प्लगइन नाम या विवरण से खोजें…',
addPluginsNoMatch: 'कोई प्लगइन मेल नहीं खाता',
addPluginsRecommended: 'अनुशंसित प्लगइन',
@@ -416,7 +416,7 @@ export const hi: Record<string, string> = {
pluginGithubWorkbenchDesc: 'GitHub Workbench टैब: रिमोट रिपॉज़िटरी ट्री + Issues / Pull requests / Actions, पढ़ने के साथ-साथ पूर्ण लेखन समर्थन — Issue/PR बनाना, टिप्पणी करना, संपादन, बंद करना/पुनः खोलना, squash·merge·rebase मर्ज (पुष्टि अनिवार्य), CI पुनः चलाना/रद्द करना; रिपॉज़िटरी स्विचर स्वतः सुलभ रिपॉज़िटरी सूचीबद्ध करता है और सार्वजनिक रिपॉज़िटरी खोजता है; better-sidebar न होने पर स्वतंत्र दाईं ओर पैनल में फ़ॉलबैक होता है',
pluginMdExportDesc: 'better-sidebar के Markdown टूलबार में "एक्सपोर्ट" बटन जोड़ता है: एक क्लिक में वर्तमान .md को स्टैंडअलोन HTML (टेबल/कोड ब्लॉक/Mermaid आरेख इनलाइन, लेआउट प्रीव्यू थीम का अनुसरण करता है) में रेंडर करके उसी फ़ोल्डर में सहेजता है, या प्रिंट डायलॉग से PDF निर्यात करता है',
pluginCodeNavDesc: 'कोड प्रीव्यू नेविगेशन: फ़ाइल प्रकार से भाषा पहचानकर सिंटैक्स हाइलाइट, सिंबल आउटलाइन (क्लास/मेथड/वेरिएबल फ़िल्टर + एक-क्लिक जंप), फ़ाइल में खोज (सभी मैच हाइलाइट, पिछला/अगला, केस मैच) — better-sidebar एडिटर में कोड फ़ाइल प्रीव्यू संभालता है',
pluginSuhuangScrollDesc: 'स्थानीय Suhuang Scroll Runtime को DSH सेटिंग्स और better-sidebar से जोड़ता है, ताकि मॉडल कॉन्फ़िगरेशन, कनेक्शन परीक्षण और निरंतर ग्रेडिंग नियंत्रण उपलब्ध हों; Suhuang Scroll Runtime और dsh-better-sidebar आवश्यक हैं',
pluginSuhuangScrollDesc: 'स्थानीय Suhuang Scroll Runtime को DSH सेटिंग्स और better-sidebar से जोड़ता है, ताकि मॉडल कॉन्फ़िगरेशन, कनेक्शन परीक्षण और निरंतर ग्रेडिंग नियंत्रण उपलब्ध हों; Suhuang Scroll Runtime और dsh-apes-sidebar आवश्यक हैं',
pluginBetterOverleafDesc: 'better-sidebar के लिए Overleaf टैब: सीधे CDP ब्राउज़र से लॉगिन (तीसरे पक्ष का Chromium समर्थित), प्रोजेक्ट सूची/स्विच, <workspace>/overleaf/ में स्थानीय git मिरर, रीड-ओनली API फ़ॉलबैक के साथ द्विदिश git सिंक, और फ़ाइल प्रीव्यू साइडबार वर्कबेंच से',
pluginGitRemotesDesc: 'Git Remotes टैब: ब्रांच/अपस्ट्रीम/ahead-behind, fetch (वैकल्पिक prune), ff-only pull, और केवल टैब-में पुष्टि के बाद push। अंतर्निहित Git स्टेज/कमिट टैब को प्रतिस्थापित नहीं करता, और force-push या मॉडल ऑटो-push टूल नहीं देता',
pluginSentinelDesc: 'स्थिति-संचालित एजेंट जागरूकता: फ़ाइल/प्रक्रिया/पोर्ट/http/कमांड/webhook सेंसर स्थितियाँ पूरी होने पर निष्क्रिय सत्र जगाते हैं; सर्वर-व्यापी वॉच टेबल के साथ "Sentinel" टैब पंजीकृत करता है',
+2 -2
View File
@@ -401,7 +401,7 @@ export const id: Record<string, string> = {
addPluginsViewerCardDesc: 'Daftarkan pratinjau tipe berkas',
addPluginsTabDesc: 'Halaman sidebar (tab) dapat diperluas oleh plugin. Plugin mendaftar melalui layanan ctx.betterSidebar; klik Install menyalin perintah instalasi — tempel ke terminal tempat profil DSH Anda berada dan jalankan.',
addPluginsViewerDesc: 'Pratinjau berkas dapat diperluas oleh plugin. Plugin mendaftar melalui layanan ctx.betterSidebar; klik Install menyalin perintah instalasi — tempel ke terminal tempat profil DSH Anda berada dan jalankan.',
addPluginsBrowseMore: 'Jelajahi lebih banyak plugin di GitHub (topic: dsh-better-sidebar)',
addPluginsBrowseMore: 'Jelajahi lebih banyak plugin di GitHub (topic: dsh-apes-sidebar)',
addPluginsSearch: 'Cari berdasarkan nama / deskripsi plugin…',
addPluginsNoMatch: 'Tidak ada plugin yang cocok',
addPluginsRecommended: 'Plugin yang direkomendasikan',
@@ -414,7 +414,7 @@ export const id: Record<string, string> = {
pluginGithubWorkbenchDesc: 'Tab GitHub Workbench: pohon repo remote + Issues / Pull requests / Actions dengan dukungan tulis penuh — buat Issue/PR, komentar, edit, tutup/buka ulang, merge squash·merge·rebase (konfirmasi kuat), jalankan ulang/batalkan CI; pengalih repo otomatis mencantumkan repo yang dapat diakses dan mencari repo publik; tanpa better-sidebar otomatis beralih ke panel kanan mandiri',
pluginMdExportDesc: 'Menambahkan tombol "Ekspor" ke bilah alat Markdown di better-sidebar: merender .md saat ini menjadi HTML mandiri dalam satu klik (tabel/blok kode/diagram Mermaid inline, tata letak mengikuti tema pratinjau) dan menyimpannya di folder yang sama, atau mengekspor ke PDF lewat dialog cetak',
pluginCodeNavDesc: 'Navigasi pratinjau kode: mengenali bahasa dari jenis berkas dan menyorot sintaks, kerangka simbol (filter kelas/metode/variabel + lompat sekali klik), serta pencarian dalam berkas (sorot semua kecocokan, sebelum/berikutnya, peka huruf besar-kecil) — mengambil alih pratinjau berkas kode di editor better-sidebar',
pluginSuhuangScrollDesc: 'Menghubungkan Suhuang Scroll Runtime lokal ke pengaturan DSH dan better-sidebar untuk konfigurasi model, uji koneksi, dan kontrol penilaian berkelanjutan; memerlukan Suhuang Scroll Runtime dan dsh-better-sidebar',
pluginSuhuangScrollDesc: 'Menghubungkan Suhuang Scroll Runtime lokal ke pengaturan DSH dan better-sidebar untuk konfigurasi model, uji koneksi, dan kontrol penilaian berkelanjutan; memerlukan Suhuang Scroll Runtime dan dsh-apes-sidebar',
pluginBetterOverleafDesc: 'Tab Overleaf untuk better-sidebar: login langsung lewat peramban CDP (mendukung Chromium pihak ketiga), daftar/pergantian proyek, mirror git lokal di <workspace>/overleaf/, sinkronisasi git dua arah dengan fallback API baca-saja, dan pratinjau berkas melalui workbench sidebar',
pluginGitRemotesDesc: 'Tab Git Remotes: branch/upstream/ahead-behind, fetch (prune opsional), ff-only pull, dan push hanya setelah konfirmasi dalam tab. Tidak menggantikan tab stage/commit Git bawaan, dan tidak menawarkan force-push atau alat auto-push model',
pluginSentinelDesc: 'Sistem bangun agen berbasis kondisi: sensor berkas/proses/port/http/perintah/webhook membangunkan sesi yang tidur saat kondisi terpicu; mendaftarkan tab "Sentinel" dengan tabel pengawasan server-wide',
+3 -3
View File
@@ -3,7 +3,7 @@
* consumed by the better-locale override store (see `locales.ts` for the
* zh/en/ja lookup chain and the `attachBetterLocale` contract).
*
* @module dsh-better-sidebar/client/locales-it
* @module dsh-apes-sidebar/client/locales-it
*/
export const it: Record<string, string> = {
@@ -394,7 +394,7 @@ export const it: Record<string, string> = {
addPluginsViewerCardDesc: 'Registri un’anteprima di tipo di file',
addPluginsTabDesc: 'Le pagine della barra laterale (schede) possono essere estese dai plugin. I plugin si registrano tramite il servizio ctx.betterSidebar; cliccando Installa viene copiato il comando di installazione — lo incolli in un terminale dove risiede il profilo DSH e lo esegua.',
addPluginsViewerDesc: 'I visualizzatori di file possono essere estesi dai plugin. I plugin si registrano tramite il servizio ctx.betterSidebar; cliccando Installa viene copiato il comando di installazione — lo incolli in un terminale dove risiede il profilo DSH e lo esegua.',
addPluginsBrowseMore: 'Sfogli altri plugin su GitHub (topic: dsh-better-sidebar)',
addPluginsBrowseMore: 'Sfogli altri plugin su GitHub (topic: dsh-apes-sidebar)',
addPluginsSearch: 'Cerca per nome / descrizione del plugin…',
addPluginsNoMatch: 'Nessun plugin corrispondente',
addPluginsRecommended: 'Plugin consigliati',
@@ -407,7 +407,7 @@ export const it: Record<string, string> = {
pluginGithubWorkbenchDesc: 'Scheda GitHub Workbench: albero del repository remoto + Issues / Pull requests / Actions con pieno supporto in scrittura — creazione di issue/PR, commenti, modifica, chiusura/riapertura, merge squash·merge·rebase (conferma rafforzata), riavvio/annullamento della CI; il selettore dei repository elenca automaticamente quelli accessibili e cerca tra quelli pubblici; senza better-sidebar esegue il fallback a un pannello laterale destro indipendente',
pluginMdExportDesc: 'Aggiunge un pulsante «Esporta» alla barra degli strumenti Markdown di better-sidebar: con un clic esegue il rendering del .md corrente in HTML autonomo (tabelle/blocchi di codice/diagrammi Mermaid in linea, layout fedele al tema di anteprima) e lo salva accanto al .md, oppure esporta in PDF tramite la finestra di stampa',
pluginCodeNavDesc: 'Navigazione dell’anteprima del codice: rileva il linguaggio dal tipo di file ed evidenzia la sintassi, struttura dei simboli (filtri classi/metodi/variabili + salto con un clic) e ricerca nel file (tutte le corrispondenze evidenziate, precedente/successiva, maiuscole/minuscole) — gestisce l’anteprima dei file di codice nell’editor better-sidebar',
pluginSuhuangScrollDesc: 'Collega il Runtime Suhuang Scroll locale alle impostazioni DSH e a better-sidebar per configurare il modello, testare la connessione e controllare la correzione continua; richiede Suhuang Scroll Runtime e dsh-better-sidebar',
pluginSuhuangScrollDesc: 'Collega il Runtime Suhuang Scroll locale alle impostazioni DSH e a better-sidebar per configurare il modello, testare la connessione e controllare la correzione continua; richiede Suhuang Scroll Runtime e dsh-apes-sidebar',
pluginBetterOverleafDesc: 'Tab Overleaf per better-sidebar: accesso diretto tramite browser CDP (supporta Chromium di terze parti), elenco/cambio progetti, mirror git locali in <workspace>/overleaf/, sincronizzazione git bidirezionale con fallback API di sola lettura e anteprima dei file tramite la workbench della barra laterale',
pluginGitRemotesDesc: 'Scheda Git Remotes: rami/upstream/ahead-behind, fetch (prune opzionale), pull ff-only e push solo dopo una conferma nella scheda. Non sostituisce la scheda stage/commit integrata di Git, e non offre force-push o uno strumento di push automatico del modello',
pluginSentinelDesc: 'Sistema di risveglio dell’agente guidato da condizioni: sensori file/processo/porta/HTTP/comando/webhook risvegliano le sessioni dormienti quando le condizioni si verificano; registra una scheda «Sentinella» con la tabella di monitoraggio globale del server',
+2 -2
View File
@@ -403,7 +403,7 @@ export const ja: Record<string, string> = {
addPluginsViewerCardDesc: '新しいファイル種別プレビューを登録',
addPluginsTabDesc: 'サイドバーページ(Tab)はプラグインで拡張可能。プラグインは ctx.betterSidebar サービス経由で登録。「インストール」をクリックしてインストールコマンドをコピー、DSH 環境のターミナルに貼り付けて実行。',
addPluginsViewerDesc: 'ファイルプレビューアーはプラグインで拡張可能。プラグインは ctx.betterSidebar サービス経由で登録。「インストール」をクリックしてインストールコマンドをコピー、DSH 環境のターミナルに貼り付けて実行。',
addPluginsBrowseMore: 'GitHub でプラグинをさらに閲覧(topic: dsh-better-sidebar)',
addPluginsBrowseMore: 'GitHub でプラグинをさらに閲覧(topic: dsh-apes-sidebar)',
addPluginsSearch: 'プラグイン名 / 説明で検索…',
addPluginsNoMatch: '一致するプラグインなし',
addPluginsRecommended: '推奨プラグイン',
@@ -415,7 +415,7 @@ export const ja: Record<string, string> = {
pluginFlowglassDesc: 'ライブセッションフローグラフ:ユーザー、アシスタント、ツール呼び出しの 3 レーン泳道、並列グループ、サブエージェント支線、ドリルダウン、ライブ状態をサポート。better-sidebar インストール時にネイティブ「Flowglass」Tab を登録、未インストール時は独立ドロワーを保持',
pluginGitForgeDesc: 'better-sidebar「Git Forge」Tab:GitHub/Gitea 等 Forge アカウントライブラリ + プロジェクト単位の認可 + push ポリシー硬核拒絶。token はローカル secrets のみ、モデルコンテキストに入らない。読み取り専用 GitForge ツールと agent HTTPS credential helper を提供',
pluginGithubWorkbenchDesc: 'better-sidebar「GitHub ワークベンチ」タブ:リモートリポジトリのツリー + Issues / Pull requests / Actions タブ。読み取りに加え、Issue・PR の作成、コメント、編集、クローズ/再オープン、squash・merge・rebase マージ(強確認)、CI の再実行/キャンセルに対応。リポジトリ切替ポップアップはアクセス可能なリポジトリを自動取得し、公開リポジトリの検索にも対応。better-sidebar 未導入時は独立右パネルへ自動フォールバック',
pluginSuhuangScrollDesc: 'ローカルの Suhuang Scroll Runtime を DSH 設定と better-sidebar に接続し、モデル設定、接続テスト、連続採点の操作を提供。Suhuang Scroll Runtime と dsh-better-sidebar が必要',
pluginSuhuangScrollDesc: 'ローカルの Suhuang Scroll Runtime を DSH 設定と better-sidebar に接続し、モデル設定、接続テスト、連続採点の操作を提供。Suhuang Scroll Runtime と dsh-apes-sidebar が必要',
pluginBetterOverleafDesc: 'better-sidebar の Overleaf タブ:CDP ブラウザーに直接接続してログイン(サードパーティー Chromium 対応)、プロジェクト一覧/切替、<workspace>/overleaf/ のローカル git ミラー、双方向 git 同期(API 読み取り専用フォールバック)、ファイルプレビューはサイドバーのワークベンチを利用',
pluginGitRemotesDesc: 'better-sidebar Git リモート Tab:ブランチ/アップストリーム/ahead-behind 表示、fetch(prune 可)、ff-only pull、確認後のみ push。内蔵 Git のステージ/コミットは置き換えず、force-push やモデル自動 push ツールは提供しない',
pluginSentinelDesc: '条件駆動の agent 起動システム:ファイル/プロセス/ポート/HTTP/コマンド/webhook センサーが条件達成時に休眠会話を自動起動。「Sentinel」Tab を登録しサーバー全体の監視テーブルを表示',
+2 -2
View File
@@ -395,7 +395,7 @@ export const ko: Record<string, string> = {
addPluginsViewerCardDesc: '새 파일 형식 미리보기 등록',
addPluginsTabDesc: '사이드바 페이지(Tab)는 플러그인으로 확장할 수 있습니다. 플러그인은 ctx.betterSidebar 서비스를 통해 등록합니다. "설치"를 클릭하면 설치 명령이 복사되며, DSH가 설치된 환경의 터미널에 붙여넣어 실행하세요.',
addPluginsViewerDesc: '파일 미리보기는 플러그인으로 확장할 수 있습니다. 플러그인은 ctx.betterSidebar 서비스를 통해 등록합니다. "설치"를 클릭하면 설치 명령이 복사되며, DSH가 설치된 환경의 터미널에 붙여넣어 실행하세요.',
addPluginsBrowseMore: 'GitHub에서 더 많은 플러그인 보기(topic: dsh-better-sidebar)',
addPluginsBrowseMore: 'GitHub에서 더 많은 플러그인 보기(topic: dsh-apes-sidebar)',
addPluginsSearch: '플러그인 이름 / 설명 검색…',
addPluginsNoMatch: '일치하는 플러그인 없음',
addPluginsRecommended: '추천 플러그인',
@@ -408,7 +408,7 @@ export const ko: Record<string, string> = {
pluginGithubWorkbenchDesc: 'better-sidebar "GitHub Workbench" Tab: 원격 저장소 트리 + Issues / Pull requests / Actions 탭. 읽기 외에 Issue·PR 생성, 댓글, 편집, 닫기/다시 열기, squash·merge·rebase 병합(강한 확인), CI 재실행/취소를 지원합니다. 저장소 전환 팝업은 접근 가능한 저장소를 자동으로 가져오고 공개 저장소 검색도 지원합니다. better-sidebar 미설치 시 독립 우측 패널로 자동 폴백합니다',
pluginMdExportDesc: 'better-sidebar의 Markdown 툴바에 "내보내기" 버튼을 추가: 클릭 한 번으로 현재 .md를 독립 실행형 HTML(표/코드 블록/Mermaid 다이어그램 인라인, 레이아웃은 미리보기 테마 따름)로 렌더링해 같은 폴더에 저장하거나, 인쇄 대화상자로 PDF를 내보냅니다',
pluginCodeNavDesc: '코드 미리보기 내비게이션: 파일 형식으로 언어를 자동 인식해 구문 강조, 심볼 아웃라인(클래스/메서드/변수 필터 + 원클릭 이동), 파일 내 검색(모든 일치 강조, 이전/다음, 대소문자 구분) — better-sidebar 편집기의 코드 파일 미리보기를 대신 맡습니다',
pluginSuhuangScrollDesc: '로컬 Suhuang Scroll Runtime을 DSH 설정과 better-sidebar에 연결하여 모델 구성, 연결 테스트, 연속 채점 제어를 제공합니다. Suhuang Scroll Runtime과 dsh-better-sidebar가 필요합니다',
pluginSuhuangScrollDesc: '로컬 Suhuang Scroll Runtime을 DSH 설정과 better-sidebar에 연결하여 모델 구성, 연결 테스트, 연속 채점 제어를 제공합니다. Suhuang Scroll Runtime과 dsh-apes-sidebar가 필요합니다',
pluginBetterOverleafDesc: 'better-sidebar의 Overleaf 탭: CDP 브라우저 직접 로그인(서드파티 Chromium 지원), 프로젝트 목록/전환, <workspace>/overleaf/ 로컬 git 미러, 읽기 전용 API 폴백이 있는 양방향 git 동기화, 파일 미리보기는 사이드바 워크벤치 사용',
pluginGitRemotesDesc: 'better-sidebar Git 원격 Tab: 브랜치/업스트림/ahead-behind 확인, fetch(prune 가능), ff-only pull, 확인 후에만 push. 내장 Git의 스테이징/커밋을 대체하지 않으며, force-push나 모델 자동 push도 제공하지 않습니다',
pluginSentinelDesc: '조건 기반 agent 웨이크업 시스템: 파일/프로세스/포트/HTTP/명령/webhook 센서가 조건을 충족하면 휴면 세션을 자동으로 깨웁니다. "센티널(哨兵)" Tab을 등록하여 서버 전역 모니터링 표를 보여줍니다',
+2 -2
View File
@@ -401,7 +401,7 @@ export const nl: Record<string, string> = {
addPluginsViewerCardDesc: 'Een bestandstype-preview registreren',
addPluginsTabDesc: 'Zijbalkpagina\'s (tabbladen) kunnen door plugins worden uitgebreid. Plugins registreren via de ctx.betterSidebar-service; op Installeren klikken kopieert het installatiecommando — plak het in een terminal waar uw DSH-profiel zich bevindt en voer het uit.',
addPluginsViewerDesc: 'Bestandsviewers kunnen door plugins worden uitgebreid. Plugins registreren via de ctx.betterSidebar-service; op Installeren klikken kopieert het installatiecommando — plak het in een terminal waar uw DSH-profiel zich bevindt en voer het uit.',
addPluginsBrowseMore: 'Meer plugins bekijken op GitHub (topic: dsh-better-sidebar)',
addPluginsBrowseMore: 'Meer plugins bekijken op GitHub (topic: dsh-apes-sidebar)',
addPluginsSearch: 'Zoek op plugin-naam of -beschrijving…',
addPluginsNoMatch: 'Geen plugins komen overeen',
addPluginsRecommended: 'Aanbevolen plugins',
@@ -414,7 +414,7 @@ export const nl: Record<string, string> = {
pluginGithubWorkbenchDesc: 'GitHub Workbench-tab: externe repositoryboom + Issues / Pull requests / Actions met volledige schrijfontsteuning — issue/PR aanmaken, reageren, bewerken, sluiten/heropenen, squash·merge·rebase-merge (sterke bevestiging), CI opnieuw uitvoeren/annuleren; de repo-wisselaar toont automatisch toegankelijke repo’s en doorzoekt openbare; zonder better-sidebar valt het terug op een zelfstandig rechterpaneel',
pluginMdExportDesc: 'Voegt een knop «Exporteren» toe aan de Markdown-werkbalk van better-sidebar: rendert de huidige .md in één klik naar zelfstandige HTML (tabellen/codeblokken/Mermaid-diagrammen inline, lay-out volgt het voorbeeldthema) en slaat die naast het .md op, of exporteert via het afdrukvenster naar PDF',
pluginCodeNavDesc: 'Codevoorbeeldnavigatie: detecteert de taal op bestandstype en kleurt de syntaxis, symbooloverzicht (filters voor klassen/methoden/variabelen + sprong met één klik) en zoeken in het bestand (alle treffers gemarkeerd, vorige/volgende, hoofdlettergevoelig) — neemt het codevoorbeeld in de better-sidebar-editor over',
pluginSuhuangScrollDesc: 'Verbindt de lokale Suhuang Scroll Runtime met DSH-instellingen en better-sidebar voor modelconfiguratie, verbindingstests en doorlopende beoordelingsbediening; vereist Suhuang Scroll Runtime en dsh-better-sidebar',
pluginSuhuangScrollDesc: 'Verbindt de lokale Suhuang Scroll Runtime met DSH-instellingen en better-sidebar voor modelconfiguratie, verbindingstests en doorlopende beoordelingsbediening; vereist Suhuang Scroll Runtime en dsh-apes-sidebar',
pluginBetterOverleafDesc: 'Overleaf-tabblad voor better-sidebar: directe browserlogin via CDP (ondersteunt Chromium van derden), projectlijst/wisselen, lokale git-spiegels onder <workspace>/overleaf/, tweerichtingssynchronisatie met read-only-API-terugval en bestandsvoorbeeld via de sidebar-werkbank',
pluginGitRemotesDesc: 'Git Remotes-tabblad: branch/upstream/ahead-behind, fetch (optioneel prune), ff-only pull, en push pas na een bevestiging in het tabblad. Vervangt het ingebouwde Git stage/commit-tabblad niet, en biedt geen force-push of een model-auto-push-tool',
pluginSentinelDesc: 'Conditie-gedreven agent-wakeupsysteem: bestands/proces/poort/http/command/webhook-sensoren wekken slapende sessies wanneer condities af gaan; registreert een "Sentinel"-tabblad met de serverbrede watch-tabel',
+2 -2
View File
@@ -405,7 +405,7 @@ export const pl: Record<string, string> = {
addPluginsViewerCardDesc: 'Zarejestruj podgląd typu pliku',
addPluginsTabDesc: 'Strony panelu bocznego (karty) mogą być rozszerzane przez wtyczki. Wtyczki rejestrują się przez usługę ctx.betterSidebar; kliknięcie „Zainstaluj” kopiuje polecenie instalacji — wklej je do terminala tam, gdzie żyje Twój profil DSH, i uruchom.',
addPluginsViewerDesc: 'Podglądy plików mogą być rozszerzane przez wtyczki. Wtyczki rejestrują się przez usługę ctx.betterSidebar; kliknięcie „Zainstaluj” kopiuje polecenie instalacji — wklej je do terminala tam, gdzie żyje Twój profil DSH, i uruchom.',
addPluginsBrowseMore: 'Przeglądaj więcej wtyczek na GitHub (topic: dsh-better-sidebar)',
addPluginsBrowseMore: 'Przeglądaj więcej wtyczek na GitHub (topic: dsh-apes-sidebar)',
addPluginsSearch: 'Szukaj po nazwie lub opisie wtyczki…',
addPluginsNoMatch: 'Brak pasujących wtyczek',
addPluginsRecommended: 'Zalecane wtyczki',
@@ -418,7 +418,7 @@ export const pl: Record<string, string> = {
pluginGithubWorkbenchDesc: 'Karta GitHub Workbench: drzewo zdalnego repozytorium + Issues / Pull requests / Actions z pełną obsługą zapisu — tworzenie issue/PR, komentowanie, edycja, zamykanie/ponowne otwieranie, scalanie squash·merge·rebase (mocne potwierdzenie), ponowne uruchomienie/anulowanie CI; przełącznik repozytoriów automatycznie wyświetla dostępne repozytoria i wyszukuje publiczne; bez better-sidebar następuje przejście na samodzielny panel po prawej stronie',
pluginMdExportDesc: 'Dodaje przycisk „Eksportuj" do paska narzędzi Markdown w better-sidebar: jednym kliknięciem renderuje bieżący .md do samodzielnego HTML (tabele/bloki kodu/diagramy Mermaid w treści, układ zgodny z motywem podglądu) i zapisuje obok pliku .md albo eksportuje do PDF przez okno drukowania',
pluginCodeNavDesc: 'Nawigacja podglądu kodu: rozpoznaje język po typie pliku i koloruje składnię, konspekt symboli (filtry klas/metod/zmiennych + skok jednym kliknięciem) oraz wyszukiwanie w pliku (podświetlanie wszystkich trafień, poprzedni/następny, rozróżnianie wielkości liter) — przejmuje podgląd plików kodu w edytorze better-sidebar',
pluginSuhuangScrollDesc: 'Łączy lokalny Suhuang Scroll Runtime z ustawieniami DSH i better-sidebar, zapewniając konfigurację modelu, test połączenia i sterowanie ciągłym ocenianiem; wymaga Suhuang Scroll Runtime i dsh-better-sidebar',
pluginSuhuangScrollDesc: 'Łączy lokalny Suhuang Scroll Runtime z ustawieniami DSH i better-sidebar, zapewniając konfigurację modelu, test połączenia i sterowanie ciągłym ocenianiem; wymaga Suhuang Scroll Runtime i dsh-apes-sidebar',
pluginBetterOverleafDesc: 'Karta Overleaf dla better-sidebar: logowanie bezpośrednio przez przeglądarkę CDP (obsługa Chromium innych firm), lista/przełączanie projektów, lokalne kopie lustrzane git w <workspace>/overleaf/, dwukierunkowa synchronizacja git z awaryjnym API tylko do odczytu oraz podgląd plików przez warsztat panelu bocznego',
pluginGitRemotesDesc: 'Karta Git Remotes: gałąź/nadrzędne/ahead-behind, fetch (z opcjonalnym prune), ff-only pull i push dopiero po potwierdzeniu w karcie. Nie zastępuje wbudowanej karty Git stage/commit i nie oferuje force-push ani narzędzia auto-push modelu',
pluginSentinelDesc: 'Budzenie agenta sterowane warunkami: czujniki plików/procesów/portów/HTTP/poleceń/webhooków wybudzają uśpione sesje, gdy warunek zajdzie; rejestruje kartę „Sentinel” z ogólnoserwerową tabelą obserwacji',
+2 -2
View File
@@ -386,7 +386,7 @@ export const pt: Record<string, string> = {
addPluginsViewerCardDesc: 'Registrar uma pré-visualização de tipo de arquivo',
addPluginsTabDesc: 'As páginas da barra lateral (abas) podem ser estendidas por plugins. Os plugins registram pelo serviço ctx.betterSidebar; clicar em Instalar copia o comando de instalação — cole-o em um terminal onde seu perfil do DSH vive e execute.',
addPluginsViewerDesc: 'Os visualizadores de arquivos podem ser estendidos por plugins. Os plugins registram pelo serviço ctx.betterSidebar; clicar em Instalar copia o comando de instalação — cole-o em um terminal onde seu perfil do DSH vive e execute.',
addPluginsBrowseMore: 'Navegue por mais plugins no GitHub (topic: dsh-better-sidebar)',
addPluginsBrowseMore: 'Navegue por mais plugins no GitHub (topic: dsh-apes-sidebar)',
addPluginsSearch: 'Pesquisar por nome ou descrição do plugin…',
addPluginsNoMatch: 'Nenhum plugin corresponde',
addPluginsRecommended: 'Plugins recomendados',
@@ -399,7 +399,7 @@ export const pt: Record<string, string> = {
pluginGithubWorkbenchDesc: 'Aba GitHub Workbench: árvore do repositório remoto + Issues / Pull requests / Actions com suporte completo de escrita — criar Issue/PR, comentar, editar, fechar/reabrir, merge squash·merge·rebase (confirmação reforçada), reexecutar/cancelar CI; o seletor de repositórios lista automaticamente os repositórios acessíveis e busca nos públicos; sem better-sidebar, faz fallback para um painel lateral direito independente',
pluginMdExportDesc: 'Adiciona um botão «Exportar» à barra de ferramentas do Markdown no better-sidebar: com um clique, renderiza o .md atual em HTML autônomo (tabelas/blocos de código/diagramas Mermaid embutidos, layout seguindo o tema de pré-visualização) e salva na mesma pasta, ou exporta para PDF pela caixa de impressão',
pluginCodeNavDesc: 'Navegação de pré-visualização de código: detecta a linguagem pelo tipo de arquivo e destaca a sintaxe, contorno de símbolos (filtros de classes/métodos/variáveis + salto em um clique) e busca no arquivo (todas as ocorrências destacadas, anterior/próxima, diferenciando maiúsculas) — assume a pré-visualização de arquivos de código no editor do better-sidebar',
pluginSuhuangScrollDesc: 'Conecta o Suhuang Scroll Runtime local às configurações do DSH e ao better-sidebar para configurar o modelo, testar a conexão e controlar a correção contínua; requer Suhuang Scroll Runtime e dsh-better-sidebar',
pluginSuhuangScrollDesc: 'Conecta o Suhuang Scroll Runtime local às configurações do DSH e ao better-sidebar para configurar o modelo, testar a conexão e controlar a correção contínua; requer Suhuang Scroll Runtime e dsh-apes-sidebar',
pluginBetterOverleafDesc: 'Aba Overleaf para better-sidebar: login direto pelo navegador via CDP (compatível com Chromium de terceiros), lista/troca de projetos, espelhos git locais em <workspace>/overleaf/, sincronização git bidirecional com fallback de API somente leitura e pré-visualização de arquivos pela bancada da barra lateral',
pluginGitRemotesDesc: 'Aba de remotos Git: branch/upstream/ahead-behind, fetch (prune opcional), pull ff-only e push apenas após confirmar na aba. Não substitui a aba interna de stage/commit do Git e não oferece force-push nem push automático pelo modelo',
pluginSentinelDesc: 'Despertar de agentes dirigido por condições: sensores de arquivo/processo/porta/HTTP/comando/webhook acordam sessões adormecidas quando as condições disparam; registra uma aba "Sentinelas" com a tabela de monitoramento global do servidor',
+2 -2
View File
@@ -401,7 +401,7 @@ export const ru: Record<string, string> = {
addPluginsViewerCardDesc: 'Зарегистрировать предпросмотр типа файлов',
addPluginsTabDesc: 'Страницы боковой панели (вкладки) расширяются плагинами. Плагины регистрируются через службу ctx.betterSidebar; щелчок по «Установить» копирует команду — вставьте её в терминал, где находится профиль DSH, и выполните.',
addPluginsViewerDesc: 'Просмотрщики файлов расширяются плагинами. Плагины регистрируются через службу ctx.betterSidebar; щелчок по «Установить» копирует команду — вставьте её в терминал, где находится профиль DSH, и выполните.',
addPluginsBrowseMore: 'Найти больше плагинов на GitHub (topic: dsh-better-sidebar)',
addPluginsBrowseMore: 'Найти больше плагинов на GitHub (topic: dsh-apes-sidebar)',
addPluginsSearch: 'Поиск по имени или описанию плагина…',
addPluginsNoMatch: 'Нет совпадающих плагинов',
addPluginsRecommended: 'Рекомендуемые плагины',
@@ -414,7 +414,7 @@ export const ru: Record<string, string> = {
pluginGithubWorkbenchDesc: 'Вкладка «GitHub Workbench»: дерево удалённого репозитория + Issues / Pull requests / Actions с полной поддержкой записи — создание Issue/PR, комментарии, редактирование, закрытие/повторное открытие, merge squash·merge·rebase (с сильным подтверждением), перезапуск/отмена CI; переключатель репозиториев автоматически показывает доступные репозитории и ищет по публичным; без better-sidebar используется отдельная правая панель',
pluginMdExportDesc: 'Добавляет кнопку «Экспорт» на панель инструментов Markdown в better-sidebar: одним кликом рендерит текущий .md в самостоятельный HTML (таблицы/блоки кода/Mermaid-диаграммы встроены, вёрстка следует теме предпросмотра) и сохраняет рядом с .md либо экспортирует в PDF через диалог печати',
pluginCodeNavDesc: 'Навигация по предпросмотру кода: определяет язык по типу файла и подсвечивает синтаксис, схема символов (фильтры классов/методов/переменных + переход в один клик) и поиск по файлу (подсветка всех совпадений, назад/вперёд, учёт регистра) — берёт на себя предпросмотр файлов кода в редакторе better-sidebar',
pluginSuhuangScrollDesc: 'Подключает локальный Suhuang Scroll Runtime к настройкам DSH и better-sidebar для настройки модели, проверки соединения и управления непрерывной проверкой; требуются Suhuang Scroll Runtime и dsh-better-sidebar',
pluginSuhuangScrollDesc: 'Подключает локальный Suhuang Scroll Runtime к настройкам DSH и better-sidebar для настройки модели, проверки соединения и управления непрерывной проверкой; требуются Suhuang Scroll Runtime и dsh-apes-sidebar',
pluginBetterOverleafDesc: 'Вкладка Overleaf для better-sidebar: вход напрямую через CDP-браузер (поддержка стороннего Chromium), список/переключение проектов, локальные git-зеркала в <workspace>/overleaf/, двунаправленная синхронизация git с откатом к API только для чтения, просмотр файлов через рабочую панель сайдбара',
pluginGitRemotesDesc: 'Вкладка Git Remotes: просмотр веток/апстрима/ahead-behind, fetch (с возможностью prune), ff-only pull и push только после подтверждения во вкладке. Не заменяет встроенную вкладку Git (индексирование/коммит), не предоставляет force-push и инструмент авто-push для модели',
pluginSentinelDesc: 'Система пробуждения агента по условиям: сенсоры файлов/процессов/портов/HTTP/команд/webhook будят спящие сессии при срабатывании; регистрирует вкладку «Sentinel» с глобальной таблицей наблюдений сервера',
+2 -2
View File
@@ -386,7 +386,7 @@ export const sv: Record<string, string> = {
addPluginsViewerCardDesc: 'Registrera en filtypsförhandsgranskning',
addPluginsTabDesc: 'Sidopanelens sidor (flikar) kan utökas av plugins. Plugins registreras via ctx.betterSidebar-tjänsten; att klicka på Installera kopierar installationskommandot — klistra in det i en terminal där er DSH-profil lever och kör det.',
addPluginsViewerDesc: 'Filförhandsgranskare kan utökas av plugins. Plugins registreras via ctx.betterSidebar-tjänsten; att klicka på Installera kopierar installationskommandot — klistra in det i en terminal där er DSH-profil lever och kör det.',
addPluginsBrowseMore: 'Bläddra bland fler plugins på GitHub (topic: dsh-better-sidebar)',
addPluginsBrowseMore: 'Bläddra bland fler plugins på GitHub (topic: dsh-apes-sidebar)',
addPluginsSearch: 'Sök på plugin-namn eller beskrivning…',
addPluginsNoMatch: 'Inga plugins matchar',
addPluginsRecommended: 'Rekommenderade plugins',
@@ -399,7 +399,7 @@ export const sv: Record<string, string> = {
pluginGithubWorkbenchDesc: 'GitHub Workbench-flik: fjärrrepo-träd + Issues / Pull requests / Actions med fullt skrivstöd — skapa issue/PR, kommentera, redigera, stäng/öppna igen, squash·merge·rebase-merge (stark bekräftelse), kör om/avbryt CI; repoväxlaren listar automatiskt tillgängliga repon och söker i publika; utan better-sidebar växlar den till en fristående höger panel',
pluginMdExportDesc: 'Lägger till en «Exportera»-knapp i Markdown-verktygsfältet i better-sidebar: renderar nuvarande .md till fristående HTML i ett klick (tabeller/kodblock/Mermaid-diagram inline, layout följer förhandsvisningstemat) och sparar bredvid .md-filen, eller exporterar till PDF via utskriftsdialogen',
pluginCodeNavDesc: 'Kodförhandsvisningsnavigering: känner automatiskt av språket från filtypen och färglägger syntaxen, symbolöversikt (filter för klasser/metoder/variabler + hopp med ett klick) och filsökning (markera alla träffar, föregående/nästa, skiftlägeskänsligt) — tar över kodfilförhandsvisning i better-sidebar-redigeraren',
pluginSuhuangScrollDesc: 'Ansluter den lokala Suhuang Scroll Runtime till DSH-inställningar och better-sidebar för modellkonfiguration, anslutningstest och styrning av kontinuerlig rättning; kräver Suhuang Scroll Runtime och dsh-better-sidebar',
pluginSuhuangScrollDesc: 'Ansluter den lokala Suhuang Scroll Runtime till DSH-inställningar och better-sidebar för modellkonfiguration, anslutningstest och styrning av kontinuerlig rättning; kräver Suhuang Scroll Runtime och dsh-apes-sidebar',
pluginBetterOverleafDesc: 'Overleaf-flik för better-sidebar: direktinloggning via CDP-webbläsare (stöd för tredjeparts-Chromium), projektlista/byte, lokala git-speglar under <workspace>/overleaf/, dubbelriktad git-synk med read-only-API-fallback och förhandsvisning av filer via sidofältets workbench',
pluginGitRemotesDesc: 'Git Remotes-flik: gren/upstream/ahead-behind, fetch (valbar prune), ff-only pull och push endast efter bekräftelse i fliken. Ersätter inte den inbyggda Git-köa/commit-fliken, och erbjuder inte force-push eller ett model-auto-push-verktyg',
pluginSentinelDesc: 'Villkorsdriven agentväckning: fil-/process-/port-/http-/kommando-/webhook-sensorer väcker vilande sessioner när villkor utlöses; registrerar en "Sentinel"-flik med serverns globala bevakningstabell',
+2 -2
View File
@@ -403,7 +403,7 @@ export const th: Record<string, string> = {
addPluginsViewerCardDesc: 'ลงทะเบียนพรีวิวประเภทไฟล์',
addPluginsTabDesc: 'หน้าแถบด้านข้าง (tab) สามารถขยายได้ด้วยปลั๊กอิน ปลั๊กอินลงทะเบียนผ่านบริการ ctx.betterSidebar; การคลิก Install จะคัดลอกคำสั่งติดตั้ง — วางลงในเทอร์มินัลที่ DSH profile ของคุณอยู่แล้วเรียกใช้',
addPluginsViewerDesc: 'ตัวพรีวิวไฟล์สามารถขยายได้ด้วยปลั๊กอิน ปลั๊กอินลงทะเบียนผ่านบริการ ctx.betterSidebar; การคลิก Install จะคัดลอกคำสั่งติดตั้ง — วางลงในเทอร์มินัลที่ DSH profile ของคุณอยู่แล้วเรียกใช้',
addPluginsBrowseMore: 'เรียกดูปลั๊กอินเพิ่มเติมบน GitHub (topic: dsh-better-sidebar)',
addPluginsBrowseMore: 'เรียกดูปลั๊กอินเพิ่มเติมบน GitHub (topic: dsh-apes-sidebar)',
addPluginsSearch: 'ค้นหาตามชื่อหรือคำอธิบายปลั๊กอิน…',
addPluginsNoMatch: 'ไม่มีปลั๊กอินที่ตรง',
addPluginsRecommended: 'ปลั๊กอินแนะนำ',
@@ -416,7 +416,7 @@ export const th: Record<string, string> = {
pluginGithubWorkbenchDesc: 'แท็บ GitHub Workbench: โครงสร้าง repo รีโมต + Issues / Pull requests / Actions พร้อมรองรับการเขียนครบวงจร — สร้าง Issue/PR, แสดงความคิดเห็น, แก้ไข, ปิด/เปิดใหม่, รวมแบบ squash·merge·rebase (ยืนยันก่อนเสมอ), รัน/ยกเลิก CI ใหม่; ตัวสลับ repo จะดึงรายการ repo ที่มีสิทธิ์เข้าถึงโดยอัตโนมัติและค้นหา repo สาธารณะได้; หากไม่ได้ติดตั้ง better-sidebar จะสลับไปใช้แผงด้านขวาแบบสแตนด์อโลนอัตโนมัติ',
pluginMdExportDesc: 'เพิ่มปุ่ม "ส่งออก" ในแถบเครื่องมือ Markdown ของ better-sidebar: เรนเดอร์ .md ปัจจุบันเป็น HTML แบบสแตนด์อโลนในคลิกเดียว (ตาราง/บล็อกโค้ด/ไดอะแกรม Mermaid แบบอินไลน์ เลย์เอาต์ตามธีมพรีวิว) แล้วบันทึกไว้โฟลเดอร์เดียวกัน หรือส่งออกเป็น PDF ผ่านกล่องโต้ตอบการพิมพ์',
pluginCodeNavDesc: 'การนำทางพรีวิวโค้ด: ระบุภาษาอัตโนมัติจากประเภทไฟล์และไฮไลต์ไวยากรณ์ โครงร่างสัญลักษณ์ (กรองคลาส/เมธอด/ตัวแปร + กระโดดคลิกเดียว) และค้นหาในไฟล์ (ไฮไลต์ทุกจุดที่ตรง ก่อนหน้า/ถัดไป แยกตัวพิมพ์ใหญ่เล็ก) — รับช่วงพรีวิวไฟล์โค้ดในตัวแก้ไข better-sidebar',
pluginSuhuangScrollDesc: 'เชื่อมต่อ Suhuang Scroll Runtime ในเครื่องกับการตั้งค่า DSH และ better-sidebar สำหรับการกำหนดค่าโมเดล การทดสอบการเชื่อมต่อ และการควบคุมการตรวจงานต่อเนื่อง โดยต้องติดตั้ง Suhuang Scroll Runtime และ dsh-better-sidebar',
pluginSuhuangScrollDesc: 'เชื่อมต่อ Suhuang Scroll Runtime ในเครื่องกับการตั้งค่า DSH และ better-sidebar สำหรับการกำหนดค่าโมเดล การทดสอบการเชื่อมต่อ และการควบคุมการตรวจงานต่อเนื่อง โดยต้องติดตั้ง Suhuang Scroll Runtime และ dsh-apes-sidebar',
pluginBetterOverleafDesc: 'แท็บ Overleaf สำหรับ better-sidebar: เข้าสู่ระบบผ่านเบราว์เซอร์ CDP โดยตรง (รองรับ Chromium ของบุคคลที่สาม) รายการ/สลับโปรเจกต์ มิเรอร์ git ภายในเครื่องที่ <workspace>/overleaf/ ซิงก์ git สองทางพร้อมสำรองแบบ API อ่านอย่างเดียว และดูตัวอย่างไฟล์ผ่านเวิร์กเบนช์ของแถบข้าง',
pluginGitRemotesDesc: 'tab Git Remotes: branch/upstream/ahead-behind, fetch (optional prune), ff-only pull และ push หลังการยืนยันใน tab เท่านั้น ไม่แทนที่ tab stage/commit ของ Git แบบ built-in และไม่เสนอ force-push หรือเครื่องมือ auto-push ของโมเดล',
pluginSentinelDesc: 'การปลุกตัวแทนที่ขับเคลื่อนด้วยเงื่อนไข: เซนเซอร์ file/process/port/http/command/webhook ปลุกเซสชันที่หลับเมื่อเงื่อนไขเกิดขึ้น; ลงทะเบียน tab "Sentinel" พร้อมตารางเฝ้าระวังทั่วทั้งเซิร์ฟเวอร์',
+2 -2
View File
@@ -403,7 +403,7 @@ export const tr: Record<string, string> = {
addPluginsViewerCardDesc: 'Yeni bir dosya türü önizlemesi kaydet',
addPluginsTabDesc: 'Kenar çubuğu sayfaları (sekmeler) eklentilerce genişletilebilir. Eklentiler ctx.betterSidebar hizmeti aracılığıyla kaydolur; Yükle’ye tıklamak kurulum komutunu kopyalar — DSH profilinizin yaşadığı bir terminale yapıştırın ve çalıştırın.',
addPluginsViewerDesc: 'Dosya önizleyicileri eklentilerce genişletilebilir. Eklentiler ctx.betterSidebar hizmeti aracılığıyla kaydolur; Yükle’ye tıklamak kurulum komutunu kopyalar — DSH profilinizin yaşadığı bir terminale yapıştırın ve çalıştırın.',
addPluginsBrowseMore: 'GitHub’da daha fazla eklentiye göz atın (topic: dsh-better-sidebar)',
addPluginsBrowseMore: 'GitHub’da daha fazla eklentiye göz atın (topic: dsh-apes-sidebar)',
addPluginsSearch: 'Eklenti adı / açıklamasına göre ara…',
addPluginsNoMatch: 'Eşleşen eklenti yok',
addPluginsRecommended: 'Önerilen eklentiler',
@@ -416,7 +416,7 @@ export const tr: Record<string, string> = {
pluginGithubWorkbenchDesc: 'GitHub Workbench sekmesi: uzak depo ağacı + Issues / Pull requests / Actions tam yazma desteğiyle — Issue/PR oluşturma, yorum yapma, düzenleme, kapatma/yeniden açma, squash·merge·rebase birleştirme (güçlü onay), CI’yi yeniden çalıştırma/iptal etme; repo değiştirici erişilebilir depoları otomatik listeler ve genel depolarda arama yapar; better-sidebar yoksa bağımsız sağ panele geri düşer',
pluginMdExportDesc: 'better-sidebar Markdown araç çubuğuna «Dışa aktar» düğmesi ekler: geçerli .md dosyasını tek tıkla bağımsız HTML biçimine dönüştürür (tablolar/kod blokları/Mermaid diyagramları gömülü, düzen önizleme temasını izler) ve .md dosyasının yanına kaydeder ya da yazdırma penceresinden PDF dışa aktarır',
pluginCodeNavDesc: 'Kod önizleme gezinmesi: dosya türünden dili otomatik algılayıp söz dizimini vurgular, simge şeması (sınıf/yöntem/değişken filtreleri + tek tıkla atlama) ve dosya içi arama (tüm eşleşmeleri vurgula, önceki/sonraki, büyük/küçük harf duyarlı) — better-sidebar düzenleyicisinde kod dosyası önizlemesini devralır',
pluginSuhuangScrollDesc: 'Yerel Suhuang Scroll Runtime ortamını model yapılandırması, bağlantı testi ve kesintisiz değerlendirme denetimi için DSH ayarlarına ve better-sidebar öğesine bağlar; Suhuang Scroll Runtime ve dsh-better-sidebar gerektirir',
pluginSuhuangScrollDesc: 'Yerel Suhuang Scroll Runtime ortamını model yapılandırması, bağlantı testi ve kesintisiz değerlendirme denetimi için DSH ayarlarına ve better-sidebar öğesine bağlar; Suhuang Scroll Runtime ve dsh-apes-sidebar gerektirir',
pluginBetterOverleafDesc: 'better-sidebar için Overleaf sekmesi: doğrudan CDP tarayıcı girişi (üçüncü taraf Chromium destekli), proje listesi/geçiş, <workspace>/overleaf/ altında yerel git aynaları, salt okunur API yedekli çift yönlü git senkronizasyonu ve dosya önizlemesi kenar çubuğu workbench üzerinden',
pluginGitRemotesDesc: 'Git Remotes sekmesi: dal/yukarı akış/ahead-behind, fetch (isteğe bağlı prune), ff-only pull ve yalnızca sekme içi onaydan sonra push. Yerleşik Git sahnele/işle sekmesini değiştirmez; force-push veya model otomatik push aracı sunmaz',
pluginSentinelDesc: 'Koşul odaklı aracı uyandırma: dosya/süreç/port/HTTP/komut/web algılayıcılar koşullar tetiklendiğinde uyuyan oturumları uyandırır; sunucu geneli izleme tablosuyla «Sentinel» sekmesi kaydeder',
+2 -2
View File
@@ -403,7 +403,7 @@ export const vi: Record<string, string> = {
addPluginsViewerCardDesc: 'Đăng ký xem trước loại tệp mới',
addPluginsTabDesc: 'Trang thanh bên (tab) có thể mở rộng qua plugin. Plugin đăng ký qua service ctx.betterSidebar; nhấp «Cài đặt» để sao chép lệnh cài, dán vào terminal nơi DSH chạy rồi thực thi.',
addPluginsViewerDesc: 'Trình xem tệp có thể mở rộng qua plugin. Plugin đăng ký qua service ctx.betterSidebar; nhấp «Cài đặt» để sao chép lệnh cài, dán vào terminal nơi DSH chạy rồi thực thi.',
addPluginsBrowseMore: 'Duyệt thêm plugin trên GitHub (topic: dsh-better-sidebar)',
addPluginsBrowseMore: 'Duyệt thêm plugin trên GitHub (topic: dsh-apes-sidebar)',
addPluginsSearch: 'Tìm theo tên / mô tả plugin…',
addPluginsNoMatch: 'Không có plugin phù hợp',
addPluginsRecommended: 'Plugin đề xuất',
@@ -416,7 +416,7 @@ export const vi: Record<string, string> = {
pluginGithubWorkbenchDesc: 'Tab GitHub Workbench: cây repo từ xa + Issues / Pull requests / Actions với hỗ trợ ghi đầy đủ — tạo Issue/PR, bình luận, chỉnh sửa, đóng/mở lại, merge squash·merge·rebase (xác nhận mạnh), chạy lại/hủy CI; bộ chuyển repo tự liệt kê các repo có quyền truy cập và tìm kiếm repo công khai; khi chưa cài better-sidebar sẽ tự động chuyển sang panel bên phải độc lập',
pluginMdExportDesc: 'Thêm nút «Xuất» vào thanh công cụ Markdown của better-sidebar: một cú nhấp chuyển .md hiện tại thành HTML độc lập (bảng/khối mã/sơ đồ Mermaid nhúng, bố cục theo theme xem trước) và lưu cạnh tệp .md, hoặc xuất PDF qua hộp thoại in',
pluginCodeNavDesc: 'Điều hướng xem trước mã: tự nhận diện ngôn ngữ theo loại tệp và tô sáng cú pháp, phác thảo ký hiệu (lọc lớp/phương thức/biến + nhảy một cú nhấp), tìm kiếm trong tệp (tô sáng mọi kết quả, trước/sau, phân biệt hoa thường) — đảm nhận xem trước tệp mã trong trình soạn thảo better-sidebar',
pluginSuhuangScrollDesc: 'Kết nối Suhuang Scroll Runtime cục bộ với phần cài đặt DSH và better-sidebar để cấu hình mô hình, kiểm tra kết nối và điều khiển chấm bài liên tục; yêu cầu Suhuang Scroll Runtime và dsh-better-sidebar',
pluginSuhuangScrollDesc: 'Kết nối Suhuang Scroll Runtime cục bộ với phần cài đặt DSH và better-sidebar để cấu hình mô hình, kiểm tra kết nối và điều khiển chấm bài liên tục; yêu cầu Suhuang Scroll Runtime và dsh-apes-sidebar',
pluginBetterOverleafDesc: 'Tab Overleaf cho better-sidebar: đăng nhập trực tiếp qua trình duyệt CDP (hỗ trợ Chromium bên thứ ba), danh sách/chuyển dự án, mirror git cục bộ trong <workspace>/overleaf/, đồng bộ git hai chiều với dự phòng API chỉ đọc, xem trước tệp qua workbench của thanh bên',
pluginGitRemotesDesc: 'Tab Git Remotes của better-sidebar: xem branch/upstream/ahead-behind, fetch (có thể prune), pull ff-only, push chỉ sau khi xác nhận trong tab. Không thay thế tab stage/commit Git built-in, cũng không cung cấp force-push hay công cụ auto-push cho mô hình',
pluginSentinelDesc: 'Hệ thống đánh thức agent theo điều kiện: cảm biến file/process/port/HTTP/command/webhook, khi điều kiện đạt sẽ tự đánh thức phiên ngủ; đăng ký tab «Sentinel» hiển thị bảng giám sát toàn server',
+2 -2
View File
@@ -418,7 +418,7 @@ export const zhHK: Record<string, string> = {
addPluginsViewerCardDesc: '註冊新的檔案類型預覽',
addPluginsTabDesc: '側邊欄頁面(Tab)可以由插件擴展。插件透過 ctx.betterSidebar 服務註冊;點擊「安裝」複製安裝命令,貼到 DSH 所在環境的終端執行。',
addPluginsViewerDesc: '檔案預覽器可以由插件擴展。插件透過 ctx.betterSidebar 服務註冊;點擊「安裝」複製安裝命令,貼到 DSH 所在環境的終端執行。',
addPluginsBrowseMore: '在 GitHub 上瀏覽更多插件(topic: dsh-better-sidebar)',
addPluginsBrowseMore: '在 GitHub 上瀏覽更多插件(topic: dsh-apes-sidebar)',
addPluginsSearch: '搜尋插件名稱 / 描述…',
addPluginsNoMatch: '沒有匹配的插件',
addPluginsRecommended: '推薦插件',
@@ -431,7 +431,7 @@ export const zhHK: Record<string, string> = {
pluginGithubWorkbenchDesc: 'better-sidebar「GitHub 工作台」Tab:遠端儲存庫目錄樹 + Issues / Pull requests / Actions 頁簽,讀取之外支援新建 Issue/PR、評論、編輯、關閉重開、squash·merge·rebase 合併(強確認)與重跑/取消 CI;儲存庫彈出層自動拉取有權限列表並支援公開儲存庫搜尋;未安裝 better-sidebar 時自動降級為獨立右側面板',
pluginMdExportDesc: '在 better-sidebar 的 Markdown 工具列上新增「匯出」按鈕:一鍵把目前 .md 渲染為獨立 HTML(表格/程式碼區塊/Mermaid 圖表內嵌,版面跟隨預覽主題)直接儲存到同目錄,或透過列印對話框匯出為 PDF',
pluginCodeNavDesc: '程式碼預覽導覽:按檔案類型自動識別語言並高亮語法,符號大綱(類別/方法/變數篩選 + 一鍵跳轉),檔案內搜尋(全部符合高亮、上/下一處、區分大小寫),接管 better-sidebar 的程式碼檔案預覽',
pluginSuhuangScrollDesc: '把本機蘇黃共閱 Runtime 接入 DSH 設定與 better-sidebar,支援模型設定、連線測試及連續閱卷控制;使用前須安裝 Suhuang Scroll Runtime 與 dsh-better-sidebar',
pluginSuhuangScrollDesc: '把本機蘇黃共閱 Runtime 接入 DSH 設定與 better-sidebar,支援模型設定、連線測試及連續閱卷控制;使用前須安裝 Suhuang Scroll Runtime 與 dsh-apes-sidebar',
pluginBetterOverleafDesc: 'better-sidebar 的 Overleaf 分頁:直連 CDP 瀏覽器登入(支援第三方 Chromium),專案列表/切換,<workspace>/overleaf/ 本地 git 映像,git 雙向同步(API 唯讀兜底),檔案預覽走側邊欄工作台',
pluginGitRemotesDesc: 'better-sidebar Git 遠端 Tab:看分支/上游/ahead-behind,fetch(可 prune)、ff-only pull、確認後才 push。不替換內建 Git 的暫存/提交,也不提供 force-push 或模型自動推送',
pluginSentinelDesc: '條件驅動的 agent 喚醒系統:檔案/程序/連接埠/HTTP/命令/webhook 感測器,條件達成自動喚醒休眠工作階段;註冊「哨兵」Tab 展示伺服器全域監控表',
+2 -2
View File
@@ -418,7 +418,7 @@ export const zhMO: Record<string, string> = {
addPluginsViewerCardDesc: '註冊新的檔案類型預覽',
addPluginsTabDesc: '側邊欄頁面(Tab)可以由插件擴展。插件透過 ctx.betterSidebar 服務註冊;點擊「安裝」複製安裝命令,貼到 DSH 所在環境的終端執行。',
addPluginsViewerDesc: '檔案預覽器可以由插件擴展。插件透過 ctx.betterSidebar 服務註冊;點擊「安裝」複製安裝命令,貼到 DSH 所在環境的終端執行。',
addPluginsBrowseMore: '在 GitHub 上瀏覽更多插件(topic: dsh-better-sidebar)',
addPluginsBrowseMore: '在 GitHub 上瀏覽更多插件(topic: dsh-apes-sidebar)',
addPluginsSearch: '搜尋插件名稱 / 描述…',
addPluginsNoMatch: '沒有匹配的插件',
addPluginsRecommended: '推薦插件',
@@ -431,7 +431,7 @@ export const zhMO: Record<string, string> = {
pluginGithubWorkbenchDesc: 'better-sidebar「GitHub 工作台」Tab:遠端儲存庫目錄樹 + Issues / Pull requests / Actions 頁簽,讀取之外支援新建 Issue/PR、評論、編輯、關閉重開、squash·merge·rebase 合併(強確認)與重跑/取消 CI;儲存庫彈層自動拉取有權限清單並支援公開儲存庫搜尋;未安裝 better-sidebar 時自動降級為獨立右側面板',
pluginMdExportDesc: '在 better-sidebar 的 Markdown 工具列上新增「匯出」按鈕:一鍵把目前 .md 渲染為獨立 HTML(表格/程式碼區塊/Mermaid 圖表內嵌,版面跟隨預覽主題)直接儲存到同目錄,或透過列印對話框匯出為 PDF',
pluginCodeNavDesc: '程式碼預覽導覽:按檔案類型自動識別語言並高亮語法,符號大綱(類別/方法/變數篩選 + 一鍵跳轉),檔案內搜尋(全部符合高亮、上/下一處、區分大小寫),接管 better-sidebar 的程式碼檔案預覽',
pluginSuhuangScrollDesc: '把本機蘇黃共閱 Runtime 接入 DSH 設定與 better-sidebar,支援模型設定、連線測試及連續閱卷控制;使用前須安裝 Suhuang Scroll Runtime 與 dsh-better-sidebar',
pluginSuhuangScrollDesc: '把本機蘇黃共閱 Runtime 接入 DSH 設定與 better-sidebar,支援模型設定、連線測試及連續閱卷控制;使用前須安裝 Suhuang Scroll Runtime 與 dsh-apes-sidebar',
pluginBetterOverleafDesc: 'better-sidebar 的 Overleaf 分頁:直連 CDP 瀏覽器登入(支援第三方 Chromium),專案列表/切換,<workspace>/overleaf/ 本地 git 映像,git 雙向同步(API 唯讀兜底),檔案預覽走側邊欄工作台',
pluginGitRemotesDesc: 'better-sidebar Git 遠端 Tab:看分支/上游/ahead-behind,fetch(可 prune)、ff-only pull、確認後才 push。不替換內建 Git 的暫存/提交,也不提供 force-push 或模型自動推送',
pluginSentinelDesc: '條件驅動的 agent 喚醒系統:檔案/程序/連接埠/HTTP/命令/webhook 感測器,條件達成自動喚醒休眠工作階段;註冊「哨兵」Tab 展示伺服器全域監控表',
+2 -2
View File
@@ -418,7 +418,7 @@ export const zhTW: Record<string, string> = {
addPluginsViewerCardDesc: '註冊新的檔案類型預覽',
addPluginsTabDesc: '側邊欄頁面(Tab)可以由插件擴展。插件透過 ctx.betterSidebar 服務註冊;點擊「安裝」複製安裝命令,貼到 DSH 所在環境的終端執行。',
addPluginsViewerDesc: '檔案預覽器可以由插件擴展。插件透過 ctx.betterSidebar 服務註冊;點擊「安裝」複製安裝命令,貼到 DSH 所在環境的終端執行。',
addPluginsBrowseMore: '在 GitHub 上瀏覽更多插件(topic: dsh-better-sidebar)',
addPluginsBrowseMore: '在 GitHub 上瀏覽更多插件(topic: dsh-apes-sidebar)',
addPluginsSearch: '搜尋插件名稱 / 描述…',
addPluginsNoMatch: '沒有匹配的插件',
addPluginsRecommended: '推薦插件',
@@ -431,7 +431,7 @@ export const zhTW: Record<string, string> = {
pluginGithubWorkbenchDesc: 'better-sidebar「GitHub 工作台」Tab:遠端儲存庫目錄樹 + Issues / Pull requests / Actions 頁籤,讀取之外支援建立 Issue/PR、留言、編輯、關閉重開、squash·merge·rebase 合併(強確認)與重跑/取消 CI;儲存庫彈出視窗自動載入有權限清單並支援公開儲存庫搜尋;未安裝 better-sidebar 時自動降級為獨立右側面板',
pluginMdExportDesc: '在 better-sidebar 的 Markdown 工具列上新增「匯出」按鈕:一鍵把目前 .md 渲染為獨立 HTML(表格/程式碼區塊/Mermaid 圖表內嵌,版面跟隨預覽主題)直接儲存到同目錄,或透過列印對話框匯出為 PDF',
pluginCodeNavDesc: '程式碼預覽導覽:按檔案類型自動識別語言並高亮語法,符號大綱(類別/方法/變數篩選 + 一鍵跳轉),檔案內搜尋(全部符合高亮、上/下一處、區分大小寫),接管 better-sidebar 的程式碼檔案預覽',
pluginSuhuangScrollDesc: '把本機蘇黃共閱 Runtime 接入 DSH 設定與 better-sidebar,支援模型設定、連線測試及連續閱卷控制;使用前須安裝 Suhuang Scroll Runtime 與 dsh-better-sidebar',
pluginSuhuangScrollDesc: '把本機蘇黃共閱 Runtime 接入 DSH 設定與 better-sidebar,支援模型設定、連線測試及連續閱卷控制;使用前須安裝 Suhuang Scroll Runtime 與 dsh-apes-sidebar',
pluginBetterOverleafDesc: 'better-sidebar 的 Overleaf 分頁:直連 CDP 瀏覽器登入(支援第三方 Chromium),專案列表/切換,<workspace>/overleaf/ 本地 git 映像,git 雙向同步(API 唯讀兜底),檔案預覽走側邊欄工作台',
pluginGitRemotesDesc: 'better-sidebar Git 遠端 Tab:看分支/上游/ahead-behind,fetch(可 prune)、ff-only pull、確認後才 push。不替換內建 Git 的暫存/提交,也不提供 force-push 或模型自動推送',
pluginSentinelDesc: '條件驅動的 agent 喚醒系統:檔案/程序/連接埠/HTTP/命令/webhook 感測器,條件達成自動喚醒休眠工作階段;註冊「哨兵」Tab 展示伺服器全域監控表',
+4 -4
View File
@@ -415,7 +415,7 @@ export const zh = {
addPluginsViewerCardDesc: '注册新的文件类型预览',
addPluginsTabDesc: '侧边栏页面(Tab)可以由插件扩展。插件通过 ctx.betterSidebar 服务注册;点击「安装」复制安装命令,粘贴到 DSH 所在环境的终端执行。',
addPluginsViewerDesc: '文件预览器可以由插件扩展。插件通过 ctx.betterSidebar 服务注册;点击「安装」复制安装命令,粘贴到 DSH 所在环境的终端执行。',
addPluginsBrowseMore: '在 GitHub 上浏览更多插件(topic: dsh-better-sidebar)',
addPluginsBrowseMore: '在 GitHub 上浏览更多插件(topic: dsh-apes-sidebar)',
addPluginsSearch: '搜索插件名称 / 描述…',
addPluginsNoMatch: '没有匹配的插件',
addPluginsRecommended: '推荐插件',
@@ -427,7 +427,7 @@ export const zh = {
pluginFlowglassDesc: '实时会话流程图:三列泳道展示用户、助手与工具调用,支持并行分组、子代理支线、逐层钻取和实时状态;安装 better-sidebar 后注册原生「流镜」Tab,未安装时保留独立抽屉',
pluginGitForgeDesc: 'better-sidebar「Git 凭据」Tab:GitHub/Gitea 等 Forge 账号库 + 按项目授权 + push 策略硬拦;token 仅存本地 secrets,不进模型上下文;提供只读 GitForge 工具与 agent HTTPS credential helper',
pluginGithubWorkbenchDesc: 'better-sidebar「GitHub 工作台」Tab:远端仓库目录树 + Issues / Pull requests / Actions 页签,读之外支持新建 Issue/PR、评论、编辑、关闭重开、squash·merge·rebase 合并(强确认)与重跑/取消 CI;仓库弹层自动拉取有权限列表并支持公开仓搜索;未装 better-sidebar 时自动降级为独立右侧面板',
pluginSuhuangScrollDesc: '把本地苏黄共阅 Runtime 接入 DSH 设置与 better-sidebar,支持模型配置、连接测试和连续阅卷控制;使用前需安装 Suhuang Scroll Runtime 与 dsh-better-sidebar',
pluginSuhuangScrollDesc: '把本地苏黄共阅 Runtime 接入 DSH 设置与 better-sidebar,支持模型配置、连接测试和连续阅卷控制;使用前需安装 Suhuang Scroll Runtime 与 dsh-apes-sidebar',
pluginBetterOverleafDesc: 'better-sidebar 的 Overleaf 标签页:直连 CDP 浏览器登录(支持第三方 Chromium),项目列表/切换,<workspace>/overleaf/ 本地 git 镜像,git 双向同步(API 只读兜底),文件预览走侧边栏工作台',
pluginGitRemotesDesc: 'better-sidebar Git 远程 Tab:看分支/上游/ahead-behind,fetch(可 prune)、ff-only pull、确认后才 push。不替换内置 Git 的暂存/提交,也不提供 force-push 或模型自动推送',
pluginSentinelDesc: '条件驱动的 agent 唤醒系统:文件/进程/端口/HTTP/命令/webhook 传感器,条件达成自动唤醒休眠会话;注册「哨兵」Tab 展示服务器全局监控表',
@@ -857,7 +857,7 @@ export const en: Record<keyof typeof zh, string> = {
addPluginsViewerCardDesc: 'Register a file-type preview',
addPluginsTabDesc: 'Sidebar pages (tabs) can be extended by plugins. Plugins register through the ctx.betterSidebar service; clicking Install copies the install command — paste it into a terminal where your DSH profile lives and run it.',
addPluginsViewerDesc: 'File previewers can be extended by plugins. Plugins register through the ctx.betterSidebar service; clicking Install copies the install command — paste it into a terminal where your DSH profile lives and run it.',
addPluginsBrowseMore: 'Browse more plugins on GitHub (topic: dsh-better-sidebar)',
addPluginsBrowseMore: 'Browse more plugins on GitHub (topic: dsh-apes-sidebar)',
addPluginsSearch: 'Search by plugin name or description…',
addPluginsNoMatch: 'No plugins match',
addPluginsRecommended: 'Recommended plugins',
@@ -875,7 +875,7 @@ export const en: Record<keyof typeof zh, string> = {
pluginSidebarQaDesc: 'Select-and-ask: Select conversation text → ask in the right-side panel → a dedicated follow-up session (❓追问) in the same workspace; a fast no-thinking model compresses the main context and injects it with the quote, without interrupting the main conversation. Follow-ups nest, continue, and archive',
pluginSidenoteDesc: 'Codex-style side chat + selection annotations: fork the current session into a persistent side panel (archived out of the session list, multi-instance, /side command, survives reload, model follows the main session); select assistant text → numbered badge + note editor → an "N annotations" chip that rides your next message, or ask straight into a side chat',
pluginSshTunnelDesc: 'SSH Tunnel tab: multi-host inventory + per-project grants + local secrets; SSHManager tool (exec/SFTP/session strategies); center interactive terminal and dual-pane SFTP',
pluginSuhuangScrollDesc: 'Connect the local Suhuang Scroll Runtime to DSH settings and better-sidebar for model configuration, connection tests, and continuous grading controls; requires Suhuang Scroll Runtime and dsh-better-sidebar',
pluginSuhuangScrollDesc: 'Connect the local Suhuang Scroll Runtime to DSH settings and better-sidebar for model configuration, connection tests, and continuous grading controls; requires Suhuang Scroll Runtime and dsh-apes-sidebar',
pluginTurnReviewDesc: 'A human gate on the just-finished turn: Approve / Request changes per path with an optional comment; paths grouped by main session / subagent / unattributed; inline snapshot-vs-now diff before you decide. No fork, no /rewind',
pluginVideoPreviewDesc: 'Inline video preview (.mp4/.webm/.mov/.mkv/.avi etc.) for the better-sidebar editor, backed by a dedicated /video host route with HTTP Range (206) support — scrubbing works and files are not capped by the 20MB mediaLimit',
pluginCodeNavDesc: 'Code preview navigator: detects the language by file type and highlights syntax, symbol outline (class / method / variable filters + one-click jump), and in-file search (highlight all matches, prev/next, match case) — takes over code file preview in the better-sidebar editor',
+2 -2
View File
@@ -55,7 +55,7 @@ const FILES_KIND = 'files'
/** The plugin's implementation id for a descriptor (unique across kinds). */
function nativeId(descriptorId: string): string {
return `dsh-better-sidebar:${descriptorId}`
return `dsh-apes-sidebar:${descriptorId}`
}
/** The descriptor's title text, evaluated fresh for the current locale. */
@@ -214,7 +214,7 @@ export function registerNativeSurface(deps: NativeSurfaceDeps): () => void {
/** One `files`-kind takeover: the plugin's explorer under the built-in kind. */
const registerFilesKind = (editor: TabDescriptor | undefined): (() => void) => {
const id = 'dsh-better-sidebar:files'
const id = 'dsh-apes-sidebar:files'
const disposeType = tabs.register({
id,
kind: FILES_KIND,
+1 -1
View File
@@ -291,7 +291,7 @@ export function NativeTabBody(props: NativeBodyInjected & NativeBodyFrameworkPro
// it gets the same full-height box (its own root also relies on the
// `flex: 1` contract the column host restores). The host div carries
// `data-dsh-native-tab-host` (empty value) so the e2e lane can assert
// the fill, mirroring `data-dsh-better-sidebar`.
// the fill, mirroring `data-dsh-apes-sidebar`.
return createElement(
'div',
{ className: css.nativeTabHost, 'data-dsh-native-tab-host': '' },
+2 -2
View File
@@ -7,8 +7,8 @@
* 文件预览 grids).
*/
/** The GitHub topic page listing every repo tagged `dsh-better-sidebar`. */
export const PLUGIN_TOPIC_URL = 'https://github.com/topics/dsh-better-sidebar'
/** The GitHub topic page listing every repo tagged `dsh-apes-sidebar`. */
export const PLUGIN_TOPIC_URL = 'https://github.com/topics/dsh-apes-sidebar'
/** One curated plugin entry (name / url / description / install script). */
export interface PluginEntry {
+20 -20
View File
@@ -31,25 +31,25 @@ export const builtinTabPlugins: readonly PluginEntry[] = [
description: () => t('pluginEgoBrowserDesc'),
// Registers a sidebar tab for the agent browser; optional peer of
// better-sidebar (auto-tab when present, floating bubble when not).
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-better-sidebar && dsh plugin --profile web add git+https://github.com/Fisfzy/ego-browser.git',
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-apes-sidebar && dsh plugin --profile web add git+https://github.com/Fisfzy/ego-browser.git',
},
{
id: 'dsh-better-overleaf',
name: () => t('pluginBetterOverleafName'),
url: 'https://github.com/Hoemr/dsh-better-overleaf',
description: () => t('pluginBetterOverleafDesc'),
// Published on npm; peer-depends on dsh-better-sidebar (Overleaf tab),
// Published on npm; peer-depends on dsh-apes-sidebar (Overleaf tab),
// so the install line installs the prerequisite first.
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-better-sidebar && dsh plugin --profile web add dsh-better-overleaf',
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-apes-sidebar && dsh plugin --profile web add dsh-better-overleaf',
},
{
id: 'dsh-docs-panel',
name: () => t('pluginDocsPanelName'),
url: 'https://github.com/mlosun/dsh-docs-panel',
description: () => t('pluginDocsPanelDesc'),
// dsh-docs-panel hard-depends on dsh-better-sidebar (required peer), so
// dsh-docs-panel hard-depends on dsh-apes-sidebar (required peer), so
// the install line installs the prerequisite first, then the plugin.
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-better-sidebar && dsh plugin --profile web add dsh-docs-panel',
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-apes-sidebar && dsh plugin --profile web add dsh-docs-panel',
},
{
id: 'dsh-flowglass',
@@ -58,23 +58,23 @@ export const builtinTabPlugins: readonly PluginEntry[] = [
description: () => t('pluginFlowglassDesc'),
// Flowglass keeps its standalone drawer as a fallback and registers the
// native tab automatically when better-sidebar is present.
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-better-sidebar && dsh plugin --profile web add dsh-flowglass',
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-apes-sidebar && dsh plugin --profile web add dsh-flowglass',
},
{
id: 'dsh-git-forge',
name: () => t('pluginGitForgeName'),
url: 'https://github.com/thirsty5034/dsh-git-forge',
description: () => t('pluginGitForgeDesc'),
// Peer-depends on dsh-better-sidebar (Git Forge tab). Install the
// Peer-depends on dsh-apes-sidebar (Git Forge tab). Install the
// prerequisite first; package is GitHub-sourced until npm publish.
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-better-sidebar && dsh plugin --profile web add "dsh-git-forge@github:thirsty5034/dsh-git-forge"',
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-apes-sidebar && dsh plugin --profile web add "dsh-git-forge@github:thirsty5034/dsh-git-forge"',
},
{
id: 'dsh-git-remotes',
name: () => t('pluginGitRemotesName'),
url: 'https://github.com/yq04/dsh-git-remotes',
description: () => t('pluginGitRemotesDesc'),
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-better-sidebar && dsh plugin --profile web add git+https://github.com/yq04/dsh-git-remotes.git',
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-apes-sidebar && dsh plugin --profile web add git+https://github.com/yq04/dsh-git-remotes.git',
},
{
id: 'dsh-github-workbench',
@@ -91,18 +91,18 @@ export const builtinTabPlugins: readonly PluginEntry[] = [
name: () => t('pluginSidebarQaName'),
url: 'https://github.com/ChenRuoT/dsh-sidebar-qa',
description: () => t('pluginSidebarQaDesc'),
// dsh-sidebar-qa hard-depends on dsh-better-sidebar (required peer), so
// dsh-sidebar-qa hard-depends on dsh-apes-sidebar (required peer), so
// the install line installs the prerequisite first, then the plugin.
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-better-sidebar && dsh plugin --profile web add git+https://github.com/ChenRuoT/dsh-sidebar-qa.git',
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-apes-sidebar && dsh plugin --profile web add git+https://github.com/ChenRuoT/dsh-sidebar-qa.git',
},
{
id: 'dsh-sidenote',
name: () => t('pluginSidenoteName'),
url: 'https://github.com/g-yixuan/dsh-sidenote',
description: () => t('pluginSidenoteDesc'),
// dsh-sidenote hard-depends on dsh-better-sidebar (required peer), so
// dsh-sidenote hard-depends on dsh-apes-sidebar (required peer), so
// the install line installs the prerequisite first, then the plugin.
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-better-sidebar && dsh plugin --profile web add dsh-sidenote',
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-apes-sidebar && dsh plugin --profile web add dsh-sidenote',
},
{
id: 'dsh-server-deck',
@@ -112,7 +112,7 @@ export const builtinTabPlugins: readonly PluginEntry[] = [
// Published on npm; dual-mount like flowglass — registers the native
// "Servers" tab when better-sidebar is present, standalone drawer
// otherwise. Install the prerequisite first.
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-better-sidebar && dsh plugin --profile web add dsh-server-deck@latest',
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-apes-sidebar && dsh plugin --profile web add dsh-server-deck@latest',
},
{
id: 'dsh-suhuang-scroll',
@@ -121,24 +121,24 @@ export const builtinTabPlugins: readonly PluginEntry[] = [
description: () => t('pluginSuhuangScrollDesc'),
// Suhuang Scroll is a DSH Web plugin whose runtime console registers in
// better-sidebar. Install the sidebar prerequisite before the npm package.
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-better-sidebar && dsh plugin --profile web add dsh-suhuang-scroll',
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-apes-sidebar && dsh plugin --profile web add dsh-suhuang-scroll',
},
{
id: 'dsh-ssh-tunnel',
name: () => t('pluginSshTunnelName'),
url: 'https://github.com/thirsty5034/dsh-ssh-tunnel',
description: () => t('pluginSshTunnelDesc'),
// Peer-depends on dsh-better-sidebar (SSH Tunnel tab + center terminal/SFTP).
// Peer-depends on dsh-apes-sidebar (SSH Tunnel tab + center terminal/SFTP).
// Install the prerequisite first; package is GitHub-sourced until npm publish.
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-better-sidebar && dsh plugin --profile web add "dsh-ssh-tunnel@github:thirsty5034/dsh-ssh-tunnel"',
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-apes-sidebar && dsh plugin --profile web add "dsh-ssh-tunnel@github:thirsty5034/dsh-ssh-tunnel"',
},
{
id: 'dsh-turn-review',
name: () => t('pluginTurnReviewName'),
url: 'https://github.com/yq04/dsh-turn-review',
description: () => t('pluginTurnReviewDesc'),
// Needs dsh-better-sidebar (optional peer) for the tab; no model tools.
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-better-sidebar && dsh plugin --profile web add git+https://github.com/yq04/dsh-turn-review.git',
// Needs dsh-apes-sidebar (optional peer) for the tab; no model tools.
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-apes-sidebar && dsh plugin --profile web add git+https://github.com/yq04/dsh-turn-review.git',
},
{
id: 'dsh-bilingual-reader',
@@ -148,6 +148,6 @@ export const builtinTabPlugins: readonly PluginEntry[] = [
// Bilingual paper reading: a native-PDF tab with LLM selection translation,
// isolated from the main conversation context. Hard-depends on the
// better-sidebar tab service, so install the prerequisite first.
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-better-sidebar && dsh plugin --profile web add github:Johnblur/dsh-bilingual-reader',
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-apes-sidebar && dsh plugin --profile web add github:Johnblur/dsh-bilingual-reader',
},
]
+8 -8
View File
@@ -616,7 +616,7 @@ export function matchUrlTarget(tabs: readonly TabDescriptor[], url: URL): TabDes
try {
claimed = tab.urlTarget(url) === true
} catch (error) {
console.error('[dsh-better-sidebar] urlTarget error:', error)
console.error('[dsh-apes-sidebar] urlTarget error:', error)
continue
}
if (claimed) return tab
@@ -628,7 +628,7 @@ export function matchUrlTarget(tabs: readonly TabDescriptor[], url: URL): TabDes
* The plugin version this service instance reports. Keep in lockstep with
* `package.json`'s version — `tests/service.spec.ts` asserts the pair.
*/
export const SIDEBAR_SERVICE_VERSION = '0.19.1'
export const SIDEBAR_SERVICE_VERSION = '1.2.0'
/**
* Monotonic capability list consumers use to gate new API usage (features
@@ -671,7 +671,7 @@ function safeCall(fn: () => void): void {
try {
fn()
} catch (error) {
console.error('[dsh-better-sidebar] plugin callback error:', error)
console.error('[dsh-apes-sidebar] plugin callback error:', error)
}
}
@@ -699,7 +699,7 @@ export function createBetterSidebarService(store: SidebarStore): BetterSidebarSe
const registerTab = (descriptor: TabDescriptor): (() => void) => {
if (tabs.has(descriptor.id)) {
throw new Error(`[dsh-better-sidebar] tab type "${descriptor.id}" already registered`)
throw new Error(`[dsh-apes-sidebar] tab type "${descriptor.id}" already registered`)
}
tabs.set(descriptor.id, descriptor)
notify()
@@ -713,7 +713,7 @@ export function createBetterSidebarService(store: SidebarStore): BetterSidebarSe
const registerFileViewer = (descriptor: FileViewerDescriptor): (() => void) => {
if (viewers.has(descriptor.id)) {
throw new Error(`[dsh-better-sidebar] file viewer "${descriptor.id}" already registered`)
throw new Error(`[dsh-apes-sidebar] file viewer "${descriptor.id}" already registered`)
}
viewers.set(descriptor.id, descriptor)
notify()
@@ -732,7 +732,7 @@ export function createBetterSidebarService(store: SidebarStore): BetterSidebarSe
const registerFileIcon = (descriptor: FileIconDescriptor): (() => void) => {
if (fileIcons.has(descriptor.id)) {
throw new Error(`[dsh-better-sidebar] file icons "${descriptor.id}" already registered`)
throw new Error(`[dsh-apes-sidebar] file icons "${descriptor.id}" already registered`)
}
fileIcons.set(descriptor.id, descriptor)
notify()
@@ -795,7 +795,7 @@ export function createBetterSidebarService(store: SidebarStore): BetterSidebarSe
try {
return d.icon(path, size, open)
} catch (error) {
console.error(`[dsh-better-sidebar] file icon factory "${d.id}" error:`, error)
console.error(`[dsh-apes-sidebar] file icon factory "${d.id}" error:`, error)
return undefined
}
}
@@ -873,7 +873,7 @@ export function createBetterSidebarService(store: SidebarStore): BetterSidebarSe
// + menu nor from derived flows (file opens, subagent auto-open,
// external plugins). Already-open tabs keep rendering.
if (!isTabEnabled(seed.type)) {
console.warn(`[dsh-better-sidebar] tab type "${seed.type}" is disabled in the side card settings`)
console.warn(`[dsh-apes-sidebar] tab type "${seed.type}" is disabled in the side card settings`)
return
}
const descriptor = tabs.get(seed.type)
+1 -1
View File
@@ -10,7 +10,7 @@
* disposal, so the adaptation remains HMR-safe.
*/
export const SETTINGS_NAV_MARKER = 'data-dsh-better-sidebar-settings-nav'
export const SETTINGS_NAV_MARKER = 'data-dsh-apes-sidebar-settings-nav'
/**
* Keep the marker on the settings-nav button whose visible text is this
+1 -1
View File
@@ -2047,7 +2047,7 @@ body[data-dsh-sidebar-dragging] .bottomPanel {
restores the `flex: 1` semantics our tab roots rely on, and gives
third-party descriptors' components the same box inside the native pane.
The wrapper div carries `data-dsh-native-tab-host` so the e2e lane can
assert the fill (mirroring `data-dsh-better-sidebar`). */
assert the fill (mirroring `data-dsh-apes-sidebar`). */
.nativeTabHost {
display: flex;
flex-direction: column;
+2 -2
View File
@@ -22,8 +22,8 @@ import { BottomDockToggle } from '../Sidebar.tsx'
export function registerBottomToggle(ctx: Context, store: SidebarStore): () => void {
return ctx.slots.inject('conversation.session.header.utilities', () => ctx.slots.register({
name: 'conversation.session.header.utilities',
id: 'dsh-better-sidebar:bottom-toggle',
id: 'dsh-apes-sidebar:bottom-toggle',
order: 10,
registrant: 'dsh-better-sidebar',
registrant: 'dsh-apes-sidebar',
}, () => <BottomDockToggle store={store} />))
}
+2 -2
View File
@@ -131,7 +131,7 @@ export function useHostFeeds(feeds: {
if (closed) return
failures += 1
if (failures >= FAILURE_LIMIT) {
console.error('[dsh-better-sidebar] agent-terminals connection failed; stopping reconnect loop', sessionId)
console.error('[dsh-apes-sidebar] agent-terminals connection failed; stopping reconnect loop', sessionId)
return
}
retry = window.setTimeout(connect, 2000)
@@ -203,7 +203,7 @@ export function useHostFeeds(feeds: {
if (closed) return
failures += 1
if (failures >= FAILURE_LIMIT) {
console.error('[dsh-better-sidebar] agent-opens connection failed; stopping reconnect loop', sessionId)
console.error('[dsh-apes-sidebar] agent-opens connection failed; stopping reconnect loop', sessionId)
return
}
retry = window.setTimeout(connect, 2000)
+1 -1
View File
@@ -2,7 +2,7 @@
* Serializable configuration and defaults for the sidebar host half. Loader
* schema validation normally fills defaults; {@link resolveSidebarConfig}
* applies the same defaults for direct callers that bypass the Loader.
* @module dsh-better-sidebar/config
* @module dsh-apes-sidebar/config
*/
import z from 'schemastery'
+2 -2
View File
@@ -572,14 +572,14 @@ export interface SidebarContextShape {
/**
* The Context this plugin sees: the vendored cordis Context intersected with
* the structural service faces above. Re-exported from the package root so a
* consumer can `import type { Context } from 'dsh-better-sidebar'`.
* consumer can `import type { Context } from 'dsh-apes-sidebar'`.
*/
export type Context = CordisContext & SidebarContextShape
/**
* Consumer-facing augmentation (deliberately the only one kept): a plugin
* that imports `Context` from `@deepseek-ai/cordis` and does
* `import type {} from 'dsh-better-sidebar'` sees `ctx.betterSidebar`
* `import type {} from 'dsh-apes-sidebar'` sees `ctx.betterSidebar`
* without importing this package's own Context type.
*/
declare module '@deepseek-ai/cordis' {
+42 -27
View File
@@ -1,5 +1,5 @@
/**
* dsh-better-sidebar host half: the /sidebar JSON API (explorer listing, file
* dsh-apes-sidebar host half: the /sidebar JSON API (explorer listing, file
* read/write, git), the /sidebar/file media route (images), the /sidebar/html
* preview route, the /sidebar/bundle lazy-chunk route (client code splits),
* and the terminal WebSocket upgrade. Every route passes the same
@@ -61,7 +61,7 @@ import { readPersistedSession } from './session-store.ts'
export { Config }
export type { SidebarConfig, ResolvedSidebarConfig }
// Re-export the Context augmentation (`declare module '@deepseek-ai/cordis'`)
// so consumers `import type {} from 'dsh-better-sidebar'` and gain
// so consumers `import type {} from 'dsh-apes-sidebar'` and gain
// `ctx.betterSidebar`; the Context re-export below is the vendored cordis
// Context intersected with the structural service faces.
// Also re-export the service descriptor types so consumers can type their
@@ -77,7 +77,7 @@ export type {
} from './client/service.ts'
/** Plugin identity for cordis.yml rows. */
export const name = 'dsh-better-sidebar'
export const name = 'dsh-apes-sidebar'
/** Services required before mounting: the webserver routes, the session store, the web runtime's trusted hosts, and the tool registry. */
export const inject = ['webServer', 'sessions', 'webRuntime', 'tools']
@@ -744,7 +744,7 @@ export function apply(ctx: Context, config?: SidebarConfig): void {
const detail = status.ok
? 'unknown cause'
: `${status.cause}. Repair: ${status.command}`
ctx.logger?.warn(`[dsh-better-sidebar] node-pty (${DSH_NODE_PTY_RANGE}) failed to load: ${detail}`)
ctx.logger?.warn(`[dsh-apes-sidebar] node-pty (${DSH_NODE_PTY_RANGE}) failed to load: ${detail}`)
}
const ptyManager = nodePty !== null
? new PtyManager(terminalShell, resolved.terminalsPerSession, resolved.shellArgs, nodePty)
@@ -799,22 +799,28 @@ export function apply(ctx: Context, config?: SidebarConfig): void {
}
}
ctx.inject(['settings'], (sctx) => {
// DSH 0.1.2-alpha.2 validates namespaces at compile time
// (SettingsNamespaceInput); the 'dsh-better-sidebar' literal passes, so the
// runtime helper this used to call (settingsNamespace) is gone upstream.
const ns = SIDEBAR_PREFS_NS
// The structural settings mirror types `schema` as unknown, so the
// generic is not inferred here; the real service resolves it from the
// schemastery schema (PrefsSchema) — narrow the owner scope explicitly.
const scope = sctx.settings.register(ns, PrefsSchema) as {
get(): SidebarPrefs
watch(callback: (next: SidebarPrefs, prev: SidebarPrefs) => void): () => void
// 0.2.0-rc.2: `settings.register()` is gone. The plugin's Config schema
// (config.ts → Config) is auto-registered by the loader under ns =
// entry.options.id (= package name). We read it through describe() and
// watch for changes via the `settings/document-updated` event.
const readPrefs = (): SidebarPrefs => {
const descriptor = sctx.settings.describe({ redactSecrets: true }).find(c => c.ns === ns)
const raw = descriptor?.value as Record<string, unknown> | undefined
return raw !== null && typeof raw === 'object'
? raw as unknown as SidebarPrefs
: SIDEBAR_PREFS_DEFAULTS
}
const scope = { get: readPrefs }
const viewOf = (): { value?: unknown; revision?: number } => {
const descriptor = sctx.settings.describe({ redactSecrets: true }).find(candidate => candidate.ns === ns)
return descriptor === undefined
? { value: undefined, revision: undefined }
: { value: descriptor.value, revision: descriptor.revision }
if (descriptor === undefined) return { value: undefined, revision: undefined }
// 0.2.0-rc.2: describe() may return value=undefined for a namespace that
// has no user-set document yet (the Config schema defaults are applied
// lazily). Fall back to the schema defaults so the client always gets a
// well-formed object (React #130 guard).
const value = descriptor.value ?? SIDEBAR_PREFS_DEFAULTS
return { value, revision: descriptor.revision }
}
// Mutual exclusion with the dsh-web-ui family right panel: the aionui
// panel's provider choice (`aionui-panel.rightPanel`) is the authority.
@@ -869,7 +875,12 @@ export function apply(ctx: Context, config?: SidebarConfig): void {
// ONE watch subscription drives both gates: settings commits re-evaluate
// the terminal tools AND the open tool together (each gate is idempotent
// and owns its own disposer).
scope.watch(() => { syncToolsGate(scope); syncOpenToolsGate() })
// 0.2.0-rc.2: `scope.watch()` is gone; use the `settings/document-updated`
// event emitted on the plugin context (ownerContext) by the SettingsForms
// service on every describe() call that detects a raw-value change.
ctx.on('settings/document-updated', (changedNs: string) => {
if (changedNs === ns) { syncToolsGate(scope); syncOpenToolsGate() }
})
})
// ── JSON API ────────────────────────────────────────────────────────────
@@ -879,7 +890,11 @@ export function apply(ctx: Context, config?: SidebarConfig): void {
// transcript and the inherited in-progress snapshot read them here. The
// effect releases the listener on fiber disposal.
const assistantLive = createAssistantLiveBuffer(ctx)
ctx.effect(() => () => { assistantLive.dispose() }, 'dsh-better-sidebar: live assistant stream buffer')
ctx.effect(() => () => { assistantLive.dispose() }, 'dsh-apes-sidebar: live assistant stream buffer')
// NOTE: `buildApi` must come AFTER `ctx.inject(['settings'], ...)` so the
// closure captures the settingsFace value set inside the inject callback.
// 0.2.0-rc.2 removed `settings.register()`; settingsFace is now populated
// synchronously during inject, not lazily at first call.
const api = buildApi(ctx, ptyManager, agentPtyRegistry, resolved, terminalShell, () => settingsFace, assistantLive)
ctx.effect(() => ctx.webServer.register({
kind: 'prefix',
@@ -910,7 +925,7 @@ export function apply(ctx: Context, config?: SidebarConfig): void {
writeError(res, error)
}
},
}), 'dsh-better-sidebar: /sidebar/api routes')
}), 'dsh-apes-sidebar: /sidebar/api routes')
// ── Raw upload route ───────────────────────────────────────────────────
// One request writes one file without JSON/base64 inflation. Folder uploads
@@ -952,13 +967,13 @@ export function apply(ctx: Context, config?: SidebarConfig): void {
writeError(res, error)
}
},
}), 'dsh-better-sidebar: /sidebar/upload route')
}), 'dsh-apes-sidebar: /sidebar/upload route')
// ── Lazy chunk route (client bundle splits) ─────────────────────────────
// Serves the client half's split bundles (lib/client-<name>.js) so the
// heavy preview/terminal libraries load on first use, not at page start
// (see bundle-route.ts / src/client/chunk-loader.ts).
ctx.effect(() => registerBundleRoute(ctx, fence), 'dsh-better-sidebar: /sidebar/bundle chunk route')
ctx.effect(() => registerBundleRoute(ctx, fence), 'dsh-apes-sidebar: /sidebar/bundle chunk route')
// ── Media route (images for the editor) ─────────────────────────────────
ctx.effect(() => ctx.webServer.register({
@@ -1000,7 +1015,7 @@ export function apply(ctx: Context, config?: SidebarConfig): void {
writeError(res, error)
}
},
}), 'dsh-better-sidebar: /sidebar/file media route')
}), 'dsh-apes-sidebar: /sidebar/file media route')
// ── HTML preview route (sandboxed HTML + its relative assets) ───────────
// Serves files under the session cwd for the built-in HTML previewer. The
@@ -1062,7 +1077,7 @@ export function apply(ctx: Context, config?: SidebarConfig): void {
writeError(res, error)
}
},
}), 'dsh-better-sidebar: /sidebar/html preview route')
}), 'dsh-apes-sidebar: /sidebar/html preview route')
// ── Terminal WebSocket ──────────────────────────────────────────────────
// One upgrade endpoint serves both UI-tab terminals (?tab=...) and
@@ -1086,7 +1101,7 @@ export function apply(ctx: Context, config?: SidebarConfig): void {
void attachTerminal(ctx, ptyManager, agentPtyRegistry, ws, req, resolved, () => settingsFace)
})
},
}), 'dsh-better-sidebar: terminal WebSocket')
}), 'dsh-apes-sidebar: terminal WebSocket')
// ── Agent terminals push WebSocket ──────────────────────────────────────
// Pushes the live list of agent terminals for one session to the sidebar
@@ -1108,7 +1123,7 @@ export function apply(ctx: Context, config?: SidebarConfig): void {
void attachAgentList(agentPtyRegistry, ws, req)
})
},
}), 'dsh-better-sidebar: agent-terminals push WebSocket')
}), 'dsh-apes-sidebar: agent-terminals push WebSocket')
// ── Agent opens push WebSocket ─────────────────────────────────────────
// Pushes `sidebar_open` requests for one session to the sidebar view: the
@@ -1128,7 +1143,7 @@ export function apply(ctx: Context, config?: SidebarConfig): void {
void attachAgentOpen(agentOpenRegistry, ws, req)
})
},
}), 'dsh-better-sidebar: agent-opens push WebSocket')
}), 'dsh-apes-sidebar: agent-opens push WebSocket')
ctx.effect(() => () => {
toolsDisposers?.()
@@ -1139,7 +1154,7 @@ export function apply(ctx: Context, config?: SidebarConfig): void {
wss.close()
agentListWss.close()
agentOpenWss.close()
}, 'dsh-better-sidebar: teardown')
}, 'dsh-apes-sidebar: teardown')
}
/** Push queued `sidebar_open` requests for one session to a connected view. */
+4 -4
View File
@@ -1,15 +1,15 @@
/**
* Package-owned invariant companion for `dsh-better-sidebar`.
* @module dsh-better-sidebar/invariant
* Package-owned invariant companion for `dsh-apes-sidebar`.
* @module dsh-apes-sidebar/invariant
*/
/* jscpd:ignore-start */
import type { Context } from './context-types.ts'
const PACKAGE_NAME = 'dsh-better-sidebar'
const PACKAGE_NAME = 'dsh-apes-sidebar'
/** Cordis companion plugin name. */
export const name = 'dsh-better-sidebar-invariant'
export const name = 'dsh-apes-sidebar-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
+1 -1
View File
@@ -165,7 +165,7 @@ function createJobOutputMirror(ctx: Context): { entries(sessionId: string): read
push(sessionId, trace)
}
})
ctx.effect(() => dispose, 'dsh-better-sidebar: job-output event mirror')
ctx.effect(() => dispose, 'dsh-apes-sidebar: job-output event mirror')
const push = (sessionId: string, trace: JobOutputTrace): void => {
let list = perSession.get(sessionId)
+1 -1
View File
@@ -7,7 +7,7 @@
*/
/** The user-settings namespace holding the side card preferences. */
export const SIDEBAR_PREFS_NS = 'dsh-better-sidebar'
export const SIDEBAR_PREFS_NS = 'dsh-apes-sidebar'
/** User-facing side card preferences. */
export interface SidebarPrefs {
+1 -1
View File
@@ -147,7 +147,7 @@ function isPluginRoot(dir: string): boolean {
if (!existsSync(file)) return false
try {
const parsed = JSON.parse(readFileSync(file, 'utf8')) as { name?: unknown }
return parsed.name === 'dsh-better-sidebar'
return parsed.name === 'dsh-apes-sidebar'
} catch {
return false
}
+1 -1
View File
@@ -39,7 +39,7 @@ export const SIDE_BOUNDARY_PREFIX = 'Side conversation boundary'
/** The plugin identity stamped on the source of context-injection messages
* (boundary prompt + parked snapshot), so the transcript recognizes them
* structurally — not by text prefix. */
export const SIDE_INJECTION_PLUGIN = 'dsh-better-sidebar'
export const SIDE_INJECTION_PLUGIN = 'dsh-apes-sidebar'
/**
* The boundary prompt delivered as the thread's first user message: the
+122
View File
@@ -0,0 +1,122 @@
/**
* apes:* tab registration tests (Phase 1/1b, C6: 3 tabs). The fork registers
* its three information-layer tabs (agents / apps / profile) through the same
* BetterSidebarService the upstream builtins use (dogfood). `apes:tasks` was
* removed in C6 — it overlaps the official DSH session tree in the native
* sidebar. The spec pins the registration contract (id / single / order /
* component presence) and the isolated i18n contract (the apes namespace
* resolves zh vs en through the attached locale service, and every apesZh
* key has an apesEn entry).
*
* Runs in the node vitest environment like builtins.spec.ts — no DOM, no
* real DSH host (a live cordis scratch mount is Phase 2+).
*/
import { describe, expect, it } from 'vitest'
// Browser globals before the client graph loads (mirror of builtins.spec.ts).
import './browser-globals.ts'
import type { Context } from '../src/context-types.ts'
import { createBetterSidebarService } from '../src/client/service.ts'
import { createSidebarStore } from '../src/client/state.ts'
import { registerApesTabs } from '../src/client/apes/index.ts'
import { apesZh, apesEn, attachApesLocale, t, type ApesCopyKey } from '../src/client/apes/locales.ts'
function setup(): {
service: ReturnType<typeof createBetterSidebarService>
store: ReturnType<typeof createSidebarStore>
dispose: () => void
} {
const store = createSidebarStore()
const service = createBetterSidebarService(store)
const dispose = registerApesTabs({} as Context, service)
return { service, store, dispose }
}
describe('apes:* tab registrations', () => {
it('registers the 3 apes tabs with the apes: namespace', () => {
const { service, dispose } = setup()
try {
expect(service.getTabs().map(t => t.id).sort()).toEqual(
['apes:agents', 'apes:apps', 'apes:profile'],
)
} finally {
dispose()
}
})
it('every apes tab is single-instance, visible, and component-carrying', () => {
const { service, dispose } = setup()
try {
for (const id of ['apes:agents', 'apes:apps', 'apes:profile']) {
const tab = service.getTab(id)
expect(tab, `${id} registered`).toBeDefined()
expect(tab?.single, `${id} single`).toBe(true)
expect(tab?.hidden, `${id} hidden`).not.toBe(true)
expect(tab?.component, `${id} component`).toBeDefined()
expect(tab?.icon, `${id} icon`).toBeDefined()
}
} finally {
dispose()
}
})
it('apes tabs sort after the upstream builtins (order 40+)', () => {
const { service, dispose } = setup()
try {
const orders = {
agents: service.getTab('apes:agents')?.order,
apps: service.getTab('apes:apps')?.order,
profile: service.getTab('apes:profile')?.order,
}
// C8: order 40-50 places apes tabs immediately after the native builtins
// (10-35), not in a far-away 900+ band — visual unity in the tab strip.
expect(orders.agents).toBeGreaterThanOrEqual(40)
expect(orders.apps).toBeGreaterThanOrEqual(40)
expect(orders.profile).toBeGreaterThanOrEqual(40)
// Strictly ordered: agents < apps < profile.
expect(orders.agents!).toBeLessThan(orders.apps!)
expect(orders.apps!).toBeLessThan(orders.profile!)
} finally {
dispose()
}
})
it('the disposer unregisters all apes tabs (HMR-safe)', () => {
const store = createSidebarStore()
const service = createBetterSidebarService(store)
const dispose = registerApesTabs({} as Context, service)
expect(service.getTabs()).toHaveLength(3)
dispose()
expect(service.getTabs()).toHaveLength(0)
})
})
describe('apes i18n contract', () => {
it('every apesZh key has an apesEn entry (no missing translations)', () => {
const zhKeys = Object.keys(apesZh) as ApesCopyKey[]
for (const key of zhKeys) {
expect(apesEn[key], `en entry for ${key}`).toBeDefined()
expect(typeof apesEn[key]).toBe('string')
expect((apesEn[key] ?? '').length).toBeGreaterThan(0)
}
})
it('resolves zh when the DSH preference is zh', () => {
attachApesLocale({ getSnapshot: () => ({ active: 'zh-CN' }) })
expect(t('apesAgentsTitle')).toBe('智能体')
expect(t('apesAppsTitle')).toBe('应用')
expect(t('apesProfileTitle')).toBe('画像')
})
it('resolves en when the DSH preference is en', () => {
attachApesLocale({ getSnapshot: () => ({ active: 'en' }) })
expect(t('apesAgentsTitle')).toBe('Agents')
expect(t('apesAppsTitle')).toBe('Apps')
expect(t('apesProfileTitle')).toBe('Profile')
})
it('falls back to zh when no locale service is attached', () => {
attachApesLocale(undefined)
expect(t('apesAgentsTitle')).toBe('智能体')
})
})
+1 -1
View File
@@ -8,7 +8,7 @@
*
* Mirrors the "external consumer" fixture: what is importable here from
* `../src/client/service.ts` must also be importable from
* `dsh-better-sidebar/client/service` in the built package.
* `dsh-apes-sidebar/client/service` in the built package.
*/
import type {} from '../src/client/service.ts'
import {
+10 -10
View File
@@ -10,7 +10,7 @@
* (the same `workspace.create` / `session.create` calls the UI makes),
* so the sidebar has a real session to render;
* 2. loads the page in headless Chromium and asserts the shell and the
* plugin's `[data-dsh-better-sidebar]` host mount;
* plugin's `[data-dsh-apes-sidebar]` host mount;
* 3. asserts the plugin's crash markers never appear (no RenderBoundary /
* fail() strips, no `pageerror`, no plugin-prefixed console errors);
* 4. expands DSH's native right Sidebar, sweeps every built-in tab type
@@ -54,7 +54,7 @@ const SEEDED_README_FILE = 'readme-style.md'
* renders a strip whose text starts with these prefixes instead of crashing
* (see src/client/index.tsx `fail()` and src/client/RenderBoundary.tsx).
*/
const CRASH_STRIP_PATTERNS = [/^dsh-better-sidebar:/, /^\[dsh-better-sidebar\]/]
const CRASH_STRIP_PATTERNS = [/^dsh-apes-sidebar:/, /^\[dsh-apes-sidebar\]/]
/** Built-in tab titles the sweep drives (en-US copy; follows DSH locale). */
const NATIVE_TABS = ['files', 'git', 'subagent', 'sidechat', 'terminal', 'browser']
@@ -150,7 +150,7 @@ test('plugin mounts into the DSH shell and survives a built-in tab sweep', async
})
// Load the shell. The app renders into #root; the plugin appends its own
// [data-dsh-better-sidebar] host once its client half activates.
// [data-dsh-apes-sidebar] host once its client half activates.
//
// The editor chunk (client-editor.js) loads as soon as ANY files-window tab
// renders — the seeded home tab mounts the moment the panel expands, long
@@ -162,7 +162,7 @@ test('plugin mounts into the DSH shell and survives a built-in tab sweep', async
)
await page.goto(PAGE_URL, { waitUntil: 'domcontentloaded' })
await expect(page.locator('#root > *')).not.toHaveCount(0, { timeout: 90_000 })
const sidebar = page.locator('[data-dsh-better-sidebar]')
const sidebar = page.locator('[data-dsh-apes-sidebar]')
await expect(sidebar).toBeAttached({ timeout: 90_000 })
// The unified panel host: the fixed containing block every panel lives in
// (data-dsh-panel-host). Its presence is part of the injection contract.
@@ -249,7 +249,7 @@ test('plugin mounts into the DSH shell and survives a built-in tab sweep', async
}).map((node) => (node.textContent ?? '').trim()),
CRASH_STRIP_PATTERNS,
)
expect(stripTexts, 'a dsh-better-sidebar error strip is present in the sidebar').toEqual([])
expect(stripTexts, 'a dsh-apes-sidebar error strip is present in the sidebar').toEqual([])
}
// The native Sidebar's way in lives in the conversation header's corner,
@@ -550,7 +550,7 @@ test('plugin mounts into the DSH shell and survives a built-in tab sweep', async
// The plugin's own console prefix must never appear in errors, and no
// unhandled rejection may escape the sweep.
const pluginErrors = consoleErrors.filter((text) => /dsh-better-sidebar|Unhandled/.test(text))
const pluginErrors = consoleErrors.filter((text) => /dsh-apes-sidebar|Unhandled/.test(text))
expect(pluginErrors, 'plugin-prefixed or unhandled console errors during the sweep').toEqual([])
expect(pageErrors, 'pageerrors during the sweep').toEqual([])
@@ -566,7 +566,7 @@ test('conservative auto: URL stamps alone never modify the layout; plugin chrome
// semantics) — the strip/body attribute appear only for real standard
// geometry (see the WCO scenario below) or an opt-in preset.
await gotoPage(page, { 'dsh-desktop-mode': 'advanced', 'dsh-desktop-platform': 'win32' })
await expect(page.locator('[data-dsh-better-sidebar]')).toBeAttached({ timeout: 90_000 })
await expect(page.locator('[data-dsh-apes-sidebar]')).toBeAttached({ timeout: 90_000 })
await expect(
page.locator('body[data-dsh-title-bar-compat]'),
'stamps alone must NOT auto-enable title-bar compatibility under auto',
@@ -616,7 +616,7 @@ test('standard WCO geometry drives the strip reactively (issue #257)', async ({
}
})
await page.goto(PAGE_URL, { waitUntil: 'domcontentloaded' })
await expect(page.locator('[data-dsh-better-sidebar]')).toBeAttached({ timeout: 90_000 })
await expect(page.locator('[data-dsh-apes-sidebar]')).toBeAttached({ timeout: 90_000 })
// Real reported height (36px, not a hardcoded 32) drives the strip.
await expect(page.locator('body[data-dsh-title-bar-compat]')).toBeAttached({ timeout: 90_000 })
await expect
@@ -643,7 +643,7 @@ test('opt-in shell preset applies its strip when WCO is absent (data-driven, man
expect(update.ok(), `settings.update: ${update.status()}`).toBe(true)
try {
await gotoPage(page, { 'dsh-desktop-mode': 'advanced', 'dsh-desktop-platform': 'win32' })
await expect(page.locator('[data-dsh-better-sidebar]')).toBeAttached({ timeout: 90_000 })
await expect(page.locator('[data-dsh-apes-sidebar]')).toBeAttached({ timeout: 90_000 })
await expect(page.locator('body[data-dsh-title-bar-compat]')).toBeAttached({ timeout: 90_000 })
await expect
.poll(() => page.evaluate(() => document.documentElement.style.getPropertyValue('--dsh-title-bar-strip')))
@@ -668,7 +668,7 @@ test('custom scheme injects the user stylesheet live', async ({ request, page })
expect(update.ok(), `settings.update: ${update.status()}`).toBe(true)
try {
await page.goto(PAGE_URL, { waitUntil: 'domcontentloaded' })
await expect(page.locator('[data-dsh-better-sidebar]')).toBeAttached({ timeout: 90_000 })
await expect(page.locator('[data-dsh-apes-sidebar]')).toBeAttached({ timeout: 90_000 })
await expect(page.locator('style[data-dsh-custom-css="custom"]')).toBeAttached()
// The injected CSS is live (a custom property the page can read back).
const marker = await page.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue('--dsh-e2e-marker').trim())
+6 -6
View File
@@ -5,7 +5,7 @@
* It exists to put NUMBERS on the sidebar's client-side cost in a real
* mounted `dsh web` (the same scratch-profile boot as scripts/e2e-mount.sh):
*
* 1. mount latency — navigationStart → `[data-dsh-better-sidebar]`
* 1. mount latency — navigationStart → `[data-dsh-apes-sidebar]`
* attached, sampled by an init-script probe (rAF loop, so it catches
* the attach even before Playwright's first poll);
* 2. longtasks — every >50ms main-thread task from first paint
@@ -63,7 +63,7 @@ const PERF_PROBE = `(() => {
for (const entry of list.getEntries()) perf.longtasks.push({ start: entry.startTime, duration: entry.duration })
}).observe({ entryTypes: ['longtask'] })
const check = () => {
if (document.querySelector('[data-dsh-better-sidebar]') !== null) { perf.mountAt = performance.now(); return }
if (document.querySelector('[data-dsh-apes-sidebar]') !== null) { perf.mountAt = performance.now(); return }
requestAnimationFrame(check)
}
requestAnimationFrame(check)
@@ -114,7 +114,7 @@ test('measure: mount latency, longtasks and bundle cost through a full tab sweep
await page.addInitScript(PERF_PROBE)
await page.goto(PAGE_URL, { waitUntil: 'domcontentloaded' })
await expect(page.locator('#root > *')).not.toHaveCount(0, { timeout: 90_000 })
const sidebar = page.locator('[data-dsh-better-sidebar]')
const sidebar = page.locator('[data-dsh-apes-sidebar]')
await expect(sidebar).toBeAttached({ timeout: 90_000 })
const mountLatency = await page.evaluate(() =>
@@ -183,7 +183,7 @@ test('measure: bottom-strip drag frame pacing', async ({ page }) => {
await page.addInitScript(PERF_PROBE)
await page.goto(PAGE_URL, { waitUntil: 'domcontentloaded' })
await expect(page.locator('#root > *')).not.toHaveCount(0, { timeout: 90_000 })
const sidebar = page.locator('[data-dsh-better-sidebar]')
const sidebar = page.locator('[data-dsh-apes-sidebar]')
await expect(sidebar).toBeAttached({ timeout: 90_000 })
await dismissOnboarding(page)
@@ -227,7 +227,7 @@ test('measure: bottom-strip drag frame pacing', async ({ page }) => {
await expect
.poll(async () => {
const probe = await page.evaluate(() => {
const bottom = document.querySelector('[data-dsh-better-sidebar] [data-dsh-bottom-panel]')
const bottom = document.querySelector('[data-dsh-apes-sidebar] [data-dsh-bottom-panel]')
if (bottom === null) return null
const strip = [...bottom.querySelectorAll<HTMLElement>('*')].find(el => getComputedStyle(el).cursor === 'row-resize')
if (strip === undefined) return null
@@ -239,7 +239,7 @@ test('measure: bottom-strip drag frame pacing', async ({ page }) => {
}, { timeout: 30_000 })
.toBeLessThanOrEqual(8)
const stripPoint = await page.evaluate(() => {
const bottom = document.querySelector('[data-dsh-better-sidebar] [data-dsh-bottom-panel]')
const bottom = document.querySelector('[data-dsh-apes-sidebar] [data-dsh-bottom-panel]')
if (bottom === null) return null
const strip = [...bottom.querySelectorAll<HTMLElement>('*')].find(el => getComputedStyle(el).cursor === 'row-resize')
if (strip === undefined) return null
+2 -2
View File
@@ -1,7 +1,7 @@
# Fixture aggregate bundle: mounts dsh-better-sidebar under its own entry id,
# Fixture aggregate bundle: mounts dsh-apes-sidebar under its own entry id,
# the way real aggregate bundles (e.g. @linxin666/dsh-web-ui-all) do. Used by
# scripts/e2e-aggregate-mount.sh to prove the plugin's own bundle patch backs
# off when another enabled entry already mounts the same package.
- insert:
- id: aggregate-better-sidebar
name: 'dsh-better-sidebar'
name: 'dsh-apes-sidebar'
+2 -2
View File
@@ -6,9 +6,9 @@ import { join, resolve } from 'node:path'
import { parseWorktreeList, resolveWorktree, status, worktrees } from '../src/git.ts'
const IDENTITY = {
GIT_AUTHOR_NAME: 'dsh-better-sidebar-test',
GIT_AUTHOR_NAME: 'dsh-apes-sidebar-test',
GIT_AUTHOR_EMAIL: 'test@dsh.invalid',
GIT_COMMITTER_NAME: 'dsh-better-sidebar-test',
GIT_COMMITTER_NAME: 'dsh-apes-sidebar-test',
GIT_COMMITTER_EMAIL: 'test@dsh.invalid',
}
+1 -1
View File
@@ -20,7 +20,7 @@ const canonical = (path: string): string => normalizePath(realpathSync.native(pa
describe('git parsing', () => {
it('discovers and selects direct child repositories under a workspace directory', async () => {
const workspace = await mkdtemp(join(tmpdir(), 'dsh-better-sidebar-git-'))
const workspace = await mkdtemp(join(tmpdir(), 'dsh-apes-sidebar-git-'))
const first = join(workspace, 'first-repo')
const second = join(workspace, 'second-repo')
try {
+95
View File
@@ -0,0 +1,95 @@
/**
* HtmlPreviewFrame delivery tests — the piece that exists ONLY because of the
* DSH Desktop renderer fence: the host refuses `/sidebar/html/…` to any
* request without the Electron renderer capability, which a frame sandboxed
* without allow-same-origin (an opaque origin) can never carry. The document
* is therefore fetched by the MAIN frame and handed over through `srcdoc`,
* with the sandbox attribute untouched.
*
* Mounted client-side (createRoot + act) because the delivery path is an
* effect; the sandbox-contract assertions live in sandbox-views.spec.tsx.
*/
// @vitest-environment jsdom
import { afterEach, describe, expect, it, vi } from 'vitest'
import { createElement } from 'react'
import { act } from 'react-dom/test-utils'
import { HtmlPreviewFrame } from '../src/client/HtmlPreviewFrame.tsx'
import { HTML_IFRAME_SANDBOX } from '../src/client/html-preview.ts'
import { renderRoot, setupReactAct } from './test-utils.ts'
setupReactAct()
const URL_ = '/sidebar/html/s1/p/a/index.html'
const DOC = '<html><head><title>t</title></head><body><h1>hi</h1></body></html>'
/** Stub the global fetch with one canned response. */
function stubFetch(text: string, ok = true): void {
vi.stubGlobal('fetch', vi.fn(async () => ({ ok, status: ok ? 200 : 403, text: async () => text })))
}
/** Flush the fetch's microtasks + the React commit they trigger. */
async function flush(): Promise<void> {
await act(async () => { await new Promise(resolve => { setTimeout(resolve, 0) }) })
}
afterEach(() => { vi.unstubAllGlobals() })
describe('HtmlPreviewFrame delivery', () => {
it('starts empty, never pointing the frame at the route itself', () => {
stubFetch(DOC)
const r = renderRoot(createElement(HtmlPreviewFrame, { url: URL_, sandboxed: true, title: 'index.html' }))
const iframe = r.container.querySelector('iframe')!
expect(iframe.getAttribute('srcdoc')).toBe('')
expect(iframe.getAttribute('src')).toBe(null)
// The boundary is unchanged while the document is in flight.
expect(iframe.getAttribute('sandbox')).toBe(HTML_IFRAME_SANDBOX)
r.unmount()
})
it('hands the fetched markup to the frame through srcdoc, base-rewritten, sandbox kept', async () => {
stubFetch(DOC)
const r = renderRoot(createElement(HtmlPreviewFrame, { url: URL_, sandboxed: true, title: 'index.html' }))
await flush()
const iframe = r.container.querySelector('iframe')!
const srcdoc = iframe.getAttribute('srcdoc')
expect(srcdoc).toContain('<base href="/sidebar/html/s1/p/a/">')
expect(srcdoc).toContain('<h1>hi</h1>')
// Still no route src: the opaque frame must not be asked to fetch.
expect(iframe.getAttribute('src')).toBe(null)
expect(iframe.getAttribute('sandbox')).toBe(HTML_IFRAME_SANDBOX)
expect(HTML_IFRAME_SANDBOX).not.toContain('allow-same-origin')
r.unmount()
})
it('drops the sandbox attribute for the warned unlock path (the frame inherits the GUI origin)', async () => {
stubFetch(DOC)
const r = renderRoot(createElement(HtmlPreviewFrame, { url: URL_, sandboxed: false, title: 'index.html' }))
await flush()
const iframe = r.container.querySelector('iframe')!
expect(iframe.getAttribute('sandbox')).toBe(null)
expect(iframe.getAttribute('srcdoc')).toContain('<h1>hi</h1>')
r.unmount()
})
it('falls back to the route URL when the fetch fails, so the route still answers', async () => {
stubFetch('', false)
const r = renderRoot(createElement(HtmlPreviewFrame, { url: URL_, sandboxed: true, title: 'index.html' }))
await flush()
const iframe = r.container.querySelector('iframe')!
expect(iframe.getAttribute('src')).toBe(URL_)
expect(iframe.getAttribute('srcdoc')).toBe(null)
r.unmount()
})
it('refetches when the previewed file changes', async () => {
stubFetch('<h1>one</h1>')
const r = renderRoot(createElement(HtmlPreviewFrame, { url: URL_, sandboxed: true, title: 'a.html' }))
await flush()
expect(r.container.querySelector('iframe')!.getAttribute('srcdoc')).toContain('<h1>one</h1>')
stubFetch('<h1>two</h1>')
r.rerender(createElement(HtmlPreviewFrame, { url: '/sidebar/html/s1/p/a/two.html', sandboxed: true, title: 'b.html' }))
await flush()
expect(r.container.querySelector('iframe')!.getAttribute('srcdoc')).toContain('<h1>two</h1>')
r.unmount()
})
})
+51
View File
@@ -0,0 +1,51 @@
/**
* HTML preview vocabulary tests: the srcdoc delivery path needs the previewed
* document's relative references to resolve back into its own
* `/sidebar/html/…` route (a srcdoc document has no URL of its own — the
* browser would otherwise resolve `./style.css` against the GUI page).
*
* Pure module, plain node environment.
*/
import { describe, expect, it } from 'vitest'
import { previewBaseOf, withPreviewBase } from '../src/client/html-preview.ts'
const URL_ = '/sidebar/html/s1/p/a/index.html'
describe('previewBaseOf', () => {
it('keeps everything up to the last slash', () => {
expect(previewBaseOf(URL_)).toBe('/sidebar/html/s1/p/a/')
})
it('keeps the UNC double slash intact (the route encodes it that way)', () => {
expect(previewBaseOf('/sidebar/html/s1//server/share/a.html')).toBe('/sidebar/html/s1//server/share/')
})
it('returns the input unchanged when it has no slash', () => {
expect(previewBaseOf('s1')).toBe('s1')
})
})
describe('withPreviewBase', () => {
it('injects the base as the first child of <head>, ahead of the document’s own elements', () => {
const out = withPreviewBase('<html><head><title>t</title></head><body></body></html>', URL_)
expect(out).toBe('<html><head><base href="/sidebar/html/s1/p/a/"><title>t</title></head><body></body></html>')
})
it('falls back to <html> when the document declares no head', () => {
const out = withPreviewBase('<html><body>x</body></html>', URL_)
expect(out).toBe('<html><base href="/sidebar/html/s1/p/a/"><body>x</body></html>')
})
it('prepends for a headless fragment', () => {
expect(withPreviewBase('<h1>x</h1>', URL_)).toBe('<base href="/sidebar/html/s1/p/a/"><h1>x</h1>')
})
it('leaves a document that declares its own base alone (the author owns it)', () => {
const doc = '<head><base href="https://example.com/"></head>'
expect(withPreviewBase(doc, URL_)).toBe(doc)
})
it('quotes the injected href', () => {
expect(withPreviewBase('<head></head>', '/x/"y/')).toBe('<head><base href="/x/&quot;y/"></head>')
})
})
+2 -2
View File
@@ -60,7 +60,7 @@ function assertInMemoryInvocation(executable: string): void {
const output = `${result.stdout}\n${result.stderr}`
expect(result.status, output).toBe(0)
expect(output).toContain('dsh-better-sidebar@0.11.0')
expect(output).toContain('dsh-apes-sidebar@0.11.0')
expect(output).toContain('[dry-run]')
expect(output).not.toContain('False False')
}
@@ -126,7 +126,7 @@ describe('PowerShell installer entry points', { timeout: 30_000 }, () => {
const output = `${result.stdout}\n${result.stderr}`
expect(result.status, output).toBe(0)
expect(output).toContain('dsh-better-sidebar@0.11.0')
expect(output).toContain('dsh-apes-sidebar@0.11.0')
expect(output).toContain('[dry-run]')
})
+4 -4
View File
@@ -210,9 +210,9 @@ describe('registerNativeSurface lifecycle (service-driven registration)', () =>
}
runInjected?.()
expect(registered.map(entry => entry.kind).sort()).toEqual(['browser', 'editor', 'files', 'terminal'])
expect(registered.map(entry => entry.id)).toContain('dsh-better-sidebar:files')
expect(slotKeys).toContain('dsh-better-sidebar:terminal')
expect(slotKeys).toContain('dsh-better-sidebar:files')
expect(registered.map(entry => entry.id)).toContain('dsh-apes-sidebar:files')
expect(slotKeys).toContain('dsh-apes-sidebar:terminal')
expect(slotKeys).toContain('dsh-apes-sidebar:files')
// A resource tab is titled by the FILE it shows (the descriptor's own
// title would make every open file look identical in the strip), while a
@@ -261,7 +261,7 @@ describe('NativeTabBody full-height host wrapper', () => {
// definite height, not a flex container — our tab roots (`flex: 1;
// min-height: 0`) collapse there without a column-flex wrapper of
// height:100%. The stable `data-dsh-native-tab-host` marker (mirroring
// `data-dsh-better-sidebar`) lets the e2e lane assert the fill; this
// `data-dsh-apes-sidebar`) lets the e2e lane assert the fill; this
// unit check pins the structure: the marker wrapper exists and the
// descriptor's output is INSIDE it (before the fix the component was
// rendered bare, with no wrapper at all).

Some files were not shown because too many files have changed in this diff Show More