内容
# 再流
一种基于节点的视觉编程语言和编辑器,具有反应性和AI层。
一张实时的反应图,三个同步的表面:一个**笔记本**,包含表达式单元格,一个**画布**,包含连接的节点,以及**MCP通道**,让 Claude Code 可以读取、编辑和订阅同一个正在运行的程序。值是UI规范的节点会**实时渲染 React UI** — 包括一个聊天助手,它本身是用 Reflow 单元格编写的,可以创建新节点。
| 笔记本(单元格 + 内联UI) | 画布(节点,节点内聊天渲染) |
| --- | --- |
|  |  |
## 亮点
- **反应引擎**(`@reflow/core`):在DAG上具有记忆输出和相等修剪的变更事件的脏传播调度器;重新计算是**O(受影响的子图)**。测量:一个包含10,000个节点的链在约17毫秒内更新(**≈560k 节点更新/秒**)。
- **图灵完备**:通过lambda单元格和函数子图(惰性`if` + `call`)进行递归,bounded `while`迭代。通过运行作为测试的程序证明:图灵机模拟器和Rule 110,均已针对独立的JS引用进行验证。
- **自动持久或短暂变量**:`count = var(0)`具有会话范围;`best = pvar(0)`在启动时自动加载,在写入时自动保存。持久性是变量的属性 — 写入看起来相同。
- **一张图,每个表面**:笔记本单元格*是*图节点(类型`expr`),依赖关系从自由标识符自动连接。在笔记本、画布、聊天或MCP中编辑 — 所有地方都实时更新。
- **节点中的UI**:任何使用`ui.*`构建值的单元格/节点都会内联渲染为真实的React组件(切换`{}`/`ui`以查看原始规范)。事件处理程序是可序列化的操作(`set('count', count + 1)`),分派回引擎。
- **LLM作为语言原语**:`llm(prompt, {system, model}?)`是一个异步反应源 — 待处理时为`null`,然后文本流经图;结果缓存每个节点+提示,并且提示更改会反应性地重新请求。`agent(conversation)`还允许Claude**编辑程序**(创建/更新/删除单元格)通过工具。两者都在服务器端运行在 [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk)。
- **聊天被自己验证**:助手UI大约是6个Reflow单元格 — `chatMessages = pvar([])`(持久性记录),`chatReply = … agent(chatConvo) …`,以及`ui.*`视图。要求它“创建一个单元格…”然后节点出现,实时。
- **用于Claude Code的MCP通道**:MCP服务器公开正在运行的程序 — `list_nodes / read_node / add_cell / update_cell / remove_cell / set_variable`,加上 **`subscribe(channel)`** (一个通道 = 一个节点输出)和长轮询 **`get_updates`** ,以便代理可以对流经图的數據做出反应。
## 语言
单元格是单个纯表达式(`name = expression`),JS风格:
```text
count = var(0) // 短暂变量
best = pvar(0) // 持久(自动保存/恢复)
doubled = count * 2 // 当count变化时重新计算
fib = n => n < 2 ? n : fib(n-1) + fib(n-2)
haiku = llm('写一首关于 ' + topic) ?? '…思考中'
view = ui.column(ui.h1('计数器'),
ui.text('计数:' + count),
ui.button('+1', set('count', count + 1)))
```
Lambda,ternary,结构`==`,数组/对象,和纯stdlib(`map filter reduce range len sum sort join split slice push assoc merge keys values get str num while(init, cond, body)`)。互递归起作用(lambda主体引用延迟绑定)。图灵机,来自测试套件:
```text
final = while({tape: {}, pos: 0, state: 'A', steps: 0},
s => s.state != 'H', s => tmStep(s), 100000)
```
## 架构
**代码和数据一起存活**在一个中心进程中;每个客户端都是对等体:
```
┌────────────────────────────────────┐
浏览器编辑器 ──────┤ apps/server — 中心 │
(SSE + 操作) │ · @reflow/core 引擎(图) │
│ · vars.json + graph.json 持久 │
Claude Code ─────────┤ · llm()/agent() 通过 Claude 代理 │
(MCP stdio 桥) │ SDK,代理编辑实时应用 │
│ · REST /api/state /api/op │
curl / 脚本 ──────┤ · SSE /api/events (状态流) │
└────────────────────────────────────┘
```
- `packages/core` — 语言 + 引擎(解析器,评估器,调度器,变量,节点注册表,函数图,llm/代理提供者作为注入接口)。无DOM,无网络。
- `apps/server` — 中心(`src/hub.ts`),Claude SDK 提供者(`src/providers.ts`),MCP 服务器(`src/mcp.ts`),演示种子。反应图保持非循环;图灵完备递归/迭代生活在具有深度/迭代上限的函数层中,因此编辑器永远不会挂起。
- `apps/web` — React + Vite + @xyflow/react 薄客户端:状态镜像通过SSE,编辑作为操作发送回。
## 运行
```bash
pnpm install
pnpm dev # 中心 :5200 + 编辑器 :5199 (通过代理SDK身份验证的真实Claude)
REFLOW_LLM_MOCK=1 pnpm dev:hub # 确定性模拟LLM/代理代替
pnpm test # 97个核心测试
pnpm e2e # 12个Playwright端到端测试(模拟中心,自动启动)
pnpm verify:mcp # MCP客户端端到端检查(需要运行的中心)
```
将Claude Code连接到正在运行的程序:
```bash
claude mcp add reflow -- pnpm --dir <repo>/apps/server mcp
# 然后:订阅一个通道,例如“订阅计数单元格,当它更改时通知我”
```
模型默认为`claude-haiku-4-5`用于`llm()`和`claude-opus-4-8`用于聊天代理(`REFLOW_LLM_MODEL` / `REFLOW_AGENT_MODEL`,或每个调用 `{model}`)。
## 验证
- **核心(vitest,97个测试)**:解析器/评估器语义;增量重新计算证明(计算计数断言);循环检测/恢复;具有来源的错误传播;持久性往返于真实文件;递归 + 互递归程序;**图灵机 + Rule 110 与独立JS引用**;llm()/agent()异步语义(去重,反应性,错误路径,图编辑);性能边界。
- **端到端(Playwright,12个测试)**:实时值 + 内联UI渲染;通过中心的动作调度;UI/原始切换;带有错误恢复的单元格编辑;输入到笔记本的斐波那契数列;异步llm()单元格;**聊天对话,在引擎重启时持续存在,并通过代理创建节点**;pvar-vs-var重启语义;画布编辑,端口拖动边缘创建,跨表面同步。零个未捕获的页面错误。
- **MCP(`verify:mcp`)**:真实的MCP客户端通过stdio锻炼每个工具,包括订阅 → 外部更改 → get_updates长轮询传递。
- **真实Claude现货检查**:`llm('回复并精确:pong')` → `"pong"`;聊天要求“一个名为triple的单元格,乘以count 3” → 代理创建`triple = count * 3`,当count设置为7时重新计算为21。