dsh-aloof
把团队的做事方式装进这台机器的 agent。
装上并配好票之后,公司在 Aloof 上定的规矩就会自动进到你这台 dsh 的 agent 里:
- 红线进系统提示——每一步都在,无条件生效。「客户数据不许发给外部服务」这类。
- 手册进 dsh 的技能目录——agent 觉得跟当前任务相关时才去读。「周报怎么写」这类。
管理员在网页上改一句话,你这台机器五分钟内跟上,谁都不用重启。
反过来也通:你摸清一件事之后,可以就地把它交回团队——在会话里说一句「把这个存成团队手册」,它会进公司的待确认列表,管理员放行之后下发给所有人的 agent。你不用离开 dsh,也不用把刚才那段再写一遍。
顺带还有两个只读工具和一颗悬浮按钮,用来确认这台机器真的接到了公司那台 Aloof 上、而且下发到位了。
先说清楚:这是个客户端插件,得配一台 Aloof 才有用。 Aloof 是私有部署的企业平台,服务端不在这个仓库、也不公开。手上没有 Aloof 实例的话,装上这个插件只会得到一句「没配 ALOOF_TOKEN」——它不是能独立干活的工具集。
要什么
- Node ≥ 22
- dsh
- 一台跑着的 Aloof,以及你在上面生成的一张 dsh 接入令牌
装
dsh plugin --profile web add dsh-aloof
想跟着改代码就用本地开发链接:
git clone https://github.com/aloof-ai/dsh-aloof.git
dsh plugin --profile web add /绝对路径/dsh-aloof
配
只有一个东西要配:那张票。
在 Aloof 网页上点左下角自己的名字 →「dsh 接入」→ 生成,复制出来长这样:
alf_xxxxxxxxxxxx@https://aloof.你们公司
整串放进环境变量 ALOOF_TOKEN,或者写进 $DSH_HOME/.credentials.yaml:
ALOOF_TOKEN: alf_xxxxxxxxxxxx@https://aloof.你们公司
地址是跟着票一起来的,所以不用再配服务器地址。这不是为了少打几个字:地址和票如果是两个各自可填的字段,「填串了、把票发到别人服务器上」这件事就永远可能发生;粘在一起之后它在物理上就不成立了。
注意别只粘前半截——@ 后面那截就是地址,少了它插件不知道该往哪儿发(真发生了会有一句明确的报错告诉你去重新复制)。
放的必须是接入令牌,不是网页的登录票。 登录票带着这个人的全部权限、而且没法单独作废;接入令牌读全放行、写只走白名单,并且能按设备单独吊销——某台机器丢了,在网页上把那一张吊掉就行,不影响你其他机器。
改了 credentials 不用重启 dsh:它盯着 .credentials.yaml 热更新,改完下一次调用就走新票(实测半秒内)。唯一的例外是把票写成了环境变量——那份在进程启动时冻结,而且优先级压过文件,所以「文件明明改了却没生效」先怀疑有个同名环境变量。
票会在两种情况下自己失效,都不是 bug:
- 你改了 Aloof 的密码。改密会吊销你名下所有令牌(改密的场合通常是「怀疑泄露了」,只换密码不撤令牌等于换了大门锁却留着侧门钥匙)。回网页重新生成一张。
- 那张票设了有效期而且到期了。生成时可以选,默认是永不过期。
两种情况 /aloof 和工具报的错都会直说是哪一种,照着做就行。
团队上下文是怎么进到 agent 里的
插件每五分钟去 Aloof 拉一次 /api/context,那一份里有两样东西,走两条不同的路:
| 去哪 | 什么时候生效 | 代价 | |
|---|---|---|---|
| 红线 | 系统提示(ctx.systemPrompt,排在部署人格之后) |
每一次请求,无条件 | 每次请求都要发一遍,所以必须短 |
| 手册 | 技能目录(ctx.skills.register) |
agent 判断相关时才读正文 | 只在用得上时付 |
分成两条路是整件事的关键。合成一条的话只有两个选择:把手册塞进每次请求(贵,而且长上下文里的规矩反而更容易被忽略),或者把红线做成按需加载(那它就不是红线了——agent 想不起来读的时候等于不存在)。
几个值得知道的行为:
- 同名时的顺序是「项目 > 公司 > 个人」:项目里的(
.dsh/skills、AGENTS.md旁边那些)赢过公司下发的,公司下发的赢过你~/.dsh/skills里自己写的。正好是想要的语义——公司规矩压过个人习惯,具体项目的特殊要求又能压过公司的通用规矩。这是 dsh 给运行时注册的技能定的位次,插件改不了。 - 拉不到的时候保持上一次的内容,不清空。 红线绝大多数是「不许做什么」,Aloof 一挂就把约束全撤了,比按一份稍旧的规矩走危险得多。这时
/aloof会明说「按几点几分的内容在跑」。 - 换票之后立刻重拉,不用等下一轮(换票是你刚做完的动作,会马上去看有没有生效)。
- 启动后第一次同步可能赶在票加载好之前,所以失败会 5 秒后重试、每次翻倍、封顶五分钟。不这么做的话每次开机后前五分钟都没有团队规矩,而
/aloof还会说「已连上」——一个看起来正常、实际没生效的窗口。 - agent 改不了这些东西。 它能提议(见下一节),但改不动生效的那一份:服务端只放开了「提交提案」这一个写操作,而提案要真人管理员接受才生效。能让模型改自己要遵守的红线的话,红线就只是一段可以被绕过的建议。
想知道这台机器现在收到了什么,敲 /aloof,或者点右下角那颗按钮。
把知识交回团队
摸清了一件事、或者踩了个坑,跟 agent 说一句就行:
把刚才那套发版检查存成团队手册
它会调 aloof_contribute 交上去。然后:
- 进公司 Aloof 的待确认列表,这时候还没生效。
- 管理员在网页上看到「谁提的、为什么、改前改后长什么样」,接受或者拒绝。
- 接受之后,团队里每个人的 dsh 在下一轮同步时拿到。
/aloof 里会有一行告诉你「你交回的 N 条还在等确认」。
几点值得知道:
- 它只在你明确要求时才提,不会自作主张替你沉淀。提得太多,审的人就不看了,那这条路就废了。
- 想补充已有的那份手册,用它现在的名字(
/skills里看到的那个)。同名就是「改」,管理员会看到改前/改后的对照。 - 提上去不等于生效。 如果 agent 跟你说「已经加好了」,那是它说错了——去
/aloof看那一行,或者问管理员。 - 红线(
rule)门槛高得多:它每次请求都会塞进所有人的系统提示,所以只有真正全局的硬约束才配得上。拿不准就提成手册。
有哪些工具
| 工具 | 干什么 |
|---|---|
aloof_whoami |
连上了吗、Aloof 认出我是谁。连不上会直接说是哪一种问题 |
aloof_devices |
我有哪几台机器连着,每台最后一次什么时候、从哪个 IP 连的 |
aloof_contribute |
把一份做法/坑交回团队,等管理员放行(唯一的写操作) |
发新票和吊销都只能在网页上做,这里故意没有对应的工具——不能让模型给自己续期或者增发。同理,接受提案也只能在网页上做:允许 agent 提的前提就是它提的东西不生效,两头都放开的话这个前提就没了。
/aloof:不问模型,直接看通没通
/aloof
打出这台 dsh 连的是哪个地址、票的前几位、Aloof 认出你是谁。
为什么要有它、而不是让人问模型:「连上了吗」是个关于环境的事实问题,答案必须确定。走模型的话它可能不调工具就凭上下文猜、可能把报错重新表述一遍、也可能因为工具报错就开始自己排查。这条命令由插件直接执行,不经过模型——所以它的输出可以当证据用。
连不上的时候它会一起给出排查顺序。先看它打出来的那个地址对不对:票带着地址走,从线上页面生成的票拿到本机来用,它会去连线上那台;这种情况报的是 fetch failed,看着百分百像网络问题,能让人(和 AI)白查一轮 DNS 和证书。
右下角那颗按钮
dsh 界面右下角常驻一颗带 logo 的圆钮,角上一个红绿灯:绿的通着、红的断了、灰的正在看。按住能拖到任何顺手的位置(记在这台机器的浏览器里,卡片会朝屏幕中间开,不会开到视口外)。
点开是一张卡,两段:
- 状态:连的哪台、认出你是谁、用的哪张票;断的时候把报错和排查顺序一起摆出来。
- 凭据:这张票叫什么(默认
ALOOF_TOKEN)、从哪来、换票、清票。粘一张新的按保存,灯当场就重新验,不用重启 dsh、也不用手编 YAML。
「从哪来」这一行别当装饰看:dsh 取票的优先级是启动时的环境变量 > .credentials.yaml > 各种 .env,而环境变量那层启动时冻结、且压在文件之上。所以同名环境变量存在时,在这儿改票是不生效的——现象和「插件把票缓存住了」一模一样,为这个误判查过一整轮。这时卡里会直接说清:先去启动 dsh 的那个终端 unset,再重启。
它和 /aloof 读的是同一个状态(经插件自己的 GET /dsh-aloof/status),所以两处不会打架。区别只是你要不要开口:红绿灯是你没问它也在看的那一路——票会在别处失效(被吊销、改密连带、到期),30 秒复查一次,灯自己就变了,不用等到某次调用失败才发现;票在别处被改了(dsh 设置页、直接编辑文件、另一个窗口)也会立刻跟上。不想点开的话,鼠标悬上去也能看到同一句话。
票不会到浏览器里。页面问的是本机插件,插件在 Node 那边拿票去问 Aloof,只把结论发回页面。票留在 $DSH_HOME/.credentials.yaml,不进 devtools、不进浏览器扩展、也不在 XSS 的射程内。换票那条路同理:值只往里写、从不读回来,输入框是 password 且每次都是空的,已配置只用占位符提示(和 dsh 自己的模型设置页一个规矩)。
改票走的是宿主的 /api RPC(connection.api.credentials),不是插件自己开的 HTTP 路由。因为 webServer.register 上没有 CSRF / Origin 检查,而 dsh 就听在 127.0.0.1:自开一条写路由,等于让你访问的任何一个网页都能跨源改掉你的票。/api 那条路上有 sec-fetch-site 拒跨站、Origin 同源校验、强制 application/json(挡住不触发预检的「简单」POST),凭据三个方法还额外钉死 loopback。
可选配置
平常一行都不用写。真需要时在 cordis 配置里给:
| 键 | 默认 | 什么时候动它 |
|---|---|---|
tokenEnv |
ALOOF_TOKEN |
一台机器上要接两套 Aloof,各用一个凭据名 |
timeoutMs |
20000 |
服务端在很慢的网络后面 |
baseUrl |
空(跟着票走) | 反向代理、内网另有入口,网页地址和 dsh 能到达的地址不是同一个。填了以它为准 |
连不上的时候
报错都是照着「哪一种问题」写的,直接照做:
| 看到 | 意思 |
|---|---|
没配 ALOOF_TOKEN |
票没放进环境变量,也没写进 .credentials.yaml |
…那串票不带地址 |
只粘了 @ 前面那半截,回网页重新整串复制 |
连不上 Aloof(…) |
地址通不了:服务没起、端口不对、或者不在同一个网里。先核对括号里那个地址是不是你想连的那台 |
令牌无效 |
票不对(粘漏了、或者那台 Aloof 上没有这张) |
已被吊销 |
有人在网页上把它吊了 |
改密码时被一起吊销 |
你改过 Aloof 的密码,回网页重新生成一张 |
已过期 |
这张票当初设了有效期,到了。重新生成一张(可以选永不过期) |
Aloof 403 |
票是好的,但这个接口对接入令牌关着(写操作默认关闭) |
跟服务端的关系
插件调的是 Aloof 的 HTTP 接口,两边是锁步演进的:服务端接口一改,这里可能要跟着改。服务端接口契约以那边导出的 openapi.json 为准。
这个插件整份 index.js 里没有一句 import,是故意的:defineTool 这些都在 @deepseek-ai/dsh-* 包里,用了就把插件钉死在某个 dsh 内部版本上,而且插件被软链进 profile 时 Node 会从真实路径往上找 node_modules、找不到那些包。所以这里直接手写 JSON Schema、只用 ctx 上的服务。对一个只做 HTTP 转发的薄壳来说,代价只是少了编译期类型推导。
client.js(浏览器半部)走的是同一条路,而且理由更硬:dsh 的 client 产物形状是固定的——一个包在 window.__ModuleLoader__.load({ id, factory }) 里的 CJS 闭包工厂,react 之类的「平台模块」由宿主的 require 表提供、不打进包。官方包用 tsdown 的 preset 生成这个形状;这里直接手写产物,因为一颗悬浮按钮除了 react 什么都不需要,而引那条流水线换来的是 tsc + tsdown + 一串 @deepseek-ai/dsh-client-* 开发依赖——那些包在 npm 上的版本比宿主实际跑的旧,等于照着旧类型写、跑在新宿主上。代价是这份文件里不能用 JSX 和 CSS Modules,用 React.createElement 和一段注入的 <style> 顶替。
所以这个包没有构建步骤:npm pack 出来的就是源码本身。改 client.js 之后重启 dsh 即可,不用 build。
还没有评论,来写第一条。