dsh-aloof
把 Aloof 的办公审批接成 DeepSeek Harness(dsh)的原生工具:查待办、列模板、读表单、提单。写操作过 dsh 审批闸门,也是一份可运行的 dsh 插件样板。
- Stars
- 0
- Language
- JavaScript
- Created
- Aug 19, 2026
- Updated
- Aug 20, 2026
Introduction
dsh-aloof
把公司在 Aloof 上的团队资料库和办公审批,接成你自己电脑上 DeepSeek Harness(dsh)的原生工具。
装上之后,你在本机的 dsh 里可以直接说:
- 「这个客户的电价方案,我们以前是怎么算的?」→ 它去团队资料库里搜,读出同事写过的那份,照着来
- 「把刚才这套踩坑记追加到《电价踩坑记》里」→ 写回同一份共享文档,同事在网页上立刻看得见
- 「我这个月的差旅报销提一下」→ 按模板发起审批,走公司的审批链
分工是清楚的:Aloof 是团队那一份——权限、数据范围、审批链、审计留痕都在它那边,它是权威源;你本机这台 dsh 是你自己的手脚,快、私密、想怎么折腾都行。这个插件是把两者接起来的受控通路:一张能单独吊销的票、一份写操作白名单、加上本机的确认闸。不是把公司数据拷到本地,也不是把本机的 AI 放进公司系统随便动手。
它同时是一份可运行的 dsh 插件样板——插件机制的每一层都用到了(工具注册、凭据服务、写操作审批闸门、结果渲染),单文件、没有一句 import(为什么见 index.js 顶部)。
装
dsh plugin --profile web add dsh-aloof
然后把票粘进来就完事了(下一节说票从哪拿):
export ALOOF_TOKEN='alf_xxxx@https://aloof.你的公司.com'
dsh web
只有这一个东西要配,没有第二项。 票里 @ 后面那截就是你公司那台 Aloof 的地址——密钥和它该发去哪台绑在一起,所以不存在「地址填串了、票发到别人服务器上」这回事。整串复制,别只粘前半截。
也可以直接从 GitHub 装,建议钉住某个提交,免得哪天一次推送悄悄改了你机器上跑的东西:
dsh plugin --profile web add 'github:gaochonggeng/dsh-aloof#<commit-sha>'
(这个插件是纯 JS、没有构建步骤,所以 git 安装不需要 allowBuilds 那道构建授权。)
卸载 dsh plugin --profile web remove dsh-aloof,profile 的 bundles 列表会自动摘掉这一层。
工具
团队资料库(六个)
这组是「团队共享知识」落到本机的通路。你在网页上进得去的空间,这里进得去;进不去的,拿这张票一样进不去——空间 ACL 是同一套,插件不放宽任何东西。
| 工具 | 干什么 | 读/写 |
|---|---|---|
kb_spaces | 我能进哪些空间,返回 spaceId 和我在里面的角色 | 读 |
kb_search | 按名字、用途说明和正文全文搜,返回 nodeId 和命中的那句话 | 读 |
kb_list | 列一个目录下的一层,用来摸结构 / 挑 parentId | 读 |
kb_read | 读一份的全文(太长会截断并说明) | 读 |
kb_write | 新建一份,或往已有的那份追加 / 覆盖 | 写,过闸门 |
kb_delete | 删一个文件或目录 | 写,过闸门;不可恢复 |
几个刻意的设计:
kb_write默认是追加不是覆盖(mode默认append)。「把这条记下来」的常态是往一份活文档后面接一段,不是把整篇换掉;要覆盖得显式mode: replace,确认文案里也会写明「原文会没了」。- 版本号(
rev)插件自己处理,不让模型编。改一份文档时它先读一遍拿到rev再提交,中间有人改过就会被后端拒——这是乐观锁,撞上了重读一遍再写就行。要是把rev交给模型,它迟早会随便填一个数把同事的修改盖掉。 kb_read碰到读不了的东西会明说。Word / Excel / PPT 是压缩包,按文本读只会读出一堆乱码;PDF、图片同理。这种情况返回「读不出来,去网页看」,不把乱码当正文喂给模型——喂了它会照着乱码瞎猜。kb_delete的确认文案是服务端问出来的,不是模型自己报的。删目录之前先查出这个目录叫什么、底下直接挂着几项,写进确认里。让模型自己报的话,它报错一次,人就是在给一句假话点同意。资料库没有回收站,删了就是没了,所以这一句必须准。
办公审批(五个)
| 工具 | 干什么 | 读/写 |
|---|---|---|
oa_todo | 查等我处理的单子,返回 taskId | 读 |
oa_templates | 我能发起哪些审批,返回 templateId | 读 |
oa_form | 某模板要填哪些字段(key / 类型 / 必填 / 选项) | 读 |
oa_submit | 发起一单 | 写,过闸门 |
oa_decide | 同意 / 驳回一条待办 | 写;接入令牌下后端直接拒,见下 |
刻意分成四步(列模板 → 读字段 → 填 → 提交):模型不该猜表单字段名,oa_form 就是把「猜」换成「查」。
票据:用「dsh 接入令牌」,不要用登录票
在 Aloof 里点左下角自己的名字 → dsh 接入 → 生成一张。复制出来的是这么一整串:
alf_kQ3xN…7Bv@https://aloof.你的公司.com
前半截是密钥,@ 后面是这台 Aloof 的地址(就是你访问它用的那个地址)。明文只显示一次,当场抄走;丢了不要紧,回来吊销掉再生成一张。
为什么把地址塞进票里:这样 dsh 那头只有一个东西要配。地址和票各自是一个可填字段时,「填串了、票发到别处去」就永远可能发生;合成一串之后这件事在物理上就不成立了。
为什么不直接把网页的登录票复制过来:那张票带着这个人的全部权限、三十天有效、没法单独作废——放在笔记本上被捞走的人能替他批审批。接入令牌反过来长:
| 登录票 | 接入令牌(alf_ 开头) | |
|---|---|---|
| 查数据 | ✅ | ✅ 范围完全一样(该看见什么就看见什么) |
| 读写资料库内容 | ✅ | ✅ 共享知识要能双向流动,只读的话「共享」只剩一半 |
| 提审批单 / 撤回 | ✅ | ✅ 提单是「请人来批」,不是终局动作 |
| 同意 / 驳回 | ✅ | ❌ 403,只能本人在网页上点 |
| 拉人进空间 / 发邀请链接 | ✅ | ❌ 403,那是授权动作,得在网页上当面点 |
| 建空间 / 删整个空间 | ✅ | ❌ 403 |
| 改别的业务数据 | ✅ | ❌ 403 |
| 单独吊销 | ❌ | ✅ 按设备,管理员那边还看得见它最后一次从哪连的 |
分界线是:内容可以从 dsh 写,权限和终局动作不行。写一份文档是干活,谁能看这个空间、这单批不批,是要人当面拍的。
所以 oa_decide 在接入令牌下必然 403。这是设计如此,不是配置错了——工具描述里写了这句话,模型不会改参数重试。
配
正常情况下什么都不用配——地址跟着票来,装完粘一串票就能用。下面这些是给需要拧的人看的。
默认值在包自带的 cordis.patch.yml 里。不要改那个文件(升级会覆盖),要改就在 profile 自己的 cordis.patch.yml 里按 id 覆盖。patch 是整块替换 config 而不是深合并,所以覆盖时键要写全:
- id: aloof
name: 'dsh-aloof'
config:
tokenEnv: ALOOF_TOKEN
timeoutMs: 20000
requireApproval: true
| 键 | 说明 |
|---|---|
tokenEnv | 令牌的引用名(POSIX 标识符),不是令牌本身 |
timeoutMs | 单次 HTTP 超时 |
requireApproval | 写操作是否必须先问人。true = fail closed |
baseUrl | 默认没有这一项,通常也不该加。 填了就以它为准(盖掉票里带的地址) |
baseUrl 只有一种情形该填:网页地址和 dsh 能到达的地址确实不是同一个——反向代理、内外网双入口,网页在 https://aloof.corp.com,而你的 dsh 只能走 http://10.0.0.5:8000。除此之外填它只是给自己留一个填错的机会,而包里之所以不带这一项,是因为一旦带了具体域名,装完没改的人的票就会发到那个域名去(那边只会回 401,但票已经出网了)。
令牌的值走 dsh 的 credentials(进程环境变量或 $DSH_HOME/.credentials.yaml),配置里只留引用名——这样配置可以随便同步、随便渲染到界面上,换令牌也不用碰文件,而且每次调用现取不缓存,换完(甚至换成另一家公司的实例)下一次请求就生效,不用重启 dsh。
票里带不带地址,取决于发票的那台 Aloof(是它的网页拼上去的),和插件版本无关。老票不带 @,还能用,但得自己在 baseUrl 里补地址——去网页上重新生成一张就不用管了。
团队里多个人一起用
一人一张票,各自在自己机器上生成,互不相干。想在同一台机器上模拟多个人(测试、或者一台共用的机器),靠 DSH_HOME 隔开就行——profile、插件、凭据全在那个目录里:
DSH_HOME=~/.dsh-alice ALOOF_TOKEN='alf_甲的票@https://aloof.你的公司.com' dsh web --port 3081
DSH_HOME=~/.dsh-bob ALOOF_TOKEN='alf_乙的票@https://aloof.你的公司.com' dsh web --port 3082
两边搜到的资料库内容按各自账号的空间权限算,写进去的东西对方立刻搜得到——共享的是同一份,不是各存一份。
管理员那边(Aloof 的 系统管理 → dsh 接入)能看到全公司谁的哪台机器连着、最后一次什么时候从哪个 IP 连的、用的什么客户端,也能替人吊销(人离职了、电脑丢了)。但看不到明文、也发不了票——票只能本人自己生成,不然审计里「这是他干的」就不成立了。
写操作的两道闸
别搞混,这是两道独立的闸,分别在两个地方:
本机这道:所有写工具(kb_write / kb_delete / oa_submit / oa_decide)在发请求之前先走 dsh 的 ctx.approval.request(),把「要改什么」写进 reason 交给人,只有拿到 allowed-once 才继续。三条拒绝路径都是不落库的——人点拒绝(rejected)、人关掉提示或会话被取消(cancelled)、这台 dsh 根本没有审批服务(直接拒,不是默认放行)。
approval 故意没写进模块级 inject:写进去会让整个插件在没有审批服务的装配里根本不挂载,连查资料都用不了。改成运行时探测 + fail closed,是能力降级而不是整体消失。
服务端那道:Aloof 后端按令牌类型和一张 (方法, 路由) 白名单拦——不在名单里的写请求一律 403,以后新加的写端点默认也进不来。所以 oa_decide 就算在本机被人点了同意,到了后端照样 403:「批准」这个动作不接受来自一张笔记本上的票。
两道闸管的是不同的事:本机那道防「模型自己悄悄干了」,服务端那道防「这张票压根不该能干这个」。前者可以关(requireApproval: false,你自己的机器你负责),后者关不掉。
有问题
提 issue。这个插件只是个转发壳,业务逻辑(权限、数据范围、审批链、审计)全在 Aloof 后端——所以「它不让我批审批」「它进不去某个空间」这类不是插件的问题,见上面「票据」那节。
还没做
- 没有设置页:
baseUrl/ 令牌目前只能写 YAML 或环境变量。做成设置里的表单需要installSettingsSection+ schemastery,那就得引入运行时依赖。 - 没有专用 UI 卡片:
output.render只给了文本。要把同意/驳回按钮、资料 diff 画在对话里,得写浏览器那半边。 - 传不了文件:
kb_write只写文本文档(md / csv / html / txt / json)。上传二进制要走 multipart,那是另一套写法。 - 没做转办 / 催办 / 抄送:后端接口都有,照现有工具复制即可。
License
MIT