fix: resolve PR #51 review comments for session discovery

- Add resolveMetaDir() helper to find sessions in project-specific
  subdirs (*-sessions/). get(), kill(), send() now work for sessions
  discovered in subdirs, not just top-level dataDir.
- Log warning when duplicate session IDs are found across subdirs
  instead of silently dropping duplicates.
- Fix buildPrompt() to not skip exploratory prompt on bare sessions
  (same fix as PR #49).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
sjd9021 2026-02-15 23:57:21 +05:30
parent eb96fecb46
commit 7e7d9ab47f
2 changed files with 40 additions and 6 deletions

View File

@ -167,7 +167,8 @@ export function buildPrompt(config: PromptBuildConfig): string | null {
const hasUserPrompt = Boolean(config.userPrompt);
// Nothing to compose — return null for backward compatibility
if (!hasIssue && !hasRules && !hasUserPrompt) {
// (but exploratory sessions always need the exploratory prompt)
if (!hasIssue && !hasRules && !hasUserPrompt && !config.exploratory) {
return null;
}

View File

@ -162,6 +162,34 @@ export function createSessionManager(deps: SessionManagerDeps): SessionManager {
return { runtime, agent, workspace, tracker, scm };
}
/**
* Resolve which metadata directory a session lives in.
* Checks top-level dataDir first, then project-specific *-sessions subdirs.
*/
function resolveMetaDir(sessionId: SessionId): string {
// Check top-level first
const topLevel = readMetadataRaw(config.dataDir, sessionId);
if (topLevel) return config.dataDir;
// Check project-specific subdirs
try {
const entries = readdirSync(config.dataDir);
for (const entry of entries) {
if (!entry.endsWith("-sessions")) continue;
const subdirPath = join(config.dataDir, entry);
try {
if (!statSync(subdirPath).isDirectory()) continue;
} catch { continue; }
const raw = readMetadataRaw(subdirPath, sessionId);
if (raw) return subdirPath;
}
} catch {
// dataDir doesn't exist or can't be read
}
return config.dataDir;
}
// Define methods as local functions so `this` is not needed
async function spawn(spawnConfig: SessionSpawnConfig): Promise<Session> {
const project = config.projects[spawnConfig.projectId];
@ -378,6 +406,8 @@ export function createSessionManager(deps: SessionManagerDeps): SessionManager {
if (!sessionIds.includes(sid)) {
sessionIds.push(sid);
projectSubdirs.set(sid, subdirPath);
} else {
console.warn(`[session-manager] Duplicate session ID "${sid}" found in ${subdirPath}, skipping (already seen in ${projectSubdirs.get(sid) ?? config.dataDir})`);
}
}
}
@ -435,14 +465,15 @@ export function createSessionManager(deps: SessionManagerDeps): SessionManager {
}
async function get(sessionId: SessionId): Promise<Session | null> {
const raw = readMetadataRaw(config.dataDir, sessionId);
const metaDir = resolveMetaDir(sessionId);
const raw = readMetadataRaw(metaDir, sessionId);
if (!raw) return null;
// Get file timestamps for createdAt/lastActivityAt
let createdAt: Date | undefined;
let modifiedAt: Date | undefined;
try {
const metaPath = join(config.dataDir, sessionId);
const metaPath = join(metaDir, sessionId);
const stats = statSync(metaPath);
createdAt = stats.birthtime;
modifiedAt = stats.mtime;
@ -475,7 +506,8 @@ export function createSessionManager(deps: SessionManagerDeps): SessionManager {
}
async function kill(sessionId: SessionId): Promise<void> {
const raw = readMetadataRaw(config.dataDir, sessionId);
const metaDir = resolveMetaDir(sessionId);
const raw = readMetadataRaw(metaDir, sessionId);
if (!raw) throw new Error(`Session ${sessionId} not found`);
const projectId = raw["project"] ?? "";
@ -516,7 +548,7 @@ export function createSessionManager(deps: SessionManagerDeps): SessionManager {
}
// Archive metadata
deleteMetadata(config.dataDir, sessionId, true);
deleteMetadata(metaDir, sessionId, true);
}
async function cleanup(projectId?: string): Promise<CleanupResult> {
@ -584,7 +616,8 @@ export function createSessionManager(deps: SessionManagerDeps): SessionManager {
}
async function send(sessionId: SessionId, message: string): Promise<void> {
const raw = readMetadataRaw(config.dataDir, sessionId);
const metaDir = resolveMetaDir(sessionId);
const raw = readMetadataRaw(metaDir, sessionId);
if (!raw) throw new Error(`Session ${sessionId} not found`);
// Build handle: use stored runtimeHandle, or fall back to session ID as tmux session name