From e5983b01037b47b097ef1373e53413c58b456759 Mon Sep 17 00:00:00 2001 From: benpankow Date: Wed, 29 Jul 2026 13:49:16 -0700 Subject: [PATCH] fix(eve): target approval candidate feedback Signed-off-by: benpankow --- packages/eve/src/harness/tool-loop.ts | 8 +++--- .../public/channels/slack/defaults.test.ts | 27 +++++++++++++++++++ .../eve/src/public/channels/slack/defaults.ts | 7 ++++- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/packages/eve/src/harness/tool-loop.ts b/packages/eve/src/harness/tool-loop.ts index 3694874a6..c47136ccd 100644 --- a/packages/eve/src/harness/tool-loop.ts +++ b/packages/eve/src/harness/tool-loop.ts @@ -581,9 +581,11 @@ export function createToolLoopHarness(config: ToolLoopHarnessConfig): StepFn { outcome: authorized.kind === "authorization-required" ? "pending" : authorized.kind, requestId: authorized.requestId, responderPrincipalId: - getApprovalAuditState(session.state).activeCandidates.find( - (candidate) => candidate.candidateId === authorized.candidateId, - )?.responder.principalId ?? "unknown", + [ + ...getApprovalAuditState(session.state).activeCandidates, + ...getApprovalAuditState(session.state).candidateHistory, + ].find((candidate) => candidate.candidateId === authorized.candidateId)?.responder + .principalId ?? "unknown", safeReason: "safeReason" in authorized ? authorized.safeReason : undefined, sequence: emissionState.sequence, stepIndex: emissionState.stepIndex, diff --git a/packages/eve/src/public/channels/slack/defaults.test.ts b/packages/eve/src/public/channels/slack/defaults.test.ts index 6399db72d..12a500fd2 100644 --- a/packages/eve/src/public/channels/slack/defaults.test.ts +++ b/packages/eve/src/public/channels/slack/defaults.test.ts @@ -81,6 +81,33 @@ describe("defaultEvents approval lifecycle", () => { ); }); + it("delivers an immediate rejection to the current responder without prior mapping", async () => { + const { channel, postEphemeral } = buildChannelStub(); + const ctx = sessionContext({ + attributes: { user_id: "U777" }, + authenticator: "slack-webhook", + principalId: "slack:T1:U777", + principalType: "user", + }); + + await defaultEvents["approval.candidate"]!( + { + candidateId: "candidate-1", + outcome: "rejected", + requestId: "approval-1", + responderPrincipalId: "slack:T1:U777", + safeReason: "GitHub write access is required.", + sequence: 1, + stepIndex: 0, + turnId: "turn-1", + }, + channel, + ctx, + ); + + expect(postEphemeral).toHaveBeenCalledWith("U777", "GitHub write access is required."); + }); + it("updates the shared card only after settlement", async () => { const { channel, request } = buildChannelStub({ pendingApprovalCards: { diff --git a/packages/eve/src/public/channels/slack/defaults.ts b/packages/eve/src/public/channels/slack/defaults.ts index 156bb7a0f..9ae7c50bc 100644 --- a/packages/eve/src/public/channels/slack/defaults.ts +++ b/packages/eve/src/public/channels/slack/defaults.ts @@ -175,7 +175,12 @@ export const defaultEvents: SlackChannelInternalEvents = { ); return; } - const userId = channel.state.pendingApprovalCandidateUsers?.[event.candidateId]; + const mappedUserId = channel.state.pendingApprovalCandidateUsers?.[event.candidateId]; + const userId = + mappedUserId ?? + (ctx.session.auth.current?.principalId === event.responderPrincipalId + ? currentUserId + : undefined); if (userId === undefined) return; if (event.outcome === "rejected" || event.outcome === "failed") { await channel.thread.postEphemeral(