From 447713244820e875e192a5815c4d8bc76c03b40f Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 25 Nov 2025 16:02:27 -0600 Subject: fix: sanitize absolute paths --- packages/ui/src/components/message-part.css | 11 ++++ packages/ui/src/components/message-part.tsx | 73 +++++++++++++++---------- packages/ui/src/components/message-progress.tsx | 3 +- packages/ui/src/components/session-turn.tsx | 6 +- packages/ui/src/context/data.tsx | 4 +- 5 files changed, 63 insertions(+), 34 deletions(-) (limited to 'packages/ui/src') diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index dd6166112..0b1e8d490 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -63,6 +63,17 @@ [data-component="tool-output"] { white-space: pre; + padding: 8px 12px; + height: fit-content; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; + + pre { + margin: 0; + padding: 0; + } } [data-component="edit-trigger"], diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index dd920f101..40740fa1f 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -16,35 +16,26 @@ import { Checkbox } from "./checkbox" import { Diff } from "./diff" import { DiffChanges } from "./diff-changes" import { Markdown } from "./markdown" +import { getDirectory, getFilename } from "@opencode-ai/util/path" +import { sanitize, sanitizePart } from "@opencode-ai/util/sanitize" export interface MessageProps { message: MessageType parts: PartType[] + sanitize?: RegExp } export interface MessagePartProps { part: PartType message: MessageType hideDetails?: boolean + sanitize?: RegExp } export type PartComponent = Component export const PART_MAPPING: Record = {} -function getFilename(path: string) { - if (!path) return "" - const trimmed = path.replace(/[\/]+$/, "") - const parts = trimmed.split("/") - return parts[parts.length - 1] ?? "" -} - -function getDirectory(path: string) { - const parts = path.split("/") - const dir = parts.slice(0, parts.length - 1).join("/") - return dir ? dir + "/" : "" -} - export function registerPartComponent(type: string, component: PartComponent) { PART_MAPPING[type] = component } @@ -57,21 +48,27 @@ export function Message(props: MessageProps) { {(assistantMessage) => ( - + )} ) } -export function AssistantMessageDisplay(props: { message: AssistantMessage; parts: PartType[] }) { +export function AssistantMessageDisplay(props: { message: AssistantMessage; parts: PartType[]; sanitize?: RegExp }) { const filteredParts = createMemo(() => { return props.parts?.filter((x) => { if (x.type === "reasoning") return false return x.type !== "tool" || (x as ToolPart).tool !== "todoread" }) }) - return {(part) => } + return ( + {(part) => } + ) } export function UserMessageDisplay(props: { message: UserMessage; parts: PartType[] }) { @@ -88,7 +85,13 @@ export function Part(props: MessagePartProps) { const component = createMemo(() => PART_MAPPING[props.part.type]) return ( - + ) } @@ -99,6 +102,7 @@ export interface ToolProps { tool: string output?: string hideDetails?: boolean + sanitize?: RegExp } export type ToolComponent = Component @@ -166,6 +170,7 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) { metadata={metadata} output={part.state.status === "completed" ? part.state.output : undefined} hideDetails={props.hideDetails} + sanitize={props.sanitize} /> @@ -177,10 +182,11 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) { PART_MAPPING["text"] = function TextPartDisplay(props) { const part = props.part as TextPart + const sanitized = createMemo(() => (props.sanitize ? (sanitizePart(part, props.sanitize) as TextPart) : part)) return (
- +
) @@ -205,7 +211,7 @@ ToolRegistry.register({ icon="glasses" trigger={{ title: "Read", - subtitle: props.input.filePath ? getFilename(props.input.filePath) : "", + subtitle: props.input.filePath ? getFilename(sanitize(props.input.filePath, props.sanitize)) : "", }} /> ) @@ -216,9 +222,12 @@ ToolRegistry.register({ name: "list", render(props) { return ( - + -
{props.output}
+
{sanitize(props.output, props.sanitize)}
) @@ -321,12 +330,14 @@ ToolRegistry.register({ icon="console" trigger={{ title: "Shell", - subtitle: "Ran " + props.input.command, + subtitle: props.input.description, }} > - -
{props.output}
-
+
+ +
) }, @@ -344,9 +355,13 @@ ToolRegistry.register({
Edit
- {getDirectory(props.input.filePath!)} + + {getDirectory(sanitize(props.input.filePath!, props.sanitize))} + - {getFilename(props.input.filePath ?? "")} + + {getFilename(sanitize(props.input.filePath ?? "", props.sanitize))} +
@@ -361,11 +376,11 @@ ToolRegistry.register({
diff --git a/packages/ui/src/components/message-progress.tsx b/packages/ui/src/components/message-progress.tsx index ca42d26ec..adb245ab4 100644 --- a/packages/ui/src/components/message-progress.tsx +++ b/packages/ui/src/components/message-progress.tsx @@ -6,6 +6,7 @@ import type { AssistantMessage as AssistantMessageType, ToolPart } from "@openco export function MessageProgress(props: { assistantMessages: () => AssistantMessageType[]; done?: boolean }) { const data = useData() + const sanitizer = createMemo(() => (data.directory ? new RegExp(`${data.directory}/`, "g") : undefined)) const parts = createMemo(() => props.assistantMessages().flatMap((m) => data.part[m.id])) const done = createMemo(() => props.done ?? false) const currentTask = createMemo( @@ -152,7 +153,7 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa ) return (
- +
) }} diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index a7bd456a4..d146bae95 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -31,6 +31,7 @@ export function SessionTurn( const match = Binary.search(data.session, props.sessionID, (s) => s.id) if (!match.found) throw new Error(`Session ${props.sessionID} not found`) + const sanitizer = createMemo(() => (data.directory ? new RegExp(`${data.directory}/`, "g") : undefined)) const messages = createMemo(() => (props.sessionID ? (data.message[props.sessionID] ?? []) : [])) const userMessages = createMemo(() => messages() @@ -116,7 +117,7 @@ export function SessionTurn(
- +
{/* Summary */} @@ -222,10 +223,11 @@ export function SessionTurn( p?.id !== last()?.id)} + sanitize={sanitizer()} /> ) } - return + return }} diff --git a/packages/ui/src/context/data.tsx b/packages/ui/src/context/data.tsx index c2766a5af..ad5a212ab 100644 --- a/packages/ui/src/context/data.tsx +++ b/packages/ui/src/context/data.tsx @@ -23,7 +23,7 @@ type Data = { export const { use: useData, provider: DataProvider } = createSimpleContext({ name: "Data", - init: (props: { data: Data }) => { - return props.data + init: (props: { data: Data; directory: string }) => { + return { ...props.data, directory: props.directory } }, }) -- cgit v1.2.3