npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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.tab keyed 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__.load handoff,模块表外部依赖走 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 kind data-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