REFACTOR —— 重构 / 拆包 / 分解方案(市场为核心 · AML+Cloudflare 为基建)
2026-06-19 收敛后,对现有框架做的一次深度分析 + 落地重构蓝图。真相源:本文档(收敛蓝图)。 目标一句话: 市场(marketplace)= 核心价值 / 壁垒;AML + Cloudflare = 基础设施;我们 = 把市场架在这套基建上的集成 + 一个会生成 & fork app 的 Agent。 一个【应用】=
{ aml(声明式后端数据模型 → 框架自动 D1 表 + 自描述数据接口)+ index.html(LLM 自由写、沙箱跑、调那层接口)+ 描述 + 预览 }。用户聊 → Agent 生成 app → 部署 + 发布到市场;Agent 靠市场 skill 搜 / 截图 / fork,不从零写。无 verticals。
1. 现状地理(深度分析结论)
依赖图是干净的单向链;关键是 aml 已造好但没人消费:
零依赖叶子: @agentaily/aml(DSL+emitter) · @agentaily/llm · studio-protocol · i18n · persistence
studio-protocol ←(7 个消费者:client/presets/backend/eval/agent/form-schema/slides-schema)
数据后端: studio-protocol+form/slides-schema ← backend ← db ←(apps/web/functions · apps/publish)
aml: aml ← verticals/*/aml ──✗── 没有任何 运行时/db/functions 消费它的 sql/api(核心断点)
agent: llm+studio-protocol ← agent ← agent-ui
产品: agent+backend+db+presets+studio-client+design-system ← apps/web ; backend+db ← apps/publish五个关键发现:
- aml emitter 是现成地基,但运行时没接 ——
aml/emit.ts已产{tables, sql(CREATE TABLE), api(ApiDescriptor)};运行时却仍跑「内容文档 → 派生 recordSchema(JSON Schema)→ compiler → 每项目动态表data_<id>」,完全没读 aml 的产物。这是最大待建块。 - 要整条抽掉的旧主线 = 「zod 内容文档 + Studio 驱动 + per-project 派生表」,贯穿
agent工具集 /App.tsx/projectClient/presets/backend.submissions(VERTICALS 硬编码)/db.compiler。 - 安全两个裸洞(必修):①
api/data/[id]+ publish 的 query 完全无鉴权(注释「open for now」)→ 谁都能查任意 project 数据;②apps/web预览 iframe 无sandbox+ 分享走同源/f/:id(App.tsx:562,632)→ LLM 写的 HTML 能读平台 localStorage/Bearer。 - 现成可复用:
apps/publish(独立源 UGC Worker,= 沙箱目标的落点,已 e2e 绿)·backend/runtime.ts(window.agentaily桥 + 注入)·backend/publish.ts(host→app 解析)·verticals/*/studio里的html2canvasscreenshot 机制(→ 抽成市场预览生成器)·apiClient/auth(正交)。 - 1 个 bug(原 2 个,workspace 漏列已修):
✅ 已修复(PR #3) ——pnpm-workspace.yaml漏列theme/persistencepackages:现含aml/theme/persistence等全部包;仍待办:persistence默认写.agentaily.comcookie —— 平台鉴权态绝不能用它(UGC 子域会读到平台会话)。
2. 目标架构(分层 + 包边界)
基建叶子(零依赖): @agentaily/aml(DSL + emitter,核心引擎,基本不动) · @agentaily/llm(模型网关,正交不动)
↑
@agentaily/runtime (= @agentaily/db 重做 + 改名):吃 aml 的 EmitResult →
│ ① D1 provisioning(跑 DDL、aml 变更 diff 迁移)
│ ② 从 ApiDescriptor 派生的 scoped/authed 数据接口执行引擎(查询编译/relation/codec)
│ ③ 平台固定表(apps/users/authTokens/customHostnames)的 Drizzle SoT
↑
@agentaily/backend (瘦身、去 vertical):鉴权 + 公开运行时桥(runtime.ts)+ host 解析(publish.ts)
│ + 通用「带 scope+auth 的 submit/query 编排」(吃 runtime 接口,不 import 任何 vertical/schema)
↑
@agentaily/market (= @agentaily/presets 升级 + 改名,★核心价值):
│ AppEntry{id,name,description,aml,indexHtml,preview,author,forkedFrom,tags}
│ list/get/search/publish/fork;服务端存储(D1 元数据 + KV/R2 存 blob);form/slides 种子;预览截图
↑
@agentaily/agent (工具集重写):write_aml/read_aml(接 aml parse+validate 回灌自修)·
│ write_index_html/read_index_html · screenshot · market_search/preview/fork · data_query;
│ DEFAULT_SYSTEM 整段重写;loop/memory/use_skill/vision 保留;+ 一份「市场用法」文本 skill
↑
apps/web(产品壳:ChatApp + 市场浏览/部署/发布 UI;去 schema/vertical;预览 iframe 加 sandbox)
apps/publish(UGC 独立源沙箱运行时,保留;补 per-app scope + wildcard/for-SaaS 基建)
正交保留: design-system · i18n · theme · persistence · llm · core/apiClient · core/auth一句话边界: aml(纯产物)→ runtime(一切 D1 I/O + scoped 数据引擎)→ backend(鉴权+桥+编排)→ market(核心价值)→ agent(工具,领域无关、靠注入)→ apps(壳+UI+沙箱)。llm/apiClient/auth 两条正交基建不动。
3. 全仓 keep / rework / cut / rename 表(覆盖所有包)
| 包 / 目录 | 决定 | 理由 |
|---|---|---|
@agentaily/aml | keep(核心) | 升为后端数据唯一真相源;emitter 已就绪,只需被 runtime 消费 |
@agentaily/llm | keep | 模型网关,与数据模型正交 |
@agentaily/i18n · theme · persistence · design-system | keep | 纯基建,零内容模型耦合(persistence 安全注意:平台鉴权态禁用 .agentaily.com cookie) |
@agentaily/db | rework + rename → @agentaily/runtime | 职责变:从 JSON-Schema 派生表 → 吃 aml EmitResult provision + scoped 数据引擎;固定表 Drizzle 瘦身保留 |
@agentaily/backend | rework | 去 VERTICALS 硬编码 + 去 recordSchemaFor/checkRecord(依赖已砍的 zod);留鉴权 + 桥 + host 解析 + 通用 scoped 编排 |
@agentaily/presets | rework + rename → @agentaily/market | AppEntry 数据模型(+aml +preview)、加 search/fork、换服务端存储;★核心价值 |
@agentaily/agent | rework | 工具集从 studio_(内容文档/renderer)→ write_aml/write_index_html/market_/screenshot;系统提示重写;loop 不动 |
@agentaily/eval | rework | 断言面从「zod 派生 schema」转到「aml 模型 / 生成的 index.html / 实际数据」;headless 引擎重做 |
studio/protocol · studio/client | cut(仅抽 screenshot) | index.html 沙箱直跑后不需要 postMessage RPC;唯一留下的是 html2canvas screenshot → 移进 market 做预览 |
@agentaily/form-schema · slides-schema | cut | zod 内容 schema 已拍砍 |
@agentaily/form-studio · slides-studio | cut | iframe-host-renderer 被 index.html 沙箱直跑取代(screenshot 逻辑先抽走) |
verticals/* 整层 | cut / 降级 | 无 verticals;form/slides 的 aml+index.html → 市场两条种子 AppEntry(配预览截图),源料可留 market/seeds/ |
@agentaily/agent-ui | cut | 自标 legacy,已被 apps/web React UI 取代;仅 LocalStorageStore 上移进 @agentaily/agent |
apps/web | rework | App.tsx 去 VERTICALS/staleSchema/documentSchema/connectStudio;加市场/部署/发布 UI;预览 iframe 加 sandbox |
apps/publish | keep | UGC 独立源沙箱,正是目标;补 per-app scope + 基建 |
apps/website | keep(文案更新) | 官网正交;文案从「Form 官网」更新到「平台/万物」 |
4. 数据流改造(旧 → 新)
- 旧:
PUT /api/projects/:id→recordSchemaFor(内容文档派生 JSON Schema)→ensureDataTable建data_<projectId>(每字段一列)→ submit 经checkRecord闸 → 按 record_schema 映射列 INSERT。 - 新:app 创建 / aml 变更时 → 读 app 的
aml→emit()→ runtime 跑 DDL provision app 的固定表(form = 通用Submission{answers Json}单表)→ submit/query 经 从ApiDescriptor派生的 scoped/authed 接口,服务端强制注入project_id/owner_id谓词(绝不信前端传的 id)→ 入/查共享库里该 app 的行。 - checkRecord 闸:保留但换源 —— 不再用 zod 内容 schema,改成基于 aml 的类型/required/enum 校验(emit 已带这些);不能裸 INSERT LLM 页面提交的任意 payload(安全/完整性最后防线)。
5. 安全 / 沙箱模型(命门)
- index.html 一律沙箱:统一走
apps/publish独立源 Worker serve(已建);让同源/f/:id退场;预览态(authoring)给 iframesandbox="allow-scripts"(不给allow-same-origin)→ LLM 脚本读不到平台 localStorage/Bearer;截图走 postMessage(Studio 自截自身 DOM)不依赖同源。 - 每 app 数据隔离 = 共享单库 + 行级 scope(不是每 app 一个 D1 库 —— CF D1 数量受限、百万 app 不可行):每张表带
project_id(+owner_id)列 + 索引;数据接口每个 resolver 强制带project_id谓词(由已认证上下文 / host 解析决定,绝不由请求体决定)。 - 两条入口分清:owner 看自己数据 = 平台 session JWT(
requireSession+ 校验app.ownerId===session.sub,现在完全缺,必修);陌生人公开提交 = host 解析出的 app-scope + 匿名写权限 + 限频。 - CF for SaaS / 自带域名:
custom_hostnames(hostname→appId)已在 publish 设计;自带域名仍按 appId 落同库对应行,隔离模型不变。 - 铁律:平台会话绝不设
Domain=.agentaily.comcookie(否则 UGC 子域可读注入);persistence只存主题/locale 这类无害偏好。
6. 待你锁的决策(带我的推荐)
- 存储粒度 —— 早先设想「每垂类固定表」,但 per-app 市场模型下改为推荐 共享单库 + 每 app 行级 scope(project_id/owner_id 列 + 强制谓词)。✅ 推荐这个(复用现有 D1/部署、隔离够、不爆库)。
- R6/R7 冲突 —— 目标「LLM 生成 index.html + fork + 预览」否决 ROADMAP 早先的 R6/R7「LLM 不生成 index.html、退役 setRenderer」。✅ 推荐:认目标、作废 R6/R7,ROADMAP 那节重写。
- Studio 协议命运 —— index.html 沙箱直跑后 postMessage RPC 基本没用;✅ 推荐 studio-protocol/client 整体退役,只把
screenshot机制抽进@agentaily/market做预览(比「砍到剩两条缝」更干净)。 - GraphQL 程度 —— ✅ 推荐延续你早先定的「GraphQL 精神」:从
ApiDescriptor自动生成自描述、类型化接口即可,不上 graphql-yoga(运行时更轻;要生态再后插)。 - 命名 —— ✅ 推荐
db→@agentaily/runtime、presets→@agentaily/market(语义对齐职责)。 - 2 个 bug ——
补✅ 已修(PR #3);仍待办:审计平台鉴权不用pnpm-workspace.yaml的- theme/- persistence.agentaily.comcookie。
7. 重构工单(可认领,worktree + PR,带依赖)
| # | 工单 | 依赖 | 产出 |
|---|---|---|---|
| M0 | 锁 §6 决策 + 修 2 bug + 重写 ROADMAP 收敛节(作废 R6/R7) | — | 决策定调 + workspace 补全 |
| M1 | @agentaily/runtime(db 重做):aml EmitResult → D1 provision + 迁移;固定表瘦身 | aml(✅)、M0 | 通用建表/迁移引擎 |
| M2 | runtime 的 scoped/authed 数据接口引擎(从 ApiDescriptor 派生 + 强制 project_id 谓词) | M1 | 数据查询/写入引擎 + 鉴权 |
| M3 | @agentaily/market(presets 升级):AppEntry + search/fork + 服务端存储 + 种子 + 预览截图(抽 screenshot) | M0 | 市场(核心) |
| M4 | 安全/沙箱:apps/publish per-app scope;预览 iframe sandbox;/f/:id 退场;数据 API 补鉴权 | M2 | 沙箱 + scoped 上线安全 |
| M5 | @agentaily/agent 工具重写(write_aml/index_html/market_*/screenshot)+ 系统提示 + 市场 skill | M2,M3 | 会生成 & fork 的 Agent |
| M6 | apps/web:市场浏览/部署/发布 UI;去 VERTICALS/schema/connectStudio;接新工具 | M3,M5 | 产品壳 |
| M7 | ✅ 完成 — 砍 verticals(schema+studio)/studio-protocol/client/agent-ui;删旧 App.tsx(990 行 studio 工作台)+ agent 的 studioTools/studio DEFAULT_SYSTEM;eval 重做 + backend 去 vertical(#33);assemble-pages 退役(无 studio 可汇入) | M5,M6 | 清旧主线 |
| M8 | 文档对齐:重写 ARCHITECTURE.md/SPEC.md 到终态;各包 README;apps/website 文案 | 跟随 | 真相源对齐 |
建议顺序:M0 先(锁决策)→ M1→M2(runtime 引擎,核心)→ M3(市场,可与 M1/M2 并行)→ M4(安全)→ M5(agent)→ M6(壳)→ M7(清旧)→ M8(文档)。M1/M2 是最大、最该先砸的硬骨头。
8. Agent 持久化层 @agentaily/agent-store(2026-06-20 调研后补,配套 M5)
调研结论(对标 Claude Code + LangGraph / OpenAI Agents SDK / Claude Agent SDK / CrewAI):
- ✅ skill 加载我们没错 —— 渐进式披露(system prompt 只列 name+description,正文经
use_skill按需拉),和 CC 的 L1 元数据 / L2 正文 / L3 资源三层 + 各框架共识一致(工具规模化的终极答案 RAG-over-tools 也是同一思想)。 - ⚠️ 两个工程缺口:① session/会话记录没有任何跨会话持久化(对话只活在 loop 私有
messages[]+ React state,D1 无 conversation 表,刷新/切项目即丢;loop 私有持有 messages、无注入/rehydrate seam → 违反「loop 与持久化解耦」这条普遍最佳实践)。② memory 只有 localStorage 全局单例(不分 user/project、不上云、未分短期/长期)。
方案 = skill + memory + session 抽成独立内部包 @agentaily/agent-store(职责分离):
- 装:
MemoryStore+Skill/useSkillTool+ConversationStore(新,补 session;接口对标 checkpointer/SessionABC:list/create/load/append/remove) + 默认实现(InMemory / LocalStorage)。 - loop 留
@agentaily/agent,改为依赖 agent-store 接口;messages从私有字段 → 经ConversationStore注入 + 可 rehydrate。 - 依赖:
llm ← agent-store ← agent ← apps/web;db 不进 agent-store(别拖进浏览器 bundle),D1-backed 实现做适配器(apps/web 或@agentaily/agent-store-d1)。 - session 持久化补缺:D1 加
conversations+messages表(owner-scoped,照projects+requireSession那套),会话升一等公民;长上下文后续加 compaction。 - 命名:不叫
agent-kernel/runtime(=loop 本身)、不叫agent-persistence(撞@agentaily/persistence)、不叫agent-memory(太窄)。 - 落地:先抽 agent-store(接口 + 本地实现,低风险)→ loop 改注入式 + rehydrate → 补 D1 会话表/端点上云;与 M5 配套(作 M5 前置或并入)。
8.1 整个 agent 层的包拆分(2026-06-20,扣死「客户端 loop」特色)
前提:loop 跑【客户端】(BYOK 浏览器直连,无服务端代跑模型)。 Anthropic/OpenAI 支持浏览器直连(anthropic-dangerous-direct-browser-access / dangerouslyAllowBrowser)→ 调模型 / 派子 agent / 压缩全在前端 fetch+SSE;后端(CF)只做轻持久化 +(可选)给不发 CORS 的 provider(火山 Ark)做代理。
4 个包,单向依赖:
@agentaily/llm 模型网关(BYOK 浏览器直连/流式/Message 类型) 【已存在】
↑
@agentaily/agent-store 状态/持久化层(可插拔 seam + 浏览器默认实现):
SkillStore · MemoryStore(InMemory/IndexedDB) ·
ConversationStore(会话,补 session 持久化) · 压缩摘要存放 【新】
↑
@agentaily/agent loop:Agent 类 + 工具(use_skill/remember/aml/market…) +
sub-agent 编排(agent-as-tool,子 loop 跑 Web Worker) +
压缩策略(首选服务端 compaction,退路客户端自摘要);依赖 store 接口 【重写=M5】
↑
@agentaily/agent-react React 绑定:AgentEvent → design-system 组件 +「父流+子 loop 折叠子树」编排容器 【替 agent-ui】
↑
apps/web 注入具体实现(D1 会话 store=走轻后端的 fetch client;BYOK key UI)三块能力(都客户端):
- Sub-agent:走 agent-as-tool(编排者保持控制、子 agent 只回最终结果),少用 handoff。每个子 agent = Web Worker 里独立 loop(独立
messages[]全新窗口 + 裁剪工具集 + 可降级便宜模型),postMessage 回结果。并行 ≤ 2–3(单域 6 连接 + 单 key 限流)。 - 压缩:首选 Anthropic 服务端 compaction(beta
compact-20260112,加头即可,前端只存返回的 compaction block);要 provider 无关再退客户端自摘要(混合:摘要 + 最近 N 条,system 永不丢,保住 tool_use↔tool_result 配对)。 - UI:design-system 已有
ai/Task/ai/ToolCall/ai/Reasoning/ai/Context(上下文用量计)/ai/Checkpoint/display/Collapsible|Progress;只缺「父流 + 子 loop 折叠子树」编排容器(放 agent-react)。
客户端特有的坑(必须设计进去): 刷新丢状态→IndexedDB 增量落盘;浏览器 6 连接/域硬上限;子 agent 跑完丢内部 transcript 只留摘要(省内存=压缩目标);BYOK 429→退避重试 + UI 明示限流;key 是用户自己的、只存浏览器本地、不进日志。
对 M5/M7 的影响: M5(agent 重写)= agent-store 抽包 + agent loop 重写(注入式 + sub-agent + 压缩)+ 工具重写;M7 的「砍 agent-ui」改为 agent-ui → 重写为 @agentaily/agent-react。落地序:agent-store(低风险先行)→ agent loop 注入式改造 + rehydrate → sub-agent(Web Worker)→ 压缩(先服务端)→ agent-react UI 编排容器 → D1 会话表/端点上云。
9. App 文件模型(2026-06-20 定:零构建 VFS)
一个 app 的前端 = 一组静态文件(客户端 VFS),沙箱 iframe 里直接 serve、运行时无构建步骤(LLM 写完即跑)。
index.html收敛为唯一一个(入口)。- 允许的文件(浏览器原生):
.css(<link>)·.js含 ES module(<script type=module>+ 相对import)·.json· 资源(.svg/.png/.woff2…)。 - 用第三方库:
index.html里写 import map → CDN ESM(esm.sh/skypack),import React from "react"无需打包;要 JSX 式手感用htm(标签模板,零构建)。 - 不支持
.jsx/.tsx/.ts:浏览器不认 → 会逼出构建步骤;否决(避免:publish 端塞 bundler、构建时长/失败态、npm 供应链进沙箱、破坏「写完即跑」)。 - publish 沙箱运行时 = 纯静态 serve 整组文件(不转译);
index.html用相对路径引用同 app 内其它文件。
落地点(都不改 VFS 工具本身,工具是文件类型无关的):
APP_BUILDER_SYSTEM提示词:教模型「1 个 index.html + 按需 .css/.js(ESM)/.json;用 import map + CDN ESM 引库、htm 写组件;别写 .jsx/.tsx、别假设有构建」。- publish 运行时 / apps/publish:serve 整组文件(index.html 入口 + 引用的 css/js/资源),不加任何转译/打包。
- AppEntry(market):
indexHtml: string→files: Record<path, content>(承载这组文件)。