This commit is contained in:
seb
2026-10-06 03:39:38 +02:00
parent d5e58e76e1
commit 75ab6ce171

View File

@@ -109,9 +109,9 @@ async function buildSystemPrompt(vfsRoot, googleAuthId) {
`Date: ${today}`,
'',
'YOUR TOOLS:',
'1. dashback_request: HTTP requests (GET, POST, PUT, PATCH, DELETE) against Dashback endpoints.',
'1. dashback_request: HTTP requests (GET, POST, PUT, PATCH, DELETE) against QuixPOS Article Management System.',
'2. ask_user: Prompt user for clarification or decisions. Pauses execution until response.',
'3. javascript_execute: Sandboxed Node environment for computation, aggregation, and binary I/O. Assign module.exports. API: fetch("/path"). VFS: fs/vfs.',
'3. javascript_execute: Sandboxed Node environment for computation, aggregation, and binary I/O. Assign module.exports. API: fetch("/path"). vfs.',
'4. find_files: List VFS files. Set skills=true for SKILL files.',
'5. read_file: Read VFS text file (seek/limit windowed).',
'6. patch: Create, overwrite, or replace text in VFS files (only text write path; creates SKILL files).',
@@ -126,13 +126,12 @@ async function buildSystemPrompt(vfsRoot, googleAuthId) {
'',
'VIRTUAL FILESYSTEM:',
'- Persistent per-company VFS. Paths are root-relative forward-slash (e.g. "labels/sheet1.png").',
'- Text: list with find_files, read with read_file, write with patch. Binary/buffers: javascript_execute.',
'- Never invent paths; verify with find_files before reading or referencing.',
'- Text: list with find_files, read with read_file, write with patch.',
'',
'SKILL FILES:',
'- Reusable instruction docs: "<slug>.SKILL" at VFS root. First line is title, followed by markdown.',
'- Root-only (no "/"). Discover via find_files({ skills: true }).',
'- Read with read_file BEFORE starting covered tasks; follow instructions. Propose saving improved procedures as new SKILL files.',
'- Read with read_file BEFORE starting covered tasks. Propose saving improved procedures as new SKILL files.',
'- Exempt from reset_vfs. Delete permanently via rm.',
'',
buildSkillIndex(vfsRoot),
@@ -143,10 +142,7 @@ async function buildSystemPrompt(vfsRoot, googleAuthId) {
'- `image/` is READ-ONLY database storage. Write generated images elsewhere (e.g. "images/…").',
'',
'OPERATING GUIDELINES:',
'- Endpoint reference: see DASHBACK API ROUTES below.',
'- SELLING PRICES: In article responses (GET /article/{sku}), selling price field is `net` (NOT `price`). Schema in `article.prices`: { minmultiplier, net, taxRate, customergroupId }.',
'- GROSS vs NET PRICES: User prices are almost always GROSS (incl. VAT); API requires NET. Formula: `net = gross / (1 + taxRate / 100)`. Compute in javascript_execute with high precision (4–6 decimals) to prevent rounding error.',
'- WIRE TYPES: Strict validation (IDs, weight, price, quantity must be numbers; flags booleans).',
'- DISPLAYING GENERATED IMAGES: Inline markdown images supported. Embed written VFS image paths: `![descriptive alt](artifacts/example.png)`. Other files (PDF, JSON, CSV) do not render inline; provide download links.',
'- LABEL TEMPLATES (/labeltemplate): Must use Dashback JSON schema `{ "objects": [ ... ] }`. Do NOT use ZPL.',
'- Format output with markdown tables, bullet points, or bold key-value pairs.',
@@ -168,6 +164,38 @@ async function buildSystemPrompt(vfsRoot, googleAuthId) {
* `idle`/`busy` state. The finer SSE states (`thinking`, `working`, `done`)
* and the streamed `delta` chunks are SSE-only.
*/
function parseUtcDate(val) {
if (!val) return new Date();
if (val instanceof Date) return isNaN(val.getTime()) ? new Date() : val;
if (typeof val === 'string') {
const s = val.trim();
if (/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}/.test(s) && !s.endsWith('Z') && !/[+-]\d{2}(:?\d{2})?$/.test(s)) {
const parsed = new Date(s.replace(' ', 'T') + 'Z');
if (!isNaN(parsed.getTime())) return parsed;
}
const parsed = new Date(s);
if (!isNaN(parsed.getTime())) return parsed;
}
return new Date();
}
export function formatTimePrefix(createdDate) {
const d = parseUtcDate(createdDate);
const parts = new Intl.DateTimeFormat('en-GB', {
timeZone: 'Europe/Berlin',
hour: '2-digit',
minute: '2-digit',
hour12: false,
timeZoneName: 'short',
}).formatToParts(d);
const hour = parts.find((p) => p.type === 'hour')?.value.padStart(2, '0') || '00';
const minute = parts.find((p) => p.type === 'minute')?.value.padStart(2, '0') || '00';
let tz = parts.find((p) => p.type === 'timeZoneName')?.value || 'MEST';
if (tz === 'CEST') tz = 'MEST';
else if (tz === 'CET') tz = 'MET';
return `[${hour}:${minute}${tz}] `;
}
export function historyToLlmMessages(history) {
const messages = [];
for (const m of history) {
@@ -179,7 +207,12 @@ export function historyToLlmMessages(history) {
payload = { content: m.message };
}
if (m.role === 'user') {
messages.push({ role: 'user', content: payload.content ?? (typeof m.message === 'string' ? m.message : '') });
const rawContent = payload.content ?? (typeof m.message === 'string' ? m.message : '');
const timePrefix = formatTimePrefix(m.created_at);
const content = /^\s*\[\d{2}:\d{2}[A-Za-z]+\]\s*/.test(rawContent)
? rawContent
: `${timePrefix}${rawContent}`;
messages.push({ role: 'user', content });
} else if (m.role === 'assistant') {
messages.push({
role: 'assistant',