diff --git a/APP架构设计.md b/APP架构设计.md index d7e4b87..39292b3 100644 --- a/APP架构设计.md +++ b/APP架构设计.md @@ -225,7 +225,6 @@ interface LineUpMiniAppSDK { inbox: MiniAppInboxAPI; tools: MiniAppToolAPI; - ui: StandardInteractionAPI; lifecycle: MiniAppLifecycleAPI; surfaces: MiniAppSurfaceAPI; capabilities: MiniAppCapabilityRequestAPI; @@ -270,38 +269,79 @@ interface MiniAppToolAPI { `call_id` 的实例归属、合法状态迁移、输入/输出 schema、幂等性和权限,然后持久化、审计并 写入 outbox。 -### 7.3 标准交互、Lifecycle、Surface 与 Capability +一旦 Runtime 接受某个 App instance 的关闭,关闭表示释放该 instance,不是把它留在后台:Runtime 停止 +向它投递新普通 Tool,将仍为 `received / routing / waiting_for_app / running` 的绑定 Tool 原子收敛为 +`cancelled(app_closed)`,每条 Tool 只写一条 cancelled outbox;已 `submitted` 的结果不可改写,继续可靠发出。 +然后才关闭 Surface、停止 instance、结束 App 子会话并恢复焦点。普通 Tool 的完成或失败本身不关闭 App。 +标准交互是例外:App 关闭只通知 Agent,不自动取消仍 pending 的人与 Agent 交互。 -Runtime 通过 `sdk.ui` 提供 `notice`、`choice`、`confirm`、`input` 四种标准交互原语。这是由 -Runtime 统一拥有的标准输入输出库:Runtime 创建和持久化交互记录,注入 owner、校验请求/结果、 -负责恢复、审计与路由,并依照 Shell Policy 决定呈现位置。 +### 7.3 人与 Agent 标准交互、Lifecycle、Surface 与 Capability -`owner` 不属于 SDK 调用参数。Runtime 仅从 SDK 已绑定的 `MiniAppContext` 注入 -`app_scope`、`instance_id`、`conversation_id`,并可带有已验证的 `parent_call_id`、`surface_id` 与 -来源;MiniApp 不可伪造、覆盖或访问其他 owner 的交互。`presentation` 只能作为 `inline`、`modal` -或默认形式的偏好,最终仍由 Runtime/Shell 决定。 +`notice`、`choice`、`confirm`、`input` 是 Interact 的人与 Agent 交互组件,与 IM 消息同属于 +当前会话;它们不是 MiniApp SDK,也不用于 MiniApp 自己的业务表单、确认或输入。Agent 通过 +标准 Tool 交互请求发起它们,Runtime 校验、持久化、恢复、审计并可靠回传结果,Interact 负责呈现。 -Runtime 内部至少持久化以下归属信息: +这四种是普通的人与 Agent 会话交互,不在 SDK v1 中统一定义为密码或秘密输入。已提交内容遵循普通 IM +的会话历史、保留和日志基线;尤其不能因为它们是普通输入,就突破全局“日志不记录会话正文、Tool 参数、 +token 等内容”的约束。未提交 `input` 草稿仅保留在当前运行期,重启清空。密码、卡密、私钥、临时 token +等真正秘密输入留待未来作为独立的 `password-input` 原语设计;届时 Runtime 必须按类型强制其展示、记录、 +传递和清理规则,Agent 不能用普通 `input` 绕过这些保护。 -```ts -type StandardInteractionOwner = { - app_scope: string; - instance_id: string; - conversation_id: string; - parent_call_id?: string; - surface_id?: string; - source: "agent_tool" | "miniapp_tool" | "runtime_policy"; -}; -``` +每条人与 Agent 的标准交互都必须在 Interact / IM 中留下相应的卡片、气泡或结果记录。`notice` 是只读 +IM 卡片;当前界面可同时短暂 Toast 提醒,但 Toast 只是同一条记录的辅助呈现,不能作为唯一消息或用户 +已阅读的证明。Runtime 成功持久化 notice 卡片并交给 Interact 呈现后,即可向 Agent 返回 `accepted`。 -Agent 在 IM 中的提问由 Interact 作为默认可信 Renderer Provider 呈现在 IM 时间线/卡片内。前台 -MiniApp 自己请求的低复杂度事务提示,可在该 MiniApp 的有效容器或 Surface 中以 Runtime 批准的 -标准 modal、sheet 或 card 呈现,不能因此强制切换到 Interact。结果始终先返回 Runtime,再仅投递 -给 owner MiniApp;MiniApp 不可自行创建 Host 弹窗或直接将结果发送给 Agent。 +MVP 中,一个 LineUp Runtime 只连接一个 Agent,当前登录会话的 `agent_uid` 作为每条交互和 +App 子会话的 `agent_id`。此处保存 Agent 身份是为了让答案按创建时的上下文回到正确对象;本阶段 +不实现多 Agent 连接、切换、会话列表、outbox 或跨 Agent 路由。 -复杂、高频或私有的业务 UI(例如 Whiteboard 画布文字编辑、画笔、颜色选择、拖放和工具栏)留在 -MiniApp 自己的 Surface 内,不使用标准交互库。系统权限确认(文件、麦克风、通知等)则属于 -Runtime/Host Capability Gateway,而不是普通 `confirm`。 +Agent 发 interactive Tool 时,未带经过 Runtime 验证的 `app_session_context.app_session_id`,一律归入主 IM; +Runtime 绝不能按当前前台 App 猜测归属。若带有该上下文,Runtime 只接受同一 Agent、同一父会话的真实 +App 子会话;已经关闭的子会话可保留为历史上下文,但不复活旧 App。Runtime 创建子会话时会可靠告知 Agent +其稳定 ID,非法或跨会话的引用直接拒绝且不创建交互。 + +当 Interact 位于前台时,标准交互显示在 IM 时间线/卡片中;当 bundled MiniApp 位于前台时, +Interact / Shell 可以在当前 App 之上显示同一会话的交互层。视觉位置不改变归属:请求和结果始终 +属于当前 conversation、Agent call 与 Interact instance,结果经 Runtime 回传 Agent,而不交给 +前台 MiniApp。 + +用户作答时,Interact 只把“交互 ID + 用户动作/答案”交给 Runtime,不直接把答案发送给 Agent。Runtime +从创建时保存的记录取得 Agent、主会话、App 子会话和 Tool 的归属,并核对当前 LineUp / Interact 会话、 +有效期、答案格式和是否已结束。只有第一次有效回答可以写入会话历史和可靠 outbox;后续重复或重放提交 +不得改变结果,也不得再次通知 Agent。展示位置不是交互的 owner 或提交权限:App 前后台切换、关闭或从 +覆盖层改在 IM 显示,都不改变交互归属。客户端不能提交或改写 Agent、会话、Tool 或 App 子会话的归属字段。 + +用户回答、Agent remote dismiss、到期和 Runtime 失败都只能竞争同一条交互的唯一终态。Runtime 以第一个 +成功完成的原子状态写入为准,并同时持久化唯一 Tool 结果/outbox;后来到达的动作不得覆盖结果或再次通知 +Agent。MVP 中 interactive Tool 的等待期限与交互的 `expires_at` 相同,Agent 主动停止等待必须走 Runtime +私有的 `interaction.dismiss(control_id, call_id)`:Runtime 从受认证 Envelope 推导 Agent 和会话,以 `call_id` +定位交互。重放或目标已终态只返回稳定幂等回执,绝不再写 outbox;该控制契约不属于 MiniApp SDK。 + +启动一个 App instance 会在主 IM 会话中创建或恢复与之关联的 App 子会话,并可靠向当前 Agent 发送 +`app_session.opened`,提供 `agent_id`、`conversation_id`、`app_session_id`、`app_scope` 与 `instance_id`。 +主 IM 将其显示为可展开的 +折叠组,记录 Agent 的提问、用户回答和 Agent 的简洁结果;它不记录 App 内部业务操作,例如用户在 +Task Dashboard 点击按钮或填写 App 自己的表单、在 Whiteboard 绘制和编辑内容。App 进入后台时, +子会话仍继续;Agent Tool 完成也不自动结束子会话。只有 Runtime 的 `AppLifecycleManager.close` 使 +instance 进入 stopped 或 failed、并从 focus stack 移除时,子会话才结束但保留为主 IM 的历史。新的 +App instance 必须创建新的子会话,不接续旧实例记录。 + +已结束子会话可在主 IM 中只读展开,但不会重新启动 App、重新执行操作或复活未回答的问题。用户选择 +“继续处理”旧工作时,Runtime 启动新的 App instance 和新的子会话;新子会话可以引用旧会话、artifact +或已保存的 App 状态作为上下文,但不能向旧子会话追加记录。 + +若 App 子会话关联等待回答的 Agent 问题,App 位于前台时 Interact 可在其上方显示提问层;用户切换到 +其他 App 或普通 IM 时,该层收起但问题继续等待,主 IM 的对应折叠组标记“等待你的回答”。用户既可 +展开该组直接回答,也可回到原 App 后回答。Shell 可避免用户看到重复的视觉卡片,但展示位置不改变交互 +归属或提交资格;Runtime 通过首次有效回答和终态检查防止重复结果。真正关闭 App 会按普通 Tool 的 +`app_closed` 规则先收敛未终态 Tool,随后移除该 App 上方的展示层并结束 App 子会话。Runtime 将关闭事件 +(含仍 pending 的 interaction call_id)可靠通知 Agent;问题本身仍属于 Interact / 主 IM,直到 Agent 远程 +dismiss、用户回答、到期或 Runtime 失败才结束。 + +因此 bundled MiniApp 不能发起、读取、提交或取消这类 Agent 交互,也不因其显示在自身上方而获得 +Host DOM 或会话数据。复杂、高频或私有业务 UI(例如 Task Dashboard 的“新建任务”表单、Whiteboard +画布文字编辑、画笔、颜色选择、拖放和工具栏)必须留在 MiniApp 自己的 Surface 内。系统权限确认 +(文件、麦克风、通知等)则属于 Runtime/Host Capability Gateway,而不是普通 `confirm`。 ```ts interface MiniAppLifecycleAPI { @@ -311,8 +351,8 @@ interface MiniAppLifecycleAPI { } ``` -MiniApp 只能请求生命周期变化;Runtime 决定是否允许,处理 pending Tool/Surface,并负责 -焦点恢复。 +MiniApp 只能请求生命周期变化;Runtime 决定是否允许。若接受关闭,则按上面的 `app_closed` 收敛普通 Tool、 +处理 Surface 并恢复焦点;若用户只想暂时离开,应请求后台化。 Surface 与 Capability 也只能请求: @@ -331,20 +371,26 @@ MiniApp requestOpenSurface / requestCapability MiniApp 只能在 Manifest 中声明 Tool;Agent 只能调用当前 Runtime 已公布 Inventory 中的 Tool。 ```ts -type ToolDescriptor = { +type ToolDescriptorBase = { id: string; // 例如 task-dashboard.open version: 1; - handling: "direct" | "interactive" | "launch" | "foreground" | "operation"; - target: { - app_scope: string; - requires_foreground: boolean; - restore_previous_focus: boolean; - }; input_schema: JsonSchema; output_schema: JsonSchema; permissions?: readonly string[]; - timeout_ms?: number; }; + +type ToolDescriptor = ToolDescriptorBase & ( + | { handling: "interactive"; target?: never } + | { + handling: "direct" | "launch" | "foreground" | "operation"; + target: { + app_scope: string; + requires_foreground: boolean; + restore_previous_focus: boolean; + }; + timeout_ms?: number; + } +); ``` 统一调度路径: @@ -354,15 +400,19 @@ Agent Tool Invoke → Runtime 校验 Envelope、Inventory revision、Tool Descriptor、参数、scope、App 状态和权限 → 创建可持久化 Tool Call / 审计记录 → Tool Router 判定 direct / interactive / launch / foreground / operation -→ 投递到目标 MiniApp instance + ├── interactive:不使用 target、不创建业务 instance,只由 Runtime 建立 Interact 会话交互 + └── 其他 handling:投递到目标 MiniApp instance → MiniApp SDK 报告 progress / result / error → Runtime 校验输出 schema、持久化、更新焦点、写 outbox → Agent 收到可靠结果 ``` -`notice`、`choice`、`confirm`、`input` 是 Runtime 的统一 `interactive` Tool/交互记录能力。 -Interact 提供 Agent/IM 场景的默认 Renderer,而不拥有记录或绕过统一 Tool Call、持久化和结果路径; -其他 MiniApp 只能通过 `sdk.ui` 在自身有效容器使用 Runtime 批准的标准 Renderer。 +`notice`、`choice`、`confirm`、`input` 是 Agent 调起的 Runtime 统一 `interactive` Tool/交互记录 +能力。Interact 提供会话语义和呈现,Runtime 持有记录与可靠结果路径;其他 MiniApp 只能作为当前 +前台界面被 Interact 交互层覆盖,不能调用、实现或取得该交互的内容与结果。 + +可回答交互的请求以相对 `expires_in_ms` 指定期限;Runtime 计算和持久化 `expires_at`,缺省 15 分钟, +仅接受 1 分钟至 24 小时。notice 不等待回答,也不接受期限。 建议稳定拒绝码至少包括: @@ -466,7 +516,7 @@ tauri/src/core-apps/chat/ ### 10.3 下一阶段:03.sdk_and_coreapp 下一阶段不是应用市场,也不是立即建设服务端下载目录。目标见 -[03.sdk_and_coreapp.md](迭代/03.sdk_and_coreapp.md):定义并验证 MiniApp SDK v1: +[03.sdk_and_coreapp.md](迭代/03.sdk_and_coreapp/03.sdk_and_coreapp.md):定义并验证 MiniApp SDK v1: ```text 冻结 AppManifest、ToolDescriptor、Tool Call、App Context、Result/Progress/Error 和错误码 @@ -476,17 +526,15 @@ tauri/src/core-apps/chat/ → Whiteboard 验证受限 Surface、状态 patch、Artifact 与 Capability 请求 ``` -参考 MiniApp 在此阶段可以是 `bundled-development`:随开发 Host 内置、有 Manifest 和 Runtime -注册记录,但不依赖服务端 Catalog、远程下载或第三方发布。 +参考 MiniApp 在此阶段使用 `kind = bundled`:随开发 Host 内置、有 Manifest 和 Runtime 注册记录, +但不依赖服务端 Catalog、远程下载或第三方发布。`bundled` 不是系统级信任;参考只描述这些 +MiniApp 在当前阶段用于验证 SDK 的工作目的,不是 Manifest 类型名称。 ### 10.4 后续阶段 MiniApp SDK 与参考实现稳定后,再按顺序推进: ```text -03.reference-miniapps -→ 完成 Task Dashboard / Whiteboard 的端到端参考实现 - 04.app-delivery-registry → Catalog Entry、Manifest/Bundle 下载、验签、安装、启用、禁用、更新、回滚、移除 @@ -494,6 +542,9 @@ MiniApp SDK 与参考实现稳定后,再按顺序推进: → 搜索、分类、发布者、安装 UX、组织分发与可能的商业能力 ``` +`03.sdk_and_coreapp` 已经包含 Task Dashboard 和 Whiteboard 的端到端参考实现;不再另设 +`03.reference-miniapps`,以免把同一批工作拆成两个编号。 + “市场”属于最后的产品分发层,不能反向决定 Runtime、SDK、Tool 或安全模型。 ## 11. 验收与发布门槛 @@ -508,19 +559,27 @@ npm run build 对于 SDK/参考 MiniApp,还必须有以下证据: 1. 合法与非法 Manifest、Inventory revision、Tool 输入/输出 schema 的自动化测试; -2. Tool 启动 MiniApp、前后台切换、结果回传、失败回焦和重启恢复的场景测试; -3. Interact 的 `notice / choice / confirm / input` IM 呈现不退化,且覆盖 owner 自动注入、跨 owner - 访问/提交拒绝、owner 结果回路与 MiniApp 容器内呈现的测试; -4. Installed MiniApp 无法取得 Host DOM、Tauri invoke、token、任意网络或其他 MiniApp 数据; -5. 有头浏览器验证登录、同步、本地回显和代表性 MiniApp 路径; -6. 对生产 Bundle,验证签名 `open → patch → close → rollback`、隔离 Bridge 和 Capability 拒绝路径; -7. `git diff --check` 无格式错误,日志不得包含身份、会话、消息正文、Tool 参数、token 或 +2. Tool 启动 MiniApp、前后台切换、结果回传、失败回焦和重启恢复的场景测试;接受关闭后,未终态普通 Tool + 仅产生一次 `app_closed` 取消、已 submitted 结果继续 outbox,随后才关闭 Surface / instance 并恢复焦点; +3. Interact 的 `notice / choice / confirm / input` 会话呈现不退化,覆盖 IM 内联与前台 bundled + MiniApp 上交互层的显示、conversation/call 绑定、结果可靠回传 Agent、默认/越界交互期限、非法 + `app_session_context` 拒绝、`interaction.dismiss` 幂等重放,以及 bundled MiniApp 无法访问或提交交互的测试; +4. MVP 的单 Agent 身份随交互和 App 子会话持久化;不新增多 Agent 连接、切换、会话列表或路由; +5. App instance 的子会话创建、后台/暂停保留、`AppLifecycleManager.close` 后结束折叠、重启恢复和新 instance 隔离可验证;创建时会可靠向 Agent + 提供 `app_session_id`,关闭时会发出包含仍 pending interaction call_id 的可靠 App 已关闭事件,但不会自动 + 取消 Interact 交互;子会话只包含人与 Agent 的交互,不包含 MiniApp 的内部业务操作; +6. 已结束子会话可只读查看;“继续处理”旧工作会创建新 instance / 新子会话,可引用旧上下文但不复活旧问题; +7. App 切换时未回答问题从前台 App 上方收起并在对应子会话标记;用户可在 IM 或回到原 App 后回答,且同一问题不可重复提交; +8. Installed MiniApp 无法取得 Host DOM、Tauri invoke、token、任意网络或其他 MiniApp 数据; +9. 有头浏览器验证登录、同步、本地回显和代表性 MiniApp 路径; +10. 对生产 Bundle,验证签名 `open → patch → close → rollback`、隔离 Bridge 和 Capability 拒绝路径; +11. `git diff --check` 无格式错误,日志不得包含身份、会话、消息正文、Tool 参数、token 或 artifact 内容。 ## 12. 相关文档与源码导航 -- [迭代/00.base.md](迭代/00.base.md):Runtime 托管 Interact IM 的稳定基线。 -- [迭代/01.kernel.md](迭代/01.kernel.md):Runtime Kernel 与 MiniApp 编排目标和验收。 +- [迭代/00.base/00.base.md](迭代/00.base/00.base.md):Runtime 托管 Interact IM 的稳定基线。 +- [迭代/01.kernel/01.kernel.md](迭代/01.kernel/01.kernel.md):Runtime Kernel 与 MiniApp 编排目标和验收。 - [tauri/src/README.md](tauri/src/README.md):当前源码目录和依赖方向。 - [tauri/README.md](tauri/README.md):Tauri/Web Host 的运行、回归与历史实施细节。 - [程序文件清单与功能说明.md](程序文件清单与功能说明.md):实现文件和功能说明。 diff --git a/README.md b/README.md index 561bc2c..2c56457 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ npm run desktop:dev - [程序文件清单与功能说明.md](程序文件清单与功能说明.md):客户端 Runtime、Chat Core App、Tauri/Web Host 与测试的文件清单; - [tauri/src/README.md](tauri/src/README.md):源码目录、依赖方向与模块放置规则; - [tauri/README.md](tauri/README.md):Tauri/Web Host 的运行、构建、行为回归和历史里程碑; -- [迭代/](迭代/00.base.md):按迭代记录当前基线和后续 Runtime Kernel / 应用编排目标; +- [迭代/](迭代/00.base/00.base.md):按迭代目录记录当前基线、设计评审和后续 Runtime Kernel / 应用编排目标; - [LineUp App 最终设计方案](../设计/02.正式方案/app_final_design.md):当前架构的唯一汇总入口; - [LineUp App 层架构方案](../设计/02.正式方案/lineup-app-layer-architecture.md):M0~M4 历史实施记录与迁移基础; - [LineUp Runtime 与 App SDK 架构方案](../设计/02.正式方案/lineup-runtime-sdk-architecture.md):Runtime / SDK 的详细契约来源; diff --git a/tauri/src/runtime/app-management/golden/miniapp-sdk-v1.json b/tauri/src/runtime/app-management/golden/miniapp-sdk-v1.json new file mode 100644 index 0000000..c863ae0 --- /dev/null +++ b/tauri/src/runtime/app-management/golden/miniapp-sdk-v1.json @@ -0,0 +1,66 @@ +{ + "contract": "lineup-miniapp-sdk-v1-golden-1", + "description": "03.sdk_and_coreapp frozen runtime contract cases. These cases express the required observable result; they do not grant MiniApps access to Runtime internals.", + "cases": [ + { + "id": "bundled-manifest-requires-surface", + "input": { "manifest": { "kind": "bundled", "host": { "surface_required": true } } }, + "expected": { "accepted": true } + }, + { + "id": "bundled-manifest-without-surface-rejected", + "input": { "manifest": { "kind": "bundled", "host": { "surface_required": false } } }, + "expected": { "accepted": false, "code": "manifest_denied" } + }, + { + "id": "interactive-without-app-context-goes-to-main-im", + "input": { "tool": { "handling": "interactive" } }, + "expected": { "app_session_id": null, "creates_business_instance": false, "enters_miniapp_inbox": false } + }, + { + "id": "interactive-with-verified-closed-app-session-keeps-history-context", + "input": { "tool": { "handling": "interactive" }, "app_session_context": { "app_session_id": "session-whiteboard-1", "state": "ended", "same_agent": true, "same_conversation": true } }, + "expected": { "app_session_id": "session-whiteboard-1", "creates_business_instance": false, "revives_instance": false } + }, + { + "id": "interactive-with-cross-conversation-app-session-rejected", + "input": { "tool": { "handling": "interactive" }, "app_session_context": { "app_session_id": "session-other", "state": "active", "same_agent": true, "same_conversation": false } }, + "expected": { "accepted": false, "code": "app_session_context_invalid", "creates_interaction": false } + }, + { + "id": "answerable-interaction-uses-default-expiry", + "input": { "request": { "kind": "confirm" } }, + "expected": { "expires_in_ms": 900000 } + }, + { + "id": "answerable-interaction-expiry-boundary", + "input": { "request": { "kind": "input", "expires_in_ms": 60000 } }, + "expected": { "accepted": true, "expires_in_ms": 60000 } + }, + { + "id": "notice-does-not-accept-expiry", + "input": { "request": { "kind": "notice", "expires_in_ms": 60000 } }, + "expected": { "accepted": false, "code": "interaction_request_invalid" } + }, + { + "id": "dismiss-is-idempotent-and-addressed-by-call-id", + "input": { "dismiss": { "control_id": "dismiss-1", "call_id": "interactive-call-1" }, "interaction": { "status": "pending", "same_agent": true, "same_conversation": true } }, + "expected": { "status": "cancelled", "tool_outbox_count": 1, "replay_outbox_count": 0 } + }, + { + "id": "accepted-app-close-cancels-running-tool", + "input": { "close": { "accepted": true }, "tool": { "status": "running", "instance_id": "task-dashboard-1" } }, + "expected": { "tool_status": "cancelled", "cancel_reason": "app_closed", "tool_outbox_count": 1, "close_surface": true, "stop_instance": true } + }, + { + "id": "accepted-app-close-preserves-submitted-result", + "input": { "close": { "accepted": true }, "tool": { "status": "submitted", "instance_id": "whiteboard-1" } }, + "expected": { "tool_status": "submitted", "continue_existing_outbox": true, "close_surface": true, "stop_instance": true } + }, + { + "id": "tool-complete-does-not-close-bundled-app", + "input": { "tool": { "status": "completed", "app_kind": "bundled" } }, + "expected": { "close_surface": false, "stop_instance": false, "end_app_session": false } + } + ] +} diff --git a/tauri/src/runtime/app-management/miniapp-sdk-golden-contract.test.ts b/tauri/src/runtime/app-management/miniapp-sdk-golden-contract.test.ts new file mode 100644 index 0000000..11cc2fe --- /dev/null +++ b/tauri/src/runtime/app-management/miniapp-sdk-golden-contract.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from "vitest"; +import fixture from "@/runtime/app-management/golden/miniapp-sdk-v1.json"; + +type GoldenCase = Readonly<{ + id: string; + input: Readonly>; + expected: Readonly>; +}>; + +const cases = fixture.cases as readonly GoldenCase[]; + +describe("MiniApp SDK v1 frozen golden contract", () => { + it("uses the versioned 03.sdk_and_coreapp fixture and stable case ordering", () => { + expect(fixture.contract).toBe("lineup-miniapp-sdk-v1-golden-1"); + expect(cases.map(entry => entry.id)).toEqual([ + "bundled-manifest-requires-surface", + "bundled-manifest-without-surface-rejected", + "interactive-without-app-context-goes-to-main-im", + "interactive-with-verified-closed-app-session-keeps-history-context", + "interactive-with-cross-conversation-app-session-rejected", + "answerable-interaction-uses-default-expiry", + "answerable-interaction-expiry-boundary", + "notice-does-not-accept-expiry", + "dismiss-is-idempotent-and-addressed-by-call-id", + "accepted-app-close-cancels-running-tool", + "accepted-app-close-preserves-submitted-result", + "tool-complete-does-not-close-bundled-app", + ]); + }); + + it("contains only cases with an input and observable expected result", () => { + for (const entry of cases) { + expect(entry.id).toMatch(/^[a-z][a-z0-9-]+$/); + expect(Object.keys(entry.input).length).toBeGreaterThan(0); + expect(Object.keys(entry.expected).length).toBeGreaterThan(0); + } + }); +}); diff --git a/程序文件清单与功能说明.md b/程序文件清单与功能说明.md index bc4dd8b..a0867d2 100644 --- a/程序文件清单与功能说明.md +++ b/程序文件清单与功能说明.md @@ -151,7 +151,7 @@ npm run desktop:dev ## 7. 相关文档 - [工作区 README](README.md):产品基线、MVP-R1、开发入口与文档导航。 -- [迭代基线与目标](迭代/00.base.md):`00.base` 已实现内容和 `01.kernel` Runtime Kernel 目标。 +- [迭代基线与目标](迭代/00.base/00.base.md):`00.base` 已实现内容和 `01.kernel` Runtime Kernel 目标。 - [APP 架构设计](APP架构设计.md):LineUp App、Runtime、MiniApp SDK、发布安全与后续阶段。 - [Tauri/Web 源码导航](tauri/src/README.md):目录边界与依赖方向。 - [Tauri/Web Host README](tauri/README.md):Host 运行、构建、行为回归与历史记录。 diff --git a/迭代/00.base.md b/迭代/00.base/00.base.md similarity index 96% rename from 迭代/00.base.md rename to 迭代/00.base/00.base.md index 01d038b..4bd99cc 100644 --- a/迭代/00.base.md +++ b/迭代/00.base/00.base.md @@ -119,6 +119,6 @@ Interaction App 的 IM / Audio / Video 模式统一运行时 完整应用市场 ``` -这些内容属于 [01.kernel.md](01.kernel.md) 定义的下一阶段目标。`00.base` 的意义是保护 +这些内容属于 [01.kernel.md](../01.kernel/01.kernel.md) 定义的下一阶段目标。`00.base` 的意义是保护 已经完成的通信、可靠投递、作用域和安全边界,后续重构不得让这些能力回到 App 或 `main.ts` 中。 diff --git a/迭代/01.kernel.md b/迭代/01.kernel/01.kernel.md similarity index 99% rename from 迭代/01.kernel.md rename to 迭代/01.kernel/01.kernel.md index da6e2f5..8c7be91 100644 --- a/迭代/01.kernel.md +++ b/迭代/01.kernel/01.kernel.md @@ -3,7 +3,7 @@ **迭代编号:** 01.kernel **状态:** 目标设计,待实现 **日期:** 2026-08-04 -**前置基线:** [00.base.md](00.base.md) +**前置基线:** [00.base.md](../00.base/00.base.md) ## 1. 迭代目标 diff --git a/迭代/03.sdk_and_coreapp.md b/迭代/03.sdk_and_coreapp.md deleted file mode 100644 index 92892e1..0000000 --- a/迭代/03.sdk_and_coreapp.md +++ /dev/null @@ -1,733 +0,0 @@ -# LineUp App 迭代定义:MiniApp SDK v1 与内置参考 MiniApp - -**迭代编号:** 03.sdk_and_coreapp -**状态:** 目标设计,待实现 -**日期:** 2026-08-04 -**前置基线:** [00.base.md](00.base.md)、[01.kernel.md](01.kernel.md) -**权威架构:** [APP架构设计.md](../APP架构设计.md) - -## 1. 迭代目标 - -本迭代不建设应用市场、服务端 Catalog、远程下载或第三方发布能力。它的目标是定义并实现 -**LineUp MiniApp SDK v1**,再用多个随 LineUp 开发版本内置的参考 MiniApp 验证这份 SDK。 - -```text -LineUp App - → LineUp Runtime - → LineUp MiniApp SDK v1 - → Interact MiniApp - → Task Dashboard MiniApp - → Whiteboard MiniApp -``` - -本迭代完成后,应能证明: - -```text -Runtime 能以同一套 SDK 语义承载系统级 MiniApp 与受限参考 MiniApp; -MiniApp 只能通过 SDK 收消息、接 Tool、报告进度/结果/错误、请求生命周期与 Surface; -Runtime 始终拥有通信、Tool、焦点、存储、权限和审计的最终决定权。 -``` - -这里的“内置”指 `bundled-development`:MiniApp 的 Manifest、代码和测试 fixture 随当前 -LineUp 开发 Host 提供。它们不是远程下载的 Bundle,也不等于已经开放的应用市场。 - -## 2. 本迭代的产品与信任模型 - -### 2.1 正确层级 - -```text -LineUp App 用户使用的主应用 -├── App Shell / Host 挂载、切换、通知、恢复、Host Provider -├── LineUp Runtime 通信、可靠性、MiniApp 调度和安全决策 -└── MiniApps 运行在 Runtime 上的功能单元 - ├── System MiniApp: Interact - ├── Bundled Reference: Task Dashboard - └── Bundled Reference: Whiteboard -``` - -Runtime 不是一个与 MiniApp 平级的产品 App;它是 LineUp App 内部提供给 MiniApp 的运行环境。 - -### 2.2 Interact 的定位 - -`Interact` 是第一个系统级 MiniApp,是用户与 Agent 的默认入口: - -```text -Interact -├── IM Mode:本迭代必须实现并适配 SDK -├── Audio Mode:只定义 SDK / 生命周期扩展位,不实现真实媒体能力 -├── Video Mode:只定义 SDK / 生命周期扩展位,不实现真实媒体能力 -└── IM 标准交互的默认 Renderer Provider:notice / choice / confirm / input -``` - -当前实现仍保持兼容名称: - -```text -产品概念:Interact MiniApp -当前 app_scope:chat -当前目录:tauri/src/core-apps/chat/ -``` - -本迭代不得把 `chat` 重命名为 `interact`,不得迁移目录或旧 `lineup.v1` scope;命名迁移另行 -定义,避免破坏已验证的通信与恢复行为。 - -### 2.3 相同语义,不同权限视图 - -所有 MiniApp 都遵守 MiniApp SDK v1 的同一语义;系统级与参考/已安装 MiniApp 的区别是来源和 -UI 容器,而不是是否可以绕过 Runtime。 - -| 能力 | Interact System MiniApp | Task Dashboard / Whiteboard | -|---|---:|---:| -| 读取自身 Inbox、ACK | 可以 | 可以 | -| 接收 Runtime Tool Call | 可以 | 可以 | -| 上报 progress / result / error | 可以 | 可以 | -| 请求前台、后台、关闭 | 可以,Runtime 决定 | 可以,Runtime 决定 | -| 请求 Surface / Capability | 可以,经 Policy | 可以,经 Policy | -| 使用可信内建 DOM | 可以 | Task Dashboard 可由受信 Host adapter 挂载;Whiteboard 必须走受限 Surface | -| 直接访问 Transport、Store、Agent | 不可以 | 不可以 | -| 直接访问 Tauri、Host DOM 根节点、任意网络 | 不可以 | 不可以 | -| 修改 Focus、Registry、其他 MiniApp 数据 | 不可以 | 不可以 | - -## 3. MiniApp SDK v1 契约 - -SDK 是 Runtime 根据 Manifest、实例状态、scope 和 Policy 注入的受限对象。SDK 中的所有写操作都 -是 **request**,不是对 Host、Store、Transport 或其他 MiniApp 的直接命令。 - -```ts -interface LineUpMiniAppSDK { - readonly context: MiniAppContext; - - readonly inbox: MiniAppInboxAPI; - readonly tools: MiniAppToolAPI; - readonly ui: StandardInteractionAPI; - readonly lifecycle: MiniAppLifecycleAPI; - readonly surfaces: MiniAppSurfaceAPI; - readonly capabilities: MiniAppCapabilityRequestAPI; -} - -type MiniAppContext = { - app_scope: string; - instance_id: string; - conversation_id: string; - app_version: string; - state: "starting" | "foreground" | "background" | "suspended"; -}; -``` - -`context` 由 Runtime 在创建实例时注入。MiniApp 不可传入、伪造或修改 `app_scope`、 -`instance_id`、`conversation_id`,也不可获得用户身份、Agent 身份、登录 token、AppServer -地址、Transport endpoint 或其他实例上下文。 - -### 3.1 Inbox API - -```ts -interface MiniAppInboxAPI { - list(after_message_id?: string): readonly MiniAppMessage[]; - subscribe(listener: (message: MiniAppMessage) => void): Unsubscribe; - acknowledge(message_id: string): boolean; -} -``` - -行为约束: - -```text -Agent / Runtime 消息 -→ Runtime 校验 Envelope、scope、目标 app_scope、instance 和去重 -→ 先持久化到该 MiniApp Inbox -→ SDK 投递已筛选消息 -→ MiniApp 成功接管后 ACK -→ Runtime 移除该可投递记录 -``` - -后台化、刷新、Surface 重载、断线和 Runtime 重启不得丢失未 ACK 的 Inbox 消息。MiniApp 只能 -列出和 ACK 自己 `app_scope + conversation_id + instance_id` 范围内的投递。 - -### 3.2 Tool API - -```ts -interface MiniAppToolAPI { - subscribe(listener: (call: MiniAppToolCall) => void): Unsubscribe; - get(call_id: string): MiniAppToolCall | undefined; - reportProgress(call_id: string, progress: MiniAppToolProgress): Promise; - complete(call_id: string, result: JsonObject): Promise; - fail(call_id: string, failure: MiniAppToolFailure): Promise; - cancel(call_id: string, reason?: string): Promise; -} -``` - -每一个调用都必须有 Runtime 持久化的通用记录: - -```ts -type RuntimeToolCallRecord = { - call_id: string; - tool_id: string; - inventory_revision: string; - app_scope: string; - instance_id?: string; - conversation_id: string; - status: - | "received" - | "routing" - | "waiting_for_app" - | "running" - | "submitted" - | "completed" - | "failed" - | "cancelled" - | "expired" - | "rejected"; - input: JsonObject; - result?: JsonObject; - error_code?: string; - created_at: string; - updated_at: string; -}; -``` - -`complete`、`fail`、`cancel` 绝不能直接发送网络请求。Runtime 必须验证: - -```text -call_id 属于当前 MiniApp instance -调用状态允许此迁移 -结果符合 ToolDescriptor.output_schema -调用不是重复终态 -调用仍属于当前 conversation / scope -MiniApp 和 Tool 仍处于启用状态 -``` - -验证成功后,Runtime 持久化记录和审计,再写入可靠 outbox 并回传 Agent。 - -### 3.3 Lifecycle API - -```ts -interface MiniAppLifecycleAPI { - requestForeground(): Promise; - requestBackground(): Promise; - requestClose(reason?: string): Promise; -} -``` - -MiniApp 只能请求,不能直接改变焦点或挂载其他 MiniApp: - -```text -MiniApp requestClose() -→ Runtime 检查 pending Tool、Surface、operation 和 Policy -→ 允许关闭或返回受控拒绝码 -→ 若关闭成功,Runtime 调整 focus stack -→ Runtime 恢复前一有效 foreground instance -``` - -### 3.4 标准交互 API - -SDK v1 通过 `sdk.ui` 提供由 **Runtime 管理的标准交互库**。这是一组低复杂度、可恢复、可审计的 -标准输入输出,不是 Interact 专有 API,也不是 MiniApp 取得 Host 弹窗权限的旁路。Runtime 创建、 -持久化和校验记录,并依据调用者、容器和 Shell Policy 选择实际呈现方式。 - -每次请求的 `owner` 必须由 Runtime 根据 SDK 已绑定的 `MiniAppContext` 自动注入;MiniApp 不能传入、 -伪造或覆盖 `app_scope`、`instance_id`、`conversation_id` 或 `surface_id`。调用者可选提供 -`parent_call_id`,但 Runtime 必须验证该调用属于当前实例且状态允许关联,不能将其作为可信 owner。 - -```ts -interface StandardInteractionAPI { - request( - request: StandardInteractionRequest, - options?: { - parent_call_id?: string; - presentation?: "default" | "inline" | "modal"; - expires_at?: string; - }, - ): Promise; - - get(interaction_id: string): StandardInteractionRecord | undefined; - subscribe(listener: (record: StandardInteractionRecord) => void): Unsubscribe; - cancel(interaction_id: string, reason?: string): Promise; -} - -type StandardInteractionRequest = - | NoticeRequest - | ChoiceRequest - | ConfirmRequest - | InputRequest; -``` - -第一版固定支持以下四种原语: - -| 原语 | 用途 | 用户结果 | -|---|---|---| -| `notice` | 安全显示只读提示、状态或下一步说明。 | `acknowledged`;也可由 Runtime 记录为只读。 | -| `choice` | 单选、多选或有限按钮选择。 | 有序且去重的 `action_ids`。 | -| `confirm` | 明确同意/取消一个可解释操作。 | `approved: boolean`。 | -| `input` | 受 schema 限制的文本、多行文本或数字输入。 | 按字段 ID 返回的结构化值。 | - -最低请求形态: - -```ts -type NoticeRequest = { - kind: "notice"; - title: string; - message: string; - acknowledge_label?: string; -}; - -type ChoiceRequest = { - kind: "choice"; - title: string; - prompt: string; - mode: "single-choice" | "multi-choice" | "button"; - actions: readonly { id: string; label: string; description?: string }[]; -}; - -type ConfirmRequest = { - kind: "confirm"; - title: string; - prompt: string; - approve_label?: string; - cancel_label?: string; -}; - -type InputRequest = { - kind: "input"; - title: string; - prompt: string; - fields: readonly { - id: string; - label: string; - type: "text" | "textarea" | "number"; - required?: boolean; - placeholder?: string; - min_length?: number; - max_length?: number; - minimum?: number; - maximum?: number; - }[]; - submit_label?: string; - cancel_label?: string; -}; -``` - -标准交互的 owner 路由与呈现策略如下: - -```text -Task Dashboard / Whiteboard 正在处理自身工作 -→ sdk.ui.request(choice / confirm / input / ..., { parent_call_id? }) -→ Runtime 从当前 SDK Context 注入不可伪造的 owner,并校验可选 parent_call_id、schema 与 Policy -→ Runtime 创建持久化 StandardInteractionRecord -→ Runtime / Shell 根据 owner 的有效容器选择受批准的标准 Renderer - ├── Agent 在 IM 中的提问:由 Interact 在时间线/卡片内呈现 - └── 前台 MiniApp 的通用事务提示:在该 MiniApp 自己有效的容器/Surface 中呈现标准 modal、sheet 或 card -→ 用户选择、确认、输入、取消或超时 -→ Runtime 持久化结果,仅投递回 owner MiniApp 的 Tool/Inbox -→ 原 MiniApp 决定 complete / fail / 继续 progress -``` - -`presentation` 只是调用偏好,不是强制指令;Runtime / Shell 可基于焦点、Surface、Policy 和可用 -Renderer 降级或拒绝。Agent 发来的 `lineup.v1.tool.call(choice | confirm | input)` 保持兼容:Runtime -创建同一类记录,其 owner 是 Interact 的 IM 上下文(`source = agent_tool`),并由 Interact 作为 -默认可信 IM Renderer Provider 呈现。它们不需要 MiniApp 提供父 Tool Call。 - -这意味着标准交互不会成为 MiniApp 直连用户界面或跨 App 操作的旁路: - -```text -MiniApp 不能在 Host DOM 注入任意弹窗、表单或远端 HTML -MiniApp 不能伪造 owner、interaction_id,或替其他 owner 查询、提交、取消结果 -Interact 不能直接把结果发送给 Agent -交互结果必须先回到 Runtime,再交给拥有该 owner 的 MiniApp -``` - -`notice` 是 SDK v1 新增的只读确认原语。Agent IM 与 MiniApp 发起的请求共享一套 -`StandardInteractionRecord`、状态机、持久化、过期、去重、owner 路由和标准 Renderer 协议, -但不要求共享一个 Interact 卡片实例或切换到 Interact。 - -`StandardInteractionRecord` 至少包含以下 Runtime 内部字段;其中 `owner` 只可由 Runtime 写入: - -```ts -type StandardInteractionOwner = { - app_scope: string; - instance_id: string; - conversation_id: string; - parent_call_id?: string; - surface_id?: string; - source: "agent_tool" | "miniapp_tool" | "runtime_policy"; -}; - -type StandardInteractionRecord = { - interaction_id: string; - owner: StandardInteractionOwner; - kind: "notice" | "choice" | "confirm" | "input"; - request: StandardInteractionRequest; - status: "pending" | "presented" | "submitted" | "completed" | "cancelled" | "expired" | "failed"; - result?: StandardInteractionResult; - created_at: string; - updated_at: string; - expires_at?: string; -}; -``` - -状态必须有界: - -```text -pending → presented → submitted → completed | cancelled | expired | failed -``` - -终态不可重新操作;刷新、断线和重启后可恢复尚未终态的可信卡片与未提交的 `input` 草稿。 - -### 3.5 Surface 与 Capability API - -SDK v1 定义请求语义,不直接授予 Host 权限: - -```ts -interface MiniAppSurfaceAPI { - requestOpen(request: MiniAppSurfaceRequest): Promise; - update(surface_id: string, patch: JsonObject): Promise; - requestClose(surface_id: string): Promise; -} - -interface MiniAppCapabilityRequestAPI { - request(request: { - capability: string; - reason: string; - arguments: JsonObject; - }): Promise; -} -``` - -Runtime 必须根据 Manifest、实例状态、Capability Policy、用户确认和 Host 支持情况决定是否 -执行。SDK v1 不开放: - -```text -fetch / 任意网络 -任意文件系统 -Tauri invoke -直接剪贴板、麦克风、摄像头 -Host DOM 根节点 -原始 AppServer / Agent 协议 -``` - -本迭代可定义 Capability Request 的类型、拒绝码、审计和测试,但不必为参考 MiniApp 开放真实 -麦克风、摄像头或文件选择权限。 - -## 4. Manifest、Tool Contract 与 Inventory - -### 4.1 MiniApp Manifest v1 - -本迭代冻结最小 Manifest 契约: - -```ts -type MiniAppManifest = { - app_scope: string; - version: string; - kind: "system" | "bundled-reference"; - tools: readonly ToolDescriptor[]; - subscriptions: readonly MiniAppSubscription[]; - requested_capabilities: readonly string[]; - host: { - min_version: string; - surface_required: boolean; - }; -}; -``` - -Manifest 是 Runtime 的输入,不是 MiniApp 可写状态;MiniApp 不可在运行时扩展 Tool、订阅或 -权限。动态安装、下载、更新、回滚和移除不属于本迭代。 - -### 4.2 Tool Descriptor v1 - -```ts -type ToolDescriptor = { - id: string; // 例如 task-dashboard.open - version: 1; - handling: "direct" | "interactive" | "launch" | "foreground" | "operation"; - target: { - app_scope: string; - requires_foreground: boolean; - restore_previous_focus: boolean; - }; - input_schema: JsonSchema; - output_schema: JsonSchema; - permissions?: readonly string[]; - timeout_ms?: number; -}; -``` - -第一版 JSON Schema 只支持可明确实现和测试的子集: - -```text -object / string / number / boolean / array -required -properties -additionalProperties = false -enum -minLength / maxLength -minimum / maximum -maxItems -``` - -禁止接受远端 schema 中的递归引用、正则执行、脚本、URL 加载、函数名或任意扩展关键字。 - -### 4.3 Revisioned Inventory - -Runtime 在 MiniApp 的启用状态、Manifest Tool 或可见 Capability 发生变化时,生成最小化且带 -revision 的 Inventory。Agent Tool Invoke 必须携带该 revision: - -```text -Agent Tool Invoke -→ Runtime 发现 inventory_revision 不匹配 -→ 拒绝,不投递给 MiniApp,不执行任何 Host 行为 -→ 以受控状态提示 Agent 使用最新 Inventory -``` - -本迭代可复用现有 `lineup.v1.client.inventory` 发送路径;AppServer 只需要像普通会话消息一样 -透明转发,不需要实现应用目录或下载 API。 - -### 4.4 通用 Tool 路由 - -```text -Agent Tool Invoke -→ Runtime 校验 Envelope、Inventory revision、ToolDescriptor、输入 schema、scope、MiniApp 状态和权限 -→ 创建 RuntimeToolCallRecord 与审计记录 -→ Tool Router 判定 direct / interactive / launch / foreground / operation -→ App Orchestrator 创建或复用 instance,调整 focus -→ SDK Tool Inbox 投递 -→ MiniApp 经 SDK 报告 progress / result / error -→ Runtime 校验 output schema,持久化,写 outbox,回传 Agent -``` - -`notice`、`choice`、`confirm`、`input` 必须映射为 Runtime 统一的 `interactive` Tool Call / -`StandardInteractionRecord`。Interact 负责 Agent/IM 情况下的默认可信渲染;其他 MiniApp 可在其 -有效容器中使用 Runtime 批准的同一标准 Renderer,不能因此保留 Chat 专用旁路或获得 Host 特权。 - -建议稳定拒绝码: - -```text -inventory_revision_mismatch -tool_not_advertised -tool_input_invalid -tool_output_invalid -app_disabled -app_instance_not_found -app_scope_mismatch -lifecycle_denied -capability_denied -call_already_final -``` - -## 5. 内置参考 MiniApp 工作定义 - -本迭代通过三个内置 MiniApp 覆盖 SDK v1 的不同能力面。它们不是应用市场候选,也不要求完整 -业务功能;每个 MiniApp 只实现足以验证 SDK 契约的最小真实闭环。 - -### 5.1 Interact:系统级 MiniApp 样本 - -**身份:** `kind = system`;当前兼容 `app_scope = chat`。 -**本迭代交付:** IM Mode 适配到 MiniApp SDK v1。 - -| 范围 | 工作定义 | -|---|---| -| Inbox | 使用通用 `sdk.inbox` 订阅、恢复和 ACK;不再依赖 Chat 特有投递语义。 | -| 用户消息 | 保留 `conversation.sendText` 这一系统级扩展,但它必须进入 Runtime Action / outbox。 | -| 标准原语 | 作为 Agent/IM 请求的默认可信 Renderer Provider,在时间线/卡片内呈现 Runtime 投递的标准交互记录。 | -| 交互边界 | 不拥有交互记录,也不直接向 Agent 回传结果;结果必须由 Runtime 按 owner 路由。其他 MiniApp 可在自身有效容器使用 Runtime 批准的标准 Renderer。 | -| Tool 结果 | 用户完成、取消或超时后调用 `sdk.tools.complete/fail/cancel`,由 Runtime 回传。 | -| Mode | 明确 IM 的 `mode = im` Context;仅定义 Audio/Video 入口和恢复语义,不启用真实媒体。 | -| 焦点 | 被参考 MiniApp 覆盖时接受 Runtime 的 background/suspended;不能自行切换回前台。 | - -**不在范围:** 语音录制、视频通话、摄像头、独立 Audio/Video MiniApp、改名 `chat → interact`。 - -### 5.2 Task Dashboard:Tool 与生命周期样本 - -**身份:** `kind = bundled-reference`,`app_scope = task-dashboard`。 -**目标:** 验证 MiniApp Tool、progress/result/error、instance、focus 和恢复的最小闭环。 - -最小 Tool: - -```text -task-dashboard.open - handling = launch - 输入:task_id、title、可选初始状态 - 输出:status = completed | cancelled | failed - -task-dashboard.update - handling = operation - 输入:task_id、进度或状态 - 输出:当前任务摘要 -``` - -最小用户体验: - -```text -Agent 调用 task-dashboard.open -→ Runtime 创建 task-dashboard instance -→ Interact 进入 background -→ Shell 挂载 Task Dashboard -→ MiniApp 展示任务标题、进度、当前状态和关闭动作 -→ MiniApp 用 sdk.tools.reportProgress / complete / fail 上报 -→ Runtime 持久化、回传 Agent、关闭实例并恢复 Interact -``` - -Task Dashboard 的业务状态必须通过 SDK Tool / Inbox 获得;不得自行访问 AppServer、Store 或 -Agent。它可以使用随 LineUp 发布的受信 Host adapter,但 adapter 只做渲染装配,不可成为 -Transport 或 Tool Router 的第二实现。 - -Task Dashboard 必须至少使用一次 `sdk.ui.request`:例如在关闭未完成任务前请求 `confirm`,或需要 -任务说明时请求 `input`。Runtime 必须从该实例注入 owner,并在 Task Dashboard 当前有效容器中使用 -受批准的标准 modal、sheet 或 card;结果经 Runtime 返回该实例,期间不得强制切换到 Interact。 - -### 5.3 Whiteboard:隔离 Surface 样本 - -**身份:** `kind = bundled-reference`,`app_scope = whiteboard`。 -**目标:** 验证 SDK Surface Bridge、隔离执行、状态 patch、Artifact 元数据与恢复。 - -最小 Tool: - -```text -whiteboard.open - handling = launch - 输入:board_id、title、可选初始画布状态 - 输出:status、artifact_id(可选) - -whiteboard.submit - handling = operation - 输入:board_id、提交请求 - 输出:artifact_id 或结构化画布摘要 -``` - -最小用户体验: - -```text -Agent 调用 whiteboard.open -→ Runtime 校验已注册 bundled-reference Manifest -→ Runtime 创建 whiteboard instance 并请求受限 Surface -→ Host 挂载 opaque-origin iframe / Surface Bridge -→ Whiteboard 仅通过 Bridge SDK 请求状态更新和提交结果 -→ Runtime 校验 patch / Artifact 元数据并持久化 -→ App 完成或失败,Runtime 关闭 Surface、恢复 Interact -``` - -Whiteboard 不需要在本迭代实现多人协作、任意网络同步或完整绘图工具;重点是证明隔离 Surface -不能读取 Host DOM、Tauri、认证状态或其他 MiniApp 数据。 - -Whiteboard 如需要低复杂度的事务提示(例如“确认提交白板?”),可通过 `sdk.ui` 请求标准交互。 -但画板内的文字编辑、画笔与颜色选择、拖放、工具栏、上下文菜单等高频或私有业务 UI 必须保留在 -Whiteboard 自己的 Surface 中,不能被错误抽象为 Runtime 标准交互。 - -## 6. Runtime 与 Host 的实现模块 - -本迭代预计在现有目录中演进,不重建并行 Runtime: - -```text -tauri/src/runtime/ -├── app-management/ -│ ├── miniapp-manifest.ts # Manifest v1、bundled-reference 注册 -│ ├── miniapp-sdk.ts # SDK v1 公共类型与受限视图 -│ ├── miniapp-tool-call-store.ts # 通用 Tool Call 持久化/恢复 -│ └── runtime-app-host.ts # 按 foreground instance 挂载/卸载 Host -│ -├── coordination/ -│ ├── tool-router.ts # revision、schema、路由决策 -│ ├── miniapp-tool-orchestrator.ts # Tool → instance → SDK Inbox -│ └── standard-interaction-service.ts # owner 注入、记录、路由、恢复与呈现 Policy -│ -├── inventory/ -│ └── client-inventory.ts # revisioned Tool Inventory -│ -└── persistence/ - └── conversation-store.ts # Tool Call、Inbox、workspace 有界恢复 - -tauri/src/ -├── core-apps/chat/ # Interact IM 的兼容实现 -│ └── standard-interaction-im-renderer.ts # Agent/IM 的默认标准交互 Renderer -├── core-apps/task-dashboard/ # bundled-reference Tool/lifecycle 样本 -└── core-apps/whiteboard/ # bundled-reference Surface/Bridge 样本 -``` - -若目录名称最终改为 `miniapps/`,应单独进行机械迁移;本迭代优先保证 Runtime 边界和 SDK -兼容,不能让命名迁移扩大风险。 - -## 7. 不在本迭代范围 - -以下内容必须明确排除: - -```text -服务端 App Catalog 或应用清单 API -远程 Manifest / Bundle 下载 -安装、更新、回滚、卸载 UI -第三方开发者发布、账号、审核、评分、支付或搜索 -任意网络 API -真实麦克风、摄像头、文件选择或系统通知授权 -多人白板、实时游戏、完整语音/视频通话 -chat / interaction 命名和协议 scope 迁移 -``` - -这些能力将在 MiniApp SDK 与参考 MiniApp 完成验证后,作为独立的应用分发和市场阶段推进。 - -## 8. 实施步骤 - -1. **冻结契约与 golden fixtures** - - 定义 `MiniAppManifest v1`、`ToolDescriptor v1`、`MiniAppContext`、通用 Tool Call、 - Result/Progress/Error、Standard Interaction、SDK 稳定错误码; - - 为合法、重复、过期 revision、非法 schema、scope 不匹配和终态重复建立 fixture; - - 明确旧 `lineup.v1.tool.call` 的 `choice/confirm/input` 到 `interactive` Tool 的兼容映射, - 并为 `notice`、owner 自动注入、MiniApp 发起的 choice/confirm/input、取消、超时和重启恢复建立 fixture。 - -2. **实现 Runtime 通用 Tool 闭环** - - 将 Inventory revision、输入/输出 schema、Tool Call Record、审计和 outbox 接入 Runtime; - - 让 `ToolRouter` 和 `AppOrchestrator` 根据 ToolDescriptor 创建/复用实例并投递 SDK Tool Call; - - 完成 Tool、Inbox、workspace 的断线与重启恢复。 - -3. **适配 Interact IM** - - 以 `sdk.inbox` 和 `sdk.tools` 替换 Chat SDK 中的专用交互旁路; - - 实现 Agent/IM 标准交互的默认 Renderer Provider,保持 Markdown、消息、notice、choice、confirm、input、Task 和现有回归行为; - - 固化 IM Mode Context 与被覆盖/恢复的生命周期语义。 - -4. **实现 Task Dashboard 参考 MiniApp** - - 注册 bundled-reference Manifest 和两个最小 Tool; - - 实现启动、进度、结果、错误、关闭、回焦和重启恢复; - - 使用真实 SDK,不测试用 Runtime 内部对象直连。 - -5. **实现 Whiteboard 参考 MiniApp** - - 注册 bundled-reference Manifest、最小 Tool 和受限 Surface; - - 验证 Bridge、patch、Artifact 元数据、关闭、失败和恢复; - - 验证隔离拒绝路径与 Capability Request 拒绝路径。 - -6. **端到端验收与文档回填** - - 运行完整单元测试和生产构建; - - 通过 Tauri/Web Reference Host 完成代表性浏览器验收; - - 将最终 SDK 形态与参考 MiniApp 结果回填 [APP架构设计.md](../APP架构设计.md)。 - -## 9. 验收目标 - -```text -1. Runtime 向每个 MiniApp instance 注入不可伪造、范围受限的 MiniAppContext。 -2. Interact、Task Dashboard、Whiteboard 均通过 SDK 获取 Inbox、Tool 和生命周期能力; - 不直接访问 Transport、Store、Agent 或 Host 特权。 -3. Manifest Tool 具有稳定 ID、输入/输出 schema、handling、目标 scope 和版本。 -4. Runtime 只接受当前 Inventory revision 中、输入 schema 合法的 Tool Invoke。 -5. Runtime 拒绝未知 Tool、过期 revision、非法输入/输出、scope 不匹配、禁用 MiniApp、 - 错误 instance 和重复终态;拒绝请求不得到达 MiniApp 或 Host。 -6. Interact 的 notice / choice / confirm / input 通过统一 Standard Interaction 在 IM 中完成、恢复和回声, - 不退化。 -7. Runtime 从 SDK-bound MiniAppContext 自动注入不可伪造的 interaction owner;可选 parent Tool - 引用必须归属于当前实例且通过验证。结果只能经 Runtime 返回 owner,不能直接发给 Agent 或其他 MiniApp。 -8. Task Dashboard 可验证 launch → foreground → progress → 标准交互 → result/error → close → - Interact 恢复;标准交互在其有效容器呈现且不触发焦点切换。 -9. Whiteboard 可验证隔离 Surface open → patch → submit/close,并不能访问 Host DOM/Tauri/token; - 画板内的文字编辑等私有高频 UI 不通过 `sdk.ui` 路由。 -10. MiniApp 的 progress/result/error 经 Runtime schema 校验、持久化、审计和 outbox 后才回传 Agent。 -11. 断线或重启后,pending Tool、Standard Interaction、MiniApp Inbox、实例、焦点和 Surface 以有界方式恢复;中断操作 - 不得伪装为完成。 -12. 不新增 AppServer Catalog、下载或市场接口;现有服务端只透明转发会话/Inventory 消息。 -13. 00.base 和 01.kernel 中的登录、同步、本地回显、Markdown、安全 fallback、Tool Call、 - Task、Surface、Capability、App Inbox、outbox 和焦点恢复测试不退化。 -14. `npm test -- --run`、`npm run build`、`git diff --check` 通过;浏览器验收无未处理错误。 -``` - -## 10. 完成定义 - -本迭代完成不是“已经有应用市场”,也不是“完成全部 MiniApp 业务功能”。完成的判断是: - -```text -LineUp Runtime 已提供经过类型、schema、scope、Inventory revision、生命周期、权限、持久化和 -审计约束的 MiniApp SDK v1; - -Interact、Task Dashboard、Whiteboard 已以不同信任和 UI 形式使用同一套 SDK 语义,证明 -LineUp 可在不增加旁路通信或 Host 特权泄漏的前提下承载系统级与受限 MiniApp。 -``` diff --git a/迭代/03.sdk_and_coreapp_comment.md b/迭代/03.sdk_and_coreapp/01.design_review.md similarity index 53% rename from 迭代/03.sdk_and_coreapp_comment.md rename to 迭代/03.sdk_and_coreapp/01.design_review.md index 763cb6c..2d41d1e 100644 --- a/迭代/03.sdk_and_coreapp_comment.md +++ b/迭代/03.sdk_and_coreapp/01.design_review.md @@ -1,31 +1,91 @@ -# `03.sdk_and_coreapp` 设计评审记录 +# `03.sdk_and_coreapp` 第 1 次设计评审记录 > 评审日期:2026-08-05 -> 评审基线:[APP架构设计.md](../APP架构设计.md) +> 评审编号:01 +> 评审基线:[APP架构设计.md](../../APP架构设计.md) > 评审对象:[03.sdk_and_coreapp.md](03.sdk_and_coreapp.md) > 评审方式:独立子 agent 只读评审;本文件记录评审意见,不代表已采纳或已实现。 +> **后续决议(2026-08-05):** `MiniAppManifest.kind` 收敛为 `system | bundled`。Task Dashboard +> 与 Whiteboard 均为非系统级 `bundled` MiniApp,必须采用与一般 MiniApp 相同的受限 Surface / +> Bridge 模型;“参考”仅描述它们在本迭代验证 SDK 的目的。下文的 `bundled-reference` 为评审时的 +> 历史术语,已由此决议取代。 + +> **后续决议(2026-08-05):** `notice / choice / confirm / input` 始终是 Interact 的人与 Agent +> 会话交互,不属于 MiniApp SDK,也不用于 MiniApp 内部业务逻辑。当前 bundled MiniApp 位于前台时, +> Interact / Shell 可在其上方显示同一会话的交互层;请求与答案仍只属于 Interact、当前会话和 Agent。 + +> **后续决议(2026-08-05):** 每个启动的 App instance 在主 IM 中创建或恢复一个 App 子会话;其中 +> 只保留人与 Agent 围绕该 App 的提问、回答和简洁结果。App 的内部按钮、表单、画布编辑等业务操作 +> 不进入子会话。后台化不结束子会话;真正结束时保留折叠历史;新的 instance 创建新的子会话。 + +> **后续决议(2026-08-05):** 已结束的 App 子会话可以在主 IM 中只读展开。用户选择“继续处理”时, +> Runtime 创建新的 App instance 和新的子会话;新会话可引用旧会话或 App 数据,但不能续写旧记录或 +> 复活旧问题。 + +> **后续决议(2026-08-05,已更新):** App 子会话只在 `AppLifecycleManager.close` 使对应 instance +> 停止或失败时结束;前后台切换、暂停和 Agent Tool 完成都不结束子会话。关闭时,Runtime 向 Agent +> 可靠发送 App 已关闭事件;尚未回答的 Interact 交互不自动取消,Agent 可 remote dismiss,或让其继续 +> 在主 IM 中等待用户回答/超时。子会话保留为主 IM 历史。 + +> **后续决议(2026-08-05):** MVP 中一个 LineUp Runtime 只连接一个 Agent。每条标准交互和 +> App 子会话仍保存当前 `agent_uid` 作为 `agent_id`,但本迭代不实现多个 Agent 的连接、切换、 +> 会话列表、outbox 或路由。 + +## 问题清单(Outline) + +> **状态标记:** ✅ 已解决并回填设计; 🟡 待产品/架构决策; 🔵 待在契约中细化; ⚪ 待实施编排或文档整理; — 不再适用。 +> +> 本清单是当前有效视图;后文保留评审时的原始问题、分析和建议作为依据。每次确认一个决策或完成 +> 回填时,应先更新此处状态,再更新对应正文和验收项。 + +| 状态 | 编号 | 问题 | 当前结论 / 下一步 | +|---|---|---|---| +| ✅ | B1 | Task Dashboard 的信任级别与 UI 容器 | 已确认:Task Dashboard、Whiteboard 均为非系统级 `kind = bundled` MiniApp,必须运行于受限 Surface / Bridge,不能使用可信 Host DOM。 | +| ✅ | I1 | `bundled-reference` / `bundled-development` 命名歧义 | 已确认:Manifest 枚举为 `system \| bundled`;“参考”仅描述当前迭代的工作目的。 | +| ✅ | I2 | 标准交互由谁显示、答案如何回传 | 已确认:Interact / Shell 显示人与 Agent 的交互;bundled MiniApp 前台时可被该交互层覆盖。结果由 Runtime 可靠回传 Agent,不交给 MiniApp。 | +| ✅ | I3 | 标准交互与 Agent Tool Call 的关系 | 已确认:标准交互仅由 Agent Tool 调起并创建交互记录;MiniApp 不存在 `sdk.ui.request`,不会自行创建 Agent Tool Call。 | +| ✅ | I4 | 标准交互结果的返回通道 | 已确认:用户答案先回 Runtime,再可靠回传 Agent;不经 MiniApp Inbox、Tool 订阅或 MiniApp SDK 返回。 | +| ✅ | I5 | Request/Result schema、状态机、幂等与错误码 | 已确认第一版:`notice` 非阻塞;`confirm` 二选一且默认取消;`choice` 只支持 2~6 项单选;`input` 只支持一个文本输入;首次有效回答终结交互;默认 15 分钟超时,可在 1 分钟~24 小时内调整。 | +| ✅ | I6 | 敏感输入的持久化、恢复、审计和日志边界 | 已确认:未提交草稿仅存在当前运行期间,重启即清空;已提交的人与 Agent 答案保留在所属主 IM 或 App 子会话中,随父 IM 会话处理;二者均不写日志、Telemetry 或普通审计明文。 | +| ✅ | I7 | App 子会话与主 IM 的关系 | 已确认:每个 App instance 创建/恢复一个子会话;后台保留,真正结束后在主 IM 中折叠存档,新 instance 独立。仅记录人与 Agent 围绕 App 的交互,不记录 App 内部业务操作。 | +| ✅ | I8 | 已结束 App 子会话的查看与继续处理 | 已确认:旧子会话可只读展开;继续旧工作创建新 instance / 新子会话,可引用旧上下文,但不追加旧记录或复活旧问题。 | +| ✅ | I9 | App 子会话何时结束 | 已确认:仅 `AppLifecycleManager.close` 导致 instance stopped/failed 时结束;前后台切换、暂停、Agent Tool 完成不结束。关闭时 Runtime 向 Agent 发送 App 已关闭事件,但不自动取消未回答的 Interact 交互。 | +| ✅ | I10 | MVP 的 Agent 身份范围 | 已确认:Runtime 仅连接一个 Agent;交互和 App 子会话保存该 `agent_id`,但不实现多 Agent 连接、切换或路由。 | +| — | S1 | `parent_call_id` 的关联校验与父 Tool 终止后的处置 | 不再适用:MiniApp 不再发起标准交互;标准交互直接绑定 Agent call 与 conversation。 | +| ✅ | S2 | 交互层显示与恢复规则 | 已确认:原 App 前台时显示交互层;切换到其他 App/IM 时收起并在子会话标记“等待你的回答”;用户可在 IM 或回到原 App 后回答;关闭 App 只移除覆盖层并通知 Agent,不自动取消交互。 | +| ✅ | S3 | Interact 呈现与 Agent Tool 回传的分层 | 已确认:Interact / Shell 只显示并把用户动作/答案交给 Runtime;Runtime 根据已保存的交互上下文校验、持久化和可靠回传 Agent;不经过 MiniApp。 | +| ✅ | S4 | 阶段编号重复 | 已确认:Task Dashboard / Whiteboard 的端到端参考实现属于 `03.sdk_and_coreapp`;移除重复的 `03.reference-miniapps`,后续应用分发阶段保持为 `04.app-delivery-registry`。 | +| ✅ | S5 | 实施步骤顺序 | 已确认:先完成通用 Tool 闭环,再完成 Runtime 独占的 Agent interaction service 和 Interact 回传契约,之后才适配 Interact、实现两个 bundled 参考 MiniApp,最后端到端验收。 | + ## 1. 总体结论 -`03.sdk_and_coreapp` 的主方向已经与架构基线基本一致,以下核心决策已正确写入: +这份文件保留了首次评审时提出的问题和建议,方便追溯讨论过程;其中涉及 `sdk.ui`、MiniApp 发起 +标准交互、owner 注入等早期设想,均已被本文件顶部的“后续决议”和问题清单中的最终结论取代,不能作为 +实施依据。 -- 产品层级保持为 **LineUp App → LineUp Runtime → MiniApps**;Runtime 没有被写成与 MiniApp 平级的 App。 -- SDK 使用 `sdk.ui`,不再使用旧的 `sdk.interactions`。 -- 标准交互属于 Runtime 提供的标准输入输出库;Runtime 负责记录、owner 注入、校验、恢复、审计、路由和呈现 Policy。 -- `owner` 不是 MiniApp 调用参数,而是从 SDK 已绑定的 `MiniAppContext` 自动注入;MiniApp 不能伪造或访问其他 owner 的交互。 -- Interact 是 Agent/IM 场景的默认可信 Renderer Provider,不拥有交互记录,也不直接把结果发送给 Agent。 -- Task Dashboard 可验证 `sdk.ui` 的通用事务提示,且不应因提示强制切换到 Interact。 -- Whiteboard 的文字编辑、画笔、颜色选择、拖放、工具栏等高频或私有 UI 留在自身 Surface,不走 Runtime 标准交互。 -- 保留 `lineup.v1.tool.call(choice | confirm | input)` 兼容路径;不把 `chat → interact` 命名迁移放进本迭代。 -- 本迭代不建设 AppServer Catalog、下载、安装、更新、市场、远程 Bundle 或真实媒体/文件能力。 +当前已经收敛的核心结论是: -但仍存在 **1 个阻塞级信任模型冲突**,以及若干会造成实现分叉或验收不可判定的重要契约缺口。建议在进入实现前完成收敛。 +- 产品层级是 **LineUp App → LineUp Runtime → MiniApps**,Runtime 不是与 MiniApp 平级的产品 App。 +- `notice / choice / confirm / input` 是 Interact 中人与 Agent 对话的一部分,不是 MiniApp SDK,也不能由 + bundled MiniApp 发起、读取、提交或取消。 +- Interact / Shell 只呈现和收集用户答案;Runtime 保存交互归属、校验首次有效回答、写入可靠 outbox,并 + 向当前唯一 Agent 回传结果。 +- Task Dashboard 与 Whiteboard 都是 `kind = bundled`;它们使用一般 MiniApp 的受限 Surface、Bridge、 + 生命周期和 SDK,只保留自身的业务 UI。 +- 保留 `lineup.v1.tool.call(choice | confirm | input)` 的 Agent 交互兼容入口;不在本迭代迁移 + `chat → interact` 命名。 +- 本迭代不建设 AppServer Catalog、下载、安装、更新、市场、远程 Bundle、真实媒体/文件能力或多 Agent + Runtime 连接。 + +本轮评审问题均已得到设计结论,后续进入实现时应以 [03.sdk_and_coreapp.md](03.sdk_and_coreapp.md) +和 [APP架构设计.md](../../APP架构设计.md) 为准。 ## 2. 阻塞问题 ### B1. Task Dashboard 的可信 DOM / Host adapter 权限突破了当前信任模型 -**架构基线**在 [APP架构设计.md](../APP架构设计.md) 的 MiniApp 信任模型中明确: +**架构基线**在 [APP架构设计.md](../../APP架构设计.md) 的 MiniApp 信任模型中明确: - `Interact / System MiniApp` 可使用可信内建 DOM 组件; - Installed MiniApp 必须经隔离 iframe / Surface Bridge 运行。 @@ -227,39 +287,45 @@ parent_call_invalid | renderer 不可用 | 保持 pending 或返回稳定拒绝码;不能把 payload 注入 Host DOM。 | | 系统能力确认 | 走 Capability Gateway,禁止降级为普通 `confirm`。 | -### S3. 分离 Interact 的 Renderer 行为与 legacy Agent Tool 完结行为 +### S3. Interact 呈现与 Agent Tool 回传的分层 -当前“Renderer 呈现标准交互记录”与“Interact 以 `sdk.tools.complete/fail/cancel` 结束 Tool”的表述容易混淆。 - -建议统一为: +**已确认(2026-08-05):** 用户在 Interact 中回答时,Interact 只负责显示问题、收集用户动作并把 +答案交给 Runtime。它不是 Agent 的消息发送端,也不负责判断答案属于哪个 Agent、哪个会话或哪个 App +子会话。 ```text -Renderer → Runtime:完成 interaction record -Runtime → owner:sdk.ui 状态更新 -owner(legacy Agent Tool 场景中为 Interact)→ sdk.tools.complete / fail / cancel +Interact / Shell 显示问题 +→ 用户作答 +→ Interact 向 Runtime 提交「interaction_id + 用户动作/答案」 +→ Runtime 从已保存的交互记录取得 agent_id、conversation、App 子会话和 Agent Tool 归属 +→ Runtime 核对 Interact 实例、展示位置、状态、期限、答案格式和首次提交 +→ Runtime 持久化会话记录与终态,并写入可靠 outbox +→ Runtime 向当前唯一 Agent 回传一次结果 ``` -这样不会把一般 Renderer 误写成跨域 Tool 结果出口。 +提交端不能传递或覆盖 `agent_id`、`conversation_id`、`call_id`、`app_session_id` 等归属信息;这些 +只能由 Runtime 创建交互时保存。重复点击、刷新后的旧页面、无效展示位置、过期或已结束问题的提交都 +必须被拒绝,且不能改变已经保存的结果或再次通知 Agent。Task Dashboard、Whiteboard 等 bundled +MiniApp 始终不参与这条链路,也读不到问题或答案。 ### S4. 统一阶段编号 -架构主文档当前既有 `03.sdk_and_coreapp`,又有后续 `03.reference-miniapps`,但前者已经将 Task Dashboard / Whiteboard 的实现和端到端验收列为目标。 - -需要二选一: - -- 删除或合并 `03.reference-miniapps` 到当前 `03.sdk_and_coreapp`;或 -- 当前 `03` 只做 SDK/Interact 核心,参考 MiniApp 另起新编号并顺延 Catalog 阶段。 +**已确认(2026-08-05):** Task Dashboard 与 Whiteboard 是 `03.sdk_and_coreapp` 中用来验证 SDK 的 +bundled 参考 MiniApp,本阶段就完成其端到端路径。因此删除重复的 `03.reference-miniapps`;后续阶段 +保持为 `04.app-delivery-registry`,不需要整体重编号。 ### S5. 在实施步骤中拆出标准交互服务和 renderer/Bridge 契约 -建议在通用 Tool 闭环之后、适配 Interact 之前增加独立步骤: +**已确认(2026-08-05):** 实施顺序已调整为: -1. 实现 `standard-interaction-service`:owner 注入、记录、状态机、结果校验、过期、恢复、owner-scoped query/subscription; -2. 实现 renderer registration 与 presentation policy; -3. 定义并测试 System IM renderer 与 isolated Surface renderer 的最小回传协议; -4. 再接入 legacy Tool mapping、Interact 与两个参考 MiniApp。 +1. 冻结契约与测试样本; +2. 实现 Runtime 通用 Tool 闭环; +3. 实现 Runtime 独占的 Agent interaction service,以及 Runtime ↔ Interact 的最小呈现/提交契约; +4. 再适配 Interact IM 和 App 子会话; +5. 最后通过 Task Dashboard、Whiteboard 验证一般 bundled MiniApp 路径,并进行端到端验收。 -这样可以避免先把能力实现成 Chat 专用逻辑、后续再进行高风险重构。 +这样标准交互不会先被做成 Chat 专用或 MiniApp SDK 能力;Interact 只是第一个使用 Runtime 私有交互 +契约的系统级呈现者,两个 bundled MiniApp 则只验证各自的 Tool、Surface 和业务 UI 边界。 ## 5. 建议新增的最小验收场景 @@ -282,6 +348,6 @@ owner(legacy Agent Tool 场景中为 Interact)→ sdk.tools.complete / fail 2. 冻结 **I2:Renderer → Runtime 的提交/Bridge 契约**; 3. 冻结 **I3/I4:StandardInteractionRecord 与 Tool Call 的关系,以及 UI result 的唯一返回通道**; 4. 细化 **I5/I6:schema、状态机、幂等、错误码、持久化与日志最小化**; -5. 把 S1~S5 与第 5 节的验收场景回填入 [03.sdk_and_coreapp.md](03.sdk_and_coreapp.md) 和必要的 [APP架构设计.md](../APP架构设计.md)。 +5. 把 S1~S5 与第 5 节的验收场景回填入 [03.sdk_and_coreapp.md](03.sdk_and_coreapp.md) 和必要的 [APP架构设计.md](../../APP架构设计.md)。 在上述收敛前,不建议开始 `standard-interaction-service` 或参考 MiniApp 的实现,以免重新形成 Chat / Interact 专用旁路。 diff --git a/迭代/03.sdk_and_coreapp/02.design_review.md b/迭代/03.sdk_and_coreapp/02.design_review.md new file mode 100644 index 0000000..c107d10 --- /dev/null +++ b/迭代/03.sdk_and_coreapp/02.design_review.md @@ -0,0 +1,164 @@ +# `03.sdk_and_coreapp` 第 2 次设计评审记录 + +> 评审编号:02 +> 评审日期:2026-08-05 +> 评审对象:[03.sdk_and_coreapp.md](03.sdk_and_coreapp.md)、[01.design_review.md](01.design_review.md) +> 评审方式:独立子 agent 只读复核 +> 结论:核心方向已收敛;没有 P0 阻塞问题。以下 P1 项需在开始编码前逐项确认并回填主定义文档。 + +## 问题清单(Outline) + +> **状态标记:** ✅ 已确认并回填设计; 🟡 待产品/架构决策; 🔵 待在契约中细化; ⚪ 文档或实施编排建议; — 不适用或无此问题。 +> +> 本清单是本次评审的当前有效视图。后文保留每个问题的背景和建议;在问题被确认前,建议不是实施依据。 +> 确认后应先更新本清单,再回填 [03.sdk_and_coreapp.md](03.sdk_and_coreapp.md) 的契约、实施步骤和验收项。 + +| 状态 | 编号 | 问题 | 当前结论 / 下一步 | +|---|---|---|---| +| — | P0 | 阻塞级架构冲突 | 本次未发现需要推倒既有模型的 P0 问题。 | +| ✅ | P1-1 | Task Dashboard 示例中 Tool 完成后关闭 App | 已回填:Tool 完成只回传 Agent;App 保留、后台或关闭由用户动作、`requestClose` 或 Lifecycle Policy 决定,只有真正 `AppLifecycleManager.close` 才结束 App 与子会话。 | +| ✅ | P1-2 | `interactive` Tool 的专用路由 | 已回填:`interactive` 是 Runtime 自己处理的 Agent 交互分支,不创建/复用业务 MiniApp instance,不调整业务 App 焦点,也不进入任何 MiniApp SDK Tool Inbox;Runtime 私有契约交给 Interact / Shell 呈现。 | +| — | P1-3 | 展示位置切换后的提交资格 | 不再适用:展示位置不是交互 owner 或提交权限。切换 App 前后台、关闭 App 或改在 IM 显示不改变同一 `interaction_id` 的归属;Runtime 只接受首次有效回答,之后才拒绝重复/重放。 | +| ✅ | P1-4 | Tool 与交互的取消、超时和并发提交 | 已回填:用户回答、Agent dismiss、到期和 Runtime 失败竞争同一唯一终态;Runtime 第一个原子状态写入获胜,Tool 跟随同一结果,且只写一条 outbox。interactive Tool 仅使用交互 `expires_at`,不维护独立超时。 | +| ✅ | P1-5 | `notice` 的 Tool 完成时点与结果 | 已回填:notice 必须保留为 Interact / IM 的只读卡片,可选同时 Toast 数秒;Runtime 持久化卡片并安排呈现后,立即以 `{ outcome: "accepted" }` 完成并回传,不表示用户已阅读。 | +| — | P2-1 | 将普通标准交互统一当作敏感秘密输入 | 不再适用:`notice / choice / confirm / input` 按普通 IM 会话内容与日志基线处理;本迭代不为它们另设秘密输入契约或专门负向扫描。未提交草稿仍只在运行期存在,重启清空。 | +| ⚪ | P4-1 | 首次评审中的历史提案可读性 | **遗留问题:** 不阻塞本迭代;建议在下一次文档整理或新评审时,进一步突出其中 `sdk.ui`、owner、`parent_call_id` 等旧提案仅供追溯、不可实施。 | +| ⚪ | P4-2 | `password-input` 秘密输入原语 | **遗留问题:** 当前不阻塞 `03`;真正出现密码、卡密、私钥或临时 token 的 Agent 交互需求时,单独定义该原语及其不显示明文、不进入普通 IM 正文/日志/Telemetry/审计、可靠传递与清理等安全契约。 | + +## 通过项 + +- 标准交互已经清楚限定为 Interact 中的人与 Agent 会话交互,而非 MiniApp SDK;公开 SDK 不包含 `sdk.ui`。 +- Task Dashboard、Whiteboard 已收敛为 `kind = bundled`,必须走受限 Surface / Bridge,不能取得可信 Host DOM、Tauri、Transport、Store、Agent 或其他 MiniApp 数据。 +- 用户答案经 Interact 交给 Runtime;Runtime 保存交互归属、校验、持久化并可靠回传当前唯一 Agent,bundled MiniApp 不参与。 +- App 子会话正确区分了人与 Agent 的记录和 MiniApp 内部业务操作;前后台、关闭、历史只读和“继续处理”的总体规则一致。 +- MVP 单 Agent 边界明确,没有提前引入多 Agent 连接、切换或路由。 + +## P0:阻塞问题 + +无。 + +## P1:开始编码前需要确认的事项 + +### P1-1:Tool 完成不应自动关闭 Task Dashboard + +**已解决(2026-08-05):** 主定义文档已经确认“Agent Tool 完成不自动关闭 App,也不结束 App +子会话”。Task Dashboard 示例与验收现已统一为该规则。 + +当前规则: + +```text +Tool 完成 +→ Runtime 持久化并回传 Agent +→ App 是否保持前台、进入后台或关闭,取决于用户动作、MiniApp requestClose 或 Lifecycle Policy +→ 只有真正执行 AppLifecycleManager.close,才结束 App 与 App 子会话 +``` + +验收已要求:Tool 成功回传后 Dashboard 可继续使用;只有真正关闭才结束子会话。 + +### P1-2:interactive Tool 必须不进入 MiniApp SDK Tool Inbox + +**已解决(2026-08-05):** 通用 Tool 路由、实施步骤和验收已明确拆开 `interactive` 与一般 Tool。 +`notice / choice / confirm / input` 不会投递到 App Orchestrator、业务 MiniApp instance、SDK Tool Inbox +或 `sdk.tools.subscribe`。 + +当前规则: + +```text +handling = interactive +→ 不投递任何 MiniApp SDK Tool Inbox +→ Runtime Agent interaction service 创建 StandardInteractionRecord +→ Runtime 私有契约交给 Interact / Shell 呈现 +→ Interact 提交 interaction_id + 用户动作/答案 +→ Runtime 校验、持久化、outbox 回传 Agent + +handling = direct / launch / foreground / operation +→ 才进入 App Orchestrator、SDK Tool Inbox、MiniApp tools.* 路径 +``` + +验收已要求:Agent 发起的四类标准交互不得出现在任何 MiniApp Inbox 或 `sdk.tools.subscribe`。 + +### P1-3:切换展示位置后,旧页面必须失去提交资格 + +**不再适用(2026-08-05):** 该问题错误地把 App 上方覆盖层或 IM 卡片当成了交互的 owner。标准交互 +始终属于 Interact / 主 IM;`app_session_id` 只保留与哪段 App 工作相关的上下文,不形成 UI 父子关系。 + +切换 App 前后台、关闭 App 或改变展示位置不改变同一个 `interaction_id` 的归属,也不需要用 +`presentation_id` / `presentation_revision` 废止旧页面的回答资格。只要用户仍处于有效 LineUp / +Interact 会话且交互尚未终态,Runtime 可接受该交互的首次有效回答;以后到达的重复、重放或迟到提交 +因交互已经终态而被拒绝。Shell 可以避免用户看到重复视觉卡片,但这是体验策略,不是结果正确性的依据。 + +验收应覆盖:交互从 Whiteboard 覆盖层改在 IM 中显示、Whiteboard 关闭后交互仍 pending 时,任一有效 +Interact 页面提交的第一份答案只产生一次持久化结果和一次 Agent 回传;后续提交不改变结果。 + +### P1-4:Tool 与交互的取消、超时和并发提交需要收敛规则 + +**已解决(2026-08-05):** Runtime 是标准交互的唯一终态裁决者。用户回答、Agent dismiss、到期和 +Runtime 失败都竞争同一条交互的唯一结果。 + +当前规则: + +```text +Runtime 对仍为 pending / presented 的 interaction 执行原子状态写入 +→ 第一个成功写入的动作获胜 +→ 用户回答:持久化答案,Tool 得到 answered +→ Agent dismiss:交互与 Tool 得到 cancelled +→ expires_at 到期:交互与 Tool 得到 expired +→ Runtime 失败:交互与 Tool 得到 failed +→ 同一事务或等价原子动作中只创建一条 Tool 结果 outbox + +任何后到的回答、dismiss、取消或超时处理 +→ interaction_already_final +→ 不覆盖结果,不创建第二条 outbox +``` + +MVP 中 interactive Tool 没有另一套独立 timeout;其期限就是交互 `expires_at`。Agent 主动停止等待必须走 +`dismiss`,不走独立的通用 Tool 取消路径。 + +### P1-5:`notice` 的完成时点和 Tool 结果需要固定 + +**已解决(2026-08-05):** `notice` 是 Interact / IM 中持久保留的只读卡片;Toast 只是同一条 notice +记录的可选短暂提醒,不是没有历史的独立消息。 + +```text +Runtime 校验并持久化 notice 卡片 +→ 交给 Interact 呈现 +→ 当前前台界面可同时 Toast 数秒 +→ Runtime 立即以 { outcome: "accepted" } 完成并回传一次 Tool 结果 +``` + +`accepted` 只表示 Runtime 已接受、保存并安排呈现,不表示用户已看见或阅读。Toast 自动消失、用户忽略 +Toast 或收起 notice 卡片,均不产生新的 Agent 结果;持久化或安排呈现前 Runtime 失败时,结果为 `failed`。 + +## P2:本迭代验收前必须处理(本次无未决项) + +### P2-1:将普通标准交互统一当作敏感秘密输入 + +**不再适用(2026-08-05):** 此问题把所有标准交互一概当成秘密输入,边界过重且不符合产品语义。 +`notice`、`choice`、`confirm` 与普通 `input` 是人与 Agent 的正常 IM 会话内容:已提交的内容按普通 IM +会话历史、保留与日志基线处理,不在 SDK v1 中额外定义为密码或秘密。全局日志基线仍然有效,不能因此 +随意打印会话正文、Tool 参数或 token。 + +未提交的 `input` 草稿仍只能存在于当前运行期;重启后清空,不自动提交、发送或进入 outbox。这是恢复 +和正确性要求,不代表普通 `input` 已升级为秘密输入能力。 + +## P4:遗留问题(不阻塞当前迭代) + +### P4-1:首次评审中的历史提案可读性 + +**延期原因:** 顶部 Outline 和总体结论已经说明早期 `sdk.ui`、owner、`parent_call_id` 等提案不再是 +实施依据;这不影响当前 SDK、Runtime 或验收实现。 + +建议在下一次文档整理或新的设计评审时,将原始评审正文加上“仅供追溯,禁止作为实现、测试或验收依据” +的更醒目标识,或移至附录。当前有效结论始终以主定义文档和评审记录顶部的 Outline 为准。 + +### P4-2:`password-input` 秘密输入原语 + +**延期原因:** 当前 `03` 要完成的是 `notice / choice / confirm / input` 的 SDK v1 与会话交互闭环; +目前没有真实的密码、卡密、私钥或临时 token 输入场景。把秘密输入仅做成普通 `input` 的掩码样式,不能 +解决内容怎样保留、传递、重试和清理的问题,因此不在本迭代仓促实现。 + +**重新评估条件:** Agent 确实需要向用户收集密码、卡密、私钥、临时 token 或同类秘密时,启动单独设计。 +届时 `password-input` 应作为与 `input` 并列的 Agent 交互原语,由 Runtime 按 `kind` 强制执行安全契约, +而不是允许 Agent 用普通 `input` 自行约定保护方式。至少要明确:界面不显示明文;主 IM 只保留“已提交 +敏感信息”等替代记录;内容不进入普通日志、Telemetry 或审计;可靠 outbox 的暂存、加密(如需要)、 +Agent 接收后的清理、重启、失败、重试与一次性传递规则。 diff --git a/迭代/03.sdk_and_coreapp/03.design_review.md b/迭代/03.sdk_and_coreapp/03.design_review.md new file mode 100644 index 0000000..943f72a --- /dev/null +++ b/迭代/03.sdk_and_coreapp/03.design_review.md @@ -0,0 +1,174 @@ +# `03.sdk_and_coreapp` 第 3 次设计评审记录 + +> 评审编号:03 +> 评审日期:2026-08-05 +> 评审对象:[03.sdk_and_coreapp.md](03.sdk_and_coreapp.md) +> 参考资料:[APP架构设计.md](../../APP架构设计.md)、[01.design_review.md](01.design_review.md)、[02.design_review.md](02.design_review.md) +> 评审方式:基于当前主定义的独立只读复核;重点检查已确认的边界在契约、实施步骤与验收目标之间是否能够由同一套实现兑现。 +> 结论:产品层级、标准交互归属和受限 MiniApp 信任模型已稳定;未发现 P0 架构冲突。经本轮统一收敛,3 项 P1 与 2 项 P3 均已确认并回填主定义文档;后续不再进行纯设计评审,直接进入实现,并在实现完成后做一次关闭式复核。 + +## 问题清单(Outline) + +> **状态标记:** ✅ 已确认并回填设计; 🟡 待产品/架构决策; 🔵 待在契约中细化; ⚪ 文档或实施编排建议; — 不适用或无此问题。 +> +> 本清单是本次评审的当前有效视图。后文说明问题为何会造成实现分歧,并给出需要冻结的最小决策;在问题确认前,建议不是实施依据。确认后应先更新本清单,再回填 [03.sdk_and_coreapp.md](03.sdk_and_coreapp.md) 的契约、实施步骤和验收项。 + +| 状态 | 编号 | 问题 | 当前结论 / 下一步 | +|---|---|---|---| +| — | P0 | 阻塞级架构冲突 | 未发现。Runtime 为唯一通信与裁决方、标准交互属于 Interact、bundled MiniApp 受限运行的主线一致。 | +| ✅ | P1-1 | 用户关闭 App 时,尚未结束的普通 MiniApp Tool 怎样收敛 | 已确认并回填:一旦 Runtime 接受关闭,未终态的普通 Tool 原子转为 `cancelled(app_closed)`;已提交结果只继续 outbox;随后关闭 Surface、停止 instance、结束子会话并恢复焦点。标准交互不自动取消。 | +| ✅ | P1-2 | Agent `dismiss` 等待中的标准交互,缺少可执行的控制契约 | 已确认并回填:Runtime 私有 `interaction.dismiss` 以 `control_id + call_id` 请求;Runtime 从受认证 Envelope 推导 Agent/会话,幂等处理并只写唯一 cancelled outbox。App 已关闭事件带出仍 pending 的交互 call_id。 | +| ✅ | P1-3 | `interactive` Tool 如何确定 App 子会话关联,和通用 `ToolDescriptor.target` 怎样一致 | 已确认并回填:无 `app_session_context` 一律归主 IM;有上下文时 Runtime 验证同 Agent、同父会话的 App 子会话,已关闭会话可仅作历史关联。interactive 不使用普通 Tool target。 | +| ✅ | P3-1 | 标准交互请求类型不能直接作为 fixture 或代码依据 | 已确认并回填:去除重复字段,定义请求联合类型;可回答交互使用 Runtime 计算的 `expires_in_ms`,默认 15 分钟、范围 1 分钟至 24 小时;notice 不接受期限。 | +| ✅ | P3-2 | Whiteboard 的完成后关闭表述与统一生命周期规则冲突 | 已确认并回填:所有 bundled App 的普通 Tool 完成只结束 Tool;关闭 Surface/instance/子会话只能由显式 Lifecycle 关闭处理。重复目录条目已删除。 | + +## 通过项 + +- `Interact` 是唯一 `kind = system` MiniApp;Task Dashboard 与 Whiteboard 均是 `kind = bundled`,没有可信 Host DOM、Tauri、Transport、Store、Agent 或任意网络特权。 +- `notice / choice / confirm / input` 始终是人与 Agent 的会话交互,不进入任何 MiniApp SDK Inbox 或 `sdk.tools.subscribe`;前台 bundled App 上的视觉覆盖不成为 owner 或提交权限。 +- Runtime 保存交互归属并以原子状态写入决定唯一结果;用户首次有效回答、Agent dismiss、到期和 Runtime 失败不会产生多条 Agent outbox。 +- App 子会话只保存人与 Agent 围绕 App 的交互,不记录 Dashboard 表单、画板编辑等 App 内部业务动作;关闭 App 不自动取消仍 pending 的 Interact 交互。 +- 普通 `input` 按现有 IM 内容与日志基线处理;秘密输入已正确作为未来独立的 `password-input` P4 遗留事项保留在 [02.design_review.md](02.design_review.md)。 + +## P0:阻塞问题 + +无。 + +## P1:开始相关编码前必须确认(均已解决) + +### P1-1:用户关闭 App 时,尚未结束的普通 MiniApp Tool 怎样收敛 + +**用人话说:** 用户把 Task Dashboard 或 Whiteboard 关掉时,Runtime 不能让刚才交给那个 App 的工作 +悬在半空。Agent 要么收到“这项工作已取消/失败”的唯一结果,要么 Runtime 明确保留一个可恢复、仍有执行者 +的工作;不能只关闭画面而不说明 Tool 的命运。 + +当前文档同时出现了三种没有被统一的说法: + +```text +Task Dashboard Tool 完成 +→ 不自动关闭 Dashboard 或 App 子会话 + +Whiteboard App 完成或失败 +→ Runtime 关闭 Surface、恢复 Interact + +MiniApp requestClose() +→ Runtime 检查 pending Tool、Surface、operation 和 Policy +→ 允许关闭或返回拒绝码 +``` + +最后一条没有说明“检查之后”的规则。若用户关闭正在执行 Tool 的 App,Tool 是被拒绝关闭、由 Runtime 先取消 +Tool、由 App 收到取消后再关闭,还是允许 Surface 消失但实例在后台恢复执行?这些选择对 outbox、恢复和用户 +看到的状态都不同。Whiteboard 的“完成后关闭”也与 Task Dashboard 的“完成不关闭”相互冲突。 + +**已确认(2026-08-05):** 一旦 Runtime 接受用户、MiniApp 或 Policy 的关闭请求,关闭的意思就是释放该 +App instance,不是把它偷偷留在后台。Runtime 不再向该 instance 投递新 Tool;对绑定该 instance 且仍为 +`received / routing / waiting_for_app / running` 的普通 Tool,以 `app_closed` 原因原子转为 `cancelled`,并且 +每条 Tool 只写一条 cancelled outbox。已处于 `submitted` 的 Tool 结果不可改写,Runtime 继续将已固定的结果 +可靠发出。之后 Runtime 关闭 Surface、停止 instance、结束 App 子会话并恢复前一有效前台 App。 + +同一时刻 Tool 完成与关闭竞争时,第一个原子终态写入获胜;重复关闭、重启恢复和迟到上报不得产生第二条 +outbox。用户若只想暂时离开 App,应进入后台而不是关闭。此规则只处理普通 MiniApp Tool;仍 pending 的 +人与 Agent 标准交互不自动取消,Runtime 仅通知 Agent,由 Agent 选择是否 dismiss。 + +以下原“需要冻结”的项目均由上述决议覆盖: + +1. 对每种 Tool 状态(`waiting_for_app`、`running`、`submitted` 等),规定用户/Policy 请求关闭时的行为; +2. 规定普通 Tool 的最终结果由谁写入、是否必须先让 Tool 进入 `completed / failed / cancelled / expired` 才能完成关闭; +3. 明确“关闭 App instance”“关闭该 App 的 Surface”“Tool 成功/失败”三者不是同一个事件,并定义允许的先后顺序; +4. 将 Task Dashboard 与 Whiteboard 的完成、关闭和焦点恢复规则统一到同一条生命周期原则; +5. 增加关闭进行中 Tool、重启恢复和重复关闭只产生一个最终 Tool outbox 的验收 fixture。 + +### P1-2:Agent `dismiss` 等待中的标准交互,缺少可执行的控制契约 + +**用人话说:** 文档已经允许 Agent 看到“画板已关闭”后,决定把之前的问题收起来。这很好;但还没有写清 +Agent 发来的“收起这题”消息长什么样,Runtime 怎么确认它收的是正确那一道题,以及网络重发时如何不重复 +通知 Agent。实现者因此可能各自发明一个临时控制消息。 + +当前仅有行为描述: + +```text +Agent remote dismiss +→ Runtime 将 pending / presented interaction 终结为 cancelled +→ 写入唯一 cancelled outbox +``` + +但缺少下面的契约: + +- Agent 发起 `dismiss` 使用 `interaction_id`、原始 `call_id`,还是二者都使用; +- Runtime 如何从已保存记录校验 `agent_id`、`conversation_id` 与当前状态,而不是信任客户端字段; +- 目标已回答、已到期或同一条 dismiss 重放时,应得到何种稳定回执,是否绝不新增 outbox; +- Runtime 发出的 App 已关闭事件如何关联原 Tool/interaction,使 Agent 能准确选择要 dismiss 的问题; +- dismiss 控制请求本身如何去重、审计并在 Runtime 重启后恢复处理。 + +**已确认(2026-08-05):** 定义 Runtime 私有的 Agent → Runtime `interaction.dismiss` 控制契约。Agent 使用 +自己发起原 interactive Tool 时持有的 `call_id` 定位目标,并为每次控制请求提供唯一 `control_id`。Runtime 从 +受认证 Envelope 取得 `agent_id` 与 `conversation_id`,不信任请求额外携带的归属字段;它以这两个值和 +`call_id` 查找交互,原子地将仍 pending / presented 的记录转为 cancelled 并写入唯一 outbox。已终态或重放 +请求只返回稳定幂等回执,不覆盖结果、不再写 outbox。 + +App 已关闭事件会携带仍 pending 的 `pending_interaction_call_ids`,让 Agent 能按业务需要精确 dismiss;该控制 +契约不属于 MiniApp SDK,也不能投递给 Interact 或 bundled MiniApp。 + +### P1-3:`interactive` Tool 如何确定 App 子会话关联,和通用 `ToolDescriptor.target` 怎样一致 + +**用人话说:** 视觉上哪个 App 在前台,不等于 Agent 的问题就属于哪个 App。例如用户正看 Whiteboard, +Agent 仍可能问一条普通 IM 问题;反过来,画板已经关了,Agent 仍可能针对刚才的画板提问。因此 Runtime +不能用“当前前台 App”猜测问题应该放进哪个子会话。 + +当前记录有可选 `app_session_id`,且要求与 App 有关的问题写进子会话;但没有说明 Agent Tool 请求如何 +表达这层上下文、Runtime 如何验证它。与此同时,所有 `ToolDescriptor` 都要求 `target.app_scope`,而 +`interactive` 明确不创建/复用任何业务 MiniApp,也不投递给 Interact 的 SDK Tool Inbox。这会造成至少两种 +不兼容实现:有人将交互强行 target 到 `chat`,有人直接跳过 `target`,也有人按当前前台 App 自动归档。 + +**已确认(2026-08-05):** 不带 `app_session_context` 的 interactive Tool 一律记录在主 IM,不创建或猜测 +App 子会话。Agent 若要关联某段 App 工作,可在 interactive Tool 的运行时上下文中提供 +`app_session_context.app_session_id`;Runtime 必须验证其属于同一 `agent_id`、父 conversation 和真实 App +子会话。已经关闭的子会话仍可作为历史上下文关联,但不会复活 instance 或旧问题。验证失败则拒绝该 Tool, +不创建交互。 + +`handling = interactive` 不使用普通 MiniApp Tool 的 `target`,也不因此变为 Interact MiniApp Tool。Runtime +在创建 App 子会话时向当前 Agent 可靠发送其稳定 ID,供后续 Agent 交互引用。 + +以下原“需要冻结”的项目均由上述决议覆盖: + +1. 不带经验证 App 上下文的 `interactive` Tool 一律写入主 IM,不创建 App 子会话; +2. 若 Agent 需要关联某段 App 工作,定义它可引用的稳定 App 子会话标识,以及 Runtime 必须验证的 + `agent_id`、父 conversation、App session 状态和权限边界;已关闭 App 关联的问题是否仍允许保留,需要明确; +3. 为 `handling = interactive` 定义与普通 `ToolDescriptor.target` 不同的明确规则:例如 target 对它不适用, + 或只允许一个受限的“会话上下文”字段;不能让它暗中变成 Interact MiniApp Tool; +4. 在 fixture 中覆盖“Whiteboard 前台但问题属于主 IM”“App 已关闭但问题仍关联旧子会话”“非法/跨会话 + `app_session_id` 被拒绝并不产生交互”三种情况。 + +## P2:本迭代验收前必须处理(本次无未决项) + +本次未发现独立 P2 问题。P1 项处理后,应把其中的关闭、dismiss、上下文绑定 fixture 纳入本迭代验收。 + +## P3:本迭代结束前必须处理(均已解决) + +### P3-1:标准交互请求类型不能直接作为 fixture 或代码依据 + +`ChoiceRequest` 代码块中 `prompt` 出现两次。它不是不同字段,直接照抄会造成 TypeScript 重复属性定义。 +此外,记录使用了尚未声明的 `StandardInteractionRequest`,文字规定 Agent 可给出 1 分钟至 24 小时的期限, +但四种最低请求类型都没有统一的期限字段。 + +**已确认(2026-08-05):** 删除重复 `prompt`;`StandardInteractionRequest` 由四种请求组成。可回答交互 +在请求中使用 `expires_in_ms`,Runtime 根据自身当前时间计算并持久化 `expires_at`;未提供时默认 15 分钟, +仅接受 1 分钟至 24 小时。notice 不等待回答,也不接受期限。fixture 覆盖默认、越界和 notice 错带期限。 + +### P3-2:Whiteboard 的完成后关闭表述与统一生命周期规则冲突 + +Task Dashboard 已清楚规定 Tool 完成不会关闭 App;Whiteboard 的最小体验却写成“App 完成或失败,Runtime +关闭 Surface、恢复 Interact”。这会让两个同为 `kind = bundled` 的参考 App 获得不同且没有声明依据的 +生命周期语义,也和“只有 `AppLifecycleManager.close(instance_id)` 才停止实例/结束子会话”不一致。 + +**已确认(2026-08-05):** Whiteboard 与 Task Dashboard 使用相同规则:Tool `complete / fail` 只结束 Tool; +Surface、instance、焦点和 App 子会话仅由前后台或显式 `AppLifecycleManager.close` 处理。SDK v1 不为 +Whiteboard 预设“提交后自动关闭”的例外;将来若需要,必须作为显式 Lifecycle Policy 并遵循 P1-1 的关闭收敛。 + +实现模块目录树中的 `conversation-store.ts` 也重复出现一次,应一并删除重复行,避免错误引导目录改动。 + +## P4~P5:遗留问题 + +本次没有新增 P4/P5。第 2 次评审已登记的 `P4-1`(历史提案可读性)和 `P4-2`(未来 `password-input` +秘密输入原语)继续作为不阻塞当前迭代的遗留问题,其延期原因与重新评估条件以 +[02.design_review.md](02.design_review.md) 为准。 diff --git a/迭代/03.sdk_and_coreapp/03.sdk_and_coreapp.md b/迭代/03.sdk_and_coreapp/03.sdk_and_coreapp.md new file mode 100644 index 0000000..df9b343 --- /dev/null +++ b/迭代/03.sdk_and_coreapp/03.sdk_and_coreapp.md @@ -0,0 +1,1095 @@ +# LineUp App 迭代定义:MiniApp SDK v1 与内置参考 MiniApp + +**迭代编号:** 03.sdk_and_coreapp +**状态:** 目标设计,待实现 +**日期:** 2026-08-05 +**前置基线:** [00.base.md](../00.base/00.base.md)、[01.kernel.md](../01.kernel/01.kernel.md) +**权威架构:** [APP架构设计.md](../../APP架构设计.md) + +## 1. 迭代目标 + +本迭代不建设应用市场、服务端 Catalog、远程下载或第三方发布能力。它的目标是定义并实现 +**LineUp MiniApp SDK v1**,再用多个随 LineUp 开发版本内置的参考 MiniApp 验证这份 SDK。 + +```text +LineUp App + → LineUp Runtime + → LineUp MiniApp SDK v1 + → Interact MiniApp + → Task Dashboard MiniApp + → Whiteboard MiniApp +``` + +本迭代完成后,应能证明: + +```text +Runtime 能以同一套 SDK 语义承载系统级 MiniApp 与受限参考 MiniApp; +MiniApp 只能通过 SDK 收消息、接 Tool、报告进度/结果/错误、请求生命周期与 Surface; +Runtime 始终拥有通信、Tool、焦点、存储、权限和审计的最终决定权。 +``` + +这里的“内置参考 MiniApp”指 Task Dashboard 与 Whiteboard 的 Manifest、代码和测试 fixture 随当前 +LineUp 开发 Host 提供。它们是 `kind = bundled`,不是远程下载的 Bundle,也不等于已经开放的应用市场。 +Interact 同样随产品发布,但它是唯一的 `kind = system` MiniApp。 + +`kind = bundled` 描述非系统级 MiniApp 的当前交付来源与受限运行策略;`kind = system` 只用于随 +LineUp 可信代码发布的系统级 MiniApp。本迭代中只有 Interact 是 System MiniApp;Task Dashboard +与 Whiteboard 都是 `kind = bundled` MiniApp。“参考”只描述它们在本迭代用于验证 SDK 的工作目的, +不是 Manifest 类型名称,也不增加 Host 信任或特权。 + +### 1.1 已确认的实施决议 + +本节是本迭代的实施基线。后文对每项决议给出更完整的契约、状态和验收要求;评审记录中的早期方案 +或未确认建议不得覆盖本节。 + +| 主题 | 已确认的结论 | +|---|---| +| 产品层级 | 产品是 **LineUp App → LineUp Runtime → MiniApps**。Runtime 是 App 内的核心运行环境,不是与 MiniApp 平级的产品。 | +| MiniApp 类型 | `MiniAppManifest.kind` 只有 `system \| bundled`。Interact 是唯一 `system`;Task Dashboard、Whiteboard 都是 `bundled`,必须使用一般 MiniApp 相同的 SDK、权限、生命周期和受限 Surface / Bridge。 | +| MVP Agent 范围 | 一个 LineUp Runtime 只连接一个 Agent。交互和 App 子会话保存当前 `agent_uid` 作为 `agent_id`,但本迭代不做多 Agent 连接、切换、会话列表、outbox 或路由。 | +| 人与 Agent 交互 | `notice / choice / confirm / input` 是 Interact 中人与 Agent 的会话交互,与 IM 消息同层;它们不是 MiniApp SDK,也不用于 App 内业务 UI。只有 Agent Tool 能发起。 | +| 回传责任 | Interact / Shell 只展示问题并收集用户动作/答案。Runtime 保存交互归属,校验提交、持久化、去重、恢复并可靠回传 Agent。bundled MiniApp 不可发起、读取、提交、取消或伪造这类交互。 | +| App 前台时的交互 | bundled App 在前台时,Interact / Shell 可以把同一会话的交互层显示在其上方;视觉覆盖不改变交互归属,App 也不会取得问题或答案。 | +| App 子会话 | 每个启动的 App instance 创建或恢复一个与主 IM 关联的子会话。它只记录人与 Agent 围绕该 App 的说明、提问、回答和简洁结果;App 内按钮、表单、画板编辑等业务操作不记录。 | +| 子会话生命周期 | 前后台切换、暂停和 Agent Tool 完成都不结束子会话。只有 `AppLifecycleManager.close(instance_id)` 使 instance 停止或失败时,才结束子会话并在主 IM 保留折叠历史;未回答的 Interact 交互不随 App 自动取消,Runtime 会将关闭事件通知 Agent。 | +| App 关闭与普通 Tool | Runtime 一旦接受关闭请求,即停止向该 instance 投递新 Tool,并将其未终态普通 Tool 原子收敛为 `cancelled(app_closed)`;已提交结果只继续可靠发送。随后关闭 Surface、停止 instance、结束子会话并恢复焦点。用户若仅暂时离开 App,应进入后台而不是关闭。 | +| App 子会话关联 | Agent 的 interactive Tool 未带 `app_session_context` 时归主 IM;只有 Runtime 验证通过的 `app_session_id` 才能关联子会话,绝不从当前前台 App 猜测。已关闭子会话可保留为历史上下文,不会复活旧 instance。 | +| 历史与继续处理 | 已结束子会话只能只读查看。用户选择“继续处理”时,Runtime 创建新的 instance 和新的子会话;可引用旧 artifact 或已保存状态,但不续写旧记录,也不复活旧问题。 | +| 草稿与已提交答案 | 未提交 `input` 草稿仅存在当前运行期间,重启即清空,不自动提交或发送;已提交答案保存在所属会话历史中,二者均遵循普通 IM 的内容保留与日志基线。SDK v1 不把普通 `input` 统一视为秘密输入;`password-input` 不在本迭代范围。 | + +## 2. 本迭代的产品与信任模型 + +### 2.1 正确层级 + +```text +LineUp App 用户使用的主应用 +├── App Shell / Host 挂载、切换、通知、恢复、Host Provider +├── LineUp Runtime 通信、可靠性、MiniApp 调度和安全决策 +└── MiniApps 运行在 Runtime 上的功能单元 + ├── System MiniApp: Interact + ├── Bundled MiniApp: Task Dashboard(SDK 参考实现) + └── Bundled MiniApp: Whiteboard(SDK 参考实现) +``` + +Runtime 不是一个与 MiniApp 平级的产品 App;它是 LineUp App 内部提供给 MiniApp 的运行环境。 + +### 2.2 Interact 的定位 + +`Interact` 是第一个系统级 MiniApp,是用户与 Agent 的默认入口: + +```text +Interact +├── IM Mode:本迭代必须实现并适配 SDK +├── Audio Mode:只定义 SDK / 生命周期扩展位,不实现真实媒体能力 +├── Video Mode:只定义 SDK / 生命周期扩展位,不实现真实媒体能力 +└── IM 标准交互的默认 Renderer Provider:notice / choice / confirm / input +``` + +当前实现仍保持兼容名称: + +```text +产品概念:Interact MiniApp +当前 app_scope:chat +当前目录:tauri/src/core-apps/chat/ +``` + +本迭代不得把 `chat` 重命名为 `interact`,不得迁移目录或旧 `lineup.v1` scope;命名迁移另行 +定义,避免破坏已验证的通信与恢复行为。 + +### 2.3 相同语义,不同权限视图 + +所有 MiniApp 都遵守 MiniApp SDK v1 的同一语义;系统级与参考/已安装 MiniApp 的区别是来源和 +UI 容器,而不是是否可以绕过 Runtime。 + +| 能力 | Interact System MiniApp | Task Dashboard / Whiteboard | +|---|---:|---:| +| 读取自身 Inbox、ACK | 可以 | 可以 | +| 接收 Runtime Tool Call | 可以 | 可以 | +| 上报 progress / result / error | 可以 | 可以 | +| 请求前台、后台、关闭 | 可以,Runtime 决定 | 可以,Runtime 决定 | +| 请求 Surface / Capability | 可以,经 Policy | 可以,经 Policy | +| 使用可信内建 DOM | 可以 | 不可以;两者都必须在受限 Surface / Bridge 中运行 | +| 直接访问 Transport、Store、Agent | 不可以 | 不可以 | +| 直接访问 Tauri、Host DOM 根节点、任意网络 | 不可以 | 不可以 | +| 修改 Focus、Registry、其他 MiniApp 数据 | 不可以 | 不可以 | + +Task Dashboard 和 Whiteboard 的安全与运行时模型必须与未来的一般非系统级 MiniApp 相同。它们的 +区别仅是本迭代随开发 Host 预置、并分别覆盖轻量 Tool/lifecycle 和复杂 Surface/Artifact 能力面, +不是拥有额外 Host 信任或特权。Host adapter 如存在,只能是 Runtime/Shell 内部创建、销毁、恢复和 +绑定受限 Surface 的装配代码;它不得把 MiniApp 业务 UI 挂到可信 Host DOM,也不得提供 Tauri、 +Transport、Store、Agent、任意网络或其他 MiniApp 数据访问。 + +## 3. MiniApp SDK v1 契约 + +SDK 是 Runtime 根据 Manifest、实例状态、scope 和 Policy 注入的受限对象。SDK 中的所有写操作都 +是 **request**,不是对 Host、Store、Transport 或其他 MiniApp 的直接命令。 + +```ts +interface LineUpMiniAppSDK { + readonly context: MiniAppContext; + + readonly inbox: MiniAppInboxAPI; + readonly tools: MiniAppToolAPI; + readonly lifecycle: MiniAppLifecycleAPI; + readonly surfaces: MiniAppSurfaceAPI; + readonly capabilities: MiniAppCapabilityRequestAPI; +} + +type MiniAppContext = { + app_scope: string; + instance_id: string; + conversation_id: string; + app_version: string; + state: "starting" | "foreground" | "background" | "suspended"; +}; +``` + +`context` 由 Runtime 在创建实例时注入。MiniApp 不可传入、伪造或修改 `app_scope`、 +`instance_id`、`conversation_id`,也不可获得用户身份、Agent 身份、登录 token、AppServer +地址、Transport endpoint 或其他实例上下文。 + +### 3.1 Inbox API + +```ts +interface MiniAppInboxAPI { + list(after_message_id?: string): readonly MiniAppMessage[]; + subscribe(listener: (message: MiniAppMessage) => void): Unsubscribe; + acknowledge(message_id: string): boolean; +} +``` + +行为约束: + +```text +Agent / Runtime 消息 +→ Runtime 校验 Envelope、scope、目标 app_scope、instance 和去重 +→ 先持久化到该 MiniApp Inbox +→ SDK 投递已筛选消息 +→ MiniApp 成功接管后 ACK +→ Runtime 移除该可投递记录 +``` + +后台化、刷新、Surface 重载、断线和 Runtime 重启不得丢失未 ACK 的 Inbox 消息。MiniApp 只能 +列出和 ACK 自己 `app_scope + conversation_id + instance_id` 范围内的投递。 + +### 3.2 Tool API + +```ts +interface MiniAppToolAPI { + subscribe(listener: (call: MiniAppToolCall) => void): Unsubscribe; + get(call_id: string): MiniAppToolCall | undefined; + reportProgress(call_id: string, progress: MiniAppToolProgress): Promise; + complete(call_id: string, result: JsonObject): Promise; + fail(call_id: string, failure: MiniAppToolFailure): Promise; + cancel(call_id: string, reason?: string): Promise; +} +``` + +每一个调用都必须有 Runtime 持久化的通用记录: + +```ts +type RuntimeToolCallRecord = { + call_id: string; + tool_id: string; + inventory_revision: string; + app_scope: string; + instance_id?: string; + conversation_id: string; + status: + | "received" + | "routing" + | "waiting_for_app" + | "running" + | "submitted" + | "completed" + | "failed" + | "cancelled" + | "expired" + | "rejected"; + input: JsonObject; + result?: JsonObject; + error_code?: string; + cancel_reason?: "app_closed" | "agent_cancelled" | "runtime_cancelled"; + created_at: string; + updated_at: string; +}; +``` + +`complete`、`fail`、`cancel` 绝不能直接发送网络请求。Runtime 必须验证: + +```text +call_id 属于当前 MiniApp instance +调用状态允许此迁移 +结果符合 ToolDescriptor.output_schema +调用不是重复终态 +调用仍属于当前 conversation / scope +MiniApp 和 Tool 仍处于启用状态 +``` + +验证成功后,Runtime 持久化记录和审计,再写入可靠 outbox 并回传 Agent。 + +### 3.3 Lifecycle API + +```ts +interface MiniAppLifecycleAPI { + requestForeground(): Promise; + requestBackground(): Promise; + requestClose(reason?: string): Promise; +} +``` + +MiniApp 只能请求,不能直接改变焦点或挂载其他 MiniApp: + +```text +MiniApp requestClose() +→ Runtime 检查 pending Tool、Surface、operation 和 Policy +→ 允许关闭或返回受控拒绝码 +→ 一旦允许,instance 进入 closing,Runtime 不再投递新 Tool +→ 仍为 received / routing / waiting_for_app / running 的、绑定该 instance 的普通 Tool + 原子进入 cancelled(app_closed),每条只写一条 cancelled outbox +→ 已 submitted 的 Tool 结果不可改写,Runtime 继续可靠发送既有结果 +→ 关闭该 instance 的 Surface,停止 instance,结束 App 子会话 +→ Runtime 调整 focus stack,并恢复前一有效 foreground instance +``` + +“关闭 App instance”“关闭 Surface”“普通 Tool 完成”是三个不同事件。普通 Tool 的 `complete / fail` 不会 +自动关闭 App、Surface 或子会话;只有 Runtime 接受的显式 Lifecycle 关闭才执行上面的释放流程。Tool 完成 +与关闭请求并发时,以 Runtime 的第一个原子终态写入为准;重试、重启恢复和迟到上报均不得再写第二条 outbox。 +用户若只想离开当前页面,应请求后台化,而不是关闭。 + +### 3.4 人与 Agent 的标准交互(不属于 MiniApp SDK) + +`notice`、`choice`、`confirm`、`input` 是 **Interact 的人与 Agent 交互组件**,与 IM 消息同属 +用户和 Agent 的对话过程。它们不是 MiniApp SDK,也不能被 Task Dashboard、Whiteboard 或未来的 +普通 MiniApp 用作自身业务表单、确认框或输入框。 + +MVP 阶段,单个 LineUp Runtime 只连接一个 Agent:当前登录会话中的 `agent_uid` 就是所有交互的 +`agent_id`。本迭代不实现多 Agent 连接、Agent 切换、多个 Agent 的主 IM 会话、多个 outbox 或 +跨 Agent 路由;但 Runtime 仍须在每个交互记录中保存 `agent_id`,不能只依赖当前前台页面猜测 +答案该交给谁。 + +Agent 需要说明、让用户选择、确认或输入信息时,通过已定义的 Agent Tool 交互请求(当前兼容 +`lineup.v1.tool.call(choice | confirm | input)`)发起。Runtime 负责校验、持久化、去重和可靠回传; +Interact 负责将其作为当前会话的一部分呈现给用户。 + +第一版固定支持以下四种原语: + +| 原语 | 用途 | 用户结果 | +|---|---|---| +| `notice` | 安全显示只读提示、状态或下一步说明。 | 不等待用户结果;Runtime 返回 `accepted`,不表示用户已阅读。 | +| `choice` | 在有限选项中选择一个。 | 一个 `action_id`。 | +| `confirm` | 明确同意/取消一个可解释操作。 | `approved: boolean`。 | +| `input` | 输入一段受长度限制的文本或多行文本。 | `text: string`。 | + +最低请求形态: + +```ts +type NoticeRequest = { + kind: "notice"; + title: string; + message: string; + dismiss_label?: string; +}; + +type AnswerableInteractionRequestBase = { + title: string; + prompt: string; + // Runtime 用自身当前时间计算 expires_at;缺省 15 分钟,范围 1 分钟至 24 小时。 + expires_in_ms?: number; +}; + +type ChoiceRequest = AnswerableInteractionRequestBase & { + kind: "choice"; + mode: "single-choice"; + actions: readonly { id: string; label: string; description?: string }[]; +}; + +type ConfirmRequest = AnswerableInteractionRequestBase & { + kind: "confirm"; + approve_label?: string; + cancel_label?: string; +}; + +type InputRequest = AnswerableInteractionRequestBase & { + kind: "input"; + field: { + id: string; + label: string; + type: "text" | "textarea"; + required?: boolean; + placeholder?: string; + max_length?: number; + }; + submit_label?: string; + cancel_label?: string; +}; + +type StandardInteractionRequest = + | NoticeRequest + | ChoiceRequest + | ConfirmRequest + | InputRequest; +``` + +第一版的问答范围必须保持小而清晰: + +| 原语 | 第一版规则 | +|---|---| +| `notice` | 只用于告知,不等待用户回答,也不阻塞 Agent。每条 notice 必须作为 Interact / IM 的只读卡片或气泡保留;当前界面可同时以 Toast 提醒数秒,但 Toast 不是唯一载体。需要用户决定时必须使用 `confirm` 或 `choice`。 | +| `confirm` | 只有确认和取消两个结果;默认安全结果是取消。涉及删除、覆盖、发送等后果时,确认按钮必须写出真实动作,不能只写“确定”。 | +| `choice` | 只支持单选;必须有 2~6 个 ID 唯一的选项,用户只能提交其中一个 ID。多选留待后续版本。 | +| `input` | 只支持一个文本或多行文本输入框;可要求非空,默认最多 1,000 字,Agent 只能请求更小的上限。标题、描述、日期等多字段业务表单属于 MiniApp 自己的 UI,不属于 Interact。 | + +这四种交互是普通会话交互,不在 SDK v1 中统一按密码或秘密输入对待。已提交的内容按普通 IM 的会话历史、 +保留和日志基线处理;未提交 `input` 草稿只在当前运行期存在。密码、卡密、私钥、临时 token 等真正的 +秘密输入将来使用独立的 `password-input` 原语,本迭代不实现。届时由 Runtime 按交互类型强制相应保护, +不能让 Agent 用普通 `input` 来绕过该保护。 + +每个需要用户回答的交互都有且只有一次有效回答。用户的第一次有效提交会结束该问题;重复点击、 +网络重试、刷新、旧页面重放或已经失效的交互都不能改变答案,也不能向 Agent 再发送一次结果。 +若 Agent 需要追问,必须创建新的交互,不能重新打开旧问题。 + +标准交互的流程如下: + +```text +Agent 在当前会话中请求 choice / confirm / input / notice +→ 可选携带 app_session_context.app_session_id,表示“这与哪段 App 工作有关” +→ Runtime 校验 call_id、conversation_id、请求 schema、状态、去重与 App 子会话上下文 + ├── 未携带 app_session_context:归入主 IM,不创建或猜测 App 子会话 + └── 携带时:只接受同一 agent_id、同一父会话且由 Runtime 创建的 app_session_id; + 已关闭 App 子会话可作为历史上下文关联,非法或跨会话引用则拒绝且不创建交互 +→ Runtime 创建持久化 StandardInteractionRecord +→ Interact 将它作为人与 Agent 的会话交互呈现 + ├── Interact 位于前台:显示在 IM 时间线 / 卡片内 + └── bundled MiniApp 位于前台:由 Interact / Shell 在当前 App 之上显示同一会话的交互层 +→ 用户选择、确认、输入、取消或超时 +→ Interact 仅把用户动作和答案交给 Runtime;不直接发送给 Agent +→ Runtime 核对交互 ID、当前会话、创建时保存的 agent_id、有效期、答案格式和是否已经结束 +→ Runtime 只接受第一次有效回答,持久化结果和状态变化,并写入可靠 outbox +→ Agent 收到对应的 Tool 结果或取消结果 +``` + +换句话说,Interact 是用户操作的入口,不是消息发送端。它提交的只是“这个问题的用户答案”;Runtime +根据自己保存的上下文判断该答案属于哪一个 Agent Tool 和哪一个主会话/App 子会话。这样即使页面刷新、 +用户重复点击或网络暂时失败,也不会把同一个答案交给 Agent 两次;网络重试由 Runtime 的 outbox 处理, +用户不必重新回答。 + +Interact 向 Runtime 提交回答时,Runtime 核对 `interaction_id`、当前 LineUp / Interact 会话、当前状态和 +有效期。客户端不传递、也不能改写 `agent_id`、`conversation_id`、`call_id` 或 `app_session_id`;这些都只从 +Runtime 创建交互时保存的记录中取得。App 前后台切换、App 关闭或交互从覆盖层改在 IM 卡片显示,都不改变 +这条交互的归属,也不废止此前有效 Interact 页面提交同一问题的资格;Runtime 只用“首次有效回答”决定结果, +之后的重复或重放提交才因交互已经终态而被拒绝。 + +当 bundled MiniApp 位于前台时,这个交互层在视觉上可以覆盖当前 App,但它仍属于 Interact 和当前 +会话:MiniApp 不读取请求内容、不接收用户答案、不决定提交或取消,也不因它获得 Host DOM 权限。 +用户回答后,Runtime 将结果交回 Agent;若 Agent 后续要创建或更新任务,再由 Agent 调用对应的 +MiniApp Tool。 + +这意味着标准交互不会成为 MiniApp 的业务 UI 或跨 App 操作旁路: + +```text +MiniApp 不能发起、读取、提交、取消或伪造人与 Agent 的标准交互 +MiniApp 不能在 Host DOM 注入任意弹窗、表单或远端 HTML +Interact 不能直接把结果发送给 Agent +交互结果必须先回到 Runtime,再由 Runtime 可靠回传 Agent +Interact 不能代替 Runtime 判断答案应发送给哪个 Agent、哪个会话或哪个 App 子会话 +``` + +所有人与 Agent 的交互都必须在 Interact / IM 中留下对应的卡片、气泡或结果记录;不能只靠一次性 +Toast 而不保留会话痕迹。`notice` 是只读记录:它在 IM 中保留一张 notice 卡片,当前前台界面可选同时 +显示数秒 Toast。Toast 与卡片是同一 `interaction_id` 的两种呈现,不是两条消息;Toast 自动消失或用户 +未注意到,不改变记录和 Agent 结果。 + +所有 Agent 标准交互共享一套 `StandardInteractionRecord`、状态机、持久化、过期、去重和可靠回传路径; +它们可以根据当前前台 App 选择 IM 内联或系统交互层的显示方式,但不改变其 Interact / 会话归属。 + +`StandardInteractionRecord` 至少包含以下 Runtime 内部字段: + +```ts +type StandardInteractionRecord = { + interaction_id: string; + call_id: string; + agent_id: string; + conversation_id: string; + interact_instance_id: string; + app_session_id?: string; + presentation_surface_id?: string; // 仅记录展示位置元数据,不是 owner 或提交权限 + kind: "notice" | "choice" | "confirm" | "input"; + request: StandardInteractionRequest; + status: "pending" | "presented" | "submitted" | "completed" | "cancelled" | "expired" | "failed"; + result?: StandardInteractionResult; + created_at: string; + updated_at: string; + expires_at?: string; +}; +``` + +状态必须有界: + +```text +notice:pending → presented → completed + +choice / confirm / input: +pending → presented → submitted → completed | cancelled | expired | failed +``` + +对 Agent 而言,标准结果只表达用户最后是否回答及答案,不携带界面的排版、点击次数或草稿变化: + +```ts +type StandardInteractionResult = + | { outcome: "accepted" } // notice 已持久化并交给 Interact 呈现,不代表用户阅读 + | { outcome: "answered"; answer: { action_id: string } } + | { outcome: "answered"; answer: { approved: boolean } } + | { outcome: "answered"; answer: { text: string } } + | { outcome: "cancelled" } + | { outcome: "expired" } + | { outcome: "failed"; error_code: string }; +``` + +`notice` 的 `completed` 表示 Runtime 已持久化 notice 卡片、安排 Interact 呈现并写入一次 `{ outcome: +"accepted" }` 的 Tool 结果/outbox;它不等待 Toast 的显示时长,也不等待用户阅读。若在持久化或安排 +呈现前 Runtime 失败,则 notice 的 Tool 结果为 `failed`。Toast 自动消失或用户手动收起 notice 卡片,都 +只是本地展示动作,不能产生新的 Agent 结果。 + +`choice`、`confirm`、`input` 的 `expires_in_ms` 未提供时默认等待 15 分钟;仅接受 1 分钟到 24 小时。 +Runtime 按自己的当前时间计算并保存 `expires_at`,不信任 Agent 的绝对时间。`notice` 不等待回答,也不得 +携带期限。用户点击取消、关闭交互层或明确放弃时,结果为 `cancelled`;到期未回答时为 `expired`;Runtime +自身无法继续时为 `failed`。系统不得猜测用户意图,也不得把取消或超时伪装成确认。 + +Agent 也可以向 Runtime 发送远程 `interaction.dismiss` 指令,要求收起仍为 `pending` / `presented` 的交互; +它是 Runtime 私有的 Agent 控制契约,不属于 MiniApp SDK: + +```ts +type InteractionDismissRequest = { + control_id: string; // 本次控制请求的唯一编号,用于 Agent 重试去重 + call_id: string; // 原 interactive Tool Call;MVP 中一条 Call 对应一条交互 + reason?: string; // 受控诊断原因,例如 app_closed +}; +``` + +Runtime 从受认证 Envelope 取得 `agent_id`、`conversation_id`,并以它们和 `call_id` 查找记录;客户端不能 +传入或改写归属。若交互仍为 `pending / presented`,Runtime 原子将其按 `cancelled` 终结、从 Interact 中移除 +并写入唯一 cancelled outbox。若目标已终态或同一 `control_id` 被重放,只返回稳定的幂等回执,不覆盖结果、 +不再写 outbox。典型场景是 Runtime 已通知 Agent“关联的 App 已关闭”,而 Agent 判断这个问题已不再有意义。 +App 本身不能发送该指令;关闭 App 也不会隐式产生该指令。 + +##### 最终结果只能产生一次 + +`choice`、`confirm`、`input` 的用户回答、Agent 的远程 `dismiss`、到期和 Runtime 失败,都在争夺同一条 +交互的唯一最终结果。Runtime 是唯一裁决者:它以同一个持久化事务或等价的原子比较/更新动作检查交互 +仍为 `pending` / `presented`,并由第一个成功写入的动作获胜。 + +```text +用户首次有效回答先成功落库 +→ Runtime 持久化答案,交互进入 submitted(该答案已不可被取消、超时或其他答案覆盖) +→ 写入唯一的 Tool 结果 outbox +→ 交互完成为 completed,Tool 得到 answered + +Agent dismiss 先成功落库 +→ 交互与 Tool 均为 cancelled +→ 写入唯一的 cancelled outbox + +Runtime 在 expires_at 后先成功处理到期 +→ 交互与 Tool 均为 expired +→ 写入唯一的 expired outbox + +Runtime 无法继续先成功记录失败 +→ 交互与 Tool 均为 failed +→ 写入唯一的 failed outbox +``` + +之后到达的回答、dismiss、取消或超时处理不得覆盖已有结果,也不得创建第二条 outbox,只返回稳定的 +`interaction_already_final` 或等价状态。这里“先”指 Runtime 成功完成原子状态写入的先后,而不是客户端 +点击时间或网络到达顺序。 + +MVP 中,`interactive` Tool 不维护另一套独立的等待期限:其唯一超时来源就是关联交互的 `expires_at`。 +Agent 若要主动停止等待,必须请求 `dismiss`,而不是走独立的通用 Tool 取消路径。这样 Tool 的终态始终 +跟随交互终态,且每条交互只产生一次 Agent 结果。 + +刷新或短暂断线后,可恢复尚未终态、且仍未到期的交互;LineUp 重启后也可恢复仍有效的未回答问题, +但 `input` 的未提交草稿必须直接清空,用户需要重新输入。草稿绝不能自动提交、自动发送给 Agent 或进入 +outbox;它的本地观测仍遵循普通 IM 的日志基线。终态交互不可再次操作。 + +建议稳定拒绝码至少包括: + +```text +interaction_not_found +interaction_state_invalid +interaction_expired +interaction_result_invalid +interaction_already_final +``` + +#### App 子会话与主 IM + +Interact 是所有人与 Agent 交互的系统级入口,但对话会随着用户当前处理的 App 形成清晰的上下文。 +当 Runtime 启动一个 MiniApp instance 时,必须为它创建或恢复一个与主 IM 会话关联的 **App 子会话**: + +```text +主 IM 会话 +├── 普通人与 Agent 对话 +├── Task Dashboard 子会话 +└── Whiteboard 子会话 +``` + +主 IM 中的 App 子会话以可展开的折叠组显示,例如“Whiteboard · 4 条交互”。展开后只显示用户和 +Agent 围绕该 App 的对话:Agent 的说明、提问、用户回答,以及 Agent 给出的简洁结果。标准交互在 +前台 bundled MiniApp 上方显示时,也必须写入该 App 子会话,而不是混入主 IM 的普通消息。 + +App 子会话**不是 App 操作日志**。用户在 Task Dashboard 点击“新建任务”、填写 Dashboard 自己的 +表单,或在 Whiteboard 画线、拖放、选择颜色、点击保存,均属于 App 内部业务操作,不自动写入 +主 IM 或 App 子会话。只有用户与 Agent 围绕该 App 的交互才会被记录。 + +```text +启动一个 App instance +→ 创建新的 App 子会话;若正在恢复同一个未结束 instance,则恢复原子会话 +→ Runtime 可靠向当前 Agent 发送 app_session.opened,至少关联 agent_id、conversation_id、app_session_id、 + app_scope 与 instance_id;Agent 后续可用 app_session_id 作为 interactive Tool 的可选上下文 + +App 进入 background / suspended +→ 子会话继续存在 + +用户明确关闭 App,或 App 失败并被 Runtime 关闭 +→ 子会话结束,在主 IM 中保留为可展开的历史记录 +→ Runtime 向当前 Agent 可靠发送“该 App instance / App 子会话已关闭”的事件 + (至少包含 agent_id、conversation_id、app_session_id、instance_id、关闭原因和仍 pending 的 pending_interaction_call_ids) +→ 尚未回答的 Interact 交互保持 pending;它仍属于主 IM / Interact,只保留与已关闭 App 子会话的上下文关联 + +以后启动一个新的 App instance +→ 创建新的子会话,不接续旧 instance 的记录 +``` + +每个 App 子会话至少绑定 `agent_id`、`parent_conversation_id`、`app_session_id`、`app_scope`、 +`instance_id`、创建/结束时间和状态。已提交的用户回答属于该子会话的会话历史,随其父 IM 会话的 +既有保留、删除和日志基线处理;SDK v1 不为普通 `input` 另设秘密输入规则。 + +App 子会话的生命周期必须接入 Runtime 的 `AppLifecycleManager`,而不是 `RuntimeAppHost`: + +```text +foreground / background / suspended +→ 只改变 App 是否在前台或是否暂时暂停 +→ 不结束 instance,也不结束 App 子会话 + +Agent Tool 完成 +→ 只表示 Agent 当前工作完成 +→ 不自动关闭 App,也不自动结束 App 子会话 + +AppLifecycleManager.close(instance_id) +→ Runtime 按普通 Tool 的 app_closed 收敛规则处理该 instance 的未终态 Tool;已 submitted 的结果继续可靠发送 +→ 关闭 Surface,App instance 进入 stopped 或 failed,并从 focus stack 移除 +→ 结束 App 子会话,保留其 IM 历史折叠组 +→ Runtime 向当前 Agent 可靠发送 App 已关闭事件,事件至少关联 conversation_id、app_session_id、instance_id、关闭原因、agent_id 和 pending_interaction_call_ids +→ 不自动取消、改写或重建关联的 Interact 交互;交互仍按自身的回答、取消、超时或失败规则收敛 +``` + +当某个 App 子会话有尚未回答的 Agent 问题时,显示规则如下: + +```text +该 App 位于前台 +→ Interact 的提问层可显示在该 App 上方 + +用户切换到另一个 App 或回到普通 IM +→ 原 App 上方的提问层收起 +→ 问题保持等待,不取消 +→ 主 IM 的对应 App 子会话折叠组显示“等待你的回答” + +用户展开该 App 子会话 +→ 可以直接在 Interact 中回答,不需要强制重新打开原 App + +用户回到原 App +→ 同一个尚未结束的问题可再次显示在该 App 上方 + +用户真正关闭原 App +→ App 上方的交互层消失,因为该 App 已不再存在 +→ 同一条 Interact 交互仍在主 IM 中等待;Runtime 将关闭事件发送给 Agent +→ Agent 可根据业务上下文发送远程 dismiss 指令,将交互按 cancelled 结束;也可保留问题,等待用户在 IM 中回答或自然超时 +``` + +Shell 可将同一个问题优先显示在前台 App 上方或主 IM 的关联位置,避免用户看见重复的视觉卡片;这只是 +展示策略,不是交互的 parent、owner 或提交权限。切换前后台、关闭 App 或切换展示位置不会改变问题所属 +的主 IM / Interact 交互记录和 `app_session_id` 关联。Runtime 依靠交互状态和“首次有效回答”防止重复 +结果,而不是依靠展示位置废止回答资格。 + +用户可以随时在主 IM 中展开已结束的 App 子会话,查看当时 Agent 的提问、用户回答和结果;这是 +**只读查看历史**,不会重新打开 App、重新执行操作或让旧问题再次可回答。 + +若用户选择“继续处理”某次旧工作,Runtime 必须启动一个新的 App instance 并创建新的 App 子会话。 +新的子会话可保存 `continued_from_app_session_id`,并在 App 支持时以旧 artifact 或已保存的 App 状态 +作为初始内容,但只能引用旧会话,不能向其中追加新的 **App 业务记录**。已终态交互永远不会复活;仍 +pending 的 Interact 交互也不属于新 App instance,Agent 如仍需要针对新工作提问,必须在新子会话中创建 +新的交互。 + +### 3.5 Surface 与 Capability API + +SDK v1 定义请求语义,不直接授予 Host 权限: + +```ts +interface MiniAppSurfaceAPI { + requestOpen(request: MiniAppSurfaceRequest): Promise; + update(surface_id: string, patch: JsonObject): Promise; + requestClose(surface_id: string): Promise; +} + +interface MiniAppCapabilityRequestAPI { + request(request: { + capability: string; + reason: string; + arguments: JsonObject; + }): Promise; +} +``` + +Runtime 必须根据 Manifest、实例状态、Capability Policy、用户确认和 Host 支持情况决定是否 +执行。SDK v1 不开放: + +```text +fetch / 任意网络 +任意文件系统 +Tauri invoke +直接剪贴板、麦克风、摄像头 +Host DOM 根节点 +原始 AppServer / Agent 协议 +``` + +本迭代可定义 Capability Request 的类型、拒绝码、审计和测试,但不必为参考 MiniApp 开放真实 +麦克风、摄像头或文件选择权限。 + +## 4. Manifest、Tool Contract 与 Inventory + +### 4.1 MiniApp Manifest v1 + +本迭代冻结最小 Manifest 契约: + +```ts +type MiniAppManifest = { + app_scope: string; + version: string; + kind: "system" | "bundled"; + tools: readonly ToolDescriptor[]; + subscriptions: readonly MiniAppSubscription[]; + requested_capabilities: readonly string[]; + host: { + min_version: string; + surface_required: boolean; + }; +}; +``` + +Manifest 是 Runtime 的输入,不是 MiniApp 可写状态;MiniApp 不可在运行时扩展 Tool、订阅或 +权限。动态安装、下载、更新、回滚和移除不属于本迭代。 + +`kind = bundled` 的 Manifest 必须声明 `host.surface_required = true`,并只在 Runtime +创建和绑定的受限 Surface / Bridge 内运行;不得因其随 Host 预置而退化为可信 Host DOM。只有 +`kind = system` 的 Interact 可使用受信内建 UI 容器,且同样不能绕过 Runtime 的 SDK、Tool、 +Lifecycle、Capability 或审计边界。 + +### 4.2 Tool Descriptor v1 + +```ts +type ToolDescriptorBase = { + id: string; // 例如 task-dashboard.open + version: 1; + input_schema: JsonSchema; + output_schema: JsonSchema; + permissions?: readonly string[]; +}; + +type ToolDescriptor = ToolDescriptorBase & ( + | { + // Agent → Runtime → Interact 会话交互;不是目标 MiniApp 的 Tool。 + handling: "interactive"; + target?: never; + } + | { + handling: "direct" | "launch" | "foreground" | "operation"; + target: { + app_scope: string; + requires_foreground: boolean; + restore_previous_focus: boolean; + }; + timeout_ms?: number; + } +); + +type InteractiveToolInvokeContext = { + // 只表示与哪段 App 工作有关;不是展示 owner,也不改变提交权限。 + app_session_context?: { + app_session_id: string; + }; +}; +``` + +`handling = interactive` 不使用 `target.app_scope`,也不能把 `chat` 伪装为其 target。Agent 如需将提问关联 +到某段 App 工作,只能在该次 Tool Invoke 的 `app_session_context` 中提供上面的 `app_session_id`;未提供时 +Runtime 一律归入主 IM。Runtime 验证失败必须以 `app_session_context_invalid` 拒绝请求,不创建交互或 outbox。 + +第一版 JSON Schema 只支持可明确实现和测试的子集: + +```text +object / string / number / boolean / array +required +properties +additionalProperties = false +enum +minLength / maxLength +minimum / maximum +maxItems +``` + +禁止接受远端 schema 中的递归引用、正则执行、脚本、URL 加载、函数名或任意扩展关键字。 + +### 4.3 Revisioned Inventory + +Runtime 在 MiniApp 的启用状态、Manifest Tool 或可见 Capability 发生变化时,生成最小化且带 +revision 的 Inventory。Agent Tool Invoke 必须携带该 revision: + +```text +Agent Tool Invoke +→ Runtime 发现 inventory_revision 不匹配 +→ 拒绝,不投递给 MiniApp,不执行任何 Host 行为 +→ 以受控状态提示 Agent 使用最新 Inventory +``` + +本迭代可复用现有 `lineup.v1.client.inventory` 发送路径;AppServer 只需要像普通会话消息一样 +透明转发,不需要实现应用目录或下载 API。 + +### 4.4 通用 Tool 路由 + +```text +Agent Tool Invoke +→ Runtime 校验 Envelope、Inventory revision、ToolDescriptor、输入 schema、scope、MiniApp 状态和权限 +→ 创建 RuntimeToolCallRecord 与审计记录 +→ Tool Router 判定 direct / interactive / launch / foreground / operation + ├── handling = interactive + │ → 不创建或复用业务 MiniApp instance,不调整业务 App 焦点,不投递 SDK Tool Inbox + │ → 可选 app_session_context 仅用于关联已验证的 App 子会话;缺省一律归主 IM,不从前台 App 猜测 + │ → Runtime 的 Agent interaction service 创建 StandardInteractionRecord + │ → Runtime 私有呈现契约交给 Interact / Shell + │ → Interact 只提交 interaction_id + 用户动作/答案 + │ → Runtime 校验、持久化并写 outbox,向 Agent 回传唯一结果 + └── handling = direct / launch / foreground / operation + → 按 ToolDescriptor 的目标创建或复用所需 MiniApp instance,并调整焦点 + → 向目标 MiniApp 的 SDK Tool Inbox 投递 + → MiniApp 经 SDK 报告 progress / result / error + → Runtime 校验 output schema,持久化,写 outbox,回传 Agent +``` + +`notice`、`choice`、`confirm`、`input` 是 Agent 调起的 Runtime 统一 `interactive` Tool Call / +`StandardInteractionRecord`。Interact 负责呈现和会话语义;当其他 MiniApp 在前台时,Interact / +Shell 可把交互层显示在其上方,但这些 MiniApp 不能调用、实现或取得该交互的内容与结果。 + +因此,`interactive` 不是“投递给 Interact 的公开 MiniApp Tool”。它是 Runtime 自己处理的 Agent +交互分支:Interact 只通过 Runtime 私有的呈现/提交契约参与,Task Dashboard、Whiteboard 及其他 +bundled MiniApp 的 `sdk.tools.subscribe` 和 Inbox 中均不得出现这四类交互。 + +建议稳定拒绝码: + +```text +inventory_revision_mismatch +tool_not_advertised +tool_input_invalid +tool_output_invalid +app_disabled +app_instance_not_found +app_scope_mismatch +app_session_context_invalid +lifecycle_denied +capability_denied +call_already_final +``` + +## 5. 内置参考 MiniApp 工作定义 + +本迭代通过三个内置 MiniApp 覆盖 SDK v1 的不同能力面。它们不是应用市场候选,也不要求完整 +业务功能;每个 MiniApp 只实现足以验证 SDK 契约的最小真实闭环。 + +### 5.1 Interact:系统级 MiniApp 样本 + +**身份:** `kind = system`;当前兼容 `app_scope = chat`。 +**本迭代交付:** IM Mode 适配到 MiniApp SDK v1。 + +| 范围 | 工作定义 | +|---|---| +| Inbox | 使用通用 `sdk.inbox` 订阅、恢复和 ACK;不再依赖 Chat 特有投递语义。 | +| 用户消息 | 保留 `conversation.sendText` 这一系统级扩展,但它必须进入 Runtime Action / outbox。 | +| 标准原语 | 作为 Agent/IM 请求的默认可信 Renderer Provider,在时间线/卡片内呈现 Runtime 投递的标准交互记录。 | +| 交互边界 | 负责所有人与 Agent 标准交互的会话语义与呈现;交互记录和 Agent 回传仍由 Runtime 持有。前台 bundled MiniApp 只能被覆盖,不读取或处理交互内容。 | +| Tool 结果 | 用户完成、取消或超时后由 Runtime 持久化并写 outbox,可靠回传给 Agent。 | +| Mode | 明确 IM 的 `mode = im` Context;仅定义 Audio/Video 入口和恢复语义,不启用真实媒体。 | +| 焦点 | 被参考 MiniApp 覆盖时接受 Runtime 的 background/suspended;不能自行切换回前台。 | + +**不在范围:** 语音录制、视频通话、摄像头、独立 Audio/Video MiniApp、改名 `chat → interact`。 + +### 5.2 Task Dashboard:Tool 与生命周期样本 + +**身份:** `kind = bundled`,`app_scope = task-dashboard`;本迭代作为 SDK 参考实现。 +**目标:** 验证 MiniApp Tool、progress/result/error、instance、focus 和恢复的最小闭环。 + +最小 Tool: + +```text +task-dashboard.open + handling = launch + 输入:task_id、title、可选初始状态 + 输出:status = completed | cancelled | failed + +task-dashboard.update + handling = operation + 输入:task_id、进度或状态 + 输出:当前任务摘要 +``` + +最小用户体验: + +```text +Agent 调用 task-dashboard.open +→ Runtime 创建 task-dashboard instance +→ Interact 进入 background +→ Runtime 请求并绑定 task-dashboard 的受限 Surface +→ Host 挂载 opaque-origin iframe / Surface Bridge +→ MiniApp 展示任务标题、进度、当前状态和关闭动作 +→ MiniApp 仅通过 Bridge SDK 使用 sdk.tools.reportProgress / complete / fail 上报 +→ Runtime 持久化并回传 Agent +→ Tool 的完成不关闭 Task Dashboard,也不结束其 App 子会话 +→ Dashboard 是否继续留在前台、进入后台或关闭,取决于用户动作、MiniApp requestClose 或 Lifecycle Policy +→ 只有真正执行 AppLifecycleManager.close(instance_id) 后,才停止实例、结束子会话并恢复前一有效 foreground App +``` + +Task Dashboard 的业务状态必须通过 SDK Tool / Inbox 获得;不得自行访问 AppServer、Store 或 +Agent。它必须与一般非系统级 MiniApp 一样运行在受限 Surface / Bridge 中,不能访问可信 Host DOM、 +Tauri、认证状态或其他 MiniApp 数据。Host adapter 如存在,只能做 Runtime/Shell 的 Surface 装配, +不可成为 MiniApp 的 UI 容器、Transport 或 Tool Router 的第二实现。 + +Task Dashboard 必须验证两种不同交互:第一,Agent 在任务流程中请求用户是否创建任务或输入任务 +描述时,Interact 的会话交互层可显示在当前 Task Dashboard 之上,用户回答由 Runtime 回传 Agent; +第二,用户自己点击“新建任务”时,Task Dashboard 使用其 Surface 内的私有业务表单。两者不得混用: +Task Dashboard 不能调用或处理 Agent 标准交互,也不能将自己的业务表单伪装为 Agent 提问。 + +Runtime 必须将第一类交互记录在该 Task Dashboard instance 的 App 子会话中,并在主 IM 中作为可展开 +的折叠组显示;第二类 App 内部操作不进入子会话记录。 + +### 5.3 Whiteboard:隔离 Surface 样本 + +**身份:** `kind = bundled`,`app_scope = whiteboard`;本迭代作为 SDK 参考实现。 +**目标:** 验证 SDK Surface Bridge、隔离执行、状态 patch、Artifact 元数据与恢复。 + +最小 Tool: + +```text +whiteboard.open + handling = launch + 输入:board_id、title、可选初始画布状态 + 输出:status、artifact_id(可选) + +whiteboard.submit + handling = operation + 输入:board_id、提交请求 + 输出:artifact_id 或结构化画布摘要 +``` + +最小用户体验: + +```text +Agent 调用 whiteboard.open +→ Runtime 校验已注册 bundled Manifest +→ Runtime 创建 whiteboard instance 并请求受限 Surface +→ Host 挂载 opaque-origin iframe / Surface Bridge +→ Whiteboard 仅通过 Bridge SDK 请求状态更新和提交结果 +→ Runtime 校验 patch / Artifact 元数据并持久化 +→ Tool 完成或失败只结束该 Tool;Whiteboard、Surface 与 App 子会话保持,直到用户、MiniApp 或 Policy 显式请求关闭 +``` + +Whiteboard 不需要在本迭代实现多人协作、任意网络同步或完整绘图工具;重点是证明隔离 Surface +不能读取 Host DOM、Tauri、认证状态或其他 MiniApp 数据。 + +即使 Whiteboard 处于前台,Agent 对用户的提问仍由 Interact 的会话交互层负责;Whiteboard 不调用 +也不处理这些交互。画板内的文字编辑、画笔与颜色选择、拖放、工具栏、上下文菜单、确认提交等 +业务 UI 必须保留在 Whiteboard 自己的 Surface 中,不能被错误抽象为人与 Agent 的标准交互。 + +Whiteboard instance 启动时创建自己的 App 子会话;Agent 与用户围绕该画板的提问、回答和简洁结果 +记录在其中,而画板的内部编辑操作不写入该子会话。 + +## 6. Runtime 与 Host 的实现模块 + +本迭代预计在现有目录中演进,不重建并行 Runtime: + +```text +tauri/src/runtime/ +├── app-management/ +│ ├── miniapp-manifest.ts # Manifest v1、bundled MiniApp 注册 +│ ├── miniapp-sdk.ts # SDK v1 公共类型与受限视图 +│ ├── miniapp-tool-call-store.ts # 通用 Tool Call 持久化/恢复 +│ ├── app-session-store.ts # 接入 Lifecycle 的子会话创建、结束、恢复与主 IM 分组 +│ └── runtime-app-host.ts # 按 foreground instance 挂载/卸载 Host +│ +├── coordination/ +│ ├── tool-router.ts # revision、schema、路由决策 +│ ├── miniapp-tool-orchestrator.ts # Tool → instance → SDK Inbox +│ └── agent-interaction-service.ts # Agent 交互记录、回传、恢复与显示协调 +│ +├── inventory/ +│ └── client-inventory.ts # revisioned Tool Inventory +│ +└── persistence/ + └── conversation-store.ts # 主 IM、App 子会话、Tool Call、Inbox、workspace 有界恢复 + +tauri/src/ +├── core-apps/chat/ # Interact IM 的兼容实现 +│ └── standard-interaction-im-renderer.ts # Agent/IM 的默认标准交互 Renderer +├── core-apps/task-dashboard/ # bundled Tool/lifecycle 参考实现 +└── core-apps/whiteboard/ # bundled Surface/Bridge 参考实现 +``` + +若目录名称最终改为 `miniapps/`,应单独进行机械迁移;本迭代优先保证 Runtime 边界和 SDK +兼容,不能让命名迁移扩大风险。 + +## 7. 不在本迭代范围 + +以下内容必须明确排除: + +```text +服务端 App Catalog 或应用清单 API +远程 Manifest / Bundle 下载 +安装、更新、回滚、卸载 UI +第三方开发者发布、账号、审核、评分、支付或搜索 +任意网络 API +真实麦克风、摄像头、文件选择或系统通知授权 +多人白板、实时游戏、完整语音/视频通话 +`password-input` 及密码、卡密、私钥、临时 token 等秘密输入的安全交互原语 +chat / interaction 命名和协议 scope 迁移 +多 Agent Runtime 连接、Agent 切换、跨 Agent 会话或结果路由 +``` + +这些能力将在 MiniApp SDK 与参考 MiniApp 完成验证后,作为独立的应用分发和市场阶段推进。 + +## 8. 实施步骤 + +1. **冻结契约与 golden fixtures** + - 定义 `MiniAppManifest v1`、`ToolDescriptor v1`、`MiniAppContext`、通用 Tool Call、 + Result/Progress/Error、Standard Interaction、SDK 稳定错误码; + - 为合法、重复、过期 revision、非法 schema、scope 不匹配和终态重复建立 fixture; + - 明确旧 `lineup.v1.tool.call` 的 `choice/confirm/input` 到 `interactive` Tool 的兼容映射;冻结 + `StandardInteractionRequest`、`expires_in_ms`、`interaction.dismiss` 和 `app_session_context`; + - 为 `notice`、期限缺省/越界、会话/App 子会话绑定、非法上下文、前台 bundled App 上的 Interact 交互层、 + dismiss、取消、超时和重启恢复建立 fixture。 + +冻结后的 v1 基线保存在 +[`miniapp-sdk-v1.json`](../../tauri/src/runtime/app-management/golden/miniapp-sdk-v1.json),并由 +[`miniapp-sdk-golden-contract.test.ts`](../../tauri/src/runtime/app-management/miniapp-sdk-golden-contract.test.ts) +校验 fixture 版本和用例顺序。后续实现必须逐项满足这份 fixture;若确需修改,必须先更新本迭代设计决议与 +fixture,再修改实现,不能在业务代码里悄悄改变契约。 + +2. **实现 Runtime 通用 Tool 闭环** + - 将 Inventory revision、输入/输出 schema、Tool Call Record、审计和 outbox 接入 Runtime; + - 让 `ToolRouter` 先区分 `interactive` 与一般 Tool:只有 `direct / launch / foreground / operation` + 进入 `AppOrchestrator`、创建/复用实例并投递 SDK Tool Call;`interactive` 不得进入任何 MiniApp + SDK Tool Inbox,也不使用普通 `target`; + - 实现 App 关闭的普通 Tool 收敛:接受关闭后停止新投递,将未终态 Tool 原子转为 `cancelled(app_closed)`, + 已 submitted 结果继续 outbox,随后关闭 Surface / instance / 子会话并恢复焦点; + - 完成 Tool、Inbox、workspace 的断线与重启恢复。 + +3. **实现 Agent 交互服务与 Runtime → Interact 回传契约** + - 实现 Runtime 独占的 Agent interaction service:创建和保存交互记录、绑定唯一 `agent_id`、会话、 + Agent Tool、Interact instance 与可选 App 子会话,处理状态机、首次有效回答、取消、超时和重启恢复; + - 定义 Runtime 到 Interact 的最小呈现数据,以及 Interact 到 Runtime 的最小回答提交:提交端只可提交 + `interaction_id` 和用户动作/答案,不能指定或改写 Agent、会话、Tool、App 子会话等归属; + - 由 Runtime 校验当前 LineUp / Interact 会话、有效期、答案 schema 和一次性提交,持久化终态后通过 + outbox 向当前唯一 Agent 回传;展示位置不是 owner 或提交权限,重试只由 Runtime 发起,Interact 不直接发送 Agent 消息; + - 实现私有 `interaction.dismiss(control_id, call_id)`,从受认证 Agent / 会话上下文查找交互并幂等终结; + 在 `app_session.opened` / `app.closed` 事件中可靠提供 App 子会话上下文及仍 pending 的交互 call_id; + - 为重复点击、刷新后的旧页面、前后台/关闭 App 后仍 pending 的交互、用户回答与 dismiss/超时/失败并发、 + 过期问题、断线和 outbox 重试建立自动化 fixture;每个并发场景必须只产生一个终态和一条 outbox。 + +4. **适配 Interact IM** + - 以 `sdk.inbox` 和 `sdk.tools` 替换 Chat SDK 中的专用交互旁路; + - 实现 Agent/IM 标准交互层:所有交互在 IM 中有对应卡片/气泡;notice 为持久只读卡片并可在当前界面 + 同时 Toast 数秒,choice / confirm / input 为可操作卡片;既支持 IM 时间线/卡片,也支持覆盖在前台 + bundled App 之上;保持 Markdown、消息、Task 和现有回归行为; + - 实现主 IM 的 App 子会话折叠组,以及 instance 启动、后台、结束和恢复时的子会话生命周期; + - 实现前后台切换时 Agent 提问层的收起、子会话“等待回答”标记、IM 内直接回答及回到原 App 后的再次显示; + - 将子会话结束、普通 Tool 的 app_closed 收敛、App 已关闭事件和折叠历史保留接入 + `AppLifecycleManager.close`,不得依赖 `RuntimeAppHost` 或 Tool 完成事件;关闭不自动取消未回答交互, + Agent 可用 call_id 远程 dismiss; + - 实现已结束子会话的只读展开,以及“继续处理”创建新 instance / 新子会话并引用旧上下文的路径; + - 固化 IM Mode Context 与被覆盖/恢复的生命周期语义。 + +5. **实现 Task Dashboard 参考 MiniApp** + - 注册 bundled Manifest 和两个最小 Tool; + - 实现受限 Surface / Bridge 中的启动、进度、结果、错误、关闭、回焦和重启恢复; + - 验证 Agent 交互层覆盖时的会话回答回传,以及用户主动创建任务时的私有业务表单; + - 使用真实 SDK,不测试用 Runtime 内部对象直连。 + +6. **实现 Whiteboard 参考 MiniApp** + - 注册 bundled Manifest、最小 Tool 和受限 Surface; + - 验证 Bridge、patch、Artifact 元数据、关闭、失败和恢复; + - 验证隔离拒绝路径与 Capability Request 拒绝路径。 + +7. **端到端验收与文档回填** + - 运行完整单元测试和生产构建; + - 通过 Tauri/Web Reference Host 完成代表性浏览器验收; + - 将最终 SDK 形态与参考 MiniApp 结果回填 [APP架构设计.md](../../APP架构设计.md)。 + +## 9. 验收目标 + +```text +1. Runtime 向每个 MiniApp instance 注入不可伪造、范围受限的 MiniAppContext。 +2. Interact、Task Dashboard、Whiteboard 均通过 SDK 获取 Inbox、Tool 和生命周期能力; + 不直接访问 Transport、Store、Agent 或 Host 特权。 +3. Manifest Tool 具有稳定 ID、输入/输出 schema、handling 和版本;普通 MiniApp Tool 必须有目标 scope, + `interactive` 明确不使用普通 target。 +4. Runtime 只接受当前 Inventory revision 中、输入 schema 合法的 Tool Invoke。 +5. Runtime 拒绝未知 Tool、过期 revision、非法输入/输出、scope 不匹配、禁用 MiniApp、 + 错误 instance 和重复终态;拒绝请求不得到达 MiniApp 或 Host。 +6. Interact 的 notice / choice / confirm / input 始终属于人与 Agent 的会话交互,并在 IM 中保留对应卡片/ + 气泡;notice 可额外 Toast 数秒但不以 Toast 作为唯一记录,也不等待用户阅读。IM 前台时以内联卡片呈现, + bundled MiniApp 前台时可显示为 Interact 管理的交互层,完成、恢复和回声均不退化。 +7. 每个标准交互必须绑定当前 conversation、Agent call 和 Interact instance;未提供经验证的 + `app_session_context` 时归主 IM,提供时只可关联同 Agent、同父会话的真实 App 子会话,非法引用必须拒绝且 + 不创建交互。`interactive` 不创建或复用 + 业务 MiniApp instance,也不得投递到任何 MiniApp 的 SDK Tool Inbox 或 `sdk.tools.subscribe`。只有 Runtime + 可持久化结果并可靠回传 Agent,bundled MiniApp 不能调用、读取、提交、取消或伪造此类交互。用户回答、 + Agent dismiss、到期和 Runtime 失败竞争时,Runtime 以第一个原子终态写入为准,且每条交互至多产生一个 + Tool 结果和一条 outbox。`interaction.dismiss(control_id, call_id)` 只能由当前 Agent 经 Runtime 私有契约调用; + 重放或已终态交互只能得到稳定幂等回执,不能新增 outbox。 +8. MVP 中 Runtime 只连接当前登录会话的一个 Agent;每条标准交互和 App 子会话均记录该 `agent_id`, + 不新增多个 Agent 的连接、切换、会话列表、outbox 或路由能力。 +9. 每个启动的 App instance 创建或恢复一个 App 子会话;Runtime 向当前 Agent 可靠发送包含稳定 + `app_session_id` 的 opened 事件。前后台切换、暂停和 Agent Tool 完成均不结束子会话;仅在 + `AppLifecycleManager.close` 使 instance stopped/failed 后结束并在主 IM 保留折叠历史。Runtime 一旦接受关闭, + 不再向该 instance 投递新普通 Tool,并将其未终态 Tool 原子收敛为 `cancelled(app_closed)`;已 submitted + 的结果继续可靠发出,随后关闭 Surface、停止 instance、结束子会话并恢复焦点。关闭事件包含仍 pending 的 + interaction call_id;关闭不自动取消关联的 Interact 交互,Agent 可 remote dismiss,或保留其在主 IM 中等待 + 用户回答/超时。新 instance 不接续旧子会话;已结束子会话可只读展开;“继续处理”创建新 instance / 新子会话 + 并可引用旧上下文,但不复活已终态问题。子会话只记录人与 Agent 围绕 App 的交互,不记录 App 内部操作。 +10. App 子会话关联的未回答 Agent 问题在原 App 前台时可显示为 Interact 提问层;切换到其他 App/IM 时 + 收起并在子会话标记“等待你的回答”,用户可在 IM 内直接回答或回到原 App 后回答。展示位置不改变交互 + 归属或提交资格;Runtime 只持久化和回传首次有效回答,后续重复提交不得改变结果。 +11. Task Dashboard 可验证 launch → foreground → progress → 标准交互 → result/error → Agent 回传,且 Tool + 完成后 Dashboard 仍可留在前台或后台、子会话仍可继续;用户真正关闭时,未终态普通 Tool 收敛为一次 + `app_closed` 取消、已提交结果继续 outbox,随后才 close → 恢复 Interact。Agent 交互层覆盖时不触发焦点 + 切换,交互记录仅在带有已验证上下文时写入 Task Dashboard 子会话;Task Dashboard 也不能访问可信 Host + DOM/Tauri/token。 +12. Whiteboard 可验证隔离 Surface open → patch → submit → 保持 App 可继续编辑或由用户显式 close,且不能 + 访问 Host DOM/Tauri/token;提交 Tool 的成功或失败不自动关闭 Whiteboard。画板内的文字编辑等私有高频 UI + 不通过 Agent 标准交互路由。 +13. MiniApp 的 progress/result/error 经 Runtime schema 校验、持久化、审计和 outbox 后才回传 Agent。 +14. 断线或重启后,pending Tool、Standard Interaction、App 子会话、MiniApp Inbox、实例、焦点和 Surface 以有界方式恢复; + `app_closed` 取消、submitted 结果继续投递、`interaction.dismiss` 重放、默认/越界 `expires_in_ms` 和非法 + `app_session_context` 均可验证,且中断操作不得伪装为完成。 +15. 不新增 AppServer Catalog、下载或市场接口;现有服务端只透明转发会话/Inventory 消息。 +16. 00.base 和 01.kernel 中的登录、同步、本地回显、Markdown、安全 fallback、Tool Call、 + Task、Surface、Capability、App Inbox、outbox 和焦点恢复测试不退化。 +17. `npm test -- --run`、`npm run build`、`git diff --check` 通过;浏览器验收无未处理错误。 +``` + +## 10. 完成定义 + +本迭代完成不是“已经有应用市场”,也不是“完成全部 MiniApp 业务功能”。完成的判断是: + +```text +LineUp Runtime 已提供经过类型、schema、scope、Inventory revision、生命周期、权限、持久化和 +审计约束的 MiniApp SDK v1; + +Interact、Task Dashboard、Whiteboard 已以不同信任和 UI 形式使用同一套 SDK 语义,证明 +LineUp 可在不增加旁路通信或 Host 特权泄漏的前提下承载系统级与受限 MiniApp。 +``` diff --git a/迭代/README.md b/迭代/README.md new file mode 100644 index 0000000..2fe0b96 --- /dev/null +++ b/迭代/README.md @@ -0,0 +1,48 @@ +# LineUp App 迭代文档 + +每个迭代使用一个独立目录,目录名固定为“迭代编号.简短名称”。目录内至少保留该迭代的主定义文档; +每进行一次设计、架构或实现评审,都必须新增一份独立评审记录,不覆盖之前的记录。 + +```text +迭代/ +├── 00.base/ +│ └── 00.base.md +├── 01.kernel/ +│ └── 01.kernel.md +└── 03.sdk_and_coreapp/ + ├── 03.sdk_and_coreapp.md # 本迭代的权威目标、范围、契约、步骤和验收 + ├── 01.design_review.md # 第 1 次评审记录 + ├── 02.design_review.md # 第 2 次评审记录 + └── 03.design_review.md # 第 3 次评审记录 +``` + +约定如下: + +1. 主定义文档命名为 `<迭代目录名>.md`,是当前实施依据; +2. 评审记录按发生顺序编号,命名为 `<两位序号>.design_review.md`; +3. 每份评审记录必须在元信息之后、评审正文之前维护“问题清单(Outline)”,格式参考 + [`01.design_review.md`](03.sdk_and_coreapp/01.design_review.md):列出状态、编号、问题和当前结论/下一步; +4. 评审记录必须写明评审日期、编号、对象、方式和总体结论。新问题先在 Outline 中登记;问题确认后先 + 更新 Outline 状态,再回填主定义文档、验收项和评审正文; +5. 已确认的结论需要回填主定义文档和验收项;评审记录保留原始意见,用于追溯,不能替代主定义文档; +6. 迭代完成后的实现验收、发布复盘等文档也保留在对应目录内,并使用清晰的递增编号。 + +## 评审优先级与遗留问题 + +评审问题使用 `P0`~`P5` 标示处理优先级。优先级表达的是“最晚何时必须解决”,而不是问题描述的 +修辞强弱。 + +| 级别 | 含义 | 当前迭代的处理规则 | +|---|---|---| +| P0 | 根本性阻塞:安全边界、数据正确性或总体架构不能成立。 | 立即处理;在解决前不得进入相关实现。 | +| P1 | 关键规则未定:不解决会让不同实现互相冲突或明显返工。 | 在开始相关模块实现前处理。 | +| P2 | 交付或验收缺口:核心方向正确,但缺失会使恢复、安全验证或验收不完整。 | 必须在本迭代验收前处理。 | +| P3 | 局部行为、一致性或质量问题:存在安全默认行为,但相关模块完成前仍需收敛。 | 必须在本迭代结束前处理。 | +| P4 | 优化、可读性或维护性建议。 | 可以登记为遗留问题,不阻塞当前迭代。 | +| P5 | 观察记录或未来机会,当前没有足够的产品需求或证据进入排期。 | 可以登记为遗留问题,不纳入当前迭代。 | + +因此,**P0~P3 必须在当前迭代处理完毕,P4~P5 可以作为遗留问题延期处理。** + +延期不是删除:评审中出现 P4/P5 时,必须在该评审文件的 Outline 或“遗留问题”段落中保留编号、问题、 +延期原因和建议重新评估的阶段/条件。只有在对应迭代主定义文档或新的评审记录中明确重新纳入后,才将 +其提升为当前迭代的 P0~P3 问题。