authorgravatar for 269035359+t3-code[bot]@users.noreply.github.comt3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com> 2026-08-06 12:55:31-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-06 12:55:31-04:00
log99d91ddaa4996203b675cbd086d5acaa642d4827
tree5b62db770d476419b9e509c9f0d99b3f49879b74
parentab3b55e29ac20a6212f09e38f1983b856fd77695
signature Signed by PGP key B5690EEEBB952194

fix(acp): keep unknown approvals actionable (#5430)

Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com>

3 files changed, 68 insertions(+), 2 deletions(-)

apps/server/src/provider/acp/AcpCoreRuntimeEvents.test.ts+40
...@@ -68,6 +68,46 @@ describe("AcpCoreRuntimeEvents", () => {...@@ -68,6 +68,46 @@ describe("AcpCoreRuntimeEvents", () => {
68 });68 });
69 });69 });
7070
71 it("maps generic ACP permission kinds to dynamic tool approvals", () => {
72 const stamp = { eventId: "event-1" as never, createdAt: "2026-03-27T00:00:00.000Z" };
73
74 for (const kind of ["search", "fetch", "other", "unknown", "future-tool-kind"]) {
75 const permissionRequest = { kind };
76 const request = {
77 stamp,
78 provider: ProviderDriverKind.make("cursor"),
79 threadId: "thread-1" as never,
80 turnId: TurnId.make("turn-1"),
81 requestId: RuntimeRequestId.make(`request-${kind}`),
82 permissionRequest,
83 };
84
85 expect(
86 makeAcpRequestOpenedEvent({
87 ...request,
88 detail: kind,
89 args: {},
90 source: "acp.jsonrpc",
91 method: "session/request_permission",
92 rawPayload: { sessionId: "session-1" },
93 }),
94 ).toMatchObject({
95 type: "request.opened",
96 payload: { requestType: "dynamic_tool_call" },
97 });
98
99 expect(
100 makeAcpRequestResolvedEvent({
101 ...request,
102 decision: "accept",
103 }),
104 ).toMatchObject({
105 type: "request.resolved",
106 payload: { requestType: "dynamic_tool_call" },
107 });
108 }
109 });
110
71 it("maps ACP core plan, tool-call, and content updates", () => {111 it("maps ACP core plan, tool-call, and content updates", () => {
72 const stamp = { eventId: "event-1" as never, createdAt: "2026-03-27T00:00:00.000Z" };112 const stamp = { eventId: "event-1" as never, createdAt: "2026-03-27T00:00:00.000Z" };
73 const turnId = TurnId.make("turn-1");113 const turnId = TurnId.make("turn-1");
apps/server/src/provider/acp/AcpCoreRuntimeEvents.ts+2-2
...@@ -26,7 +26,7 @@ interface AcpEventStamp {...@@ -26,7 +26,7 @@ interface AcpEventStamp {
2626
27type AcpCanonicalRequestType = Extract<27type AcpCanonicalRequestType = Extract<
28 CanonicalRequestType,28 CanonicalRequestType,
29 "exec_command_approval" | "file_read_approval" | "file_change_approval" | "unknown"29 "exec_command_approval" | "file_read_approval" | "file_change_approval" | "dynamic_tool_call"
30>;30>;
3131
32function canonicalRequestTypeFromAcpKind(kind: string | "unknown"): AcpCanonicalRequestType {32function canonicalRequestTypeFromAcpKind(kind: string | "unknown"): AcpCanonicalRequestType {
...@@ -40,7 +40,7 @@ function canonicalRequestTypeFromAcpKind(kind: string | "unknown"): AcpCanonical...@@ -40,7 +40,7 @@ function canonicalRequestTypeFromAcpKind(kind: string | "unknown"): AcpCanonical
40 case "move":40 case "move":
41 return "file_change_approval";41 return "file_change_approval";
42 default:42 default:
43 return "unknown";43 return "dynamic_tool_call";
44 }44 }
45}45}
4646
apps/web/src/session-logic.test.ts+26
...@@ -130,6 +130,32 @@ describe("derivePendingApprovals", () => {...@@ -130,6 +130,32 @@ describe("derivePendingApprovals", () => {
130 ]);130 ]);
131 });131 });
132132
133 it("derives dynamic tool requests as actionable generic approvals", () => {
134 const activities: OrchestrationThreadActivity[] = [
135 makeActivity({
136 id: "approval-open-dynamic-tool",
137 createdAt: "2026-02-23T00:00:01.000Z",
138 kind: "approval.requested",
139 summary: "Approval requested",
140 tone: "approval",
141 payload: {
142 requestId: "req-dynamic-tool",
143 requestType: "dynamic_tool_call",
144 detail: "Search the web",
145 },
146 }),
147 ];
148
149 expect(derivePendingApprovals(activities)).toEqual([
150 {
151 requestId: "req-dynamic-tool",
152 requestKind: "command",
153 createdAt: "2026-02-23T00:00:01.000Z",
154 detail: "Search the web",
155 },
156 ]);
157 });
158
133 it("clears stale pending approvals when provider reports unknown pending request", () => {159 it("clears stale pending approvals when provider reports unknown pending request", () => {
134 const activities: OrchestrationThreadActivity[] = [160 const activities: OrchestrationThreadActivity[] = [
135 makeActivity({161 makeActivity({