u
This commit is contained in:
@@ -46,6 +46,20 @@ let httpsServer;
|
|||||||
let starting;
|
let starting;
|
||||||
let startedAt;
|
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) {
|
function corsMiddleware(req, res, next) {
|
||||||
const origin = req.headers.origin;
|
const origin = req.headers.origin;
|
||||||
|
|
||||||
@@ -59,11 +73,15 @@ function corsMiddleware(req, res, next) {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (origin && env.corsOrigins.includes(origin)) {
|
if (origin && isAllowedOrigin(origin)) {
|
||||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||||
res.setHeader('Vary', 'Origin');
|
res.setHeader('Vary', 'Origin');
|
||||||
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
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');
|
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default {
|
|||||||
httpsKeyPath: process.env.HTTPS_KEY_PATH || resolve(__dirname, '../../certs/key.pem'),
|
httpsKeyPath: process.env.HTTPS_KEY_PATH || resolve(__dirname, '../../certs/key.pem'),
|
||||||
httpsCertPath: process.env.HTTPS_CERT_PATH || resolve(__dirname, '../../certs/cert.pem'),
|
httpsCertPath: process.env.HTTPS_CERT_PATH || resolve(__dirname, '../../certs/cert.pem'),
|
||||||
bodyLimit: process.env.BODY_LIMIT || '100mb',
|
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(',')
|
.split(',')
|
||||||
.map((s) => s.trim())
|
.map((s) => s.trim())
|
||||||
.filter(Boolean),
|
.filter(Boolean),
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { createDashbackRequestTool } from '../tools/dashback-request.js';
|
|||||||
import { createAskUserTool } from '../tools/ask-user.js';
|
import { createAskUserTool } from '../tools/ask-user.js';
|
||||||
import { createJavascriptExecuteTool } from '../tools/javascript.js';
|
import { createJavascriptExecuteTool } from '../tools/javascript.js';
|
||||||
import { createResetVfsTool } from '../tools/reset-vfs.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 { listSkillFiles } from '../tools/skill.js';
|
||||||
import { createApiSuggestionsOrIssuesTool } from '../tools/apisuggestionsorissues.js';
|
import { createApiSuggestionsOrIssuesTool } from '../tools/apisuggestionsorissues.js';
|
||||||
import { createFindFilesTool } from '../tools/find-files.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).
|
// Transcript comes from the database (includes user, assistant, tool, reasoning).
|
||||||
const history = await db.listMessages(googleAuthId, this.conversationId);
|
const history = await db.listMessages(googleAuthId, this.conversationId);
|
||||||
|
|
||||||
// Resolve and prepare the VFS root so the prompt can list the company's SKILL files.
|
// Resolve the VFS root so the prompt can list the company's SKILL files. A failure
|
||||||
// A failure here must not abort the turn — the prompt simply falls back to "no skills".
|
// here must not abort the turn — the prompt simply falls back to "no skills".
|
||||||
let vfsRoot = null;
|
let vfsRoot = null;
|
||||||
try {
|
try {
|
||||||
vfsRoot = await prepareVfs({ googleAuthId });
|
vfsRoot = await resolveVfsRoot(googleAuthId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('conversation: could not prepare VFS root for skill index:', err.message);
|
console.warn('conversation: could not resolve VFS root for skill index:', err.message);
|
||||||
try {
|
|
||||||
vfsRoot = await resolveVfsRoot(googleAuthId);
|
|
||||||
} catch { /* ignore fallback error */ }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages = [
|
const messages = [
|
||||||
|
|||||||
@@ -50,9 +50,6 @@ export function createResetVfsTool({ googleAuthId }) {
|
|||||||
// touching disk so a store failure cannot leave the disk wiped while the database
|
// touching disk so a store failure cannot leave the disk wiped while the database
|
||||||
// still claims the files exist.
|
// still claims the files exist.
|
||||||
for (const file of existing) {
|
for (const file of existing) {
|
||||||
if (file.name === '.vfs_initialized') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (keepSkills && isSkillPath(file.name)) {
|
if (keepSkills && isSkillPath(file.name)) {
|
||||||
keptSkills.push(file.name);
|
keptSkills.push(file.name);
|
||||||
continue;
|
continue;
|
||||||
@@ -66,9 +63,6 @@ export function createResetVfsTool({ googleAuthId }) {
|
|||||||
let deletedCount = 0;
|
let deletedCount = 0;
|
||||||
if (fs.existsSync(vfsRoot)) {
|
if (fs.existsSync(vfsRoot)) {
|
||||||
for (const entry of fs.readdirSync(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
|
// Only root-level entries can be SKILL files, so the bare name is enough
|
||||||
// to decide here — nested directories are always disposable.
|
// to decide here — nested directories are always disposable.
|
||||||
if (keepSkills && isSkillPath(entry)) {
|
if (keepSkills && isSkillPath(entry)) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { getStore } from '../db/store.js';
|
import { getStore } from '../db/store.js';
|
||||||
import env from '../config/env.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 FULL_HASH_REGEX = /^[a-fA-F0-9]{64}$/;
|
||||||
const VIRTUAL_IMAGE_FOLDERS = new Set(['image', 'images']);
|
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
|
* @param {{ googleAuthId: number, store?: object, vfsRoot?: string }} ctx
|
||||||
* @returns {Promise<string>} vfsRoot directory path
|
* @returns {Promise<string>} vfsRoot directory path
|
||||||
@@ -146,23 +146,11 @@ export async function initCompanyVfs({ googleAuthId, store: passedStore, vfsRoot
|
|||||||
fs.mkdirSync(vfsRoot, { recursive: true });
|
fs.mkdirSync(vfsRoot, { recursive: true });
|
||||||
|
|
||||||
await loadDefaultSkills({ googleAuthId, store, vfsRoot });
|
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;
|
return vfsRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure the local directory for this company's VFS exists and is hydrated from modelvfs.
|
* 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
|
* @param {{ googleAuthId: number }} ctx
|
||||||
* @returns {Promise<string>} vfsRoot directory path
|
* @returns {Promise<string>} vfsRoot directory path
|
||||||
@@ -173,25 +161,8 @@ export async function prepareVfs({ googleAuthId }) {
|
|||||||
fs.mkdirSync(vfsRoot, { recursive: true });
|
fs.mkdirSync(vfsRoot, { recursive: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let files = await store.modelvfs.list(googleAuthId);
|
const 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) {
|
for (const file of files) {
|
||||||
if (file.name.startsWith('.')) continue;
|
|
||||||
const diskPath = path.resolve(vfsRoot, file.name);
|
const diskPath = path.resolve(vfsRoot, file.name);
|
||||||
if (!diskPath.startsWith(vfsRoot)) continue;
|
if (!diskPath.startsWith(vfsRoot)) continue;
|
||||||
if (!fs.existsSync(diskPath)) {
|
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.
|
* Syncs any modified or newly written files from the disk vfsRoot back to the modelvfs table.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user