从多人编辑到 Agent 写文档,Hocuspocus v4 正在改写协同系统 😍😍😍

大家好 👋,我是 Moment,目前正在使用 Next.js、NestJS、Tiptap 和 LangGraph 开发 DocFlow。

DocFlow 是一个面向 AI 全栈场景的协同文档平台,主要围绕富文本编辑、实时协作和 AI 工作流展开。

如果你对 AI 全栈开发、Tiptap、LangGraph 或协同文档感兴趣,欢迎添加我的微信 yunmz777 一起交流。觉得项目还不错的话,也欢迎给 DocFlow 点个 star ⭐

如果你是前端工程师,正在从 React、Next.js 和 TypeScript 走向 AI Agent,这份小册会很适合你。里面的代码示例都以 TypeScript 为主,会从 Prompt、工具调用、Workflow 一直讲到 Agent 工程落地。

很多人第一次看 Hocuspocus v4 的升级说明,注意力很容易被 breaking changes 吸走。

Node.js 要升到 22+,requestHeaders 的读取方式要改,transactionOrigin 的判断方式要改,onStoreDocument 的 payload 也被重构了。

这些改动当然重要,但如果只把 v4 理解成一次 API 迁移,就会错过它真正值得关注的地方。

Tiptap 官方升级文档 明确写到,Hocuspocus v4 带来了跨运行时支持、generic Context 类型和 structured transaction origins。再结合 Hocuspocus 4 stable release 来看,这次升级的重点不是让协同编辑多几个功能,而是让 Hocuspocus 从一个偏 Node.js 的 WebSocket 协同服务,变成更适合生产环境的实时协同基础设施。

这个定位变化很关键。

以前做协同编辑,我们主要关心用户和用户之间能不能同步。现在不一样了。一个生产级文档系统里,除了用户,服务端流程、Redis 同步、系统任务、AI Agent 也可能成为同一份文档状态的写入者。

这时系统关心的就不只是能不能同步,而是:

  • 这次修改是谁发起的
  • 修改来自客户端、Redis,还是服务端本地写入
  • 这次写入要不要触发持久化
  • 要不要进入审计日志
  • 要不要更新 block 索引
  • 多个异步更新进入系统后,顺序能不能稳定
  • Agent 写文档时,能不能被追踪、被确认、被回滚

Hocuspocus v4 的很多设计,都是围绕这些问题展开的。

Hocuspocus v4 的核心价值,不在某个 hook 改名,也不在某个参数换了位置,而在于它开始承认协同文档已经进入多来源写入阶段。

只要写入来源变多,系统就不能只靠文档变了这个模糊信号工作。它必须知道谁在写、从哪里写、按什么顺序写、写完之后如何存、如何查、如何审计。

v3 解决多人编辑,v4 开始解决共享状态的生产问题

Hocuspocus 4 stable release 提到,Hocuspocus 虽然由 Tiptap 维护,但它并不绑定 Tiptap,本质上可以服务任何 Yjs client,比如 Tiptap、Slate、Quill、Monaco、ProseMirror,甚至你自己定义的 Yjs 共享数据结构。

这句话很关键。

它说明 Hocuspocus 的核心不是富文本编辑器,而是共享状态同步。

在 v3 时代,我们谈协同编辑,脑子里通常是这样一条链路:

  • 用户 A 输入内容
  • 用户 B 同步看到
  • 用户 B 修改内容
  • 用户 A 同步看到
  • 文档最终自动合并

这个阶段,Hocuspocus 解决的是多人编辑的基础问题:

  • 多人实时同步
  • 光标和 awareness 同步
  • 文档持久化
  • Redis 横向扩展
  • 断线重连和离线同步恢复
  • 和 Tiptap、Yjs 的协同链路打通

这些能力已经很重要,但它们更像是协同系统的第一阶段:让多人可以同时写同一份文档。

到了生产环境,写入来源会变复杂:

  • 用户在浏览器里手动编辑
  • 协作者远程编辑
  • Redis 从其他实例同步更新
  • 服务端做格式修复
  • Agent 根据指令修改某个 block
  • 系统任务恢复历史版本
  • 后台流程批量迁移文档结构

