| 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,7 +306,7 @@ function requestKindFromCanonicalRequestType( |
| 306 | } | 306 | } |
| 307 | } | 307 | } |
| 308 | 308 | ||
| 309 | function runtimeEventToActivities( | 309 | export function runtimeEventToActivities( |
| 310 | event: ProviderRuntimeEvent, | 310 | event: ProviderRuntimeEvent, |
| 311 | taskTitle?: string, | 311 | taskTitle?: string, |
| 312 | ): ReadonlyArray<OrchestrationThreadActivity> { | 312 | ): ReadonlyArray<OrchestrationThreadActivity> { |
| ... | @@ -340,7 +340,7 @@ function runtimeEventToActivities( | ... | @@ -340,7 +340,7 @@ function runtimeEventToActivities( |
| 340 | requestId: toApprovalRequestId(event.requestId), | 340 | requestId: toApprovalRequestId(event.requestId), |
| 341 | ...(requestKind ? { requestKind } : {}), | 341 | ...(requestKind ? { requestKind } : {}), |
| 342 | requestType: event.payload.requestType, | 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 | turnId: toTurnId(event.turnId) ?? null, | 345 | turnId: toTurnId(event.turnId) ?? null, |
| 346 | ...maybeSequence, | 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,6 +16,12 @@ export const ComposerPendingApprovalPanel = memo(function ComposerPendingApprova |
| 16 | : approval.requestKind === "file-read" | 16 | : approval.requestKind === "file-read" |
| 17 | ? "File-read approval requested" | 17 | ? "File-read approval requested" |
| 18 | : "File-change approval requested"; | 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 | return ( | 26 | return ( |
| 21 | <div className="px-4 py-3.5 sm:px-5 sm:py-4"> | 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,6 +32,18 @@ export const ComposerPendingApprovalPanel = memo(function ComposerPendingApprova |
| 26 | <span className="text-xs text-muted-foreground">1/{pendingCount}</span> | 32 | <span className="text-xs text-muted-foreground">1/{pendingCount}</span> |
| 27 | ) : null} | 33 | ) : null} |
| 28 | </div> | 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 | </div> | 47 | </div> |
| 30 | ); | 48 | ); |
| 31 | }); | 49 | }); |