| author | |
| committer | |
| log | a135f2c2d32bcded09fca32d4147c06df90ec84d |
| tree | 5e77c7ec91857409fc0d5788e520d57c84f3f678 |
| parent | 946b867666be94482e9e41015a304f55fe1e4754 |
| signature | Signed by PGP key B5690EEEBB952194 |
4 files changed, 81 insertions(+), 2 deletions(-)
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.approval.test.ts created+33| ... | ... | @@ -0,0 +1,33 @@ |
| 1 | import { | |
| 2 | EventId, | |
| 3 | ProviderDriverKind, | |
| 4 | RuntimeRequestId, | |
| 5 | ThreadId, | |
| 6 | type ProviderRuntimeEvent, | |
| 7 | } from "@t3tools/contracts"; | |
| 8 | import { describe, expect, it } from "vite-plus/test"; | |
| 9 | ||
| 10 | import { runtimeEventToActivities } from "./ProviderRuntimeIngestion.ts"; | |
| 11 | ||
| 12 | describe("runtimeEventToActivities approval details", () => { | |
| 13 | it("preserves complete multiline command details", () => { | |
| 14 | const detail = `bun run release -- ${"long-argument ".repeat(20)}\nsecond line`; | |
| 15 | const event = { | |
| 16 | type: "request.opened", | |
| 17 | eventId: EventId.make("evt-request-opened"), | |
| 18 | provider: ProviderDriverKind.make("codex"), | |
| 19 | createdAt: "2026-07-18T00:00:00.000Z", | |
| 20 | threadId: ThreadId.make("thread-1"), | |
| 21 | requestId: RuntimeRequestId.make("approval-1"), | |
| 22 | payload: { | |
| 23 | requestType: "command_execution_approval", | |
| 24 | detail, | |
| 25 | }, | |
| 26 | } satisfies ProviderRuntimeEvent; | |
| 27 | ||
| 28 | const [activity] = runtimeEventToActivities(event); | |
| 29 | ||
| 30 | expect(activity?.kind).toBe("approval.requested"); | |
| 31 | expect((activity?.payload as Record<string, unknown> | undefined)?.detail).toBe(detail); | |
| 32 | }); | |
| 33 | }); |
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts+2-2| ... | ... | @@ -306,7 +306,7 @@ function requestKindFromCanonicalRequestType( |
| 306 | 306 | } |
| 307 | 307 | } |
| 308 | 308 | |
| 309 | function runtimeEventToActivities( | |
| 309 | export function runtimeEventToActivities( | |
| 310 | 310 | event: ProviderRuntimeEvent, |
| 311 | 311 | taskTitle?: string, |
| 312 | 312 | ): ReadonlyArray<OrchestrationThreadActivity> { |
| ... | ... | @@ -340,7 +340,7 @@ function runtimeEventToActivities( |
| 340 | 340 | requestId: toApprovalRequestId(event.requestId), |
| 341 | 341 | ...(requestKind ? { requestKind } : {}), |
| 342 | 342 | requestType: event.payload.requestType, |
| 343 | ...(event.payload.detail ? { detail: truncateDetail(event.payload.detail) } : {}), | |
| 343 | ...(event.payload.detail ? { detail: event.payload.detail } : {}), | |
| 344 | 344 | }, |
| 345 | 345 | turnId: toTurnId(event.turnId) ?? null, |
| 346 | 346 | ...maybeSequence, |
apps/web/src/components/chat/ComposerPendingApprovalPanel.test.tsx created+28| ... | ... | @@ -0,0 +1,28 @@ |
| 1 | import { ApprovalRequestId } from "@t3tools/contracts"; | |
| 2 | import { renderToStaticMarkup } from "react-dom/server"; | |
| 3 | import { describe, expect, it } from "vite-plus/test"; | |
| 4 | ||
| 5 | import { ComposerPendingApprovalPanel } from "./ComposerPendingApprovalPanel"; | |
| 6 | ||
| 7 | describe("ComposerPendingApprovalPanel", () => { | |
| 8 | it("renders complete multiline command details without hover or truncation", () => { | |
| 9 | const detail = `bun run release -- ${"long-argument ".repeat(20)}\nsecond line`; | |
| 10 | const markup = renderToStaticMarkup( | |
| 11 | <ComposerPendingApprovalPanel | |
| 12 | approval={{ | |
| 13 | requestId: ApprovalRequestId.make("approval-1"), | |
| 14 | requestKind: "command", | |
| 15 | createdAt: "2026-07-18T00:00:00.000Z", | |
| 16 | detail, | |
| 17 | }} | |
| 18 | pendingCount={1} | |
| 19 | />, | |
| 20 | ); | |
| 21 | ||
| 22 | expect(markup).toContain('data-approval-detail="complete"'); | |
| 23 | expect(markup).toContain('aria-label="Command"'); | |
| 24 | expect(markup).toContain(detail); | |
| 25 | expect(markup).not.toContain("truncate"); | |
| 26 | expect(markup).not.toContain("line-clamp"); | |
| 27 | }); | |
| 28 | }); |
apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx+18| ... | ... | @@ -16,6 +16,12 @@ export const ComposerPendingApprovalPanel = memo(function ComposerPendingApprova |
| 16 | 16 | : approval.requestKind === "file-read" |
| 17 | 17 | ? "File-read approval requested" |
| 18 | 18 | : "File-change approval requested"; |
| 19 | const detailLabel = | |
| 20 | approval.requestKind === "command" | |
| 21 | ? "Command" | |
| 22 | : approval.requestKind === "file-read" | |
| 23 | ? "File to read" | |
| 24 | : "File change"; | |
| 19 | 25 | |
| 20 | 26 | return ( |
| 21 | 27 | <div className="px-4 py-3.5 sm:px-5 sm:py-4"> |
| ... | ... | @@ -26,6 +32,18 @@ export const ComposerPendingApprovalPanel = memo(function ComposerPendingApprova |
| 26 | 32 | <span className="text-xs text-muted-foreground">1/{pendingCount}</span> |
| 27 | 33 | ) : null} |
| 28 | 34 | </div> |
| 35 | {approval.detail ? ( | |
| 36 | <div className="mt-3 rounded-lg border border-border/65 bg-background/70 p-3"> | |
| 37 | <p className="text-xs font-medium text-muted-foreground">{detailLabel}</p> | |
| 38 | <pre | |
| 39 | aria-label={detailLabel} | |
| 40 | className="mt-2 max-h-40 overflow-auto whitespace-pre-wrap break-words font-mono text-xs leading-relaxed text-foreground" | |
| 41 | data-approval-detail="complete" | |
| 42 | > | |
| 43 | {approval.detail} | |
| 44 | </pre> | |
| 45 | </div> | |
| 46 | ) : null} | |
| 29 | 47 | </div> |
| 30 | 48 | ); |
| 31 | 49 | }); |