diff --git a/apps/server/src/provider/acp/AcpCoreRuntimeEvents.test.ts b/apps/server/src/provider/acp/AcpCoreRuntimeEvents.test.ts index 7fe25699bbc..394ada83f76 100644 --- a/apps/server/src/provider/acp/AcpCoreRuntimeEvents.test.ts +++ b/apps/server/src/provider/acp/AcpCoreRuntimeEvents.test.ts @@ -68,6 +68,46 @@ describe("AcpCoreRuntimeEvents", () => { }); }); + it("maps generic ACP permission kinds to dynamic tool approvals", () => { + const stamp = { eventId: "event-1" as never, createdAt: "2026-03-27T00:00:00.000Z" }; + + for (const kind of ["search", "fetch", "other", "unknown", "future-tool-kind"]) { + const permissionRequest = { kind }; + const request = { + stamp, + provider: ProviderDriverKind.make("cursor"), + threadId: "thread-1" as never, + turnId: TurnId.make("turn-1"), + requestId: RuntimeRequestId.make(`request-${kind}`), + permissionRequest, + }; + + expect( + makeAcpRequestOpenedEvent({ + ...request, + detail: kind, + args: {}, + source: "acp.jsonrpc", + method: "session/request_permission", + rawPayload: { sessionId: "session-1" }, + }), + ).toMatchObject({ + type: "request.opened", + payload: { requestType: "dynamic_tool_call" }, + }); + + expect( + makeAcpRequestResolvedEvent({ + ...request, + decision: "accept", + }), + ).toMatchObject({ + type: "request.resolved", + payload: { requestType: "dynamic_tool_call" }, + }); + } + }); + it("maps ACP core plan, tool-call, and content updates", () => { const stamp = { eventId: "event-1" as never, createdAt: "2026-03-27T00:00:00.000Z" }; const turnId = TurnId.make("turn-1"); diff --git a/apps/server/src/provider/acp/AcpCoreRuntimeEvents.ts b/apps/server/src/provider/acp/AcpCoreRuntimeEvents.ts index c93e61dc37b..bd25e9815ae 100644 --- a/apps/server/src/provider/acp/AcpCoreRuntimeEvents.ts +++ b/apps/server/src/provider/acp/AcpCoreRuntimeEvents.ts @@ -26,7 +26,7 @@ interface AcpEventStamp { type AcpCanonicalRequestType = Extract< CanonicalRequestType, - "exec_command_approval" | "file_read_approval" | "file_change_approval" | "unknown" + "exec_command_approval" | "file_read_approval" | "file_change_approval" | "dynamic_tool_call" >; function canonicalRequestTypeFromAcpKind(kind: string | "unknown"): AcpCanonicalRequestType { @@ -40,7 +40,7 @@ function canonicalRequestTypeFromAcpKind(kind: string | "unknown"): AcpCanonical case "move": return "file_change_approval"; default: - return "unknown"; + return "dynamic_tool_call"; } } diff --git a/apps/web/src/session-logic.test.ts b/apps/web/src/session-logic.test.ts index 0f12e672f66..3c9c04396d5 100644 --- a/apps/web/src/session-logic.test.ts +++ b/apps/web/src/session-logic.test.ts @@ -117,6 +117,32 @@ describe("derivePendingApprovals", () => { ]); }); + it("derives dynamic tool requests as actionable generic approvals", () => { + const activities: OrchestrationThreadActivity[] = [ + makeActivity({ + id: "approval-open-dynamic-tool", + createdAt: "2026-02-23T00:00:01.000Z", + kind: "approval.requested", + summary: "Approval requested", + tone: "approval", + payload: { + requestId: "req-dynamic-tool", + requestType: "dynamic_tool_call", + detail: "Search the web", + }, + }), + ]; + + expect(derivePendingApprovals(activities)).toEqual([ + { + requestId: "req-dynamic-tool", + requestKind: "command", + createdAt: "2026-02-23T00:00:01.000Z", + detail: "Search the web", + }, + ]); + }); + it("clears stale pending approvals when provider reports unknown pending request", () => { const activities: OrchestrationThreadActivity[] = [ makeActivity({