这时,系统不能只知道文档变了,还必须知道这次变化的业务含义是什么。

同样是一次 Yjs update,不同来源对应的处理方式完全不同:

  • 用户输入内容,通常要更新最近编辑人、版本记录和搜索索引。
  • Redis 同步更新,可能只是其他实例同步过来的变化,不能重复广播。
  • Agent 写入 patch,应该进入 Agent trace 和审计链路。
  • 系统恢复版本,不能触发和普通用户输入一样的通知逻辑。

这就是 v4 的底层变化:它开始把协同文档当成一个多来源共享状态系统,而不是只把它当成浏览器之间的实时同步通道。

如果放到 AI 文档系统里,这个变化会更明显。未来的文档不再只由人类写入,Agent、后台任务和服务端流程都会参与状态变化。Hocuspocus v4 做的,就是给这些变化提供统一入口,而不是让每一种写入都绕开协同层各自落库。

跨运行时支持的重点不是换库,而是解除 Node.js 绑定

v4 最显眼的变化,是 Hocuspocus 不再只围绕 Node.js 的 ws 设计。

Hocuspocus 4 stable release 和 v4.0.0 Release Notes 都提到,v4 把底层从 Node-only 的 ws 转到了 crossws,从而支持更多运行时,比如 Node.js、uWebSockets.js、Bun、Deno 和 Cloudflare Workers。

这件事不能只理解成换了一个 WebSocket 库。

它背后的真正意义是,Hocuspocus 开始把协同内核从某个具体运行时里抽出来。

协同服务一旦进生产,团队很快会遇到这些问题:

  • 想用 uWebSockets.js 提升长连接吞吐。
  • 想把部分协同能力放到边缘节点。
  • 想在 Bun 或 Deno 环境里运行。
  • 想在 Cloudflare Workers 这类运行时接入实时能力。
  • 想复用同一套协同逻辑,而不是每个运行时维护一份。

如果 Hocuspocus 的核心能力强绑定 Node.js 专属对象,这些事情都会变得麻烦。

v4 的方向是把真正稳定的东西保留下来:

  • 协同协议
  • Yjs update 处理
  • hook 生命周期
  • 业务上下文
  • 修改来源识别
  • 持久化链路
  • 多实例同步逻辑

底层跑在什么运行时,应该交给部署层选择。

跨运行时真正要强调的,不是把 Node.js、Bun、Deno、Cloudflare Workers 这些名字排出来,而是让协同系统稳定在 Yjs 状态、hook 生命周期、来源识别和持久化链路上。

对简单 demo 来说,这类变化感知不强。但对长期项目来说,它很重要。

当协同能力被越来越多业务模块依赖时,底层运行时就不应该反过来限制系统形态。运行时可以变,协同语义不能乱。

Web 标准 Request 和 Headers 重做了运行时边界

为了支持跨运行时,v4 还做了一个很容易被忽略、但很关键的变化:hook payload 统一改成了 Web 标准的 Request 和 Headers。

Tiptap 官方升级文档 明确写到,原来很多基于 Node.js IncomingMessage、IncomingHttpHeaders 的访问方式,在 v4 里都要改掉。

以前可能这样写:

ts 复制代码
async onAuthenticate({ requestHeaders }) {
  const token = requestHeaders["authorization"];
  const ip = requestHeaders["x-forwarded-for"];
}

现在应该改成这样:

ts 复制代码
async onAuthenticate({ requestHeaders }) {
  const token = requestHeaders.get("authorization");
  const ip = requestHeaders.get("x-forwarded-for");
}

表面上看,这只是从对象取值变成了 Headers.get(),但本质上是在清理 Hocuspocus 对 Node.js 环境的隐式依赖。

Bun、Deno、Cloudflare Workers 并不会天然暴露 Node 的 IncomingMessage。如果协同系统的 hook 生命周期还建立在这些对象上,所谓跨运行时支持就只能停留在表面。

同理,request.socket.remoteAddress 也不能继续依赖了。Tiptap 官方升级文档 提醒,要获取真实 IP,应该通过反向代理传进来的 x-forwarded-for 或 x-real-ip。

这件事落到工程里,其实是在提醒我们:连接层信息不应该散落在各个 hook 里,而应该在入口阶段被转换成明确的业务上下文。

