# dsh-worktable 项目规则 > 工作台容器插件:侧边栏里收纳 agent 级项目的「应用抽屉」。纯增量,不替换官方插件。 ## 协作方式(用户定案,最高优先级) > 与全局 `~/.dsh/AGENTS.md` 同步;外部 Agent(如 Codex)不加载全局文件,故此处保留全文。 - **设计先给最小版**:任何 UI/文案/方案先交「最少元素」版本给用户拍板,确认后再增量; 默认克制——没有存在理由的元素不放;不一次搭「完整版」再返工。 - **长任务拆 checkpoint**:每个可验收阶段完成后停下汇报,等用户确认再进下一段; 不一口气跑完长任务。 - **对外动作永远先审**:发布、评论、给观众的内容、任何公开操作,一律先给用户过目, 用户不点头不执行。 - **改完必读回**:每次编辑后读回改动处的完整行/段落,确认无残留、无断尾 (教训:改一半的 URL 留下旧尾巴,被复审抓为发布阻断)。 - **验收用最终产物,不用中间信号**:任何交付物(tgz、命令、文档、UI)的验收动作必须是 「解包 / 复制 / 实跑用户路径」;「构建成功」「bundle 里有字符串」「退出码 0」不算验收。 - **发布前自跑最终产物清单**:干净目录安装、最终包逐文件核对、双资产哈希、关键行逐字 grep —— 先自查再报告,不等外部复审来抓。 - **发布包验收=结构检查 + 真实安装,两道都保留**:npm pack 标准格式(package/ 前缀 + files 白名单精确 7 文件,不含 src/、不含 .map)与「独立目录 npm install + import() 断言」 缺一不可。教训:v0.3.0 手工 tar 出无前缀平铺包且误卷源码树,「解包核对(文件都在)」发现不了, 只有 npm install 复现失败;「文件都在」 ≠ 「消费方(npm/dsh plugin add)能装」。 - **声明可证伪,不许写满**:验收结论只写已验证范围。「通知全覆盖」「等价端到端」这类表述会被 反向检查打脸——只写「版本比较逻辑上重新激活」「验证了安装与服务端模块导入」。 - **同 tag 资产不可覆盖(用户已拍板)**:坏包处理 = 发新版本号 + 旧 Release 正文标注问题, 默认不替换旧资产;仅经用户明确批准的紧急补救例外,且须:不改名(保持原 URL 名)、 用已验证包、不删不盖现有资产、上传后从完整 URL 下载核对、旧 Release 注明补救经过。 ## 边界 - 插件包根目录 = `01_content/`;本仓库其余目录是项目文档与本地工具。 - **不替换、不禁用任何官方插件**(ui-sidebar / ui-workspace / ui-layout)。 - 项目/布局/绑定存 localStorage,媒体存 IndexedDB;文件窗、mkdir 与产物握手会读写用户配置的项目目录,权限边界见 `01_content/README.md`。 - dsh-travelatlas 是入驻项目而非本仓库的一部分;协议见 `02_process/PRD.md` §5.3/5.4。 - **平台边界**:Windows 网页端已测范围见 `01_content/README.md`,不宣称全功能或桌面端验收;macOS 为实验性支持(核心文件路径代码已做跨平台适配, 尚未真机端到端验证)。路径拼接必须走 `pathutil.ts` helper 或 Node `path` API,不手写分隔符。 ## 构建与验证 ```powershell cd 01_content npm install npm run build # lib/index.js + lib/client.js node --check lib/index.js ``` - 客户端 bundle 必须保持 `window.__ModuleLoader__.load` 握手与 external react/@deepseek-ai/*。 - 变更视图状态结构时同步更新 PRD 的持久化说明。 - **构建必须 `cd 01_content` 后执行**:误在仓库根跑会把 lib 写到仓库根 `lib/`,宿主仍加载 `01_content/lib` 旧 bundle,出现「改完不生效」假象(已有教训,见工作日志)。 - **发布打包唯一入口 = `npm run pack`(01_content/release-prep.mjs)**:身份断言(package/manifest/cordis.patch.yml 严格结构)→ 版本一致性 → **会话/输入兼容回归**(test:input / test:session / test:details 共 53 项,评估源码契约)→ 构建(cwd 固定 01_content)→ node --check → npm pack → 结构清单断言(package/ 前缀 + 精确 7 文件, 无 src/、无 .map)→ 独立临时目录 npm install + import() 断言(apply 函数/inject 含 webServer+sessions/name/ HEALTH_PATH/包内双 bundle 版本)→ **客户端工厂求值门禁**(ModuleLoader 恰好注册一次 + ID 校验 + 精确外部依赖 白名单 react/react/jsx-runtime + apply/inject 断言)→ **分栏锚点 DOM 回归**(8 场景,评估安装产物 lib/client.js; 测试接受包路径参数)→ **服务端数据目录回归**(3 组场景,评估安装产物 lib/index.js)→ dist/v版本号/ 双资产(终态恰好 2 文件 + 双 SHA 同源)。 脚本零 git/gh 动作,发布上传由 gh 手动完成。 **发布禁止裸 npm pack 或手工 tar 生成发布包**;脚本从仓库任意目录调用均安全(以自身位置解析)。 - **配套检查入口**:`npm run test:gate` = 工厂门禁 10 个失败/正向用例; `node 04_test/anchor-dom.test.mjs [lib/client.js 路径]` = 分栏锚点 8 场景(缺省用工作目录构建产物); `node 04_test/server-home.test.mjs [lib/index.js 路径]` = 数据目录解析 3 组场景(子进程隔离夹具; **突变体验证法**:把 loadPkg 兜底的 baseDshHome() 故意改回 resolveDshHomeSafe() 恢复循环,测试必须红)。 `npm run verify:remote -- --expect-sha [tag]` = 发布后只读核对 (远端固定名+版本化双资产文件名/结构/版本/双 SHA 同源且等于本地验收 SHA/安装/导入; 远端只读,本地仅临时目录)。上传后必须跑 verify:remote 并用 --expect-sha 比对, 防「双资产同错」;tag 模式断言 tag==='v'+包内版本。 ## 领域约定(会话中必须遵守) - **0.3.4 网页端兼容**:声明已测版本 0.1.1-rc.2 / 0.1.2-rc.1 / 0.2.0-rc.2,不外推 Desktop 或未测宿主。当前会话走 `currentSessionOf`(旧 current / 新 mainView),导航走 `openHostSession`(新 uiWorkspace / 旧 sessions.open),标注走 `appendHostInput`。Cordis 可选服务用 `ctx.get` 探测;返回 undefined 后不得再访问未 inject 的同名属性。旧 current 契约的引用坐标是显示文本位置,新 mainView 契约按单字符 chip 投影;不得因旧版也有 input.for 就误用新版坐标。`npm run test:input` 为 15 项定向回归。 - **0.2 会话状态/发送**:可选订阅 `uiSession.sessionStatus`,只读归一化列表与 subagentCatalog;completionUnread 不是任务成功证明,打开主会话会清除。已观察到的 running→false 供原产物握手使用,下一轮 running 清除;停止不等于成功。新会话发送通过 `sessions.using` 临时持有,等待 ready 后发送并由宿主释放;业务拒绝/超时不得再换通道重发。`npm run test:session` 为 20 项回归;0.2.0-rc.2 已实测短文本发送/回复,真实待决/子代理仍未验收。 - **0.2 新建/预览/选择接口**:`createHostSession` 在新宿主先用官方 workspaces 登记/复用项目路径(无路径则 initializeDefault),再按 workspaceId 创建;仅 cwd 会留下“可打开但不能输入”的未分组空会话。旧宿主保留原分支,不自动迁移既有会话。预览读临时持有的 eventSource,模型目录在持有期间使用,结束即释放;预设接 remote.agentPresets。`npm run test:details` 为 18 项回归,不能替代真实模型会话验收。 - **窗口编号**:用户说「窗口1/2/3…」指布局里按「左栏 → 顶行 → 主行」顺序的第 N 个内容窗。 例:田字格预设(g4)窗口1/2 = 顶行左右、窗口3/4 = 底行左右;l13 窗口1 = 顶部大窗, 窗口2/3/4 = 底部三小窗(从左到右)。需要定位时按此映射,不要凭猜测。 - **预设追加规则**:新布局预设只允许追加到 `PRESET_DEFS` 末尾(选择器里的「+自定义」磁贴 永远是最后一个);字段 leftCount/topCount/contentCount/chatFull/topHeightDefault/topHeightRatio, 聊天窗恒在右侧;缩略图在 presetThumb() 加分支。 - **分栏引擎双版本锚点(v0.3.3 兼容契约,0.1.1-rc.2 与 0.1.2-rc.1)**:会话根三种结构—— 0.1.1 active = [头部, 滚动区];空会话 hero = [隐藏头部(headerHidden), 内容区];0.1.2 无会话 hero = [内容区](单子元素)。resolveAnchor:仅「存在第二个子元素」且第一个可见(自身有高度, 或零高槽位包装内第一个可见后代——visibleHeaderIn)才作头部;否则顶部取根顶部。phase 排序 active > hero > settling,settling 等待不关闭。**同根重锚(hero↔active)只在锚点元素真正 更换时才更新 saved 原值**,否则关闭时泄漏已应用的 margin/宽度变量。改锚点必须跑 anchor-dom 8 场景(含 H:0.1.2 零高包装+内部头部)。0.1.2 的 children[0] 是 display:contents 式零高包装、真实标题栏在内部——不要凭 0.1.1 的结构想当然。 - **数据目录解析无循环(v0.3.3 红线)**:resolveDshHomeSafe = 官方 @deepseek-ai/dsh-home-paths (可用时)→ baseDshHome 兜底;**loadPkg 的 profiles 兜底只能用 baseDshHome,禁止回调 resolveDshHomeSafe(会成环)**。baseDshHome 规则对齐官方:DSH_HOME 优先、空/纯空白视为未设、 ~ 与 ~/ 与 ~\ 展开、相对路径按 cwd、默认 ~/.dsh;trim 只用于空白判断、路径保留原字符串。 **不把 dsh-home-paths 声明为生产依赖**(其 peer cordis ^4.0.2 不满足 0.1.1-rc.2 的 4.0.1, 已试过并撤回)。改这块必须跑 server-home 3 组场景 + 突变体验证(恢复循环测试必须红)。 - **新会话预设修复**:新建会话(createCustomSession / bindConsoleNew)创建后调用 ensureSessionPreset——用宿主 api.agentPresets.list/select 显式应用「部署默认预设」 (isDefault ?? 首个,失败逐个尝试其余预设;select 仅对 blank 会话生效)。 - **新会话模型修复(真根因)**:会话级模型选择独立于预设、随默认选择持久化——用户删掉 provider 后新会话继承失效选择,prompt 报 model-unavailable。ensureSessionModel: ① 无条件继承「当前会话」正在用的模型(用户控制用哪个就用哪个,相同则跳过); ② 无当前会话且新会话不可用时 → 最近会话众数 → 失效选择的家族词匹配 → 目录首个。 session.selectModel 同时把新选择存为默认(继承的 Pro 会写回默认)。失败静默、缺 API 跳过。 - **对话绑定**:projects.v1.bindings = { 项目id → 会话id };打开项目时引擎自动 sessions.open(绑定会话)(openSplit / DOM 桥两处入口);未绑定/解绑 = 不切换。 - **项目×对话联动**:打开项目记录「打开前会话」(projectAttachRef.sessionId);项目打开期间切到 非绑定会话 = 自动关项目(suppressRestoreRef 跳过回切);✕/反选关项目 = 回切「打开前会话」。 未绑定项目的归属会话 = 打开前会话。 **例外**:插件自身发起的会话切换(新建对话 sessions.open(新会话)、发送到会话)不得触发 自动关项目——createCustomSession/sendCustomToSession 用 markPluginSessionOpen 豁免 (pluginOpenedSessionsRef),用户要继续在项目里跟新对话沟通;同时 CustomPane 在发送成功后 调用 autoBind:项目未绑定则自动绑定到新建/选中的会话。 - **项目文件夹**:projects.v1.folders = { 项目id → 绝对路径 };新建项目强制填写(父目录必填, 文件夹名留空 = 用项目名),保存时走 /api/worktable/mkdir 建目录;绑定面板可改。自定义窗口 新建会话(未选分组时)用 sessions.create({cwd: 项目文件夹}),提示词携带文件夹与「所有产出 放进该文件夹」指令——用户要求项目产出文件不得落到默认位置。 - **窗口任务提示词**:buildWindowTaskText 统一组装(窗口身份「项目+窗口N」+ 项目文件夹 + 插件知识包);知识包注明「不要重新侦察插件源码」,改提示词时保持这个原则。 - **自动挂载(widget-result.json 握手)**:提示词第 5 条要求 agent 完成后用一句话 告知用户挂载结果(不提问、不等确认,例:「已自动挂到窗口N,想调整直接说」); 第 6 条要求 agent 完成后在项目文件夹写 widget-result.json {window:'窗口N', path, kind:html|url|file};客户端监听绑定会话 completed → buildMountContent 转换产物 → 项目开着直接 openTab 进「窗口N」(windowLabelToPane 按窗口 编号规则定位),项目没开暂存 pendingMountRef(localStorage,打开项目时补挂); mountConsumedRef 保证一次完成只消费一次。 **锁死语义**:挂载用 splitStore.lockPane——清空该窗格原有标签,把产物作为唯一固定标签 (active 0),onSpecMutated 立即持久化;用户下次打开工作台窗口直接显示产物,不丢失不重置。 待挂载记录带 {content,row,index},补挂同样 lockPane。 - **原生皮肤模板**:01_content/template/dshell.css + dshell.html(esbuild text loader 嵌入服务端 bundle,/api/worktable/template 路由下发);知识包要求产出 HTML 一律引用该样式表,组件类 参考模板。新增组件样式只加到 dshell.css,保持单一来源。 - **提示词零泄漏(硬约束)**:所有对外生成的提示词(buildWindowTaskText 窗口任务提示词、 buildCustomLayoutPrompt 剪贴板布局提示词)禁止写入用户的个人工作区分组名(如 Projects / DeepseekHarness)、其他用户的项目名与私人路径。剪贴板提示词会发给别人的 DSH,必须只含 插件通用知识;窗口任务提示词只发用户自己的会话,允许携带该项目自己的文件夹路径。 分组下拉只是会话创建工作区的选择,绝不进入任何提示词文本。 - **任务完成/待决提醒镜像**:绑定会话在宿主快照 byId 里 completed=true → 项目卡双圆点变 绿色发光(data-bound=done);pendingInteraction != null → 黄色发光(data-bound=need); 点开项目 = ack(notifyAck.v1 按会话存状态)恢复常态实心。数据源 = sessionsSnapshotStore (syncSessionScope 写入完整快照并通知监听者);跨状态(done↔need)会重新点亮。 **工作中(busy)**:byId[sid].running === true → data-bound=busy,蓝色 #4f8ef7 发光 + dsh-wt-busyA/B 关键帧两圆交替亮灭(对应 DSH 转圈标记);优先级 need > done > busy——等待判断时 pendingInteraction 与 running 同时为真, 原生 UI 以黄点优先,镜像必须一致;busy 无需 ack,running 变 false 自动切换。 **子代理聚合**:待决状态常挂在子代理会话上(父会话只有 running)——bindNotifyMap 用 collectKids(byId.parentId + subagentsByParent 双通道)聚合父会话及其子代理的 pending; 会话面 binding(id).session.getSnapshot().pending 非空也判 need(列表不映射时的兜底); ackProjectNotify 同步 ack 子代理。 **ack 生命周期**:新版按父/子会话的 opaque pending key+kind 集合保存 `need:...`,不保存问题正文/答案; 同为 need 的问题替换也重新点亮,排序/重复目录不触发误亮。旧无 key 主机继续存 need,无法识别无 key 的直接替换。 状态/身份转移清旧 ack 时同步清除本次读取副本;点开确认同时记录已见身份,防下一次渲染反清刚保存的 ack。 - **「工作台」控制室项目(默认自带)**: - 固定 id `wt-console`(CONSOLE_ID),卡片恒排项目列表第一位(order 0)、不可删除 (不进设置管理列表 + removeProject 兜底拒绝);图标 🖥️,名称走 locale console.name。 - 点开:已绑定 → openConsole(默认布局 buildConsoleSpec:单一大窗格 content {kind:'builtin',type:'console'} + 右侧对话,spec 持久化在 views['wt-console']); 未绑定 → 强制绑定弹窗(左「加入现有对话」列表 / 右「新建对话」:分组 无/现有/新建, 建空会话 sessions.create 后自动绑定并打开控制室)。绑定也走 projects.v1.bindings。 - 控制室面板(split.tsx ConsolePane):卡片网格每行 3 张、超出换行;每卡 = 图标/名称/ 状态大字与三色光效(need>done>busy>idle,不过滤 ack,永远显示事实状态)/运行时长 (后台任务 JobView.startedAt → 新版 uiConversation 的 chat.legacy.turnTimings → 旧会话面, 未加载/无起点不显示时长;不为计时激活 chat 或持有冷会话)/最近消息预览。数据组装 = index.tsx getConsoleCards(env.console 注入),刷新走 consoleListeners(项目/会话 快照变化推送)+ 面板每秒 tick。 - 卡片动作:点卡片 = 打开该项目(openSplit 或入驻项目切绑定对话);工作台自己的卡片 点击无操作。💬 跳转按钮已删除(用户定案无意义)。 - 主题:面板三选一开关(图标按钮 🌙/☀️/🖥️,title/aria 保留文字;存 view.v1 consoleTheme); system 读宿主 html 的 color-scheme(DSH 深色/白色/跟随系统设置都会反映到它)+ prefers-color-scheme 兜底;落成 .dsh-wt_console[data-wt-theme=dark|light] 作用域变量 --wt-*(宿主不发布 --dsw-alias-*,工作台全站一直靠回退色渲染——控制室自带主题作用域, 不受其影响)。 - 状态光效(整卡霓虹描边,参考侧栏双圆点发光质感):工作=蓝色彗星式光点顺时针绕卡旋转 (.dsh-wt_consoleCard-busy ::before conic-gradient + @property --consoleAngle + consoleAngleSpin;环 inset -4/padding 4、高亮段 #dcebff→#9cc6ff、filter drop-shadow rgba(140,190,255,.85) 光点自带辉光、外发光双层 10px+34px);完成=绿光、待决=黄光 (-glowDone/-glowNeed:亮色描边 + 双层外发光 + 微弱内辉光;glow 字段 = done/need 且 本轮未 ack,点卡片先 onAck 熄光再进入,与提醒 ack 生命周期一致)。 - 命名:侧栏区块标题 = 「工作台」(title locale,整个插件);默认项目卡名与面板标题 = 「控制室」(console.name/console.title locale,工作台的控制室)。 - 控制室标签不可关:PaneBody 对 content.type==='console' 的标签 locked(不渲染 ✕、 禁拖拽)——关掉会退化成窗格选择器,不可逆。 - 布局尺度:网格 gap 64px(4 倍间距)不变、max-width 856px 左右居中;卡片 1:1(面积 2×边长 1.4,实测 236px:名字 20/状态 22/预览 8.5 四行截断);标题下横向分隔线;无子代理 徽章、无 💬 跳转按钮;网格最后一位恒为「创建卡片」(虚线+)→ openAddPanel;入口卡无描边。 - 背景三选一(data-wt-bg):纯色/流光/自定义,各自独立记忆——色相/饱和度/明度(-180..180/0..200,纯色随主题默认:深 #0a0d13、浅 #eef1f5)、 贴片模糊 B(0-20;预设 纯色0/流光8/自定义8)、网格线不透明度 T(0-30;纯色/流光各自)、 流光速度 S(0-100,0=完全不动,默认50=原速,consoleGlowSpeed;--wt-glowScale=50/速度;恢复初始一并复位)、 照片网格开关(consoleBgPhotoGrid)+ 网格透明度(consoleBgGridOpacity);卡片 glass 底含 backdrop-filter blur(var(--wt-cardBlur))。 - 自定义背景 = 媒体库(照片+视频):IndexedDB photoRecords(id/createdAt/kind/blob/order); 首用预置两张默认图(defaultBg.ts SVG 极光 + waveBg.ts JPEG 181KB,标记 defaultBgSeeded.v1); 缩略图类型角标/全行删除/抓手拖拽(槽位制+FLIP,photoStore.reorder);视频背景双轨首尾帧交叉渐融(ConsoleVideo ≥1.2s 最长2s、备轨就绪才淡入)。 - 底部操作台:5 个 ghost 按钮玻璃 dock(主题/形状/背景/每行数量/更新公告);菜单点选保持打开、点空白关闭; 下拉宽度 = dock 总宽 186px(媒体宽版 264px)。 - 顶部标题克制:控制室页只保留侧栏入口卡一个「控制室」——分栏标题栏对 wt-console 不渲染 title(保留 ⇄/✕)、PaneBody singleConsole 不渲染标签栏;控制项集中在底部操作台 5 键。 - 更新公告(第 5 键,反选切换大磁片替代网格视图):顶部=当前版本/检查更新/自动检查开关; 新版本横幅=复制升级指令(点击变绿并提示「请在任意对话中发送」)/查看发布页(蓝 hover)/忽略此版本(红 hover)+ CHANGELOG_V030 正文(changelog.ts 纯文本,不做 md 渲染); 检查核心 = updateCheck.ts(updateCache.v1 缓存,与设置面板旧更新卡共用 lastUpdateCheck/skipVersion/updateCheck 键, 旧卡暂保留未去重);铃铛按钮发现新版本时右上角琥珀呼吸灯(dockBadge)。 - 指哪打哪标注:窗格折叠键旁 annotBtn——蓝泡光标→点选/拖框(小拖=点)→输入→✓ 注入宿主 textarea(不发送,失败回退剪贴板)。 payload v3.2:窗口身份(编号+窗格标题+内容类型/URL)+ 主目标(caretPositionFromPoint 取字 + computed style 字号)+ 整行 + 候选; 同源 iframe 下钻取字(boxPayload),跨域输出「读取受限」+ src;提示词含「禁止编造,缺失时如实说明,建议截图或视觉模型」; 知识包含标注协议行(窗口编号+处理方式);回退锚点 tag:pre-annotate-v3 / pre-annotate-v3.1 / pre-annotate-v3.2。 - 分隔线:DIVIDER=4(分栏可拖分隔条更细)。 - 冷会话消息预览:0.2 用 sessions.using 临时持有后读 eventSource 的已加载事件窗口;旧版预热走 face.history({maxMessages:6}) (旧版运行期内建方法、非公开接口);尾部扫 user/message 与 assistant/message 的 text 块 → cleanPreviewText(滤除 ```围栏与行内代码、压缩空白;不足 8 字符回退更早消息)→ previewCache;sweepPreviews 在打开控制室时 + 控制室开着且会话快照变化防抖 6s 触发; 失败静默回退缓存/内存路径 lastTextOf;预热期间不因自身 retain/release 排入新一轮。拉取是带宽成本不是 Token 成本。 - 状态指示:卡片右上角小圆点已删(整卡光效表达状态);状态计算不变。 - **更新检查(v0.2.2)**:客户端直连 GitHub Releases API 比版本(只读 GET;自动每天最多 一次,手动「立即检查」绕过节流;单次 8s 超时(AbortController)+ 最多 3 次重试, in-flight 防重入、检查中按钮禁用、组件卸载后停止重试与状态更新;失败后状态行显示「上次检查未成功」)。 状态四态 idle/checking/uptodate/failed;徽标 = 「工作台」标题右侧琥珀呼吸小圆(SVG 同步 图标、不显示版本号),仅发现更新时出现;更新卡在设置面板顶部(复制 AI 提示词 + 忽略此 版本 + 命令框供终端用户手抄),版本号与自动检查开关在面板底部;设置弹窗右上角 ✕ 关闭、 底部防溢出钳制(POP_BOTTOM_MARGIN=12,贴底后向上生长;按钮与开关必须平级防冒泡)。 localStorage 键 lastUpdateCheck.v1 / skipVersion.v1 / updateCheck.v1(控制室第 5 键公告页共用,另缓存 updateCache.v1)。 **发布纪律**:改动≠发布,只有 tag+Release 才触发提醒;新版本 = 新 tag + 新 Release,每个 Release 必须同时附固定名资产 `dsh-worktable.tgz`(供 releases/latest/download 永久链接) 与版本化资产。版本注入:build.mjs 把 package.json version 打进 __WT_VERSION__,发版前 改 version 再构建;升级动作(执行 add + 重启)永远留给用户或其 Agent,插件不自更新。 ## 旧版 link 加载兼容边界 - 特定旧版 0.1.1-rc.2 的裸包 import / link peer 解析曾使用上级 node_modules junction 兜底,不是通用修法。 本机 0.2.0-rc.2 经无旧链接冷启动与真实工作台/控制室检查后已移除该链接,原目标目录保留。 重新遇到加载失败时先查实际宿主、profile、插件及第一条完整错误;不得直接创建更高层级链接或删除它指向的依赖目录。 ## 宿主 bug 跟踪(UTF-16 路径截断) - 官方 native picker 的 readUtf16 只看 UTF-16LE 低字节,含 U+XX00 字符(开/一/言/Ā/🀀 等)的路径被截断。非我们插件问题。 修复与回归测试存档:`02_process/upstream/utf16-picker-fix.patch`;官方 Discussions #580 已接单。 待办:官方修 master 后核验对应 npm 版本是否含修复,随后可清理 patch 存档。 ## 安装 / 重启 - 注册:`dsh plugin --profile web add "link:/01_content"`(写 ~/.dsh,需用户授权)。 - 发布版安装(给用户):`dsh plugin --profile web add "https://github.com/Aisland-SJL/dsh-worktable/releases/latest/download/dsh-worktable.tgz"`(依赖每个 Release 的固定名资产)。 - bundle 层只在启动时组合:改动后必须重启 dsh web 并刷新 GUI。