chat: pwd selector — switch the /chat working directory

The Sessions panel header gets a pwd selector: the default claude_sessions dir,
plus every directory that already has Claude sessions (auto-discovered by reading
the real cwd back from ~/.claude/projects), plus free-text entry. Switching pwd
refetches the list for that cwd's Claude project group and runs New Chat / resume
in it. Backend: GET /chat/pwds + a ?cwd= param on the session ops; the WS handler
honors a chosen cwd for /chat (default claude_sessions). Browse-modal picker next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-24 12:41:49 +00:00
co-authored by Claude Opus 4.8
parent 420aa08783
commit 04bde70ed3
8 changed files with 237 additions and 36 deletions
+28 -12
View File
@@ -1,38 +1,54 @@
import type { Context } from 'hono';
import { createRouter } from '../../create-router';
import { getClaudeSessionsCwd, listClaudeSessions, loadClaudeSession, deleteClaudeSession, renameClaudeSession } from './claude-sessions';
import {
getClaudeSessionsCwd,
listClaudePwds,
listClaudeSessions,
loadClaudeSession,
deleteClaudeSession,
renameClaudeSession,
} from './claude-sessions';
export const chatRouter = createRouter();
// GET /chat/sessions — the /chat route's conversations, read straight from Claude's own transcript
// store for the dedicated claude_sessions working directory (Claude is the source of truth).
chatRouter.get('/sessions', (ctx) => {
// The working directory a request operates on: an explicit ?cwd= (a chosen pwd), else the default
// claude_sessions dir. Claude groups sessions by cwd, so this selects which project group we read.
const cwdOf = (ctx: Context, email: string): string => ctx.req.query('cwd')?.trim() || getClaudeSessionsCwd(email);
// GET /chat/pwds — the default /chat dir plus every directory that already has Claude sessions.
chatRouter.get('/pwds', (ctx) => {
const email = ctx.get('user').email;
const sessions = listClaudeSessions(email, getClaudeSessionsCwd(email));
return ctx.json({ sessions });
return ctx.json({ pwds: listClaudePwds(email), default: getClaudeSessionsCwd(email) });
});
// GET /chat/sessions/:idone conversation's full transcript, parsed into display-ready messages.
// GET /chat/sessions[?cwd=]the conversations for a working directory, from Claude's transcripts.
chatRouter.get('/sessions', (ctx) => {
const email = ctx.get('user').email;
return ctx.json({ sessions: listClaudeSessions(email, cwdOf(ctx, email)) });
});
// GET /chat/sessions/:id[?cwd=] — one conversation's full transcript, parsed into display messages.
chatRouter.get('/sessions/:id', (ctx) => {
const email = ctx.get('user').email;
const detail = loadClaudeSession(email, getClaudeSessionsCwd(email), ctx.req.param('id'));
const detail = loadClaudeSession(email, cwdOf(ctx, email), ctx.req.param('id'));
if (!detail) return ctx.text('Not found', 404);
return ctx.json(detail);
});
// DELETE /chat/sessions/:id — remove a conversation (deletes Claude's transcript file).
// DELETE /chat/sessions/:id[?cwd=] — remove a conversation (deletes Claude's transcript file).
chatRouter.delete('/sessions/:id', (ctx) => {
const email = ctx.get('user').email;
const ok = deleteClaudeSession(email, getClaudeSessionsCwd(email), ctx.req.param('id'));
const ok = deleteClaudeSession(email, cwdOf(ctx, email), ctx.req.param('id'));
if (!ok) return ctx.text('Not found', 404);
return ctx.json({ ok: true });
});
// PATCH /chat/sessions/:id/title — rename by writing a summary entry into Claude's transcript.
// PATCH /chat/sessions/:id/title[?cwd=] — rename by writing a summary entry into Claude's transcript.
chatRouter.patch('/sessions/:id/title', async (ctx) => {
const email = ctx.get('user').email;
const { title } = await ctx.req.json<{ title?: string }>();
if (!title?.trim()) return ctx.text('title is required', 400);
const ok = renameClaudeSession(email, getClaudeSessionsCwd(email), ctx.req.param('id'), title.trim());
const ok = renameClaudeSession(email, cwdOf(ctx, email), ctx.req.param('id'), title.trim());
if (!ok) return ctx.text('Not found', 404);
return ctx.json({ ok: true });
});