更稳的思路是:

  • 在入口阶段完成 header 解析和鉴权。
  • 把用户身份、workspace、document、ip、userAgent 等信息统一装进 Context。
  • 后续 hook 只消费 Context,不再直接依赖底层 request。

这样做的价值不是少写几行代码,而是让协同链路从连接对象驱动,变成业务上下文驱动。

如果后续要把协同服务放到不同运行时里,这个边界会非常重要。你不能让权限判断、IP 识别、用户身份、租户信息都依赖某个 Node.js 对象,否则跨运行时只是部署层换了,业务层仍然被旧模型锁住。

Generic Context 的价值不是类型提示,而是统一业务身份

v4 对业务工程最实用的升级之一,就是 generic Context。

Hocuspocus 4 stable release 和 v4.0.0 Release Notes 都写得很明确:Server、Connection、DirectConnection、所有 extension 和 hook payload 都支持 generic Context。

如果只从 TypeScript 角度看,它好像只是类型更好了。但对协同系统来说,它解决的是另一个更关键的问题:整条协同生命周期,到底用什么承载业务身份。

一次协同连接不是一个孤立请求,它会经历很多阶段:

  • onConnect
  • onAuthenticate
  • onLoadDocument
  • onChange
  • onStoreDocument
  • onDisconnect

这些阶段都需要知道同一组信息:

  • 当前用户是谁
  • 属于哪个 workspace
  • 当前文档是什么
  • 当前角色是什么
  • 有没有写权限
  • 这次写入是用户、Agent 还是系统触发
  • 这次链路对应哪个 traceId

如果这些信息没有统一结构,短期靠约定还能跑,长期一定会出问题。

常见问题包括:

  • 字段命名不一致
  • 某些 hook 读到了不完整的身份信息
  • Agent 写入和用户写入混在一起
  • 审计日志缺少关键字段
  • 多租户系统里上下文边界不清楚

一个更适合生产协同系统的 Context,至少应该长这样:

ts 复制代码
type CollaborationContext = {
  userId: string;
  workspaceId: string;
  documentId: string;
  role: "owner" | "editor" | "viewer";
  permissions: Array<"read" | "write" | "comment" | "agent_write">;
  actorType: "user" | "agent" | "system";
  traceId: string;
};

有了这份 Context,系统边界就清楚了:

  • 鉴权阶段负责生产可信上下文。
  • 后续 hook 只消费上下文。
  • 审计、持久化、索引更新围绕同一份上下文展开。
  • Agent 或系统任务进入协同链路时,也要显式带上 actorType 和 traceId。

Context 不是某个 hook 里的临时变量,而是协同连接进入业务系统后的身份证。权限判断、审计日志、索引更新、Agent trace,都应该围绕同一份上下文工作。

没有这条 Context 链路,协同系统很容易停留在能同步。有了它,系统才有机会继续往可审计、可回滚、可观测的方向演进。

这也是 v4 和 v3 很不一样的地方:v4 不只是让 Hocuspocus 自己更强,而是让它更容易嵌入真实业务系统。

Structured Transaction Origin 是 v4 最有工程价值的升级之一

如果说 v4 里有一个最值得认真理解的 breaking change,我会选 transactionOrigin。

Tiptap 官方升级文档 专门把这一项拿出来讲。v3 时代,很多项目会通过字符串、原始值或者 instanceof 去猜一次更新来自哪里。

典型写法可能像这样:

ts 复制代码
async onChange({ transactionOrigin }) {
  if (transactionOrigin === "__hocuspocus__redis__origin__") {
    // 来自 Redis
  }

  if (transactionOrigin instanceof Connection) {
    // 来自客户端连接
  }
}

这种方式不是不能用,但问题很明显:它把一个非常关键的系统语义,建立在隐式约定上。

项目简单的时候,大家还能记住哪些字符串代表 Redis,哪些实例代表客户端连接。可一旦系统里出现多实例同步、服务端写入、Agent 修改、回滚任务、格式修复任务,这种隐式判断就会越来越脆。

到了 v4,transactionOrigin 变成了结构化对象。你应该通过 isTransactionOrigin() 和 transactionOrigin.source 来判断来源。

