Commit Graph
605 Commits
Author SHA1 Message Date
Menghuan1918 aa1b9945d9 fix(native): register the plugin's tab types when the registry service arrives
The native seat declares `sidebar.right.pane.tab` BEFORE it provides
`sidebarRightTabs`, so driving the registration off the slot declaration
read the service as missing and registered nothing — permanently, since a
declaration never collapses. Observed on a real DSH profile (0.1.5-alpha.1,
`dsh web` on the web profile): the plugin host and its other slot
registrations mounted, the native guide page stayed empty, and a probe
printed `sidebarRightTabs= undefined` at declaration time with the registry
present three seconds later. The scratch mount lane never saw it because
there the plugin activates after the native package finished providing.

`registerNativeSurface` now waits on the SERVICE (`ctx.inject`), which
re-runs whenever the registry appears/reappears, and keeps the per-body
slot registrations on `slots.inject` (they only need the declaration).

Regression test: `tests/native-surface.spec.ts` models the real order
(slot callback fires first, registry arrives later) and fails on the old
implementation. Verified on the real web profile: all guide entries
(files / editor / git / subagent / sidechat / terminal / browser) appear
and the terminal tab renders.
2026-09-09 13:07:53 +08:00
Menghuan1918 d7e3e2b986 fix(test): give the native-surface terminal descriptor a component
TabDescriptor requires `component`; the fixture omitted it, so CI's
typecheck step failed on the phase-2 branch (vitest never compiled the
file's props strictly, so the local run stayed green).
2026-09-09 12:33:49 +08:00
Menghuan1918 51546f7433 feat(native): render the plugin's tabs in DSH's native right Sidebar
Phase 2 of the 0.1.5 adaptation: the plugin stops drawing its own right
panel and contributes tab TYPES to DSH's native right Sidebar, which owns
the right column. The bottom workbench (terminal dock) stays plugin-owned.

Registrations (src/client/native/):
- every TabDescriptor becomes a native tab type (kind = descriptor.id, the
  default `extension` band) plus a keyed body in `sidebar.right.pane.tab`
  and a live chip title in `sidebar.right.pane.tab.title`
- the `editor` type also claims `dsh-resource://file/**`, so it outranks
  the built-in `text` preview (fallback) and every file the product opens
  lands in the plugin's editor
- the built-in `files` page kind is TAKEN OVER by an extension
  registration of the same kind: `openTab('files')` draws the plugin's
  explorer, and the built-in resumes when the plugin unregisters
- registrations follow the plugin registry + the side-card settings: a
  type registered later (external plugin) gets its native type too, a
  disabled type is unregistered (absent from the guide, refuses opens)

Adapter (src/client/native/tab-adapter.tsx):
- one synthetic plugin `SidebarTab` per native tab id, minted from the
  native record + navigation params (or the descriptor's own createTab,
  which a native guide open bypasses), kept live across navigations and
  updateTab writes
- the explorer's per-tab expansion/reveal sets and the per-kind instance
  counter, which the plugin's own layout used to hold

Service routing (src/client/service.ts):
- `setSurface` installs the native write face; openTab/openFile land
  natively (a path becomes a resource address, a path-less editor open
  becomes the `files` page, the rest carry the seed as navigation params),
  closeTab/updateTab/activateTab recognise native tab ids
- new optional `OpenTabSeed.target` ('right' default, 'bottom' keeps the
  bottom workbench); the bottom panel's own + menu and auto-terminal pass
  'bottom'
- src/client/native/surface.ts probes the controller's per-session writes
  (`openTabIn`/`openResourceIn`/`closeIn`, not in `ISidebarRight`) and
  queues opens for a session whose surface is not mounted yet

Right panel retired behaviourally (Phase 3 deletes the code): Sidebar
renders its bottom workbench only, keeps the panel collapsed, and hides
the right-panel toggle. The panel-specific e2e specs are removed; the
mount lane now drives the native guide (tab sweep), the plugin explorer
inside the native pane (editor/mermaid/README chunks) and the sidechat
host routes.

Verified: pnpm typecheck / lint / build / test (126 files, 1349 tests) /
check:consumer-types, plus pnpm test:mount against a real
@deepseek-ai/dsh@0.1.5-alpha.1 host (7/7).
2026-09-09 11:47:31 +08:00
Menghuan1918 ba0114749f feat(dsh): adapt to DSH 0.1.5-alpha.1
Move the plugin onto the DSH 0.1.5-alpha.1 baseline (peer floor
^0.1.5-alpha.1, CI pinned to the alpha dist-tag) and fix the two host
contract changes that alpha line introduced.

Baseline:
- all @deepseek-ai/dsh-* peer/dev deps -> 0.1.5-alpha.1; optional peer
  @deepseek-ai/dsh-client-ui-sidebar-right (declared, unused until the
  native-surface phase); devDep @deepseek-ai/dsh-agent-loop (inbox fold)
- dsh.plugin.json engines >=0.1.5-alpha.1; version 0.19.0-alpha.0;
  SIDEBAR_SERVICE_VERSION follows

Live assistant stream (assistant/chunk is gone):
- 0.1.5 publishes in-flight model deltas as process-local
  agent/assistant-stream frames instead of durable assistant/chunk events
  and embeds the settled stream in assistant/message / assistant/attempt.
  src/assistant-live.ts folds the frames into a bounded per-session buffer;
  sidechat.events serves the current attempt as `live` rows (replaced every
  poll, never a delta) and the transcript renders them; the inherited
  in-progress snapshot reads message content / attempt stream / live frames.

Cold session reads (persistence.inspect is gone):
- 0.1.5 reads a stored session through an explicit read handle
  (open(id,'read') -> handle.read() -> close()). src/session-store.ts owns
  that protocol; every cold read (sidechat transcript, cold-resume preset,
  session.cwd, changes replay) goes through it.

Dead code and docs:
- drop the remote.session.openWorkspacePath interception: 0.1.5's chat opens
  files through ctx.sidebarRight.openResource instead, so the wrapper had no
  caller and would hijack the host's "open in app" gesture; the
  interceptOpenPath pref, its settings row and its locale keys go with it
- AGENTS.md §3 + external-plugin-guide.md describe the new baseline
- vitest setup bridges Node's localStorage accessor to jsdom's store (the
  pre-existing 26 failures on Node >= 22)
- desktop-layout e2e seeds through host RPC and types into the Lexical
  composer (0.1.5 replaced the textarea) instead of driving the directory
  picker

Verified: pnpm typecheck / lint / build / test (124 files, 1327 tests) /
check:consumer-types, plus pnpm test:mount against a real
@deepseek-ai/dsh@0.1.5-alpha.1 host (18/18).
2026-09-09 11:15:30 +08:00
Atlas Zeng d88dcfc3a5 Merge pull request #595 from omdsh-dev/feat/wait-for-regex-needle
feat(terminal): wait_for needle 支持 regex,found 返回实际匹配文本
2026-09-08 23:27:45 +08:00
HuanLinOTO 3e408dbe48 feat(terminal): wait_for needle 支持 regex,found 返回实际匹配文本
needle 以 JavaScript 正则优先(编译失败回退字面匹配,旧字面 needle 完全兼容);found 结果新增 match 字段回报实际匹配文本——单个 needle 可覆盖多结局场景(如 (BUILD_OK|BUILD_FAIL) 同步构建成功/失败,match 区分命中分支);工具 description / needle 参数 / output schema / render 同步更新并写明多重匹配场景;新增 3 个 pty 级测试(alternation 命中分支、非法正则回退、元字符匹配);全量测试失败集合与干净 main 基线完全一致(27 个失败均为预存在 win32 非回归)
2026-09-08 23:07:07 +08:00
Atlas Zeng eecf9dc536 Merge pull request #593 from omdsh-dev/feat/terminal-shell-quoted-args-probe
feat(terminal): 终端 shell 配置加固——引号解析、POSIX 预检、本地化报错
2026-09-08 22:28:22 +08:00
Menghuan1918 ad22abcfe6 docs(plans): 修正实施偏差记录的 commit 引用(cbbed88 → 8704994) 2026-09-08 22:20:15 +08:00
Menghuan1918 81919516d8 fix(terminal): WS close reason 按 UTF-8 字节截断,避免超 123 字节抛错
ws 用 Buffer.byteLength 校验 close reason 的 123 字节上限(ws/lib/sender.js),
原 slice(0, 100) 按 UTF-16 code unit 截断,非 ASCII shell 名可达 316 字节
(实测 200 个 CJK 字符),ws.close() 会抛 RangeError 顶掉原始错误。
改为按字节截断且不切碎 code point,并补回归测试。
2026-09-08 22:20:15 +08:00
HuanLinOTO e847771954 docs(plans): 回填终端 shell 配置加固实施偏差 2026-09-08 21:21:31 +08:00
HuanLinOTO e8761ee851 feat(terminal): 配置 shell 未找到时显示本地化可操作横幅(全语言) 2026-09-08 21:21:30 +08:00
HuanLinOTO 8aa9441627 feat(terminal): shell 覆盖去引号进解析、标题跟随覆盖、close reason 机器标记 2026-09-08 21:21:30 +08:00
HuanLinOTO 870499430b docs(terminal): resolveShellExecutable JSDoc 跟随 shell-not-found 新错误码 2026-09-08 21:21:30 +08:00
HuanLinOTO 26706bc3e9 feat(terminal): POSIX 也预检 shell 可执行文件,统一 shell-not-found 错误码 2026-09-08 21:21:30 +08:00
HuanLinOTO 829ad49ddf feat(terminal): 引号感知的 shell 参数解析与路径去引号纯函数 2026-09-08 21:21:30 +08:00
HuanLinOTO de0eac54b7 docs(plans): 终端 shell 配置加固设计与实施计划 2026-09-08 21:21:29 +08:00
Atlas Zeng 146840bb4f Merge pull request #586 from omdsh-dev/chore/release-0.18.1
chore(release): 0.18.1
v0.18.1
2026-09-08 16:20:03 +08:00
Menghuan1918 364a48df98 chore(release): 0.18.1
- package.json / dsh.plugin.json / SIDEBAR_SERVICE_VERSION → 0.18.1(三者锁步由
  tests/service.spec.ts 与 tests/manifest-consistency.spec.ts 断言)
- README / README_EN:新增 v0.18.1 条目并同步三处徽章版本号——自 v0.18.0 起的
  11 个 PR:#499 变更面板预览增强(md 阅读模式 / html·pdf 渲染 / 密钥脱敏)、
  #550 文件树重命名与删除、#535 目录名词典化、#562 侧边对话种子补 fork 标记、
  #569 markdown 本地图片改写、#576 git 折叠上下文按需展开(含 git.show 路由
  rev:path 修正),以及 #524/#526/#527/#534/#536/#540/#541/#542 的内部收敛
