Skip to content

实时流式传输

打开一个仍在被写入的会话,Chronicle 会实时跟随它——新消息会随着 AI 的产出而出现,并带有实时指示器和自动重连。

如果一个会话的日志文件在过去五分钟内被写入过,Chronicle 就会将它视为**实时(live)**并附加一个流:当你的 AI 工具向其日志追加对话时,它们会在一两秒内出现在 Playback(回放)中,无需刷新。这之所以可行,是因为 Chronicle 从磁盘增量读取日志——不与工具本身做任何协调——因此它可以在一个活跃的 Claude Code 或 Cursor 会话仍在运行时跟随其进展。和其他一切一样,它是本地的:只读,原始文件不受触动。

会话何时进入实时状态

触发条件简单且基于文件:如果一个会话的日志文件修改时间在过去 5 分钟之内,它就是一个实时候选(live candidate)server/live.js 中的 isLiveCandidate())。打开这样一个会话,客户端会自动连接到 GET /api/sessions/:id/live,一个 Server-Sent Events 流。无需按任何按钮——是最近活跃度触发的。

你会看到什么

  • 新消息淡入——它们到达时会淡入,在视觉上与已加载的历史区分开来。
  • 处于底部时自动滚动——如果你正在跟随末尾,窗格会保持钉在最新的消息上。
  • 一个“N new messages”按钮——当你向上滚动去读某些内容时出现。Chronicle 不会把你从正在阅读的地方猛地拽走;而是会出现一个浮动的 ↓ N new messages 按钮,点击它(或滚回底部)就会跳到最新处并清除计数。

实时指示器

一个状态标签反映连接状态,在会话打开期间会在整个应用范围内呈现:

  • ● LIVE — 已连接并正在跟随。
  • Reconnecting — 流断开了,Chronicle 正在重试。
  • Stopped — 流已结束(文件安静得够久了,或者重试次数已用尽)。

在连接断开时,客户端会以指数退避方式重试(几次尝试,每次等待更久),并在新消息一到达时立即重置;在最后一次尝试之后,它会稳定进入 Stopped 状态。当你关闭会话时,两端的监视器都会停止——客户端断开连接,而服务端会在最后一个查看者离开后拆除文件监视器,因此不会有任何东西在后台持续轮询。

当一个会话处于实时状态时,它的源日志删除功能会被禁用——你无法删除一个正在被写入的文件。

它是如何跟随的(两种策略)

不同工具存储日志的方式不同,因此 Chronicle 使用两种监视器,两者都是只读的:

  • JSONL 尾随(Claude Code、Codex)——Watcher 会记住文件末尾的字节偏移量,并在一次成本低廉的约 700 ms stat 轮询中,仅在文件增长时读取新增的字节,将每一行追加内容解析为消息。它通过从头重新读取来处理被截断或轮转的文件,并且会跳过任何无法解析的行,而不是让流中断。
  • SQLite 轮询(Cursor、OpenCode)——这些工具写入的是 SQLite 数据库,因此 SqlitePollWatcher 会做只读的周期性重新解析:它监视数据库文件的修改时间(感知 WAL——它还会检查 -wal 附属文件,因为写入可能落在那里而不触及主文件),当其发生变化时,重新解析并只发出它上次所见之外的消息。解析器会在读取前将数据库快照到一个临时副本,因此工具的实时数据库永远不会被直接打开或写入。

两种监视器都会在约两分钟的静默后放慢轮询,以在空闲会话上保持低成本,并在新内容一出现时立即加速。

注意: 实时消息被直接流入视图,并被赋予很高的序列号(从 1,000,000 起),因此它们绝不会与已存储的消息冲突。它们只存在于客户端中,直到会话被重新导入——执行一次 Sync Update(⇧⌘U)即可将新流入的对话持久化到数据库中。

相关内容

  • 导入会话 — 会话最初是如何进入 Chronicle 的,以及实时尾随同样遵守的只读保证。
  • 安全、实时与回放内幕Watcher / SqlitePollWatcher 的内部实现、SSE 布线,以及 server/live.js 中的监视器生命周期。

Released under the MIT License.