ts 复制代码
import { isTransactionOrigin } from "@hocuspocus/server";

async function handleChange({ transactionOrigin }) {
  if (!isTransactionOrigin(transactionOrigin)) {
    return;
  }

  switch (transactionOrigin.source) {
    case "connection":
      break;
    case "redis":
      break;
    case "local":
      break;
  }
}

Hocuspocus 4 stable release 也提到,现在 transaction origins 是带 source 的结构化对象,来源包括 connection、redis 和 local。

这个改动重要,是因为生产协同系统里的文档变了从来不是一个统一语义。

同样都是文档变化,它背后可能是完全不同的业务事件:

  • connection:用户或客户端发起的编辑。
  • redis:其他服务实例同步来的更新。
  • local:服务端本地代码写入,比如 DirectConnection、Agent、系统任务。

如果系统分不清这三类来源,后续逻辑就会变得危险:

  • Redis 同步过来的更新,可能被错误地再次广播。
  • Agent 写入可能被误记成普通用户输入。
  • 系统恢复版本时,触发了和用户操作一样的通知链路。
  • block 索引更新不知道该按什么规则记录来源。
  • 审计日志里看不出这次修改到底是谁发起的。

这里的核心不是 connection、redis、local 这三个词本身,而是它们后面代表的工程分流。

一旦来源清楚,系统才能决定后面的动作:

  • 哪些变化要写入审计
  • 哪些变化要跳过重复同步
  • 哪些变化要触发索引更新
  • 哪些变化要进入 Agent Trace
  • 哪些变化可以自动执行,哪些必须让用户确认

所以 structured transaction origin 的意义,不只是 API 更规范,而是 Hocuspocus 开始把修改来源当成底层模型的一部分,而不是丢给业务代码自己猜。

这个变化对 Agent 写文档尤其重要。Agent 写入不是普通用户敲字,也不是 Redis 同步,它应该有自己的来源、上下文和审计链路。没有结构化 origin,后面做回滚、Diff Review、Agent trace 都会很痛苦。

Ordered Message Processing 解决的是 async hooks 下的状态漂移

很多人看 v4 发布说明时,容易低估另一个变化:ordered message processing。

Yjs 官方文档 说得很清楚,Yjs 是一个高性能 CRDT,共享类型可以并发修改并自动合并,不需要手写冲突解决逻辑。

但这里很容易产生一个误解:既然 Yjs 能自动合并,那服务端处理顺序是不是就不重要了?

不是。

Yjs 解决的是 CRDT 合并问题,Hocuspocus 还要解决业务 hook 的处理问题。

真实项目里,onChange、onStoreDocument、beforeHandleMessage 这种 hook 往往会做很多异步事情:

  • 查权限
  • 写数据库
  • 写审计日志
  • 更新 block 索引
  • 推送 webhook
  • 触发队列任务
  • 调用安全检查
  • 通知 Agent 文档发生变化

只要这些逻辑里有异步操作,顺序问题就会变得非常真实。

Hocuspocus 4 stable release 提到,v4 会按收到顺序处理 document updates。以前如果 async hooks 参与,并发消息可能被重排。现在每个连接内部都有消息队列,按顺序处理更新。

这个变化的价值不在 demo,而在生产。

假设用户连续输入三次,文档状态按顺序应该经历:

  • A
  • AB
  • ABC

如果 async hooks 导致服务端处理完成顺序变成:

  • 第二次更新先完成
  • 第一次更新后完成
  • 第三次更新最后完成

Yjs 最终或许还能合并,但业务系统已经可能出问题:

  • 审计日志顺序不符合真实输入顺序。
  • 派生索引可能先写新状态,又被旧状态覆盖。
  • webhook 接收方看到错乱事件序列。
  • Agent 可能读取到了旧状态或中间态。
  • 排查线上问题时很难复原真实链路。

这里要把两个层次分清楚:Yjs 负责合并状态,Hocuspocus v4 负责让服务端处理链路更稳定。

协同更新进入业务系统以后,顺序不只是技术细节,它会影响审计、索引、webhook 和 Agent 读取。