- docs/external-plugin-guide.md:当前版本行 → v0.18.1
- DSH 基线不变(peer ^0.1.2-rc.1,CI 钉 0.1.2-rc.1),无运行时行为改动

验证:make check(typecheck → lint → build → test → check:consumer-types)全绿,
124 test files / 1319 passed, 9 skipped
2026-09-08 16:16:16 +08:00
Atlas Zeng df1f3b9666 Merge pull request #576 from LaoYueHanNi/fix/diff-fold-expand
fix(diff): git 折叠上下文按需展开,修复「点击展开」无反应
2026-09-08 16:14:53 +08:00
haruhi 5bf7dfe3c4 fix(git): git.show 路由改用仓库相对路径,修复折叠展开空白
- src/index.ts: git.show 的 path 不再经 resolveGitPath 转绝对路径——rev:path 语法只认仓库相对路径(diff 输出剥 a// b/ 前缀的形态),绝对路径恒 fatal 返 null,折叠展开渲染空行;透传后暴露面与 git.diff/git.log 一致(仅本仓库版本树)
- src/client/changes/DiffPane.tsx / src/client/DiffTab.tsx: 两侧内容皆空时抛错走「上下文未加载」降级,不再静默空白展开

背景:真机 link 测试发现展开后 marker 消失但零行渲染——git.show 路由此前无真实调用方,resolveGitPath 的工作区语义与 rev:path 树寻址不匹配的缝隙首次暴露。
设计 docs/plans/2026-09-07-git-fold-expand-design.md 实施偏差第 8 条同提交。
2026-09-08 00:04:25 +08:00
haruhi 900654b8fd fix(diff): git lens 折叠行接入按需展开,修复点击无反应
- src/client/changes/DiffPane.tsx / src/client/DiffTab.tsx: 按 SidebarDiffRef 构造 fold loader——commit 侧 <hash>^ 与 <hash>;staged 侧 HEAD 与 :0;unstaged 侧 :0 与工作区 fsRead(失败/二进制降级空串);per 文件内容 Promise 缓存,目标或刷新 tick 变化时重置
- fallback 换侧时记录 effectiveStaged,loader 依实际渲染侧取数,修正切片行号错位
- src/client/locales.ts 及 19 个 locales-*.ts: 新增 changesFoldLoading / changesFoldUnavailable 全量补译(共 20 文件 21 处)
- tests/diff-fold-expand.spec.tsx: 展开/折叠/缓存去重、失败降级、无 loader 保持 inert、session 路径回归

背景:git 提交/工作区 diff 折叠行承诺「点击展开」却永远不可点,与 session lens 行为不一致;接入状态机后承诺为真,session 路径与现有文案零变化。
设计 docs/plans/2026-09-07-git-fold-expand-design.md §4.5-4.6。
2026-09-07 23:51:10 +08:00
haruhi d990c10e59 feat(diff): 折叠行异步展开状态机与内容切片
- src/client/api.ts: 仿 gitCommitDiff 新增 gitShow 封装,走既有 git.show 服务路由(payload {rev, path},返回 content 或 null),服务端零改动
- src/client/diff/rows.ts: 新增 foldRowsFromContents 纯函数——old 侧按 fold 行号切片为 context 行(new 侧经区间偏移映射,落界才带行号),new 侧补纯增行;越界截断、\r 保留
- src/client/diff/DiffRows.tsx: 新增 resolveFold prop;foldData/foldLoading/foldFailed 三态,加载中不可点,失败降级静态标记;foldEpoch 使 segments 重置后 in-flight 回调失效
- src/client/diff/DiffFiles.tsx: resolveFold 下传各文件 DiffRows,fold 级 Promise 缓存去重并发点击与重挂载
- tests/diff-rows.spec.ts: foldRowsFromContents 六用例(对称/纯删/纯增/\r\n/空内容/越界)

背景:git -U3 不输出 hunk 间未改动行,gap fold 只有行号范围没有 rows,渲染复用「点击展开」文案却无交互;展开能力建为独立状态机(含 api 封装),git lens 只需注入取数 loader。
设计 docs/plans/2026-09-07-git-fold-expand-design.md §4.1-4.4。
2026-09-07 23:51:06 +08:00
haruhi 6b02285742 docs(diff): git 折叠行按需真实展开设计文档
- 新增 docs/plans/2026-09-07-git-fold-expand-design.md:bug 证据链、切片算法、两 lens 取数映射与边缘 case 清单
- §8 仓库规范补「风格对齐」条款;文末补实施偏差记录 7 条

背景:changes 的 git lens 折叠行显示「点击展开」却无交互(-U3 裁剪使 gap fold 无行文本),文档定稿按需拉取两侧内容真实展开的修复方案。
2026-09-07 23:51:05 +08:00
Atlas Zeng 6fbfeedc01 Merge pull request #499 from lhh010/feat/changes-md-reading
feat(changes): markdown reading mode + expanded syntax highlighting
2026-09-07 16:06:27 +08:00
Menghuan1918 9302d02cf5 refactor(changes): bring the PDF render toggle in line with the pane conventions
Escape the .pdf extension in the op-target regex (the unescaped /.pdf$/
matched any path ending in pdf) and fold the copy-pasted toggle button
into PaneToggle; record the sixth deliverable in the design doc.
2026-09-06 23:48:25 +08:00
Menghuan1918 0c3d1d6e32 docs(changes): design doc for the op-preview enhancements
Records the five features shipped on this PR (md reading mode incl.
mermaid, highlight expansion, secret redaction, html render mode), their
deliberate boundaries (redaction heuristics, saved-file render
semantics), and the 2026-09-06 standards pass.
2026-09-06 23:46:46 +08:00
Menghuan1918 157e02e768 refactor(changes): dedup the op-preview path and align conventions
- redaction useMemo: one redactText pass per field (the outcome carries
  both text and hit; was a hit-scan plus a mask pass), and the banner
  condition drops the redundant redactionOn check (hit implies on)
- three copy-pasted header toggles -> PaneToggle; the md reading body
  -> MdReadingView (absorbing the mermaid branch + codeLabels)
- redaction preference persisted under the repo's dsh-sidebar:v1: prefix
- render toggle limited to .html/.htm (the editor html viewer's ext set;
  .xhtml went beyond any claim)
- tsx/jsx share TS_WORDS/JS_WORDS; CSS word list loses its duplicate
  entries and its nonexistent true/false constants
- parseReadContent/parseReadLines share the contentLines() envelope core
  (content extraction + Showing-lines filter)
2026-09-06 23:46:46 +08:00
Menghuan1918 04693192df fix(redact): keep the credential heuristics off ordinary files
Path layer: patterns now match with a right boundary (must not continue
into [a-z0-9]) and list only store-plural forms (credentials/secrets/
passwords, not tokens), so tokenizer.ts / dev.environment.ts /
style.keys.ts / api-tokens.md are no longer masked whole while
.env.local / credentials.json / mytoken.yaml still are.

Content layer: drop the bare key/auth/token/pass/bearer field names —
they label far more ordinary content (keyboard keys, CSS custom
properties, lexical tokens) than secrets; qualified names stay and
Bearer schemes remain covered by BEARER_RE.

Adds the over-masking regression cases; fixes the spec's source import
extension to .ts (repo convention).
2026-09-06 23:46:46 +08:00
Menghuan1918 03e19c4cd4 fix(changes): use real skin tokens in the preview-pane additions
.htmlFrame hardcoded #fff -> var(--dsw-alias-bg-base) (mirrors the editor's
.editorHtml); .redactBanner referenced --dsw-alias-state-warning-primary,
which does not exist in the DSW vocabulary (it is state-warn-*), so the
var() always fell through to a hardcoded #b8860b — use the real token with
no literal fallback.
2026-09-06 23:46:46 +08:00
lhh010 dc40f82180 chore: re-run CI (agent-pty timing flake on ci-windows) 2026-09-06 23:18:53 +08:00
lhh010 6df06717ad feat(changes): PDF render preview for .pdf op targets 2026-09-06 23:06:18 +08:00
lhh010 8456d77a92 Merge remote-tracking branch 'origin/main' into feat/changes-md-reading
# Conflicts:
#	src/client/MarkdownHtml.tsx
2026-09-06 22:59:56 +08:00
Atlas Zeng 2dc2dcf418 Merge pull request #569 from omdsh-dev/fix/markdown-preview-local-images
fix(editor): rewrite local images in markdown runs of the split renderer
2026-09-06 22:52:37 +08:00
Menghuan1918 2a99968fa3 fix(editor): rewrite local images in markdown runs of the split renderer
Every .md preview renders through MarkdownDocument (htmlInfo is ungated),
but its markdown segments passed the raw source into MarkdownText, whose
chat-security stance drops non-http(s) image destinations — so local
markdown-syntax images (![alt](./icon.svg), reference-style definitions)
always fell back to alt text. Only HTML <img src> tags were rewritten
(postProcessSanitized).

