Import sesssions (#9474)

Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
Douwe Osinga
2026-06-02 16:35:57 -04:00
committed by GitHub
parent cd12199604
commit 003252fb52
12 changed files with 1411 additions and 14 deletions
@@ -608,9 +608,32 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(
[intl]
);
const handleImportClick = useCallback(() => {
const handleImportClick = useCallback(async () => {
const native = window.electron?.selectImportSessionFile;
if (typeof native === 'function') {
try {
const result = await native();
if (!result) return;
if (result.error) {
toast.error(intl.formatMessage(i18n.importFailed, { error: result.error }));
return;
}
await importSession({
body: { json: result.contents },
throwOnError: true,
});
toast.success(intl.formatMessage(i18n.importSuccess));
await loadSessions();
} catch (error) {
toast.error(
intl.formatMessage(i18n.importFailed, { error: errorMessage(error, 'Unknown error') })
);
}
return;
}
// Fallback for non-Electron contexts (tests, web build).
fileInputRef.current?.click();
}, []);
}, [intl, loadSessions]);
const handleImportNostrLink = useCallback(async () => {
const deeplink = nostrImportLink.trim();
@@ -1081,7 +1104,7 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(
<input
ref={fileInputRef}
type="file"
accept=".json"
accept=".json,.jsonl,application/json,application/x-ndjson"
onChange={handleImportSession}
className="hidden"
/>
+27
View File
@@ -1879,6 +1879,33 @@ ipcMain.handle('select-file-or-directory', async (_event, defaultPath?: string)
return null;
});
// Native picker tailored for session imports: shows hidden files (so users can
// reach `~/.claude/projects/...` or `~/.pi/agent/sessions/...`), filters for
// .json/.jsonl, and returns the file's contents inline so the renderer doesn't
// need a separate read step.
ipcMain.handle('select-import-session-file', async () => {
const result = (await dialog.showOpenDialog({
title: 'Import session',
defaultPath: os.homedir(),
properties: ['openFile', 'showHiddenFiles'],
filters: [
{ name: 'Session files', extensions: ['json', 'jsonl'] },
{ name: 'All files', extensions: ['*'] },
],
})) as unknown as OpenDialogReturnValue;
if (result.canceled || result.filePaths.length === 0) {
return null;
}
const filePath = result.filePaths[0];
try {
const contents = await fs.readFile(filePath, 'utf8');
return { filePath, contents };
} catch (err) {
return { filePath, contents: '', error: errorMessage(err) };
}
});
// ── Mesh-LLM lifecycle (see mesh.ts) ────────────────────────────────
ipcMain.handle('check-mesh', () => mesh.check());
+6
View File
@@ -126,6 +126,11 @@ type ElectronAPI = {
startMesh: (args: string[]) => Promise<{ started: boolean; error?: string; pid?: number }>;
stopMesh: () => Promise<{ stopped: boolean }>;
selectFileOrDirectory: (defaultPath?: string) => Promise<string | null>;
selectImportSessionFile: () => Promise<{
filePath: string;
contents: string;
error?: string;
} | null>;
getBinaryPath: (binaryName: string) => Promise<string>;
readFile: (directory: string) => Promise<FileResponse>;
writeFile: (directory: string, content: string) => Promise<boolean>;
@@ -223,6 +228,7 @@ const electronAPI: ElectronAPI = {
selectFileOrDirectory: (defaultPath?: string) =>
ipcRenderer.invoke('select-file-or-directory', defaultPath),
selectImportSessionFile: () => ipcRenderer.invoke('select-import-session-file'),
getBinaryPath: (binaryName: string) => ipcRenderer.invoke('get-binary-path', binaryName),
readFile: (filePath: string) => ipcRenderer.invoke('read-file', filePath),
writeFile: (filePath: string, content: string) =>