所以 ordered message processing 不是为了证明 Yjs 之前不可靠,而是为了补上服务端业务链路里的顺序稳定性。

CRDT 能处理并发合并,但审计日志、索引更新、Agent 读取、webhook 推送这些业务动作,也需要可解释的顺序。否则线上出现偶发问题时,排查成本会非常高。

onStoreDocument 的重构说明持久化模型变了

v4 对 onStoreDocument 的改动,也很能说明 Hocuspocus 的定位变化。

Tiptap 官方升级文档 写得很清楚,onStoreDocument 和 afterStoreDocument 的 payload 被重构了,很多和具体连接强绑定的字段被移除了,因为 store hook 现在可能由非连接来源触发。

以前我们更容易把持久化理解成这样一条链路:

  • 用户通过 WebSocket 修改文档
  • Hocuspocus 收到 update
  • onStoreDocument 写入数据库

这套理解在简单场景下没问题,但进入真实生产系统就不够了。

因为现在更真实的写入来源可能是:

  • 用户手动编辑
  • 协作者远程编辑
  • Redis 同步更新
  • Agent 生成 patch
  • 服务端通过 DirectConnection 写入
  • 系统任务恢复历史版本

这些写入都可能修改同一份 Yjs document,而且都应该进入统一持久化机制。

这也是为什么 v4 要把 requestHeaders、requestParameters、socketId 这些连接相关字段从 onStoreDocument 里拿掉,改成 lastContext、lastTransactionOrigin、documentName、clientsCount 这样的信息。

这背后的思路很重要:

  • 旧思路:某个连接改了文档,所以我要存。
  • 新思路:文档状态变化了,所以我要存,并且要知道最后一次变化来自哪里。

后者才符合今天协同系统的现实。

放到文档系统里,这件事还会直接影响数据设计。

比较稳的模型一般会拆成三层:

  • yjs_state:协同状态真相。
  • content_json:可读的渲染或导出视图。
  • document_blocks:搜索、RAG、Agent 定位用的派生索引。

如果把 Tiptap JSON 当成唯一真相,多人协同和 Agent 写入一进来,很多问题都会暴露。JSON 更像某一刻的文档快照,而 Yjs state 才能表达协同更新和自动合并的语义。

onLoadDocument 支持 Uint8Array 是在提醒我们什么才是协同真相

v4.0.0 Release Notes 还提到一个很实用的变化:onLoadDocument 现在可以直接返回 Uint8Array,扩展不必先构造完整的 Y.Doc。

这看起来像一个小优化,但它背后其实是在提醒我们,协同系统真正应该尊重的数据形态是什么。

在生产里,Yjs 状态通常不会只以普通 JSON 形式存在。更常见的可能是:

  • PostgreSQL 的 bytea
  • MySQL 的 blob
  • S3 或 MinIO 里的 state 文件
  • Redis 里的压缩 update
  • update log 或 snapshot

如果每次都要求扩展先把这些数据还原成完整 Y.Doc,再返回给 Hocuspocus,中间就多了一层没有必要的转换。

支持 Uint8Array,就是让 onLoadDocument 更贴近 Yjs 的原始数据模型。

这里不要把三层存储理解成三个互相覆盖的版本。它们不是平级真相,而是真实状态和派生视图的关系。

yjs_state 承担协同真相,content_json 和 document_blocks 服务渲染、搜索、RAG 和 Agent 定位。

协同文档不能完全按普通文档思路去存。普通文档更关心当前内容是什么,协同文档除了内容,还要关心更新如何合并、状态如何恢复、多来源更新如何同步、离线写入如何补齐。

所以,Tiptap JSON 可以存在,但它更适合作为派生视图。真正的协同真相,应该是 Yjs state。

Provider 侧的变化是在为复杂工作台铺路

v4 不只是 server 变了,provider 侧也做了不少调整。

GitHub v4.0.0 Release Notes 提到,provider 侧新增或增强了 session awareness、auth retry、awareness deduplication、application-level Ping/Pong、attach collision detection 等能力。

其中最值得关注的是 session awareness。

它允许在同一个 WebSocket 上挂多个相同 document name 的 provider,每个 provider 有自己的 sessionId。这件事在复杂文档工作台里非常有用。