Apply rewriteLocalImageUrls to each markdown run (segment + appended
reference definitions) inside MarkdownDocument's prepared memo, through
the same media trust fence the sanitized HTML leaves already use.
Idempotent for already-absolute media URLs; remote destinations
untouched. Also correct the stale "non-null only for HTML documents"
comment on TextEditor's htmlInfo.
2026-09-06 21:53:49 +08:00
Atlas Zeng bb21657eae Merge pull request #562 from omdsh-dev/fix/sidechat-seed-inherited-inbox
fix(sidechat): 种子补 fork 标记,子会话不再继承父会话未领取的 inbox 消息
2026-09-06 21:43:13 +08:00
lhh010 e356791661 feat(changes): HTML render preview for .html op targets
.html/.htm/.xhtml op previews (read / write / edit, non-error) gain a
Render/Raw toggle in the preview pane head: the render view loads the
SAVED file through the same /sidebar/html route the editor's html viewer
uses, so relative assets (./style.css, img/x.png) resolve inside the
route and a segmented read still renders the whole document (the route
serves the file, not the op snapshot — the file-trace-style srcDoc
approach would render a cut-off document whose scripts never run).

The frame is ALWAYS sandboxed (HTML_IFRAME_SANDBOX, moved to the shared
html-preview.ts module — opaque origin, no allow-same-origin, no
top-navigation) plus the route's CSP sandbox header as
defense-in-depth; unlike the editor viewer this surface has no
no-sandbox escape hatch. The contract is pinned by new sandbox-spec
tests on the extracted HtmlRenderPreview component and the pane toggle
(off by default; absent for non-html and error targets).

