88 lines
2.3 KiB
TypeScript
88 lines
2.3 KiB
TypeScript
import { join } from "path";
|
|
import { unlink, readdir, rename } from "fs/promises";
|
|
|
|
const HEARTBEAT_DIR = join(process.cwd(), ".qwen", "qwenclaw");
|
|
const SESSION_FILE = join(HEARTBEAT_DIR, "session.json");
|
|
|
|
export interface GlobalSession {
|
|
sessionId: string;
|
|
createdAt: string;
|
|
lastUsedAt: string;
|
|
}
|
|
|
|
let current: GlobalSession | null = null;
|
|
|
|
async function loadSession(): Promise<GlobalSession | null> {
|
|
if (current) return current;
|
|
try {
|
|
current = await Bun.file(SESSION_FILE).json();
|
|
return current;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function saveSession(session: GlobalSession): Promise<void> {
|
|
current = session;
|
|
await Bun.write(SESSION_FILE, JSON.stringify(session, null, 2) + "\n");
|
|
}
|
|
|
|
/** Returns the existing session or null. Never creates one. */
|
|
export async function getSession(): Promise<{ sessionId: string } | null> {
|
|
const existing = await loadSession();
|
|
if (existing) {
|
|
existing.lastUsedAt = new Date().toISOString();
|
|
await saveSession(existing);
|
|
return { sessionId: existing.sessionId };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Save a session ID obtained from Qwen's output. */
|
|
export async function createSession(sessionId: string): Promise<void> {
|
|
await saveSession({
|
|
sessionId,
|
|
createdAt: new Date().toISOString(),
|
|
lastUsedAt: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
/** Returns session metadata without mutating lastUsedAt. */
|
|
export async function peekSession(): Promise<GlobalSession | null> {
|
|
return await loadSession();
|
|
}
|
|
|
|
export async function resetSession(): Promise<void> {
|
|
current = null;
|
|
try {
|
|
await unlink(SESSION_FILE);
|
|
} catch {
|
|
// already gone
|
|
}
|
|
}
|
|
|
|
export async function backupSession(): Promise<string | null> {
|
|
const existing = await loadSession();
|
|
if (!existing) return null;
|
|
|
|
// Find next backup index
|
|
let files: string[];
|
|
try {
|
|
files = await readdir(HEARTBEAT_DIR);
|
|
} catch {
|
|
files = [];
|
|
}
|
|
|
|
const indices = files
|
|
.filter((f) => /^session_\d+\.backup$/.test(f))
|
|
.map((f) => Number(f.match(/^session_(\d+)\.backup$/)![1]));
|
|
|
|
const nextIndex = indices.length > 0 ? Math.max(...indices) + 1 : 1;
|
|
const backupName = `session_${nextIndex}.backup`;
|
|
const backupPath = join(HEARTBEAT_DIR, backupName);
|
|
|
|
await rename(SESSION_FILE, backupPath);
|
|
current = null;
|
|
return backupName;
|
|
}
|