Compare commits
3 Commits
1f09e620e3
...
06dc4629da
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06dc4629da | ||
|
|
88ecf5bf5c | ||
|
|
280ed1b2b8 |
@@ -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 } }`
|
||||||
|
|||||||
@@ -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": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -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.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -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.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -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.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -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.
|
||||||
//
|
//
|
||||||
|
|||||||
129
src/defaultSkills/applet.SKILL
Normal file
129
src/defaultSkills/applet.SKILL
Normal 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.
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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: . 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: . 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.
|
||||||
|
|||||||
Reference in New Issue
Block a user