fix(runtime): persist miniapp tool progress
This commit is contained in:
@@ -416,6 +416,28 @@ describe("LineUpRuntime", () => {
|
||||
expect(secondTransport.sent.filter(entry => entry.payload.includes("restart-tool-call"))).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("restores the latest MiniApp progress after Runtime restart", async () => {
|
||||
const conversationID = "user_1:2:channel_1";
|
||||
const call = JSON.stringify({
|
||||
v: 1, id: "progress-restart-call", type: "lineup.v1.miniapp.tool.call", conversation_id: conversationID,
|
||||
sender: { kind: "agent", id: "agent_1" },
|
||||
payload: { call_id: "progress-restart-call", tool_id: "task-dashboard.open", app_scope: "task-dashboard", inventory_revision: "catalog-1", input: { title: "进度恢复" } },
|
||||
});
|
||||
const storage = new MemoryStorage();
|
||||
const firstTransport = new FakeTransport([[{ message_seq: 71, from_uid: "agent_1", payload: call }], []]);
|
||||
const first = new LineUpRuntime({ storage, createTransport: () => firstTransport });
|
||||
await first.login(login);
|
||||
await first.syncOnce();
|
||||
const instance = first.lifecycle.instances.activeFor("task-dashboard", conversationID)!;
|
||||
const sdk = first.openBundledMiniApp("task-dashboard", instance.instance_id);
|
||||
await sdk.tools.reportProgress("progress-restart-call", { percent: 42, status: "working", detail: "正在处理" });
|
||||
expect(first.miniAppToolCalls()).toEqual([expect.objectContaining({ call_id: "progress-restart-call", status: "running", progress: { percent: 42, status: "working", detail: "正在处理" } })]);
|
||||
|
||||
const second = new LineUpRuntime({ storage, createTransport: () => new FakeTransport() });
|
||||
await second.login(login);
|
||||
expect(second.miniAppToolCalls()).toEqual([expect.objectContaining({ call_id: "progress-restart-call", status: "waiting_for_app", progress: { percent: 42, status: "working", detail: "正在处理" } })]);
|
||||
});
|
||||
|
||||
it("projects Agent state and Chat-safe Runtime status through the SDK", async () => {
|
||||
const conversationID = "user_1:2:channel_1";
|
||||
const transport = new FakeTransport([[
|
||||
|
||||
@@ -763,7 +763,7 @@ export class LineUpRuntime {
|
||||
|
||||
public reportMiniAppToolProgress(instanceID: string, callID: string, progress: MiniAppToolProgress): Promise<CommandReceipt> {
|
||||
if (!validProgress(progress)) return Promise.resolve({ disposition: "rejected", code: "invalid_action" });
|
||||
const transition = this.miniappTools.progress(callID, instanceID, this.now());
|
||||
const transition = this.miniappTools.progress(callID, instanceID, progress, this.now());
|
||||
if (transition.disposition !== "accepted") return Promise.resolve({ disposition: "rejected", code: "invalid_action" });
|
||||
this.store.replaceMiniAppToolCalls(this.miniappTools.list());
|
||||
return this.queueProtocolAction("app-result", "lineup.v1.miniapp.tool.progress", {
|
||||
|
||||
@@ -19,4 +19,14 @@ describe("MiniAppToolStateMachine", () => {
|
||||
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" } });
|
||||
});
|
||||
|
||||
it("keeps the latest progress in the durable call record", () => {
|
||||
const machine = new MiniAppToolStateMachine();
|
||||
machine.receive({ call_id: "call-progress", 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-progress", "task:1", "2026-08-04T00:00:01.000Z");
|
||||
expect(machine.progress("call-progress", "task:1", { percent: 42, status: "working", detail: "正在处理" }, "2026-08-04T00:00:02.000Z")).toMatchObject({ disposition: "accepted", record: { progress: { percent: 42, status: "working", detail: "正在处理" } } });
|
||||
const restored = new MiniAppToolStateMachine();
|
||||
restored.restore(machine.list(), "2026-08-04T00:00:03.000Z");
|
||||
expect(restored.get("call-progress")).toMatchObject({ status: "waiting_for_app", progress: { percent: 42, status: "working", detail: "正在处理" } });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,12 @@ export type MiniAppToolStatus =
|
||||
|
||||
export type MiniAppToolCancelReason = "app_closed" | "agent_cancelled" | "runtime_cancelled";
|
||||
|
||||
export type MiniAppToolProgress = {
|
||||
percent?: number;
|
||||
status?: string;
|
||||
detail?: string;
|
||||
};
|
||||
|
||||
export type MiniAppToolCallRecord = {
|
||||
call_id: string;
|
||||
tool_id: string;
|
||||
@@ -23,6 +29,7 @@ export type MiniAppToolCallRecord = {
|
||||
conversation_id: string;
|
||||
status: MiniAppToolStatus;
|
||||
input: JsonObject;
|
||||
progress?: MiniAppToolProgress;
|
||||
result?: JsonObject;
|
||||
error_code?: string;
|
||||
cancel_reason?: MiniAppToolCancelReason;
|
||||
@@ -66,10 +73,11 @@ export class MiniAppToolStateMachine {
|
||||
});
|
||||
}
|
||||
|
||||
public progress(callID: string, instanceID: string, now: string): MiniAppToolTransition {
|
||||
public progress(callID: string, instanceID: string, progress: MiniAppToolProgress, now: string): MiniAppToolTransition {
|
||||
return this.transition(callID, now, ["waiting_for_app", "running"], record => {
|
||||
if (record.instance_id !== instanceID) throw new Error("instance mismatch");
|
||||
record.status = "running";
|
||||
record.progress = clone(progress);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user