流式对话
模型输出按 token 切成 assistant.delta 事件推送,四端各自用同一份 applyEvent reducer 把事件流折叠成 UI 状态。delta 类事件是 live-only:不落盘,断线重连后由 durable 的 assistant.message 重建终态。
assistant.delta · reasoning.delta · tool.progress = live-only
开源MIT · 四端同一协议 · 事件溯源
流式对话、工具调用可视化、fail-closed 人工审批。27 种事件实时驱动四端界面,会话以 append-only JSONL 落盘,可回放、可分叉、可回滚。
MIT License · Node.js ≥ 24 · 默认监听 127.0.0.1:4318 · 数据落盘 ~/.spark
会话文件为什么只追加不改写?
append-only JSONL:第 0 行是 header,其后每行一个事件信封,seq 等于文件行号。只追加不改写,因此可回放、可分叉、可回滚。
断线重连之后界面怎么恢复?
回放:按 since=seq 拉取 durable 事件重算;live-only 的 delta 不落盘,由 assistant.message 重建终态。
>
引擎申请改写工作区文件,等待人工确认:
bash 执行命令请求
git commit -m "feat: bash 池"
✓ 已批准(一次) · 超时未答复将自动拒绝(fail-closed)
产品形态
一份 applyEvent reducer 投影出四种界面——不是四个客户端各写一遍。
先读后改,改动走审批。 写类工具与 bash 需人工确认——once / always / reject 三种答复,超时未答复自动拒绝(fail-closed)。
permission.resolved { reply } · fail-closed
import { createClient } from "@spark/sdk";// HTTP 客户端:装配 HttpTransport + 便利分组(ADR D30)const client = createClient("http://127.0.0.1:4318");// 事件流是 UI 的唯一状态源:SSE 按 seq 续播client.events.subscribe((envelope) => { // 27 种事件经 applyEvent 折叠成 UI 状态});四项能力共用一份事件词表:流式渲染、工具状态机、审批卡、四端同步 都是同一套 reducer 的不同分支。
模型输出按 token 切成 assistant.delta 事件推送,四端各自用同一份 applyEvent reducer 把事件流折叠成 UI 状态。delta 类事件是 live-only:不落盘,断线重连后由 durable 的 assistant.message 重建终态。
assistant.delta · reasoning.delta · tool.progress = live-only
工具状态机 started → progress → completed 全程上屏:输入、输出、耗时(durationMs)、是否错误(isError)都是事件字段。失败时错误码(E_PATH_OUTSIDE / E_SANDBOX_UNAVAILABLE 等)同屏呈现。
tool.started { input } → tool.completed { output, isError, durationMs }
permission.asked 弹卡,答复只有 once / always / reject;超时、异常、中断一律结清为 reject(fail-closed)。审批事件是 log-only:永不进模型历史,但 durable 落盘,事后可回放每一次决策。
permission.resolved { reply: 'once' | 'always' | 'reject' }
Web、Desktop、CLI、Mobile(含小程序)共享 @spark/protocol 的 27 种事件词表与 Transport 接口。词表扩展走 declaration merging,schema registry 是唯一来源,四端不会各自漂移出一套事件名。
@spark/protocol · 27 种事件 · Web / Desktop / CLI / Mobile
五层:四端 UI / 协议 / 服务端 / 引擎 / 会话文件。
↓ data flow: user input → engine → events → UI
缺省绑定回环;审批超时、异常、中断一律结清为拒绝;路径越界先于 审批拦下;事件全程 append-only 落盘。
server 缺省只监听回环地址,会话数据全部落在本机 ~/.spark/ 下。需要对外暴露时(非环回绑定)强制开启配对鉴权:6 位短码换长效 token。
server: { host: '127.0.0.1', port: 4318 }写类工具与 bash 必须人类确认,答复只有 once / always / reject 三种。超时、异常、中断、级联、模式切换一律结清为 reject,审计主体记 system;bash 工具缺省全审批。
timeout → permission.resolved { reply: 'reject' }允许根 = cwd,路径 resolve 归一后越界直接抛 E_PATH_OUTSIDE,发生在审批之前而不是事后审计。可选的 bash 沙箱走平台 wrapper 前缀(Linux bwrap / macOS Seatbelt),wrapper 不可用即拒跑,不降级裸跑。
resolveInRoot → E_PATH_OUTSIDEappend-only JSONL:第 0 行是 header,其后每行一个事件信封,seq 等于文件行号。只追加不改写,因此可回放、可分叉、可回滚(checkpoint)。
~/.spark/sessions/<mungeDir(cwd)>/<ts>_<id>.jsonlnpm 发布落地前先 clone 源码:装一次依赖、出一条 server bundle,一条命令进 TUI。
从协议包读起:事件词表与 Transport 是四端共享的运行时核。
MIT 许可 · 默认只监听 127.0.0.1 · 无账号体系