一个真正的知识库或 AI 文档工作台,页面上可能不只有一个编辑器状态,还可能同时存在:

  • 正文编辑器 provider
  • 评论区 provider
  • 在线协作 awareness
  • AI 草稿协同状态
  • 子文档 provider
  • 局部 block 状态
  • 右侧 Agent 面板的临时协同状态

如果每个状态都独立建一条 WebSocket,连接管理会很乱。如果复用 WebSocket,又需要更细的会话区分能力。session awareness 就是在为这种场景做准备。

application-level Ping/Pong 也说明了 v4 的方向。

不是所有运行时都支持 WebSocket-level ping/pong,所以 v4 把心跳的一部分放到应用层协议里,让它不再依赖某个特定运行时的细节。

这些变化放在一起看,其实都在说明同一件事:v4 不只优化 server,它也在让 provider、协议和运行时边界更适合复杂协同工作台。

对产品形态来说,这意味着 Hocuspocus 不再只是编辑器旁边的连接库。它开始更适合承载多文档、多会话、多状态区域的协同工作台,比如编辑器、评论、Agent 面板、草稿区、嵌入式 block 同时存在的页面。

v4 和 AI Agent 的关系不是蹭热点,而是共享状态模型变了

Tiptap stable release 专门提到,CRDT 很适合让人和 AI Agent 共同写入共享状态,因为不需要锁,也不需要重新发明协调协议。

这句话其实把 v4 的长期方向说得很透。

传统协同编辑里,核心问题是:

  • 两个人同时编辑,怎么合并?

Agent 进来之后,问题变成了:

  • 人和 Agent 同时写文档,怎么保证可控、可审计、可回滚?

这已经不是同一个层次的问题。

Agent 真正进入文档系统时,你不能只关心它写得好不好,还必须关心它怎么写进去:

  • Agent 能不能直接写入文档
  • 是否必须先生成 diff
  • 用户确认前能不能落库
  • Agent 写入算谁的修改
  • 和用户当前编辑冲突时怎么办
  • 写入后如何更新 block 索引
  • 写入失败后如何回滚
  • 这次修改对应哪个 runId、traceId
  • 审计日志里能不能完整还原修改过程

v4 的几个能力,刚好是在为这条链路打底:

  • Generic Context,让 Agent 也能带着业务身份写入。
  • Structured transaction origin,让系统知道这是 local 写入。
  • DirectConnection context,让服务端写文档时也能带上上下文。
  • Store hooks on all changes,让 Agent 写入也能进入统一持久化。
  • Ordered message processing,让用户和 Agent 高频写入时顺序更稳定。

如果真的要做 Document Agent,更稳的链路应该是:

  1. 用户提出修改要求。
  2. Agent 读取 document_blocks。
  3. Agent 定位目标 block。
  4. Agent 生成局部 patch。
  5. 用户在 Diff Review 中确认。
  6. 服务端通过 DirectConnection 写入 Yjs。
  7. Hocuspocus 记录 local transaction origin。
  8. onStoreDocument 统一持久化。
  9. 更新 content_json 和 document_blocks。
  10. 审计日志记录 actor、traceId、documentId、patch 结果。

Agent 不是越能写越好,而是越可控越好。

它不应该绕开协同层直接覆盖 JSON,而应该先定位、再生成 patch、再进入 Diff Review,最后通过 Hocuspocus 写入 Yjs 状态。

这条链路比让 Agent 直接生成整篇 JSON 覆盖文档要稳得多,也更符合协同编辑系统的边界。

它保留了几个关键前提:

  • Agent 先定位,再修改。
  • 修改以 patch 形式进入系统,而不是整篇覆盖。
  • 用户可以在 Diff Review 里确认。
  • 服务端通过 DirectConnection 写入 Yjs,保留协同语义。
  • Hocuspocus 通过 transaction origin 区分这是 local 写入。
  • onStoreDocument 统一处理持久化。
  • 审计日志记录 actor、traceId、documentId 和 patch 结果。

这也是 v4 对 AI 文档系统最重要的价值:它不是让 Agent 获得更大的权限,而是让 Agent 的写入进入同一套可审查、可追踪、可持久化的协同机制里。