Locales: zh/en + all 19 shipped third-language overrides (key-set-equal
gate green). Typecheck + eslint clean; sandbox/redact/diff-rows/frontmatter
suites 36/36 green. The full-suite host-side failures (fs-operations /
pty smoke / git / install-powershell) are pre-existing on the base
branch (verified via stash) and unrelated.
2026-09-06 10:37:53 +08:00
Menghuan1918 93ad87dfe7 fix(sidechat): stamp the seed with fork markers so the child inherits no pending inbox
sidechat.start passed `seed` without `meta.isSeeded: true` +
`inheritedEventCount`, so dsh-session treated the whole seed as the
child's OWN events and the child's Inbox constructor replayed the
parent's `agent/inbox/spliced` history — inheriting whatever input sat
UNCLAIMED in the parent at the click moment:

- next-turn: a queued follow-up behind a still-running turn;
- next-step (the reported long-context case, parent queue empty): a
  tool-result context / steering spliced between step boundaries.

The first side prompt then claimed [phantom, boundary, question] — the
stale parent message was sent to the model BEFORE the boundary and
logged as a user/message. The fix passes the exact marker pair the
host's own session.fork uses (api-session-controller); ownEvents() now
stops at the end-seed boundary and the inherited inbox replays empty.

Regression tests run the REAL Session.create + Inbox in both
directions (old shape inherits the phantom, marked shape stays empty);
routes tests pin the two create options. Verified: pnpm typecheck /
test (5 pre-existing unrelated UI-spec failures on main unchanged) /
build.
2026-09-06 00:59:03 +08:00
Atlas Zeng a5c52b3f1b Merge pull request #550 from omdsh-dev/feat/file-tree-rename-delete
feat(files): 文件树重命名/删除 + 右键菜单减重与子菜单钳制
2026-09-04 23:04:37 +08:00
Menghuan1918 e39ea92ade feat(files): tree rename (inline) + delete (confirmed) + submenu visual tether
资源管理器补全:文件树右键菜单新增「重命名」(行内编辑:预填+全选、
Enter/失焦提交、Esc 取消、IME 组合键守卫)与「删除」(Modal 确认,文件/
目录文案区分,永久删除——宿主无回收站)。根行(工作区根)不提供两项,
服务端 fs.rename/fs.remove 双保险拒绝;目标已存在 409;link-aware——
symlink 行只改/删链接本身(lstat 判定),fence 校验对解析后目标进行。

