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:
@@ -1,7 +1,7 @@
|
||||
import { describe, it, expect } from "bun:test"
|
||||
import { describe, it, expect, spyOn } from "bun:test"
|
||||
import type { RunContext } from "./types"
|
||||
import { createEventState } from "./events"
|
||||
import { handleSessionStatus } from "./event-handlers"
|
||||
import { handleSessionStatus, handleMessagePartUpdated, handleTuiToast } from "./event-handlers"
|
||||
|
||||
const createMockContext = (sessionID: string = "test-session"): RunContext => ({
|
||||
sessionID,
|
||||
@@ -70,4 +70,211 @@ describe("handleSessionStatus", () => {
|
||||
//#then - state.mainSessionIdle remains unchanged
|
||||
expect(state.mainSessionIdle).toBe(true)
|
||||
})
|
||||
|
||||
it("recognizes idle from camelCase sessionId", () => {
|
||||
//#given - state with mainSessionIdle=false and payload using sessionId
|
||||
const ctx = createMockContext("test-session")
|
||||
const state = createEventState()
|
||||
state.mainSessionIdle = false
|
||||
|
||||
const payload = {
|
||||
type: "session.status",
|
||||
properties: {
|
||||
sessionId: "test-session",
|
||||
status: { type: "idle" as const },
|
||||
},
|
||||
}
|
||||
|
||||
//#when - handleSessionStatus called with camelCase sessionId
|
||||
handleSessionStatus(ctx, payload as any, state)
|
||||
|
||||
//#then - state.mainSessionIdle === true
|
||||
expect(state.mainSessionIdle).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("handleMessagePartUpdated", () => {
|
||||
it("extracts sessionID from part (current OpenCode event structure)", () => {
|
||||
//#given - message.part.updated with sessionID in part, not info
|
||||
const ctx = createMockContext("ses_main")
|
||||
const state = createEventState()
|
||||
const stdoutSpy = spyOn(process.stdout, "write").mockImplementation(() => true)
|
||||
|
||||
const payload = {
|
||||
type: "message.part.updated",
|
||||
properties: {
|
||||
part: {
|
||||
id: "part_1",
|
||||
sessionID: "ses_main",
|
||||
messageID: "msg_1",
|
||||
type: "text",
|
||||
text: "Hello world",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
//#when
|
||||
handleMessagePartUpdated(ctx, payload as any, state)
|
||||
|
||||
//#then
|
||||
expect(state.hasReceivedMeaningfulWork).toBe(true)
|
||||
expect(state.lastPartText).toBe("Hello world")
|
||||
expect(stdoutSpy).toHaveBeenCalled()
|
||||
stdoutSpy.mockRestore()
|
||||
})
|
||||
|
||||
it("skips events for different session", () => {
|
||||
//#given - message.part.updated with different session
|
||||
const ctx = createMockContext("ses_main")
|
||||
const state = createEventState()
|
||||
|
||||
const payload = {
|
||||
type: "message.part.updated",
|
||||
properties: {
|
||||
part: {
|
||||
id: "part_1",
|
||||
sessionID: "ses_other",
|
||||
messageID: "msg_1",
|
||||
type: "text",
|
||||
text: "Hello world",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
//#when
|
||||
handleMessagePartUpdated(ctx, payload as any, state)
|
||||
|
||||
//#then
|
||||
expect(state.hasReceivedMeaningfulWork).toBe(false)
|
||||
expect(state.lastPartText).toBe("")
|
||||
})
|
||||
|
||||
it("handles tool part with running status", () => {
|
||||
//#given - tool part in running state
|
||||
const ctx = createMockContext("ses_main")
|
||||
const state = createEventState()
|
||||
const stdoutSpy = spyOn(process.stdout, "write").mockImplementation(() => true)
|
||||
|
||||
const payload = {
|
||||
type: "message.part.updated",
|
||||
properties: {
|
||||
part: {
|
||||
id: "part_1",
|
||||
sessionID: "ses_main",
|
||||
messageID: "msg_1",
|
||||
type: "tool",
|
||||
tool: "read",
|
||||
state: { status: "running", input: { filePath: "/src/index.ts" } },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
//#when
|
||||
handleMessagePartUpdated(ctx, payload as any, state)
|
||||
|
||||
//#then
|
||||
expect(state.currentTool).toBe("read")
|
||||
expect(state.hasReceivedMeaningfulWork).toBe(true)
|
||||
stdoutSpy.mockRestore()
|
||||
})
|
||||
|
||||
it("clears currentTool when tool completes", () => {
|
||||
//#given - tool part in completed state
|
||||
const ctx = createMockContext("ses_main")
|
||||
const state = createEventState()
|
||||
state.currentTool = "read"
|
||||
const stdoutSpy = spyOn(process.stdout, "write").mockImplementation(() => true)
|
||||
|
||||
const payload = {
|
||||
type: "message.part.updated",
|
||||
properties: {
|
||||
part: {
|
||||
id: "part_1",
|
||||
sessionID: "ses_main",
|
||||
messageID: "msg_1",
|
||||
type: "tool",
|
||||
tool: "read",
|
||||
state: { status: "completed", input: {}, output: "file contents here" },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
//#when
|
||||
handleMessagePartUpdated(ctx, payload as any, state)
|
||||
|
||||
//#then
|
||||
expect(state.currentTool).toBeNull()
|
||||
stdoutSpy.mockRestore()
|
||||
})
|
||||
|
||||
it("supports legacy info.sessionID for backward compatibility", () => {
|
||||
//#given - legacy event with sessionID in info
|
||||
const ctx = createMockContext("ses_legacy")
|
||||
const state = createEventState()
|
||||
const stdoutSpy = spyOn(process.stdout, "write").mockImplementation(() => true)
|
||||
|
||||
const payload = {
|
||||
type: "message.part.updated",
|
||||
properties: {
|
||||
info: { sessionID: "ses_legacy", role: "assistant" },
|
||||
part: {
|
||||
type: "text",
|
||||
text: "Legacy text",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
//#when
|
||||
handleMessagePartUpdated(ctx, payload as any, state)
|
||||
|
||||
//#then
|
||||
expect(state.hasReceivedMeaningfulWork).toBe(true)
|
||||
expect(state.lastPartText).toBe("Legacy text")
|
||||
stdoutSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
describe("handleTuiToast", () => {
|
||||
it("marks main session as error when toast variant is error", () => {
|
||||
//#given - toast error payload
|
||||
const ctx = createMockContext("test-session")
|
||||
const state = createEventState()
|
||||
|
||||
const payload = {
|
||||
type: "tui.toast.show",
|
||||
properties: {
|
||||
title: "Auth",
|
||||
message: "Invalid API key",
|
||||
variant: "error" as const,
|
||||
},
|
||||
}
|
||||
|
||||
//#when
|
||||
handleTuiToast(ctx, payload as any, state)
|
||||
|
||||
//#then
|
||||
expect(state.mainSessionError).toBe(true)
|
||||
expect(state.lastError).toBe("Auth: Invalid API key")
|
||||
})
|
||||
|
||||
it("does not mark session error for warning toast", () => {
|
||||
//#given - toast warning payload
|
||||
const ctx = createMockContext("test-session")
|
||||
const state = createEventState()
|
||||
|
||||
const payload = {
|
||||
type: "tui.toast.show",
|
||||
properties: {
|
||||
message: "Retrying provider",
|
||||
variant: "warning" as const,
|
||||
},
|
||||
}
|
||||
|
||||
//#when
|
||||
handleTuiToast(ctx, payload as any, state)
|
||||
|
||||
//#then
|
||||
expect(state.mainSessionError).toBe(false)
|
||||
expect(state.lastError).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user