YeonGyu-Kim d0bd24bede fix(cli-run): rely on continuation markers for completion
Use hook-written continuation marker state to gate run completion checks and remove the noisy event-stream shutdown timeout log in run mode.
2026-02-17 17:50:47 +09:00

170 lines
5.4 KiB
TypeScript

import { afterEach, describe, expect, test } from "bun:test"
import { mkdtempSync, rmSync } from "node:fs"
import { join } from "node:path"
import { tmpdir } from "node:os"
import { readContinuationMarker } from "../../features/run-continuation-state"
import { createStopContinuationGuardHook } from "./index"
describe("stop-continuation-guard", () => {
const tempDirs: string[] = []
function createTempDir(): string {
const directory = mkdtempSync(join(tmpdir(), "omo-stop-guard-"))
tempDirs.push(directory)
return directory
}
afterEach(() => {
while (tempDirs.length > 0) {
const directory = tempDirs.pop()
if (directory) {
rmSync(directory, { recursive: true, force: true })
}
}
})
function createMockPluginInput() {
return {
client: {
tui: {
showToast: async () => ({}),
},
},
directory: createTempDir(),
} as any
}
test("should mark session as stopped", () => {
// given - a guard hook with no stopped sessions
const input = createMockPluginInput()
const guard = createStopContinuationGuardHook(input)
const sessionID = "test-session-1"
// when - we stop continuation for the session
guard.stop(sessionID)
// then - session should be marked as stopped
expect(guard.isStopped(sessionID)).toBe(true)
const marker = readContinuationMarker(input.directory, sessionID)
expect(marker?.sources.stop?.state).toBe("stopped")
})
test("should return false for non-stopped sessions", () => {
// given - a guard hook with no stopped sessions
const guard = createStopContinuationGuardHook(createMockPluginInput())
// when - we check a session that was never stopped
// then - it should return false
expect(guard.isStopped("non-existent-session")).toBe(false)
})
test("should clear stopped state for a session", () => {
// given - a session that was stopped
const guard = createStopContinuationGuardHook(createMockPluginInput())
const sessionID = "test-session-2"
guard.stop(sessionID)
// when - we clear the session
guard.clear(sessionID)
// then - session should no longer be stopped
expect(guard.isStopped(sessionID)).toBe(false)
})
test("should handle multiple sessions independently", () => {
// given - multiple sessions with different stop states
const guard = createStopContinuationGuardHook(createMockPluginInput())
const session1 = "session-1"
const session2 = "session-2"
const session3 = "session-3"
// when - we stop some sessions but not others
guard.stop(session1)
guard.stop(session2)
// then - each session has its own state
expect(guard.isStopped(session1)).toBe(true)
expect(guard.isStopped(session2)).toBe(true)
expect(guard.isStopped(session3)).toBe(false)
})
test("should clear session on session.deleted event", async () => {
// given - a session that was stopped
const guard = createStopContinuationGuardHook(createMockPluginInput())
const sessionID = "test-session-3"
guard.stop(sessionID)
// when - session is deleted
await guard.event({
event: {
type: "session.deleted",
properties: { info: { id: sessionID } },
},
})
// then - session should no longer be stopped (cleaned up)
expect(guard.isStopped(sessionID)).toBe(false)
})
test("should not affect other sessions on session.deleted", async () => {
// given - multiple stopped sessions
const guard = createStopContinuationGuardHook(createMockPluginInput())
const session1 = "session-keep"
const session2 = "session-delete"
guard.stop(session1)
guard.stop(session2)
// when - one session is deleted
await guard.event({
event: {
type: "session.deleted",
properties: { info: { id: session2 } },
},
})
// then - other session should remain stopped
expect(guard.isStopped(session1)).toBe(true)
expect(guard.isStopped(session2)).toBe(false)
})
test("should clear stopped state on new user message (chat.message)", async () => {
// given - a session that was stopped
const guard = createStopContinuationGuardHook(createMockPluginInput())
const sessionID = "test-session-4"
guard.stop(sessionID)
expect(guard.isStopped(sessionID)).toBe(true)
// when - user sends a new message
await guard["chat.message"]({ sessionID })
// then - stop state should be cleared (one-time only)
expect(guard.isStopped(sessionID)).toBe(false)
})
test("should not affect non-stopped sessions on chat.message", async () => {
// given - a session that was never stopped
const guard = createStopContinuationGuardHook(createMockPluginInput())
const sessionID = "test-session-5"
// when - user sends a message (session was never stopped)
await guard["chat.message"]({ sessionID })
// then - should not throw and session remains not stopped
expect(guard.isStopped(sessionID)).toBe(false)
})
test("should handle undefined sessionID in chat.message", async () => {
// given - a guard with a stopped session
const guard = createStopContinuationGuardHook(createMockPluginInput())
guard.stop("some-session")
// when - chat.message is called without sessionID
await guard["chat.message"]({ sessionID: undefined })
// then - should not throw and stopped session remains stopped
expect(guard.isStopped("some-session")).toBe(true)
})
})