Compare commits

..

3 Commits

Author SHA1 Message Date
seb
06dc4629da u 2026-10-12 14:18:54 +02:00
seb
88ecf5bf5c u 2026-10-12 14:07:40 +02:00
seb
280ed1b2b8 u 2026-10-12 13:52:13 +02:00
11 changed files with 221 additions and 54 deletions

View File

@@ -801,7 +801,7 @@ _none_
- Returns: `200 str` - Returns: `200 str`
#### Execute a virtual filesystem applet — `POST /vfs/{*path}` #### Execute a virtual filesystem applet — `POST /vfs/{*path}`
- Params: `path: str, body?: { input?: {}, timeout?: int, max_output?: int }` - Params: `path: str, body?: { input?: {}, timeout?: int, max_output?: int }`
- Returns: `200 { ok: bool, result: str, columns: { name: str, type: str }[], rows: int, stdout: str, consoleLines: int, durationMs: int, warnings?: str[], truncated?: bool, truncationNote?: str }` - Returns: `200 { ok: bool, result: str, columns: { name: str, type: str }[], rows: int, stdout: str, consoleLines: int, durationMs: int, description?: str, warnings?: str[], truncated?: bool, truncationNote?: str }`
#### Create or overwrite a virtual filesystem file — `PUT /vfs/{*path}` #### Create or overwrite a virtual filesystem file — `PUT /vfs/{*path}`
- Params: `path: str, body: { content: str, mime?: str }` - Params: `path: str, body: { content: str, mime?: str }`
- Returns: `200 { success: bool, file: { name: str, size: int, mime: str, updated_at: str } }` - Returns: `200 { success: bool, file: { name: str, size: int, mime: str, updated_at: str } }`

View File

