From 1d1017e44bf71e1cfb31434589f9ff02fe1aeb37 Mon Sep 17 00:00:00 2001 From: seb Date: Tue, 6 Oct 2026 10:53:45 +0200 Subject: [PATCH] u --- src/api/index.js | 22 ++++++++++++++++-- src/config/env.js | 2 +- src/services/conversationManager.js | 13 ++++------- src/tools/reset-vfs.js | 6 ----- src/tools/vfs.js | 36 ++++------------------------- 5 files changed, 30 insertions(+), 49 deletions(-) diff --git a/src/api/index.js b/src/api/index.js index 5b108e5..aedf875 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -46,6 +46,20 @@ let httpsServer; let starting; let startedAt; +function isAllowedOrigin(origin) { + if (!origin) return false; + if (env.corsOrigins.includes(origin)) return true; + try { + const u = new URL(origin); + if (u.hostname === 'quixpos.com' || u.hostname.endsWith('.quixpos.com')) { + return true; + } + } catch { + // ignore invalid URL + } + return false; +} + function corsMiddleware(req, res, next) { const origin = req.headers.origin; @@ -59,11 +73,15 @@ function corsMiddleware(req, res, next) { return next(); } - if (origin && env.corsOrigins.includes(origin)) { + if (origin && isAllowedOrigin(origin)) { res.setHeader('Access-Control-Allow-Origin', origin); res.setHeader('Vary', 'Origin'); res.setHeader('Access-Control-Allow-Credentials', 'true'); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); + const reqHeaders = req.headers['access-control-request-headers']; + res.setHeader( + 'Access-Control-Allow-Headers', + reqHeaders || 'Content-Type, Authorization, x-auth-token, authtoken, auth-token, token, x-token' + ); res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS'); } diff --git a/src/config/env.js b/src/config/env.js index 0488770..9c6f3aa 100644 --- a/src/config/env.js +++ b/src/config/env.js @@ -18,7 +18,7 @@ export default { httpsKeyPath: process.env.HTTPS_KEY_PATH || resolve(__dirname, '../../certs/key.pem'), httpsCertPath: process.env.HTTPS_CERT_PATH || resolve(__dirname, '../../certs/cert.pem'), bodyLimit: process.env.BODY_LIMIT || '100mb', - corsOrigins: (process.env.CORS_ORIGINS || 'http://localhost:5173') + corsOrigins: (process.env.CORS_ORIGINS || 'http://localhost:5173,https://quixpos.com,https://app.quixpos.com') .split(',') .map((s) => s.trim()) .filter(Boolean), diff --git a/src/services/conversationManager.js b/src/services/conversationManager.js index 2c3150f..ada84ef 100644 --- a/src/services/conversationManager.js +++ b/src/services/conversationManager.js @@ -11,7 +11,7 @@ import { createDashbackRequestTool } from '../tools/dashback-request.js'; import { createAskUserTool } from '../tools/ask-user.js'; import { createJavascriptExecuteTool } from '../tools/javascript.js'; import { createResetVfsTool } from '../tools/reset-vfs.js'; -import { prepareVfs, resolveVfsRoot } from '../tools/vfs.js'; +import { resolveVfsRoot } from '../tools/vfs.js'; import { listSkillFiles } from '../tools/skill.js'; import { createApiSuggestionsOrIssuesTool } from '../tools/apisuggestionsorissues.js'; import { createFindFilesTool } from '../tools/find-files.js'; @@ -446,16 +446,13 @@ export class ConversationManager { // Transcript comes from the database (includes user, assistant, tool, reasoning). const history = await db.listMessages(googleAuthId, this.conversationId); - // Resolve and prepare the VFS root so the prompt can list the company's SKILL files. - // A failure here must not abort the turn — the prompt simply falls back to "no skills". + // Resolve the VFS root so the prompt can list the company's SKILL files. A failure + // here must not abort the turn — the prompt simply falls back to "no skills". let vfsRoot = null; try { - vfsRoot = await prepareVfs({ googleAuthId }); + vfsRoot = await resolveVfsRoot(googleAuthId); } catch (err) { - console.warn('conversation: could not prepare VFS root for skill index:', err.message); - try { - vfsRoot = await resolveVfsRoot(googleAuthId); - } catch { /* ignore fallback error */ } + console.warn('conversation: could not resolve VFS root for skill index:', err.message); } const messages = [ diff --git a/src/tools/reset-vfs.js b/src/tools/reset-vfs.js index cbe3808..c7142dc 100644 --- a/src/tools/reset-vfs.js +++ b/src/tools/reset-vfs.js @@ -50,9 +50,6 @@ export function createResetVfsTool({ googleAuthId }) { // touching disk so a store failure cannot leave the disk wiped while the database // still claims the files exist. for (const file of existing) { - if (file.name === '.vfs_initialized') { - continue; - } if (keepSkills && isSkillPath(file.name)) { keptSkills.push(file.name); continue; @@ -66,9 +63,6 @@ export function createResetVfsTool({ googleAuthId }) { let deletedCount = 0; if (fs.existsSync(vfsRoot)) { for (const entry of fs.readdirSync(vfsRoot)) { - if (entry === '.vfs_initialized') { - continue; - } // Only root-level entries can be SKILL files, so the bare name is enough // to decide here — nested directories are always disposable. if (keepSkills && isSkillPath(entry)) { diff --git a/src/tools/vfs.js b/src/tools/vfs.js index 00e155b..3e92a9c 100644 --- a/src/tools/vfs.js +++ b/src/tools/vfs.js @@ -2,7 +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'; +import { getDefaultSkills } from './skill.js'; const FULL_HASH_REGEX = /^[a-fA-F0-9]{64}$/; const VIRTUAL_IMAGE_FOLDERS = new Set(['image', 'images']); @@ -135,7 +135,7 @@ export async function loadDefaultSkills({ googleAuthId, store: passedStore, vfsR } /** - * Initializes the VFS for the selected company with default SKILL files and marks it initialized. + * Initializes the VFS for the selected company with default SKILL files. * * @param {{ googleAuthId: number, store?: object, vfsRoot?: string }} ctx * @returns {Promise} vfsRoot directory path @@ -146,23 +146,11 @@ export async function initCompanyVfs({ googleAuthId, store: passedStore, vfsRoot 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} vfsRoot directory path @@ -173,25 +161,8 @@ export async function prepareVfs({ googleAuthId }) { fs.mkdirSync(vfsRoot, { recursive: true }); try { - 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 */ } - } - + const files = await store.modelvfs.list(googleAuthId); 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)) { @@ -210,6 +181,7 @@ export async function prepareVfs({ googleAuthId }) { } + /** * Syncs any modified or newly written files from the disk vfsRoot back to the modelvfs table. *