Commit Graph
67 Commits
Author SHA1 Message Date
Menghuan1918 6d877471d4 fix(pty): contain node-pty's deferred Windows resize after pty exit
node-pty's WindowsTerminal queues resize calls that arrive before the
ConPTY control socket's first data flush (_deferNoArgs). If the pty exits
before the flush, the deferred resize throws 'Cannot resize a pty that
has already exited' inside the socket's 'data' handler — uncatchable by
any caller and fatal to the host process. The Windows CI lane caught
exactly this as an unhandled error while all 1233 tests passed.

Park pre-first-output resizes in our own gate (armed at every spawn
site) and replay them via setImmediate once output has flushed the
deferred list — by then node-pty executes resizes synchronously, so the
exited throw lands in tryResizePty's catch instead of the host. POSIX
terminals resize synchronously (no deferral), so the gate is armed on
Windows only and the other platforms keep their exact behavior.

Also route AgentPtyRegistry.resize through tryResizePty: it previously
called pty.resize bare, missing the just-exited throw guard the tool and
attach-socket paths already had.
2026-09-03 10:42:56 +08:00
Kunping Wang 180b16de1b fix: 修复 WSL 会话 Linux 路径解析 (#399) 2026-09-02 23:20:17 +08:00
Atlas Zeng 5815ad0f09 Merge pull request #428 from Jstn-1g/fix/426-safe-pty-resize
fix(terminal): contain transient resize failures
2026-09-02 23:08:52 +08:00
Menghuan1918 a78dd57b23 feat: adapt to DSH 0.1.2-alpha.5 and bump to v0.19.0-alpha.0
- pins: CI mount-gate CLI, dsh.plugin.json engines floor, and the
  @deepseek-ai/* peer floor / devDependencies baseline move to
  0.1.2-alpha.5 (dsh-client-locale trails at 0.1.2-alpha.3 — no such
  release upstream; ^0.1.2-alpha.3 already accepts the alpha.5 runtime)
- code: migrate the alpha.4 Session.events removal to snapshotEvents()
  (sidechat transcript live reads, fork inheritance, jobs.output replay,
  subagent activity) and drop the seedLength meta field the host removed
  from CreateAgentOptions.meta
- gates: pnpm peers check clean, typecheck + 1137 unit tests green,
  real-host mount smoke 14/14 on npm-published 0.1.2-alpha.5
2026-09-02 21:43:34 +08:00
Menghuan1918 f024fbf092 fix(changes): 会话事件改走宿主 changes.ops 路由 + diff 展开二级设置 + 视觉迭代
CI 修复(plugin-mount 的 ctx.sessions.get TypeError ×1003):
- 客户端 runtime 无事件日志面——badge/SessionLens 原在客户端直读
  ctx.sessions.get(id).events / ctx.on('session/event'),全部不可达。
- 新增宿主路由 changes.ops:live 会话日志优先,冷会话回放
  sessionPersistence.inspect;afterSeq 增量(缺省游标含 seq 0,
  显式游标为 seq > afterSeq,同 sidechat.events 约定),窗口上限
  4000 与客户端累加器一致。
- ChangesTab 轮询(mount 一拉 + visible 2.5s 增量)折入 ops,写
  模块级 opCounts 缓存供 badge 同步读取;SessionLens 改纯展示。

diff 展开二级设置(changesDiffFloat,默认浮窗):
- prefs 四处接线 + git tab settings select(浮窗/面板,图标选项卡)
- 预览面板「展开为 diff tab」按设置落点:浮窗居中弹出或源 pane
  下方 split;i18n 全 21 词典同步。

视觉迭代(真机挂载截图审查后调整):
- Session 视角:kind 徽章为左轨,大小+相对时间右对齐元列;失败
  操作行红色内嵌条;文件组头降为三级标签并加组间距。
- Git 视角:untracked '?' 徽章与新增同绿色;stage 按钮改 plus 图标。
- diff 折叠行:可展开折叠带 › 箭头 + 次级对比度,静态 gap 不再
  显示 pointer/hover;预览头路径改尾部截断(RTL 技巧,保文件名)。

验证:typecheck ✓ / vitest 1123 ✓ / pnpm test:mount(钉 alpha.2 CLI)
14/14 ✓;真机挂载截图(badge=5、双视角、浮窗展开)0 console error。
2026-08-31 17:52:59 +08:00
Menghuan1918 f9e00bb0d0 feat: adapt to DSH 0.1.2-alpha.2 (npm-published) and bump to v0.17.2
DSH 0.1.2-alpha.2 is on npm under the `alpha` dist-tag, so the CI mount
pin and the @deepseek-ai/* devDependencies baseline move up from
0.1.1-rc.1/rc.2 (the dead dsh-client-runtime devDep goes away with
them). Verified end-to-end against a real 0.1.2-alpha.2 host: full
vitest suite (1106), typecheck, build/pack, consumer type surface, and
the headless mount smoke 14/14 — the token URL, slash RPC and
MarkdownText labels contracts are unchanged from alpha.1.

- dsh-settings dropped the runtime `settingsNamespace` export:
  namespaces are validated at compile time (SettingsNamespaceInput), so
  the host passes the 'dsh-better-sidebar' constant directly and the
  fake settings service in smoke.spec casts to the branded type.
- dsh-subagent bumped SUBAGENT_DESCRIPTOR_VERSION 2 -> 3: the version is
  stamped by the host package; the sidechat seed assertion now follows
  the constant instead of pinning a literal.
- Restored SessionEvent.ignorable and the RemoteError wrapping were
  verified to have no impact on this plugin.
- e2e scratch profiles (e2e-mount.sh / e2e-aggregate-mount.sh) add
  '@deepseek-ai/*' to minimumReleaseAgeExclude (mirroring the repo
  root): alpha releases are often <24h old when the lane runs, which
  pnpm 11's default minimumReleaseAge would refuse to install.
- vitest excludes .worktrees/** — local task worktrees carried stale
  code against the shared node_modules and broke on the devDeps bump.
- Version lockstep 0.17.2 (package.json, dsh.plugin.json,
  SIDEBAR_SERVICE_VERSION, README/AGENTS/guide docs).
2026-08-30 22:38:09 +08:00
Menghuan1918 a9353ce627 feat: workspace fence toggle (workspaceFence pref + one-click off in error surfaces)
- New persisted built-in pref workspaceFence (default true) under the
  files card's secondary settings modal; host reads it per request via
  the settings seam (fenceEnabledOf — absent/malformed stays ON).
- ensureWorkspacePath/ensureWorkspaceWritePath gain a fence param:
  disarmed skips the containment assert but keeps realpath resolution;
  wired through fs.tree/fs.read/fs.write, the media + html routes, and
  the upload path (writeWorkspaceUpload fence input).
- Editor load failures and file-tree level errors show the localized
  reason plus a one-click global off (FenceErrorNotice) that flips the
  pref, adopts it into the store, and auto-retries the failed operation
  (reloadSeq / retryDir) — the raw wire text is never shown.
- GitView's two client-side pre-checks (hidden open action + no-op
  defense) respect the pref; store threaded through TreePanel/FileTree.
- i18n: 4 new keys across all 21 dictionaries; tests: fence-off route
  coverage, prefs parsing, schema default, editor toggles list;
  docs: guide §8/§8.1 + plans/2026-08-29 design record.
2026-08-29 15:49:19 +08:00
Justin Skowyra cdd5706f5b fix(terminal): contain transient resize failures 2026-08-29 15:31:12 +08:00
多玩幻灵qwq d71261ace3 fix(cwd): resolve detached session cwd via persistence index, not host process cwd (#372)
* fix(cwd): resolve detached session cwd via persistence index, not host process cwd

sessionCwdOf fell back to process.cwd() when a session was not yet
attached and the client did not send a cwd — on Windows the host
process cwd is the DSH source root (dsh.cmd's pushd %DSH_ROOT%),
so every user-project path failed the workspace containment guard
with 'path "..." is outside workspace'. The bug surfaced when
clicking chat file links at first paint (session still hydrating)
or any flow that hit a sidebar route before the client's
fetchedCwd effect resolved.

Fix: thread ctx.get('sessionPersistence') as the fallback between
the client cwd and the host process cwd. The persistence index
holds every persisted session's header (including cwd), so a cold
(detached) session resolves its real project cwd instead of the
DSH source root. The host process cwd remains the final fallback
for deployments without persistence (tests / stripped-down hosts);
production always provides persistence.

sessionCwdOf is now async; all 8 call sites await it. The two tool
resolvers (registerTools / registerOpenTool) take an async
resolveCwd callback; the one execute path that used it
(terminal_create) is now async.

Tests: two new regression cases in smoke.spec.ts cover the cold
session via persistence path and the persistence-without-cwd
fallback. Existing stubs updated to the async resolveCwd signature.

* Address Copilot review: fix JSDoc accuracy and validate persistence cwd

- tools.ts / agent-opens.ts: correct the resolveCwd JSDoc — the resolver
  does fall back to the host process cwd as a final tier (tests /
  stripped-down hosts); the previous "never falls back" wording
  contradicted the actual sessionCwdOf contract.
- index.ts: route the persistence meta.cwd through requireAbsolute,
  matching the clientCwd path. A buggy/corrupt persistence layer that
  stored a relative cwd would otherwise flow into the workspace guard,
  be resolved against the host process cwd, and potentially recreate
  the original "outside workspace" misclassification in a harder-to-
  debug way.
- smoke.spec.ts: use resolvePath() for the cold-cwd fixture so it is
  absolute on both POSIX and Windows; add a regression test for the
  relative-cwd rejection path.
2026-08-27 21:44:04 +08:00
HuanLinOTO 699ce448a1 fix: 处理 Copilot 二轮 review 的 10 条评论
- BrowserView: 即使 host 在白名单,GUI 自身 origin 永不给 allow-same-origin(裸 host 条目覆盖所有端口时可提权)
- intercept: revealInExplorer 相对路径经 resolveSidebarPath 解析后再 revealPaths(交付物路径是相对的)
- intercept: ctx.betterSidebar 改为 ctx.get(#357 纤维链读取约定)
- FileTree/EditorHost/TreePanel/tabs: 把 revealed 贯通到树行渲染 + 高亮类 + scrollIntoView(此前高亮永不渲染)
- state.revealPaths: 重建祖先目录时保留前导分隔符(POSIX/UNC 此前目录永远折叠)
- state.schedulePersist: 仅活动会话写入全局宽度(targeted open 不再用非活动会话的过期宽度覆盖拖拽值)
- browser.probe: GET 回退显式取消 body(否则流式/大响应挂住 socket)
- terminal-font: leading generic 前只放 symbols-only 字体;fully-patched 放 generic 后(避免 hijack 度量字体)
- sidebar.module.css: 恢复拖拽反馈(#130 的 silent resize 与 PR 描述冲突,取 HEAD)
- Sidebar: 剩余 4 处 ctx.betterSidebar 改 ctx.get
- tests: 补 revealPaths/iframeSandboxFor/terminal-font 回归测试 + EditorHost/FileTree 测试补 revealed prop
2026-08-24 18:40:04 +08:00
HuanLinOTO 6001f0167c fix: 合并 main 后的测试回归修复
- index.ts: 移除 #130 遗留的 ctx.on('session/disposed')(fake ctx 无 on,
  且 #130 已按取 HEAD 决策,closeSession 调用方一并移除)
- browser.probe: 405→GET 重试后跳过 embed-signals GET,避免多余第 3 次请求(#69)
- EditorHost: 移植 #228 的 dirty 确认守卫到 HEAD 的 reloadSeq 结构(refreshFile)
- tests: fake ctx 补 get()(#357 ctx.get 回归,free-window/editor-refresh/markdown)
- tests: prefs 期望对象补 browserAllowedLoopback(smoke/plugin-shape/state)
- tests: builtins 浏览器设置行补 browserAllowedLoopback(#69)
- tests: install-powershell BOM 门限仅对实际文档化远程调用的文件生效
- locales: 19 语言补齐 showInFolder / refreshUnsavedConfirm(#94 #215 #228)
2026-08-24 17:54:33 +08:00
HuanLinOTO 15e90d936e merge: 合入最新 main(#353 agent-open-tools / #360 markdown-html-toc 等),解决 PR 合并性
# Conflicts:
#	src/client/EditorHost.tsx
#	tests/prefs.spec.ts
2026-08-24 16:41:02 +08:00
HuanLinOTO 2357fc08a5 fix: typecheck after PR train merge
- Add revealed prop to TabContentMemoKey + comparator (PR #94 threads
  it through TabContent but the memo key lacked it).
- Destructure revealed in TabContent body.
- openpath-intercept test: provide no-op revealInExplorer default.
- prefs test: add browserAllowedLoopback to setPrefs calls (PR #69).
- smoke test: drop duplicate encodeHtmlUrl import (PR #193 dup).
- index.ts: null-guard ptyManager in session/disposed (PR #130 revert).
- locales-fr/nl: avoid unescaped apostrophes in loopback desc (PR #69).
2026-08-24 01:39:53 +08:00
HuanLinOTO b5eb71a136 merge #228: markdown manual preview refresh (MuziIsabel)
# Conflicts:
#	src/client/EditorHost.tsx
#	src/client/TextEditor.tsx
2026-08-24 01:34:55 +08:00
HuanLinOTO 64338f4244 merge #193: html preview UTF-8 encoding declaration (Qiuner) 2026-08-24 01:34:15 +08:00
HuanLinOTO 4feb5ef8de merge #130: drag-release bounce, session-delete terminal cleanup, silent resize (chalei1122-cell)
# Conflicts:
#	src/client/Sidebar.tsx
2026-08-24 01:33:44 +08:00
HuanLinOTO 8e04ee6f74 merge #69: probe embed signals via GET fallback when HEAD omits them (trinhthiminh650-tech)
# Conflicts:
#	src/client/builtins/tabs.tsx
#	src/client/locales.ts
#	src/client/prefs.ts
#	src/config.ts
#	src/index.ts
#	src/prefs-shared.ts
2026-08-24 01:31:54 +08:00
Menghuan1918 68640101e1 feat: sidebar_open tool gates model-driven sidebar opens behind a global setting
New global Side card setting `agentOpenTools` (default off, 常规 group):
when enabled, one `sidebar_open` tool is injected into the model so it can
actively open a local file, a local folder (tree rooted there), or an
HTTP(S) page in the calling session's sidebar.

- host: AgentOpenRegistry + registerOpenTool (src/agent-opens.ts); the tool
  classifies http(s) URLs / stat-driven file/folder paths, refuses other
  schemes, resolves relative paths against the session cwd, and reports
  target-tab-disabled instead of silently no-oping. Gated like
  agentTerminalTools (register/drain on settings watch).
- delivery: /sidebar/ws/agent-opens push (consume-on-send queue, replay on
  attach); the tool result reports `delivered` so queued opens for
  non-active sessions are honest.
- client: Sidebar subscribes and routes pushes to editor (file), folder
  window (meta.dir, tree rooted at the folder — EditorHost), or browser
  (URL) tabs; settings row in 常规 + 20 locale dictionaries.
- tests: agent-opens registry/tool spec, smoke gating + WS route list,
  prefs/plugin-shape defaults, editor-host folder window, side-card rows.
- docs: design doc, AGENTS.md key lists + reference, README feature bullets.
2026-08-24 01:19:43 +08:00
HuanLinOTO 751fb954a2 fix: address Copilot review (9 comments)
#1 GitView: hide/disable 'open in editor' for external linked worktrees
   (path outside session workspace is rejected by the fence); add
   isWithinWorkspace client helper for the gate.
#2 DiffTab: untracked diff read resolves against diff.repoRoot (child-repo
   paths are repo-root-relative, not cwd-relative); fs.read route threads
   selectedRepoOf into resolveGitPath so the host resolves the right base.
#3 GitView: chooseRepo handler mirrors chooseWorktree — clears status/
   history/log and bumps generation before async refresh, preventing stale
   rows from acting on the newly selected repository.
#4 git.ts: repoRoots/repoRoot compare roots via pathIdentity() (platform-
   aware) instead of raw includes(), fixing win32 forward/backslash
   mismatch that silently fell back to roots[0].
#5 markdown-images: mask fenced code blocks and inline code spans before
   rewriting; only rewrite reference definitions whose label is actually
   referenced by image syntax (not plain links). +3 tests.
#6 package.json: downgrade unrun ^0.3.1 → ^0.2.39 (0.3.x requires Node
   ^22.13, incompatible with declared engines >=20).
#7 market-manifest.spec: shell:true on win32 so execFileSync resolves the
   pnpm .cmd shim.
#8 smoke.spec: build html-route URL via encodeHtmlUrl() (production
   encoder) so the Windows drive separator is correct.
#9 index.ts git.worktrees route: thread selectedRepoOf so a workspace
   container's selected child repo's linked worktrees appear.
2026-08-23 22:02:47 +08:00
HuanLinOTO b4118a4c77 integrate train/git: worktree RC8 forward-port + multi-repo selector
# Conflicts:
#	src/index.ts
2026-08-23 20:28:17 +08:00
HuanLinOTO d63de6f815 merge #326: support selecting child git repositories
# Conflicts:
#	src/client/GitView.tsx
#	src/client/state.ts
#	src/index.ts
2026-08-23 20:25:52 +08:00
HuanLinOTO 90584535d4 merge #345: 限制文件 API 访问会话工作区 2026-08-23 20:19:41 +08:00
HuanLinOTOandxxxg0001 5c88279d7a merge #285: forward-port linked worktrees to RC8
Co-authored-by: xxxg0001 <xxxg0001@users.noreply.github.com>
2026-08-23 20:17:14 +08:00
kelvinwww e9ce58afea fix: 限制文件 API 访问会话工作区 2026-08-23 10:28:05 +08:00
lx 57a4ad82e1 merge main into fix/multi-repository-git-selector 2026-08-23 02:50:34 +08:00
Menghuan1918 ddb66c9c08 fix(market): 移除公开版 cordis peer 并迁移类型基底到 @deepseek-ai/cordis
DSH 市场(dsh-community-market)预览硬拒依赖字段出现 'cordis'
(dependencies/peerDependencies/optionalDependencies 任一字段、
optional 均无效),导致 dshfind/1024Store 目录拿不到
repository_backlink 验证目标,点击安装报
"This catalog item has no verified install target"。

- package.json: 删除 cordis peer + peerDependenciesMeta;版本 0.15.2
- context-types.ts: Context 基底改 @deepseek-ai/cordis,结构服务面改
  交集(避免 DS host/client 对同一成员类型不同导致的 TS2717 merge
  冲突),仅保留 betterSidebar 的 module augmentation;删除
  declare module 'cordis'
- client/index.tsx: 4 处 effect 体失败分支改返回 no-op disposer
  (vendored cordis effect 契约拒绝 undefined 形状)
- 新增 tests/market-manifest.spec.ts:无 cordis 依赖字段、打包产物
  无生命周期脚本、bundle patch 安全路径、仓库身份、精确稳定版本
- 消费者检查扩展:@deepseek-ai/cordis 路径的 Context augmentation
  断言;文档示例同步迁移
2026-08-23 00:26:39 +08:00
Atlas Zeng 22a5d372be feat(editor): 文件树「在应用中打开」子菜单(外部编辑器/图钉固定/SSH 远端/自定义编辑器) (#334)
* feat(editor): file tree 'open with' submenu (external editors, pin, SSH-remote, custom editors)

The file tree right-click menu gains an 'Open with >' submenu with the
resource manager (reveal in file manager), VS Code, Cursor and Zed, plus
user-defined custom editors (name + URL template + VSCode-family flag).
Each submenu row carries a pushpin that promotes the target to a direct
top-level menu item.

A configurable SSH host (settings: Files card gear) switches VSCode-family
openers to <scheme>://vscode-remote/ssh-remote+<host>/<path> and hides
host-local targets (file manager / Zed / non-VSCode custom editors).

External opens run through the new fenced /sidebar/api/open.external route
which spawns the platform opener with an argv array (no shell); the client
never navigates raw scheme URLs itself. The settings seam's render panel now
appends after the declarative rows instead of replacing them.

* feat(editor): polish open-with menu UI — brand icons, pushpin, chevron, row alignment

- Submenu rows: the custom label is now a block-level flex row filling the
  primitives itemLabel, so the pushpin docks at the row's far right (matching
  the trailing check of every other menu); menu row icons normalized to 16px
  (DSH native menus use 16, ours were 14).
- The submenu parent row gains a right-aligned chevron (the primitives Menu
  renders no arrow for submenu parents).
- Icons via react-icons: codicons VscPin/VscPinned pushpins (replacing the
  hand-drawn pin), VscFile/VscFolder/VscFolderOpened tree glyphs (replacing
  the </> file mark), VscLinkExternal for the open-with parent and the
  browser tab's open-external button (globally), SiCursor/SiZedindustries
  brand silhouettes; the VS Code mark is inlined from simple-icons@11.0.0
  (CC0) as IconVscode16 since later simple-icons releases dropped it. All
  monochrome currentColor to follow the active skin.
- tsdown: pin react-icons si/vsc to their ESM entries via resolve.alias —
  the package's exports map lists require before import, so the shared
  conditionNames picked the unshakeable CJS entry and bloated the core
  bundle by ~6.4 MB; tree-shaken it now adds only ~12 KB.
2026-08-22 23:19:17 +08:00
lx 78de79c9d6 fix: support selecting child git repositories 2026-08-22 18:13:50 +08:00
多玩幻灵qwq 29b8615bff fix(terminal): keep pty alive across conversation switches via park frame (#323)
Switching from session A to B unmounted A's TerminalView, whose socket
close the host treated as a transient drop and started the 30s reconnect-
grace countdown — leaving the user in B for >30s killed A's shell, so
switching back respawned a fresh one instead of reattaching.

Add a third control frame {type:'park'} distinct from refresh/crash
drops: the client sends it on unmount when the tab is still open in its
session's state but the active session has moved on; the host marks the
pty as parked and skips the grace countdown. Switching back (open()
cancels the parked state) or explicitly closing the tab (close frame's
scheduleClose clears it) resume the normal lifecycle. Agent terminals
skip park — their lifetime is already indefinite on a bare drop.
2026-08-22 17:21:27 +08:00
NuperAkiandMenghuan1918 6cea4bdd6f fix(subagent): 子代理页实时预览改为批量接口,避免 O(N²) history 请求风暴 (#298)
* fix(subagent): 子代理页实时预览改为批量接口,避免 O(N²) history 请求风暴

## 背景

DSH Web UI 中,当主代理同时运行多个子代理时,`dsh-better-sidebar` 的「任务管理/子代理」页面会出现明显卡顿。经调查,卡顿根因不在页面渲染,而在实时预览的请求模式。

## 根因

旧实现中,每个 running 子代理卡片都独立轮询 `subagents.history`:

- N 个 running 子代理 → 每 3 秒产生 N 次 `subagents.history` 请求;
- 每次 `subagents.history` 都会在 host 侧触发一次完整的 `listChildren` 全量子代理目录枚举;
- 子代理数量越多,服务端重复扫描越严重,近似 O(N²) 放大;
- 请求超过 3 秒时,客户端还会 abort 旧请求并重发,进一步形成请求风暴。

## 改动内容

1. **新增 host 批量接口 `POST /sidebar/api/subagents.live`**
   - 请求 `{ rootSessionId }`;
   - 通过 `ctx.subagents.listDescendants(rootSessionId)` 一次枚举整棵子代理树;
   - 仅对 running 且非 Side Chat 的子代理,从 session events 尾部提取最新 `text/tool` 活动;
   - 返回 `{ live: Record<childId, { text?, tool? }> }`;
   - 缺 `subagents`/`agents` 服务或枚举失败时返回 503;单个子代理日志异常只跳过该子代理。

2. **客户端改为单轮询 + 单飞行请求**
   - 删除每个 running 子代理独立的 `subagents.history` 定时器;
   - 在 `SubagentView` 中合并为一个 `useSubagentLive` 轮询 hook;
   - 使用递归 `setTimeout` 和单在途请求,慢请求不再被 abort/重发;
   - 页面隐藏或 root 变化时停止轮询并中止在途请求。

3. **解析器优化**
   - `lastActivity` 改为接收原始 `SidebarSessionEvent[]`;
   - 从事件尾部向前扫描,找到最新 text 和 tool 后提前停止,避免复制/遍历完整历史。

4. **UI 保持不变**
   - 实时预览的展示逻辑、文案、截断规则均未改变。

## 测试

- 新增 `tests/subagent-live-route.spec.ts`:覆盖批量接口成功聚合、只返回 running、跳过 Side Chat、缺服务 503、枚举失败 503、`ctx.get` 回退、bad-request。
- 新增 `tests/subagent-live-polling.spec.tsx`:覆盖每轮只发 1 次 `subagents.live`、不再调用 `subagents.history`、单在途不并发。
- 更新 `tests/subagent-activity.spec.ts` 与 `tests/subagent-jobs-view.spec.tsx`。
- `pnpm typecheck` ✅
- `pnpm build` ✅
- 相关测试 29 个全部通过 ✅

> 本机完整 `pnpm test` 仍有与本改动无关的环境性失败(node-pty 超时、Windows CRLF 断言、pty-deps 路径断言),不影响本 PR 逻辑。

* fix(subagent): live 预览以 catalog activity 为准,删除 agents 第二事实源 (#298)

- host 路由 running 门改回 entry.activity === 'running'(与客户端卡片渲染门一致),
  恢复 PR 承诺的 UI 不变:turn 间 idle 的 continuable 子代理仍显示最后活动
- 删除 ctx.agents 依赖与 SidebarAgent.status 新增(agents 缺失时不再静默 200 空 map;
  503 契约收窄为只涉及 subagents,与文档/测试一致)
- service 解析统一为 ctx.get('subagents')(兄弟路由惯例;插件未 inject subagents,
  直接属性访问有抛错风险)
- buildSubagentLiveApi 签名改收完整 Context,删除 SidebarSubagentLiveContext 窄接口
- api.ts 复用 LastActivity,删除重复类型 SubagentLiveEntry
- 测试:route spec 删除 agents 相关用例;polling spec 新增页面隐藏停止轮询、
  root 变化清空 live 并重启轮询两个用例

* fix(subagent): live 预览恢复最近 12 条消息窗口,排除陈旧活动 (#298)

CR 反馈:新批量接口把完整 session event log 喂给 lastActivity,
- 长时间只输出文本、早期调用过工具的 running 子代理会持续显示陈旧 tool
- 无 tool 的长会话每次轮询反向扫描整个事件数组,重新引入与历史长度
  相关的 CPU 成本

修复(与旧 subagents.history({ maxMessages: 12 }) 窗口语义一致):
- lastActivity 增加可选 maxMessages 参数:反向扫描按表面消息
  (user/message + assistant/message)计数,窗口满后更老事件一律跳过
- 路由传 LIVE_WINDOW_MESSAGES = 12(导出常量)
- 回归测试:窗口外旧 tool 不返回;窗口内 tool 保留;非消息事件不消耗配额

---------

Co-authored-by: Menghuan1918 <menghuan2003@outlook.com>
2026-08-21 18:15:49 +08:00
Atlas Zeng 7f4f84c4cf feat(sidechat): 侧边对话——每对话一 Tab(Codex 对齐)+ 完整上下文继承 + 保存为新会话 (#286)
三轮实测驱动迭代:种子信封透传(surfaceOp 校验)、每线程一 Tab 交互重做完、四连修(fork 丢 this / 自绘 history+save 图标 / collectOwnEvents 大页回源修重开丢工具行 / descriptor 入种子修目录 corrupt 行 + 前缀过滤保拓扑零噪音)。739 单测 + 挂载冒烟 12/12 全绿。
2026-08-21 15:41:14 +08:00
dsh-better-sidebar-fix 6d6d582996 fix(git): show changes across linked worktrees
The Git panel only ran git status against the session's own worktree,
so when DSH (or any coding agent) created a linked worktree via
'git worktree add' and modified files there, the sidebar's Git panel
stayed empty - no changed files, no diff, nothing to commit.

Enumerate all linked checkouts of the session repository and let the
user select which one the panel targets. Auto-select the dirty linked
worktree when the primary checkout is clean and exactly one linked
worktree has changes (the common DSH agent pattern). Poll status every
2s while the panel is visible so model-authored edits appear without
a manual refresh.

Security: resolveWorktree validates the client-supplied worktree path
against 'git worktree list' before using it as a git -C cwd. Uses -z
porcelain framing, which disables core.quotePath C-style quoting so
non-ASCII checkout paths round-trip losslessly.
2026-08-21 00:45:24 +08:00
bb2456bea9 feat(explorer): upload files into the workspace from the file tree (#239)
* feat(explorer): upload files into the workspace from the file tree

- TreePanel header: upload-files / upload-folder buttons (folder picker keeps
  the tree via webkitdirectory), with a one-line progress/result hint
- FileTree: drop files/folders onto the tree body (workspace root) or onto a
  directory row (that directory); directory context menu gains 上传到此处
- api.ts: uploadFile posts base64 to /sidebar/upload (host writes it through
  fs + shell decode; route provided for now by the local demo plugin)
- upload.ts: bytes->base64 (global-btoa-independent), FileList/DataTransfer
  collection, sequential upload with per-file results; ~48MB per-file cap
- locales: zh/en strings; icons: IconUploadOutline16

* fix(explorer): reload tree after upload and auto-dismiss the status hint

The drop/context-menu upload path cleared the level cache but never
refetched (the refreshTick bump belongs to the button path), so the
visible tree emptied and only the persistent '已上传 N 个文件' hint
remained — the tree only came back after reopening the tab.

- FileTree.uploadInto now reloads root + expanded levels after clearing,
  matching the refresh-tick path
- success status auto-dismisses after 3.5s (failures persist until the
  next action) in both TreePanel and FileTree

* feat(host): add the /sidebar/upload route (raw bytes, workspace-safe)

The file tree's upload UI needs a host route; until now it pointed at a
locally-injected demo route. This is the real implementation:

- src/index.ts: register POST /sidebar/upload (exact route, same fence and
  session-cwd resolution as every /sidebar route). Query params sessionId,
  dir, relativePath; the raw request body is streamed into the workspace.
- src/fs-operations.ts: writeWorkspaceUpload streams chunks to a temp
  sibling and renames into place, so a failed or oversized upload never
  leaves a partial file. Upload dir must sit inside the session cwd, the
  relativePath is sanitized (no '.', '..', empty or absolute segments) and
  the target must stay inside both.
- src/config.ts: uploadLimit (default 128MiB; oversized -> 413 too-large,
  a new wire code in src/wire.ts).
- client: send the File straight into the POST body (no base64 inflation)
  and cap at the same 128MiB (upload.ts, api.ts).

Tests: tests/fs-operations.spec.ts covers write, nested dirs, traversal /
containment refusal, the size cap, and no-partial-file on failure.
(pnpm test: only pre-existing node-pty/git sandbox failures, identical on
clean main; typecheck and build pass.)

* fix(host): harden upload containment, temp-file atomicity and stream failure cleanup

- relativePath: absolute paths and empty segments are refused (400), not
  silently collapsed or re-anchored (the spec's 'reject' semantics)
- temp file: uniquely named per request (randomUUID) so concurrent uploads
  to the same target stay independent; crashed-process leftovers never
  block later uploads (no pid reuse)
- write stream: permanent 'error' listener + error checks after every await
  so a failing disk rejects the operation instead of crashing the host or
  hanging the drain wait; the temp file is removed after the stream fully
  closes (Windows file locks), never leaving a partial file
- docstring now states the containment is lexical (symlink trust model)

* fix(explorer): thread upload error codes and cancellation through the client

- api.uploadFile/fetchUpload accept an AbortSignal (long-op convention);
  an aborted request rejects with the DOMException as-is instead of being
  wrapped into a 'network' error
- UploadResult carries the wire error code; the client pre-check now emits
  the same 'too-large' code as the host, and summarizeResults localizes
  'too-large' instead of showing the raw English 413 message
- uploadToDir stops the queue on abort: completed files stay, in-flight
  request is cancelled, nothing after is started
- uploadTooLarge copy no longer hardcodes a byte number (the host limit is
  configurable); MAX_UPLOAD_BYTES is documented as a mirror, not authority
- relativePathOf rejects absolute paths (isAbsolutePath) instead of
  stripping leading slashes, matching the host's 400 semantics
- progress text shared via uploadHintText for the hint and the overlay;
  success-hint duration is the exported UPLOAD_HINT_MS

* feat(explorer): full-window upload progress with cancel and VSCode-style drop targets

- UploadOverlay: blurred scrim + card over the file window (Modal token
  recipe: bg-mask-1 + mask blur, bg-layer-2, border-inverted, shadow-lv3),
  file-level progress bar, cancel button and Esc; scrim clicks do not abort
- TreePanel owns the upload session: header pickers, tree drag-drop and the
  context-menu picker funnel through startUpload; one session at a time
  (buttons disabled while busy, drops guarded); on settle the tree refreshes
  and the status hint shows success (3.5s, transient), failure or cancel
  (error color, stays)
- FileTree: drops on directory rows target that directory, drops on FILE
  rows target their parent (VSCode semantics, no more accidental root
  uploads), root row and body drop into the workspace root; drag-over
  highlights the target row (explorerRowDropTarget) plus the body outline
  and a hint pill (uploadDropHint / uploadTo) — raw-color inline outline
  replaced with the repo's dashed-accent classes
- upload status hint uses editorSearchHint + editorError like the search
  status; duplicated upload flow removed from FileTree (single caller)

* test: register the upload route in the smoke inventory

The new /sidebar/upload route joined the fenced route list, so the mount
assertion must expect it (the PR's CI gate was red).

* chore: bump to 0.14.1

Rebased onto main and cut a release version for the upload feature:
package.json / dsh.plugin.json / SIDEBAR_SERVICE_VERSION stay in sync
(test-guarded), README changelogs get the v0.14.1 entry.

* feat(explorer): keep the drop hint above DSH's page overlay and suppress its intake over the tree

DSH's whole-page image-drop intake (ui-conversation InputBar) listens on
document in the bubble phase: any 'Files' drag entering the page shows its
full-screen overlay (z-1000) and a drop anywhere adds images to the chat.
Our tree handled dragover/drop but not dragenter, so the overlay flashed
over the file window and drops outside the tree rows could leak to the
chat.

- TreePanel root now swallows dragenter/dragover/drop (preventDefault +
  stopPropagation) before the events reach document: the file window is an
  upload target, not an image target. Row/body handlers still fire first
  (bubble order) for their own targets; the root is the backstop for the
  search row, gaps and the search-results view.
- The drop hint pill is portaled to document.body at z-index 1001 — above
  DSH's overlay (z-1000) — so when a drag enters from outside the tree and
  DSH's mask lingers until dragend, our 'Drop files/folders here' /
  'Upload into {dir}' hint stays readable on top (transient decoration,
  pointer-events none; the drop still lands on the tree).

* feat(explorer): zone-split the drop overlays and fix the mask flicker over the sidebar

Sidebar.tsx's panel host now swallows the whole file-drag quartet
(enter/over/leave/drop, gated on the 'Files' type): DSH's document-level
intake keeps an enter/leave depth counter, and the leaves that escaped
the tree unbalanced it — that was the native full-screen mask flickering
over the sidebar. The region is now a black hole to that listener, so the
conversation column keeps DSH's native overlay and image intake while the
sidebar presents its own upload surface (drops over it never add images
to the chat).

The tree's drop affordance is now a pointer-inert frame portaled to
document.body at z-1001, anchored to the explorer body's viewport rect:
it replaces DSH's mask over the sidebar instead of competing with it, and
the hint pill docks at the bottom edge so the rows — the actual drop
targets — stay visible and aimable instead of sitting behind a centered
pill. Inside the tree, enter/leave depth counting (InputBar's own
pattern) replaces the relatedTarget check, which is unreliable for drag
events and flickered the row highlight on icon/label transitions; the row
target is now set authoritatively by the continuously-firing dragover.
dragleave handlers on rows are gone.

Polish: capsule hint pill with the upload icon and a float shadow, the
progress overlay fades in (the repo's dsh-row-in motion), its title gains
the icon, and progress numbers render tabular. tests/file-tree-drop.spec
pins the depth-counter behavior (child transitions keep the zone) and the
parent-directory drop semantics.

* feat(explorer): dim the whole viewport behind the tree's drop zone

The bottom-docked pill sat below the fold of attention; the zone frame
alone also failed to teach the left/right split — a drag over the sidebar
gave no hint that the conversation column still accepts drops (into the
chat, natively). The zone frame now carries the full-viewport dim itself
(a 200vmax box-shadow spread in DSH's own mask-drop token): everything
outside the tree's rect fades, the tree stays clear and aimable, and a
muted invitation centered in the space left of the zone (skipped under
200px) names the chat as the image target. The pill moves to the TOP edge
of the zone, right under the search row — the first thing the eye meets.

* feat(explorer): colorful drop heroes for both zones

The overlay is this flow's one brand moment, so it gets the color the
rest of the UI never does: hand-drawn SVG illustrations in the palette of
DSH's own native drop art (#3964FE/#679EFE/#9CE5ED) so the two zones read
as one family. The upload zone docks a hero at its top edge — a tray
illustration over the hint pill, stacked so the rows below stay aimable;
the chat zone's invitation becomes a tilted photo-cards card over its
label.

* feat(explorer): traverse dropped folders instead of reading dataTransfer.files

A dropped OS folder never surfaces in dataTransfer.files (that flat list
carries files only), so folder drops were silent no-ops while the UI
advertised 'drop files/folders'. uploadItemsFromDrop now captures entry
handles via webkitGetAsEntry — synchronously, since the dataTransfer enters
protected mode once the drop dispatch ends, while the captured handles stay
readable — then drains each directory's readEntries batches recursively,
prefixing every descendant's relative path. Entry-derived paths go through
the same sanitizer as picker paths; an entry that fails to read is skipped
rather than failing the whole drop, and the flat file list remains the
fallback where the entry API is unavailable. The FileTree drop handlers
ride the resulting promise.

---------

Co-authored-by: dsh local <dsh@local>
Co-authored-by: Menghuan1918 <menghuan2003@outlook.com>
2026-08-20 18:28:57 +08:00
Atlas Zeng 6c891514b5 feat(injection): 统一面板宿主注入重构——含块层+几何写路径+chunk缓存+锚点弱化+桌面/移动适配 (#232)
* feat(injection): 统一面板宿主——固定含块层 + 挂载自检降级 + body 锚点守卫

- Sidebar 渲染根包 [data-dsh-panel-host](fixed inset-0 pointer-events:none)
- 面板/开关簇由 fixed 改 absolute(层内视口坐标等价),免疫套壳中间层 transform 劫持
- 挂载后一次性几何自检:页面级 transform 时降级 absolute + rAF 视口同步
- MutationObserver(body childList)守卫锚点被宿主清空时重挂

* perf(injection): 几何写路径统一为 writeGeometry + 面板 layout 隔离

- 拖拽帧与打开态共用单一推挤写入器(--dsh-sidebar-width/height)
- .panel/.bottomPanel contain: layout style——内部重排不外泄
- 拖拽期(body[data-dsh-sidebar-dragging])才挂 will-change: transform

* perf(chunks): HMR 激活保留未变 chunk 的已解析 exports + ETag HEAD 重验证

- 新增 revalidateChunksOnReactivate():激活时 HEAD 每个已加载 chunk 对比 ETag,
  未变保留内存缓存(跳过重注入/重执行),变更或不可达则 fail-open 重取
- 测试夹具与孤儿缓存条目不跨激活存活
- index.tsx 激活路径由 resetChunks() 改为 revalidateChunksOnReactivate()

* fix(layout): 推挤锚点改 data 属性 + #root calc 宽度防桌面壳溢出

- nth-child(2) → #root [data-dsh-frame] > [data-pane="conversation"](实证同元素)
- #root 宽度 calc(100% - var) 与 margin 同步过渡:web 视觉不变(实测 1140+300=1440 无溢出),
  Desktop 壳(#208)width:100% 的加性溢出根因消除

* feat(desktop): 桌面信号自动探测 + win32 顶栏 overlay 自动避让 + 键盘/safe-area 适配

- 新增 desktop-env.ts:解析 dsh-desktop-mode/platform URL 信号与 preload 标记
- win32 advanced 壳自动启用 titleBarCompat(32px overlay),手动 pref 仍可覆盖
- visualViewport 键盘 inset:底部面板/窄屏抽屉键盘弹起时上移
- 面板与开关簇 safe-area-inset 适配(env() 桌面端为 0)
- 单测 7 例(含 URLSearchParams 前导 ? 剥除修复)

* docs+e2e: 注入方案实施记录(路线B关闭、偏差记录、基线数据)+ 契约同步 + e2e 新断言

- 设计文档:路线 B 标记关闭、§8.1 改单 PR 步骤(含 commit 对照)、拖拽基线数据、实施偏差记录
- AGENTS.md §8:data-dsh-panel-host 含块层、推挤锚点契约(禁 nth-child)、桌面信号
- mount.e2e:data-dsh-panel-host 断言 + win32 advanced 桌面信号场景

* fix(cr): 响应 #232 CR 三条 findings

- P1 降级同步:按未修正几何判定(跟踪自身补偿量),祖先 transform 消失才退出循环——
  修正后矩形不再被误判为无需修正(自清除缺陷)
- P1 chunk 重验证成为 loadChunk 屏障:重验证挂起期间不提供缓存,杜绝 HMR 竞态陈旧 exports;
  测试夹具加载不再被误记为生产加载
- P2 键盘 inset:innerHeight - (vv.height + vv.offsetTop),并监听 scroll(offsetTop 变化)
- 新增屏障单测 1 例;设计文档偏差记录更新

* fix(chunks): 屏障 HEAD 加超时兜底 + resetChunks 清 revalidation

- revalidateChunksOnReactivate 的 HEAD 与 recordEtag 均带
  AbortSignal.timeout(5s):路由挂起时 fail-open(drop + 重取),
  屏障不再可能无限期阻塞所有懒加载 chunk(CR #232 P1 修复的健壮性缺口)
- resetChunks 同步清 revalidation:重验证挂起期间重置后,
  loadChunk 不再等待已丢弃状态的孤儿任务(finally 身份判断 no-op)
- 单测 +2:HEAD 挂起超时 fail-open(fake AbortSignal.timeout 触发中止)、
  resetChunks 清屏障后 loadChunk 立即放行

* fix(deps): align @deepseek-ai/* peers with DSH 0.1.0-rc.8

- peer/devDependencies: 13 个 @deepseek-ai/* 从 ^0.1.0-rc.7 升至
  ^0.1.0-rc.8;删除随 rc.8 消失的 dsh-client-web-react / dsh-client-schema-form
  (无 rc.8 发布、rc.8 依赖树与壳 bundle 零引用、插件 src 零引用);
  cordis peer/devDep 同步升 ^4.0.0-rc.8
- 版本 0.13.1 -> 0.14.0(package.json / dsh.plugin.json /
  SIDEBAR_SERVICE_VERSION 三处同步)
- CHUNK_EXTERNALS / CLIENT_EXTERNALS / CLIENT_REQUIRE_ALLOWED 三处镜像
  同步删除两个死包(primitives/slots 仍是 rc.8 壳静态模块表种子,保留)
- CI 挂载冒烟钉版 @deepseek-ai/dsh@0.1.0-rc.7 -> rc.8
- pnpm clean --lockfile 全量重解析:lockfile 内 @deepseek-ai/* rc.7 残留
  清零(传递链同升 rc.8,杜绝 #206 式混用树);pnpm-workspace.yaml
  minimumReleaseAgeExclude 按 #207 模式整体换为 rc.8 列表
- 文档同步:README 安装说明、AGENTS.md CI 钉版、外部插件指南与设计文档
  cordis 示例、issue 模板版本示例

* feat(prefs): editorExplorer 默认独立(关)——文件各自新开 tab

- SIDEBAR_PREFS_DEFAULTS 与 host PrefsSchema 的 editorExplorer 默认
  true -> false:新会话树点击/打开文件按 path 新开 tab,无路径窗口即
  纯资源管理器;合并模式保留为可选手动开启(持久化过的用户设置不受影响)
- 测试同步:prefs.spec 默认断言翻转、plugin-shape.spec 与 smoke.spec
  默认对象翻转;editor-host 合并模式用例显式开启 editorExplorer(split
  用例本已显式);side-card-section-rows 的 select 行用例固定选中 Merged
- e2e 重写:mount.e2e 文件打开流程从 in-place 断言改为独立模式
  (新开 hello.txt tab、Files home tab 保留),文件头注释同步
- 契约文档同步:AGENTS.md §3.4 editor 行默认描述翻转 + CI 冒烟描述、
  README/README_EN v0.13.0 特性描述 + v0.14.0 changelog

* fix(client): rc.8 模块系统经 ctx.modules 注入 + chunk 副本全局共享

rc.8 不再暴露 window.__DSH_MODULES__ 页面全局(改由 ctx.modules 服务
提供),chunk 外部依赖解析全面失效(terminal/editor/mermaid 全部
"client module system unavailable")。修复:

- src/client/index.tsx: inject 增加 'modules'(Cordis 服务访问守卫,
  此前直接读 ctx.modules 抛 "cannot get property modules without
  inject");apply() 激活时 setChunkModuleSystem(ctx.modules)
- package.json dsh.client.inject 增加 @deepseek-ai/dsh-client-modules
- chunk-loader: 注入写插件自有页面全局 __dshSidebarModuleSystem__——
  懒加载 chunk bundle 各自内联一份 loader(rc.7 靠官方全局回退工作,
  rc.8 全局消失后副本注入变量恒空);moduleSystem() 按
  注入变量 → 插件全局 → rc.7 全局回退解析
- context-types: Context 镜像补 modules 服务声明(client half)
- e2e: assertNoCrash 失败时输出错误条文本;独立模式断言修正
  (Files 数量按存在性、mermaid 流程先切回 Files、md 预览 locator 去掉
  重复的 [data-dsh-better-sidebar] 嵌套)

验证:typecheck / 639 测试全绿;本地 rc.8 CLI + Chromium 挂载冒烟 3/3
通过(drag-layout、mount 深扫含 editor/mermaid chunk、桌面信号)

* feat(settings): 终端 shell/shellArgs 可在设置页配置(bash 管理)

- SidebarPrefs 新增 terminalShell / terminalShellArgs(默认空 = 保持
  yaml config.shell → $SHELL / 登录 shell / powershell.exe 的既有解析)
- 终端卡齿轮二级页面新增两行 text 配置(与字体行同列)
- host 侧生效链路:UI 终端 WS 打开与 agent terminal_create 均读取
  设置文档覆盖(ptyManager.open / AgentPtyRegistry.create 增加可选
  shell 覆盖参数;shellOverridesOf 解析设置文档);设置页写入即时
  影响之后打开的终端(既有 pty 保持原 shell)
- locales zh/en 文案;parsePrefs 校验新字段;测试同步

* feat(settings): 侧边卡片设置页加 DSH-better-sidebar 版本徽标

- 设置页 intro 下新增身份徽标:名称 + v{service.version}(服务实例
  版本,与 package.json 由 tests/service.spec.ts 保持同步)
- token 驱动样式(--dsw-alias-*),名称/版本 pill 布局

* fix(drag): 快速拖动提交最后位置 + HMR 后中心列重定位兜底

快速释放(浏览器合并/丢失 pointermove 突发,超快速 flick 甚至可能
完全无 move)时的回退问题:
- pointerup 优先采用最后一次已知拖动值(rAF pending),而非 up 位置
  (事件合并时可能等于按下位置)
- pointercancel / lostpointercapture 中断时同样提交最后已知位置;
  无 move 时用中断事件自身的坐标计算(clamp 后提交)——只有真正
  未产生位移的拖动才回滚
- commitDrag 统一 flush + 同值提交 + 立即重测中心列(消除提交后
  React 用旧 centerRect 覆盖底栏 right 的中间帧)

HMR 重激活后底栏空白(输入框被推挤上移、强制刷新恢复):
- 根因:新组件实例重跑 locate 时壳的 centerCol 尚未就绪,而壳重建
  复用 #root 子节点(childList 观察器不触发)→ centerRect 卡 {0,0}
  → 底栏 visibility:hidden 永久空白
- 修复:额外观察 <html> 的 style 属性(layout push 写回 --dsh-sidebar-*
  时重定位重测);底栏打开时 effect 重跑 locate/measure 兜底
- 底栏未测量时由「不渲染」改为 visibility:hidden(渲染链不被几何
  门控,jsdom 测试不回归)

e2e 新增:极快拖动(单 move 立即 up)提交拖动位置、稳定后不回退

* feat(plugins): 添加插件目录支持搜索/分组/独立滚动(多插件场景)

- PluginListBody 顶部加实时搜索框(按名称/id/描述过滤,空结果提示)
- PluginEntry 增加可选 category 分组字段(i18n 友好),目录条目按组
  渲染(未分类条目保持平铺);数量徽标跟随过滤结果
- 条目列表 max-height + overflow-y 独立滚动,插件多时弹窗不再无限
  增长;渲染数量由搜索天然收敛
- locales zh/en(搜索占位、无匹配);测试文件固定英文环境
  (vitest 4.1.11+ jsdom 跟随系统 locale,本机 zh-CN 导致英文文案
  断言漂移)

* chore(deps): 适配 pnpm 11.8 supply-chain 校验(exclude 通配)

pnpm 11.8.0 起对 lockfile 既有条目强制 minimumReleaseAge 校验,
且精确 exclude(@scope/pkg@version)对带 peer 依赖后缀的解析条目
匹配失效(44 条误报违规,CI 全红)。@deepseek-ai/* scope 为官方
可信 scope,minimumReleaseAgeExclude 改为 '@deepseek-ai/*' 通配
(保留 rc.1 历史与 stable 条目);通配经实测通过全部 504 条校验。

* docs: v0.14.0 changelog 补全(自 v0.13.1 全部更改 + 特别声明 rc.8 适配)

新增条目:终端 shell/shellArgs 设置页配置、设置页版本徽标、
添加插件目录搜索/分组/滚动、rc.8 模块系统迁移、拖拽健壮性修复;
版本说明块醒目声明本版适配 DSH 0.1.0-rc.8(rc.7 及更早无法解析依赖)
2026-08-20 02:09:36 +08:00
CI Test 23c315ed82 fix(markdown): use manual preview refresh 2026-08-19 22:05:55 +08:00
CI Test c0e41f4b0d fix(markdown): refresh previews after external changes 2026-08-19 12:39:55 +08:00
NuperAki c973d6ec1b feat(terminal): 支持配置 shell 与 shellArgs,终端标题使用 shell 名 (#125)
- 支持通过 cordis.patch.yml 指定终端 shell
- 支持 shellArgs 带参启动终端 shell
- 终端标题改用 shell 名,内部使用 uuid 管理
2026-08-18 19:56:04 +08:00
Qiuner 4d0f99ced6 fix(html): declare UTF-8 preview encoding (#170) 2026-08-18 10:29:31 +08:00
Atlas Zeng 9ad0a49b8a feat(settings): mutual exclusion with the dsh-web-ui aionui-panel provider (v0.13.0) (#181)
Reads the aionui-panel settings namespace's rightPanel provider choice: while
it resolves to 'aionui-panel', the whole sidebar does not mount (host
settings.get reports externalDisable, the client mount gate skips mounting
and the link/open-path/turn-tail takeovers step aside). Live via the
settings/document-updated push (best effort; deployments without the
'remote' service fall back to boot-time evaluation). No new user-facing
setting — the aionui panel card remains the single source of truth.
2026-08-18 00:44:22 +08:00
Atlas Zeng 6ee1ba11d0 feat(editor): 文件窗口与资源管理器二合一 + 声明式设置 select 行 (#151)
* feat(editor): merged explorer mode — path input, docked file tree, global name search

New editorExplorer pref (default on, editor card gear): the editor tab gains
a path-input header and a toggleable right-docked tree panel (per-tab state
in tab.meta.treeOpen; file tabs default closed, the seeded path-less home
tab defaults open). Fresh sessions seed that empty editor tab instead of
the explorer tab when merged mode is on; the standalone explorer tab stays
in the + menu unchanged.

- Extract FileTree.tsx from ExplorerView (shared by both hosts)
- Host fs.search route: recursive case-insensitive name search rooted at
  the session cwd, budget-capped (200 matches / 100k visited), skips .git,
  never descends symlink dirs
- EditorHost reads the pref reactively (boolean snapshot) and keeps the
  pre-merge layout verbatim when off

* feat(settings): select row type for declarative settings; editorExplorer as iconed select

- SidebarSettingToggle gains type 'select' with options {value,title,desc,icon}
  and multi (default false; stores the picked values as an array in options
  order). Any option carrying an icon renders big-icon option cards in the
  dropdown and the icon in the closed anchor; icon-less rows are one-line text.
- SelectRow in SideCardSection rides the primitives Menu (portal); single-pick
  commits and closes, multi toggles and stays open. Wired for both host-prefs
  toggles and plugin-owned pluginToggles via the new onSelectValue seam.
- The editorExplorer merged-mode pref is now an iconed select (merged vs
  separate) instead of a switch; the stored value stays boolean.
- Advertise the capability as 'settingSelect' in SIDEBAR_FEATURES.

* feat(editor): unify explorer into the editor surface, resizable tree dock, hoisted toolbar

- One tree implementation, two presentations: the shared TreePanel (global
  name search + refresh + FileTree) docks as the editor tab's right panel
  and — in merged mode — fills the explorer tab's window; the pref-off
  explorer keeps its legacy header shell.
- The docked panel drag-resizes from its left edge (160-480px clamp),
  persisted as tab.meta.treeWidth alongside treeOpen.
- The text editor's preview/edit/save toolbar moves into the path-input
  header row: FileViewerProps gains internal toolbar fields ('host' mode
  skips the viewer's own row, reports EditorToolbarState and registers
  EditorToolbarControls); non-merged mode is byte-identical to before.

* feat(editor): single files window with in-place switching; explorer descriptor removed

- editor tab replaces explorer on the + menu (order 10, Files); persisted
  explorer tabs migrate to the editor home tab via sanitizeState
- editorExplorer now only controls open behavior: merged = in-place
  updateTab, independent = openSidebarFile; both modes share the merged
  chrome (path input + hoisted preview/edit/save toolbar + tree toggle)
- file tree context menu gains 'open in new tab' / 'open to the side'
  (mintTabId avoids the id safety net; insertLeafAt splits right)
- tree dock resize uses pointer capture (touch-action none, 6px handle)
- defaults: openByDefault off, sidebar width 35%
- file tree clips horizontal overflow (no sideways scroll)
- e2e: mount lane arms the editor-chunk wait before goto and clicks the
  tree row's left edge (hover @-reference button sits mid-row); drag lane
  expands the panel first (collapsed push writes 0px), waits for the
  slide-in to settle, and picks the leftmost col-resize strip; 'Files'
  title assertions require draggable to skip the bottom panel's welcome
  cards

* feat(editor): split mode turns the path-less window into the standalone explorer

In independent (split) mode a path-less files window renders ONLY the
tree panel (search + FileTree, full-window form) — no path input, no
hoisted toolbar, no tree toggle. File clicks open per-path editor tabs
(full chrome, tree docked closed by default) through openSidebarFile,
unchanged. Merged mode is untouched (chrome + docked tree, in-place
switching). TreePanel's previously reserved 'full' prop now has its
consumer.
2026-08-17 15:00:03 +08:00
Atlas Zeng 8d9ab1cd2f Merge pull request #147 from omdsh-dev/fix/node-pty-load-failure-repair
fix: node-pty 加载失败降级为终端修复提示,不再拖垮 server(#140)
2026-08-17 00:06:20 +08:00
chalei1122-cell 165ab03ea6 fix(sidebar): drag-release bounce, session-delete terminal cleanup, silent resize
- Sidebar.tsx: useLayoutEffect for the layout-push variables so cleanup+setup
  land in the same pre-paint frame (no full-width flash on drag release);
  applyDrag writes the width only while the panel is open (a closed right
  panel no longer squeezes the conversation when dragging the bottom height);
  flush the final drag frame before stopDragScheduling on all three handles;
  sync centerRect.right on width/corner release so the bottom panel does not
  jump.
- index.ts/pty-manager.ts: close a session's UI terminals immediately on
  session/disposed (they used to linger until reconnectGraceMs expired).
- sidebar.module.css: no highlight on the resize handles while dragging
  (local preference; the corner hover hint stays).
2026-08-16 17:43:29 +08:00
oy-g fd9544f16b feat: configurable terminal shell with Windows pwsh auto-probe (#95)
* feat: configurable terminal shell with Windows pwsh auto-probe

Windows previously hardcoded powershell.exe (5.1) in defaultShell(), ignoring PowerShell 7 installs. Resolve the shell in priority order: explicit shell config -> DSH_SIDEBAR_SHELL -> PATH/known-dir probe for pwsh.exe -> inbox 5.1 fallback. The same resolved shell feeds both the UI terminal tabs (PtyManager) and the model-facing terminal_* tools (AgentPtyRegistry).

defaultShell() now accepts injectable platform/env/exists options so the Windows chain is unit-testable on the ubuntu CI, which never executes win32 branches. The probe also checks ProgramW6432 for 32-bit Node, and machines without PowerShell 7 keep the 5.1 fallback.

Refs: #46

* fix: trim resolved SHELL and document ProgramW6432 in resolver contract

- POSIX SHELL is validated with trim() but was returned untrimmed; trailing whitespace could leak into the spawned executable path. Return the trimmed value like the explicit/config and Windows/env branches.
- ShellResolutionOptions.env comment now lists ProgramW6432, which the Windows probe prefers over ProgramFiles on 32-bit Node.
- Test pins the SHELL trimming behavior.
2026-08-16 16:34:21 +08:00
trinhthiminh650-tech 8014255541 feat(browser): loopback allowlist with same-origin for local dev servers
Add a side-card preference 'browserAllowedLoopback' (comma-separated
host or host:port entries) that lets the sidebar browser navigate to
explicitly trusted loopback addresses. Bare hosts (localhost,
127.0.0.1) match every port; host:port entries match exactly.

Allowlisted local addresses additionally get the 'allow-same-origin'
sandbox token in the iframe: local dev servers (Vite etc.) need a real
origin for their module/HMR/fetch pipeline and would otherwise render
blank in the opaque-origin sandbox. The page stays cross-origin to the
GUI and to every other site.

The server-side browser.probe route mirrors the same allowlist so
probed local servers are not rejected as 'local addresses are not
probed'.
2026-08-15 01:37:39 +08:00
trinhthiminh650-tech 6f092a6b1a fix(browser): probe embed signals via GET fallback when HEAD omits them
Some servers (e.g. aliyun consoles) answer HEAD without the
X-Frame-Options / Content-Security-Policy headers that only their
GET response carries. The embeddability probe then wrongly reported
the site as embeddable, leaving the plain iframe to surface the
browser's misleading "refused to connect" error (the site actually
forbids framing via frame-ancestors).

Retry once as GET when both embed signals are absent (the body is
discarded — only headers matter), mirroring the existing 405/501
HEAD fallback. The client then shows the friendly embed-refusal
panel with an "open in browser" action instead of the misleading
connection error.
2026-08-15 01:02:35 +08:00
Menghuan1918 c0aeefc936 fix: 回应审查保留意见 — 测试钉住跨站分支、删死接口、修正注释措辞
- tests/trusted-hosts.spec.ts:用例 4 改为受信 origin + cross-site marker
  以隔离拒绝分支(原同时踩 marker 与 origin 两条路径,删除 marker 检查
  测试依旧通过——变异验证现仅该用例失败);接受侧断言从 not.toBe(403)
  收紧为 toBe(200),请求体补 sessionId(session.cwd 的真实契约)
- src/context-types.ts:删除已无引用的 SidebarLoader/SidebarLoaderEntry
  死接口(loader 成员移除后残留)
- src/index.ts:修正 "same list / 无需重启" 过承诺注释,改为与 /api 网关
  同源的准确表述(boot 采样快照 + --trusted-host 权威,读 live 服务值)
2026-08-14 23:03:26 +08:00
Rookie_Zoe ea250c9e87 fix: /sidebar 信任栅栏改用 webRuntime.trustedHosts,修复远程访问全部 403
根因:trustedHostsOf 按 entry.options.name === 'connection' 查找 connection
行,但该行 name 是模块标识符(@deepseek-ai/dsh-client-connection,id 才是
connection),查找永远落空 → trustedHosts 为空 → 栅栏只放行回环 Host → 通过
域名或局域网 IP 访问时 /sidebar/bundle chunk 加载失败、资源管理器/代码管理器
全部 403 forbidden(/api 不受影响,它读的是 apply 时已求值的配置)。

修复(按 review 建议):host half 直接注入 webRuntime 服务,栅栏每请求实时
读取 ctx.webRuntime.trustedHosts —— 与 /api 网关同一信任列表(启动时采样的
LAN IP 字面量 + --trusted-host 权威),不再依赖 Loader entry / fiber /
!!js 节点求值;自定义信任域名由 dsh web --trusted-host 提供。

新增 tests/trusted-hosts.spec.ts 回归:域名/局域网放行、空列表仅回环、
跨站标记拒绝、每请求实时读取;plugin-shape 断言同步更新。
2026-08-14 23:03:26 +08:00
Menghuan1918 82a69546d2 feat: 公共 API 基座化(v0.12.0,纯增量)— 类型/能力/设置 seam 全面补齐
- 类型生态:service 模块 re-export SidebarTab/SidebarState/SidebarStore/
  SidebarSnapshot/SidebarDiffRef/TabType/SessionScope/SidebarPrefs 等公共
  状态词汇(此前 TS2459 无法命名);context-types 去 Node 依赖(结构镜像
  SidebarHttpRequest/Response/UpgradeSocket/Head),client 可达声明图零
  node:* 引用,外部消费者无 @types/node + skipLibCheck:false 可编译
  (scripts/check-consumer-types.sh 守护)
- 服务能力:version/features 版本与能力探测(单调只增)、getSnapshot/
  subscribeState 状态订阅、updateTab(title/path/meta)、activateTab、
  openFile(路径派生 id 与内置拦截一致)、openTab(seed, scope?) 定向到
  指定 session(store.reduceFor 不扰动 UI)
- TabDescriptor:badge 角标(99+ 封顶,抛错吞掉)、onOpen/onActivate/
  onClose 生命周期回调(service 路径派发,dedupe 聚焦触发 onActivate
  而非 onOpen,抛错不打断流程);SidebarTab.meta JSON 可序列化跨刷新
  持久化(sanitize 宽松保留)
- 设置 seam 开放:PrefsSchema 增 pluginSettings 开放嵌套 map(z.dict),
  settings.pluginToggles(插件自有行,同 switch/text/number 控件)与
  settings.render(自定义面板,含 store/service/prefs/updatePluginSetting/
  close props);viewer 卡片 v0.12.0 起也有齿轮设置弹窗
- FileViewerDescriptor.load 增第三参 AbortSignal(EditorHost 生命周期中止)
- UI 接线:tab 激活/关闭改走 service(跨树激活修复 + 回调触发)、+ 菜单
  传 scope、TabBar badge pill 渲染;active-session scope 走原 reduce 路径
  保证 notify
- 测试 +23:生命周期/定向打开/updateTab/activateTab/openFile/meta 持久化/
  reduceFor/能力常量(与 package.json 锁步)、consumer-types.ts 编译门、
  api-surface.spec.ts(client 图零 node 依赖守护);既有 430 测试全绿
- 文档:AGENTS.md 修 available 三参示例并补全部新 API;external-plugin-
  guide.md 刷新至 v0.12.0(7 tab + 9 viewer、设置 seam 开放、类型导入
  路径建议);README×2 服务化小节同步

未发版 npm(用户决定);分支 feat/plugin-api-v012。
2026-08-14 20:44:43 +08:00
Menghuan1918 1bfabbbfc2 feat: 适配 DSH 命名契约重命名(jobs/terminal/webServer)并处理 Session log 按钮位置
- tasks→jobs:ctx.jobs / job_output / job_id / session/jobs / jobsBySession / Job* 词汇
- httpServer→webServer(inject + augmentation + register/registerUpgrade)
- autoOpenTasks→autoOpenJobs 偏好键
- 右上角开关簇回到角落,注入 CSS 挤开 header 的「Session log」胶囊
2026-08-13 09:06:55 +08:00
Menghuan1918 b90251cce5 fix: 遵守仓库硬约束(禁止修改 DSH 源码)——tasks.output 改为会话事件回放
- 回退对 harness peek 缝隙的依赖:输出查看不再调用注册表,改为从属主
  会话事件日志回放 task_output 的 tool/call+tool/result 对(模型已读内容)
- read:false 时面板提示「模型尚未读取」;'(no new output)' 增量与错误
  结果过滤;截断上限不变;kill 走注册表原生 API(缺席时 503)
- AGENTS.md 新增 §0 硬约束:禁止修改 DSH 源码、挂载只走 cordis.patch.yml
  + profile 机制;设计文档同步实施偏差
- 测试:tasks-routes 重写为回放语义(拼接/过滤/read 标志/截断/503),
  视图新增 read:false 提示用例;全量 369 通过
2026-08-12 23:04:30 +08:00