落定收尾:pruneTree 清旧前缀缓存 + 收起 stale expanded + 单层重载父目录;
tab 调解走 service(rename→updateTab 改路径改标题、delete→closeTab 关
受影响 tab,覆盖两侧分栏/底栏/浮动窗口,经 tree-mutations.ts)。

二级菜单视觉关系(layout.css,仍限 data-dsh-sidebar-submenu 作用域):
父行在子菜单打开期间保持高亮(宿主已渲染 aria-expanded="true"),
卡片间距 10px→4px(悬停走廊同步收窄),翻转规则一并 4px。

i18n 新增 7 key × 20 语言文件(locales.spec 的 key-set-parity 门禁)。
测试:fs-operations 追加 12 例(rename/remove 全失败面);新增
file-tree-rename-delete.spec.tsx 8 例(菜单可见性/重命名流/删除流/错误条);
menu-flip.spec.tsx 契约扩展覆盖 tether 规则。设计文档
docs/plans/2026-09-04-file-tree-rename-delete-design.md。
2026-09-04 22:08:02 +08:00
Menghuan1918 76b57508f4 fix(ui): compact context menus + viewport-clamped submenu flip
减重:全部右键菜单(FileTree 行菜单 / TabBar tab 菜单 / GitLens 文件与
历史菜单 / FreeWindow 标题菜单)与「+」菜单切到宿主 Menu 原语的 compact
密度(26px 行高 / 12px 字 / 164px 卡片),图标随槽位 16→14,「打开方式」
子菜单图钉 20→16,还原工具型右键菜单的趁手感。

