chat: rename claude_sessions → general_chat_sessions; drop dead chat_sessions
The default /chat working directory is used by both the Claude and OpenCode harnesses now, so its Claude-specific name was misleading. - Rename the dir + accessors: getClaudeSessionsCwd → getGeneralChatSessionsCwd, ensureClaudeSessionsCwd → ensureGeneralChatSessionsCwd, path segment claude_sessions → general_chat_sessions (data-path on disk + code + UI labels/comments). No history migration — the old Claude transcript slug is orphaned (intentionally). - Remove the vestigial chat_sessions dir (leftover from the retired session store): it only ever held empty claude/archived/ dirs, recreated by a signin hook. Drop that hook (+ its dead imports) and the 4 unused data-path accessors (getUserSessionsDir, getClaudeDir, getSessionDir, getArchivedSessionDir), and delete the dir. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
import type { Handler } from 'hono';
|
||||
import { mkdir } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { getUserByEmail, getPasskeysByUserIdAndOrigin } from 'officerdb';
|
||||
import { sign } from '@@/jwt';
|
||||
import { getClaudeDir } from '@@/data-path';
|
||||
import argon2 from 'argon2';
|
||||
import * as errors from '@@/custom-errors';
|
||||
import { isLockdown, noteBlocked } from './panic';
|
||||
@@ -33,8 +30,6 @@ export const signinHandler: Handler = async function (ctx) {
|
||||
|
||||
const { id, name, username, role } = dbUser;
|
||||
|
||||
mkdir(join(getClaudeDir(email), 'archived'), { recursive: true }).catch(() => {});
|
||||
|
||||
const tokenUser = { id, email, name, username, role, passkeys: passkeys.length };
|
||||
|
||||
if (passkeys.length > 0 && !origin.startsWith('chrome-extension://') && !TEST_USERS.includes(dbUser.id)) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Context } from 'hono';
|
||||
import { createRouter } from '../../create-router';
|
||||
import { getUserSettings } from 'officerdb';
|
||||
import {
|
||||
getClaudeSessionsCwd,
|
||||
getGeneralChatSessionsCwd,
|
||||
listClaudePwds,
|
||||
listClaudeSessions,
|
||||
loadClaudeSession,
|
||||
@@ -24,14 +24,14 @@ import { transcribeAudio } from '../stt/transcribe';
|
||||
export const chatRouter = createRouter();
|
||||
|
||||
// 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.
|
||||
// general_chat_sessions dir. Claude groups sessions by cwd, so this selects which project group we read.
|
||||
// (OpenCode sessions all live in the one fixed server and ignore cwd.)
|
||||
const cwdOf = (ctx: Context, email: string): string => ctx.req.query('cwd')?.trim() || getClaudeSessionsCwd(email);
|
||||
const cwdOf = (ctx: Context, email: string): string => ctx.req.query('cwd')?.trim() || getGeneralChatSessionsCwd(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;
|
||||
return ctx.json({ pwds: listClaudePwds(email), default: getClaudeSessionsCwd(email) });
|
||||
return ctx.json({ pwds: listClaudePwds(email), default: getGeneralChatSessionsCwd(email) });
|
||||
});
|
||||
|
||||
// GET /chat/sessions[?cwd=] — conversations for a working directory, merged across both harnesses
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
import { readdirSync, readFileSync, existsSync, statSync, mkdirSync, rmSync, appendFileSync, openSync, readSync, closeSync } from 'node:fs';
|
||||
import {
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
existsSync,
|
||||
statSync,
|
||||
mkdirSync,
|
||||
rmSync,
|
||||
appendFileSync,
|
||||
openSync,
|
||||
readSync,
|
||||
closeSync,
|
||||
} from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { DATA_PATH } from '../../data-path';
|
||||
|
||||
@@ -13,11 +24,11 @@ import { DATA_PATH } from '../../data-path';
|
||||
const claudeHome = (email: string): string => process.env.HOME_DIR ?? join(DATA_PATH, email, 'home');
|
||||
|
||||
/** Dedicated working directory for /chat sessions, so they form their own Claude "project" group. */
|
||||
export const getClaudeSessionsCwd = (email: string): string => join(DATA_PATH, email, 'claude_sessions');
|
||||
export const getGeneralChatSessionsCwd = (email: string): string => join(DATA_PATH, email, 'general_chat_sessions');
|
||||
|
||||
/** Same, but create the directory if it doesn't exist (call before spawning a /chat session). */
|
||||
export const ensureClaudeSessionsCwd = (email: string): string => {
|
||||
const dir = getClaudeSessionsCwd(email);
|
||||
export const ensureGeneralChatSessionsCwd = (email: string): string => {
|
||||
const dir = getGeneralChatSessionsCwd(email);
|
||||
mkdirSync(dir, { recursive: true });
|
||||
return dir;
|
||||
};
|
||||
@@ -44,7 +55,11 @@ function entryText(message: unknown): string {
|
||||
if (typeof content === 'string') return content;
|
||||
if (Array.isArray(content)) {
|
||||
return content
|
||||
.map((block) => (block && typeof block === 'object' && (block as { type?: string }).type === 'text' ? (block as { text?: string }).text ?? '' : ''))
|
||||
.map((block) =>
|
||||
block && typeof block === 'object' && (block as { type?: string }).type === 'text'
|
||||
? ((block as { text?: string }).text ?? '')
|
||||
: '',
|
||||
)
|
||||
.join('')
|
||||
.trim();
|
||||
}
|
||||
@@ -116,7 +131,14 @@ function summarizeTranscript(filePath: string, id: string): ClaudeSessionSummary
|
||||
export type ClaudeChatMessage =
|
||||
| { role: 'user'; text: string }
|
||||
| { role: 'assistant'; id: string; text: string }
|
||||
| { role: 'tool'; toolName: string; toolInput: Record<string, unknown>; toolCallId: string; output?: string; isError?: boolean };
|
||||
| {
|
||||
role: 'tool';
|
||||
toolName: string;
|
||||
toolInput: Record<string, unknown>;
|
||||
toolCallId: string;
|
||||
output?: string;
|
||||
isError?: boolean;
|
||||
};
|
||||
|
||||
type ContentBlock =
|
||||
| { type: 'text'; text?: string }
|
||||
@@ -128,7 +150,11 @@ function blockText(content: unknown): string {
|
||||
if (typeof content === 'string') return content;
|
||||
if (Array.isArray(content)) {
|
||||
return content
|
||||
.map((b) => (b && typeof b === 'object' && (b as { type?: string }).type === 'text' ? (b as { text?: string }).text ?? '' : ''))
|
||||
.map((b) =>
|
||||
b && typeof b === 'object' && (b as { type?: string }).type === 'text'
|
||||
? ((b as { text?: string }).text ?? '')
|
||||
: '',
|
||||
)
|
||||
.join('');
|
||||
}
|
||||
return '';
|
||||
@@ -185,7 +211,12 @@ export function loadClaudeSession(email: string, cwd: string, sessionId: string)
|
||||
if (block.type === 'text' && block.text?.trim()) {
|
||||
messages.push({ role: 'assistant', id: `${sessionId}-${messages.length}`, text: block.text });
|
||||
} else if (block.type === 'tool_use') {
|
||||
const tool = { role: 'tool' as const, toolName: block.name, toolInput: block.input ?? {}, toolCallId: block.id };
|
||||
const tool = {
|
||||
role: 'tool' as const,
|
||||
toolName: block.name,
|
||||
toolInput: block.input ?? {},
|
||||
toolCallId: block.id,
|
||||
};
|
||||
messages.push(tool);
|
||||
toolById.set(block.id, tool);
|
||||
}
|
||||
@@ -257,7 +288,7 @@ export type ClaudePwd = { cwd: string; sessionCount: number; updatedAt: string;
|
||||
/** All working directories that have Claude sessions, plus the default /chat dir. Newest first. */
|
||||
export function listClaudePwds(email: string): ClaudePwd[] {
|
||||
const projectsDir = claudeProjectsDir(email);
|
||||
const defaultCwd = getClaudeSessionsCwd(email);
|
||||
const defaultCwd = getGeneralChatSessionsCwd(email);
|
||||
const byCwd = new Map<string, { count: number; updatedAt: string }>();
|
||||
|
||||
if (existsSync(projectsDir)) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { ClientMessage, ServerMessage, Message, ChatEvent } from './types';
|
||||
import { sessionManager } from './session-manager';
|
||||
import { sendClaudeCodeStreaming } from '@@/channels/send-claude-code';
|
||||
import { sendOpenCodeStreaming } from '@@/channels/send-opencode';
|
||||
import { ensureClaudeSessionsCwd, getClaudeSessionsCwd } from './claude-sessions';
|
||||
import { ensureGeneralChatSessionsCwd, getGeneralChatSessionsCwd } from './claude-sessions';
|
||||
import * as sidecar from '@@/sidecar-registry';
|
||||
import { join } from 'path';
|
||||
import { getHomeDirForRole, getEmailAccountsDir } from '../../../servers/data-path';
|
||||
@@ -77,7 +77,7 @@ async function resolveEmailCwd(userId: number, ownerEmail: string, accountEmail?
|
||||
}
|
||||
|
||||
// The working directory a chat turn runs in, by context: email → the account dir; /chat → a chosen
|
||||
// pwd or the default claude_sessions dir; everything else (browser/project/dashboard) → the given cwd.
|
||||
// pwd or the default general_chat_sessions dir; everything else (browser/project/dashboard) → the given cwd.
|
||||
async function resolveChatCwd(
|
||||
msg: { context?: string; contextId?: string; cwd?: string },
|
||||
email: string,
|
||||
@@ -86,7 +86,7 @@ async function resolveChatCwd(
|
||||
): Promise<string> {
|
||||
if (msg.context === 'email') return resolveEmailCwd(userId, email, msg.contextId);
|
||||
if (msg.context === 'chat')
|
||||
return msg.cwd?.trim() ? resolveCwd(email, role, msg.cwd) : ensureClaudeSessionsCwd(email);
|
||||
return msg.cwd?.trim() ? resolveCwd(email, role, msg.cwd) : ensureGeneralChatSessionsCwd(email);
|
||||
return resolveCwd(email, role, msg.cwd);
|
||||
}
|
||||
|
||||
@@ -460,8 +460,8 @@ async function handleOpenCodeChat(
|
||||
const onEvent = createEventHandler(sessionId, model, cwd);
|
||||
|
||||
// The dir the OpenCode model should treat as its cwd (via the Officer system prompt). For the general
|
||||
// /chat, the resolved cwd is just the claude_sessions grouping placeholder, so use the user's home.
|
||||
const workingDir = cwd === getClaudeSessionsCwd(email) ? getHomeDirForRole(email, ws.data.role) : cwd;
|
||||
// /chat, the resolved cwd is just the general_chat_sessions grouping placeholder, so use the user's home.
|
||||
const workingDir = cwd === getGeneralChatSessionsCwd(email) ? getHomeDirForRole(email, ws.data.role) : cwd;
|
||||
|
||||
try {
|
||||
const handle = await sendOpenCodeStreaming({
|
||||
|
||||
@@ -25,16 +25,6 @@ export const AGENT_CONFIG_DIR = join(homedir(), '.pi', 'agent');
|
||||
|
||||
export const SEED_PATH = resolve(import.meta.dir, '../../seed');
|
||||
|
||||
export const getUserSessionsDir = (email: string) => join(DATA_PATH, email, 'chat_sessions');
|
||||
|
||||
export const getClaudeDir = (email: string) => join(DATA_PATH, email, 'chat_sessions', 'claude');
|
||||
|
||||
export const getSessionDir = (email: string, sessionId: string) =>
|
||||
join(DATA_PATH, email, 'chat_sessions', 'claude', sessionId);
|
||||
|
||||
export const getArchivedSessionDir = (email: string, sessionId: string) =>
|
||||
join(DATA_PATH, email, 'chat_sessions', 'claude', 'archived', sessionId);
|
||||
|
||||
export const getHomeDir = (email: string) => join(DATA_PATH, email, 'home');
|
||||
|
||||
export const getHomeDirForRole = (email: string, role: string | null): string =>
|
||||
@@ -53,7 +43,8 @@ export const getAttachmentsDir = (email: string, sessionId: string) => join(DATA
|
||||
export const getEmailAccountsDir = (ownerEmail: string) => join(DATA_PATH, ownerEmail, 'email_accounts');
|
||||
export const getEmailDbPath = (ownerEmail: string, accountEmail: string) =>
|
||||
join(getEmailAccountsDir(ownerEmail), accountEmail, 'emails.db');
|
||||
export const getEmailAttachmentCacheDir = (ownerEmail: string) => join(getEmailAccountsDir(ownerEmail), 'attachment_cache');
|
||||
export const getEmailAttachmentCacheDir = (ownerEmail: string) =>
|
||||
join(getEmailAccountsDir(ownerEmail), 'attachment_cache');
|
||||
|
||||
/** Derive a valid Linux username from a display username or email. */
|
||||
export const toShellUsername = (username: string, email: string): string => {
|
||||
|
||||
@@ -55,14 +55,7 @@ export async function spawnClaude(params: ClaudeSpawnParams): Promise<ClaudeCode
|
||||
|
||||
const existingSession = getClaudeSession(sessionKey);
|
||||
|
||||
const claudeArgs = [
|
||||
CLAUDE_BIN,
|
||||
'-p',
|
||||
prompt,
|
||||
'--dangerously-skip-permissions',
|
||||
'--output-format',
|
||||
'json',
|
||||
];
|
||||
const claudeArgs = [CLAUDE_BIN, '-p', prompt, '--dangerously-skip-permissions', '--output-format', 'json'];
|
||||
|
||||
const isSuperAdmin = params.role === 'Super Admin';
|
||||
const mcpConfig = isSuperAdmin ? mcpHostPath : mcpSandboxPath;
|
||||
@@ -76,7 +69,7 @@ export async function spawnClaude(params: ClaudeSpawnParams): Promise<ClaudeCode
|
||||
}
|
||||
|
||||
const spawnCmd = isSuperAdmin ? claudeArgs : [...buildSandboxArgs(email), ...claudeArgs];
|
||||
// Honor a caller-provided working directory (e.g. /chat runs from a dedicated claude_sessions dir);
|
||||
// Honor a caller-provided working directory (e.g. /chat runs from a dedicated general_chat_sessions dir);
|
||||
// fall back to the host home for Super Admin, or the sandbox default otherwise.
|
||||
const spawnCwd = isSuperAdmin ? (params.cwd ?? HOST_HOME) : undefined;
|
||||
|
||||
@@ -179,7 +172,7 @@ export async function spawnClaudeStreaming(
|
||||
}
|
||||
|
||||
const spawnCmd = isSuperAdmin ? claudeArgs : [...buildSandboxArgs(email), ...claudeArgs];
|
||||
// Honor a caller-provided working directory (e.g. /chat runs from a dedicated claude_sessions dir);
|
||||
// Honor a caller-provided working directory (e.g. /chat runs from a dedicated general_chat_sessions dir);
|
||||
// fall back to the host home for Super Admin, or the sandbox default otherwise.
|
||||
const spawnCwd = isSuperAdmin ? (params.cwd ?? HOST_HOME) : undefined;
|
||||
|
||||
|
||||
@@ -69,12 +69,18 @@ function NewChat({ resumeSummary, resumeSessionId, initialMessages }: NewChatPro
|
||||
invalidateClaudeSessions();
|
||||
}, [invalidateClaudeSessions]);
|
||||
|
||||
// context 'chat' tells the backend to run this session from the dedicated claude_sessions cwd, so
|
||||
// context 'chat' tells the backend to run this session from the dedicated general_chat_sessions cwd, so
|
||||
// its transcript lands in Claude's own store as an isolated project group (source of truth).
|
||||
const chat = useChat(undefined, locationState?.model, { resumeSummary, resumeSessionId, initialMessages, onTurnComplete, context: 'chat' });
|
||||
const chat = useChat(undefined, locationState?.model, {
|
||||
resumeSummary,
|
||||
resumeSessionId,
|
||||
initialMessages,
|
||||
onTurnComplete,
|
||||
context: 'chat',
|
||||
});
|
||||
|
||||
const sandboxed = !isSuperAdmin;
|
||||
// Run the session in the pwd chosen in the Sessions panel; null → backend default (claude_sessions).
|
||||
// Run the session in the pwd chosen in the Sessions panel; null → backend default (general_chat_sessions).
|
||||
const [activeCwd] = usePanelChannel<string | null>('chat:active-cwd', null);
|
||||
const cwd = activeCwd ? { path: activeCwd } : locationState?.cwd;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useChatPwds } from 'state/useClaudeSessions';
|
||||
import { DirPickerModal } from './DirPickerModal';
|
||||
|
||||
type PwdSelectorProps = {
|
||||
value: string | null; // null = the default claude_sessions dir
|
||||
value: string | null; // null = the default general_chat_sessions dir
|
||||
onChange: (cwd: string | null) => void;
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ export const PwdSelector = ({ value, onChange }: PwdSelectorProps) => {
|
||||
const [custom, setCustom] = useState('');
|
||||
|
||||
const isDefaultActive = value === null || value === defaultCwd;
|
||||
const label = isDefaultActive ? 'claude_sessions' : basename(value!);
|
||||
const label = isDefaultActive ? 'general_chat_sessions' : basename(value!);
|
||||
|
||||
const pick = (cwd: string | null) => {
|
||||
onChange(cwd);
|
||||
@@ -35,7 +35,7 @@ export const PwdSelector = ({ value, onChange }: PwdSelectorProps) => {
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
title={isDefaultActive ? defaultCwd ?? 'Default' : value!}
|
||||
title={isDefaultActive ? (defaultCwd ?? 'Default') : value!}
|
||||
className="flex items-center gap-1.5 max-w-[13rem] rounded-md border border-duck-dark/10 dark:border-foreground/10 px-2 py-1 text-xs text-duck-dark/70 dark:text-foreground/70 hover:bg-duck-dark/5 dark:hover:bg-foreground/5 cursor-pointer transition-colors"
|
||||
>
|
||||
<FolderOpen className="h-3.5 w-3.5 shrink-0 text-duck-teal/70" />
|
||||
@@ -58,7 +58,9 @@ export const PwdSelector = ({ value, onChange }: PwdSelectorProps) => {
|
||||
className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs cursor-pointer hover:bg-duck-dark/5 dark:hover:bg-foreground/5 ${selected ? 'text-duck-teal' : 'text-duck-dark/70 dark:text-foreground/70'}`}
|
||||
>
|
||||
<Check className={`h-3.5 w-3.5 shrink-0 ${selected ? 'opacity-100' : 'opacity-0'}`} />
|
||||
<span className="min-w-0 flex-1 truncate">{p.isDefault ? 'Default · claude_sessions' : shorten(p.cwd)}</span>
|
||||
<span className="min-w-0 flex-1 truncate">
|
||||
{p.isDefault ? 'Default · general_chat_sessions' : shorten(p.cwd)}
|
||||
</span>
|
||||
{p.sessionCount > 0 && <span className="shrink-0 opacity-40">{p.sessionCount}</span>}
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ import { PwdSelector } from './PwdSelector';
|
||||
// Clicking a session loads its transcript and continues the real Claude session via --resume.
|
||||
export const SessionList = () => {
|
||||
const navigate = useNavigate();
|
||||
// The working directory the list operates on (null = the default claude_sessions dir).
|
||||
// The working directory the list operates on (null = the default general_chat_sessions dir).
|
||||
const [activeCwd, setActiveCwd] = usePanelChannel<string | null>('chat:active-cwd', null);
|
||||
const { sessions, isLoading, refetch, loadSession, deleteSession, renameSession } = useClaudeSessions(activeCwd);
|
||||
const [selected, setSelected] = usePanelChannel<SelectedSession>('chat:selected-session', null);
|
||||
@@ -125,10 +125,20 @@ export const SessionList = () => {
|
||||
onBlur={commitRename}
|
||||
className="flex-1 min-w-0 bg-transparent border-b border-duck-teal/40 text-sm outline-none"
|
||||
/>
|
||||
<button onMouseDown={(ev) => ev.preventDefault()} onClick={commitRename} className="p-1 text-duck-teal hover:opacity-80 cursor-pointer" title="Save">
|
||||
<button
|
||||
onMouseDown={(ev) => ev.preventDefault()}
|
||||
onClick={commitRename}
|
||||
className="p-1 text-duck-teal hover:opacity-80 cursor-pointer"
|
||||
title="Save"
|
||||
>
|
||||
<Check className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<button onMouseDown={(ev) => ev.preventDefault()} onClick={() => setEditingId(null)} className="p-1 opacity-50 hover:opacity-100 cursor-pointer" title="Cancel">
|
||||
<button
|
||||
onMouseDown={(ev) => ev.preventDefault()}
|
||||
onClick={() => setEditingId(null)}
|
||||
className="p-1 opacity-50 hover:opacity-100 cursor-pointer"
|
||||
title="Cancel"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -145,7 +155,9 @@ export const SessionList = () => {
|
||||
<MessageSquare className="h-4 w-4 shrink-0 text-duck-teal/60" />
|
||||
)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm font-medium text-duck-dark/80 dark:text-foreground/80 truncate">{session.title}</div>
|
||||
<div className="text-sm font-medium text-duck-dark/80 dark:text-foreground/80 truncate">
|
||||
{session.title}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-duck-dark/40 dark:text-foreground/40">
|
||||
<span>
|
||||
{new Date(session.updatedAt).toLocaleDateString(undefined, {
|
||||
@@ -157,7 +169,9 @@ export const SessionList = () => {
|
||||
</span>
|
||||
<span>·</span>
|
||||
{session.harness === 'opencode' ? (
|
||||
<span className="rounded bg-duck-teal/10 px-1.5 py-0.5 font-medium text-duck-teal">OpenCode</span>
|
||||
<span className="rounded bg-duck-teal/10 px-1.5 py-0.5 font-medium text-duck-teal">
|
||||
OpenCode
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
{session.messageCount} msg{session.messageCount === 1 ? '' : 's'}
|
||||
@@ -170,10 +184,18 @@ export const SessionList = () => {
|
||||
{isConfirming ? (
|
||||
<div className="flex shrink-0 items-center gap-1 mr-2">
|
||||
<span className="text-xs text-red-500">Delete?</span>
|
||||
<button onClick={() => handleDelete(session.id)} className="p-1 rounded text-red-500 hover:bg-red-500/10 cursor-pointer" title="Confirm delete">
|
||||
<button
|
||||
onClick={() => handleDelete(session.id)}
|
||||
className="p-1 rounded text-red-500 hover:bg-red-500/10 cursor-pointer"
|
||||
title="Confirm delete"
|
||||
>
|
||||
<Check className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<button onClick={() => setConfirmingId(null)} className="p-1 rounded opacity-50 hover:opacity-100 cursor-pointer" title="Cancel">
|
||||
<button
|
||||
onClick={() => setConfirmingId(null)}
|
||||
className="p-1 rounded opacity-50 hover:opacity-100 cursor-pointer"
|
||||
title="Cancel"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -19,13 +19,20 @@ export type ClaudeSessionSummary = {
|
||||
export type ClaudeSessionMessage =
|
||||
| { role: 'user'; text: string }
|
||||
| { role: 'assistant'; id: string; text: string }
|
||||
| { role: 'tool'; toolName: string; toolInput: Record<string, unknown>; toolCallId: string; output?: string; isError?: boolean };
|
||||
| {
|
||||
role: 'tool';
|
||||
toolName: string;
|
||||
toolInput: Record<string, unknown>;
|
||||
toolCallId: string;
|
||||
output?: string;
|
||||
isError?: boolean;
|
||||
};
|
||||
|
||||
export type ClaudeSessionDetail = { id: string; model: string; cwd: string; messages: ClaudeSessionMessage[] };
|
||||
|
||||
export type ClaudePwd = { cwd: string; sessionCount: number; updatedAt: string; isDefault: boolean };
|
||||
|
||||
// Query string for the selected working directory (null/undefined = the default claude_sessions dir).
|
||||
// Query string for the selected working directory (null/undefined = the default general_chat_sessions dir).
|
||||
const cwdQuery = (cwd?: string | null) => (cwd ? `?cwd=${encodeURIComponent(cwd)}` : '');
|
||||
|
||||
/** The default /chat dir plus every directory that already has Claude sessions. */
|
||||
|
||||
Reference in New Issue
Block a user