feat: stream real-time progress for agent SDK install/upgrade/uninstall
Replace the spinner-only UX with live log output during agent SDK operations, matching the existing OpenCode plugin install experience. Backend: emit structured events (started/log/completed/failed) via EventEmitter during npm install and binary download. npm commands now run with piped stdio for line-by-line streaming; binary downloads report chunked progress every 1 MB. Frontend: subscribe to `app://agent-install` events through a new `useAgentInstallStream` hook and render a theme-aware log terminal below the preflight checks panel. Also fixes the install log container in both agent settings and the OpenCode plugins modal: auto-scroll no longer shifts the outer page, and colours now follow the active light/dark theme. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
74
src/hooks/use-agent-install-stream.ts
Normal file
74
src/hooks/use-agent-install-stream.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { useCallback, useRef, useState } from "react"
|
||||
import { subscribe } from "@/lib/platform"
|
||||
import type { AgentInstallEvent, AgentInstallEventKind } from "@/lib/types"
|
||||
|
||||
const AGENT_INSTALL_EVENT = "app://agent-install"
|
||||
|
||||
export type AgentInstallStatus = "idle" | "running" | "success" | "failed"
|
||||
|
||||
interface AgentInstallStreamState {
|
||||
status: AgentInstallStatus
|
||||
logs: string[]
|
||||
error: string | null
|
||||
}
|
||||
|
||||
export function useAgentInstallStream() {
|
||||
const [state, setState] = useState<AgentInstallStreamState>({
|
||||
status: "idle",
|
||||
logs: [],
|
||||
error: null,
|
||||
})
|
||||
const unsubRef = useRef<(() => void) | null>(null)
|
||||
|
||||
const start = useCallback(async (taskId: string) => {
|
||||
setState({ status: "running", logs: [], error: null })
|
||||
|
||||
unsubRef.current?.()
|
||||
|
||||
const unsub = await subscribe<AgentInstallEvent>(
|
||||
AGENT_INSTALL_EVENT,
|
||||
(event) => {
|
||||
if (event.task_id !== taskId) return
|
||||
|
||||
switch (event.kind as AgentInstallEventKind) {
|
||||
case "started":
|
||||
setState((prev) => ({ ...prev, status: "running" }))
|
||||
break
|
||||
case "log":
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
logs: [...prev.logs, event.payload],
|
||||
}))
|
||||
break
|
||||
case "completed":
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
status: "success",
|
||||
logs: [...prev.logs, event.payload],
|
||||
}))
|
||||
unsubRef.current?.()
|
||||
break
|
||||
case "failed":
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
status: "failed",
|
||||
error: event.payload,
|
||||
logs: [...prev.logs, `ERROR: ${event.payload}`],
|
||||
}))
|
||||
unsubRef.current?.()
|
||||
break
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
unsubRef.current = unsub
|
||||
}, [])
|
||||
|
||||
const reset = useCallback(() => {
|
||||
unsubRef.current?.()
|
||||
unsubRef.current = null
|
||||
setState({ status: "idle", logs: [], error: null })
|
||||
}, [])
|
||||
|
||||
return { ...state, start, reset }
|
||||
}
|
||||
Reference in New Issue
Block a user