越界:宿主子菜单纯 CSS 定位(从父行向上、向右生长)不受视口钳制——树顶
部右键时「打开方式 ▸」子菜单伸出视口上沿,窄面板(280–400px)时伸出右
沿。新增 menu-flip.ts:菜单打开期间在 <body> 发布 data-dsh-sidebar-submenu
="down|left" 方向 token(y < vh/2 → down;x + 400 > vw → left),layout.css
据此翻转子菜单生长方向与 ::before 悬停桥;属性存续期即作用域,宿主自有
菜单不受影响。刻意不做 max-height 滚动钳制——overflow 裁剪会切断悬停桥。

测试:menu-flip.spec.tsx(token 纯函数 + hook 生命周期 + CSS 契约跨文件
一致性);tab-bar / file-tree 两个 spec 追加翻转向量断言;mount.e2e 追加
compact 行高(≤30px)布局级证据。设计偏差记录同步至
docs/plans/2026-08-22-open-with-menu-design.md。
2026-09-04 20:28:35 +08:00
Atlas Zeng e1b9b539e4 Revise version details and update QR codes
Updated version information and fixed QR code image links in README.
2026-09-04 17:33:42 +08:00
lhh010 7f9a040065 fix(changes): resolve merge lint errors from origin/main sync
- DiffPane.tsx: gate the redacted view on the computed `hit` (the unused-var
  lint was the memo returning redactionHit: true unconditionally; now an
  unredacted payload short-circuits like the toggle-off path, and only an
  actual secret hit builds the masked copy)
