fix(iteration): align reference miniapp tool names
This commit is contained in:
@@ -22,7 +22,7 @@ export class TaskDashboardMiniApp {
|
|||||||
|
|
||||||
private async handleTool(call: { call_id: string; tool_id: string; input: JsonObject }): Promise<void> {
|
private async handleTool(call: { call_id: string; tool_id: string; input: JsonObject }): Promise<void> {
|
||||||
await this.sdk.tools.reportProgress(call.call_id, { status: "running", percent: 10, detail: call.tool_id });
|
await this.sdk.tools.reportProgress(call.call_id, { status: "running", percent: 10, detail: call.tool_id });
|
||||||
if (call.tool_id === "task.create") {
|
if (call.tool_id === "task-dashboard.open") {
|
||||||
const title = typeof call.input.title === "string" ? call.input.title : "未命名任务";
|
const title = typeof call.input.title === "string" ? call.input.title : "未命名任务";
|
||||||
const taskID = typeof call.input.task_id === "string" ? call.input.task_id : `task-${call.call_id}`;
|
const taskID = typeof call.input.task_id === "string" ? call.input.task_id : `task-${call.call_id}`;
|
||||||
this.createTask(taskID, { title, status: "open" });
|
this.createTask(taskID, { title, status: "open" });
|
||||||
@@ -30,8 +30,12 @@ export class TaskDashboardMiniApp {
|
|||||||
await this.sdk.tools.complete(call.call_id, { task_id: taskID });
|
await this.sdk.tools.complete(call.call_id, { task_id: taskID });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (call.tool_id === "task.list") {
|
if (call.tool_id === "task-dashboard.update") {
|
||||||
await this.sdk.tools.complete(call.call_id, { tasks: [...this.tasks.entries()].map(([task_id, task]) => ({ task_id, ...task })) });
|
const taskID = typeof call.input.task_id === "string" ? call.input.task_id : `task-${call.call_id}`;
|
||||||
|
const previous = this.tasks.get(taskID) ?? {};
|
||||||
|
const next = { ...previous, ...(typeof call.input.status === "string" ? { status: call.input.status } : {}), ...(typeof call.input.progress === "number" ? { progress: call.input.progress } : {}) };
|
||||||
|
this.tasks.set(taskID, next);
|
||||||
|
await this.sdk.tools.complete(call.call_id, { task_id: taskID, task: next });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.sdk.tools.fail(call.call_id, { code: "tool_not_supported", message: `不支持的任务工具:${call.tool_id}` });
|
await this.sdk.tools.fail(call.call_id, { code: "tool_not_supported", message: `不支持的任务工具:${call.tool_id}` });
|
||||||
@@ -45,7 +49,7 @@ export class TaskDashboardMiniApp {
|
|||||||
public async completeTask(callID: string, taskID: string): Promise<void> {
|
public async completeTask(callID: string, taskID: string): Promise<void> {
|
||||||
const task = this.tasks.get(taskID) ?? {};
|
const task = this.tasks.get(taskID) ?? {};
|
||||||
await this.sdk.tools.reportProgress(callID, { status: "completed", percent: 100 });
|
await this.sdk.tools.reportProgress(callID, { status: "completed", percent: 100 });
|
||||||
await this.sdk.tools.complete(callID, { task_id: taskID, task });
|
await this.sdk.tools.complete(callID, { status: "completed", task_id: taskID, task });
|
||||||
}
|
}
|
||||||
|
|
||||||
public stop(): void {
|
public stop(): void {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export class WhiteboardMiniApp {
|
|||||||
await this.sdk.tools.complete(call.call_id, { status: "completed" });
|
await this.sdk.tools.complete(call.call_id, { status: "completed" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (call.tool_id === "whiteboard.export") {
|
if (call.tool_id === "whiteboard.submit") {
|
||||||
const artifactID = typeof call.input.artifact_id === "string" ? call.input.artifact_id : `whiteboard-${call.call_id}`;
|
const artifactID = typeof call.input.artifact_id === "string" ? call.input.artifact_id : `whiteboard-${call.call_id}`;
|
||||||
await this.sdk.tools.reportProgress(call.call_id, { status: "exporting", percent: 80 });
|
await this.sdk.tools.reportProgress(call.call_id, { status: "exporting", percent: 80 });
|
||||||
await this.sdk.surfaces.request("patch", { state: this.state, artifact: { artifact_id: artifactID } });
|
await this.sdk.surfaces.request("patch", { state: this.state, artifact: { artifact_id: artifactID } });
|
||||||
|
|||||||
@@ -10,20 +10,20 @@ export const TASK_DASHBOARD_MANIFEST: MiniAppManifestV1 = {
|
|||||||
host: { min_version: "1.0.0", surface_required: true },
|
host: { min_version: "1.0.0", surface_required: true },
|
||||||
tools: [
|
tools: [
|
||||||
{
|
{
|
||||||
id: "task.create",
|
id: "task-dashboard.open",
|
||||||
version: 1,
|
version: 1,
|
||||||
handling: "operation",
|
handling: "launch",
|
||||||
target: { app_scope: "task-dashboard", requires_foreground: true, restore_previous_focus: true },
|
target: { app_scope: "task-dashboard", requires_foreground: true, restore_previous_focus: true },
|
||||||
input_schema: { type: "object", required: ["title"], properties: { title: { type: "string", minLength: 1, maxLength: 200 } }, additionalProperties: false },
|
input_schema: { type: "object", required: ["title"], properties: { task_id: { type: "string", minLength: 1 }, title: { type: "string", minLength: 1, maxLength: 200 }, initial_state: { type: "object" } }, additionalProperties: false },
|
||||||
output_schema: { type: "object", required: ["task_id"], properties: { task_id: { type: "string", minLength: 1 } }, additionalProperties: false },
|
output_schema: { type: "object", required: ["status", "task_id"], properties: { status: { type: "string", enum: ["completed", "cancelled", "failed"] }, task_id: { type: "string", minLength: 1 }, task: { type: "object" } }, additionalProperties: false },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "task.list",
|
id: "task-dashboard.update",
|
||||||
version: 1,
|
version: 1,
|
||||||
handling: "direct",
|
handling: "direct",
|
||||||
target: { app_scope: "task-dashboard", requires_foreground: false, restore_previous_focus: false },
|
target: { app_scope: "task-dashboard", requires_foreground: false, restore_previous_focus: false },
|
||||||
input_schema: { type: "object", additionalProperties: false },
|
input_schema: { type: "object", required: ["task_id"], properties: { task_id: { type: "string", minLength: 1 }, progress: { type: "number", minimum: 0, maximum: 100 }, status: { type: "string" } }, additionalProperties: false },
|
||||||
output_schema: { type: "object" },
|
output_schema: { type: "object", required: ["task_id"], properties: { task_id: { type: "string", minLength: 1 }, task: { type: "object" } }, additionalProperties: false },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@@ -41,16 +41,16 @@ export const WHITEBOARD_MANIFEST: MiniAppManifestV1 = {
|
|||||||
version: 1,
|
version: 1,
|
||||||
handling: "launch",
|
handling: "launch",
|
||||||
target: { app_scope: "whiteboard", requires_foreground: true, restore_previous_focus: true },
|
target: { app_scope: "whiteboard", requires_foreground: true, restore_previous_focus: true },
|
||||||
input_schema: { type: "object", additionalProperties: false },
|
input_schema: { type: "object", required: ["board_id", "title"], properties: { board_id: { type: "string", minLength: 1 }, title: { type: "string", minLength: 1 }, initial_state: { type: "object" } }, additionalProperties: false },
|
||||||
output_schema: { type: "object" },
|
output_schema: { type: "object" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "whiteboard.export",
|
id: "whiteboard.submit",
|
||||||
version: 1,
|
version: 1,
|
||||||
handling: "operation",
|
handling: "operation",
|
||||||
target: { app_scope: "whiteboard", requires_foreground: true, restore_previous_focus: true },
|
target: { app_scope: "whiteboard", requires_foreground: true, restore_previous_focus: true },
|
||||||
input_schema: { type: "object", additionalProperties: false },
|
input_schema: { type: "object", required: ["board_id"], properties: { board_id: { type: "string", minLength: 1 }, submit_request: { type: "object" } }, additionalProperties: false },
|
||||||
output_schema: { type: "object", required: ["artifact_id"], properties: { artifact_id: { type: "string", minLength: 1 } }, additionalProperties: false },
|
output_schema: { type: "object", properties: { artifact_id: { type: "string", minLength: 1 }, summary: { type: "object" } }, additionalProperties: false },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ describe("LineUpRuntime", () => {
|
|||||||
const call = JSON.stringify({
|
const call = JSON.stringify({
|
||||||
v: 1, id: "task-tool-call", type: "lineup.v1.miniapp.tool.call", conversation_id: conversationID,
|
v: 1, id: "task-tool-call", type: "lineup.v1.miniapp.tool.call", conversation_id: conversationID,
|
||||||
sender: { kind: "agent", id: "agent_1" },
|
sender: { kind: "agent", id: "agent_1" },
|
||||||
payload: { call_id: "task-tool-call", tool_id: "task.create", app_scope: "task-dashboard", inventory_revision: "catalog-1", input: { title: "写验收记录" } },
|
payload: { call_id: "task-tool-call", tool_id: "task-dashboard.open", app_scope: "task-dashboard", inventory_revision: "catalog-1", input: { title: "写验收记录" } },
|
||||||
});
|
});
|
||||||
const transport = new FakeTransport([[{ message_seq: 60, from_uid: "agent_1", payload: call }], []]);
|
const transport = new FakeTransport([[{ message_seq: 60, from_uid: "agent_1", payload: call }], []]);
|
||||||
const runtime = new LineUpRuntime({ storage: new MemoryStorage(), createTransport: () => transport, now: () => "2026-08-04T00:00:00.000Z", newID: prefix => `${prefix}-id` });
|
const runtime = new LineUpRuntime({ storage: new MemoryStorage(), createTransport: () => transport, now: () => "2026-08-04T00:00:00.000Z", newID: prefix => `${prefix}-id` });
|
||||||
@@ -285,7 +285,7 @@ describe("LineUpRuntime", () => {
|
|||||||
sdk.tools.subscribe(tool => received.push(tool.call_id));
|
sdk.tools.subscribe(tool => received.push(tool.call_id));
|
||||||
expect(received).toEqual(["task-tool-call"]);
|
expect(received).toEqual(["task-tool-call"]);
|
||||||
expect(runtime.miniAppToolCalls()).toEqual([expect.objectContaining({ call_id: "task-tool-call", status: "running", instance_id: instance!.instance_id })]);
|
expect(runtime.miniAppToolCalls()).toEqual([expect.objectContaining({ call_id: "task-tool-call", status: "running", instance_id: instance!.instance_id })]);
|
||||||
await sdk.tools.complete("task-tool-call", { task_id: "task-1" });
|
await sdk.tools.complete("task-tool-call", { status: "completed", task_id: "task-1" });
|
||||||
await runtime.flushOutbox();
|
await runtime.flushOutbox();
|
||||||
expect(runtime.miniAppToolCalls()).toEqual([expect.objectContaining({ call_id: "task-tool-call", status: "completed" })]);
|
expect(runtime.miniAppToolCalls()).toEqual([expect.objectContaining({ call_id: "task-tool-call", status: "completed" })]);
|
||||||
expect(runtime.lifecycle.instances.get(instance!.instance_id)).toMatchObject({ state: "foreground" });
|
expect(runtime.lifecycle.instances.get(instance!.instance_id)).toMatchObject({ state: "foreground" });
|
||||||
@@ -337,7 +337,7 @@ describe("LineUpRuntime", () => {
|
|||||||
const launch = JSON.stringify({
|
const launch = JSON.stringify({
|
||||||
v: 1, id: "whiteboard-launch", type: "lineup.v1.miniapp.tool.call", conversation_id: conversationID,
|
v: 1, id: "whiteboard-launch", type: "lineup.v1.miniapp.tool.call", conversation_id: conversationID,
|
||||||
sender: { kind: "agent", id: "agent_1" },
|
sender: { kind: "agent", id: "agent_1" },
|
||||||
payload: { call_id: "whiteboard-launch", tool_id: "whiteboard.open", app_scope: "whiteboard", inventory_revision: "catalog-1", input: {} },
|
payload: { call_id: "whiteboard-launch", tool_id: "whiteboard.open", app_scope: "whiteboard", inventory_revision: "catalog-1", input: { board_id: "board-1", title: "验收画板" } },
|
||||||
});
|
});
|
||||||
const transport = new FakeTransport([[{ message_seq: 62, from_uid: "agent_1", payload: launch }], []]);
|
const transport = new FakeTransport([[{ message_seq: 62, from_uid: "agent_1", payload: launch }], []]);
|
||||||
const runtime = new LineUpRuntime({ storage: new MemoryStorage(), createTransport: () => transport });
|
const runtime = new LineUpRuntime({ storage: new MemoryStorage(), createTransport: () => transport });
|
||||||
@@ -359,7 +359,7 @@ describe("LineUpRuntime", () => {
|
|||||||
|
|
||||||
it("converges an unclaimed bundled Tool once on App close and keeps no new delivery path", async () => {
|
it("converges an unclaimed bundled Tool once on App close and keeps no new delivery path", async () => {
|
||||||
const conversationID = "user_1:2:channel_1";
|
const conversationID = "user_1:2:channel_1";
|
||||||
const call = JSON.stringify({ v: 1, id: "task-tool-close", type: "lineup.v1.miniapp.tool.call", conversation_id: conversationID, sender: { kind: "agent", id: "agent_1" }, payload: { call_id: "task-tool-close", tool_id: "task.create", app_scope: "task-dashboard", inventory_revision: "catalog-1", input: { title: "关闭测试" } } });
|
const call = JSON.stringify({ v: 1, id: "task-tool-close", type: "lineup.v1.miniapp.tool.call", conversation_id: conversationID, sender: { kind: "agent", id: "agent_1" }, payload: { call_id: "task-tool-close", tool_id: "task-dashboard.open", app_scope: "task-dashboard", inventory_revision: "catalog-1", input: { title: "关闭测试" } } });
|
||||||
const transport = new FakeTransport([[{ message_seq: 61, from_uid: "agent_1", payload: call }], []]);
|
const transport = new FakeTransport([[{ message_seq: 61, from_uid: "agent_1", payload: call }], []]);
|
||||||
const runtime = new LineUpRuntime({ storage: new MemoryStorage(), createTransport: () => transport, now: () => "2026-08-04T00:00:00.000Z" });
|
const runtime = new LineUpRuntime({ storage: new MemoryStorage(), createTransport: () => transport, now: () => "2026-08-04T00:00:00.000Z" });
|
||||||
await runtime.login(login);
|
await runtime.login(login);
|
||||||
@@ -393,7 +393,7 @@ describe("LineUpRuntime", () => {
|
|||||||
const call = JSON.stringify({
|
const call = JSON.stringify({
|
||||||
v: 1, id: "restart-tool-call", type: "lineup.v1.miniapp.tool.call", conversation_id: conversationID,
|
v: 1, id: "restart-tool-call", type: "lineup.v1.miniapp.tool.call", conversation_id: conversationID,
|
||||||
sender: { kind: "agent", id: "agent_1" },
|
sender: { kind: "agent", id: "agent_1" },
|
||||||
payload: { call_id: "restart-tool-call", tool_id: "task.create", app_scope: "task-dashboard", inventory_revision: "catalog-1", input: { title: "重启恢复" } },
|
payload: { call_id: "restart-tool-call", tool_id: "task-dashboard.open", app_scope: "task-dashboard", inventory_revision: "catalog-1", input: { title: "重启恢复" } },
|
||||||
});
|
});
|
||||||
const storage = new MemoryStorage();
|
const storage = new MemoryStorage();
|
||||||
const firstTransport = new FakeTransport([[{ message_seq: 70, from_uid: "agent_1", payload: call }], []]);
|
const firstTransport = new FakeTransport([[{ message_seq: 70, from_uid: "agent_1", payload: call }], []]);
|
||||||
@@ -404,7 +404,7 @@ describe("LineUpRuntime", () => {
|
|||||||
const instance = first.lifecycle.instances.activeFor("task-dashboard", conversationID)!;
|
const instance = first.lifecycle.instances.activeFor("task-dashboard", conversationID)!;
|
||||||
const sdk = first.openBundledMiniApp("task-dashboard", instance.instance_id);
|
const sdk = first.openBundledMiniApp("task-dashboard", instance.instance_id);
|
||||||
sdk.tools.subscribe(() => undefined);
|
sdk.tools.subscribe(() => undefined);
|
||||||
await sdk.tools.complete("restart-tool-call", { task_id: "restart-task" });
|
await sdk.tools.complete("restart-tool-call", { status: "completed", task_id: "restart-task" });
|
||||||
expect(first.miniAppToolCalls()).toEqual([expect.objectContaining({ call_id: "restart-tool-call", status: "submitted" })]);
|
expect(first.miniAppToolCalls()).toEqual([expect.objectContaining({ call_id: "restart-tool-call", status: "submitted" })]);
|
||||||
|
|
||||||
const secondTransport = new FakeTransport();
|
const secondTransport = new FakeTransport();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { MiniAppToolStateMachine } from "@/runtime/coordination/miniapp-tool-sta
|
|||||||
describe("MiniAppToolStateMachine", () => {
|
describe("MiniAppToolStateMachine", () => {
|
||||||
it("moves a call through claim and submitted result, while rejecting duplicate terminal writes", () => {
|
it("moves a call through claim and submitted result, while rejecting duplicate terminal writes", () => {
|
||||||
const machine = new MiniAppToolStateMachine();
|
const machine = new MiniAppToolStateMachine();
|
||||||
machine.receive({ call_id: "call-1", tool_id: "task.create", inventory_revision: "catalog-1", app_scope: "task-dashboard", instance_id: "task:1", conversation_id: "c", input: {}, created_at: "2026-08-04T00:00:00.000Z" });
|
machine.receive({ call_id: "call-1", tool_id: "task-dashboard.open", inventory_revision: "catalog-1", app_scope: "task-dashboard", instance_id: "task:1", conversation_id: "c", input: {}, created_at: "2026-08-04T00:00:00.000Z" });
|
||||||
expect(machine.route("call-1", "task:1", "2026-08-04T00:00:01.000Z")).toMatchObject({ disposition: "accepted", record: { status: "waiting_for_app" } });
|
expect(machine.route("call-1", "task:1", "2026-08-04T00:00:01.000Z")).toMatchObject({ disposition: "accepted", record: { status: "waiting_for_app" } });
|
||||||
expect(machine.start("call-1", "task:1", "2026-08-04T00:00:02.000Z")).toMatchObject({ disposition: "accepted", record: { status: "running" } });
|
expect(machine.start("call-1", "task:1", "2026-08-04T00:00:02.000Z")).toMatchObject({ disposition: "accepted", record: { status: "running" } });
|
||||||
expect(machine.submit("call-1", "task:1", { task_id: "t1" }, "2026-08-04T00:00:03.000Z")).toMatchObject({ disposition: "accepted", record: { status: "submitted" } });
|
expect(machine.submit("call-1", "task:1", { task_id: "t1" }, "2026-08-04T00:00:03.000Z")).toMatchObject({ disposition: "accepted", record: { status: "submitted" } });
|
||||||
@@ -14,7 +14,7 @@ describe("MiniAppToolStateMachine", () => {
|
|||||||
|
|
||||||
it("cancels only the instance that owns a pending call", () => {
|
it("cancels only the instance that owns a pending call", () => {
|
||||||
const machine = new MiniAppToolStateMachine();
|
const machine = new MiniAppToolStateMachine();
|
||||||
machine.receive({ call_id: "call-2", tool_id: "task.create", inventory_revision: "catalog-1", app_scope: "task-dashboard", instance_id: "task:1", conversation_id: "c", input: {}, created_at: "2026-08-04T00:00:00.000Z" });
|
machine.receive({ call_id: "call-2", tool_id: "task-dashboard.open", inventory_revision: "catalog-1", app_scope: "task-dashboard", instance_id: "task:1", conversation_id: "c", input: {}, created_at: "2026-08-04T00:00:00.000Z" });
|
||||||
machine.route("call-2", "task:1", "2026-08-04T00:00:01.000Z");
|
machine.route("call-2", "task:1", "2026-08-04T00:00:01.000Z");
|
||||||
expect(machine.cancel("call-2", "task:2", "app_closed", "2026-08-04T00:00:02.000Z")).toEqual(expect.objectContaining({ disposition: "invalid_scope" }));
|
expect(machine.cancel("call-2", "task:2", "app_closed", "2026-08-04T00:00:02.000Z")).toEqual(expect.objectContaining({ disposition: "invalid_scope" }));
|
||||||
expect(machine.cancel("call-2", "task:1", "app_closed", "2026-08-04T00:00:02.000Z")).toMatchObject({ disposition: "accepted", record: { status: "cancelled", cancel_reason: "app_closed" } });
|
expect(machine.cancel("call-2", "task:1", "app_closed", "2026-08-04T00:00:02.000Z")).toMatchObject({ disposition: "accepted", record: { status: "cancelled", cancel_reason: "app_closed" } });
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ describe("ToolRouter", () => {
|
|||||||
const apps = new CoreAppRegistry();
|
const apps = new CoreAppRegistry();
|
||||||
apps.install({
|
apps.install({
|
||||||
app_scope: "custom-dashboard", kind: "bundled", enabled: true, default_eligible: false, recovery: false,
|
app_scope: "custom-dashboard", kind: "bundled", enabled: true, default_eligible: false, recovery: false,
|
||||||
manifest: { app_scope: "custom-dashboard", version: "1.0.0", permissions: [], tools: [{ name: "task.create", handling: "operation", parameters: [], input_schema: { type: "object", required: ["title"], properties: { title: { type: "string" } }, additionalProperties: false } }] },
|
manifest: { app_scope: "custom-dashboard", version: "1.0.0", permissions: [], tools: [{ name: "task-dashboard.open", handling: "operation", parameters: [], input_schema: { type: "object", required: ["title"], properties: { title: { type: "string" } }, additionalProperties: false } }] },
|
||||||
});
|
});
|
||||||
const router = new ToolRouter(apps);
|
const router = new ToolRouter(apps);
|
||||||
const raw = JSON.stringify({ v: 1, id: "miniapp-call-1", type: "lineup.v1.miniapp.tool.call", conversation_id: "c1", sender: { kind: "agent", id: "agent" }, payload: { call_id: "miniapp-call-1", tool_id: "task.create", app_scope: "custom-dashboard", inventory_revision: "catalog-1", input: { title: "测试任务" } } });
|
const raw = JSON.stringify({ v: 1, id: "miniapp-call-1", type: "lineup.v1.miniapp.tool.call", conversation_id: "c1", sender: { kind: "agent", id: "agent" }, payload: { call_id: "miniapp-call-1", tool_id: "task-dashboard.open", app_scope: "custom-dashboard", inventory_revision: "catalog-1", input: { title: "测试任务" } } });
|
||||||
expect(router.route(raw, { app_scope: "chat", conversation_id: "c1", inventory_revision: "catalog-1" })).toEqual(expect.objectContaining({ disposition: "miniapp", handling: "operation", app_scope: "custom-dashboard" }));
|
expect(router.route(raw, { app_scope: "chat", conversation_id: "c1", inventory_revision: "catalog-1" })).toEqual(expect.objectContaining({ disposition: "miniapp", handling: "operation", app_scope: "custom-dashboard" }));
|
||||||
expect(router.route(raw.replace("catalog-1", "catalog-2"), { app_scope: "chat", conversation_id: "c1", inventory_revision: "catalog-1" })).toEqual({ disposition: "rejected", code: "inventory_revision_mismatch" });
|
expect(router.route(raw.replace("catalog-1", "catalog-2"), { app_scope: "chat", conversation_id: "c1", inventory_revision: "catalog-1" })).toEqual({ disposition: "rejected", code: "inventory_revision_mismatch" });
|
||||||
expect(router.route(raw.replace('"title":"测试任务"', '"unknown":true'), { app_scope: "chat", conversation_id: "c1", inventory_revision: "catalog-1" })).toEqual({ disposition: "rejected", code: "invalid_request" });
|
expect(router.route(raw.replace('"title":"测试任务"', '"unknown":true'), { app_scope: "chat", conversation_id: "c1", inventory_revision: "catalog-1" })).toEqual({ disposition: "rejected", code: "invalid_request" });
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ describe("ConversationStore", () => {
|
|||||||
const storage = new MemoryStorage();
|
const storage = new MemoryStorage();
|
||||||
const conversationID = "user_test:2:agent_channel";
|
const conversationID = "user_test:2:agent_channel";
|
||||||
const record: MiniAppToolCallRecord = {
|
const record: MiniAppToolCallRecord = {
|
||||||
call_id: "miniapp_001", tool_id: "task.create", inventory_revision: "catalog-1", app_scope: "task-dashboard", instance_id: "task:instance", conversation_id: conversationID,
|
call_id: "miniapp_001", tool_id: "task-dashboard.open", inventory_revision: "catalog-1", app_scope: "task-dashboard", instance_id: "task:instance", conversation_id: conversationID,
|
||||||
status: "submitted", input: { title: "任务" }, result: { task_id: "task-1" }, created_at: "2026-08-04T00:00:00.000Z", updated_at: "2026-08-04T00:00:01.000Z", submitted_at: "2026-08-04T00:00:01.000Z",
|
status: "submitted", input: { title: "任务" }, result: { task_id: "task-1" }, created_at: "2026-08-04T00:00:00.000Z", updated_at: "2026-08-04T00:00:01.000Z", submitted_at: "2026-08-04T00:00:01.000Z",
|
||||||
};
|
};
|
||||||
const first = new ConversationStore(storage);
|
const first = new ConversationStore(storage);
|
||||||
|
|||||||
@@ -188,11 +188,11 @@ function bridgeScript(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function taskDashboardSurfaceScript(): string {
|
function taskDashboardSurfaceScript(): string {
|
||||||
return `const handled=new Set;const tasks=new Map;const editor=document.getElementById("editor");document.getElementById("cancel").onclick=()=>bridge("lifecycle.close",{reason:"用户关闭任务面板"});async function poll(){try{const result=await bridge("tools.list");for(const call of Array.isArray(result.calls)?result.calls:[]){if(handled.has(call.call_id)||!(call.status==="waiting_for_app"||call.status==="running"))continue;handled.add(call.call_id);await handleTask(call)}}catch(_){}}async function handleTask(call){try{await bridge("tools.reportProgress",{call_id:call.call_id,progress:{status:"running",percent:10}});if(call.tool_id==="task.create"){const title=typeof call.input?.title==="string"?call.input.title:"未命名任务";const task_id=typeof call.input?.task_id==="string"?call.input.task_id:"task-"+call.call_id;tasks.set(task_id,{title,status:"open"});await bridge("surface.patch",{data:{state:{title:"任务面板",status:"已创建:"+title,percent:100,steps:[...tasks.values()].map(x=>x.title)}}});await bridge("tools.complete",{call_id:call.call_id,result:{task_id}})}else if(call.tool_id==="task.list"){await bridge("tools.complete",{call_id:call.call_id,result:{tasks:[...tasks.entries()].map(([task_id,task])=>Object.assign({task_id},task))}})}else await bridge("tools.fail",{call_id:call.call_id,code:"tool_not_supported",message:"不支持的任务工具"})}catch(_){await bridge("tools.fail",{call_id:call.call_id,code:"app_bridge_failed",message:"任务面板处理失败"}).catch(()=>{})}}setInterval(poll,500);poll();`;
|
return `const handled=new Set;const tasks=new Map;const editor=document.getElementById("editor");document.getElementById("cancel").onclick=()=>bridge("lifecycle.close",{reason:"用户关闭任务面板"});async function poll(){try{const result=await bridge("tools.list");for(const call of Array.isArray(result.calls)?result.calls:[]){if(handled.has(call.call_id)||!(call.status==="waiting_for_app"||call.status==="running"))continue;handled.add(call.call_id);await handleTask(call)}}catch(_){}}async function handleTask(call){try{await bridge("tools.reportProgress",{call_id:call.call_id,progress:{status:"running",percent:10}});if(call.tool_id==="task-dashboard.open"){const title=typeof call.input?.title==="string"?call.input.title:"未命名任务";const task_id=typeof call.input?.task_id==="string"?call.input.task_id:"task-"+call.call_id;tasks.set(task_id,{title,status:"open"});await bridge("surface.patch",{data:{state:{title:"任务面板",status:"已创建:"+title,percent:100,steps:[...tasks.values()].map(x=>x.title)}}});await bridge("tools.complete",{call_id:call.call_id,result:{status:"completed",task_id}})}else if(call.tool_id==="task-dashboard.update"){const task_id=String(call.input?.task_id||("task-"+call.call_id));const task={...(tasks.get(task_id)||{}),...(typeof call.input?.status==="string"?{status:call.input.status}:{}),...(typeof call.input?.progress==="number"?{progress:call.input.progress}: {})};tasks.set(task_id,task);await bridge("tools.complete",{call_id:call.call_id,result:{task_id,task}})}else await bridge("tools.fail",{call_id:call.call_id,code:"tool_not_supported",message:"不支持的任务工具"})}catch(_){await bridge("tools.fail",{call_id:call.call_id,code:"app_bridge_failed",message:"任务面板处理失败"}).catch(()=>{})}}setInterval(poll,500);poll();`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function whiteboardSurfaceScript(): string {
|
function whiteboardSurfaceScript(): string {
|
||||||
return `const handled=new Set;let board={title:"Whiteboard",status:"可编辑",elements:[]};const editor=document.getElementById("editor");document.getElementById("cancel").onclick=()=>bridge("lifecycle.close",{reason:"用户关闭画板"});editor.addEventListener("input",()=>{board={...board,text:editor.value};bridge("surface.patch",{data:{state:board}}).catch(()=>{})});async function poll(){try{const result=await bridge("tools.list");for(const call of Array.isArray(result.calls)?result.calls:[]){if(handled.has(call.call_id)||!(call.status==="waiting_for_app"||call.status==="running"))continue;handled.add(call.call_id);await handleBoard(call)}}catch(_){}}async function handleBoard(call){try{if(call.tool_id==="whiteboard.open"){await bridge("tools.reportProgress",{call_id:call.call_id,progress:{status:"opening",percent:40}});await bridge("tools.complete",{call_id:call.call_id,result:{status:"completed"}})}else if(call.tool_id==="whiteboard.export"){const artifact_id=typeof call.input?.artifact_id==="string"?call.input.artifact_id:"whiteboard-"+call.call_id;await bridge("tools.reportProgress",{call_id:call.call_id,progress:{status:"exporting",percent:80}});await bridge("surface.patch",{data:{state:{...board,artifact:{artifact_id}}}});await bridge("tools.complete",{call_id:call.call_id,result:{artifact_id}})}else await bridge("tools.fail",{call_id:call.call_id,code:"tool_not_supported",message:"不支持的画板工具"})}catch(_){await bridge("tools.fail",{call_id:call.call_id,code:"app_bridge_failed",message:"画板处理失败"}).catch(()=>{})}}setInterval(poll,500);poll();`;
|
return `const handled=new Set;let board={title:"Whiteboard",status:"可编辑",elements:[]};const editor=document.getElementById("editor");document.getElementById("cancel").onclick=()=>bridge("lifecycle.close",{reason:"用户关闭画板"});editor.addEventListener("input",()=>{board={...board,text:editor.value};bridge("surface.patch",{data:{state:board}}).catch(()=>{})});async function poll(){try{const result=await bridge("tools.list");for(const call of Array.isArray(result.calls)?result.calls:[]){if(handled.has(call.call_id)||!(call.status==="waiting_for_app"||call.status==="running"))continue;handled.add(call.call_id);await handleBoard(call)}}catch(_){}}async function handleBoard(call){try{if(call.tool_id==="whiteboard.open"){await bridge("tools.reportProgress",{call_id:call.call_id,progress:{status:"opening",percent:40}});await bridge("tools.complete",{call_id:call.call_id,result:{status:"completed"}})}else if(call.tool_id==="whiteboard.submit"){const artifact_id=typeof call.input?.artifact_id==="string"?call.input.artifact_id:"whiteboard-"+call.call_id;await bridge("tools.reportProgress",{call_id:call.call_id,progress:{status:"submitting",percent:80}});await bridge("surface.patch",{data:{state:{...board,artifact:{artifact_id}}}});await bridge("tools.complete",{call_id:call.call_id,result:{artifact_id}})}else await bridge("tools.fail",{call_id:call.call_id,code:"tool_not_supported",message:"不支持的画板工具"})}catch(_){await bridge("tools.fail",{call_id:call.call_id,code:"app_bridge_failed",message:"画板处理失败"}).catch(()=>{})}}setInterval(poll,500);poll();`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function productionSurfaceDocument(instanceID: string, verifiedBundle: string): string {
|
export function productionSurfaceDocument(instanceID: string, verifiedBundle: string): string {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
| ✅ | P1 | A4 | 同一个 App 的多个 instance 之间 Inbox 订阅没有隔离 | `lineup-runtime.ts` | 已按 `app_scope + conversation_id + instance_id` 过滤实时订阅,并补充双实例回归测试。 |
|
| ✅ | P1 | A4 | 同一个 App 的多个 instance 之间 Inbox 订阅没有隔离 | `lineup-runtime.ts` | 已按 `app_scope + conversation_id + instance_id` 过滤实时订阅,并补充双实例回归测试。 |
|
||||||
| ✅ | P1 | A5 | Interact 使用专用 `ChatRuntimeSDK` 旁路,没有和其他 MiniApp 共用 SDK v1 语义 | `tauri/src/runtime/app-management/app-sdk.ts`、`lineup-runtime.ts`、`main.ts` | 已明确为“通用 Runtime 操作 + 可信 UI 投影”:Interact 保留可信 DOM 和 IM 展示,但行为统一走 `sdk.runtime.*`,展示走 `sdk.ui.*`;旧扁平方法仅作兼容别名。 |
|
| ✅ | P1 | A5 | Interact 使用专用 `ChatRuntimeSDK` 旁路,没有和其他 MiniApp 共用 SDK v1 语义 | `tauri/src/runtime/app-management/app-sdk.ts`、`lineup-runtime.ts`、`main.ts` | 已明确为“通用 Runtime 操作 + 可信 UI 投影”:Interact 保留可信 DOM 和 IM 展示,但行为统一走 `sdk.runtime.*`,展示走 `sdk.ui.*`;旧扁平方法仅作兼容别名。 |
|
||||||
| ✅ | P1 | A6 | 标准交互的超时、dismiss、提交和答案 schema 没有由 Runtime 统一裁决 | `lineup-runtime.ts`、`agent-interaction-service.ts`、`standard-interaction-contract.ts` | 已让本地提交、取消、过期和 Agent dismiss 同步推进 Interaction 与 Kernel;notice 也会完成 Kernel;四种答案在 Runtime 按固定 schema 校验,默认有效期和重启过期均只写一条 outbox。 |
|
| ✅ | P1 | A6 | 标准交互的超时、dismiss、提交和答案 schema 没有由 Runtime 统一裁决 | `lineup-runtime.ts`、`agent-interaction-service.ts`、`standard-interaction-contract.ts` | 已让本地提交、取消、过期和 Agent dismiss 同步推进 Interaction 与 Kernel;notice 也会完成 Kernel;四种答案在 Runtime 按固定 schema 校验,默认有效期和重启过期均只写一条 outbox。 |
|
||||||
| 🔴 | P2 | A7 | 迭代文档中的 Tool 名称和代码 Manifest 不一致 | 主文档 §5.2/§5.3;`reference-miniapps.ts` | 统一契约、Manifest、fixture、Agent Inventory 和验收脚本中的名称。 |
|
| ✅ | 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 | 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` | 如果契约要求进度可恢复,需持久化最后进度并覆盖 Runtime 重启;否则修改文档,明确 progress 只是一种可丢失事件。 |
|
| 🔴 | P2 | A9 | MiniApp progress 没有进入可恢复的 Tool 状态记录 | `miniapp-tool-state.ts` | 如果契约要求进度可恢复,需持久化最后进度并覆盖 Runtime 重启;否则修改文档,明确 progress 只是一种可丢失事件。 |
|
||||||
| 🔴 | 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` 字段 | `app-instance-manager.ts`、`lineup-runtime.ts:203-209,1052-1058` | P3 也必须在本迭代处理:App session opened/closed 事件和持久化记录补齐两个上下文字段。 |
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
## 1. 已验证通过的部分
|
## 1. 已验证通过的部分
|
||||||
|
|
||||||
- `npm run build` 通过。
|
- `npm run build` 通过。
|
||||||
- `npm test -- --run` 通过:29 个测试文件、129 个测试(含默认有效期、重启过期和答案 schema 回归)。
|
- `npm test -- --run` 通过:29 个测试文件、131 个测试(含 Tool 契约、默认有效期、重启过期和答案 schema 回归)。
|
||||||
- `git diff --check` 通过。
|
- `git diff --check` 通过。
|
||||||
- 使用独立 `agent-browser` 会话登录本地测试账号成功。
|
- 使用独立 `agent-browser` 会话登录本地测试账号成功。
|
||||||
- 主 IM 页面、同步状态、应用子会话区域和“启用任务面板”入口可见。
|
- 主 IM 页面、同步状态、应用子会话区域和“启用任务面板”入口可见。
|
||||||
@@ -88,17 +88,17 @@ Interact 仍然可以使用可信 DOM,但它的 SDK 已明确拆成两层:`s
|
|||||||
|
|
||||||
## 3. 验收结论
|
## 3. 验收结论
|
||||||
|
|
||||||
当前结论为(完成本轮 P1-1~P1-4 后):
|
当前结论为(完成本轮 P1-1~P1-4,并收敛 A7 后):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
功能回归:通过
|
功能回归:通过
|
||||||
基础构建与自动化测试:通过
|
基础构建与自动化测试:通过
|
||||||
真实登录和消息发送:通过
|
真实登录和消息发送:通过
|
||||||
App 架构边界:部分通过(A1/A2/A5/A6 已实现,A2 待浏览器复验)
|
App 架构边界:部分通过(A1/A2/A5/A6 已实现,A2 待浏览器复验)
|
||||||
MiniApp 隔离与 SDK 规范:A1~A6 已通过,仍需处理后续 P2/P3
|
MiniApp 隔离与 SDK 规范:A1~A7 已通过,仍需处理后续 P2/P3
|
||||||
标准交互终态契约:通过
|
标准交互终态契约:通过
|
||||||
迭代整体验收:不通过,继续处理 A7~A11;A2 需在最终浏览器复验中确认
|
迭代整体验收:不通过,继续处理 A8~A11;A2 需在最终浏览器复验中确认
|
||||||
```
|
```
|
||||||
|
|
||||||
本轮独立验收由子 agent 完成。A6 已在本轮修复并通过自动化验证;下一轮继续处理 A7~A11,并重新执行
|
本轮独立验收由子 agent 完成。A6 已在本轮修复并通过自动化验证,A7 已完成契约收敛并通过全量测试;下一轮继续处理 A8~A11,并重新执行
|
||||||
独立验收。P2/P3 不能被静默删除,必须在本迭代结束前解决或保留明确的延期记录。
|
独立验收。P2/P3 不能被静默删除,必须在本迭代结束前解决或保留明确的延期记录。
|
||||||
|
|||||||
Reference in New Issue
Block a user