lvwerra's picture
lvwerra HF Staff
Decouple names from folders (explicit paths + folder picker, visual-only groups), pin Codex conversations, fix drag-into-group, async usage, WS origin check, secrets filter
fd0a8b6 verified
Raw
History Blame Contribute Delete
2.62 kB
import fs from 'node:fs';
import crypto from 'node:crypto';
import { SESSIONS_FILE, allocFolder } from './config.js';
let sessions = [];
function load() {
try {
sessions = JSON.parse(fs.readFileSync(SESSIONS_FILE, 'utf8'));
if (!Array.isArray(sessions)) sessions = [];
} catch {
sessions = [];
}
}
function persist() {
const tmp = `${SESSIONS_FILE}.tmp`;
fs.writeFileSync(tmp, JSON.stringify(sessions, null, 2));
fs.renameSync(tmp, SESSIONS_FILE);
}
function slugify(name) {
return (
name
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.slice(0, 32) || 'session'
);
}
export function init() {
load();
let changed = false;
for (const s of sessions) {
if (!s.sessionUuid) { s.sessionUuid = crypto.randomUUID(); changed = true; }
}
if (changed) persist();
// NOTE: `path` migration from the old folder/group-folder model happens in
// index.js (it needs the groups store).
}
export function list() {
return sessions.slice();
}
export function get(id) {
return sessions.find((s) => s.id === id) || null;
}
export function create({ name, cli, path }) {
const cleanName = (name || '').trim() || 'session';
const id = `${slugify(cleanName)}-${crypto.randomBytes(3).toString('hex')}`;
// `path` is the workspace-relative folder the agent runs in, chosen at
// creation (or defaulted by the caller). It is a plain recorded value — the
// display name can change freely without touching disk, and nothing tracks
// the folder afterwards (deleted/moved folders are simply re-created empty on
// next start). Files agents may use null = browse the whole workspace root.
const session = {
id,
name: cleanName,
cli,
path: path ?? (cli === 'files' ? null : allocFolder(cleanName)),
// Stable per-session conversation id. Lets agents that share a folder (a
// group) each resume their OWN conversation instead of all latching onto
// the most-recent one in that directory.
sessionUuid: crypto.randomUUID(),
createdAt: new Date().toISOString(),
everStarted: false,
};
sessions.push(session);
persist();
return session;
}
export function update(id, patch) {
const s = get(id);
if (!s) return null;
Object.assign(s, patch);
persist();
return s;
}
export function remove(id) {
const before = sessions.length;
sessions = sessions.filter((s) => s.id !== id);
if (sessions.length !== before) persist();
// NOTE: the working directory under DATA_DIR/workspaces/<id> is intentionally
// left on disk so a delete never destroys the user's files.
}