diff --git a/tauri/src/runtime/coordination/lineup-runtime.test.ts b/tauri/src/runtime/coordination/lineup-runtime.test.ts index 714ce5a..e316206 100644 --- a/tauri/src/runtime/coordination/lineup-runtime.test.ts +++ b/tauri/src/runtime/coordination/lineup-runtime.test.ts @@ -75,6 +75,11 @@ describe("LineUpRuntime", () => { await runtime.login(login); await runtime.syncOnce(); + await runtime.flushOutbox(); + expect(transport.sent.map(entry => JSON.parse(entry.payload)).find(entry => entry.type === "lineup.v1.app.session.opened")).toMatchObject({ + payload: { agent_id: "agent_1", conversation_id: conversationID, app_session_id: expect.any(String) }, + }); + expect(runtime.lifecycle.instances.get("draw-and-guess:game-001")).toMatchObject({ state: "foreground", conversation_id: conversationID }); const interaction = runtime.lifecycle.instances.activeFor("chat", conversationID); expect(interaction).toMatchObject({ state: "background" }); @@ -115,6 +120,20 @@ describe("LineUpRuntime", () => { expect(chat.snapshot()).toMatchObject({ app_scope: "chat", active: true, conversation_id: "user_1:2:channel_1" }); }); + it("includes agent and conversation context in App session closed events", async () => { + const conversationID = "user_1:2:channel_1"; + const transport = new FakeTransport(); + const runtime = new LineUpRuntime({ storage: new MemoryStorage(), createTransport: () => transport }); + await runtime.login(login); + const instance = runtime.lifecycle.start({ app_scope: "whiteboard", instance_id: "whiteboard:context-1", app_session_id: "whiteboard:session-1", conversation_id: conversationID }, "2026-08-04T00:00:00.000Z"); + runtime.lifecycle.foreground(instance.instance_id, "2026-08-04T00:00:00.000Z"); + await runtime.openBundledMiniApp("whiteboard", instance.instance_id).lifecycle.requestBackground(); + runtime.closeAppInstance(instance.instance_id); + await runtime.flushOutbox(); + const event = transport.sent.map(entry => JSON.parse(entry.payload) as { type: string; payload: Record }).find(entry => entry.type === "lineup.v1.app.session.closed"); + expect(event).toMatchObject({ type: "lineup.v1.app.session.closed", payload: { agent_id: "agent_1", conversation_id: conversationID, app_session_id: "whiteboard:session-1" } }); + }); + it("adapts legacy LineUp v1 messages into chat scope before SDK delivery", async () => { const conversationID = "user_1:2:channel_1"; const transport = new FakeTransport([[{ message_seq: 7, from_uid: "agent_1", payload: agentEnvelope("agent_msg_7", conversationID) }], []]); diff --git a/tauri/src/runtime/coordination/lineup-runtime.ts b/tauri/src/runtime/coordination/lineup-runtime.ts index 2e5feb1..3058ec4 100644 --- a/tauri/src/runtime/coordination/lineup-runtime.ts +++ b/tauri/src/runtime/coordination/lineup-runtime.ts @@ -208,6 +208,8 @@ export class LineUpRuntime { .filter(interaction => interaction.app_session_id === currentInstance.app_session_id && (interaction.status === "pending" || interaction.status === "presented")) .map(interaction => interaction.call_id); void this.queueProtocolAction("app-result", "lineup.v1.app.session.closed", { + agent_id: this.session.agent_uid, + conversation_id: currentInstance.conversation_id ?? this.requireConversationID(), app_scope: currentInstance.app_scope, instance_id: currentInstance.instance_id, app_session_id: currentInstance.app_session_id, @@ -1241,9 +1243,11 @@ export class LineUpRuntime { private persistWorkspace(): void { this.store.replaceAppWorkspace(this.lifecycle.snapshot()); } - private queueAppSessionOpened(instance: { app_scope: AppScope; instance_id: string; app_session_id?: string }): void { + private queueAppSessionOpened(instance: { app_scope: AppScope; instance_id: string; app_session_id?: string; conversation_id?: string }): void { if (!instance.app_session_id) return; void this.queueProtocolAction("app-result", "lineup.v1.app.session.opened", { + agent_id: this.session.agent_uid, + conversation_id: instance.conversation_id ?? this.requireConversationID(), app_scope: instance.app_scope, instance_id: instance.instance_id, app_session_id: instance.app_session_id, diff --git a/迭代/03.sdk_and_coreapp/04.acceptance_review.md b/迭代/03.sdk_and_coreapp/04.acceptance_review.md index fc39b74..0d07dd0 100644 --- a/迭代/03.sdk_and_coreapp/04.acceptance_review.md +++ b/迭代/03.sdk_and_coreapp/04.acceptance_review.md @@ -25,7 +25,7 @@ | ✅ | P2 | A7 | 迭代文档中的 Tool 名称和代码 Manifest 不一致 | 主文档 §5.2/§5.3;`reference-miniapps.ts` | 已统一为 `task-dashboard.open`、`task-dashboard.update`、`whiteboard.open`、`whiteboard.submit`;Manifest schema、MiniApp 实现、sandbox iframe、fixture 和测试均已同步。 | | 🔴 | P2 | A8 | 当前 Task Dashboard/Whiteboard Surface 主要是 Host 硬编码的静态页面,无法证明真实业务 UI 在各自受限 Surface 内运行 | `main.ts:180-198`、`isolated-surface-host.ts` | 至少提供能代表业务边界的受限 Surface fixture,并验证 MiniApp 业务逻辑不能取得 Host DOM 或 Runtime 内部。 | | ✅ | P2 | A9 | MiniApp progress 没有进入可恢复的 Tool 状态记录 | `miniapp-tool-state.ts`、`lineup-runtime.test.ts` | 已在 Tool 记录保存最后一次 `percent/status/detail`,ConversationStore 随记录持久化;Runtime 重启后可恢复,且补充状态机和重启回归测试。 | -| 🔴 | P3 | A10 | App session 记录和 opened/closed 事件没有显式保存 `agent_id` / `conversation_id` 字段 | `app-instance-manager.ts`、`lineup-runtime.ts:203-209,1052-1058` | P3 也必须在本迭代处理:App session opened/closed 事件和持久化记录补齐两个上下文字段。 | +| ✅ | P3 | A10 | App session 记录和 opened/closed 事件没有显式保存 `agent_id` / `conversation_id` 字段 | `lineup-runtime.ts`、`lineup-runtime.test.ts` | opened/closed 事件现在都带 `agent_id`、`conversation_id`、`app_session_id`、`instance_id`、`app_scope`;并补充生命周期回归测试。 | | 🔴 | P3 | A11 | 旧的 `openExtensionApp` 公开入口仍提供较宽的旁路能力 | `lineup-runtime.ts:286-300` | P3 也必须在本迭代处理:删除公开旁路,或让它只返回受统一 SDK v1 约束的适配器。 | ## 1. 已验证通过的部分 @@ -96,10 +96,10 @@ Interact 仍然可以使用可信 DOM,但它的 SDK 已明确拆成两层:`s 基础构建与自动化测试:通过 真实登录和消息发送:通过 App 架构边界:部分通过(A1/A2/A5/A6 已实现,A2 待浏览器复验) -MiniApp 隔离与 SDK 规范:A1~A9 已通过,仍需处理后续 P3 +MiniApp 隔离与 SDK 规范:A1~A10 已通过,仍需处理后续 P3 标准交互终态契约:通过 -迭代整体验收:不通过,继续处理 A8、A10、A11;A2 需在最终浏览器复验中确认 +迭代整体验收:不通过,继续处理 A8、A11;A2 需在最终浏览器复验中确认 ``` -本轮独立验收由子 agent 完成。A6 已在本轮修复并通过自动化验证,A7 已完成契约收敛并通过全量测试,A9 已完成进度持久化并通过重启回归;下一轮继续处理 A8、A10、A11,并重新执行 +本轮独立验收由子 agent 完成。A6 已在本轮修复并通过自动化验证,A7 已完成契约收敛并通过全量测试,A9 已完成进度持久化并通过重启回归,A10 已补齐生命周期事件上下文;下一轮继续处理 A8、A11,并重新执行 独立验收。P2/P3 不能被静默删除,必须在本迭代结束前解决或保留明确的延期记录。