feat(session-manager): add project path filtering for session listing

- Add SESSION_STORAGE constant for session metadata directory
- Add getMainSessions() function to retrieve main sessions with filtering:
  - Sorts sessions by updated time (newest first)
  - Filters out child sessions (with parentID)
  - Filters sessions by directory path
- Update session_list tool to use new getMainSessions():
  - Add project_path parameter (default: current working directory)
  - Maintains existing date range filtering and limit behavior

🤖 Generated with assistance of OhMyOpenCode
This commit is contained in:
YeonGyu-Kim
2025-12-31 12:42:22 +09:00
parent 2f1ede072f
commit 0da20f21b0
3 changed files with 52 additions and 7 deletions

View File

@@ -5,6 +5,7 @@ import { getClaudeConfigDir } from "../../shared"
export const OPENCODE_STORAGE = getOpenCodeStorageDir() export const OPENCODE_STORAGE = getOpenCodeStorageDir()
export const MESSAGE_STORAGE = join(OPENCODE_STORAGE, "message") export const MESSAGE_STORAGE = join(OPENCODE_STORAGE, "message")
export const PART_STORAGE = join(OPENCODE_STORAGE, "part") export const PART_STORAGE = join(OPENCODE_STORAGE, "part")
export const SESSION_STORAGE = join(OPENCODE_STORAGE, "session")
export const TODO_DIR = join(getClaudeConfigDir(), "todos") export const TODO_DIR = join(getClaudeConfigDir(), "todos")
export const TRANSCRIPT_DIR = join(getClaudeConfigDir(), "transcripts") export const TRANSCRIPT_DIR = join(getClaudeConfigDir(), "transcripts")
export const SESSION_LIST_DESCRIPTION = `List all OpenCode sessions with optional filtering. export const SESSION_LIST_DESCRIPTION = `List all OpenCode sessions with optional filtering.

View File

@@ -1,8 +1,49 @@
import { existsSync, readdirSync } from "node:fs" import { existsSync, readdirSync } from "node:fs"
import { readdir, readFile } from "node:fs/promises" import { readdir, readFile } from "node:fs/promises"
import { join } from "node:path" import { join } from "node:path"
import { MESSAGE_STORAGE, PART_STORAGE, TODO_DIR, TRANSCRIPT_DIR } from "./constants" import { MESSAGE_STORAGE, PART_STORAGE, SESSION_STORAGE, TODO_DIR, TRANSCRIPT_DIR } from "./constants"
import type { SessionMessage, SessionInfo, TodoItem } from "./types" import type { SessionMessage, SessionInfo, TodoItem, SessionMetadata } from "./types"
export interface GetMainSessionsOptions {
directory?: string
}
export async function getMainSessions(options: GetMainSessionsOptions): Promise<SessionMetadata[]> {
if (!existsSync(SESSION_STORAGE)) return []
const sessions: SessionMetadata[] = []
try {
const projectDirs = await readdir(SESSION_STORAGE, { withFileTypes: true })
for (const projectDir of projectDirs) {
if (!projectDir.isDirectory()) continue
const projectPath = join(SESSION_STORAGE, projectDir.name)
const sessionFiles = await readdir(projectPath)
for (const file of sessionFiles) {
if (!file.endsWith(".json")) continue
try {
const content = await readFile(join(projectPath, file), "utf-8")
const meta = JSON.parse(content) as SessionMetadata
if (meta.parentID) continue
if (options.directory && meta.directory !== options.directory) continue
sessions.push(meta)
} catch {
continue
}
}
}
} catch {
return []
}
return sessions.sort((a, b) => b.time.updated - a.time.updated)
}
export async function getAllSessions(): Promise<string[]> { export async function getAllSessions(): Promise<string[]> {
if (!existsSync(MESSAGE_STORAGE)) return [] if (!existsSync(MESSAGE_STORAGE)) return []

View File

@@ -5,7 +5,7 @@ import {
SESSION_SEARCH_DESCRIPTION, SESSION_SEARCH_DESCRIPTION,
SESSION_INFO_DESCRIPTION, SESSION_INFO_DESCRIPTION,
} from "./constants" } from "./constants"
import { getAllSessions, getSessionInfo, readSessionMessages, readSessionTodos, sessionExists } from "./storage" import { getAllSessions, getMainSessions, getSessionInfo, readSessionMessages, readSessionTodos, sessionExists } from "./storage"
import { import {
filterSessionsByDate, filterSessionsByDate,
formatSessionInfo, formatSessionInfo,
@@ -32,20 +32,23 @@ export const session_list: ToolDefinition = tool({
limit: tool.schema.number().optional().describe("Maximum number of sessions to return"), limit: tool.schema.number().optional().describe("Maximum number of sessions to return"),
from_date: tool.schema.string().optional().describe("Filter sessions from this date (ISO 8601 format)"), from_date: tool.schema.string().optional().describe("Filter sessions from this date (ISO 8601 format)"),
to_date: tool.schema.string().optional().describe("Filter sessions until this date (ISO 8601 format)"), to_date: tool.schema.string().optional().describe("Filter sessions until this date (ISO 8601 format)"),
project_path: tool.schema.string().optional().describe("Filter sessions by project path (default: current working directory)"),
}, },
execute: async (args: SessionListArgs, _context) => { execute: async (args: SessionListArgs, _context) => {
try { try {
let sessions = await getAllSessions() const directory = args.project_path ?? process.cwd()
let sessions = await getMainSessions({ directory })
let sessionIDs = sessions.map((s) => s.id)
if (args.from_date || args.to_date) { if (args.from_date || args.to_date) {
sessions = await filterSessionsByDate(sessions, args.from_date, args.to_date) sessionIDs = await filterSessionsByDate(sessionIDs, args.from_date, args.to_date)
} }
if (args.limit && args.limit > 0) { if (args.limit && args.limit > 0) {
sessions = sessions.slice(0, args.limit) sessionIDs = sessionIDs.slice(0, args.limit)
} }
return await formatSessionList(sessions) return await formatSessionList(sessionIDs)
} catch (e) { } catch (e) {
return `Error: ${e instanceof Error ? e.message : String(e)}` return `Error: ${e instanceof Error ? e.message : String(e)}`
} }