diff --git a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.approval.test.ts b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.approval.test.ts new file mode 100644 index 00000000000..05370781c0d --- /dev/null +++ b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.approval.test.ts @@ -0,0 +1,33 @@ +import { + EventId, + ProviderDriverKind, + RuntimeRequestId, + ThreadId, + type ProviderRuntimeEvent, +} from "@t3tools/contracts"; +import { describe, expect, it } from "vite-plus/test"; + +import { runtimeEventToActivities } from "./ProviderRuntimeIngestion.ts"; + +describe("runtimeEventToActivities approval details", () => { + it("preserves complete multiline command details", () => { + const detail = `bun run release -- ${"long-argument ".repeat(20)}\nsecond line`; + const event = { + type: "request.opened", + eventId: EventId.make("evt-request-opened"), + provider: ProviderDriverKind.make("codex"), + createdAt: "2026-07-18T00:00:00.000Z", + threadId: ThreadId.make("thread-1"), + requestId: RuntimeRequestId.make("approval-1"), + payload: { + requestType: "command_execution_approval", + detail, + }, + } satisfies ProviderRuntimeEvent; + + const [activity] = runtimeEventToActivities(event); + + expect(activity?.kind).toBe("approval.requested"); + expect((activity?.payload as Record | undefined)?.detail).toBe(detail); + }); +}); diff --git a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts index 3e5978f4846..9d4ace94878 100644 --- a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts +++ b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts @@ -262,7 +262,7 @@ function requestKindFromCanonicalRequestType( } } -function runtimeEventToActivities( +export function runtimeEventToActivities( event: ProviderRuntimeEvent, ): ReadonlyArray { const maybeSequence = (() => { @@ -295,7 +295,7 @@ function runtimeEventToActivities( requestId: toApprovalRequestId(event.requestId), ...(requestKind ? { requestKind } : {}), requestType: event.payload.requestType, - ...(event.payload.detail ? { detail: truncateDetail(event.payload.detail) } : {}), + ...(event.payload.detail ? { detail: event.payload.detail } : {}), }, turnId: toTurnId(event.turnId) ?? null, ...maybeSequence, diff --git a/apps/web/src/components/chat/ComposerPendingApprovalPanel.test.tsx b/apps/web/src/components/chat/ComposerPendingApprovalPanel.test.tsx new file mode 100644 index 00000000000..fbaf81bc9fd --- /dev/null +++ b/apps/web/src/components/chat/ComposerPendingApprovalPanel.test.tsx @@ -0,0 +1,28 @@ +import { ApprovalRequestId } from "@t3tools/contracts"; +import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vite-plus/test"; + +import { ComposerPendingApprovalPanel } from "./ComposerPendingApprovalPanel"; + +describe("ComposerPendingApprovalPanel", () => { + it("renders complete multiline command details without hover or truncation", () => { + const detail = `bun run release -- ${"long-argument ".repeat(20)}\nsecond line`; + const markup = renderToStaticMarkup( + , + ); + + expect(markup).toContain('data-approval-detail="complete"'); + expect(markup).toContain('aria-label="Command"'); + expect(markup).toContain(detail); + expect(markup).not.toContain("truncate"); + expect(markup).not.toContain("line-clamp"); + }); +}); diff --git a/apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx b/apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx index 569fd108a4a..dd966bf5795 100644 --- a/apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx +++ b/apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx @@ -16,6 +16,12 @@ export const ComposerPendingApprovalPanel = memo(function ComposerPendingApprova : approval.requestKind === "file-read" ? "File-read approval requested" : "File-change approval requested"; + const detailLabel = + approval.requestKind === "command" + ? "Command" + : approval.requestKind === "file-read" + ? "File to read" + : "File change"; return (
@@ -26,6 +32,18 @@ export const ComposerPendingApprovalPanel = memo(function ComposerPendingApprova 1/{pendingCount} ) : null}
+ {approval.detail ? ( +
+

{detailLabel}

+
+            {approval.detail}
+          
+
+ ) : null} ); });