From 74efa28384f385ef9ad528d32c64bb19c3c605d0 Mon Sep 17 00:00:00 2001 From: Kevin Rajan <7121943+kvnloo@users.noreply.github.com> Date: Fri, 25 Sep 2026 21:11:47 -0500 Subject: [PATCH 1/4] fix: keep completed reviewed actions terminal on replay --- apps/server/src/engine/model.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/server/src/engine/model.ts b/apps/server/src/engine/model.ts index dfd3d8ff..724fb24f 100644 --- a/apps/server/src/engine/model.ts +++ b/apps/server/src/engine/model.ts @@ -221,6 +221,13 @@ export async function executeModelTask( async (data) => { const key = createHash("sha256").update(JSON.stringify(data)).digest("hex"); const action = await service.prepare(owner, task, { kind: "email.send", data }, key, ctx); + if (action.status === "succeeded") { + task = await ctx.checkpoint({ + state: { ...task.state, approvalResult: action.result }, + actionId: null, + }); + return { status: "succeeded", actionId: action.id, result: action.result }; + } outcome = { status: "waiting_approval", actionId: action.id }; return { status: "waiting_approval", actionId: action.id }; }, @@ -238,6 +245,13 @@ export async function executeModelTask( key, ctx, ); + if (action.status === "succeeded") { + task = await ctx.checkpoint({ + state: { ...task.state, approvalResult: action.result }, + actionId: null, + }); + return { status: "succeeded", actionId: action.id, result: action.result }; + } outcome = { status: "waiting_approval", actionId: action.id }; return { status: "waiting_approval", actionId: action.id }; }, From 620b2ab1d66ac105b7acdaadb1c405860cf986b8 Mon Sep 17 00:00:00 2001 From: Kevin Rajan <7121943+kvnloo@users.noreply.github.com> Date: Fri, 25 Sep 2026 21:11:50 -0500 Subject: [PATCH 2/4] fix: preserve terminal reviewed action state --- apps/server/src/engine/service.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/server/src/engine/service.ts b/apps/server/src/engine/service.ts index 8b49d254..6a91cf4b 100644 --- a/apps/server/src/engine/service.ts +++ b/apps/server/src/engine/service.ts @@ -705,6 +705,12 @@ export class AgentService { 409, ); const proposal = await this.actions.propose(owner, input, `${task.id}:${key}`, task.id); + if (proposal.status === "succeeded") return proposal; + if (proposal.status !== "awaiting_review" && proposal.status !== "executing") + throw new AppError( + `Reviewed action ${proposal.status}: ${proposal.error ?? "No further action was taken"}`, + 409, + ); try { await context.checkpoint({ actionId: proposal.id }); } catch (error) { @@ -712,11 +718,12 @@ export class AgentService { await this.actions.decide(owner, proposal.id, proposal.hash, "deny"); throw error; } - await context.event( - "approval", - proposal.title, - `Review prepared for ${proposal.account ?? "the connected account"}`, - ); + if (proposal.status === "awaiting_review") + await context.event( + "approval", + proposal.title, + `Review prepared for ${proposal.account ?? "the connected account"}`, + ); return proposal; } private async execute( From 4f3b19d6d5e10fd01c045cf546ccf8878b3719be Mon Sep 17 00:00:00 2001 From: Kevin Rajan <7121943+kvnloo@users.noreply.github.com> Date: Fri, 25 Sep 2026 21:12:03 -0500 Subject: [PATCH 3/4] test: cover completed action replay --- tests/model-worker.test.ts | 67 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/model-worker.test.ts b/tests/model-worker.test.ts index 6194a98d..d2642346 100644 --- a/tests/model-worker.test.ts +++ b/tests/model-worker.test.ts @@ -164,3 +164,70 @@ test("the model worker keeps the text a model replies with when it calls no tool await rm(directory, { recursive: true, force: true }); } }); + + +test("replaying a completed prepared action returns its receipt without reopening approval", async (t) => { + const directory = await mkdtemp(join(tmpdir(), "openmuse-model-replay-")); + const db = await createStore(); + const draft = { + title: "Sample walk", + start: "2026-10-10T10:00:00-07:00", + end: "2026-10-10T11:00:00-07:00", + }; + let calls: ({ name: string; arguments: object } | undefined)[] = [ + { name: "prepare_event", arguments: draft }, + ]; + const { requests } = await modelFixture(t, (index) => calls[index]); + const server = await createApp(db, { + mode: "sample", + port: 8787, + host: "127.0.0.1", + publicUrl: "http://localhost:8787", + dataDir: directory, + agentBackend: "model", + intelligenceApiKey: "test-project-key-never-sent", + model: "openai/fixture", + googleRedirectUri: "http://localhost:8787/api/google/callback", + allowedOrigins: [], + }); + try { + const task = await server.agent.createTask("replay-owner", { + prompt: "Put a sample walk on my calendar", + }); + await server.agent.worker.tick(); + const pending = await server.agent.getTask("replay-owner", task.id); + assert.equal(pending.status, "waiting_approval"); + assert.ok(pending.actionId); + const proposal = await db.get("replay-owner", "actions", pending.actionId); + assert.ok(proposal); + const completed = await server.actions.decide( + "replay-owner", + proposal.id, + proposal.hash, + "approve", + ); + assert.equal(completed.status, "succeeded"); + + requests.length = 0; + calls = [ + { name: "prepare_event", arguments: draft }, + { name: "finish_task", arguments: { summary: "The reviewed event is already complete." } }, + ]; + await server.agent.worker.tick(); + + const finished = await server.agent.getTask("replay-owner", task.id); + assert.equal(finished.status, "succeeded", finished.error ?? finished.question); + assert.equal(finished.actionId, null); + assert.equal(finished.state.approvalResult, completed.result); + const actions = (await db.list("replay-owner", "actions")).filter( + (action) => action.taskId === task.id, + ); + assert.equal(actions.length, 1); + assert.equal(actions[0].status, "succeeded"); + assert.ok(requests.some((request) => request.body.includes(String(completed.result)))); + } finally { + await server.agent.stop(); + await db.close(); + await rm(directory, { recursive: true, force: true }); + } +}); From 3b117656f3a00b928677b404ed0e508fd5ab0104 Mon Sep 17 00:00:00 2001 From: Kevin Rajan <7121943+kvnloo@users.noreply.github.com> Date: Fri, 25 Sep 2026 21:14:02 -0500 Subject: [PATCH 4/4] test: normalize regression formatting --- tests/model-worker.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/model-worker.test.ts b/tests/model-worker.test.ts index d2642346..a98ef23e 100644 --- a/tests/model-worker.test.ts +++ b/tests/model-worker.test.ts @@ -165,7 +165,6 @@ test("the model worker keeps the text a model replies with when it calls no tool } }); - test("replaying a completed prepared action returns its receipt without reopening approval", async (t) => { const directory = await mkdtemp(join(tmpdir(), "openmuse-model-replay-")); const db = await createStore();