dsh-weknora
Tencent/WeKnora把 WeKnora 知识库接入 dsh 的四个只读工具:列出知识库、混合检索原文片段、按顺序还原单篇文档,以及直接取用 WeKnora 自己带引用的 RAG 或 ReAct agent 回答(含可续聊的 session id)。
COM 驱动真实 Microsoft Office 实例的 DeepSeek Harness 插件:经 OfficeMCP 操作运行中的 Excel/Word——VBA 宏、透视表、活公式重算、文档深度排版;含会计场景(复式记账分录 + 借贷平衡校验、SUMIF 科目总账)。
Windows 本机真实 Office 自动化插件,已完成契约、任务级、故障注入和进程泄漏回归;真实 Office 测试需要在目标机器上执行。
COM 驱动真实 Office 实例的 DeepSeek Harness(DSH)原生插件。区别于列表内所有文件级读写的 Office 插件,本插件通过本机 OfficeMCP 的 COM 通道直接操作运行中的 Excel / Word:VBA 宏、透视表、公式重算、已有文档深度排版,外加一套会计旗舰场景(分录 → 总账 → 透视表 → 三表)。
现有 Office 插件 · 本插件 文件级读写(openpyxl/exceljs/docx) · COM 驱动真实实例 写死数值、无法重算 · 活公式链,Application.Calculate 取计算后值 无 VBA / 无透视表 · 真实宏运行 + PivotCache/PivotTable 透视表
应用发现 / 启动:office_apps · office_launch
Excel 读写:excel_new(新建工作簿并把 Excel 切到可见,工作簿直接交到用户手上)· excel_open · excel_read_range · excel_write_range
Excel 公式 / 宏:excel_formula_set(Range.Formula 原样写入)· excel_recalc(强制重算取活值)· excel_vba_run(运行已有宏)
透视表:excel_pivot_create(PivotCache/PivotTable)· excel_pivot_refresh
Word:word_open(打开读结构)· word_edit(全文查找替换,真实 Find.Execute + wdReplaceAll,只改文本、保留原文格式)
会计旗舰(底层):excel_journal_post(写分录 + 借贷平衡校验,借≠贷标红)· excel_ledger_gen(日记账 → 科目总账,聚合借贷 + 余额公式)
底层工具各管一段、各自开关文件。任务级工具把一整套业务步骤收进一次调用——Agent 不必自己编排几十个 COM 步骤,也不会中途留下一个只写了一半的工作簿。
工具 · 干什么 office_generate_accounting_report · 一份交易数据(CSV / 内联数组)→ 日记账 + 借贷平衡 + 科目总账 + 透视表 + 强制重算 + 读回校验 → 落盘。模板永不被改动 office_check_workbook · 动手前的只读体检:抓出所有求值出错的公式、外部链接、被保护的表、未保存改动,给 safe_to_edit 与逐条理由 office_replace_document_terms · Word 多术语批量替换(合同里的甲方乙方、金额日期…)。先全部计数再统一替换,保留原文格式 office_update_monthly_report · 按期间更新月度报告:只替换该期间的行,其他期间一行不动;同期间重跑是替换不是追加 office_apply_template · 把模板的外观(字体/颜色/边框/数字格式/列宽行高/冻结窗格)套到目标工作簿,数据一行不动 office_prepare_management_summary · 从数据表出「管理层摘要」:按维度聚合 + TopN + 占比,有「期间」列时可给上期→本期对比
任务级工具同样遵守执行模式与安全动作约定:都支持 mode=preview(真预演、不落笔), 结果写 output 而不是改你的原文件,覆盖已存在的输出要显式 overwrite:true。 office_check_workbook 是只读的,不暴露 mode——没实现 dry-run 差异的工具暴露 mode 只会误导。
这一族就是 Office × KnowLP 的组合面。 Office 知道「这是什么文件、能不能真被打开」,KnowLP 知道「它在讲什么」,合起来让 Agent 在陌生环境里登记一棵目录树、自己长出分类体系——不是给 Obsidian vault 加功能。
工具 · 读/写 · 干什么 kb_scan_root · 只读 · 登记任意目录树:扩展名 + magic 双判据分类;认不出的一律进「不分类」桶并显式给原因码(绝不猜)。max_deep>0 时按配额只读开簿深扫 Excel 结构 kb_taxonomy_get / kb_taxonomy_set · 只读 / 变更 · 自长分类学:增 / 改名 / 合并 / 删;每次变更自带漂移审计(重叠 / 环 / 悬空) kb_classify · 只读 · 按分类学规则归档;use_semantics=true 时走 KnowLP 语义缝(见下) kb_scheme_propose · 只读 · 影子目录方案(label 链 + 原文件名,重名加指纹前 8 位)——只出方案,不碰任何文件 kb_annotate · 变更 · 写标注(annotations.jsonl);写回原文件默认关,write_back && confirm 双闸 kb_verify · 只读 · 核账:标注可解析 / 源文件指纹吻合 / 影子方案与磁盘一致 kb_write_back_office · 变更 · 对单个 Office 文件写删 kb_* 自定义文档属性——只碰属性面板,绝不碰单元格 / 正文 / 幻灯片
组合面在哪 —— KnowLP 语义缝。 kb_classify(use_semantics=true) 把待归类的资料交给 KnowLP 检索,走三级梯子,每一级都如实回报走到了哪:
approved 经 ctx.tools.execute 审批派发(正路)
direct 拿不到审批通道时降级直连(仅降级用)
unavailable KnowLP 不可用 → 如实声明 + next_call_suggestion两处细节值得记住,都是踩出来的:工具名按注册表里的实际名字解析(MCP 接入下是 mcp____* 形状,写死 knowlp_search 必然查不到);断链 / 失败 / 不可用一律显式降级并带回错误,绝不假装做了语义判断。
还有一条跨仓纪律:指纹公式(sha256(rel_posix \0 size \0 mtime_ns))、词表(本仓 KB_* ↔ KnowLP 侧 pool_scan.*)、命名空间(按 root 分域,禁止跨根合并裸条目)——任一边改,同一提交里改另一边。
细节见 docs/kb.md。
14 个会改动文档的工具都接受 mode(8 个底层写工具 + 5 个任务级 + kb_write_back_office)。缺省按有无 `path` 推断,所以老调用一字不改。
mode · 干什么 · 什么时候用 preview · 真经 COM 打开文件看真实状态,回报拟变更,不落笔 · 动手前先确认「会改成什么样」 managed · 打开 → 修改 → 校验 → 保存 → 关闭 · 有明确文件路径的批量作业 attached · 操作当前运行实例,不主动关闭、不自动覆盖 · 用户正开着工作簿,你想在上面干活
preview 不是参数回显,是真的打开文件读:回报真实工作簿身份、真实目标地址(如 $A$2:$B$2)、 会被盖掉的非空格数量、完整算好的总账、命中次数与首处上下文。跑完文件 mtime 一字未动 (npm run test:leak 用例 D 会断言这一点)。
高风险参数必须显式——默认值一律取安全侧:
参数 · 含义 · 缺省 save · 是否落盘 · managed 为 true,attached 为 false close · 是否关闭 · managed 为 true(只关自己开的那份);attached 下无效 overwrite · 允许覆盖既有内容 · false,涉及覆盖时不给就返回 OVERWRITE_NOT_CONFIRMED confirm · 高危动作确认位 · false,excel_vba_run 不给就返回 RISKY_OP_NOT_CONFIRMED refresh_external_data · 一并刷新外部数据连接 · false(它会走网络)
重点保护:用户正在编辑的工作簿永不被代关、也不被代存——即使你给了 path,只要那份文件已经 被打开,插件会按附着处理并在信封里报 attached_existing_open(否则 Workbooks.Open 拿到的是同一个 对象,收尾时 Close(False) 会把用户的窗口关掉并丢弃其未保存修改)。
会计旗舰链路可以反复跑,第二次不会把工作簿搞坏:
excel_journal_post:拿这批分录与账簿末尾逐条对账,命中同一批默认跳过不重复入账(idempotent_skip:true,信封如实报 changed:false)。真有两笔一模一样的分录用 on_duplicate:"append";改正上一次入错的分录用 on_duplicate:"replace"(位置覆盖并复位标红)
excel_ledger_gen:总账固定落在默认表「总账」并原地刷新,不会每次新建一张;重算结果与表上一致时直接跳过。按表头判归属——是工具自己的产物就永不索要 overwrite,是用户数据才要
excel_pivot_create:字段先对着表头校验(缺字段报 SOURCE_RANGE_INVALID,且不会留下孤儿工作表);目标表与表名都取确定性名字,第二次跑复用刷新同一张(idempotent_reuse:true)而不是再堆一张
所有工具返回同一信封,Agent 只读 ok / error_code / retryable 就能分支,不必解析 21 种各不相同的返回形状。
// 成功
{ "ok": true, "operation": "excel_write_range", "mode": "managed", "preview": false,
"changed": true, "saved": true, "verified": false, "warnings": [],
"output": { "range": "A1:B2", "written": true } }
// 预演
{ "ok": true, "operation": "excel_ledger_gen", "mode": "preview", "preview": true,
"changed": false, "saved": false, "warnings": ["preview:未做任何修改"],
"output": { "rows": [ { "account": "办公费", "debit": 1200, "credit": 0 } ] } }
// 失败
{ "ok": false, "operation": "excel_ledger_gen", "mode": "managed", "preview": false,
"error_code": "OVERWRITE_NOT_CONFIRMED", "error": "…", "retryable": false, "partial_changes": false }changed / saved / verified 分开报:改了 ≠ 落盘了 ≠ 核对过。附着模式(无 path)改的是用户在用的工作簿,saved 为 falseverified 只在有校验步骤的工具上为真:excel_recalc(重算后读回活值)、excel_journal_post(借贷平衡)error_code:CHANNEL_UNAVAILABLE(通道断,可重试)/ COM_OBJECT_DEAD(通道里的对象成僵尸,可重试)/ SAVE_FAILED(保存失败,可重试)/ FILE_LOCKED(文件被占,可重试)/ MODE_INVALID / OVERWRITE_NOT_CONFIRMED / RISKY_OP_NOT_CONFIRMED / VBA_FAILED / MISSING_PARAM / DEGRADED / NO_ACTIVE_DOCUMENT / APP_UNAVAILABLE / EMPTY_SOURCE / SOURCE_RANGE_INVALID / OPEN_FAILED / UNKNOWNpartial_changes:失败后工作簿是否仍停在被修改状态。只有 Python 知道(自己开的那份收尾会 Close 丢弃 → false;附着模式改到一半 → true)COM 走的是本机 OfficeMCP(workbuddy Py3.13.12 自带),officemcp 的 Officer.Excel 是 Dispatch 出来并永久缓存的实例,默认不可见。
~/.dsh-office-com/office.lock singleton),宿主退出时回收TerminateProcess,atexit 不跑),而 Excel 只要还挂着工作簿就不会自己退 → 会留下不可见的孤儿 EXCEL.EXE(占内存 + 占文件锁)。插件在退出时做收尾,判定分两层:{镜像名: [pid]} 基线,退出时"当时不存在、现在在跑"的 pid 才是我们起的。(早先按镜像名判,导致起始时只要有一个 Excel 在跑,之后自己创建的也永远认不出来、永不回收)
Visible 的,进保护集不动(可见 = 有人在看)。先 Quit() 送一程,送不走再按 pid 强制结束,落地前复核"pid 背后还是同一个 exe"防误杀
Quit() 后进程还在退的那段时间里,对象会变成"进程活着、每个属性都抛异常"的僵尸。这类实例取不到 pid 做可见性判断,正因如此才更要靠上面的 pid 归属来认——否则它既不可见、又永不回收,会一直占着文件锁
excel_new 会主动把实例切到可见(新建的工作簿得让用户看得见、够得着),因此它建出来的实例不参与回收npm run test:leak(需本机 Office,跑前请先关掉 Excel)。四个用例:managed 链路退出后零残留 / 可见实例不被误杀 / excel_new 的实例存活 / preview 真开文件后零残留且文件 mtime 不变按用途挑最短的一条路径。全部可直接复制给 Agent 当示例用。
① 写值 → 写公式 → 重算读活值(底层三件套)
{ "tool": "excel_write_range", "range": "A1:C4",
"value": [["科目","月份","金额"],["办公费","1月",100],["办公费","2月",150],["差旅费","1月",80]] }
{ "tool": "excel_formula_set", "range": "D1", "formula": "=SUM(C2:C4)" }
// 强制重算后读回来的是**计算后的活值**,不是写死的数字
{ "tool": "excel_recalc", "range": "D1" } // → { "value": 330 }② 先预演、再动手(安全动作模式的正用法)
// 第一步:问"会改成什么样",真开文件看,一个字节都不改
{ "tool": "excel_write_range", "path": "C:/book.xlsx", "range": "A2:B2",
"value": [["x","y"]], "mode": "preview" }
// → { "preview": true, "changed": false, "output": {
// "target": { "sheet": "Sheet1", "address": "$A$2:$B$2", "shape": "1x2" },
// "overwrite_non_empty": 2, "sample_before": ["办公费", "100.0"] } }
// 第二步:确认无误再真动手
{ "tool": "excel_write_range", "path": "C:/book.xlsx", "range": "A2:B2",
"value": [["x","y"]] }③ 一条调用出整套会计报表(任务级)
{ "tool": "office_generate_accounting_report",
"source": "transactions.csv", // 或直接给分录数组
"output": "report-2026-08.xlsx",
"template": "monthly-template.xlsx", // 可选,模板不会被改动
"period": "2026-08" }
// → 日记账 + 借贷平衡校验 + 科目总账 + 透视表 + 强制重算 + 读回校验,一次调用
// → 摘要:"office_generate_accounting_report · 已修改并落盘 · posted=6 · 借贷平衡 · 已校验"④ 动工作簿之前先体检
{ "tool": "office_check_workbook", "path": "C:/book.xlsx" }
// → { "safe_to_edit": false, "formula_error_count": 2,
// "formula_errors": [{ "sheet": "数据", "cell": "$D$2" }, { "sheet": "数据", "cell": "$E$2" }],
// "flags": ["有未保存改动", "有 2 个公式求值出错", "有工作表被保护"] }⑤ Word 合同批量替换术语
{ "tool": "office_replace_document_terms", "path": "C:/contract.docx",
"terms": { "甲方": "丙方", "乙方": "丁方" } }
// → 先全部计数再统一替换;只动文本、保留原文格式
// → { "terms": [{ "find": "甲方", "replace": "丙方", "count": 3 }, ...], "total_replaced": 6 }⑥ 同一份报表反复生成,第二次不重复入账
{ "tool": "office_generate_accounting_report", "source": "tx.csv",
"output": "report.xlsx", "overwrite": true }
// 底层用 excel_journal_post 时更直观:同批次重跑会返回
// { "output": { "posted": 0, "idempotent_skip": true }, "changed": false }一份脱敏交易数据、一次 preview、一次 managed、一次异常分录、一份结构化验证报告—— npm run test:flagship 就是这段(需本机 Office):
npm run test:flagship它跑的是完整业务链,且整条跑两遍:
脱敏交易数据(6 条分录 / 5 个科目)
↓ preview 真开文件读真实状态,mtime 一字未动
↓ managed 入账 借贷平衡校验(借≠贷标红)
↓ 生成科目总账 余额=借-贷 活公式,重算后读回实际值
↓ 创建透视表 真实 PivotCache/PivotTable,按科目汇总借贷
↓ 刷新透视表
↓ 全链重跑 同批次跳过 / 总账跳过 / 透视表复用 / 工作表数与数值逐格一致
↓ 异常分录测试 不平衡被检出 + 标红 + 非致命告警(不是失败)
↓ replace 改正 位置覆盖 + 标红复位 + 总账只计一次
↓ 结构化验证报告 26 项指标逐条 PASS/FAIL没有 OfficeMCP 时会发生什么:插件照常加载,工具调用返回带 error_code 的友好报错 (DEGRADED),不会炸 DSH 插件树。
命令 · 层级 · 需要 Office npm test · 工具注册 + 信封/模式/schema 契约(纯函数,CI 跑) · 否 npm run test:e2e · 协议链路 + 底层工具编排(smoke / headless) · 是 npm run test:flagship · 任务回归:会计链路跑两遍的重复正确性 · 是 npm run test:tasks · 任务级工具:6 个 v0.4 工具的行为与安全闸(48 项) · 是 npm run test:faults · 故障注入:保存失败 / 只读 / 宏失败 / 断线重连 / 类型边界 · 是 npm run test:leak · 跨进程的 Office 进程残留与文件锁 · 是
CI 只跑第一项与语法检查——其余都要真实 Office,跑前请先关掉 Excel。
test:faults不是走过场:它上线第一轮就抓到两个真缺陷——只读工作簿下Save()
在DisplayAlerts=False时静默不写而信封报saved:true,以及非法区域引用报UNKNOWN。
故障用例的价值就在这里:只在真机上才暴露的路径,信封最容易说谎。
# npm 官方通道
dsh plugin add @eqman00003/dsh-office-com
# 或 GitHub 源
dsh plugin add "github:wly8691-jpg/dsh-office-com#main"