对 DocFlow 这类项目来说,v4 的价值会被进一步放大

如果只是一个简单的多人编辑 demo,Hocuspocus v3 其实已经能满足基本需求。

但我正在做的 DocFlow,不是一个只验证协同编辑能力的 demo,而是一个基于 Tiptap、Yjs、Hocuspocus、Next.js 和 NestJS 的协同文档项目。它的目标也不是停留在富文本编辑器,而是继续往知识库、AI 文档工作台和 Document Agent 方向演进。

这类项目后面一定会继续长出更多能力:

  • 多人协同编辑
  • 评论和批注
  • 权限管理
  • 文档版本历史
  • block 级索引
  • 全文搜索
  • RAG 检索
  • AI 写作
  • Agent 修改文档
  • Diff Review
  • 审计日志
  • 回滚机制
  • 多租户隔离

这些能力一旦叠上来,Hocuspocus 就不只是一个 WebSocket 协同服务了,而会变成整个协同状态系统的中枢。

它要承担的也不只是同步,而是要把文档变化放进一条更完整的工程链路里:

  • 谁可以连接
  • 谁可以修改
  • 修改来自用户、服务端还是 Agent
  • 消息是否按顺序进入处理链路
  • 文档变化后如何稳定持久化
  • 服务端写入是否也带有明确 Context
  • 审计、索引、回滚和 Agent trace 能不能拿到可靠依据

DocFlow 这类项目的核心边界,不是编辑器、Yjs、Hocuspocus、Agent、存储和审计各做各的,而是它们应该进入同一套协同状态系统。

这也是 Hocuspocus v4 对这类项目价值更明显的原因。它的意义不是单纯升级一个依赖,而是让协同底座更适合支撑下一阶段的 Document Agent 工作流。

Agent 修改文档这件事,不能绕开协同系统单独写库。

如果没有结构化 origin、Context、DirectConnection 上下文和更完整的 store hook,Agent 写文档就很容易变成旁路写入。短期看起来实现更快,但长期会破坏协同状态、版本历史、审计链路和索引一致性。

对 DocFlow 这样的项目来说,真正重要的不是让 Agent 能把内容写进去,而是让 Agent 的每一次修改都能被协同系统识别、记录、审计、回滚,并和用户的实时编辑保持在同一条状态链路里。

升级时真正要注意的是 hook 边界,不是只把依赖升上去

虽然 v4 提供了兼容升级路径,但它毕竟是大版本,绝对不能只改 package version。

Tiptap 官方升级文档 提到,v4 需要 Node.js 22 或更高,@hocuspocus/server 和 @hocuspocus/provider 也要升到 v4。如果使用 SQLite extension,要从 sqlite3 切到 better-sqlite3。

真正升级时,最该关注的是这些边界:

  • Node.js 版本是否在本地、CI、Docker、线上统一到 22+。
  • 所有 requestHeaders["xxx"] 是否都改成了 requestHeaders.get("xxx")。
  • 是否已经停止依赖 request.socket.remoteAddress。
  • onStoreDocument 是否还在依赖 socketId、requestHeaders 这类连接字段。
  • transactionOrigin 是否已经统一改成结构化判断。
  • WebSocket 类型是否已经收口到 WebSocketLike。
  • 是否顺手补上了审计字段和来源观测。

这里面最容易做错的一点,是只修编译错误,不修系统边界。

比如把 transactionOrigin === "__hocuspocus__redis__origin__" 改成新的判断方式,看起来代码能跑了,但如果没有顺手把审计、索引、持久化的来源语义一起整理,那就还是没有真正吃到 v4 的价值。

升级 v4 最好的机会,不是把报错修掉,而是顺手把协同系统的几个边界补齐:

  • 连接入口统一生成 Context。
  • 文档变化统一记录 origin。
  • 服务端写入统一走 DirectConnection。
  • 持久化统一围绕 Yjs state。
  • Agent 修改统一进入审计和回滚链路。

如果这些边界没有重整,v4 只是依赖版本变了,系统能力并没有真正变强。

更稳的迁移方式,是先升级服务端,再逐步打开新能力

v4 一个很友好的地方,是升级路径比较克制。

