|
|
@@ -7,6 +7,7 @@ import {
|
|
|
For,
|
|
|
Match,
|
|
|
on,
|
|
|
+ onMount,
|
|
|
Show,
|
|
|
Switch,
|
|
|
useContext,
|
|
|
@@ -1323,6 +1324,8 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
|
|
|
return props.message.time.completed - user.time.created
|
|
|
})
|
|
|
|
|
|
+ const keybind = useKeybind()
|
|
|
+
|
|
|
return (
|
|
|
<>
|
|
|
<For each={props.parts}>
|
|
|
@@ -1340,6 +1343,14 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
|
|
|
)
|
|
|
}}
|
|
|
</For>
|
|
|
+ <Show when={props.parts.some((x) => x.type === "tool" && x.tool === "task")}>
|
|
|
+ <box paddingTop={1} paddingLeft={3}>
|
|
|
+ <text fg={theme.text}>
|
|
|
+ {keybind.print("session_child_first")}
|
|
|
+ <span style={{ fg: theme.textMuted }}> view subagents</span>
|
|
|
+ </text>
|
|
|
+ </box>
|
|
|
+ </Show>
|
|
|
<Show when={props.message.error && props.message.error.name !== "MessageAbortedError"}>
|
|
|
<box
|
|
|
border={["left"]}
|
|
|
@@ -1609,6 +1620,7 @@ function InlineTool(props: {
|
|
|
iconColor?: RGBA
|
|
|
complete: any
|
|
|
pending: string
|
|
|
+ spinner?: boolean
|
|
|
children: JSX.Element
|
|
|
part: ToolPart
|
|
|
}) {
|
|
|
@@ -1665,11 +1677,18 @@ function InlineTool(props: {
|
|
|
}
|
|
|
}}
|
|
|
>
|
|
|
- <text paddingLeft={3} fg={fg()} attributes={denied() ? TextAttributes.STRIKETHROUGH : undefined}>
|
|
|
- <Show fallback={<>~ {props.pending}</>} when={props.complete}>
|
|
|
- <span style={{ fg: props.iconColor }}>{props.icon}</span> {props.children}
|
|
|
- </Show>
|
|
|
- </text>
|
|
|
+ <Switch>
|
|
|
+ <Match when={props.spinner}>
|
|
|
+ <Spinner color={fg()} children={props.children} />
|
|
|
+ </Match>
|
|
|
+ <Match when={true}>
|
|
|
+ <text paddingLeft={3} fg={fg()} attributes={denied() ? TextAttributes.STRIKETHROUGH : undefined}>
|
|
|
+ <Show fallback={<>~ {props.pending}</>} when={props.complete}>
|
|
|
+ <span style={{ fg: props.iconColor }}>{props.icon}</span> {props.children}
|
|
|
+ </Show>
|
|
|
+ </text>
|
|
|
+ </Match>
|
|
|
+ </Switch>
|
|
|
<Show when={error() && !denied()}>
|
|
|
<text fg={theme.error}>{error()}</text>
|
|
|
</Show>
|
|
|
@@ -1836,6 +1855,7 @@ function Glob(props: ToolProps<typeof GlobTool>) {
|
|
|
|
|
|
function Read(props: ToolProps<typeof ReadTool>) {
|
|
|
const { theme } = useTheme()
|
|
|
+ const isRunning = createMemo(() => props.part.state.status === "running")
|
|
|
const loaded = createMemo(() => {
|
|
|
if (props.part.state.status !== "completed") return []
|
|
|
if (props.part.state.time.compacted) return []
|
|
|
@@ -1845,7 +1865,13 @@ function Read(props: ToolProps<typeof ReadTool>) {
|
|
|
})
|
|
|
return (
|
|
|
<>
|
|
|
- <InlineTool icon="→" pending="Reading file..." complete={props.input.filePath} part={props.part}>
|
|
|
+ <InlineTool
|
|
|
+ icon="→"
|
|
|
+ pending="Reading file..."
|
|
|
+ complete={props.input.filePath}
|
|
|
+ spinner={isRunning()}
|
|
|
+ part={props.part}
|
|
|
+ >
|
|
|
Read {normalizePath(props.input.filePath!)} {input(props.input, ["filePath"])}
|
|
|
</InlineTool>
|
|
|
<For each={loaded()}>
|
|
|
@@ -1921,62 +1947,60 @@ function Task(props: ToolProps<typeof TaskTool>) {
|
|
|
const local = useLocal()
|
|
|
const sync = useSync()
|
|
|
|
|
|
+ onMount(() => {
|
|
|
+ if (props.metadata.sessionId && !sync.data.message[props.metadata.sessionId]?.length)
|
|
|
+ sync.session.sync(props.metadata.sessionId)
|
|
|
+ })
|
|
|
+
|
|
|
+ const messages = createMemo(() => sync.data.message[props.metadata.sessionId ?? ""] ?? [])
|
|
|
+
|
|
|
const tools = createMemo(() => {
|
|
|
- const sessionID = props.metadata.sessionId
|
|
|
- const msgs = sync.data.message[sessionID ?? ""] ?? []
|
|
|
- return msgs.flatMap((msg) =>
|
|
|
+ return messages().flatMap((msg) =>
|
|
|
(sync.data.part[msg.id] ?? [])
|
|
|
.filter((part): part is ToolPart => part.type === "tool")
|
|
|
.map((part) => ({ tool: part.tool, state: part.state })),
|
|
|
)
|
|
|
})
|
|
|
|
|
|
- const current = createMemo(() => tools().findLast((x) => x.state.status !== "pending"))
|
|
|
+ const current = createMemo(() => tools().findLast((x) => (x.state as any).title))
|
|
|
|
|
|
const isRunning = createMemo(() => props.part.state.status === "running")
|
|
|
|
|
|
+ const duration = createMemo(() => {
|
|
|
+ const first = messages().find((x) => x.role === "user")?.time.created
|
|
|
+ const assistant = messages().findLast((x) => x.role === "assistant")?.time.completed
|
|
|
+ if (!first || !assistant) return 0
|
|
|
+ return assistant - first
|
|
|
+ })
|
|
|
+
|
|
|
return (
|
|
|
- <Switch>
|
|
|
- <Match when={props.input.description || props.input.subagent_type}>
|
|
|
- <BlockTool
|
|
|
- title={"# " + Locale.titlecase(props.input.subagent_type ?? "unknown") + " Task"}
|
|
|
- onClick={
|
|
|
- props.metadata.sessionId
|
|
|
- ? () => navigate({ type: "session", sessionID: props.metadata.sessionId! })
|
|
|
- : undefined
|
|
|
- }
|
|
|
- part={props.part}
|
|
|
- spinner={isRunning()}
|
|
|
- >
|
|
|
- <box>
|
|
|
- <text style={{ fg: theme.textMuted }}>
|
|
|
- {props.input.description} ({tools().length} toolcalls)
|
|
|
- </text>
|
|
|
- <Show when={current()}>
|
|
|
- {(item) => {
|
|
|
- const title = item().state.status === "completed" ? (item().state as any).title : ""
|
|
|
- return (
|
|
|
- <text style={{ fg: item().state.status === "error" ? theme.error : theme.textMuted }}>
|
|
|
- └ {Locale.titlecase(item().tool)} {title}
|
|
|
- </text>
|
|
|
- )
|
|
|
- }}
|
|
|
- </Show>
|
|
|
- </box>
|
|
|
- <Show when={props.metadata.sessionId}>
|
|
|
- <text fg={theme.text}>
|
|
|
- {keybind.print("session_child_first")}
|
|
|
- <span style={{ fg: theme.textMuted }}> view subagents</span>
|
|
|
- </text>
|
|
|
- </Show>
|
|
|
- </BlockTool>
|
|
|
- </Match>
|
|
|
- <Match when={true}>
|
|
|
- <InlineTool icon="#" pending="Delegating..." complete={props.input.subagent_type} part={props.part}>
|
|
|
- {props.input.subagent_type} Task {props.input.description}
|
|
|
- </InlineTool>
|
|
|
- </Match>
|
|
|
- </Switch>
|
|
|
+ <InlineTool
|
|
|
+ icon="≡"
|
|
|
+ spinner={isRunning()}
|
|
|
+ complete={props.input.description}
|
|
|
+ pending="Delegating..."
|
|
|
+ part={props.part}
|
|
|
+ >
|
|
|
+ {props.input.description}
|
|
|
+ <Show when={isRunning() && tools().length > 0}>
|
|
|
+ {" "}
|
|
|
+ · {tools().length} toolcalls
|
|
|
+ <Show fallback={"\n└ Running..."} when={current()}>
|
|
|
+ {(item) => {
|
|
|
+ const title = createMemo(() => (item().state as any).title)
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ {"\n"}└ {Locale.titlecase(item().tool)} {title()}
|
|
|
+ </>
|
|
|
+ )
|
|
|
+ }}
|
|
|
+ </Show>
|
|
|
+ </Show>
|
|
|
+ <Show when={duration() && props.part.state.status === "completed"}>
|
|
|
+ {"\n "}
|
|
|
+ {tools().length} toolcalls · {Locale.duration(duration())}
|
|
|
+ </Show>
|
|
|
+ </InlineTool>
|
|
|
)
|
|
|
}
|
|
|
|