@@ -7,9 +7,10 @@ Execute a virtual filesystem applet
Runs a `.applet.js` file from the virtual filesystem for the authenticated user's selected company Runs a `.applet.js` file from the virtual filesystem for the authenticated user's selected company
and returns the exported function's result. and returns the exported function's result.
The applet file must begin with two metadata lines: The applet file must begin with three metadata lines:
`// @applet-input-schema {JSON Schema}` followed by `// @applet-input-schema {JSON Schema}` followed by
`// @applet-output-schema {name:type;name:type}`. The input schema describes the GUI form posted as `// @applet-output-schema {name:type;name:type}` and
`// @applet-description "description"`. The input schema describes the GUI form posted as
`input`. The output line declares the RESULT COLUMNS as typed CSV — `name:type` pairs separated by `;` `input`. The output line declares the RESULT COLUMNS as typed CSV — `name:type` pairs separated by `;`
(e.g. `id:integer;name:string;active:boolean`); allowed types are `string`, `integer`, `number`, (e.g. `id:integer;name:string;active:boolean`); allowed types are `string`, `integer`, `number`,
`boolean` and `date`. The applet exports a function that receives the posted `input` as `applet_input` `boolean` and `date`. The applet exports a function that receives the posted `input` as `applet_input`
@@ -59,6 +60,7 @@ Applet executed successfully
```json ```json
{ {
"ok": true, "ok": true,
"description": "…",
"result": "id;name\n1;Kaffee", "result": "id;name\n1;Kaffee",
"columns": [ "columns": [
{ {

View File

@@ -41,7 +41,7 @@ function appletExecErrorStatus(error) {
if (message.includes('must be an object')) return 400; if (message.includes('must be an object')) return 400;
if (message.includes('Unable to read applet') || message.includes('ENOENT')) return 404; if (message.includes('Unable to read applet') || message.includes('ENOENT')) return 404;
if (message.includes('is not a file')) return 400; if (message.includes('is not a file')) return 400;
if (message.includes('Applet metadata') || message.includes('Applet input schema') || message.includes('Applet output schema')) return 400; if (message.includes('Applet metadata') || message.includes('Applet input schema') || message.includes('Applet output schema') || message.includes('Applet description')) return 400;
if (message.includes('Sandbox exceeded')) return 504; if (message.includes('Sandbox exceeded')) return 504;
if (message.includes('Sandbox worker did not report a result payload')) return 500; if (message.includes('Sandbox worker did not report a result payload')) return 500;
return 400; return 400;
@@ -71,9 +71,10 @@ export function register(app, basePath = '') {
* Runs a `.applet.js` file from the virtual filesystem for the authenticated user's selected company * Runs a `.applet.js` file from the virtual filesystem for the authenticated user's selected company
* and returns the exported function's result. * and returns the exported function's result.
* *
* The applet file must begin with two metadata lines: * The applet file must begin with three metadata lines:
* `// @applet-input-schema {JSON Schema}` followed by * `// @applet-input-schema {JSON Schema}` followed by
* `// @applet-output-schema {name:type;name:type}`. The input schema describes the GUI form posted as * `// @applet-output-schema {name:type;name:type}` and
* `// @applet-description "description"`. The input schema describes the GUI form posted as
* `input`. The output line declares the RESULT COLUMNS as typed CSV — `name:type` pairs separated by `;` * `input`. The output line declares the RESULT COLUMNS as typed CSV — `name:type` pairs separated by `;`
* (e.g. `id:integer;name:string;active:boolean`); allowed types are `string`, `integer`, `number`, * (e.g. `id:integer;name:string;active:boolean`); allowed types are `string`, `integer`, `number`,
* `boolean` and `date`. The applet exports a function that receives the posted `input` as `applet_input` * `boolean` and `date`. The applet exports a function that receives the posted `input` as `applet_input`
@@ -127,6 +128,9 @@ export function register(app, basePath = '') {
* properties: * properties:
* ok: * ok:
* type: boolean * type: boolean
* description:
* type: string
* description: Applet description declared in the @applet-description header
* result: * result:
* type: string * type: string
* description: Typed CSV table (`;`-separated, header row first) built from the returned rows and the declared output columns * description: Typed CSV table (`;`-separated, header row first) built from the returned rows and the declared output columns

View File

@@ -1,5 +1,6 @@
// @applet-input-schema {"type":"object","properties":{"query":{"type":"string","title":"Article name contains","default":"","description":"Case-insensitive substring match on the article name. Leave empty to include every article."},"limit":{"type":"integer","title":"Max rows","default":200,"minimum":1,"maximum":1000},"onlyTracked":{"type":"boolean","title":"Only stock-tracked articles","default":false,"description":"When true, articles with track_stock=false are omitted."}}} // @applet-input-schema {"type":"object","properties":{"query":{"type":"string","title":"Article name contains","default":"","description":"Case-insensitive substring match on the article name. Leave empty to include every article."},"limit":{"type":"integer","title":"Max rows","default":200,"minimum":1,"maximum":1000},"onlyTracked":{"type":"boolean","title":"Only stock-tracked articles","default":false,"description":"When true, articles with track_stock=false are omitted."}}}
// @applet-output-schema sku:string;name:string;net:number;taxRate:number;track_stock:boolean;categoryId:integer // @applet-output-schema sku:string;name:string;net:number;taxRate:number;track_stock:boolean;categoryId:integer
// @applet-description "Article catalogue — articles with their net price, resolved by walking the category tree."
// //
// Article catalogue — articles with their net price, resolved by walking the category tree. // Article catalogue — articles with their net price, resolved by walking the category tree.
// //

View File

@@ -1,5 +1,6 @@
// @applet-input-schema {"type":"object","properties":{"sku":{"type":"string","title":"Article SKU","description":"Exact SKU to inspect (e.g. \"42000009\")."}},"required":["sku"]} // @applet-input-schema {"type":"object","properties":{"sku":{"type":"string","title":"Article SKU","description":"Exact SKU to inspect (e.g. \"42000009\")."}},"required":["sku"]}
// @applet-output-schema key:string;value:string // @applet-output-schema key:string;value:string
// @applet-description "Article lookup — the raw GET /article/{sku} payload as a key/value sheet."
// //
// Article lookup — the raw `GET /article/{sku}` payload as a key/value sheet. // Article lookup — the raw `GET /article/{sku}` payload as a key/value sheet.
// //

View File

@@ -1,5 +1,6 @@
// @applet-input-schema {"type":"object","properties":{"query":{"type":"string","title":"Category name contains","default":"","description":"Case-insensitive substring match on the category name. Leave empty to include every category."},"limit":{"type":"integer","title":"Max rows","default":100,"minimum":1,"maximum":500},"sortBy":{"type":"string","title":"Sort by","default":"articles","enum":["articles","name","depth"],"description":"articles = most articles first, name = A–Z, depth = tree depth then name."}}} // @applet-input-schema {"type":"object","properties":{"query":{"type":"string","title":"Category name contains","default":"","description":"Case-insensitive substring match on the category name. Leave empty to include every category."},"limit":{"type":"integer","title":"Max rows","default":100,"minimum":1,"maximum":500},"sortBy":{"type":"string","title":"Sort by","default":"articles","enum":["articles","name","depth"],"description":"articles = most articles first, name = A–Z, depth = tree depth then name."}}}
// @applet-output-schema id:integer;name:string;depth:integer;articles:integer;children:integer // @applet-output-schema id:integer;name:string;depth:integer;articles:integer;children:integer
// @applet-description "Category inventory — every category with its article count and child count."
// //
// Category inventory — every category with its article count and child count. // Category inventory — every category with its article count and child count.
// //

View File

@@ -1,5 +1,6 @@
// @applet-input-schema {"type":"object","properties":{"dateFrom":{"type":"string","title":"From date (YYYY-MM-DD)","default":"","description":"Only include documents dated on or after this day. Leave empty for no lower bound."},"dateTo":{"type":"string","title":"To date (YYYY-MM-DD)","default":"","description":"Only include documents dated on or before this day. Leave empty for no upper bound."},"limit":{"type":"integer","title":"Max rows","default":100,"minimum":1,"maximum":500}}} // @applet-input-schema {"type":"object","properties":{"dateFrom":{"type":"string","title":"From date (YYYY-MM-DD)","default":"","description":"Only include documents dated on or after this day. Leave empty for no lower bound."},"dateTo":{"type":"string","title":"To date (YYYY-MM-DD)","default":"","description":"Only include documents dated on or before this day. Leave empty for no upper bound."},"limit":{"type":"integer","title":"Max rows","default":100,"minimum":1,"maximum":500}}}
// @applet-output-schema number:string;date:string;supplier:string;lines:integer;net:number // @applet-output-schema number:string;date:string;supplier:string;lines:integer;net:number
// @applet-description "Goods receipt register — deliveries with their line-item count and net total."
// //
// Goods receipt register — deliveries with their line-item count and net total. // Goods receipt register — deliveries with their line-item count and net total.
// //

View File

@@ -1,5 +1,6 @@
// @applet-input-schema {"type":"object","properties":{"month":{"type":"string","title":"Month (YYYY-MM)","default":"","description":"Only include orders placed in this month. Leave empty for all months."},"limit":{"type":"integer","title":"Max rows","default":50,"minimum":1,"maximum":500}}} // @applet-input-schema {"type":"object","properties":{"month":{"type":"string","title":"Month (YYYY-MM)","default":"","description":"Only include orders placed in this month. Leave empty for all months."},"limit":{"type":"integer","title":"Max rows","default":50,"minimum":1,"maximum":500}}}
// @applet-output-schema month:string;orders:integer;positions:integer;net:number // @applet-output-schema month:string;orders:integer;positions:integer;net:number
// @applet-description "Order revenue by month — orders grouped per calendar month."
// //
// Order revenue by month — orders grouped per calendar month. // Order revenue by month — orders grouped per calendar month.
// //

View File

@@ -0,0 +1,129 @@
Applet Guide: Sandboxed Execution, Typed CSV Output
# Overview
An applet is a flat, reusable `.applet.js` script stored in the VFS that queries Dashback REST endpoints and computes tabular data.
Applets return structured, semicolon-separated (`;`) typed CSV conforming to output metadata headers.
Applets can be executed by:
1. `javascript_execute` tool using `{ "appletvfsfile": "<name>.applet.js", "applet_input": { ... } }`.
2. REST API endpoint `POST /vfs/<name>.applet.js` with `{ "input": { ... } }`.
---
# Every `.applet.js` file MUST start with three metadata lines:
```
// @applet-input-schema {"type":"object","properties":{...},"required":[...]}
// @applet-output-schema name:type;name:type
// @applet-description "Lorem ipsum"
```
`@applet-input-schema`
- JSON Schema object (`"type": "object"`).
- Parameter descriptions in the schema are optional
`@applet-output-schema`
- Semicolon-separated (`col1:type;col2:type;...`) column declarations.
- Bare column names without `:type` default to `string`.
- Allowed data types:
- `string`: plain text or serialized object.
- `integer`: truncated integer number.
- `number`: floating-point number.
- `boolean`: boolean value (`true`, `false`, `1`, `0`, `"true"`, `"false"`).
- `date`: Date object or ISO string.
`@applet-description`
- Short human-readable summary of what the applet computes (e.g. `"Article catalogue with net prices"`).
---
# Execution Environment & Globals
Applets run in an isolated worker thread pool sandbox.
- **Entry point**: Export an async function taking `applet_input`:
```javascript
module.exports = async function (applet_input) { ... };
```
- **No `require()` or `import`**: Attempting to import modules throws an error.
- **Pre-injected Globals**:
- `fetch(path, options)`: Authenticated `fetch` pre-configured with a JWT scoped to the current user and selected company. Use root-relative API routes, e.g. `fetch('/article/42000009')`.
- `fs` / `vfs`: Sandboxed VFS filesystem interface for reading/writing local company files.
- `ExcelJS`: Pre-injected spreadsheet library.
- `PDFDocument`: Pre-injected PDFKit library.
- `sharp`: Pre-injected image manipulation library.
- `crypto`: Node / WebCrypto API.
- `console.log(...)`: returned in the tool response
---
# Return Value & Typed CSV Mapping
The exported function must return:
- An **Array of row objects**: `[ { col1: 'val1', col2: 10 }, { col1: 'val2', col2: 20 } ]`
- Or a **Single row object**: `{ col1: 'val1', col2: 10 }` (treated as a 1-row table)
---
# Standard Applet Template
```javascript
// @applet-input-schema {"type":"object","properties":{"sku":{"type":"string","title":"Article SKU","description":"SKU to lookup"}},"required":["sku"]}
// @applet-output-schema sku:string;name:string;netPrice:number;tracked:boolean;brand:string
// @applet-description "Lorem ipsum"
module.exports = async function (applet_input) {
const sku = String(applet_input?.sku || '').trim();
if (!sku) {
throw new Error('Missing required parameter: sku');
}
const res = await fetch(`/article/${encodeURIComponent(sku)}`);
if (res.status === 404) throw new Error(`Article "${sku}" not found.`);
if (!res.ok) throw new Error(`GET /article/${sku} failed with status ${res.status}`);
const { article } = await res.json();
const price = (article.prices || [])[0];
console.log(`Found article ${article.sku}: ${article.name}`);
return [{
sku: article.sku,
name: article.name || '',
netPrice: price?.net ?? '',
tracked: Boolean(article.track_stock),
brand: article.brand?.name || ''
}];
};
```
---
# Common Implementation Patterns
### 1. Paging List Endpoints
Page using `offset` and `limit` until all items are loaded or `limit` is reached:
```javascript
const items = [];
let offset = 0;
while (true) {
const res = await fetch(`/order?offset=${offset}&limit=100`);
if (!res.ok) throw new Error(`Fetch failed: ${res.status}`);
const data = await res.json();
const page = data.orders || [];
items.push(...page);
offset += page.length;
if (page.length === 0 || offset >= (data.total ?? items.length)) break;
}
```
---
# Shipped Reference Applets
Pre-installed examples in the VFS root provide working reference code:
- `v1-article-lookup.applet.js`: Single entity lookup and key/value sheet generation.
- `v1-article-catalogue.applet.js`: Walking category trees and joining category article lists.
- `v1-category-inventory.applet.js`: Recursive tree flattening and depth calculation.
- `v1-delivery-register.applet.js`: Paged list fetching with batched detail requests.
- `v1-order-revenue-by-month.applet.js`: Paged order retrieval with calendar month aggregation.

View File

@@ -197,6 +197,32 @@ function runInWorker({ code, timeoutMs, vfsRoot, googleAuthId, appletInput, appl
}); });
} }
/**
* Parse the applet description header.
*
* Accepts either a quoted string (e.g. `"Lorem ipsum"` or `'Lorem ipsum'`) or bare text.
* Strips surrounding quotes if present and trims whitespace.
*
* @param {string} header raw text after the `// @applet-description ` marker
* @returns {string}
*/
function parseAppletDescription(header) {
const text = String(header || '').trim();
if (!text) {
throw new ToolError('Applet description cannot be empty.');
}
if ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'"))) {
try {
const parsed = JSON.parse(text);
if (typeof parsed === 'string' && parsed.trim()) return parsed.trim();
} catch {
const unquoted = text.slice(1, -1).trim();
if (unquoted) return unquoted;
}
}
return text;
}
function readApplet(vfsRoot, appletvfsfile) { function readApplet(vfsRoot, appletvfsfile) {
if (typeof appletvfsfile !== 'string' || !appletvfsfile.trim()) { if (typeof appletvfsfile !== 'string' || !appletvfsfile.trim()) {
throw new ToolError('"appletvfsfile" must be a non-empty VFS-relative file path.'); throw new ToolError('"appletvfsfile" must be a non-empty VFS-relative file path.');
@@ -228,14 +254,16 @@ function readApplet(vfsRoot, appletvfsfile) {
throw new ToolError(`Unable to read applet "${appletPath}" from the VFS: ${err.message}`); throw new ToolError(`Unable to read applet "${appletPath}" from the VFS: ${err.message}`);
} }
const lines = source.replace(/^\uFEFF/, '').split(/\r?\n/, 3); const lines = source.replace(/^\uFEFF/, '').split(/\r?\n/, 4);
const inputHeader = lines[0]?.match(/^\/\/ @applet-input-schema (.+)$/); const inputHeader = lines[0]?.match(/^\/\/ @applet-input-schema (.+)$/);
const outputHeader = lines[1]?.match(/^\/\/ @applet-output-schema (.+)$/); const outputHeader = lines[1]?.match(/^\/\/ @applet-output-schema (.+)$/);
if (!inputHeader || !outputHeader) { const descHeader = lines[2]?.match(/^\/\/ @applet-description (.+)$/);
if (!inputHeader || !outputHeader || !descHeader) {
throw new ToolError( throw new ToolError(
'Applet metadata must be on the first two lines: ' + 'Applet metadata must be on the first three lines: ' +
'`// @applet-input-schema {JSON Schema}` followed by ' + '`// @applet-input-schema {JSON Schema}` followed by ' +
'`// @applet-output-schema name:type;name:type` (typed CSV columns).' '`// @applet-output-schema name:type;name:type` (typed CSV columns) and ' +
'`// @applet-description "description"`.'
); );
} }
@@ -251,8 +279,9 @@ function readApplet(vfsRoot, appletvfsfile) {
} }
const outputColumns = parseTypedCsvColumns(outputHeader[1]); const outputColumns = parseTypedCsvColumns(outputHeader[1]);
const description = parseAppletDescription(descHeader[1]);
return { source, outputColumns }; return { source, outputColumns, description, inputSchema };
} }
function formatSandboxError(code, report) { function formatSandboxError(code, report) {
@@ -401,6 +430,7 @@ export function createJavascriptExecuteTool({ googleAuthId }) {
result.result = table.csv; result.result = table.csv;
result.rows = table.rows; result.rows = table.rows;
result.columns = table.columns; result.columns = table.columns;
result.description = applet.description;
if (table.warnings.length) result.warnings = table.warnings; if (table.warnings.length) result.warnings = table.warnings;
} }
if (truncated) { if (truncated) {

View File

@@ -1,9 +1,39 @@
import * as dbCompanySetting from '../db/companysetting.js'; import * as dbCompanySetting from '../db/companysetting.js';
import { listSkillFiles } from './skill.js'; import { listSkillFiles } from './skill.js';
// #region Tool Prompts & Descriptions // #region System Prompt
const NOTE_DESC = 'Status message shown to the user during execution.'; export const SYSTEM_PROMPT_RULES = [
'RULES & VFS:',
'- VFS: Persistent root-relative paths ("labels/sheet1.png"). Uploads land in "attachments/..." ([ATTACHMENT name= path= type=]); inspect via read_file, pdf_read, analyze_image, or javascript_execute + ExcelJS. Can host static sites.',
'- Links: Web links use "https://..." or domains; relative paths format as [label](relative/path). Inline images: ![alt](images/path.avif). Other files require download links.',
'- SKILLS: Flat root "<slug>.SKILL" markdown files. ALWAYS read_file matching skill BEFORE executing covered tasks. Exempt from reset_vfs.',
'- API: Routes are not inlined. BEFORE dashback_request, read_file the api-<section>.SKILL for parameters and response structures. api-binary-streaming-routes.SKILL covers fetch()-only upload/stream endpoints.',
'- Types: T<n> refers to globally shared object schemas across skills.',
'- Images: Pipeline: search_images -> analyze_image -> upload via fetch() in javascript_execute. "image/" is read-only; write generated assets to "images/...".',
'- Labels: Use Dashback JSON `{ "objects": [ ... ] }` for /labeltemplate. Never use ZPL.',
'- Navigation: Call set_user_route when the user asks to open or view any page/entity.',
].join('\n');
export const SYSTEM_PROMPT_ROUTE_MATRIX = [
'ROUTE MATRIX (client German, API skills = api-<section>):',
'- Search: /artikel?target={query}&filter={filter}&offset={n} -> article, category, brand',
'- Article Detail: /artikel/${sku} -> article, category, brand, attributename, propertyname, unit, supplier, printer, labeltemplate, ledger',
'- Warehouse / Stock: /artikel/lager -> warehouse, stock, ledger',
'- Brands: /artikel/marken -> brand',
'- Label Templates: /artikel/vorlagen -> labeltemplate, printer',
'- Attributes: /artikel/merkmale -> attributename, attributevalue, propertyname',
'- Categories: /kategorien | /kategorien/${slug1}/${slug2} -> category, article',
'- Deliveries: /lieferung | /lieferung/neu | /lieferung/${deliveryId} | /lieferung/lieferanten -> delivery, supplier, article, warehouse, stock, ledger',
'- Quotes: /auftrag | /auftrag/neu | /auftrag/${quoteId} -> quote, entity, article',
'- Orders: /auftrag/bestellung | /auftrag/bestellung/neu | /auftrag/bestellung/${orderId} -> order, entity, account, payment, shipping, package',
'- Customers: /kunde | /kunde/neu | /kunde/${entityId} | /kunde/gruppen -> entity, customergroup, number',
'- History & Stats: /infos/articlehistory | /infos/globalhistory | /infos/statistics -> article, company, order, delivery, ledger',
].join('\n');
// #endregion
// #region Tool Prompts
/** /**
* Descriptions for all tools registered in the agent's toolbelt. * Descriptions for all tools registered in the agent's toolbelt.
@@ -18,7 +48,7 @@ export const TOOL_DESCRIPTIONS = {
javascript_execute: javascript_execute:
'Execute JS in an isolated sandbox via inline `code` (assign to `module.exports`) or `appletvfsfile` ' + 'Execute JS in an isolated sandbox via inline `code` (assign to `module.exports`) or `appletvfsfile` ' +
'(.applet.js returning typed CSV per @applet-input-schema and @applet-output-schema; details in applet.SKILL). ' + '(.applet.js returning typed CSV per @applet-input-schema, @applet-output-schema, and @applet-description; details in applet.SKILL). ' +
'Pre-injected globals: fs/vfs, sharp, ExcelJS, PDFDocument, crypto, and fetch("/path"). No require/import.', 'Pre-injected globals: fs/vfs, sharp, ExcelJS, PDFDocument, crypto, and fetch("/path"). No require/import.',
find_files: find_files:
@@ -58,7 +88,7 @@ export const TOOL_DESCRIPTIONS = {
'Search articles by name, brand, EAN, SKU, or supplier SKU (AND logic, ordered by name).', 'Search articles by name, brand, EAN, SKU, or supplier SKU (AND logic, ordered by name).',
edit_image: edit_image:
'Generate or edit images via modelslugimage. Supports reference image attachments, size tier, ratio, and VFS AVIF output.', 'Generate or edit images via a vision model. Supports reference image attachments, size tier, ratio, and VFS AVIF output.',
ean_checksum: ean_checksum:
'Validate or calculate check digits for EAN-13, EAN-8, UPC-A, and GTIN-14 barcodes.', 'Validate or calculate check digits for EAN-13, EAN-8, UPC-A, and GTIN-14 barcodes.',
@@ -70,7 +100,11 @@ export const TOOL_DESCRIPTIONS = {
'Navigate user to a frontend route when asked to view/open an entity or page (see ROUTE MATRIX in prompt).', 'Navigate user to a frontend route when asked to view/open an entity or page (see ROUTE MATRIX in prompt).',
}; };
// #region Tool Parameter Descriptions // #endregion
// #region Tool Parameters
const NOTE_DESC = 'Status message shown to the user during execution.';
/** /**
* Descriptions for tool input parameters across src/tools/*. * Descriptions for tool input parameters across src/tools/*.
@@ -213,47 +247,10 @@ export const TOOL_PARAMETER_DESCRIPTIONS = {
export const PATCH_PARAMETER_DESCRIPTIONS = TOOL_PARAMETER_DESCRIPTIONS.patch; export const PATCH_PARAMETER_DESCRIPTIONS = TOOL_PARAMETER_DESCRIPTIONS.patch;
export const PATCH_PARAM_DESCRIPTIONS = TOOL_PARAMETER_DESCRIPTIONS.patch; export const PATCH_PARAM_DESCRIPTIONS = TOOL_PARAMETER_DESCRIPTIONS.patch;
// #endregion
// #region Tool-specific Subprompts
export const ANALYZE_IMAGE_VISION_COMPARE_PROMPT = export const ANALYZE_IMAGE_VISION_COMPARE_PROMPT =
'Compare these images, evaluate visual quality, flag watermarks/overlays/defects, and state the best candidate with rationale.'; 'Compare these images, evaluate visual quality, flag watermarks/overlays/defects, and state the best candidate with rationale.';
export const ANALYZE_IMAGE_VISION_DESCRIBE_PROMPT = export const ANALYZE_IMAGE_VISION_DESCRIBE_PROMPT =
'Describe this image, evaluate visual quality, flag watermarks/overlays/defects, and verify suitability as a product/category image.'; 'Describe this image, evaluate visual quality, flag watermarks/overlays/defects, and verify suitability as a product/category image.';
// #endregion
// #endregion
// #region System Prompt Components
export const SYSTEM_PROMPT_RULES = [
'RULES & VFS:',
'- VFS: Persistent root-relative paths ("labels/sheet1.png"). Uploads land in "attachments/..." ([ATTACHMENT name= path= type=]); inspect via read_file, pdf_read, analyze_image, or javascript_execute + ExcelJS. Can host static sites.',
'- Links: Web links use "https://..." or domains; relative paths format as [label](relative/path). Inline images: ![alt](images/path.avif). Other files require download links.',
'- SKILLS: Flat root "<slug>.SKILL" markdown files. ALWAYS read_file matching skill BEFORE executing covered tasks. Exempt from reset_vfs.',
'- API: Routes are not inlined. BEFORE dashback_request, read_file the api-<section>.SKILL for parameters and response structures. api-binary-streaming-routes.SKILL covers fetch()-only upload/stream endpoints.',
'- Types: T<n> refers to globally shared object schemas across skills.',
'- Images: Pipeline: search_images -> analyze_image -> upload via fetch() in javascript_execute. "image/" is read-only; write generated assets to "images/...".',
'- Labels: Use Dashback JSON `{ "objects": [ ... ] }` for /labeltemplate. Never use ZPL.',
'- Navigation: Call set_user_route when the user asks to open or view any page/entity.',
].join('\n');
export const SYSTEM_PROMPT_ROUTE_MATRIX = [
'ROUTE MATRIX (client German, API skills = api-<section>):',
'- Search: /artikel?target={query}&filter={filter}&offset={n} -> article, category, brand',
'- Article Detail: /artikel/${sku} -> article, category, brand, attributename, propertyname, unit, supplier, printer, labeltemplate, ledger',
'- Warehouse / Stock: /artikel/lager -> warehouse, stock, ledger',
'- Brands: /artikel/marken -> brand',
'- Label Templates: /artikel/vorlagen -> labeltemplate, printer',
'- Attributes: /artikel/merkmale -> attributename, attributevalue, propertyname',
'- Categories: /kategorien | /kategorien/${slug1}/${slug2} -> category, article',
'- Deliveries: /lieferung | /lieferung/neu | /lieferung/${deliveryId} | /lieferung/lieferanten -> delivery, supplier, article, warehouse, stock, ledger',
'- Quotes: /auftrag | /auftrag/neu | /auftrag/${quoteId} -> quote, entity, article',
'- Orders: /auftrag/bestellung | /auftrag/bestellung/neu | /auftrag/bestellung/${orderId} -> order, entity, account, payment, shipping, package',
'- Customers: /kunde | /kunde/neu | /kunde/${entityId} | /kunde/gruppen -> entity, customergroup, number',
'- History & Stats: /infos/articlehistory | /infos/globalhistory | /infos/statistics -> article, company, order, delivery, ledger',
].join('\n');
// #endregion // #endregion
@@ -290,7 +287,7 @@ export function buildSkillIndex(vfsRoot) {
// #endregion // #endregion
// #region System Prompt // #region Factory
/** /**
* System prompt: base instructions and contextual index. * System prompt: base instructions and contextual index.