This commit is contained in:
seb
2026-10-06 05:19:16 +02:00
parent fe5990af9f
commit 1be561376e
6 changed files with 151 additions and 6 deletions

View File

@@ -2,6 +2,7 @@ import fs from 'fs';
import path from 'path';
import { getStore } from '../db/store.js';
import env from '../config/env.js';
import { isSkillPath, getDefaultSkills } from './skill.js';
const FULL_HASH_REGEX = /^[a-fA-F0-9]{64}$/;
const VIRTUAL_IMAGE_FOLDERS = new Set(['image', 'images']);
@@ -96,8 +97,72 @@ async function vfsRootFor(store, googleAuthId) {
return path.resolve(process.cwd(), 'data/vfs', String(companyId));
}
/**
* Loads default SKILL files into the selected company's VFS (both modelvfs table and disk cache).
*
* @param {{ googleAuthId: number, store?: object, vfsRoot?: string }} ctx
* @returns {Promise<Array<string>>} List of loaded skill file names
*/
export async function loadDefaultSkills({ googleAuthId, store: passedStore, vfsRoot: passedVfsRoot } = {}) {
const store = passedStore || await getStore();
const vfsRoot = passedVfsRoot || await vfsRootFor(store, googleAuthId);
fs.mkdirSync(vfsRoot, { recursive: true });
const defaultSkills = getDefaultSkills();
const loaded = [];
for (const skill of defaultSkills) {
try {
await store.modelvfs.write(googleAuthId, {
name: skill.name,
mime: 'text/markdown',
content: Buffer.from(skill.content, 'utf8'),
});
} catch (err) {
console.error(`[vfs] failed to write default skill "${skill.name}" to modelvfs:`, err.message);
}
try {
const diskPath = path.resolve(vfsRoot, skill.name);
fs.writeFileSync(diskPath, skill.content, 'utf8');
loaded.push(skill.name);
} catch (err) {
console.error(`[vfs] failed to write default skill "${skill.name}" to disk:`, err.message);
}
}
return loaded;
}
/**
* Initializes the VFS for the selected company with default SKILL files and marks it initialized.
*
* @param {{ googleAuthId: number, store?: object, vfsRoot?: string }} ctx
* @returns {Promise<string>} vfsRoot directory path
*/
export async function initCompanyVfs({ googleAuthId, store: passedStore, vfsRoot: passedVfsRoot } = {}) {
const store = passedStore || await getStore();
const vfsRoot = passedVfsRoot || await vfsRootFor(store, googleAuthId);
fs.mkdirSync(vfsRoot, { recursive: true });
await loadDefaultSkills({ googleAuthId, store, vfsRoot });
try {
await store.modelvfs.write(googleAuthId, {
name: '.vfs_initialized',
mime: 'text/plain',
content: Buffer.from('1', 'utf8'),
});
} catch (err) {
console.warn('[vfs] failed to mark .vfs_initialized in modelvfs:', err.message);
}
return vfsRoot;
}
/**
* Ensure the local directory for this company's VFS exists and is hydrated from modelvfs.
* If the company has no initialized VFS and no skills yet, loads default skills automatically.
*
* @param {{ googleAuthId: number }} ctx
* @returns {Promise<string>} vfsRoot directory path
@@ -108,8 +173,25 @@ export async function prepareVfs({ googleAuthId }) {
fs.mkdirSync(vfsRoot, { recursive: true });
try {
const files = await store.modelvfs.list(googleAuthId);
let files = await store.modelvfs.list(googleAuthId);
const hasInitMarker = files.some((f) => f.name === '.vfs_initialized');
const hasAnySkills = files.some((f) => isSkillPath(f.name));
if (!hasInitMarker && !hasAnySkills) {
await initCompanyVfs({ googleAuthId, store, vfsRoot });
files = await store.modelvfs.list(googleAuthId);
} else if (!hasInitMarker) {
try {
await store.modelvfs.write(googleAuthId, {
name: '.vfs_initialized',
mime: 'text/plain',
content: Buffer.from('1', 'utf8'),
});
} catch { /* ignore */ }
}
for (const file of files) {
if (file.name.startsWith('.')) continue;
const diskPath = path.resolve(vfsRoot, file.name);
if (!diskPath.startsWith(vfsRoot)) continue;
if (!fs.existsSync(diskPath)) {
@@ -127,6 +209,7 @@ export async function prepareVfs({ googleAuthId }) {
return vfsRoot;
}
/**
* Syncs any modified or newly written files from the disk vfsRoot back to the modelvfs table.
*