dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DSH 插件:包装钉钉官方 dws CLI,读写钉钉 AI 表格,提供 10 个钉钉 AI 表格工具(9 个 `dingtalk_aitable_*` 加 1 个 `dingtalk_sync_job`)、设置面板与定时 CSV 导出。
让钉钉 AI 表格的数据,流进 DeepSeek Harness Read and write DingTalk AI Tables from DeepSeek Harness
= 22.19">
= 1.0.6">
简体中文 · English
一个 DSH 插件,把钉钉 AI 表格(多维表 / aitable)接到 DeepSeek Harness 上。装完即得 **10 个 dingtalk_aitable_* 工具和一个「钉钉文档」设置面板——在 Harness 里直接发现 Base、 读数据表、按条件查记录、批量写回、导出带 BOM 的中文 CSV,并支持定时导出**。
架构:包装钉钉官方 dws(dingtalk-workspace-cli)执行 dws aitable ..., 不直连 REST。这与钉钉官方 OpenClaw connector 同构——它自己也不直连 REST, 而是注入 DWS_CLIENT_ID/SECRET 后调 dws(实测,2026-09-14 直读其仓库确认)。
设计取向:只读优先 + 写门禁。写与删除默认全关,删除还需每次逐条确认。 宁可多一步确认,也不给"模型幻觉 + 权限过大"留组合空间。
⚠️ 这不是一个自包含插件——它依赖外部 CLI dws。装之前请先读 安装与前置条件,三层依赖(Node / dws / 钉钉侧授权)缺一不可。
上图为面板结构示意(非截图)。设置 → 「钉钉文档」(order 22,排在「IM机器人」之后),
内含三区:账号绑定 / AI 表格清单 / 定时导出。
真实截图待补,清单与打码要求见 docs/images/README.md。
能力 · 入口 · 写门禁 自检(版本 / 授权 / 命令面 / RPC 端点) · dingtalk_aitable_diagnose · — Base 文件:查 / 建 / 改 / 删 · dingtalk_aitable_base · 删需双锁 数据表:查表与字段 / 建 / 改 / 删 · dingtalk_aitable_table · 删需双锁 字段:查 / 建 / 改 / 删 · dingtalk_aitable_field · 删需双锁 记录:按 ID 或条件查(可全量拉) · dingtalk_aitable_record_query · 无 记录:批量写 / 改 / 删 · dingtalk_aitable_record_write · 写 / 删门禁 受控直通(注册表内任意子命令) · dingtalk_aitable_raw · 危险命令需双锁 候选 Base 发现 + 可读性实测 · dingtalk_aitable_scan · 无 全量导出 CSV(带 BOM,覆盖同名) · dingtalk_aitable_export_csv · 无 定时导出任务:建 / 列 / 删 / 启停 / 立即执行 · dingtalk_sync_job · 无
双锁 = allowDelete: true(配置层)+ confirm: true(逐次,表示已获同意)。
这三条最容易被误解,先看这里再看细节。
1. 定时器跑在 DSH host 进程内——DSH 关着时不执行,重启后重算下次触发点。不是云端定时。
2. 钉钉侧没有"列出全部 Base"的接口——扫描产出是「候选 ∪ 人工补录」,不保证穷尽,
且base search的hasMore: true是误报(游标翻页无效)。
3. 写与删除门禁默认全关——删除还需每次 confirm=true。本 README 中每条技术结论均标明来源档位:实测 / 官方原文 / 推测,并附取得日期。
· v0.1 手搓 REST(已废弃) · v0.2+ 包装 dws(当前) 端点来源 · 靠推断,标 pending · 官方 CLI 内置,已发布可用 鉴权 · 自己换 accessToken、缓存、刷新 · dws 自动管理(2h 自动刷新) 分页/错误码 · 自己实现 · dws 提供 --format json + 恢复闭环 正确性风险 · 高(路径与请求体都可能错) · 低(命令面有官方文档) 维护成本 · 跟钉钉 API 变更 · 跟 dws 版本
结论:v0.1 已废弃。改为包装 dws,消除了全部端点不确定性。
同时纠正了 v0.1 的三处硬错误(官方文档明确点名):
sheetId → 正确是 `tableId`(AI 表格的数据模型是 base/table/field/record)cells 的 key 必须是 `fieldId`(fldXXX),不是字段名完整版见 docs/安装与前置条件.md(含平台矩阵、解压依赖、组织级拦阻、一键核验清单)。
下面是速查版。
三层依赖,缺一不可:
层 · 要求 ① 本机运行时 · Node ≥ 22.19(本插件要求);DSH 0.1.2-alpha.4 ~ 0.1.5-alpha.1 ② dws CLI · npm i -g dingtalk-workspace-cli,版本 ≥ 1.0.6(实测基线 v1.0.61) ③ 钉钉侧 · 授权 + 开通「AI 表格记录读写」权限 + 把应用加为 Base 协作者(可编辑)★最易漏
npm i -g dingtalk-workspace-cli
dws --version # 必须 >= 1.0.6,实测基线 v1.0.61
npm root -g # 记下前缀,Windows 上要用它填 dwsEntry⚠️dws不是纯 JS 包——它带postinstall,安装时下载并解压一个平台原生二进制(约 25 MB)。
支持 Windows / macOS / Linux 的 x64 与 arm64 六个平台。
解压依赖:Windows 用系统自带powershell.exe,macOS / Linux 需要tar或unzip。
完整矩阵见 docs/安装与前置条件.md。
方式 A — 扫码登录(交互式)
dws auth login # 钉钉 App 扫码授权;token 自动刷新(access 2h / refresh 30d)
dws auth status # 确认已登录方式 B — 复用钉钉应用凭证(headless / 推荐给服务器)
# Windows
set DWS_CLIENT_ID=<你的 Client ID>
set DWS_CLIENT_SECRET=<你的 Client Secret>
# macOS / Linux
export DWS_CLIENT_ID=<你的 Client ID>
export DWS_CLIENT_SECRET=<你的 Client Secret>
dws auth login凭证优先级:--token>DWS_CLIENT_ID/SECRET> OAuth 加密存储。
凭证只走环境变量注入子进程,不进命令行参数(避免出现在进程列表里)。
这两件事缺一不可。dws报permission denied/ 403 时,九成是这里没做完。
# 从 npm 安装
dsh plugin --profile web add @yiyunet/dsh-dingtalk-connector
# 或从本地路径安装(开发时)
dsh plugin --profile web add "<本仓库绝对路径>"
dsh --profile web --dump-config # 期望看到 dingtalk-connector 这一层然后重启 dsh web。
自带一个引导 CLI:
npx @yiyunet/dsh-dingtalk-connector install [--profile web] # 安装 + 核对配置层
npx @yiyunet/dsh-dingtalk-connector doctor # 体检:dsh / dws / Node / 授权状态cordis.patch.yml)配置项 · 默认 · 说明 dwsCommand · dws · PATH 上的命令名 dwsEntry · 无 · Windows 建议设:dws 的 JS 入口绝对路径。设了就用 node 直接 spawn,完全不走 shell,记录文本里的引号/&/\ · 才安全 timeoutMs · 60000 · 单条命令超时 clientIdEnv / clientSecretEnv · DWS_CLIENT_ID / DWS_CLIENT_SECRET · 凭证环境变量名 allowWrite · false · 开关:create / update allowDelete · false · 开关:delete(独立,且每次还要 confirm=true) maxBatch · 30 · 单次批量上限(官方 connector skill 规定 ≤30) exportRoot · 无 · 可选:把导出落盘限制在指定目录内 defaultExportDir · 无 · 可选:面板「选表后自动填充」的目录;不设则只填文件名 scanConcurrency · 4 · 扫描可读性实测的并发数(防限流)
dwsEntry(Windows 上的安全要点)Windows 上 .cmd 必须经 shell 启动,而 shell 会解释参数里的元字符——记录文本含引号或 &/| 时可能被注入。插件对此硬拒绝(返回 ARG_UNSAFE 并给出指引)。
要彻底解决:把 dwsEntry 指向 dws 的 JS 入口,例如
dwsEntry: '<npm root -g>/dingtalk-workspace-cli/bin/dws.js'先用 npm root -g 确认你本机的实际前缀(Windows 上分隔符用 ``)。
工具 · 作用 · 门禁 dingtalk_aitable_diagnose · 自检:dws 版本 / 授权状态 / 已注册命令 · 无 dingtalk_aitable_base · Base 文件:list / search / get / create / update / delete · delete 需双锁 dingtalk_aitable_table · 数据表:get(必传 `tableIds` 才返回字段)/ create / update / delete · delete 需双锁 dingtalk_aitable_field · 字段:get / create / update / delete · delete 需双锁 dingtalk_aitable_record_query · 读记录(按 ID 或条件查;all=true 可全量拉) · 无 dingtalk_aitable_record_write · 写记录:create / update / delete · write / delete 门禁 dingtalk_aitable_raw · 受控直通:注册表内任意子命令 · 危险命令需双锁 dingtalk_aitable_scan · 候选 Base 发现 + 可读性实测 · 无 dingtalk_aitable_export_csv · 全量导出 CSV(带 BOM,表头用字段中文名,覆盖同名) · 无(受 exportRoot 可选约束) dingtalk_sync_job · 定时导出任务 create / list / remove / enable / disable / run-now · 无
双锁 = allowDelete: true(配置)+ confirm: true(逐次,表示已获同意)。
在 设置 里出现一级项 「钉钉文档」(order 22,排在「IM机器人」之后)。
区 · 内容 账号绑定 · 显示 CLI 版本、企业名 / 用户名、access token 到期、凭证来源、写门禁状态;含扫码绑定会话(二维码 / 深链 / 原始输出 / 组织拦阻提示) AI 表格清单 · 输入关键词 → 扫描(候选发现 + 可读性实测)→ 每个 Base 标「可读/不可读」→ 展开看数据表 → 单选一张表 → 复制 Base ID / Table ID 定时导出 · 选中的表 + 输出路径 + 每天/每周 + 时间 → 新建任务;任务表显示下次/上次结果,支持立即执行 / 启停 / 删除
lib/client.js 是 npm run build 的构建产物:esbuild 把 plugin-src/client/impl.mjs 打成 IIFE,再把手写的装载器 wrapper plugin-src/client/index.mjs 接在其后,产出官方客户端模块系统同形的结构:
window.__ModuleLoader__.load({
id: '@yiyunet/dsh-dingtalk-connector',
factory: (require) => { /* ... */ return module.exports }, // 契约:{ name, inject, apply }
})require('react');打包时 react / react-dom 标记为 external(运行时解析)ctx.connection.rpc.call('/api', 'dsh-dingtalk-connector', { method, payload }, signal)ctx.connection.fetch.register({ path: '/api/dsh-dingtalk-connector', methods:['POST'], ... })(见 plugin-src/host/rpc.mjs)connection 必须走 scoped 注入,绝不能写进 injectconnection 服务只存在于 web 平面(由 packages/bundle/web-app 挂载 @deepseek-ai/dsh-client-connection)。
如果把它写进本插件的顶层声明 inject = ['tools', 'connection'],那么:
在 headless / tui 等没有 web 连接的 profile 里,整个插件会永远停在 `inactive` ——
连那 10 个 dingtalk_aitable_* 工具会一起消失。因为本插件结构上分两半:工具注册(宿主平面,任何 profile 都该有)与 面板 RPC 端点(天然 web-only)。 正确写法是把后者放进子 fiber:
export const inject = ['tools'] // ← 保持不动,只声明真正必需的
ctx.inject(['connection'], (rpcCtx) => { // ← 可选依赖:就绪才执行,无此服务则静默不执行
const disposeRpc = registerConnectorRpc(rpcCtx, { ... })
rpcCtx.effect(function* () { yield () => disposeRpc?.() }, '…rpc endpoint')
})