From 4429294d8bba4e41667568a720d3692865899ad7 Mon Sep 17 00:00:00 2001 From: seb Date: Mon, 12 Oct 2026 04:05:54 +0200 Subject: [PATCH] u --- docs/api/routes/vfs/exec.md | 4 ++-- src/api/middleware/logger.js | 4 ++++ src/api/routes/vfsExec.js | 9 ++++++--- src/defaultApplets/article-lookup.applet.js | 2 +- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/api/routes/vfs/exec.md b/docs/api/routes/vfs/exec.md index 3384288..bbacf38 100644 --- a/docs/api/routes/vfs/exec.md +++ b/docs/api/routes/vfs/exec.md @@ -82,11 +82,11 @@ Applet executed successfully | Status | Description | |--------|-------------| -| 400 | No company selected, invalid path, not a .applet.js file, invalid input, or invalid applet metadata — `{"error":"…"}` | +| 400 | No company selected, invalid path, not a .applet.js file, invalid input, invalid applet metadata, or applet runtime error — `{"error":"…"}` | | 401 | Unauthorized — `{"error":"…"}` | | 403 | Not authorized, or cannot execute read-only database paths — `{"error":"…"}` | | 404 | Applet not found — `{"error":"…"}` | -| 500 | The applet threw during execution — `{"error":"…"}` | +| 500 | Sandbox worker harness failure — `{"error":"…"}` | | 504 | The applet exceeded the execution timeout and was terminated — `{"error":"…"}` | --- diff --git a/src/api/middleware/logger.js b/src/api/middleware/logger.js index 5147280..4bb1a56 100644 --- a/src/api/middleware/logger.js +++ b/src/api/middleware/logger.js @@ -23,6 +23,10 @@ function captureResponseBody(res) { res.json = (payload) => { let out = payload; if (res.statusCode >= 500 && payload && typeof payload === 'object' && 'error' in payload) { + if (!res.locals?.error && typeof payload.error === 'string') { + res.locals = res.locals || {}; + res.locals.error = new Error(payload.error); + } out = { ...payload, error: 'Internal server error' }; } body = out; diff --git a/src/api/routes/vfsExec.js b/src/api/routes/vfsExec.js index ecd0f28..2af020e 100644 --- a/src/api/routes/vfsExec.js +++ b/src/api/routes/vfsExec.js @@ -43,7 +43,8 @@ function appletExecErrorStatus(error) { 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('Sandbox exceeded')) return 504; - return 500; + if (message.includes('Sandbox worker did not report a result payload')) return 500; + return 400; } // #endregion @@ -163,7 +164,7 @@ export function register(app, basePath = '') { * truncationNote: * type: string * 400: - * description: No company selected, invalid path, not a .applet.js file, invalid input, or invalid applet metadata + * description: No company selected, invalid path, not a .applet.js file, invalid input, invalid applet metadata, or applet runtime error * content: * application/json: * schema: @@ -187,7 +188,7 @@ export function register(app, basePath = '') { * schema: * $ref: '#/components/schemas/Error' * 500: - * description: The applet threw during execution + * description: Sandbox worker harness failure * content: * application/json: * schema: @@ -241,6 +242,7 @@ export function register(app, basePath = '') { return res.status(404).json({ error: 'File not found' }); } } catch (error) { + res.locals.error = error; const status = vfsFileErrorStatus(error); if (status === 500) console.error('Execute vfs applet failed:', error); return res.status(status).json({ error: error.message }); @@ -258,6 +260,7 @@ export function register(app, basePath = '') { emit('vfs', { companyId: selected.companyId }); return res.json(result); } catch (error) { + res.locals.error = error; const status = appletExecErrorStatus(error); if (status === 500) console.error('Execute vfs applet failed:', error); return res.status(status).json({ error: error.message }); diff --git a/src/defaultApplets/article-lookup.applet.js b/src/defaultApplets/article-lookup.applet.js index 8005504..2fff7f6 100644 --- a/src/defaultApplets/article-lookup.applet.js +++ b/src/defaultApplets/article-lookup.applet.js @@ -1,4 +1,4 @@ -// @applet-input-schema {"type":"object","properties":{"sku":{"type":"string","title":"Article SKU","default":"","description":"Exact SKU to inspect. Leave empty for a single-row manifest summary."}}} +// @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 // // Article lookup — the raw `GET /article/{sku}` payload as a key/value sheet.