feat(tui): add v2 plugin context

This commit is contained in:
Dax Raad 2026-07-11 17:56:25 -04:00
commit de86d73d19
5 changed files with 175 additions and 27 deletions

View file

@ -22,10 +22,12 @@ import type {
SessionMessageAssistantText,
SessionMessageAssistantTool,
SessionInfo,
SessionPendingInfo,
ShellInfo,
SkillInfo,
OpenCodeEvent,
} from "@opencode-ai/client"
import type { Data } from "@opencode-ai/plugin/v2/tui/context"
import { createStore, produce, reconcile } from "solid-js/store"
import { createSimpleContext } from "./helper"
import { useSDK } from "./sdk"
@ -57,7 +59,7 @@ type LocationData = {
skill?: SkillInfo[]
}
type Data = {
type Store = {
session: {
info: Record<string, SessionInfo>
// Family index keyed by a family's root (or furthest-known-ancestor when the
@ -66,6 +68,7 @@ type Data = {
family: Record<string, string[]>
status: Record<string, DataSessionStatus>
message: Record<string, SessionMessageInfo[]>
pending: Record<string, SessionPendingInfo[]>
input: Record<string, string[]>
compaction: Record<string, string[]>
permission: Record<string, PermissionV2Request[]>
@ -89,12 +92,13 @@ function locationQuery(ref?: LocationRef) {
export const { use: useData, provider: DataProvider } = createSimpleContext({
name: "Data",
init: () => {
const [store, setStore] = createStore<Data>({
const [store, setStore] = createStore<Store>({
session: {
info: {},
family: {},
status: {},
message: {},
pending: {},
input: {},
compaction: {},
permission: {},
@ -123,6 +127,21 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
setStore("session", "compaction", sessionID, [...(store.session.compaction[sessionID] ?? []), inputID])
}
function addPending(item: SessionPendingInfo) {
if (store.session.pending[item.sessionID]?.some((pending) => pending.id === item.id)) return
setStore("session", "pending", item.sessionID, [...(store.session.pending[item.sessionID] ?? []), item])
}
function removePending(sessionID: string, inputID?: string) {
if (!inputID) return
setStore(
"session",
"pending",
sessionID,
(store.session.pending[sessionID] ?? []).filter((item) => item.id !== inputID),
)
}
function removeCompaction(sessionID: string, inputID?: string) {
if (!inputID || !store.session.compaction[sessionID]?.includes(inputID)) return
setStore(
@ -241,6 +260,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
delete draft.info[sessionID]
delete draft.status[sessionID]
delete draft.message[sessionID]
delete draft.pending[sessionID]
delete draft.input[sessionID]
delete draft.compaction[sessionID]
delete draft.permission[sessionID]
@ -330,6 +350,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
}
break
case "session.input.promoted": {
removePending(event.data.sessionID, event.data.inputID)
message.update(event.data.sessionID, (draft, index) => {
const position = index.get(event.data.inputID)
if (position === undefined) return
@ -350,6 +371,13 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
break
}
case "session.input.admitted":
addPending({
id: event.data.inputID,
sessionID: event.data.sessionID,
admittedSeq: event.durable.seq,
timeCreated: event.created,
...event.data.input,
})
if (!store.session.input[event.data.sessionID]?.includes(event.data.inputID))
setStore("session", "input", event.data.sessionID, [
...(store.session.input[event.data.sessionID] ?? []),
@ -633,9 +661,17 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
setSessionStatus(event.data.sessionID, "running")
break
case "session.compaction.admitted":
addPending({
id: event.data.inputID,
sessionID: event.data.sessionID,
admittedSeq: event.durable.seq,
timeCreated: event.created,
type: "compaction",
})
addCompaction(event.data.sessionID, event.data.inputID)
break
case "session.compaction.started":
removePending(event.data.sessionID, event.data.inputID)
removeCompaction(event.data.sessionID, event.data.inputID)
message.update(event.data.sessionID, (draft, index) => {
message.append(draft, index, {
@ -713,6 +749,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
})
break
case "session.compaction.failed":
removePending(event.data.sessionID, event.data.inputID)
removeCompaction(event.data.sessionID, event.data.inputID)
message.update(event.data.sessionID, (draft, index) => {
const position = draft.findLastIndex((item) => item.type === "compaction" && item.status === "running")
@ -854,16 +891,27 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
return store.session.compaction[sessionID] ?? []
},
async refresh(sessionID: string) {
if (!store.session.compaction[sessionID]) setStore("session", "compaction", sessionID, [])
await result.session.pending.refresh(sessionID)
},
},
pending: {
list(sessionID: string) {
return store.session.pending[sessionID] ?? []
},
async refresh(sessionID: string) {
const pending = await sdk.api.session.pending.list({ sessionID })
setStore("session", "pending", sessionID, reconcile(pending))
setStore(
"session",
"input",
sessionID,
reconcile(pending.filter((item) => item.type !== "compaction").map((item) => item.id)),
)
setStore(
"session",
"compaction",
sessionID,
reconcile(
(await sdk.api.session.pending.list({ sessionID }))
.filter((item) => item.type === "compaction")
.map((item) => item.id),
),
reconcile(pending.filter((item) => item.type === "compaction").map((item) => item.id)),
)
},
},
@ -872,9 +920,6 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
registerSession(sessionID)
},
message: {
ids(sessionID: string) {
return (store.session.message[sessionID] ?? []).map((message) => message.id)
},
list(sessionID: string) {
return store.session.message[sessionID] ?? []
},
@ -1063,6 +1108,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
},
},
}
result satisfies Data
async function bootstrap() {
if (bootstrapping) return bootstrapping