resolve /chat/<id> by scanning project groups and make session rows real links
deep-linking or refreshing /chat/<id> only has the id, so add loadClaudeSessionById to scan every project group for the transcript and return its real cwd. the session list page resolves on load from that, setting the cwd picker and resuming. session rows are now <Link to=/chat/<id>> so clicking updates the url and flows through the same resolve path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
listClaudePwds,
|
||||
listClaudeSessions,
|
||||
loadClaudeSession,
|
||||
loadClaudeSessionById,
|
||||
deleteClaudeSession,
|
||||
renameClaudeSession,
|
||||
} from './claude-sessions';
|
||||
@@ -50,7 +51,11 @@ chatRouter.get('/sessions/:id', async (ctx) => {
|
||||
const email = ctx.get('user').email;
|
||||
const id = ctx.req.param('id');
|
||||
const cwd = cwdOf(ctx, email);
|
||||
const detail = isOpenCodeSessionId(id) ? await loadOpenCodeSession(id) : loadClaudeSession(email, cwd, id);
|
||||
// Fall back to a by-id scan when the (default) cwd doesn't hold it — a fresh /chat/<id> deep-link/refresh
|
||||
// doesn't know the session's cwd. The returned detail carries the real cwd for the client to scope the UI.
|
||||
const detail = isOpenCodeSessionId(id)
|
||||
? await loadOpenCodeSession(id)
|
||||
: (loadClaudeSession(email, cwd, id) ?? loadClaudeSessionById(email, id));
|
||||
if (!detail) return ctx.text('Not found', 404);
|
||||
return ctx.json(detail);
|
||||
});
|
||||
|
||||
@@ -163,14 +163,13 @@ function blockText(content: unknown): string {
|
||||
export type ClaudeSessionDetail = { id: string; model: string; cwd: string; messages: ClaudeChatMessage[] };
|
||||
|
||||
/** Parse a session's JSONL transcript into a flat, display-ready message list. Claude is the source. */
|
||||
export function loadClaudeSession(email: string, cwd: string, sessionId: string): ClaudeSessionDetail | null {
|
||||
const filePath = join(claudeProjectsDir(email), projectSlug(cwd), `${sessionId}.jsonl`);
|
||||
function parseClaudeTranscript(filePath: string, sessionId: string, fallbackCwd = ''): ClaudeSessionDetail | null {
|
||||
if (!existsSync(filePath)) return null;
|
||||
|
||||
const messages: ClaudeChatMessage[] = [];
|
||||
const toolById = new Map<string, Extract<ClaudeChatMessage, { role: 'tool' }>>();
|
||||
let model = '';
|
||||
let sessionCwd = cwd;
|
||||
let sessionCwd = fallbackCwd;
|
||||
|
||||
for (const line of readFileSync(filePath, 'utf-8').split('\n')) {
|
||||
if (!line.trim()) continue;
|
||||
@@ -227,6 +226,29 @@ export function loadClaudeSession(email: string, cwd: string, sessionId: string)
|
||||
return { id: sessionId, model, cwd: sessionCwd, messages };
|
||||
}
|
||||
|
||||
/** Load a session when its cwd (project group) is known. */
|
||||
export function loadClaudeSession(email: string, cwd: string, sessionId: string): ClaudeSessionDetail | null {
|
||||
return parseClaudeTranscript(join(claudeProjectsDir(email), projectSlug(cwd), `${sessionId}.jsonl`), sessionId, cwd);
|
||||
}
|
||||
|
||||
/** Resolve a session by id ALONE — scan every project group for its transcript. Used on a deep-link /
|
||||
* refresh to /chat/<id>, when the cwd isn't known yet; the transcript records the real cwd, which the
|
||||
* caller uses to scope the list + cwd picker. */
|
||||
export function loadClaudeSessionById(email: string, sessionId: string): ClaudeSessionDetail | null {
|
||||
const projectsDir = claudeProjectsDir(email);
|
||||
let slugs: string[];
|
||||
try {
|
||||
slugs = readdirSync(projectsDir);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
for (const slug of slugs) {
|
||||
const filePath = join(projectsDir, slug, `${sessionId}.jsonl`);
|
||||
if (existsSync(filePath)) return parseClaudeTranscript(filePath, sessionId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Delete a session by removing its transcript file. Returns false if it didn't exist. */
|
||||
export function deleteClaudeSession(email: string, cwd: string, sessionId: string): boolean {
|
||||
const filePath = join(claudeProjectsDir(email), projectSlug(cwd), `${sessionId}.jsonl`);
|
||||
|
||||
Reference in New Issue
Block a user