History

[codex] show complete approval details (#4111)

Maxwell Young committed 3 months ago

Commit a135f2c1 parent 946b8674 files · +81 −2Browse files

Unified · Split

4 files
  1. apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.approval.test.ts +33 −0
  2. apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts +2 −2
  3. apps/web/src/components/chat/ComposerPendingApprovalPanel.test.tsx +28 −0
  4. apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx +18 −0
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.approval.test.ts+33−0
@@ -0,0 +1,33 @@
1import {
2 EventId,
3 ProviderDriverKind,
4 RuntimeRequestId,
5 ThreadId,
6 type ProviderRuntimeEvent,
7} from "@t3tools/contracts";
8import { describe, expect, it } from "vite-plus/test";
9
10import { runtimeEventToActivities } from "./ProviderRuntimeIngestion.ts";
11
12describe("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( · 305 unmodified lines
306 }306 }
307}307}
308 308
309function runtimeEventToActivities(309export 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( · 27 unmodified lines
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+28−0
@@ -0,0 +1,28 @@
1import { ApprovalRequestId } from "@t3tools/contracts";
2import { renderToStaticMarkup } from "react-dom/server";
3import { describe, expect, it } from "vite-plus/test";
4
5import { ComposerPendingApprovalPanel } from "./ComposerPendingApprovalPanel";
6
7describe("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−0
@@ -16,6 +16,12 @@ export const ComposerPendingApprovalPanel = memo(function ComposerPendingApprova · 15 unmodified lines
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 · 4 unmodified lines
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});