- MarkdownHtml.tsx: drop the three dead imports (ComponentType,
  lazyChunkComponent, MermaidMarkdownProps) left by the mermaid-lazy merge

Typecheck clean; eslint clean on both files; 39 targeted tests pass.
2026-09-04 14:25:33 +08:00
lhh010 16d8df2e93 Merge remote-tracking branch 'origin/main' into feat/changes-md-reading
# Conflicts:
#	src/client/changes/DiffPane.tsx
#	src/client/locales-ar.ts
#	src/client/locales-de.ts
#	src/client/locales-fr.ts
#	src/client/locales-hi.ts
#	src/client/locales-id.ts
#	src/client/locales-it.ts
#	src/client/locales-ja.ts
#	src/client/locales-ko.ts
#	src/client/locales-nl.ts
#	src/client/locales-pl.ts
#	src/client/locales-pt.ts
#	src/client/locales-ru.ts
#	src/client/locales-sv.ts
#	src/client/locales-th.ts
#	src/client/locales-tr.ts
#	src/client/locales-vi.ts
#	src/client/locales-zh-HK.ts
#	src/client/locales-zh-MO.ts
#	src/client/locales-zh-TW.ts
2026-09-04 14:13:34 +08:00
lhh010 a2d3faf7b5 feat(changes): secret redaction layer for op previews
The changes-tab op previews replay read/write/edit payloads into the DOM
(diff rows, read rows, markdown reading mode, error text) — when the model
reads a credentials file, that plaintext must not land on screen or in
inspectable DOM. New display-only redaction layer, on by default with a
one-click pane toggle (persisted in localStorage):

- path layer: credential-shaped filenames (.env / *secret* / *credential* /
  *token* / *api-key* / private keys...) mask whole files line-wise
- content layer: ordinary files mask only secret-shaped spans — secret
  field assignments keep the field name (api_key: [REDACTED]), plus bare
  token prefixes (sk- / AKIA / ghp_ / xox / ya29.), Bearer headers, and
  PEM private-key block headers
- redacted payloads flow through the same viewOp shape into diff rows,
  read rows, markdown reading mode, and error text

Known edge (documented in README): an unquoted mermaid node label
containing a redacted secret breaks the diagram and falls back to source;
workaround is quoting the label. Session data is untouched — this is
display-only and not a boundary against same-context malicious plugins.

