fix: add directory parameter and improve CLI run session handling

- Add directory parameter to session API calls (session.get, session.todo,
  session.status, session.children)
- Improve agent resolver with display name support via agent-display-names
- Add tool execution visibility in event handlers with running/completed
  status output
- Enhance poll-for-completion with main session status checking and
  stabilization period handling
- Add normalizeSDKResponse import for consistent response handling
- Update types with Todo, ChildSession, and toast-related interfaces

🤖 Generated with OhMyOpenCode assistance
This commit is contained in:
YeonGyu-Kim
2026-02-17 02:34:35 +09:00
parent e6cd1817ab
commit d3f24aa608
15 changed files with 531 additions and 56 deletions
+93 -9
View File
@@ -9,15 +9,32 @@ import type {
MessagePartUpdatedProps,
ToolExecuteProps,
ToolResultProps,
TuiToastShowProps,
} from "./types"
import type { EventState } from "./event-state"
import { serializeError } from "./event-formatting"
function getSessionId(props?: { sessionID?: string; sessionId?: string }): string | undefined {
return props?.sessionID ?? props?.sessionId
}
function getInfoSessionId(props?: {
info?: { sessionID?: string; sessionId?: string }
}): string | undefined {
return props?.info?.sessionID ?? props?.info?.sessionId
}
function getPartSessionId(props?: {
part?: { sessionID?: string; sessionId?: string }
}): string | undefined {
return props?.part?.sessionID ?? props?.part?.sessionId
}
export function handleSessionIdle(ctx: RunContext, payload: EventPayload, state: EventState): void {
if (payload.type !== "session.idle") return
const props = payload.properties as SessionIdleProps | undefined
if (props?.sessionID === ctx.sessionID) {
if (getSessionId(props) === ctx.sessionID) {
state.mainSessionIdle = true
}
}
@@ -26,7 +43,7 @@ export function handleSessionStatus(ctx: RunContext, payload: EventPayload, stat
if (payload.type !== "session.status") return
const props = payload.properties as SessionStatusProps | undefined
if (props?.sessionID !== ctx.sessionID) return
if (getSessionId(props) !== ctx.sessionID) return
if (props?.status?.type === "busy") {
state.mainSessionIdle = false
@@ -41,7 +58,7 @@ export function handleSessionError(ctx: RunContext, payload: EventPayload, state
if (payload.type !== "session.error") return
const props = payload.properties as SessionErrorProps | undefined
if (props?.sessionID === ctx.sessionID) {
if (getSessionId(props) === ctx.sessionID) {
state.mainSessionError = true
state.lastError = serializeError(props?.error)
console.error(pc.red(`\n[session.error] ${state.lastError}`))
@@ -52,10 +69,12 @@ export function handleMessagePartUpdated(ctx: RunContext, payload: EventPayload,
if (payload.type !== "message.part.updated") return
const props = payload.properties as MessagePartUpdatedProps | undefined
if (props?.info?.sessionID !== ctx.sessionID) return
if (props?.info?.role !== "assistant") return
// Current OpenCode puts sessionID inside part; legacy puts it in info
const partSid = getPartSessionId(props)
const infoSid = getInfoSessionId(props)
if ((partSid ?? infoSid) !== ctx.sessionID) return
const part = props.part
const part = props?.part
if (!part) return
if (part.type === "text" && part.text) {
@@ -66,13 +85,57 @@ export function handleMessagePartUpdated(ctx: RunContext, payload: EventPayload,
}
state.lastPartText = part.text
}
if (part.type === "tool") {
handleToolPart(ctx, part, state)
}
}
function handleToolPart(
_ctx: RunContext,
part: NonNullable<MessagePartUpdatedProps["part"]>,
state: EventState,
): void {
const toolName = part.tool || part.name || "unknown"
const status = part.state?.status
if (status === "running") {
state.currentTool = toolName
let inputPreview = ""
const input = part.state?.input
if (input) {
if (input.command) {
inputPreview = ` ${pc.dim(String(input.command).slice(0, 60))}`
} else if (input.pattern) {
inputPreview = ` ${pc.dim(String(input.pattern).slice(0, 40))}`
} else if (input.filePath) {
inputPreview = ` ${pc.dim(String(input.filePath))}`
} else if (input.query) {
inputPreview = ` ${pc.dim(String(input.query).slice(0, 40))}`
}
}
state.hasReceivedMeaningfulWork = true
process.stdout.write(`\n${pc.cyan(">")} ${pc.bold(toolName)}${inputPreview}\n`)
}
if (status === "completed" || status === "error") {
const output = part.state?.output || ""
const maxLen = 200
const preview = output.length > maxLen ? output.slice(0, maxLen) + "..." : output
if (preview.trim()) {
const lines = preview.split("\n").slice(0, 3)
process.stdout.write(pc.dim(` └─ ${lines.join("\n ")}\n`))
}
state.currentTool = null
state.lastPartText = ""
}
}
export function handleMessageUpdated(ctx: RunContext, payload: EventPayload, state: EventState): void {
if (payload.type !== "message.updated") return
const props = payload.properties as MessageUpdatedProps | undefined
if (props?.info?.sessionID !== ctx.sessionID) return
if (getInfoSessionId(props) !== ctx.sessionID) return
if (props?.info?.role !== "assistant") return
state.hasReceivedMeaningfulWork = true
@@ -84,7 +147,7 @@ export function handleToolExecute(ctx: RunContext, payload: EventPayload, state:
if (payload.type !== "tool.execute") return
const props = payload.properties as ToolExecuteProps | undefined
if (props?.sessionID !== ctx.sessionID) return
if (getSessionId(props) !== ctx.sessionID) return
const toolName = props?.name || "unknown"
state.currentTool = toolName
@@ -111,7 +174,7 @@ export function handleToolResult(ctx: RunContext, payload: EventPayload, state:
if (payload.type !== "tool.result") return
const props = payload.properties as ToolResultProps | undefined
if (props?.sessionID !== ctx.sessionID) return
if (getSessionId(props) !== ctx.sessionID) return
const output = props?.output || ""
const maxLen = 200
@@ -125,3 +188,24 @@ export function handleToolResult(ctx: RunContext, payload: EventPayload, state:
state.currentTool = null
state.lastPartText = ""
}
export function handleTuiToast(_ctx: RunContext, payload: EventPayload, state: EventState): void {
if (payload.type !== "tui.toast.show") return
const props = payload.properties as TuiToastShowProps | undefined
const title = props?.title ? `${props.title}: ` : ""
const message = props?.message?.trim()
const variant = props?.variant ?? "info"
if (!message) return
if (variant === "error") {
state.mainSessionError = true
state.lastError = `${title}${message}`
console.error(pc.red(`\n[tui.toast.error] ${state.lastError}`))
return
}
const colorize = variant === "warning" ? pc.yellow : pc.dim
console.log(colorize(`[toast:${variant}] ${title}${message}`))
}