| author | |
| committer | |
| log | 99d91ddaa4996203b675cbd086d5acaa642d4827 |
| tree | 5b62db770d476419b9e509c9f0d99b3f49879b74 |
| parent | ab3b55e29ac20a6212f09e38f1983b856fd77695 |
| signature | Signed by PGP key B5690EEEBB952194 |
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 | }); |
| 70 | 70 | ||
| 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 { |
| 26 | 26 | ||
| 27 | type AcpCanonicalRequestType = Extract< | 27 | type 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 | >; |
| 31 | 31 | ||
| 32 | function canonicalRequestTypeFromAcpKind(kind: string | "unknown"): AcpCanonicalRequestType { | 32 | function 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 | } |
| 46 | 46 |
apps/web/src/session-logic.test.ts+26| ... | @@ -130,6 +130,32 @@ describe("derivePendingApprovals", () => { | ... | @@ -130,6 +130,32 @@ describe("derivePendingApprovals", () => { |
| 130 | ]); | 130 | ]); |
| 131 | }); | 131 | }); |
| 132 | 132 | ||
| 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({ |