chore: v2.8.13 — Artifacts reinstall/edit/installs, agent steps in chat, pick a local file to attach, Windows paths as links

- Chat: an Agent step shows its task, status and tool calls and opens its conversation in place; Windows paths (E:\…, C:/…) in replies open as files
- Prompt: attach a file on this computer by path (native picker), clearer too-large messages
- Artifacts: edit title/description and a skill's files (new snapshot), Reinstall, install history, Hub-only rows, skills installed into ~/.claude/skills and ~/.agents/skills
- Files: batch upload of small files in one extract
- AI Tools: npm install falls back to --prefix ~/.local when the global prefix is not writable
This commit is contained in:
2026-10-20 16:44:55 +07:00
parent 49f87c4da3
commit 0547488f13
59 changed files with 4285 additions and 690 deletions
@@ -73,6 +73,7 @@ import { api } from "../../lib/api";
import { eventBus } from "../../lib/eventBus";
import { isRemoteDataRefreshMessage } from "../../lib/remoteDataEvents";
import { MessageList, promptTokens } from "./MessageList";
import { agentNotices, type AgentLinks, type AgentRef } from "./AgentDetails";
import { useSpin } from "../../hooks/useSpin";
import { useOnScreen, isShown } from "../../hooks/useOnScreen";
import { throttle } from "../../lib/throttle";
@@ -102,6 +103,9 @@ interface ConversationViewProps {
/** Fill the parent as a chat (a workspace's session view): no toolbar, no
* page-height cap — the parent sizes it and puts a composer under it. */
fill?: boolean;
/** A subagent's conversation inside its step: no toolbar, as tall as its
* content up to 24rem, then it scrolls. */
compact?: boolean;
/** Opens a file named in a turn's "Edited N files" card. */
onOpenFile?: (path: string) => void;
/** The session runs in a pane: show the step in progress. */
@@ -127,6 +131,7 @@ function ConversationViewPane({
sessionId,
initialTranscriptId,
fill = false,
compact = false,
onOpenFile,
live = false,
follow = 0,
@@ -186,6 +191,27 @@ function ConversationViewPane({
// Refresh-button spinner state - separate from initial `loading` so the
// existing skeleton doesn't blink during a manual refresh.
// A subagent's own conversation opens inside its step as a small chat: this
// same view, on that agent's transcript.
const renderAgent = useCallback(
(a: AgentRef) => (
<ConversationView
key={a.agentId}
sessionId={sessionId}
initialTranscriptId={a.agentId}
compact
live={live && a.status === "running"}
ended={a.status !== "running"}
/>
),
[sessionId, live]
);
const agentLinks = useMemo<AgentLinks>(() => {
const ids = new Map<string, string>();
for (const tr of transcripts) if (tr.tool_use_id) ids.set(tr.tool_use_id, tr.id);
return { ids, notices: agentNotices(messages), live, render: renderAgent };
}, [transcripts, messages, live, renderAgent]);
// Load available transcript list (also rescanned on a short interval so
// newly-spawned subagents appear in the dropdown without a page reload).
useEffect(() => {
@@ -518,7 +544,7 @@ function ConversationViewPane({
<div className={`relative flex flex-col ${fill ? "h-full" : ""}`} style={{ minHeight: 0 }}>
{/* Toolbar - always rendered after the initial load so users can
refresh even when no messages have streamed yet. */}
{!loading && !fill && (
{!loading && !fill && !compact && (
<div className="flex items-center gap-3 mb-3 flex-shrink-0">
{transcripts.length > 1 && (
<div className="relative">
@@ -566,7 +592,13 @@ function ConversationViewPane({
ref={scrollContainerRef}
onScroll={handleScroll}
className="flex-1 overflow-y-auto [scrollbar-gutter:stable]"
style={fill ? { minHeight: 0 } : { maxHeight: "calc(100vh - 320px)", minHeight: 200 }}
style={
compact
? { maxHeight: "24rem" }
: fill
? { minHeight: 0 }
: { maxHeight: "calc(100vh - 320px)", minHeight: 200 }
}
>
{/* A chat keeps its first turn clear of the pane's top-right controls
(the outline's file button floats there). */}
@@ -589,7 +621,9 @@ function ConversationViewPane({
)}
{loading ? (
<div className="flex items-center justify-center py-12 text-fg-muted text-sm">
<div
className={`flex items-center justify-center text-fg-muted text-sm ${compact ? "py-3" : "py-12"}`}
>
{t("conversation.loading")}
</div>
) : messages.length === 0 && notStarted ? (
@@ -624,6 +658,8 @@ function ConversationViewPane({
waiting={waiting}
onOpenTerminal={onOpenTerminal}
stopped={stopped}
agentLinks={agentLinks}
compact={compact}
sessionId={
selectedTranscript && selectedTranscript !== "main" ? undefined : sessionId
}
@@ -653,7 +689,7 @@ function ConversationViewPane({
)}
{/* New messages indicator */}
{showNewMsg && !fill && (
{showNewMsg && !fill && !compact && (
<button
onClick={() => {
scrollToBottom();