dsh-data-table
v0.2.0-alpha.9
Published
Univer spreadsheet terminal (Table): Agent and user edits share revisioned workbook operations through the official right Sidebar + CSV export
Readme
dsh-data-table
Univer 表格终端(DeepSeek Harness 插件):agent 产出的结构化数据直接渲染为可交互表格,通过 DSH 官方右侧 Sidebar 的「Table」标签打开,可编辑、切换 workbook,并从 Univer 顶部菜单导出 CSV / Excel。
用法
先调用 workbook_catalog 明确选择或创建工作簿,再用 workbook_edit 写入数据:
{
"workbookId": "sales-2026",
"baseRevision": 12,
"versions": [{
"summary": "补充 6 月营收",
"intent": "写入 6 月营收",
"commands": [{
"type": "append_rows",
"sheet": "营收",
"name": "月度营收",
"columns": [
{ "key": "month", "label": "月份", "type": "date" },
{ "key": "revenue", "label": "营收", "type": "currency" }
],
"rows": [{ "month": "2026-06-01", "revenue": 1280000 }],
"meta": { "source": "sales_db.orders", "notes": "按自然月汇总" }
}]
}]
}workbookId 必须是已明确选择的工作簿 id;不会隐式创建或切换到 main。从 DSH 官方右侧 Sidebar 的 guide 中选择「数据表格」即可打开工作台。单个 sheet 支持最多 100000 条数据行 × 50 列;超过 1000 行的 append_rows 会在同一个 Version 内自动拆分为多个可回放 Step。批量结果落表优先用 import_file 或 update_cells + sourcePath(一次命令 + 一个本地文件路径),不要逐格内联 values。
架构
| 面 | 落点 | 说明 |
|---|---|---|
| Host | workbook tools(ctx.tools.register) | workbook 生命周期、范围读取、细粒度修改、revision 与操作历史 |
| Host | WorkbookManager | Univer snapshot 持久化、轻量 index、append-only Version/Step 操作日志、原子写入、进程内 mutation queue;存储根目录为 <dshHome>/storages/data-table |
| Host | /data-sheet/workbook/:id/operations | Agent/用户共享的 operation 接口;支持 baseRevision 和幂等 opId |
| Host | /data-sheet/workbook/:id/changes | 浏览器投影同步的漏操作补偿接口 |
| Host | /data-sheet/workbook/:id/collaboration | 自研 SSE 协同会话:广播已提交操作、ACK 后续补偿和断线重连 |
| Host | /data-sheet/workbook/:id/presence | 临时选区/焦点 presence 写入入口;只广播,不产生 revision |
| Host | /data-sheet/workbook/:id/history/diff | 两个可重放 revision 之间的 cell/sheet/structure 差异 |
| Host | /data-sheet/workbooks.json | 浏览器顶部 workbook 选择器使用的只读目录 |
| Host | webServer 路由 /data-sheet/univer-client.js | 按需提供引擎 chunk(lib/univer-client.js) |
| Host | webServer 路由 /data-sheet/xlsx-export-worker.js | 按需提供 XLSX/CSV 编码 Worker(lib/xlsx-export-worker.js) |
| Host | webServer 路由 /data-sheet/xlsx-import-worker.js | 按需提供 XLSX 解析 Worker(lib/xlsx-import-worker.js) |
| Host | /data-sheet/workbook/:id/view | 同源新 Tab 全屏工作簿页面;复用嵌入面板的同步和保存控制器 |
| Client | sidebarRightTabs.register | 注册官方右侧 Sidebar 的 data-sheet tab type 和 guide 入口 |
| Client | sidebar.right.pane.tab keyed slot | 按官方 tab type id 注入一个 pane frame;真正的 WorkbookWorkspace 由常驻容器提供(见下) |
| Client | workspace-host.ts | 常驻工作区容器:pane 只负责把它「领养」进自己的 frame,切走时移到屏幕外停放,所以切 tab / 切 session 不会重建 Univer 引擎或重新拉取快照 |
| Client | 顶部菜单栏 | 工作簿选择弹层、新 Tab 打开、版本管理、导入 / 导出分组菜单、右对齐保存状态;不超过 48px |
| Client | DataSheetExportPlugin | Univer 命令 + data-sheet:request-* 事件桥:Excel / CSV 导入,Excel / CSV / JSON 导出(不再注册 DSH「文件」页) |
| Client | DataSheetHistoryPlugin | 注册历史命令;版本管理从顶部菜单栏打开共享 Drawer |
| Client | UniverSheetsCrosshairHighlightPlugin | 复用 Univer 开源 Crosshair Highlight,按当前单元格高亮行列 |
| Host | /data-sheet/univer-api.json | 安全的 Univer Facade API 元数据目录 |
| Client | univer-overrides.css | Ribbon 顶部菜单项左对齐 |
数据流:Agent 调用 workbook tool,或用户在 Univer 中编辑 → DSH Host 校验 operation、检查 baseRevision、持久化并递增内部 revision → 浏览器投影通过 Univer Facade/原生命令应用 operation。revision 只是并发、排序和回放游标;用户操作的是 Version,Version 内包含有序 Step。用户连续编辑加入同一个 open Version,在空闲超过 2 秒(VERSION_IDLE_CLOSE_MS,与 Notebook 共用同一策略)、显式 close、Agent 开始写入或达到 50 个 Step 的安全上限(VERSION_MAX_STEPS)时封存,不再以“固定 10 个 Step”作为唯一边界;Ctrl/Cmd+S 会立即提交当前剩余 Step。选区、焦点和滚动不计 step。Agent 使用 versions 按语义分组:每个分组原子持久化为一个 final Version,组内 commands 是可逐步回放的 Step;commands 简写会把整组命令视为一个 Version。所有写入默认直接进入主线。Host 保留完整 operation payload 和可重放 checkpoint,用于任意版本/步骤预览、回放和恢复;恢复会创建 restored Version,并把不再位于当前谱系的后续记录标记为 superseded,从不删除日志。
Agent 工具
Agent 面严格收敛为 7 个聚合根入口:
| 工具 | 职责 |
|---|---|
| workbook_catalog | list / create / rename / delete 工作簿 |
| workbook_read | 读取 workbook、sheet、range 或指定 cells,并返回 revision 与 sheetName ↔ sheetId 映射 |
| workbook_find | 搜索 sheet 名、单元格值或公式 |
| workbook_edit | 唯一业务写入口;语义 versions 落为 Version,分组内 commands 为可回放 Step |
| workbook_history | list / diff / replay 历史查询和按 Step 回放 |
| workbook_restore | 恢复可回放 revision,产生新的主线 Version |
| univer_api | catalog / search / invoke 安全 Univer Adapter Registry |
版本管理抽屉与 Data Notebook 一致:相同的版本列表、状态徽章(未归档 / 已保存 / 已恢复 / 已被替代)、Step 展开、diff 渲染、保存版本弹层与恢复确认弹层;V12 是主标题,技术 revision(r48)作为副信息。「保存版本」即使没有内容变化也会把当前快照写成一个命名的 final Version;比较基准可切换(/history/diff 只接受 from <= to,客户端会把这对 revision 归一化,因此 diff 始终按旧 → 新渲染)。
并发写入无法自动 rebase 时,HTTP 与工具层返回统一的 409 体(ConflictPayload,与 Data Notebook 共用):baseRevision / currentRevision / conflictSet(调用方漏掉的 operation 摘要)/ nextAction / retryable;同时保留原有的 expectedRevision、revision 字段以兼容旧客户端。
workbook_edit 使用 Command 模式:Sheet 和 Cell 是 Workbook aggregate 内的目标,而不是独立
Tool 命名空间。它支持写值、清空、插删行列、Sheet 生命周期、追加结构化行、格式、合并、
冻结、尺寸、排序和超链接。推荐传入 versions: [{ summary, intent?, commands }],一个分组就是
一个用户可读的 Version,commands 只构成其内部 Step;兼容 commands 简写时,整组 commands
仍只形成一个 Version。Host 分配 revision/versionId,Agent 不可指定 versionId;可选的
agentRunId 仅用于关联同一 Agent 运行产生的多个 Version;dryRun: true 只执行内存预检,不写入历史。
Agent 工具契约
- 读回显:
workbook_catalog/workbook_read/workbook_find/workbook_history的render返回「摘要 + 真实 JSON」;workbook_read(kind=workbook)至少给出revision与每个 sheet 的name ↔ id、rowCount/columnCount/maxRow/maxColumn/nonEmptyCells。序列化超过 24 000 字符时,execute会把完整结果落到<storage root>/tool-output/*.json并在返回值中给出detailPath,render 只回摘要 + 截断预览 + 路径。 - range 语义:
range的startRow/endRow/startColumn/endColumn全部 0-based 且 inclusive。update_cells可以省略range(按startRow/startColumn,默认第 1 行第 0 列),也可以只给起点由values尺寸推导终点;当显式终点与values冲突时以values为准,并在warnings中说明修正后的范围。 - 结构命令:
insert_rows/delete_rows接受range:{startRow,endRow},insert_columns/delete_columns接受range:{startColumn,endColumn};也兼容index/startRow/startColumn+count,count缺省时按 range 跨度推导。 - 同批次自引用:
create_sheet未显式给 id 时由 Host 生成稳定 id,因此同一个version内可以先create_sheet再写入该 sheet;调用方仍可用sheet/sheetId指定显式 id。 - dryRun 与提交同路径:
dryRun: true走与正式提交完全相同的 opId 校验、payload 解析、applyWorkbookOperation校验与 revision 计算,只在落盘前返回;返回值给出revisionBefore/revisionAfter/affectedSheets/affectedCellCount/warnings预检清单。 - 写回执与告警:正式提交返回同样的
revisionBefore/revisionAfter/affectedSheets/affectedCellCount/sheetRefs;同一编辑计划内重复写同一 cell 会返回warnings(后写入的命令生效)。sheetRefs把受影响 sheet 的{ name, sheetId }作为数据发布——sheet id 是哈希值,只嵌在affectedSheets的名称(id)标签里时,刚创建 sheet 的调用方还得反解自己的回执。sheet 不存在与range/values 尺寸不一致的报错会列出当前 sheet 名与 id、以及期望/实际的 rows×cols 与建议 range。 - 失败回执可直接重试:校验失败会在原消息后追加已收参数与重试范围(例如
已收参数:workbookId=retry,1 条命令(update_cells),分组提交时则为N 个 version 分组),并说明只需修正出错的那条命令,不必重发整份 payload;同样的信息挂在DataSheetError.details上供能读取它的调用方使用。 - 结构命令守卫与列定义报错:在含公式或高级结构的 sheet 上,
insert_rows/delete_rows/append_rows等结构命令会被守卫拒绝,报错会直接给出可行路径——写更多行用update_cells或import_file(越界自动扩容,无需insert_rows),整体替换改用snapshot。列定义报错不再只说「不对」:会回显期望结构{ key, label, type }与实际收到的值,未知类型还会列出可选类型。 - 文件导入通道(
sourcePath):update_cells(省略values时)与一等命令import_file都能直接读本地CSV/JSON/JSONL/XLSX/XLS(≤128 MiB、最多 100000 行数据 × 50 列),一次命令 + 一个路径即可落表;sourceFormat可显式覆盖扩展名。文件第一行作为列名、只写入数据行,CSV 走真解析器,带引号、逗号、换行的字段与源文件逐格一致。update_cells的起点沿用range,或顶层startRow/startColumn(两者都省略时保持既有默认:第 1 行第 0 列)。导入不要求 sheet 第 0 行是表头,报告型 sheet(列头不在第 0 行)也能一次写入。 append_rows的表头行:{ type: 'append_rows', sheet, columns, rows, headerRowIndex?, verifyHeader? }。headerRowIndex默认0,指向真实列头行,数据写在它下面且列头行本身不被覆盖,因此带标题块的报告型 sheet 也能追加;verifyHeader默认true,比对现有列头文本与传入columns的label,只有显式传false才跳过(仅放宽文本匹配,列数仍须一致)。两者缺省时与历史版本行为完全一致,既有工作簿和既有调用不变;列头不匹配的报错会直接点名headerRowIndex。- 读回显默认收窄:
workbook_read的 range 读取默认只返回values。formulas/cells/styles是同一份数据的另两种视图(外加样式),会成倍占用 24 000 字符的回显预算,需在include中显式索取;workbook_read(kind=sheet)与(kind=workbook)的snapshot同样按include返回。 - 长文本外置(
valueFromFile):update_cells的单个 cell 可给valueFromFile(本地文本文件路径,≤1 MiB),Host 读入后按utf8作为该 cell 的字面值,用于把长文本(典型是 SQL)从文件写进单元格而不必内联进工具调用——此前同一条 SQL 要在每个用到的册子里重复写一遍。同给value时value优先;文件缺失或超限在写入前报错,不产生部分写入。run_detail_id属 facade(sql_result_to_data_sheet)范围,不在本插件。 duplicate_sheet(布局复用):{ type: 'duplicate_sheet', sheet, name, sheetId?, fromWorkbookId? }新建一个 sheet,并复制源 sheet 的单元格内容(标题块、表头行、长 SQL 文本都算)与列宽。fromWorkbookId省略时取当前工作簿,源 sheet 必须已存在——同一批次内刚创建的 sheet 不在此列。样式、合并、冻结、数据校验不复制。该命令在持久化前展开为既有create_sheet/update_cells/set_column_width,不新增 operation 类型。- 回填状态视图:
workbook_read(kind=workbook)传include: ['backfill']会为每个 sheet 返回lastOccupiedRow/columnCount/conclusionRows/runAt/provenance/schemaUpdatedAt/complete。注意lastOccupiedRow是内容上界而非数据行数:任何写入都会重算行计数,它同样覆盖结论块;conclusionRows是启发式(未跨满数据 schema 的填充行),仅在存在 schema 时计数。 import_file命令与自证回执:{ type: 'import_file', sheet, startRow, startColumn, sourcePath, sourceFormat?, verifyCapacity?, allowTruncate? }(startRow必填,0-based 起点)在持久化前归一化为既有update_cells写入路径,Version/Step 模型与协同投影协议不变。返回值新增imports[],每项含workbookId/revisionBefore/revisionAfter/sheetId/range/rowsWritten/cellsWritten/sourceRowCount/truncated/sourcePath/rowCountBefore/rowCountAfter/capacityExpanded/warnings,以及内容自证字段firstRow/lastRow/valuesHash,Agent 无需写完整段回读。命中opId重放时不会返回imports[]:该轮没有写入,任何内容证明都可能与表内实际内容不符,此时以replayed: true为准并回读工作簿。- 越界自动扩容与行上限:写入超过当前 sheet
rowCount(默认 1000)时自动扩容,并在回执与warnings中说明扩容结果,无需先insert_rows;verifyCapacity(默认 true)只控制这条容量说明,不改变写入行为。SHEET_MAX_ROWS = 100000(物理行 0–100000,row 0 可为表头);源文件超出可写区间时import_file默认报错(避免静默的部分写入),只有显式传allowTruncate: true才写入能容纳的前若干行,并在回执置truncated: true与一条warnings;单条超过SHEET_MAX_APPEND_ROWS = 1000行的append_rows会在同一个 Version 内自动拆成多个可回放 Step。 - 框架级参数校验:
invalid arguments: missing required property "workbookId"由 DSH 参数校验产生,插件无法改写;工具描述中已把workbookId标为必填并给出获取方式。
univer_api 是“可持久化 Adapter Registry”,不是任意原始 Univer API 执行器:它不能直接
调用未登记的 univerAPI、Injector 服务或任意 executeCommand。新增 Univer 能力必须补充
参数校验、持久化 operation、回放器与权限声明,才能注册为可调用 adapter;由此保证每次
Agent 写入都能生成 Version/Step、可回放,并且不会绕过 revision 或协同广播。
编辑历史面板读取 /data-sheet/workbook/:id/history:默认按 Version 展示,展开后查看
其有序 Step;Step 默认隐藏。每个 Version 和 Step 都可按顺序预览,Version 可恢复。
GET /history?revision=N&step=M 返回 Version 内第 M 个 step 的状态;workbook_history
通过 list / diff / replay 返回同一历史模型。
历史数据由 DSH 自己维护,不依赖 @univerjs-pro/* 或 Univer Server。
修改型工具支持可选的 baseRevision。所有写入都是主线 Version;出现问题时用户可恢复任意可重放版本,恢复动作本身占用新的全局 revision,之后继续累加,不复用旧编号。回滚目标之后、且不再属于当前谱系的旧 Version 保留在历史中并标记为 superseded,不会从日志删除。工作簿目录下的 <id>.json 保存轻量元数据,<id>.snapshot.json 保存当前 snapshot,<id>.checkpoints.json 保存可回放 checkpoint,<id>.operations.json 保存完整 operation;<id>.index.json 只保存 sheet/schema、revision 和摘要。推荐 Agent 先读取目标范围,再携带读取结果中的 revision 修改;revision 冲突时重新读取并重试。opId 用于网络重试幂等,复用同一 opId 但提交不同 payload 会被拒绝。
右侧 Sidebar 的开合、tab 导航和 session-scoped 状态完全由 DSH 官方 ui-sidebar-right / ui-dockkit 管理;插件只提供 tab type、guide entry 和 keyed body,不复制布局状态。
入口控制
入口不是 npm 市场页里的固定按钮,而是插件加载后注册到 DSH 官方右侧 Sidebar guide 的一个标签。发布后,用户安装包时,包内
dsh.bundle.patch 会自动插入 data-sheet 这条 Cordis 配置,插件激活后入口才会出现。
入口位置和样式由插件提供:guide 入口顺序当前为 45,tab body 通过
sidebar.right.pane.tabkeyed slot 注入。用户控制是否显示:
dsh plugin --profile web remove dsh-data-table会卸载;也可以在 profile 的cordis.patch.yml用同一个id: data-sheet覆盖并保留包名,保留安装但停用:- id: data-sheet name: 'dsh-data-table' disabled: true默认打开状态、列宽和行为:当前默认打开宽度是视口的 50%,用户仍可拖拽调整。
npm 发布分发包内的 lib/、src/ 与 cordis.patch.yml;入口的挂载由 Cordis patch 控制,入口的视觉和位置由 client bundle 控制。用户更新插件后需重启 DSH。
包内产物
lib/client.js是轻量 client bundle(__ModuleLoader__.loadhandoff,模块表外部依赖走 loader require);lib/workbook-page.js是独立浏览器 Tab使用的自包含页面 bundle,通过/data-sheet/workbook/:id/view打开;lib/univer-client.js是按需引擎 chunk(自包含 IIFE,Univer 全量内联 + 压缩,约 11MB):页面启动不加载它,首个表格渲染时经 Host 路由取回;/assets/data-sheet-univer.js是旧服务器(无该路由)的兜底副本。
重新进入不重新加载
官方 dock 只渲染当前激活 tab 的 body,所以切 DSH tab 或切 session 都会卸载右侧面板的 React 树。工作区是一个常驻 React 树:
- pane frame 只负责把常驻容器「领养」进自己的 frame,离开时把它移到屏幕外停放,保留测量尺寸(避免引擎收到 0×0 resize);
- 再次进入只是把同一个容器挪回新 frame,引擎、滚动位置、未保存草稿和协同流都保持原样,只挂载一次;
- 停放超过 10 分钟(
PARKED_RELEASE_MS)会真正卸载并释放引擎,避免常驻内存; - 因为工作区不再随 pane 消失,窗口级
Ctrl/Cmd+S和「离开 tab 时提交待保存改动」照常生效。
约定与限制
- 命名:用户可见的 Sidebar 标题是「Table」;包名
dsh-data-table、HTTP 路由/data-sheet/*和 tab kinddata-sheet保持不变。存储根目录改名为storages/data-table:首次解析存储路径时会把旧的storages/data-sheet整体迁移一次(migrateLegacyWorkbookStorage)。迁移是幂等的,只在目标目录不存在时移动;两个目录都有数据时不合并、不删除,旧数据保持原样。 - 在新 profile 中,表格作为 DSH 官方
ui-sidebar-right的 extension tab 渲染。插件不持有 Sidebar store,也不依赖第三方dsh-better-sidebar;官方 dockkit 负责 tab、浮动、分栏和 session 生命周期。 - 表格为全尺寸网格(1000 行 × 26 列,数据落在左上角),修复了首表单行数被数据行数限制的问题。
- 引擎含免费功能预设:筛选、排序、查找替换、条件格式、数据验证、绘图、超链接、批注、评论、超级表;协同由 Datasheet 自研 Univer 插件接入,不依赖商业 collaboration/PRO preset。
- 工具参数行数上限 100000(
SHEET_MAX_ROWS,物理行索引为 0–100000,row 0 可为表头);append_rows会自动按 1000 行(SHEET_MAX_APPEND_ROWS)分块,避免 Agent 一次提交过大的 Univer mutation,import_file/update_cells + sourcePath是批量导入的推荐路径。快照最多 100 个 sheet、50 列、25 MB;超过快照大小时应拆分 workbook 或先聚合数据。 - 列类型:
string | number | boolean | date | currency(date 渲染为YYYY-MM-DD,currency 按数字处理)。 workbook_read的 range 读取默认只返回values;formulas/cells/styles要在include里显式索取(它们会成倍占用回显预算)。公式单元格携带calculation: cached | unavailable,Host 不伪造公式重算结果。- 导入/导出:顶部菜单栏分为「导入」(Excel / CSV)和「导出」(Excel / CSV / JSON)两个分组菜单,不再注册 DSH 自定义「文件」页,Univer 自带的编辑工具页不受影响。XLSX 适配器保留多 sheet、公式、合并区域、行列尺寸、数字格式和可序列化的基础样式;导入/导出均在独立 Worker 中执行(单文件最大 25 MB),CSV 会中和
= + - @公式前缀以避免公式注入。JSON 导出的是工作簿 snapshot(与 Host 持久化同一格式)。 - 当前用户的普通单元格编辑通过带
opId的增量 operation 保存;复杂操作仍使用 snapshot fallback。Host 使用全局 revision 和本地 mutation queue 排序;单元格并发写入以 Host revision 为最终顺序,后提交的写入覆盖前一个写入,客户端会实时重放远端和本地待提交轨迹。结构性操作仍进入三方冲突处理。浏览器通过自研 SSE 会话实时接收操作,断线时用/changes补齐漏操作,无法安全投影时使用「基线 + 本地 + 远端」三方快照合并,冲突单元格保留本地值并以黄色高亮,用户确认后再提交。 - CSV/Excel 编解码在
xlsx-export-worker.js/xlsx-import-worker.js中按需加载,避免 SheetJS 在 Univer 主线程阻塞滚动和编辑;Univer 本体仍是首次进入表格时按需加载的独立 chunk。 - 当前协同使用 DSH 进程内 SSE 广播 +
/changes漏操作补偿,不依赖@univerjs-pro/*;同一 DSH 进程内的多浏览器 Tab 可以实时协作。每个 workbook 最多 64 个 SSE 连接,慢客户端的待发送队列上限 100 帧,超限会断开并由客户端自动重连。跨进程部署仍需要换成共享消息总线或 WebSocket 网关。
相关
- 数据和操作契约:
src/types.ts(SheetData/WorkbookOperation) - workbook 持久化与 operation 应用:
src/workbook-manager.ts
