mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-10 04:48:32 +00:00
fix(tui): show compaction progress
This commit is contained in:
parent
9751615651
commit
858916f342
4 changed files with 128 additions and 3 deletions
|
|
@ -160,6 +160,7 @@ export function Prompt(props: PromptProps) {
|
|||
const dialog = useDialog()
|
||||
const toast = useToast()
|
||||
const status = createMemo(() => data.session.status(props.sessionID ?? ""))
|
||||
const compaction = createMemo(() => data.session.compaction.get(props.sessionID ?? ""))
|
||||
const activeSubagents = createMemo(() => {
|
||||
if (!props.sessionID) return 0
|
||||
return data.session.family(props.sessionID).filter(
|
||||
|
|
@ -1637,6 +1638,18 @@ export function Prompt(props: PromptProps) {
|
|||
</box>
|
||||
<box width="100%" flexDirection="row" justifyContent="space-between">
|
||||
<Switch>
|
||||
<Match when={compaction()}>
|
||||
<box flexDirection="row" gap={1} flexGrow={1} justifyContent="flex-start">
|
||||
<box marginLeft={1}>
|
||||
<Spinner>Compacting conversation...</Spinner>
|
||||
</box>
|
||||
<Show when={compaction() === "auto"}>
|
||||
<text fg={theme.text}>
|
||||
esc <span style={{ fg: theme.textMuted }}>interrupt</span>
|
||||
</text>
|
||||
</Show>
|
||||
</box>
|
||||
</Match>
|
||||
<Match when={status() === "running"}>
|
||||
<box flexDirection="row" gap={1} flexGrow={1} justifyContent="flex-start">
|
||||
<box marginLeft={1}>
|
||||
|
|
|
|||
|
|
@ -20,12 +20,13 @@ import type {
|
|||
SkillV2Info,
|
||||
V2Event,
|
||||
} from "@opencode-ai/sdk/v2"
|
||||
import { createStore, produce } from "solid-js/store"
|
||||
import { createStore, produce, reconcile } from "solid-js/store"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { useSDK } from "./sdk"
|
||||
import { createSignal, onCleanup } from "solid-js"
|
||||
|
||||
export type DataSessionStatus = "idle" | "running"
|
||||
type CompactionReason = Extract<V2Event, { type: "session.compaction.started" }>["data"]["reason"]
|
||||
|
||||
const messageIDFromEvent = (eventID: string) => eventID.replace(/^evt_/, "msg_")
|
||||
|
||||
|
|
@ -51,6 +52,7 @@ type Data = {
|
|||
// session ID in that family, including the key itself once its info arrives.
|
||||
family: Record<string, string[]>
|
||||
status: Record<string, DataSessionStatus>
|
||||
compaction: Record<string, CompactionReason>
|
||||
message: Record<string, SessionMessage[]>
|
||||
permission: Record<string, PermissionV2Request[]>
|
||||
question: Record<string, QuestionV2Request[]>
|
||||
|
|
@ -85,6 +87,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
info: {},
|
||||
family: {},
|
||||
status: {},
|
||||
compaction: {},
|
||||
message: {},
|
||||
permission: {},
|
||||
question: {},
|
||||
|
|
@ -200,6 +203,25 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
)
|
||||
}
|
||||
|
||||
const startCompaction = (sessionID: string, reason: CompactionReason) => {
|
||||
if (store.session.status[sessionID] !== "running") setStore("session", "status", sessionID, "running")
|
||||
if (store.session.compaction[sessionID] !== reason)
|
||||
setStore("session", "compaction", sessionID, reason)
|
||||
}
|
||||
|
||||
const clearCompaction = (sessionID: string, settleManual = true) => {
|
||||
const reason = store.session.compaction[sessionID]
|
||||
if (!reason) return
|
||||
setStore(
|
||||
"session",
|
||||
"compaction",
|
||||
produce((draft) => {
|
||||
delete draft[sessionID]
|
||||
}),
|
||||
)
|
||||
if (settleManual && reason === "manual") setStore("session", "status", sessionID, "idle")
|
||||
}
|
||||
|
||||
function handleEvent(event: V2Event) {
|
||||
switch (event.type) {
|
||||
case "session.created":
|
||||
|
|
@ -321,6 +343,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
})
|
||||
break
|
||||
case "session.step.started":
|
||||
clearCompaction(event.data.sessionID, false)
|
||||
setStore("session", "status", event.data.sessionID, "running")
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
if (index.has(event.data.assistantMessageID)) return
|
||||
|
|
@ -517,11 +540,14 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
})
|
||||
break
|
||||
case "session.retried":
|
||||
case "session.compaction.started":
|
||||
setStore("session", "status", event.data.sessionID, "running")
|
||||
break
|
||||
case "session.compaction.started":
|
||||
startCompaction(event.data.sessionID, event.data.reason)
|
||||
break
|
||||
case "session.execution.settled":
|
||||
setStore("session", "status", event.data.sessionID, "idle")
|
||||
clearCompaction(event.data.sessionID, false)
|
||||
break
|
||||
case "session.revert.staged":
|
||||
if (store.session.info[event.data.sessionID])
|
||||
|
|
@ -535,6 +561,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
case "session.compaction.delta":
|
||||
break
|
||||
case "session.compaction.ended":
|
||||
clearCompaction(event.data.sessionID)
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
message.append(draft, index, {
|
||||
id: messageIDFromEvent(event.id),
|
||||
|
|
@ -655,6 +682,17 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
status(sessionID: string) {
|
||||
return store.session.status[sessionID] ?? "idle"
|
||||
},
|
||||
compaction: {
|
||||
get(sessionID: string) {
|
||||
return store.session.compaction[sessionID]
|
||||
},
|
||||
startManual(sessionID: string) {
|
||||
startCompaction(sessionID, "manual")
|
||||
},
|
||||
clearManual(sessionID: string) {
|
||||
if (store.session.compaction[sessionID] === "manual") clearCompaction(sessionID)
|
||||
},
|
||||
},
|
||||
async refresh(sessionID: string) {
|
||||
setStore("session", "info", sessionID, mutable(await sdk.api.session.get({ sessionID })))
|
||||
registerSession(sessionID)
|
||||
|
|
@ -840,6 +878,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
|
||||
async function bootstrap() {
|
||||
if (bootstrapping) return bootstrapping
|
||||
setStore("session", "compaction", reconcile({}))
|
||||
bootstrapping = Promise.allSettled([
|
||||
sdk.api.session
|
||||
.list({
|
||||
|
|
|
|||
|
|
@ -382,7 +382,25 @@ export function Session() {
|
|||
aliases: ["summarize"],
|
||||
},
|
||||
run: () => {
|
||||
void sdk.api.session.compact({ sessionID: route.sessionID })
|
||||
const sessionID = route.sessionID
|
||||
if (data.session.status(sessionID) === "running") {
|
||||
toast.show({
|
||||
variant: "warning",
|
||||
title: "Compaction unavailable",
|
||||
message: "Wait for the current session activity to finish.",
|
||||
})
|
||||
dialog.clear()
|
||||
return
|
||||
}
|
||||
data.session.compaction.startManual(sessionID)
|
||||
void sdk.api.session.compact({ sessionID }).catch((error) => {
|
||||
data.session.compaction.clearManual(sessionID)
|
||||
toast.show({
|
||||
variant: "error",
|
||||
title: "Compaction failed",
|
||||
message: errorMessage(error),
|
||||
})
|
||||
})
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -310,6 +310,61 @@ test("tracks session status from active sessions and execution events", async ()
|
|||
})
|
||||
await wait(() => data.session.status("session-live") === "idle")
|
||||
|
||||
emitEvent(events, {
|
||||
id: "evt_compaction_started",
|
||||
created: 0,
|
||||
type: "session.compaction.started",
|
||||
durable: durable("session-compact"),
|
||||
data: {
|
||||
sessionID: "session-compact",
|
||||
reason: "manual",
|
||||
},
|
||||
})
|
||||
await wait(() => data.session.compaction.get("session-compact") === "manual")
|
||||
expect(data.session.status("session-compact")).toBe("running")
|
||||
|
||||
emitEvent(events, {
|
||||
id: "evt_compaction_ended",
|
||||
created: 0,
|
||||
type: "session.compaction.ended",
|
||||
durable: durable("session-compact", 1, 2),
|
||||
data: {
|
||||
sessionID: "session-compact",
|
||||
reason: "manual",
|
||||
text: "Summary",
|
||||
recent: "",
|
||||
},
|
||||
})
|
||||
await wait(() => data.session.compaction.get("session-compact") === undefined)
|
||||
expect(data.session.status("session-compact")).toBe("idle")
|
||||
|
||||
emitEvent(events, {
|
||||
id: "evt_auto_compaction_started",
|
||||
created: 0,
|
||||
type: "session.compaction.started",
|
||||
durable: durable("session-auto-compact"),
|
||||
data: {
|
||||
sessionID: "session-auto-compact",
|
||||
reason: "auto",
|
||||
},
|
||||
})
|
||||
await wait(() => data.session.compaction.get("session-auto-compact") === "auto")
|
||||
|
||||
emitEvent(events, {
|
||||
id: "evt_post_compaction_step",
|
||||
created: 0,
|
||||
type: "session.step.started",
|
||||
durable: durable("session-auto-compact", 1, 2),
|
||||
data: {
|
||||
sessionID: "session-auto-compact",
|
||||
assistantMessageID: "message-after-compaction",
|
||||
agent: "build",
|
||||
model: { id: "model", providerID: "provider" },
|
||||
},
|
||||
})
|
||||
await wait(() => data.session.compaction.get("session-auto-compact") === undefined)
|
||||
expect(data.session.status("session-auto-compact")).toBe("running")
|
||||
|
||||
emitEvent(events, {
|
||||
id: "evt_failed_step_started",
|
||||
created: 0,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue