Merge remote-tracking branch 'origin/main' into port-2736

# Conflicts:
#	packages/ui/src/components/chat/message/parts/ReasoningPart.tsx
This commit is contained in:
Bohdan Triapitsyn
2026-08-28 23:43:20 +03:00
1264 changed files with 107929 additions and 30300 deletions
@@ -54,12 +54,40 @@ Use this doc when you ask an agent to change tool/header/description behavior.
- Assistant markdown treats raw HTML as inert visible text. The final generated
HTML is sanitized as defense in depth, with script and style elements
forbidden, so message content cannot inject active DOM or application-wide
CSS into any runtime surface.
CSS into any runtime surface. Safe custom application links go through the
app-link confirmation flow in every supported renderer, including VS Code.
- Final assistant Markdown rendering is independent from image gallery
extraction: gallery presence never changes the chat body. Assistant image
syntax consistently renders as a shared image icon followed by its filename,
without loading the image in the body; tool and simple Markdown retain normal
inline image rendering. The gallery separately collects HTTP(S), embedded, and workspace-local
PNG/JPEG/GIF/WebP image candidates into one 100px thumbnail gallery in the
message-completion area after all message text and above the turn's changed
files. Each muted filename caption includes the shared image-file icon.
HTTP(S) images keep their browser URL. Embedded and workspace-local images
are limited to 10 MiB and validated as PNG/JPEG/GIF/WebP. Chat Markdown uses
the assistant image-label policy without gallery-specific link rewriting,
completion-state switching, or hidden placeholders. A
completed assistant message hydrates at most 12 unique image candidates,
including persisted text parts that omit their optional part-level end time.
In server-backed runtimes, a gallery approaching the viewport prepares all
local candidates in one message-level request, then reuses the authenticated
`/api/fs/raw` asset route. Each URL loads only when its thumbnail approaches
the viewport. VS Code instead loads workspace-contained images through its
local filesystem bridge and never calls the server grant route; OpenCode
temporary-directory images remain unsupported there. Mounted historical
messages therefore do not eagerly read every image.
Gallery clicks do not introduce or alter preview chrome: desktop and mobile
both reuse the pre-existing attachment image preview overlay.
Workspace-external images receive the existing path-bound `outsideFileGrant`
only when the server verifies the exact source in the owning assistant
message and the real file is inside OpenCode's dedicated temporary directory.
- `read` and `skill` are **static navigation tools** and render via `StaticToolRow`.
- Every other tool, including search/fetch, OpenCode built-ins, custom tools, plugins, and MCP tools, is **expandable** and renders through `ToolPart`.
- The managed `openchamber` plugin tool uses the expandable path and hides its broad protocol input. The plugin supplies the selected action's human description as the native tool title; the UI renders that metadata without owning an action map. The full versioned result envelope renders through the same neutral JSON summary/tree/raw views as other tools, without a tool-specific output card.
- `ToolPart` defers expanded content after a user toggle, preventing large tool input/output payloads from mounting during the initial chat render.
- Running bash output falls back to `state.metadata.output` until canonical `state.output` arrives. Its fixed-height output viewport follows new output until the user scrolls up, then resumes following when the user returns to the bottom. Live output appends or replaces rewritten snapshots as plain text without worker highlighting; finalized output normalizes ANSI terminal controls with a bounded synthetic-cell budget, bypasses the throttle, and receives the normal one-time highlighted rendering.
- The rich tool diff preview lives in `ToolPartDiffPreview.tsx` and is lazy-loaded from `ToolPart`. It is the only tool-card piece that imports the `@pierre/diffs` + Shiki rendering stack, keeping that stack out of the eager chat startup graph. While its chunk loads (first rendered diff only) the plain-text patch from `PlainDiffFallback.tsx` renders as the Suspense fallback, mirroring the preview's error fallback. `ToolPart` itself must not statically import `@pierre/diffs` runtime modules or `@/lib/shiki/appThemeRegistry`.
- Running bash output falls back to `state.metadata.output` until canonical `state.output` arrives. Its output viewport grows with the content up to `46vh`, then scrolls and follows new output until the user scrolls up; following resumes when the user returns to the bottom. Live output appends or replaces rewritten snapshots as plain text without worker highlighting; finalized output normalizes ANSI terminal controls with a bounded synthetic-cell budget, bypasses the throttle, and receives the normal one-time highlighted rendering.
- Thinking/Justification duration is hidden in `sorted` mode (handled in `ReasoningPart.tsx` + `JustificationBlock.tsx`).
- Reasoning streaming presentation derives from the live stream phase (`streaming`/`cooldown`), never from missing persisted timing: a cached part without `time.end` is not live, and a part whose `time.end` is set never streams (issue #2020).
@@ -94,7 +122,15 @@ Why: only navigation tools use the compact static path; all other tools need obs
## Quick map of files in this folder
- Text: `AssistantTextPart.tsx`, `UserTextPart.tsx`
- Tools: `ToolPart.tsx`, `ProgressiveGroup.tsx`, `toolPresentation.tsx`, `toolRenderUtils.ts`, `ToolRevealOnMount.tsx`
- User-attached context (inline code comments, terminal selections, browser
annotations, PR comments/checks): `UserContextPart.tsx`. `UserTextPart`
routes to it when the part's metadata carries an `openchamberContext`
payload (see `lib/messages/contextParts.ts`, which owns both the send-time
builder and the read-back parser). Linked GitHub issues/PRs are instead
converted to link file-parts in `normalizeUserDisplayParts.ts`. Legacy
pre-metadata messages still render via text sniffing (`<terminal_context>`
blocks, `GitHub issue context (JSON)` prefixes).
- Tools: `ToolPart.tsx`, `ToolPartDiffPreview.tsx`, `PlainDiffFallback.tsx`, `ProgressiveGroup.tsx`, `toolPresentation.tsx`, `toolRenderUtils.ts`, `ToolRevealOnMount.tsx`
- Reasoning/justification: `ReasoningPart.tsx`, `JustificationBlock.tsx`
- Status/placeholders: `WorkingPlaceholder.tsx`, `SessionActiveSpinner.tsx`, `MigratingPart.tsx`, `BusyDots.tsx`
- Utility renderers: `VirtualizedCodeBlock.tsx`, `MinDurationShineText.tsx`