Tiptap 官方升级文档 和 stable release 都提到,wire protocol 双向兼容:v3 provider 可以连接 v4 server,v4 provider 也可以连接 v3 server。

这意味着你不需要一上来就把前后端同时全部切到 v4。

更稳的迁移方式通常是:

  1. 先统一 Node.js 运行时版本。
  2. 先升级 @hocuspocus/server 到 v4。
  3. 修复 server hooks、Context、transaction origin、store hooks。
  4. 前端 provider 先维持 v3,验证和 v4 server 的兼容性。
  5. 观察鉴权、awareness、Redis、持久化、断线重连、服务关闭 flush 是否正常。
  6. 再升级 @hocuspocus/provider 到 v4。
  7. 最后再逐步开启 session awareness、DirectConnection context、Agent 写入审计等新能力。

这样做的好处是,线上一旦出问题,更容易定位到底是运行时、server hook、provider 协议,还是新增能力导致的。

尤其是已经有用户在用的协同文档系统,不建议一次性打开所有新能力。先保证原有协同、鉴权、存储、Redis、awareness 正常,再逐步引入 session awareness 和 Agent 写入链路,风险会小很多。

判断升级是否成功,不能只看能不能协同编辑

Hocuspocus v4 的升级验收,不能只看两个人在浏览器里能不能同步输入。

那只能说明最基础的实时协同没坏。

真正的生产级验收,应该看这些问题:

  • 用户连接时,Context 是否完整记录 userId、workspaceId、documentId、role、traceId。
  • 文档变化时,能否区分 connection、redis、local。
  • Agent 或服务端 DirectConnection 写入时,是否能进入统一持久化链路。
  • onStoreDocument 是否已经不依赖某个具体 socket。
  • store hook 失败时,是否有日志、重试和告警。
  • Redis 同步场景下,是否不会重复广播和重复写入。
  • 服务关闭时,pending store 是否能 flush。
  • 前端 provider 升级后,awareness、重连、auth retry 是否正常。
  • 审计日志、索引更新、版本记录是否都能带上来源信息。
  • Agent 修改文档时,是否能还原这次修改的 actor、runId、traceId。

如果这些问题没有答案,那就说明升级还停留在能跑层面,没有真正进入能治理层面。

如果升级后只是能连上、能同步,但系统依然不知道谁改了文档、从哪里改的、怎么审计、怎么回滚,那就没有真正完成 v4 升级,只是把依赖版本改上去了。

真正的升级成功,应该体现在协同链路变得更可治理:

  • 连接有上下文。
  • 修改有来源。
  • 写入有顺序。
  • 存储有真相。
  • Agent 有审计。
  • 失败有恢复路径。

总结

Hocuspocus v4 表面看是一次大版本迁移,本质上是协同后端定位的升级。

v3 更关注多人如何在浏览器里协同编辑。v4 开始处理的是另一类问题:当人、服务端、Redis、Agent 都可能共同写入同一份共享状态时,协同系统如何保持可控。

所以这次升级最值得关注的,不是 hook 参数换了几个位置,而是几个底层边界被重新整理了:运行时边界、业务上下文、修改来源、消息顺序、持久化入口和服务端写入链路。

对简单 demo 来说,v3 依然能用。但如果要做知识库、协同文档、AI 写作工作台、Document Agent 或复杂的多人在线编辑器,v4 的价值会很明显。

未来的文档系统里,编辑者不再只有人,服务端会写,Agent 也会写。Hocuspocus v4 真正做的,就是让这些写入者都进入同一份 Yjs 状态,同时还能被识别、被审计、被持久化、被回滚。

相关推荐
子兮曰5 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰5 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万5 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝5 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
胡写代码5 天前
别再前后端各写一套表单校验了
java·后端
三十而立洋5 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
晨米酱6 天前
AGENTS.md:Agent 的上下文策略层
面试·架构·agent
大勇前进6 天前
原生 PHP 还是 Laravel?小项目到底要不要上框架
后端
yuzhi_liu6 天前
我用 LangGraph4j 实现 Multi-Agent Supervisor
后端
alsmile6 天前
Node-RED 之外,国产规则引擎的新方案:基于标准语法,Go 先行实现
后端·开源·go