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({
|
||||
|
||||
Reference in New Issue
Block a user