import { readFile, writeFile, mkdir } from "fs/promises";
import { join, isAbsolute, resolve } from "path";
import * as os from "os";
const CONFIG_FILE_NAME = ".plugin_state.json";
const DEFAULT_DIR = join(os.homedir(), ".lm-studio-toolbox", "workspace");
export interface PluginState {
currentWorkingDirectory: string;
messageCount: number;
dontAskToCompress: boolean;
subAgentDocsInjected: boolean;
/** Locale ID to use for Layer 1 (Config UI) on next plugin restart. "auto" = OS detection. */
uiLanguageOverride: string;
// ── M.3: Session persistence enrichment ────────────────────────────────────
/** Last URL visited in a browser session. Injected into first-turn context on resume. */
lastBrowserUrl?: string;
/** Free-text note the model saves before a context reset (via save_session_note). */
sessionNotes?: string;
/** Last N files written by save_file — gives the model a quick "what was I working on" hint. */
recentFiles?: string[];
/** N.13: Rolling buffer of recent user messages used by auto-capture memory. Capped at 15. */
recentUserMessages?: string[];
}
function resolveWorkspaceDirectory(configuredWorkspacePath?: string): string {
const raw = (configuredWorkspacePath ?? "").trim();
if (!raw) return DEFAULT_DIR;
// Support Windows-style env vars in config values (e.g. %USERPROFILE%\projects\workspace)
const expanded = raw.replace(/%([^%]+)%/g, (_match, varName: string) => process.env[varName] ?? `%${varName}%`);
return isAbsolute(expanded) ? expanded : resolve(DEFAULT_DIR, expanded);
}
export async function getPersistedState(configuredWorkspacePath?: string): Promise<PluginState> {
const configuredDirectory = resolveWorkspaceDirectory(configuredWorkspacePath);
// If the user has explicitly set a workspace path in the plugin config, it
// always wins over whatever was last persisted. The persisted CWD is only
// used as "remember where I left off" when no workspace is configured.
const hasExplicitConfig = Boolean(configuredWorkspacePath?.trim());
try {
const statePath = join(os.homedir(), ".lm-studio-toolbox", CONFIG_FILE_NAME);
const content = await readFile(statePath, "utf-8");
const state = JSON.parse(content);
return {
currentWorkingDirectory: hasExplicitConfig
? configuredDirectory
: (state.currentWorkingDirectory ?? configuredDirectory),
messageCount: state.messageCount ?? 0,
dontAskToCompress: state.dontAskToCompress ?? false,
subAgentDocsInjected: state.subAgentDocsInjected ?? false,
uiLanguageOverride: state.uiLanguageOverride ?? "auto",
lastBrowserUrl: state.lastBrowserUrl,
sessionNotes: state.sessionNotes,
recentFiles: state.recentFiles,
};
} catch {
return {
currentWorkingDirectory: configuredDirectory,
messageCount: 0,
dontAskToCompress: false,
subAgentDocsInjected: false,
uiLanguageOverride: "auto",
};
}
}
export async function savePersistedState(state: PluginState) {
try {
const statePath = join(os.homedir(), ".lm-studio-toolbox", CONFIG_FILE_NAME);
const dir = join(os.homedir(), ".lm-studio-toolbox");
await mkdir(dir, { recursive: true });
await writeFile(statePath, JSON.stringify(state, null, 2), "utf-8");
} catch (error) {
console.error("Failed to save plugin state:", error);
}
}
export async function ensureWorkspaceExists(path: string) {
try {
await mkdir(path, { recursive: true });
} catch (error) {
console.error(`Failed to create/access directory ${path}`, error);
}
}