Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bound-slack-approval-callbacks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eve": patch
---

Keep Slack tool approval callbacks responsive for large inputs by omitting duplicated input details from fallback text. The approval card still shows the truncated input preview.
69 changes: 68 additions & 1 deletion packages/eve/src/public/channels/slack/defaults.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { describe, expect, it, vi } from "vitest";

import type { SessionContext } from "#public/definitions/callback-context.js";
import { defaultEvents } from "#public/channels/slack/defaults.js";
import { defaultEvents, defaultInputRequestedHandler } from "#public/channels/slack/defaults.js";
import { HITL_ACTION_PREFIX } from "#public/channels/slack/hitl.js";
import type { SlackChannelState, SlackEventContext } from "#public/channels/slack/slackChannel.js";

function sessionContext(
Expand Down Expand Up @@ -51,6 +52,72 @@ function authRequiredEvent(
};
}

describe("defaultInputRequestedHandler", () => {
it("bounds callback payloads independently of the full approval input", async () => {
const { channel, post } = buildChannelStub();
const handler = defaultInputRequestedHandler();

for (const inputLength of [5_000, 50_000]) {
await handler(
{
requests: [
{
action: {
callId: "call_cloud_agent",
input: {
prompt: `cloud-agent-prompt ${"x".repeat(inputLength)} tail-sentinel`,
},
kind: "tool-call",
toolName: "spawn-cloud-agent",
},
display: "confirmation",
kind: "tool-approval",
options: [
{ id: "approve", label: "Approve" },
{ id: "cancel", label: "Cancel" },
],
prompt: "Approve tool call: spawn-cloud-agent",
requestId: "approval_cloud_agent",
},
],
sequence: 0,
stepIndex: 0,
turnId: "t1",
},
channel,
sessionCtx,
);
}

const callbackSizes = post.mock.calls.map(([message]) => {
const renderedMessage = JSON.stringify(message);
expect(renderedMessage.split("cloud-agent-prompt").length - 1).toBe(1);
expect(renderedMessage).not.toContain("tail-sentinel");

const callbackBody = new URLSearchParams({
payload: JSON.stringify({
actions: [
{
action_id: `${HITL_ACTION_PREFIX}approval_cloud_agent:button:1`,
value: "approve",
},
],
channel: { id: "C123" },
message,
team: { id: "T01" },
type: "block_actions",
user: { id: "U01" },
}),
}).toString();
return new TextEncoder().encode(callbackBody).byteLength;
});

expect(callbackSizes).toHaveLength(2);
expect(callbackSizes[1]).toBe(callbackSizes[0]);
expect(callbackSizes[0]).toBeLessThanOrEqual(6_000);
});
});

describe("defaultEvents authorization.required", () => {
it("posts a public status and delivers the challenge ephemerally to the triggering user", async () => {
const { channel, post, postEphemeral } = buildChannelStub({ triggeringUserId: "U777" });
Expand Down
6 changes: 2 additions & 4 deletions packages/eve/src/public/channels/slack/hitl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ describe("renderInputRequestBlocks", () => {
});

describe("formatInputRequestFallbackText", () => {
it("includes approval tool input in Slack fallback text", () => {
it("keeps approval tool input out of Slack fallback text", () => {
const text = formatInputRequestFallbackText(
makeRequest({
action: {
Expand All @@ -386,9 +386,7 @@ describe("formatInputRequestFallbackText", () => {
}),
);

expect(text).toContain("Approve tool call: mongodb-mutate");
expect(text).toContain('"collection": "orgs"');
expect(text).toContain('"_id": "48gtnni64rxqtaoh"');
expect(text).toBe("Approve tool call: mongodb-mutate");
});

it("leaves non-approval fallback text unchanged", () => {
Expand Down
7 changes: 3 additions & 4 deletions packages/eve/src/public/channels/slack/hitl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,11 @@ export function renderInputRequestBlocks(request: InputRequest): unknown[] {

/**
* Creates the fallback text for one HITL request. Slack clients use this
* outside the rich Block Kit surface, so include the same approval details
* that appear in the blocks.
* outside the rich Block Kit surface. Keep it concise because Slack includes
* this text alongside the full block list in interaction callbacks.
*/
export function formatInputRequestFallbackText(request: InputRequest): string {
const details = formatToolInputDetails(request);
return details === undefined ? request.prompt : `${request.prompt}\n${details}`;
return request.prompt;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ describe("slackChannel() default event handlers", () => {
};
expect(body).toMatchObject({
channel: "C01",
text: 'Approve tool call: mongodb-mutate\n*Tool input*\n```\n{\n "operation": "deleteMany"\n}\n```',
text: "Approve tool call: mongodb-mutate",
thread_ts: "1700000000.000001",
});

Expand Down