Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 29 additions & 8 deletions web/oss/src/components/AgentChatSlice/assets/markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,35 @@ const MD_COMPONENTS = {code: CodeBlock, pre: PreUnwrap}
* (the streaming one), its already-settled parts — a reasoning block, text before a tool call —
* keep the same `content` string, so this skips re-parsing + re-running Prism on them each token.
* (Settled messages don't re-render at all; the stable-`onRewind` fix handles those.) */
// Anchor component ensures all markdown-rendered links open in a new tab safely.
// Only forward real anchor attributes — XMarkdown/html-react-parser also pass internal
// props (`domNode`, `node`, `streamStatus`, …) that would leak onto the DOM element.
const Anchor = ({href, children, title, className}: any) => (
<a href={href} title={title} className={className} target="_blank" rel="noopener noreferrer">
{children}
</a>
)
/** A link target that must stay a plain external link: any `scheme:` URL (http, https, mailto, tel,
* data, …), a protocol-relative `//host`, or an in-page `#fragment`. Everything else is a RELATIVE
* path, which might name a file in this conversation's drive. */
const isExternalHref = (href?: string): boolean =>
!href || /^([a-z][a-z0-9+.-]*:|\/\/|#)/i.test(href)

// Anchor: real URLs open in a new tab (as before). A RELATIVE path that names a file in this
// conversation's drive resolves to the SAME in-Files chip as an inline-code mention — issue #5481:
// nested / `NN-name/` paths get emitted as markdown links, which bypassed the file resolver and were
// forced to a new tab; now they route through the same resolver (`renderCode`), and anything it can't
// confirm as a file falls back to this plain link (label preserved). Only forward real anchor attrs —
// XMarkdown/html-react-parser also pass internal props (`domNode`, `node`, `streamStatus`, …).
const Anchor = ({href, children, title, className}: any) => {
const sessionId = useDriveSessionId()
const link = useAtomValue(chatFileLinkAtomFamily(sessionId ?? ""))
const external = (
<a
href={href}
title={title}
className={className}
target="_blank"
rel="noopener noreferrer"
>
{children}
</a>
)
if (link && !isExternalHref(href)) return <>{link.renderCode(href, external)}</>
return external
}

const Markdown = ({content, className}: {content: string; className?: string}) => (
<XMarkdown
Expand Down
10 changes: 8 additions & 2 deletions web/oss/src/components/Drives/useSessionDrive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ export const fileOrigin = (path: string): FileOrigin => {
return rel === AGENT_FILES_DIR || rel.startsWith(`${AGENT_FILES_DIR}/`) ? "agent" : "session"
}

/** The bare `agent-files` entry at the cwd top level is the fold-point SYMLINK to the agent mount,
* not a real file — its content is surfaced folded under `agent-files/` (full drive) or counted
* (summary). So the symlink row itself must never appear in a file/recents list; only its contents,
* carried via the agent mount, do. One predicate so every list drops it the same way. */
export const isAgentFilesFold = (path: string): boolean => cleanPath(path) === AGENT_FILES_DIR

/** True when a listing holds BOTH agent and session files — the only time the origin tags/filter
* carry information (a single-origin drive doesn't need them). */
export const driveHasMixedOrigins = (files: {path: string}[]): boolean => {
Expand Down Expand Up @@ -147,7 +153,7 @@ export function useSessionDrive(
const structural = useMemo(() => {
const listing = filesQuery.data ?? null
const cwdStats = driveFileStats(listing)
const cwdFiles = cwdStats.files.filter((f) => cleanPath(f.path) !== AGENT_FILES_DIR)
const cwdFiles = cwdStats.files.filter((f) => !isAgentFilesFold(f.path))

// Agent-mount files, presented under `agent-files/` so they read as a subfolder of cwd.
const agentListing = agentFilesQuery.data ?? null
Expand Down Expand Up @@ -368,7 +374,7 @@ export function useSessionDriveSummary(sessionId: string, artifactId?: string):
// Newest write/edit per path (the map already dedups by path, keeping the latest timestamp).
const recordRecents: DriveRecentFile[] = [...recordRecency.entries()]
.map(([toolPath, at]) => ({path: cleanPath(toolPath), touchedAt: at}))
.filter((f) => f.path && !isInternalDrivePath(f.path))
.filter((f) => f.path && !isInternalDrivePath(f.path) && !isAgentFilesFold(f.path))
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
.sort((a, b) =>
b.touchedAt !== a.touchedAt
? b.touchedAt - a.touchedAt
Expand Down
Loading