Commit Graph
380 Commits
Author SHA1 Message Date
NolanHo 76fa7df264 fix: resolve the account login shell and spawn terminals as login shells
Terminal tabs resolved the shell from the dsh process environment only,
falling back to a hardcoded /bin/bash — service-managed launches without
SHELL therefore ignored the account's login shell (e.g. zsh). They also
spawned with an empty argv, so even a resolved zsh ran as a non-login
shell and skipped ~/.zprofile.

defaultShell() now falls back to the account's login shell (passwd) after
$SHELL, and both registries spawn POSIX shells with -l so the tab behaves
like a terminal emulator (loads ~/.profile / ~/.zprofile). Windows keeps
powershell.exe with no login flag.

Fixes omdsh-dev/DSH-better-sidebar#68
2026-08-14 10:15:34 -07: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 4e275aa321 chore: v0.12.1(0.12.0 被 npm 判定已发布过无法复用,正式版改用 0.12.1;README 版本号同步) 2026-08-15 00:36:09 +08:00
Menghuan1918 ef8a707da8 chore: v0.12.0(正式版) 2026-08-15 00:33:26 +08:00
Menghuan1918 83a658efd4 chore: v0.12.0-beta.1(版本三处同步,api-surface 测试放行预发布版本号) 2026-08-15 00:28:59 +08:00
Menghuan1918 7d524724ae feat: 推荐插件目录收录 dsh-sentinel(Tab 注册)
- plugins-tabs.ts 收录 dsh-sentinel(条件驱动的 agent 唤醒系统,注册 dsh-sentinel:watches Tab)
- install 拼接官方一键命令:cd ~/.dsh && dsh plugin --profile web add "github:fuhefei/dsh-sentinel#v0.7.0"
- 新增 pluginSentinelDesc 文案(zh/en);测试同步(tab 目录不再为空态)
2026-08-14 23:04:25 +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
Atlas Zeng 001e86b1e1 feat: 标签页栏支持鼠标滚轮横向滚动 (#61)
普通鼠标滚轮只产生垂直 delta,overflow-x 的横向滚动容器不会消费它,
事件会冒泡导致页面滚动。给 TabBar 的滚动容器绑定原生非 passive wheel
监听(React 的 onWheel 在根节点是 passive,preventDefault 无效),
在标签条溢出时把 deltaY(含 deltaX、deltaMode 归一化)转为 scrollLeft:

- 未溢出时完全不拦截,页面正常滚动(空 pane 亦然)
- 按住 Shift/Ctrl/Cmd/Alt 时放行,保留浏览器原生横滚/缩放
- 每个 pane 的 TabBar 独立监听,只滚动悬停的标签条

新增 tests/tab-bar-wheel.spec.tsx(jsdom 挂载,7 个用例)。
2026-08-14 22:59:19 +08:00
Menghuan1918 58999c2f3b fix: 回应 Codex review——复制反馈仅在写入成功时显示;插件名改为 window.open 按钮
- copy 现在 await writeClipboard 的布尔结果,剪贴板被拒时不再显示「已复制」(避免误导用户粘贴从未写入的命令)
- 插件名由 <a target=_blank> 改为 <button>,与「跳转」按钮同走 window.open——锚点会被 document capture 的链接接管拦截(忽略 target=_blank)开进侧边栏浏览器,按钮形态绕过
- 测试:新增剪贴板拒绝用例 + 名字/跳转双按钮 window.open 断言
2026-08-14 22:53:14 +08:00
Menghuan1918 cbc28473a0 feat: 设置页「添加插件」推荐目录 + 复制安装命令;移除内置 Office 预览
- 设置页侧边栏内容/文件预览两个网格末尾各增虚线卡片,弹出对应 kind 的插件弹窗
- 弹窗:GitHub topic 按钮(新标签页)+ 推荐插件目录(plugins-tabs.ts / plugins-viewers.ts)
- 每条目「跳转」(window.open 新标签页直达仓库)与「复制」(writeClipboard + 已复制反馈)
- 不做终端预填流:不关闭弹窗、不打开终端、无失败路径(install/installNotice/installError* 文案删除)
- 内置 Office 预览(docx/xlsx/pptx)整体移除,chunk 白名单 5→2(terminal/editor)
- 文案「内置插件」→「推荐插件」;AGENTS.md/README/设计文档同步
2026-08-14 22:30:34 +08:00
Menghuan1918 0995a58454 fix: 回应独立 CR 的 P2/P3 意见(0×P1)
- P2-1 onOpen 收到真正落地的 tab:url 新建场景从 landed 态按 id 反查
  (补丁后对象),回调不再拿到未打 url 的 tab
- P2-2 插件设置行显式取值源:FeatureSettingsRows 新增 valueSource prop,
  插件行读自己的 pluginSettings blob——插件键与宿主 pref 同名时不再
  静默读宿主值(原投影合并有读宿主写插件的读写不对称);prop 命名避开
  Object.prototype.valueOf 冲突
- P2-3 consumer 类型检查接入工程:package.json 新增 check:consumer-types
  脚本 + CI 在 build 后执行;脚本补 lib/types 存在性前置检查(防假绿)
- P3-1 url seed 只在新建时应用:dedupe 聚焦的 tab 不再被覆写 path
- P3-2 closeTab/activateTab 未知 id 严格 no-op:store.reduce 对同引用
  结果短路(不 notify、不重写 localStorage),根因修复 + 回归测试
- P3-3 closeTab/activateTab 增加可选 scope(cwd 随回调传递),Sidebar
  双树激活/关闭路径传入 { sessionId, cwd }
- P3-4 定向打开不再自动展开不可见 session 的面板
- P3-5 Sidebar 终端 pty 释放查找覆盖双树(底部面板终端不再漏掉 HTTP
  兜底)
- P3-6/P3-7 与 Nits:AGENTS.md/指南文档化(dedupeKey 纯函数、meta 用
  null 清除、回调 scope 的 cwd 语义);consumer fixture 补常量导出覆盖

测试 +6(url 落地保真 / url 不覆写聚焦 / 未知 id 无 notify / 定向打开
不展开 / scope 随回调 / valueSource 键隔离),464 全绿;typecheck、
build、consumer 校验全部通过。
2026-08-14 21:28:41 +08:00
Menghuan1918 319d34ed09 fix: 回应 Codex 审查的 4 条意见(2×P1 + 2×P2)
- P1 state.ts reduceFor:恢复共享 uid 计数器到调用前水平(目标 session 的
  max 可能低于激活 session 已用 id,否则下次 mint 撞 id 损坏布局);
  回归测试实证:无修复时 split 产生重复 pane id
- P1 service.ts 生命周期分类:dedupe 聚焦判定改为基于输入态的存在性
  (dedupeKey 匹配或 id 匹配都算聚焦)——按 path 去重 + 新 id 打开时
  不再对从未插入的幽灵 tab 触发 onOpen(资源泄漏源)
- P2 SideCardSection:乐观 prefs ref(optimisticRef)驱动嵌套 map 合并
  (tabsEnabled/viewersEnabled/pluginSettings),同 tick 多次写入不再
  互相丢弃;mergePluginSetting 抽纯函数 + 单测
- P2 state.ts schedulePersist:每 session 独立防抖定时器(persistTimers
  Map),定向打开不再取消其他 session 的待写;单测验证双写都落盘
2026-08-14 21:13:53 +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
Atlas Zeng 61e31321d6 Merge pull request #48 from omdsh-dev/fix/issue-31-sidebar-crash
fix: sidebar crash #31 — layout-push leak + per-tab error containment
2026-08-14 20:02:16 +08:00
Menghuan1918 9aeb0464ce fix: sidebar crash #31 — layout-push CSS-variable leak + per-tab error containment
Two failure modes of a sidebar render crash, fixed together:

1. Layout-push leak (root cause): the layout-push effect wrote
   --dsh-sidebar-width/--dsh-sidebar-height on document.documentElement
   without a cleanup, so ANY unmount of the Sidebar (error-boundary swap,
   plugin disable, HMR) left the variables behind and layout.css kept
   squeezing #root with a stale margin — "the sidebar cannot be hidden"
   until a full reload. The effect now removes both properties on cleanup
   (the CSS fallback 0px restores the layout).

2. Crash containment (UX): the root error boundary previously replaced the
   WHOLE sidebar — toggle cluster included — with an error strip on any tab
   crash. A new shared RenderBoundary (src/client/RenderBoundary.tsx) now
   scopes containment per tab: a crashing viewer/editor shows a dismissible
   strip inside its own pane while the toggle cluster, the other tabs, and
   the panel stay alive. The descriptor is rendered as a real element
   (createElement) so even top-level component throws land in the boundary,
   not in TabContent's own render. The root boundary remains as the last
   resort for Workbench/shell errors.

Tests (tests/sidebar-crash.spec.tsx, jsdom + real Sidebar/store harness):
- unmount clears both CSS variables (red before the fix: 307px leaked)
- a crashing tab is contained: strip in-pane, collapse button alive,
  layout push still live
- the retry button recovers a tab whose crash has since been fixed

Full suite: 425 tests pass; tsc --noEmit clean.
2026-08-14 18:29:31 +08:00
马泽阳 6b4fed1010 fix(html): Windows 盘符路径解码去掉前导斜杠,修复 html 预览被 isWithin 拒绝
decodeHtmlUrl 对 'D:\' 盘符路径重拼为 '/D:/...',node path.resolve() 在 Windows
上会变成 'D:\D:\...'(盘符重复),html 路由的 isWithin(cwd) 围栏因此拒绝所有盘
符路径——打开 html 报 "html path outside the session working directory"。

修复:首段匹配 /^[A-Za-z]:$/(盘符段)时改回无前导斜杠的 'D:/...' 形式,
requireAbsolute 可原样解析回盘符路径;POSIX 路径行为不变。

测试:html-route.spec 更新 Windows round-trip 断言,新增小写盘符用例与
resolve() 回归护栏(win32 平台守卫)。
2026-08-14 16:45:44 +08:00
fsvov 05f67cd937 fix(client): share the side-card width across sessions (last drag wins)
The panel width lived in the per-session state (dsh-sidebar:v1:<id>), so a
drag in one conversation never carried to another: switching conversations
snapped the panel back to that session's own width. The width is a layout
preference, not session content, so it now persists under a cross-session
key (dsh-sidebar:v1:width):

- every persist writes the current width to the global key;
- loadState prefers the global width over a session's persisted one;
- a cache-hit session switch adopts the global width.

Backward compatible: with no global key present (first run / older data)
the behavior is unchanged. Adds a regression test covering drag-in-A ->
switch-to-B and drag-in-B -> switch-back-to-A.
2026-08-14 16:04:05 +08:00
Menghuan1918 37d91befdb fix: 终端延迟到容器有尺寸后再 open(零尺寸初始化崩溃,WKWebView 底部面板展开后终端空白,#25) 2026-08-14 14:53:03 +08:00
Menghuan1918 d83490c93e feat: 终端二级设置支持自定义字体(字体族 + 9–32px 字号,实时生效)
- SidebarPrefs 新增 terminalFontFamily(空 = 跟随主题等宽字体)/ terminalFontSize(9–32,默认 13),PrefsSchema 与 parsePrefs 同步校验
- 声明式设置 API 扩展:SidebarSettingToggle 支持 type: 'switch' | 'text' | 'number'(缺省 switch,向后兼容)+ min/max/placeholder/unit
- 终端卡片齿轮弹窗新增「终端字体」「终端字号」两行:blur/Enter 提交、number 钳制、提交失败回滚草稿
- TerminalView 经 store 订阅实时应用字体(options + re-fit + resize),懒加载 chunk 无新增依赖
- 新增 resolveTerminalFont 纯函数(回退链:用户字体 → 主题 token → 内置等宽栈)并单测
- 版本 0.11.0,README/AGENTS.md/设计文档同步
2026-08-14 14:36:49 +08:00
Menghuan1918 047f48337d feat: 侧边卡片设置页视觉重构——分组容器卡片、计数徽标、自定义开关、加宽设置弹窗(描述 + 完成按钮),卡片齿轮与勾选徽标纵向对齐同尺寸 2026-08-14 14:07:52 +08:00
Logan 1f9af78c3c fix: enable selection-to-conversation popup in the markdown editor
The "add to conversation" selection popup was registered only for the
catch-all code viewer (viewerId === 'code'), so selecting text in the
CodeMirror editor of a markdown file produced no popup; insertion only
worked in markdown preview mode via the separate DOM-selection handler.

Extend the CodeMirror selection popup to the markdown editor
(viewerId === 'markdown') so line-exact selections can be injected into
the composer draft (\`\`\`rel/path:start-end\`\`\` payload), matching
preview-mode behavior and the code viewer.
2026-08-14 12:59:17 +08:00
Menghuan1918 0f9af851ae fix: 聊天产出文件行拦截改用 slots.inject 延迟注册(issue #15)
conversation.chat.turnTail 是宿主 ui-conversation 在 conversation.chat.node
children 表中声明的子插槽,直接 slots.register 会早于声明而抛
"not declared (a parent entry's children table must declare it)",
导致拦截注册失败回退默认 deliverables 行为并弹诊断条。

改用 ctx.slots.inject 等待声明落账后再注册(与官方
dsh-client-ui-deliverables 同款写法):已声明则同步注册,未声明则
在声明提交时注册,声明撤销自动卸载、重声明重新注册,fiber 卸载
幂等清理。editor tab 禁用门控与 openInSidebar 座位不变。

新增 tests/turn-tail-intercept.spec.ts 覆盖已声明/待声明/提前销毁
三种注册时机、editor 禁用回退与 openInSidebar 座位接线。
2026-08-14 00:41:08 +08:00
Menghuan1918 0a07cff6a3 fix: 适配 DSH 0.1.x 的 [data-slot] DOM 结构,恢复 Session log 挤压与布局
DSH 0.1.x 把 slot 宿主包进 [data-slot] 容器:
#root > div[data-slot=root] > div.frame > div.centerCol
  > div[data-slot=conversation] > div.root[data-phase]
    > div[data-slot=conversation.session.header] > header

旧选择器 #root > div > div:nth-child(2) 选中的是 data-slot=root 包装
层的第 2 子元素(不存在,其唯一子元素是 frame),导致:
- 折叠态 header 右 padding 挤压失效(Session log 胶囊被按钮簇盖住)
- 底部面板纵向挤压 / 拖动过渡 / 减少动效全部失效
- Sidebar.tsx 中间列定位器(#root > div 的 children[1])失效,
  底部面板宽度测量回退为 0

修复:全部改用 [data-slot] 语义锚点——
- 中间列:#root > div[data-slot=root] > div > div:nth-child(2)
- header:body[data-dsh-sidebar-collapsed]
  [data-slot=conversation.session.header] > header
- Sidebar.tsx 定位器:#root [data-slot=conversation] 的 parentElement
  (= centerCol,探针实测确认)

v0.10.1
2026-08-13 22:53:31 +08:00
Menghuan1918 64c6aa477e chore: v0.10.0 npm 发布准备
- @deepseek-ai/* 依赖换为实际发布版本(dsh-* ^0.1.0-rc.6、cordis ^4.0.1),
  补齐 dsh-client-locale peer/dev 声明(对齐 dsh.client.inject)
- pnpm-lock.yaml 重新生成(移除旧 0.0.1-rc.1 图;锁文件不再引用未公开的 dsh-type-meta)
- pnpm-workspace.yaml 补 minimumReleaseAgeExclude 新版本条目
- src/context-types.ts 增加 @deepseek-ai/cordis 双 augmentation
- package.json 发布脚手架:publishConfig.access、prepublishOnly、files 含 README_EN/LICENSE
- README 版本注释同步(^0.1.0-rc.6 / dsh@0.1.0-rc.6)
2026-08-13 22:25:20 +08:00
Menghuan1918 b31a9a601f fix: md 预览代码块复制按钮跟随 DSH i18n(MarkdownText 补传 codeLabels) 2026-08-13 10:26:57 +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 ce052faee8 feat: 预览区划选文字浮窗「添加到对话」(md 预览 + 兜底 code 查看器) 2026-08-13 01:39:59 +08:00
Menghuan1918 a2bb04bce0 fix: 底栏宽度直测中间列,修复启动竞态下底栏跑到最左边
- 底栏 left/right 由「appSidebarWidth + detailsWidth + 面板宽反推」改为直接测量 AppFrame 中间列的 bounding rect(唯一真源,展开/收起动画期间跟随 margin-right 挤压)
- 拖动期间暂停测量(avoid per-frame 重渲染卡顿),拖动结束显式重测
- #root MutationObserver 自愈启动竞态:旧实现一次查询失败后永远卡在零尺寸回退(表现为 1px 细条贴在视口最左)
2026-08-13 01:17:01 +08:00
Menghuan1918 9c5293c94b feat: 界面文案跟随 DSH 的 i18n(ctx.locale 语言偏好、实时切换、词典入注册表) 2026-08-13 00:43:28 +08:00
Menghuan1918 6e4474cd2e fix: 任务输出回放合并活事件镜像,修复重启后 store 会话滞后导致新读取不可见
- host 半挂 ctx.on('session/event')(api-proxy 同款 API),镜像 task_output
  的 tool/call + tool/result 活事件(每会话 200 条有界环,无关结果不缓存)
- tasks.output 回放合并「store 事件日志 + 活事件镜像」,按 seq 去重排序配对
- 零 DSH 写入:会话 store 在宿主重启后可能冻结在再水合边界(持久化文件
  停在重启时刻),活事件只走 append 推送链;镜像绕开该滞后
- 测试:新增镜像用例(live 事件、跨会话隔离、种子+镜像合并不双计),
  全量 373 通过
2026-08-12 23:04:30 +08:00
Menghuan1918 88f116338a feat: 任务管理页二级开关「有新后台任务时自动展开」(autoOpenTasks)
- 新 pref autoOpenTasks(默认开):当前会话出现新后台任务(任意新任务 id,
  非仅首个)时自动展开侧边栏并打开任务管理页,与 autoOpenSubagent 并列
- 注册为 subagent tab 的声明式二级开关(设置页齿轮弹窗,key 在 PrefsSchema)
- Sidebar 新增独立基线 effect(镜像子代理自动展开;刷新不触发、开关关闭/类型
  禁用不触发);detectNewTask 纯函数(settling/同 id/他会话不触发)
- 测试:detectNewTask 用例、prefs 解析断言、builtins 开关清单、schema 默认值
  断言;全量 371 通过
2026-08-12 23:04:30 +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
Menghuan1918 8c521be26e chore: v0.8.0 — 任务管理页(子代理拓扑 + 后台命令底部输出 Dock、两击确认终止)
- tab 更名为任务管理:同页显示当前树全部后台命令(bash/pwsh 徽标、状态、时长、属主、退出码)
- 点击任务行在侧边栏底部共享 Dock 显示输出(sticky 终端式、自动跟随、单一轮询实例)
- 运行中任务两击确认强制终止;大量任务时列表保持紧凑、排序变动不跳动
- 新增 /sidebar/api tasks.output(非消费 peek,不偷模型 task_output 字节)与 tasks.kill 路由
- 修复任务镜像清空时的 React #300 hook 顺序崩溃(useCallback 移到提前 return 之前)+ 回归测试
2026-08-12 23:04:30 +08:00
Menghuan1918 cb9a7cf751 feat: 内容型打开自动展开承载面板(宽视口)
宽视口下 openTab 带 path/url seed 时,承载落点 pane 的面板折叠则自动展开:
落点在底部树展开底部面板,否则展开右侧面板;去重聚焦既有 tab 同样展开
(打开必须落在视野内)。窄视口抽屉逻辑不变;类型型打开(+ 菜单、agent
终端自动补 tab)不展开。同步 AGENTS.md / README 文档与测试。
2026-08-12 18:39:17 +08:00
Menghuan1918 ffc2270d77 fix: 移动端合并改为底部标签直接并入右侧栏(迁移式,删除双工作台堆叠方案)+ 窄屏断点收紧至 768px(不对齐宿主 1024) 2026-08-12 18:27:28 +08:00
Menghuan1918 b391ebffed feat: 移动端侧边栏体验 — 窄视口(<1024px)双工作台合并为单面板抽屉(可拖分隔条、高度与底部面板互通)、不提供底部面板按钮、文件/外链打开自动展开、新会话默认收起 2026-08-12 18:20:34 +08:00
Menghuan1918 bab659f8bd feat: 客户端懒加载分块 — 重依赖(Univer/xterm/CodeMirror/Office)拆独立 bundle 按需下发(/sidebar/bundle 路由 + __dshChunks__ 注册表),核心启动仅 ~325KB 2026-08-12 18:19:44 +08:00
Menghuan1918 c7af7dccdb feat: 侧边栏双面板 — 右上角按钮簇、底部面板(首展自动终端/关闭按钮)、跨面板拖 Tab、拖动性能优化
- 右上角持久按钮簇:横向排列、独立 glyph(底栏/侧拉)、始终钉在视口右上角(侧边栏展开时位于其标签条右端,真实挤占 Tab 空间)
- 底部面板:独立工作台,只挤占中间 Agent 输出区(不覆盖左右侧边栏),右缘分割线、关闭 x、首展自动开终端(二级设置开关,默认开)
- 两面板拐角双向拖动调节尺寸;拖动改为 rAF 节流直写 DOM,消除卡顿;面板尺寸互钳制防丢失
- 跨面板拖 Tab(合并/拆分落点),moveTab/moveTabToEdge 支持跨树移动
- 欢迎卡片改响应式网格;布局/几何全部按会话持久化,旧状态平滑迁移
2026-08-12 16:43:24 +08:00
Menghuan1918 31b78f7941 fix: IME 组合期按键守卫 — document capture 拦截,防内联第三方组件劫持方向键(#562)
Univer InputNumber 的 keydown 对 ArrowUp/Down 直接 preventDefault 且无
isComposing 守卫,中文输入法组合期候选词方向键被吞(#562 回归,同类 #535)。
新增 src/client/ime-guard.ts:document capture 阶段对组合期按键(isComposing
或 keyCode 229)stopPropagation,运行于 React 委托与任何原生监听之前,覆盖
内联依赖的全部劫持路径;apply 时经 ctx.effect 注册(HMR-safe)。

- tests/ime-guard.spec.ts:12 个用例,覆盖纯决策、原生 document/input 监听、
  React 合成事件路径与 disposer 恢复(新增 jsdom devDependency)
- 全量 280 测试通过,typecheck 干净
2026-08-12 01:16:20 +08:00
Menghuan1918 302c20109f feat: 内置 HTML 预览器 + 沙箱浏览器 tab(v0.5.0)
- html viewer:.html/.htm 沙箱 iframe 预览(相对资源经 /sidebar/html 路径编码路由加载,CSP sandbox + 信任围栏 + cwd 边界),预览/编辑切换
- browser tab:+ 菜单多开(browser:<n>),地址栏后退/前进/刷新/在浏览器中打开(15px 图标),URL 与标题按会话持久化(patchTab + nextBrowser)
- 安全:iframe 一律不透明源沙箱(无 allow-same-origin/top-navigation、no-referrer、权限策略全禁、禁 srcdoc);地址栏仅 http(s)、拒绝 localhost 等本机地址;界面实时沙箱状态(关闭红色警示)+ 临时解锁/恢复
- 设置:htmlViewerNoSandbox / htmlViewerDefaultUnsafe / browserNoSandbox / browserInterceptLinks 四个声明式二级设置(默认安全)
- 外链拦截:聊天/界面 http(s) 外链默认在侧边栏打开(openTab seed.url),Ctrl/Cmd 点击放行
- 嵌入拒绝面板:browser.probe 路由探测 X-Frame-Options/frame-ancestors,被拒站点显示原因 + 在浏览器中打开/仍然加载
- 设置页可滚动;内置 7 tab + 9 viewer;新增设计文档;268 个测试全绿
2026-08-11 19:28:48 +08:00
Menghuan1918 67ec136e98 feat: 拦截聊天区文件打开 — 聊天里的文件点击默认开进侧边栏编辑器
包装 ctx.workspaces.openPath(聊天区文件打开的唯一漏斗),接管时
经 openSidebarFile 开进 sidebar 编辑器而非系统默认应用;由
interceptOpenPath pref(默认开)与 editor tab 启用态双重门控,
拒接时原样放行,dispose 还原原始方法引用(HMR 安全)。

- prefs-shared/config/prefs: 新增 interceptOpenPath 字段(默认 true)
- openpath-intercept.ts: 零依赖包装器,纯插件层面实现,不改 DSH
- intercept.tsx: registerOpenPathInterception 接线(当前会话 scope)
- SideCardSection: 常规区新增开关卡片(zh/en 文案)
- 测试: 包装器单测 6 个 + 接线集成测试 + pref 解析/设置页断言更新
2026-08-11 16:48:59 +08:00
Menghuan1918 0e20413641 feat(settings): 小卡片响应式网格 + 二级设置原生弹窗
- 清单改为小卡片,置于 repeat(auto-fill, minmax(148px,1fr)) 响应式网格,
  一行自适应排多个、随宽度换行;卡片 = 顶行(图标+标题省略+勾选徽标
  钉在最右端)+ 类型/扩展名描述行(省略号 + title 完整值)
- 声明了 settings.toggles 的卡片右下角渲染齿轮角标(IconSettingsOutline16,
  仅父级启用时显示),点击打开原生 Modal 弹窗:DSH 原生设置行
  (标题/描述 + 品牌强调原生复选框 + hairline 分隔)
- 弹窗行体抽为 FeatureSettingsRows 供直测;Modal 条件挂载
  (常闭挂载在测试 dual-react 分裂下会崩)
- 全部 token 使用 DSH 原生别名(interactive-bg-active/button-primary-fill/
  label-* 等);规格测试改为断言网格卡片与齿轮
- 文档(README/AGENTS.md/设计文档偏差记录 5/6)同步
2026-08-11 14:27:16 +08:00
Menghuan1918 1b0d2085bb feat(settings): 开关改为可点击卡片 — 高亮即启用
- 清单项(侧边栏内容/文件预览)、嵌套相关设置与「新会话默认打开」
  全部从复选框行改为 <button aria-pressed> 卡片:整卡点击切换,
  视觉状态即状态(启用 = 品牌描边 + 交互选中填充 + 勾选徽标;
  禁用 = 中性 hairline + 文字降级),悬停仅在禁用卡上显示
- 嵌套相关设置渲染为更小的缩进卡片(仍仅父级启用时显示);
  宽度输入保持原生设置行(数值非开关)
- 设置导航齿轮为宿主 shell navIcon 硬编码(无插件注入点),
  按用户选择不改宿主、保持现状
- 新增 settingsGeneralTitle 文案;规格测试改为断言 aria-pressed
- 文档(README/AGENTS.md/设计文档偏差记录)同步
2026-08-11 14:19:18 +08:00
Menghuan1918 89ddf8a9e7 feat(settings): 侧边栏设置声明式化 — 注册表驱动的开关清单 + 内置支持插头化
- SidebarPrefs 新增 tabsEnabled / viewersEnabled 开放 map(缺省 = 启用),
  PrefsSchema 用 z.dict 校验,旧文档零迁移
- TabDescriptor/FileViewerDescriptor 新增声明式设置面:viewer 补 title/icon,
  两者补 settings.toggles(子代理声明 autoOpenSubagent、终端声明 agentTerminalTools)
- 服务新增 isTabEnabled/isViewerEnabled;openTab 拒绝禁用类型、matchFileViewer
  跳过禁用 viewer
- SideCardSection 重写为注册表驱动清单:侧边栏内容(图标+类型id+开关+嵌套开关)
  + 文件预览(图标+标题+扩展名+开关),开关持久化 + 乐观提交回滚
- 消费点 gating:+ 菜单过滤、子代理自动展开、agent 终端 reconcile、
  产出文件拦截(editor 关闭时回落默认行)、新会话 explorer 预置
- 内置 viewer 补 6 个手绘 outline 图标;store 快照携带 prefs 使开关变更即时生效
- 测试:service gating / builtins 声明 / parsePrefs map 校验 / schema 断言 /
  新增 SideCardSection renderToString 规格
- 文档:README 设置小节、AGENTS.md 描述符与服务方法、新增设计文档;版本 0.5.0
2026-08-11 14:08:55 +08:00
Menghuan1918 39455ff18e docs+fix: exports 加 ./client/api 别名;catch-all+detect 纯嗅探规则;设计文档偏差记录
- package.json exports 新增 './client/api' 别名指向 service.d.ts(设计文档
  §7 的公共导入路径,api.ts 已被 fetch 助手占用故不换文件名)
- matchFileViewer 补充 sniff-only 规则:exts: [] + detect 的 descriptor 在
  无 head 时不得盲认领(AGENTS.md 的 magic-sniffer 示例原实现会吞掉
  图片/PDF 等真实 viewer 的文件);有 head 时只认领 detect 命中的。
  新增测试断言三种情形(无 head / magic 命中 / magic miss)
- docs/plans/2026-08-11-service-registry-design.md 追加 §17 实施偏差记录:
  dedupeKey+single 语法糖、available 三参、单趟匹配+嗅探规则、EditorHost/
  TextEditor 拆分、fs.read head 字段、导出别名、+ 菜单 disabled 行、
  保留的 store 级操作、内置清单、测试
- AGENTS.md:available 三参、single 语法糖、viewerId、单趟匹配+head 语义、
  8 viewer 内置清单、openTab seed.title 可选、参考实现指向 builtins/ 目录
- README 服务化章节:6 viewer → 8 viewer,标注全注册表驱动
2026-08-11 11:25:37 +08:00
Menghuan1918 1b24bc8ff2 refactor(builtins): 目录重组为 builtins/{tabs,viewers,index},清理死代码与内联样式
- 新 src/client/builtins/ 目录:tabs.tsx(6 个 tab descriptor +
  TERMINAL_LIMIT/uiTerminalCount,terminal 配额知识离开 state.ts)、
  viewers.tsx(8 个 viewer descriptor)、index.ts(registerBuiltins 聚合
  注册 + 幂等 disposer);删除旧 builtins.tsx
- icons.tsx:删除零调用者的 tabTypeIcon 及多余 import(图标已归
  descriptor.icon,映射随 tab 类型一起注册化)
- OrphanedTab:内联 style → sidebar.module.css 新增 .orphanedType 类
- 新增 tests/builtins.spec.ts(6 tab + 8 viewer 注册断言、code catch-all、
  binary-download NUL detect、office 优先级、disposer 幂等)
- 新增 tests/orphaned-tab.spec.tsx(sanitize 保留未注册类型 tab、
  OrphanedTab 占位渲染);tests/browser-globals.ts 供拉入 xterm/
  CodeMirror 的 spec 首个 import(模块求值期需要 self/document/navigator)
- service.spec 补 openDiffTab 与 diff dedupeKey 一致性用例
2026-08-11 11:22:28 +08:00
Menghuan1918 0467101faf feat(editor): 拆分 EditorHost/TextEditor — code/markdown 成为注册 viewer,fs.read 增加 head 字节
- 新 src/client/EditorHost.tsx:'editor' tab 组件。matchFileViewer →
  按 fetchStrategy 分发(纯逻辑在 editor-load.ts)→ 渲染 viewer.component;
  binary 无匹配时渲染共享 BinaryDownload。标题栏保留在 host
- 新 src/client/TextEditor.tsx:code/markdown 的 viewer 组件(CodeMirror +
  保存/脏点/截断横幅 + md 预览/编辑切换),内容由 host 经 fsRead 传入,
  自身不再 fetch;按 viewerId === 'markdown' 分支(不再解析扩展名)
- 新 src/client/editor-load.ts:planFirstMatch / planFsReadOutcome /
  decodeHead 纯函数,策略分发可单测
- 新 src/client/binary-download.tsx:共享下载兜底组件(viewer 注册与
  host fallback 同一 UI)
- host src/index.ts:fs.read 二进制响应增加 head 字段(base64,前 4KB,
  唯一 host 触碰,设计文档 detect 机制所需);客户端 FsBinaryResult 同步
- builtins:注册 8 个 viewer(新增 markdown exts md/markdown、code
  catch-all -100);binary-download 增加 NUL detect + 复用 BinaryDownload
- 删除 src/client/EditorView.tsx(职责一分为二);陈旧注释同步
- 新增 tests/editor-load.spec.ts(12 用例)
2026-08-11 11:16:36 +08:00
Menghuan1918 db0ba36610 fix(service): 注册表核心修正 — seed.title 优先、single 语法糖、available 三参、单趟匹配、dedupe 委托
- service.openTab 的 seed.title 优先于 descriptor.title(修复编辑器 tab 标题
  从文件名变成 t('editor') 的回归);title 变为可选,descriptor.title 兜底
- TabDescriptor 新增 single?: boolean 语法糖(展开为 dedupeKey: () => id),
  dedupeKey 显式给出时优先
- available 签名升级为 (ctx, scope, state) 三参(设计文档 §5.2 的超集),
  Sidebar.buildNewTabOptions 与 terminal 配额检查适配
- matchFileViewer 从三趟全局遍历改为设计文档 §8.2 的单趟 per-descriptor 算法:
  priority 降序,每个 descriptor 先 detect(head 可用时)再 exts,[] 为 catch-all
- applyDedupe 的落位逻辑委托给 state.openTabInActivePane(消除重复实现),
  同时 id 安全网现在也覆盖 service.openTab 路径(同 id 打开聚焦既有 tab)
- state.ts:删除零引用的 SIDEBAR_BUILTIN_TAB_TYPES;TERMINAL_LIMIT 移入
  builtins.tsx(state 不再关心 terminal);openTab reducer 改名 openTabInActivePane
- Sidebar.tsx 删除 TERMINAL_LIMIT/allLeaves 死 import;openDiffTab 补充
  dedupeKey 一致性注释(其按 id 去重与 diff descriptor 的 dedupeKey 同规则)
- tests/service.spec.ts 重写匹配/去重断言并新增 title/single/available 用例
2026-08-11 11:12:29 +08:00
HuanLinOTO e40039ceec refactor: 完全解耦内置 tab — dedupe 归 service,图标归 descriptor
- state.openTab 简化为 '添加到 pane + id dedupe',移除 isSingle/editor-path/diff-id 三分支
  (dedupe 策略归 service.openTab 通过 descriptor.dedupeKey 统一)
- intercept.tsx openSidebarFile 改走 service.openTab(editor descriptor 的 dedupeKey 接管 per-path 去重)
- Sidebar.tsx 两处 subagent 触发(auto-activation + jump-back)改走 service.openTab
- service.openTab seed 加可选 id 字段(editor 需要 path-derived id)
- TabBar.tsx 加 getTabIcon 回调,tab 标签图标查 descriptor.icon 替代 tabTypeIcon
- split-pane.tsx 移除 tabTypeIcon 兜底(buildNewTabOptions 已从 descriptor 填 icon)
- Workbench/NodeView/LeafView 透传 getTabIcon
- tests/unit.spec.ts 更新 openTab 测试(id dedupe 替代 type dedupe)
2026-08-11 01:39:20 +08:00