pi session context persistence, fix new session button, add clear all sessions

- handle pi process death by nulling piProcess ref so next message respawns
- replay conversation history on respawn via --session flag
- fix user message JSONL format to array for pi compatibility
- fix container sessions mount to match storage path
- fix ChatPanelWrapper: key on inner component so usePiChat resets on new session
- add bulk delete sessions endpoint and clear all button in chat header

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 00:04:14 +00:00
co-authored by Claude Opus 4.6
parent a8d3d1a423
commit c443fe0fe2
9 changed files with 192 additions and 31 deletions
+61 -4
View File
@@ -286,8 +286,37 @@ async function handleChat(
if (!session.piProcess) {
try {
const onEvent = createEventHandler(sessionId, model, cwd, homeDir);
session.piProcess = await piBridge.spawnPi(cwd, model, userId, email, onEvent, sandboxed ? { userId, username, email, homeDir } : undefined);
logger.info('Spawned Pi process for session', { sessionId, model, cwd, sandboxed });
const sandbox = sandboxed ? { userId, username, email, homeDir } : undefined;
// If session has history, save to disk and pass --session for context replay
let spawnOptions: { sessionFile?: string } | undefined;
if (session.messages.length > 0) {
await storage.saveSession(homeDir, sessionId, session.meta, session.messages);
const hostPath = await storage.getSessionFilePath(homeDir, sessionId);
if (hostPath) {
if (sandboxed) {
const containerHome = `/home/${username}`;
const sessionsPrefix = join(homeDir, '.pi', 'agent', 'sessions');
const relativePart = hostPath.slice(sessionsPrefix.length);
spawnOptions = { sessionFile: `${containerHome}/.pi/agent/sessions${relativePart}` };
} else {
spawnOptions = { sessionFile: hostPath };
}
}
}
session.piProcess = await piBridge.spawnPi(cwd, model, userId, email, onEvent, sandbox, spawnOptions);
// Null out piProcess when the process dies so next message triggers respawn
const proc = session.piProcess;
proc.exited.then(() => {
if (session.piProcess === proc) {
session.piProcess = null;
logger.info('Pi process exited, nulled reference', { sessionId });
}
});
logger.info('Spawned Pi process for session', { sessionId, model, cwd, sandboxed, hasSessionFile: !!spawnOptions?.sessionFile });
} catch (err) {
logger.error('Failed to spawn Pi process', { sessionId, model, error: String(err) });
sendToClient(ws, { type: 'error', message: 'Failed to start Pi process' });
@@ -361,8 +390,36 @@ async function handleResume(
const homeDir = getHomeDir(email);
const sandbox = session.sandboxed && session.userId ? { userId: session.userId, username: ws.data.username, email, homeDir } : undefined;
const onEvent = createEventHandler(sessionId, session.model, session.cwd, homeDir);
session.piProcess = await piBridge.spawnPi(session.cwd, session.model, session.userId!, email, onEvent, sandbox);
logger.info('Spawned fresh Pi process for resumed session', { sessionId, model: session.model, sandboxed: session.sandboxed });
// If session has history, save to disk and pass --session for context replay
let spawnOptions: { sessionFile?: string } | undefined;
if (session.messages.length > 0) {
await storage.saveSession(homeDir, sessionId, session.meta, session.messages);
const hostPath = await storage.getSessionFilePath(homeDir, sessionId);
if (hostPath) {
if (sandbox) {
const containerHome = `/home/${ws.data.username}`;
const sessionsPrefix = join(homeDir, '.pi', 'agent', 'sessions');
const relativePart = hostPath.slice(sessionsPrefix.length);
spawnOptions = { sessionFile: `${containerHome}/.pi/agent/sessions${relativePart}` };
} else {
spawnOptions = { sessionFile: hostPath };
}
}
}
session.piProcess = await piBridge.spawnPi(session.cwd, session.model, session.userId!, email, onEvent, sandbox, spawnOptions);
// Null out piProcess when the process dies so next message triggers respawn
const proc = session.piProcess;
proc.exited.then(() => {
if (session.piProcess === proc) {
session.piProcess = null;
logger.info('Pi process exited, nulled reference', { sessionId });
}
});
logger.info('Spawned fresh Pi process for resumed session', { sessionId, model: session.model, sandboxed: session.sandboxed, hasSessionFile: !!spawnOptions?.sessionFile });
} catch (err) {
logger.error('Failed to spawn Pi process for resume', { sessionId, error: String(err) });
sendToClient(ws, { type: 'error', message: 'Failed to start Pi process' });