fix(session-manager): convert blocking sync I/O to async for improved concurrency

Convert session-manager storage layer from synchronous blocking I/O (readdirSync, readFileSync) to non-blocking async I/O (readdir, readFile from fs/promises). This fixes hanging issues in session_search and other tools caused by blocking filesystem operations.

Changes:
- storage.ts: getAllSessions, readSessionMessages, getSessionInfo now async
- utils.ts: Updated utility functions to be async-compatible
- tools.ts: Added await calls for async storage functions
- storage.test.ts, utils.test.ts: Updated tests with async/await patterns

This resolves the session_search tool hang issue and improves overall responsiveness.

🤖 Generated with assistance of OhMyOpenCode (https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
YeonGyu-Kim
2025-12-28 14:33:47 +09:00
parent 6d6102f1ff
commit 49f3be5a1f
5 changed files with 269 additions and 128 deletions
+45 -22
View File
@@ -23,7 +23,8 @@ mock.module("./constants", () => ({
TOOL_NAME_PREFIX: "session_",
}))
const { getAllSessions, getMessageDir, sessionExists, readSessionMessages, readSessionTodos, getSessionInfo } = await import("./storage")
const { getAllSessions, getMessageDir, sessionExists, readSessionMessages, readSessionTodos, getSessionInfo } =
await import("./storage")
describe("session-manager storage", () => {
beforeEach(() => {
@@ -43,48 +44,61 @@ describe("session-manager storage", () => {
}
})
test("getAllSessions returns empty array when no sessions exist", () => {
const sessions = getAllSessions()
test("getAllSessions returns empty array when no sessions exist", async () => {
// #when
const sessions = await getAllSessions()
// #then
expect(Array.isArray(sessions)).toBe(true)
expect(sessions).toEqual([])
})
test("getMessageDir finds session in direct path", () => {
// #given
const sessionID = "ses_test123"
const sessionPath = join(TEST_MESSAGE_STORAGE, sessionID)
mkdirSync(sessionPath, { recursive: true })
writeFileSync(join(sessionPath, "msg_001.json"), JSON.stringify({ id: "msg_001", role: "user" }))
// #when
const result = getMessageDir(sessionID)
// #then
expect(result).toBe(sessionPath)
})
test("sessionExists returns false for non-existent session", () => {
// #when
const exists = sessionExists("ses_nonexistent")
// #then
expect(exists).toBe(false)
})
test("sessionExists returns true for existing session", () => {
// #given
const sessionID = "ses_exists"
const sessionPath = join(TEST_MESSAGE_STORAGE, sessionID)
mkdirSync(sessionPath, { recursive: true })
writeFileSync(join(sessionPath, "msg_001.json"), JSON.stringify({ id: "msg_001" }))
// #when
const exists = sessionExists(sessionID)
// #then
expect(exists).toBe(true)
})
test("readSessionMessages returns empty array for non-existent session", () => {
const messages = readSessionMessages("ses_nonexistent")
test("readSessionMessages returns empty array for non-existent session", async () => {
// #when
const messages = await readSessionMessages("ses_nonexistent")
// #then
expect(messages).toEqual([])
})
test("readSessionMessages sorts messages by timestamp", () => {
test("readSessionMessages sorts messages by timestamp", async () => {
// #given
const sessionID = "ses_test123"
const sessionPath = join(TEST_MESSAGE_STORAGE, sessionID)
mkdirSync(sessionPath, { recursive: true })
@@ -98,26 +112,33 @@ describe("session-manager storage", () => {
JSON.stringify({ id: "msg_001", role: "user", time: { created: 1000 } })
)
const messages = readSessionMessages(sessionID)
// #when
const messages = await readSessionMessages(sessionID)
// #then
expect(messages.length).toBe(2)
expect(messages[0].id).toBe("msg_001")
expect(messages[1].id).toBe("msg_002")
})
test("readSessionTodos returns empty array when no todos exist", () => {
const todos = readSessionTodos("ses_nonexistent")
test("readSessionTodos returns empty array when no todos exist", async () => {
// #when
const todos = await readSessionTodos("ses_nonexistent")
// #then
expect(todos).toEqual([])
})
test("getSessionInfo returns null for non-existent session", () => {
const info = getSessionInfo("ses_nonexistent")
test("getSessionInfo returns null for non-existent session", async () => {
// #when
const info = await getSessionInfo("ses_nonexistent")
// #then
expect(info).toBeNull()
})
test("getSessionInfo aggregates session metadata correctly", () => {
test("getSessionInfo aggregates session metadata correctly", async () => {
// #given
const sessionID = "ses_test123"
const sessionPath = join(TEST_MESSAGE_STORAGE, sessionID)
mkdirSync(sessionPath, { recursive: true })
@@ -142,8 +163,10 @@ describe("session-manager storage", () => {
})
)
const info = getSessionInfo(sessionID)
// #when
const info = await getSessionInfo(sessionID)
// #then
expect(info).not.toBeNull()
expect(info?.id).toBe(sessionID)
expect(info?.message_count).toBe(2)