Locales: zh/en dictionaries + all 19 shipped third-language overrides
(key-set-equal gate green). 9 new unit tests; typecheck clean; targeted
suites 40/40 green. The fs-operations failure is pre-existing on the base
branch (environment-dependent, verified via stash) and unrelated.
2026-09-04 13:10:21 +08:00
Menghuan1918 f59ffd0741 docs(plans): 全方位代码防腐审查报告与 backlog(2026-09-03) 2026-09-03 21:21:22 +08:00
Atlas Zeng f7332f332b Merge pull request #542 from omdsh-dev/feat/split-sidebar
feat(client): Sidebar.tsx 按关注点增量拆分
2026-09-03 21:20:15 +08:00
Menghuan1918 e105b01f2b refactor(client): 抽取自由窗口拖出检测与浮窗层到 sidebar/free-windows.tsx
Sidebar.tsx 拆分第五块:useFloatDragout(document 捕获态 dragover/
drop/dragend/blur 监听、会话列矩形命中测试、hint 状态去抖)与
FreeWindowLayer(floats.map 渲染 + 拖出 hint 覆盖层,fragment 渲染
保证 DOM 顺序与原先逐字一致)原样迁出,注释逐字保留。

行为不变:effect 依赖与回调闭包相同;层组件接收的 renderTab/
getTabIcon/store/ctx/sessionId/cwd 即原先 JSX 内联闭包捕获的同一批
每 render 新建的值,FreeWindow 收到的 props 与回调语义不变。
2026-09-03 21:09:20 +08:00
Menghuan1918 b4ab3f154f refactor(client): 抽取 pinned 虚拟 tab 注入与动作包装到 sidebar/use-pinned-tabs.ts
Sidebar.tsx 拆分第四块:activePinnedTabId/pinnedRevision 状态、
pinnedEntries/pinnedVirtualTabs/augmentedTree 三个 memo(含
exhaustive-deps 豁免注释随迁)与 wrappedActions(虚拟 id 路由回
HOME 会话的 reduceFor + ptyClose 语义)原样迁出,注释逐字保留。
调用点移到 actions memo 之后(hook 无 effect,仅 useState/useMemo,
位置调整不改变行为)。

行为不变:memo 依赖数组逐字相同;wrappedActions 的 actions 入参即
原先闭包捕获的同一个 memo 产物。
2026-09-03 21:08:03 +08:00
Menghuan1918 d5b1201ad8 refactor(client): 抽取宿主推送与自动激活触发器到 sidebar/use-host-feeds.ts
Sidebar.tsx 拆分第三块:agent-terminals / agent-opens 两条 WS 推送
(含重连退避与失败上限)、子代理自动激活(去抖重估)、后台任务自动
激活、拓扑跳回(subagentJumpRef)连同 FAILURE_LIMIT /
AUTO_OPEN_DEBOUNCE_MS 常量原样迁出,注释逐字保留。Sidebar 侧仅
消费返回的 subagentJumpRef(renderTab 的 onSubagentJump 装填)。

行为不变:五个 effect 的依赖数组与闭包值逐字相同,hook 每 render
无条件调用,effect 挂载顺序保持(feeds → center → float →
bottomAuto → drag)。
2026-09-03 21:05:41 +08:00
Menghuan1918 6bccd21a49 refactor(client): 抽取中心列定位/测量链到 sidebar/use-center-column.ts
Sidebar.tsx 拆分第二块:centerRectRef/centerMeasured/centerColRef/
draggingRef、measureCenter(ref + 直写 DOM,避免动画帧级 re-render)
与整条 locate/ResizeObserver/MutationObserver/1.5s 重试链原样迁出到
src/client/sidebar/use-center-column.ts,注释逐字保留。bottomRef 与
state?.bottomOpen 由调用方传入(bottomRef 在主文件先建再共享,行为与
原先 measureCenter 闭包捕获一致)。

行为不变:hook 每次 render 全量重跑,effect 依赖与值完全相同;唯一
文字差异是三处依赖数组补列了跨 hook 边界后失去 useRef 溯源的稳定
ref 对象(centerColRef/draggingRef,标识恒定,不改变触发时机),
为 react-hooks/exhaustive-deps 零告警所需。
2026-09-03 21:03:14 +08:00