READMESource: main@cacfd2ce
dsh-composer-quick-actions
把「+」菜单里 「添加」小节的四项 —— 文件 / 目标 / 计划 / 反馈 做成 composer 工具栏里常驻的控件, 不用每次展开菜单。
适用于 DSH(DeepSeek Harness) 的 Web / Desktop 客户端。
[ + ] [ ⊘ 工作区内修改 ▾ ] [ ☑ 计划 ] [ 📎 文件 ] [ 🎯 目标 ] [ ✈ 反馈 ] …… 输入框
└── 官方那一格 └─┘ └──── 左侧一组 ────┘
四个控件
| 控件 | 点击后 | 底层调用 |
|---|---|---|
| 文件 | 打开当前输入框的原生文件选择器 | conversation.input.canPickFiles/pickFiles(sessionId) |
| 目标 | 在输入框上方弹出原生样式的目标编辑条,直接写目标内容 | goals.create / goals.edit(@deepseek-ai/dsh-goal) |
| 计划 | 进入计划模式;再按一下退出 | /plan ⇄ /plan off |
| 反馈 | 打开当前会话的反馈对话框 | feedbackUi.openSession(sessionId) |
目标的编辑条
点「目标」→ 在官方目标栏所在的位置出现一条编辑条(圆角卡片 + 输入框 + ✓/✕):
| 场景 | 行为 |
|---|---|
| 没有目标 | 空输入框;Enter / ✓ → 新建目标 |
| 已有目标 | 预填当前目标内容;Enter / ✓ → 更新目标 |
| Esc / ✕ | 关闭,不提交 |
| 空内容 | 不发请求,提示「目标内容不能为空」 |
| 保存失败 | 编辑条保持打开、内容不丢、错误内联显示 |
| 保存中 | 输入框与按键禁用,重复 Enter 不会重复提交 |
编辑条只从目标自身取初值,不读取输入框草稿 —— 输入框写什么是输入框的事,目标和它是两件独立的事。 写入目标也不会动输入框。
安装
# 在你的 DSH profile 目录下($DSH_HOME/profiles/<profile>)
dsh plugin --profile <profile> add <本仓库的 clone 路径或包名>
或手动登记(等价做法):
- 把本仓库 clone 到任意目录,例如
~/dsh-composer-quick-actions - 在 profile 的
node_modules下建软链/junction 指向该目录 - 在该 profile 的
package.json里登记:
{
"dsh": {
"profile": {
// 追加到 bundles
"bundles": ["…", "dsh-composer-quick-actions"]
}
},
"dependencies": {
// 追加
"dsh-composer-quick-actions": "link:~/dsh-composer-quick-actions"
}
}
改完必须重启 DSH Desktop 才生效。 宿主在启动时把 lib/client.js 快照进客户端模块图,
运行期改动不会热更新,每次改完都要重启。
卸载
从 profile 的 dsh.profile.bundles 与 dependencies 里删掉对应两行,删掉软链,重启应用。
实现要点
三个槽位
lib/client.js 把手写的闭包 factory bundle 交给 window.__ModuleLoader__,注册三条槽位条目:
| # | 槽位 | 类型 | 内容 |
|---|---|---|---|
| 1 | conversation.input.left |
list(id: 'quick-actions') |
文件 / 反馈 |
| 2 | conversation.input.plan |
single(priority: -100) |
计划 chip |
| 3 | conversation.input.dock |
list(id: 'quick-actions-goal-editor') |
目标编辑条 |
- 第 2 条:官方
PlanChip只在计划模式生效时才渲染,关着时那格是空的。本插件用更低优先级注册同一槽位把它接管, 于是「计划」永远显示在那个位置。这是注册表的既有语义 ——Slots.register只在同优先级报冲突, 渲染时取「优先级最低的存活条目」。插件卸载后官方PlanChip会自动回来。 - 第 3 条:与原生目标栏(
id: 'goal')是同槽位的不同 id,list 槽位按 id 各占一格,共存互不挤占。
⚠️ 执行斜杠命令要用 remote.<namespace> 服务
api-gateway 的客户端半侧会为每个 Remote namespace 单独起一个名为 remote.<namespace> 的 cordis 服务,
它不是网关实例上的一个属性(createNamespace() 里显式拒绝 namespace 与网关本体同名):
ctx.get('remote').commands // ❌ undefined
ctx.get('remote.commands') // ✅ 独立服务名
lib/index.js 是纯 UI 插件的空 apply,只负责让插件出现在宿主配置树里。
已知限制
- 接管计划槽位依赖「最低优先级渲染」语义。若官方将来给
PlanChip显式指定负优先级,接管会失效 (最坏情况退回官方行为,不会崩溃)。 - 文件按钮依赖输入框已挂载;未挂载时点击会提示「当前输入框不接受文件」。
- 控件只在已选中会话的 composer 上出现(「+」与权限下拉本身也只在有会话时渲染)。
No comments yet. Be the first to write one.