状态:owner 已批准。T1、Hybrid Runtime V2 与 cache-first 首次生成已实施;设置总开关和运行时遥测仍作为后续独立事项。
首次生成与运行时 patch 是两条独立链路:首次生成由 GenerationOrchestrator 负责
fingerprint、成品缓存、本地 blueprint、Instant 单轮和显式 Agentic 精修;运行时
交互仍由 RuntimeSessionStore + RuntimeInteractionCoordinator 负责 revision 和
最小目标 patch。缓存命中只复用编译后的成品,不复用任何窗口 session 或模型历史。
旧 /continue 能力继续服务 html-single-file V1 制品;新生成应用默认使用
openos-markup V2。V2 不再允许片段脚本,而是由预载 UI Kit 和 ActionRuntime
承担通用行为,由 /api/gen-apps/:id/interact 承担语义交互。
每个 draft/launch 返回独立 runtimeSessionId。服务端 RuntimeSessionStore 保存
窗口级权威 markup、revision、interactionMode 与最近 6 轮模型上下文,并设置
30 分钟 TTL 和 300 会话容量。补丁协议固定为一个最深层目标:
{
"baseRevision": 1,
"revision": 2,
"ops": [
{ "op": "replace", "targetId": "results", "html": "<section id=\"results\">...</section>" }
]
}安全与性能不变量:
- iframe 回传的
data-target/currentHtml不作为权威事实;服务端按元素 id 从会话标记重解析,快照仅在重新编译成功后作为模型上下文。 - V2 patch 必须匹配当前 revision、服务端选择的 target,并通过 parse5 声明式清洗;无效模型输出只修复一次。
- 所有
data-target、data-source、for与 ARIA ID 引用在整图合并后复验;制品最多 2,000 个元素节点。 - 固定 Shell CSP 只放行 nonce 标记的可信脚本和样式;流式原文在 iframe 内再次清洗后才挂载。
- 搜索由浏览器与 Bridge 复用同一确定性语义目录:浏览器按内存设置快照同步给出完整候选,不再发候选请求;Bridge API 供其他调用方毫秒级生成。两条链路都不调用 LLM。
- 正常交互只传单目标 patch 且每窗口串行;目标被本地动作移除时 iframe 请求一次全量 resync。服务端 revision 领先时用 409 权威快照对齐并明确提示重试,不把未执行动作误报成功;只有 session 不存在时
/resume才接受宿主快照并重建会话。 - 最近一次验收:真实候选 API 冷查询中位数 2.18ms、P95 28.41ms,浏览器 6 个候选 10.4ms 可见且零候选请求;deterministic fake 首块 2ms、完整生成 275ms、补丁 6ms、V2 线载荷相对编译 Shell 减少 82%,revision 恢复通过;制品生成的真实模型延迟仍由供应商决定,应继续按 P50/P95 观测。
AppRecipe -> EngineRegistry 当前提供 game.minesweeper、game.sudoku 和
game.snake。模型只参与候选文案;选中后由本地 recipe 直接组装声明式棋盘。
ActionRuntime 负责扫雷布雷/扩散/胜负、数独输入校验/完成状态,以及贪吃蛇方向键、
定时 tick、食物、碰撞、计分、暂停和重开。游戏不使用模型 patch,不允许任意脚本
或 canvas,动画使用预载可信网格运行时,因此生成毫秒级、交互本地 0ms。
V2 新增声明式 web.search 动作。生成应用仍受 connect-src 'none' 约束,不能直接
访问公网;ActionRuntime 只把元素 id 和输入值转发给宿主,服务端从权威 markup
重新解析 data-target/data-source,再通过 WebSearchProvider 固定出口检索。
当前搜索适配器使用 Bing RSS,结果经过结构化 XML 解析、协议白名单和 HTML
转义后,直接编译成目标区块的 revision patch,不经过 LLM。每条结果提供
web.open 业务按钮;网页读取器只允许标准 80/443 端口,解析并固定公开 DNS/IP,
逐跳复验重定向,限制 HTML 类型、512KB 体积和 12 秒预算,最终只提取纯文本标题、
描述和正文,不把远端 HTML/脚本送入 iframe。输入 Google、Bing 或
DuckDuckGo 首页 URL 时先生成搜索入口;带 q 参数的搜索 URL 或普通关键词直接
检索。任意其他 URL 不由服务端抓取,避免 SSRF。网络搜索沿用每应用运行时频控、
窗口串行、取消和 revision 冲突保护,并在结果中明确标注真实提供方。
参考对象:Steve Sanderson 的 Vibe OS 演示——纯幻觉 UI,每个窗口绑一个持续的 Copilot session(对话记忆即状态),交互靠「发元素 id → 模型回 diff → 局部更新」实现速度与连贯性。 我们保留真实执行的核心架构(不改),但吸收了它两个有价值的点:
/continue 从完全无状态单发,改为按「会话流」维护 CoreMessage 历史
(ContinueSessionStore,进程内、有界增长:每会话最多 6 轮、总会话数上限
300、30 分钟闲置回收、单轮历史文本截断 4000 字符防 token 爆炸)。
会话分组:显式 sessionId 优先;update intent 默认按目标元素分组
(update:${targetId});其余按 intent 分组——单地址栏浏览器的连续导航
天然共享一条上下文,应用代码不需要自己管理 sessionId;只有需要多个并行独立
会话(多标签页)时才显式传不同 id。
效果:生成式浏览器多次跳转后,模型记得自己虚构过的站点结构/品牌/数据, 不会每次点击都重新脑补出不一致的内容(E2E 验证:品牌名、CSS 类名前缀、 导航结构跨两次导航保持一致)。
不做字面 HTML diff 算法(parse+patch 在无 DOM 的服务端环境复杂且难安全校验), 而是「发送目标元素当前完整标记 → 模型返回替换后的同一元素 → 客户端原地 outerHTML 替换」——收益等价(只重新生成变化的部分,不必重建整个容器), 实现和校验都更简单。
await OpenOS.update({ targetId: "hero-card", instruction: "标题改得更耸动" });新增 intent update;buildContinuePrompt 要求模型只输出替换元素本身
(保留原 id),不输出周围内容、不解释。
生成的应用不再是一次性静态制品,运行中可继续生成:
- 生成浏览器:地址栏输入任意 URL / 搜索词 → 该"网页"由模型现场生成,可继续点击页内链接跳转(WebSim 式体验)
- 按需弹窗:点击应用里的设置按钮 → 设置面板此刻才生成
- 生成式搜索/输入框:应用内搜索框的结果、推荐内容由模型按需产出
核心理念:首次生成只做应用骨架,深层内容延迟到用户真正触达时生成——生成得更快、应用可以"无限深"。
当前沙箱 iframe:sandbox="allow-scripts" + CSP connect-src 'none'——应用完全无网络、无宿主访问(安全边界正确,不能破坏)。缺口:应用运行期没有任何合法通道请求再生成。
┌─ 沙箱 iframe(生成的应用)──────────────────────────┐
│ App 代码 ──调用──▶ OpenOS.generate(payload) │
│ (编译期注入的运行时 SDK) │
└──────────────────────┬──────────────────────────────┘
postMessage RPC(唯一出口,白名单协议)
┌──────────────────────▼──────────────────────────────┐
│ GenAppRunner(宿主中继) │
│ 校验 event.source / schema / 频控 → Bridge API │
└──────────────────────┬──────────────────────────────┘
POST /api/gen-apps/:id/continue
┌──────────────────────▼──────────────────────────────┐
│ Server:ContinueService │
│ 应用上下文 + intent 约束 → llm-core 流式单轮生成 │
│ → fragment 轻量清洗(去外链/限体积)→ 返回 │
└─────────────────────────────────────────────────────┘
编译制品时内联注入 openos-runtime 脚本(先于应用代码):
window.OpenOS = {
/** 返回 Promise<string>(生成的 HTML 片段或文本) */
generate({ intent, prompt, context, format }) { /* postMessage + requestId 等待应答 */ },
};intent:语义标签(browse/panel/search/content),服务端据此挑系统提示词模板prompt:应用给的生成指令(如浏览器输入的 URL)context:可选补充(当前页面摘要等,限长)format:html-fragment(默认)|text|json
iframe → host: { type: "openos:generate", requestId, payload }
host → iframe: { type: "openos:result", requestId, ok, fragment? , error? }
宿主校验:event.source === iframe.contentWindow(只认自己窗口)、schema 严格解析、单应用并发 1 + 频控。
- 单轮快速生成(不走 agent 多轮):低温度、流式(llm-core 已具备)、
maxOutputTokens默认 4k - 系统提示词 = 应用身份上下文(名称/描述/来源搜索词/沙箱规则)+ intent 模板:
browse:'你是这个生成式浏览器的网页引擎,为给定 URL/搜索词生成一个完整可读的网页正文片段,页内链接用data-href标注(app 拦截后继续 generate)'panel:'生成一个内嵌面板/弹窗片段,风格与宿主应用一致'
- 产出过 fragment 轻量清洗:复用校验器的外链规则(fatal 即拒)、体积上限(如 256KB)、剥
<html>/<head>外壳只留片段
- iframe 仍然无网络(CSP 不动);唯一出口是 postMessage → 宿主白名单 RPC → 本机 Bridge
- V1 fragment 与旧制品同沙箱运行;V2 fragment 强制声明式、禁止脚本,并由可信 ActionRuntime 承担行为
- 清洗只拦「会失效/超限」项(外链、体积),不做无意义的假净化
- 配额防滥用:每应用每分钟 N 次(默认 6)、单次输出上限、设置页总开关
生成主应用时告知 SDK 的存在与适用场景:
应用可调用
await OpenOS.generate({intent, prompt})在运行时继续生成内容。 适用:浏览器式导航、按需面板、生成式搜索结果。 不适用:普通计算/状态更新(本地 JS 完成)。调用期间必须渲染页面内 loading 态。
模型自行决定哪些交互挂运行时生成——简单工具依旧纯本地,不强加。
| 配置项 | 默认 | 说明 |
|---|---|---|
| 运行时生成开关 | 开 | 设置 → AI 应用 |
| 频控 | 6 次/分/应用 | 服务端强制 |
| 单次输出上限 | 4k tokens | intent 可微调(browse 8k) |
| 温度 | 0.4(browse 0.7) | 内容类略高 |
| Tracer | 内容 | 验收 |
|---|---|---|
| T1 | SDK 注入 + Runner 中继 + /continue 端点 + browse/panel 两个 intent 模板 + 主 prompt 增补 |
生成"浏览器"应用,输入词条能出页、页内链接能续跳 |
| T2 | 频控/配额/设置开关/错误态(频控命中时 app 收到 error 并展示) | 超频返回 429 语义错误 |
| T3 | fragment 清洗强化 + loading 规范 + 遥测(continue 次数/耗时入日志) | 校验器冒烟含 fragment 规则 |
- token 消耗放大:浏览器应用重度使用时每次导航都是一次生成——靠频控 + 设置开关 + 单次上限兜底;后续可加会话级预算
- fragment 质量:单轮无修复循环,可能偶发烂片段——app 端 loading/错误态由主 prompt 要求,烂片段重试成本低(再点一次)
- 旧应用不受益:历史制品没有 SDK 调用——无碍,重新生成即得