@mmmbuto/nexuscrew 0.8.58 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +197 -2
  2. package/README.md +23 -4
  3. package/frontend/dist/assets/index-0vuhL1YP.css +32 -0
  4. package/frontend/dist/assets/index-Bu_-2-Uu.js +93 -0
  5. package/frontend/dist/index.html +2 -2
  6. package/frontend/dist/version.json +1 -1
  7. package/lib/audio/adapters.js +50 -6
  8. package/lib/cli/commands.js +54 -2
  9. package/lib/cli/doctor.js +95 -12
  10. package/lib/cli/init.js +25 -3
  11. package/lib/cli/path.js +43 -10
  12. package/lib/cli/pidfile.js +23 -2
  13. package/lib/config.js +24 -0
  14. package/lib/fleet/builtin.js +161 -19
  15. package/lib/fleet/catalogs/opencode-go.json +50 -4
  16. package/lib/fleet/cell-exec.js +87 -9
  17. package/lib/fleet/cell-lease-server.js +719 -0
  18. package/lib/fleet/cell-lease.js +112 -0
  19. package/lib/fleet/definitions.js +101 -7
  20. package/lib/fleet/launch-broker.js +115 -3
  21. package/lib/fleet/lease-client.js +191 -0
  22. package/lib/fleet/lease-routes.js +92 -0
  23. package/lib/fleet/lease-verifier.js +230 -0
  24. package/lib/fleet/managed.js +366 -48
  25. package/lib/fleet/prompt-delivery.js +50 -2
  26. package/lib/fleet/provider.js +1 -1
  27. package/lib/fleet/runtime.js +53 -6
  28. package/lib/live-host/bridge.js +369 -0
  29. package/lib/live-host/routes.js +190 -0
  30. package/lib/live-host/store.js +96 -0
  31. package/lib/mcp/tools.js +51 -0
  32. package/lib/nodes/commands.js +21 -3
  33. package/lib/nodes/store.js +57 -0
  34. package/lib/nodes/tunnel.js +23 -1
  35. package/lib/proxy/federation.js +130 -9
  36. package/lib/proxy/node-proxy.js +33 -0
  37. package/lib/proxy/panel-auth.js +337 -0
  38. package/lib/proxy/panel-proxy.js +336 -0
  39. package/lib/server.js +252 -7
  40. package/lib/settings/pairing-coordinator.js +32 -0
  41. package/lib/settings/public-peering-routes.js +13 -1
  42. package/package.json +1 -1
  43. package/skills/aidesktop/SKILL.md +201 -0
  44. package/skills/aidesktop/docker/Dockerfile +21 -0
  45. package/skills/aidesktop/docker/custom-cont-init.d/10-cdp-relay.sh +20 -0
  46. package/skills/aidesktop/docker/docker-compose.example.yml +75 -0
  47. package/skills/crew/SKILL.md +15 -0
  48. package/skills/live/SKILL.md +90 -0
  49. package/skills/mail-assistant/SKILL.md +15 -0
  50. package/skills/memory/SKILL.md +15 -0
  51. package/skills/nexuscrew/SKILL.md +113 -0
  52. package/skills/nexuscrew-agent/SKILL.md +18 -0
  53. package/skills/vl-msa/SKILL.md +15 -0
  54. package/frontend/dist/assets/index-BEGNtmx2.js +0 -93
  55. package/frontend/dist/assets/index-CYi_lhCg.css +0 -32
  56. package/skills/alibaba-token-media/SKILL.md +0 -133
  57. package/skills/alibaba-token-media/agents/openai.yaml +0 -4
  58. package/skills/alibaba-token-media/references/api-contract.md +0 -97
  59. package/skills/alibaba-token-media/scripts/alibaba_token_media.py +0 -550
  60. package/skills/fill-forms/SKILL.md +0 -154
  61. package/skills/fill-forms/agents/openai.yaml +0 -4
  62. package/skills/fill-forms/references/overlay-technique.md +0 -99
  63. package/skills/fill-forms/requirements.txt +0 -4
  64. package/skills/fill-forms/scripts/dump_docx.py +0 -70
  65. package/skills/fill-forms/scripts/fill_docx.py +0 -207
  66. package/skills/fill-forms/scripts/fill_pdf.py +0 -424
  67. package/skills/fill-forms/scripts/inspect_pdf.py +0 -188
  68. package/skills/fill-forms/scripts/prepare_signature.py +0 -171
@@ -0,0 +1,336 @@
1
+ 'use strict';
2
+ // lib/proxy/panel-proxy.js — inoltro del pannello di UNA cella locale.
3
+ //
4
+ // Perche' esiste: `panelUrl` e' un endpoint su loopback (es. il desktop in
5
+ // container). L'iframe della PWA risolve `127.0.0.1` sul browser di CHI GUARDA,
6
+ // quindi il pannello di una cella si vede solo stando sulla stessa macchina.
7
+ // Questa route mette il traffico dalla parte giusta del loopback.
8
+ //
9
+ // TRE VINCOLI, che sono il disegno e non dettagli:
10
+ //
11
+ // 1. LA DESTINAZIONE NON ARRIVA MAI DAL CHIAMANTE. Si risolve dal `panelUrl`
12
+ // della cella indicata, e quella cella deve essere LOCALE. Non e' un
13
+ // port-forward: un cellId sconosciuto o senza pannello e' un rifiuto, non un
14
+ // default. Chi chiama sceglie QUALE cella, mai VERSO DOVE.
15
+ //
16
+ // 2. IL TOKEN DI NEXUSCREW NON ESCE DA QUI. Il pannello non e' un nodo della
17
+ // federazione: e' un servizio terzo che gira accanto. Inoltrargli la nostra
18
+ // Authorization sarebbe consegnare la credenziale del control plane a un
19
+ // container. L'header viene rimosso, e l'eventuale 401 del pannello resta
20
+ // suo — l'autenticazione al contenuto avviene dentro il frame.
21
+ //
22
+ // 3. IL VALIDATORE E' QUELLO, NON UN SECONDO. `validPanelUrl` e' importato da
23
+ // fleet/definitions.js, la stessa funzione che accetta il campo quando viene
24
+ // scritto. Riscriverne una copia qui creerebbe due decisioni sullo stesso
25
+ // fatto, e la seconda divergerebbe: e' il difetto che passiamo le giornate a
26
+ // chiudere.
27
+ //
28
+ // Certificato self-signed: il container tipico serve HTTPS con un certificato
29
+ // che nessuno ha firmato, e non possiamo installarlo nel trust store della
30
+ // macchina di chi ci ospita. Verso di lui la verifica e' disattivata di
31
+ // proposito, ma SOLO se la destinazione e' loopback — e la condizione viene
32
+ // ricontrollata qui contro la stessa lista che autorizza il campo, non data per
33
+ // scontata dal validatore. Fra noi e una porta della stessa macchina non c'e'
34
+ // un uomo in mezzo da temere; verso qualunque altro host la verifica resta
35
+ // attiva e il collegamento fallisce invece di degradare in silenzio. In cambio, il browser del visualizzatore parla solo con la NOSTRA
36
+ // origine: il frame smette di restare bianco in attesa che qualcuno accetti
37
+ // quel certificato in una scheda separata.
38
+
39
+ const http = require('node:http');
40
+ const https = require('node:https');
41
+ const { validPanelUrl, PANELURL_LOOPBACK_HOSTS } = require('../fleet/definitions.js');
42
+ const { CELL_ID_RE } = require('../live-host/store.js');
43
+
44
+ const PANEL_TIMEOUT_MS = 30000;
45
+
46
+ // Hop-by-hop (RFC 7230 §6.1) + Proxy-*: mai inoltrati end-to-end.
47
+ const HOP_BY_HOP = new Set([
48
+ 'connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization',
49
+ 'te', 'trailer', 'transfer-encoding', 'upgrade',
50
+ ]);
51
+
52
+ // `/api/panel/<cellId>` oppure `/api/panel/<cellId>/<rest...>`.
53
+ function splitPanelPath(url) {
54
+ const raw = String(url || '');
55
+ const qIndex = raw.indexOf('?');
56
+ const pathname = qIndex === -1 ? raw : raw.slice(0, qIndex);
57
+ const search = qIndex === -1 ? '' : raw.slice(qIndex);
58
+ const parts = pathname.split('/').filter(Boolean);
59
+ if (!parts.length) return null;
60
+ const cellId = decodeURIComponent(parts[0]);
61
+ const tail = parts.slice(1);
62
+ // Un `..` verrebbe inoltrato cosi' com'e' e normalizzato dal pannello: non
63
+ // cambia host ne' porta, ma e' comunque un percorso che il chiamante disegna
64
+ // dentro il pannello. Si ferma qui, in entrambe le forme in cui puo' arrivare.
65
+ for (const seg of tail) {
66
+ const decoded = (() => { try { return decodeURIComponent(seg); } catch (_) { return seg; } })();
67
+ if (seg === '..' || decoded === '..') return null;
68
+ }
69
+ const rest = tail.length ? `/${tail.join('/')}` : '/';
70
+ return { cellId, rest, search };
71
+ }
72
+
73
+ // Il token locale puo' viaggiare in query (il browser non puo' mettere header
74
+ // sull'upgrade WebSocket), e cosi' il ticket d'ingresso dell'iframe: nessuno
75
+ // dei due deve proseguire verso il pannello.
76
+ function stripLocalTokenQuery(search) {
77
+ if (!search) return '';
78
+ const params = new URLSearchParams(search.startsWith('?') ? search.slice(1) : search);
79
+ params.delete('token');
80
+ params.delete('ticket');
81
+ const out = params.toString();
82
+ return out ? `?${out}` : '';
83
+ }
84
+
85
+ // Gli stessi header che il proxy verso i nodi non inoltra. Avevo lasciato
86
+ // passare i cookie con la motivazione «sono del pannello»: era **falsa**.
87
+ // Dietro questo proxy l'origine e' la NOSTRA, quindi il browser manda i cookie
88
+ // del nostro dominio — inoltrarli significherebbe consegnare al container la
89
+ // sessione del control plane, esattamente cio' che l'Authorization rimossa
90
+ // doveva impedire. E `x-forwarded-*` da un client sono valori che un pannello
91
+ // potrebbe credere veri. Rilievo di un audit indipendente.
92
+ function isStrippedRequestHeader(key) {
93
+ if (HOP_BY_HOP.has(key)) return true;
94
+ if (key === 'authorization' || key === 'cookie' || key === 'host') return true;
95
+ // `referer` porterebbe il ticket d'ingresso (viaggiava in query sulla prima
96
+ // richiesta dell'iframe) fino al pannello: al container non deve arrivare
97
+ // NESSUNA credenziale, nemmeno di seconda mano.
98
+ if (key === 'referer') return true;
99
+ if (key.startsWith('proxy-')) return true;
100
+ if (key === 'forwarded' || key.startsWith('x-forwarded-')) return true;
101
+ return false;
102
+ }
103
+
104
+ function forwardHeaders(headers, targetHost) {
105
+ const out = {};
106
+ for (const [k, v] of Object.entries(headers || {})) {
107
+ if (isStrippedRequestHeader(k.toLowerCase())) continue;
108
+ out[k] = v;
109
+ }
110
+ // L'host deve essere quello della destinazione: un pannello che genera
111
+ // redirect assoluti li costruisce da qui.
112
+ out.host = targetHost;
113
+ return out;
114
+ }
115
+
116
+ // Header che il PANNELLO non deve poter porre sulla nostra origine. Sono una
117
+ // cosa diversa dagli hop-by-hop: quelli non hanno senso oltre un hop, questi
118
+ // avrebbero senso e proprio per questo vanno tolti.
119
+ //
120
+ // `set-cookie`: il pannello e' servito sotto la nostra origine, quindi un suo
121
+ // cookie atterra li'. Non gli darebbe l'accesso al pannello di un'altra cella
122
+ // — il cookie di visione non e' autoportante, `verifyCookie` cerca il valore
123
+ // in una mappa del server e un valore inventato non c'e' — ma puo'
124
+ // SOVRASCRIVERE o invalidare il cookie di visione legittimo, cioe' rompere il
125
+ // pannello dell'utente dal di dentro. E puo' piantare cookie arbitrari sulla
126
+ // nostra origine.
127
+ const NON_INOLTRATI_DAL_PANNELLO = new Set(['set-cookie']);
128
+
129
+ function responseHeaders(headers) {
130
+ const out = {};
131
+ for (const [k, v] of Object.entries(headers || {})) {
132
+ const key = k.toLowerCase();
133
+ if (HOP_BY_HOP.has(key)) continue;
134
+ if (NON_INOLTRATI_DAL_PANNELLO.has(key)) continue;
135
+ out[k] = v;
136
+ }
137
+ return out;
138
+ }
139
+
140
+ // Risolve la destinazione, o dice PERCHE' non si puo'. I motivi restano
141
+ // distinti: «cella sconosciuta», «cella senza pannello» e «pannello non valido»
142
+ // mandano chi indaga in tre posti diversi, e un rifiuto unico li confonderebbe.
143
+ async function resolveTarget(resolveCellPanel, cellId) {
144
+ if (!CELL_ID_RE.test(cellId)) return { ok: false, reason: 'cell-id-invalid' };
145
+ let panelUrl;
146
+ try { panelUrl = await resolveCellPanel(cellId); } catch (_) { return { ok: false, reason: 'fleet-unavailable' }; }
147
+ if (panelUrl === null) return { ok: false, reason: 'fleet-unavailable' };
148
+ if (panelUrl === undefined) return { ok: false, reason: 'cell-unknown' };
149
+ if (panelUrl === '') return { ok: false, reason: 'no-panel' };
150
+ // Stesso validatore della scrittura: se un valore e' finito nello stato per
151
+ // un'altra strada, qui viene fermato comunque.
152
+ if (!validPanelUrl(panelUrl)) return { ok: false, reason: 'panel-url-invalid' };
153
+ const parsed = new URL(panelUrl);
154
+ // Guardia in profondita': la verifica TLS viene disattivata SOLO se la
155
+ // destinazione e' loopback, e questa riga lo ricontrolla contro la stessa
156
+ // lista che autorizza il campo. Se un giorno il validatore ammettesse un host
157
+ // remoto, la disattivazione NON lo seguirebbe: si parlerebbe in TLS
158
+ // verificato, o si fallirebbe — mai in chiaro con un ignoto.
159
+ const loopback = PANELURL_LOOPBACK_HOSTS.has(parsed.hostname);
160
+ return {
161
+ ok: true,
162
+ loopback,
163
+ secure: parsed.protocol === 'https:',
164
+ host: parsed.hostname,
165
+ port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
166
+ hostHeader: parsed.host,
167
+ };
168
+ }
169
+
170
+ // IL PATH DEL PANNELLO E' `rest`, TAL QUALE. Il contratto D8 (CellPanel): il
171
+ // frame carica /api/panel/<cella> + il pathname del panelUrl, e il browser
172
+ // risolve le sotto-risorse RELATIVE rispetto a quell'origine — al proxy
173
+ // arrivano sempre path assoluti del pannello. Ricomporli col basePath del
174
+ // panelUrl (joinPath, rimossa) li RADDOPPIAVA appena il panelUrl aveva un
175
+ // path: /vnc/lite.html diventava /vnc/lite.html/vnc/lite.html, il pannello
176
+ // rispondeva 404 e il frame restava su «not found» — provato nel browser
177
+ // vero. Il pathname del panelUrl NON e' un prefisso da applicare: e' la
178
+ // prima richiesta che il client fa, non una radice.
179
+
180
+ // Risponde con o senza Express. Il resto del progetto monta le route su un
181
+ // router Express e usa `res.status().json()`, ma questo modulo e' un pezzo di
182
+ // trasporto: farlo dipendere dal framework significa che si rompe appena lo si
183
+ // usa altrove — ed e' successo alla prima prova con un server nudo, mentre la
184
+ // suite mockata non poteva accorgersene perche' il finto offriva `status()`.
185
+ function respond(res, code, body) {
186
+ const payload = JSON.stringify(body);
187
+ if (typeof res.status === 'function' && typeof res.json === 'function') {
188
+ res.status(code).json(body);
189
+ return;
190
+ }
191
+ res.writeHead(code, { 'content-type': 'application/json' });
192
+ res.end(payload);
193
+ }
194
+
195
+ function createPanelProxy({ resolveCellPanel, log = () => {}, requestImpl }) {
196
+ return async function panelProxy(req, res) {
197
+ const parsed = splitPanelPath(req.url);
198
+ if (!parsed) {
199
+ log({ event: 'panel-proxy', outcome: 'rejected', reason: 'no-cell', cell: '' });
200
+ return respond(res, 404, { error: 'pannello non trovato' });
201
+ }
202
+ const target = await resolveTarget(resolveCellPanel, parsed.cellId);
203
+ if (!target.ok) {
204
+ log({ event: 'panel-proxy', outcome: 'rejected', reason: target.reason, cell: parsed.cellId });
205
+ return respond(res, 404, { error: `pannello non disponibile: ${target.reason}` });
206
+ }
207
+ const request = requestImpl || (target.secure ? https.request : http.request);
208
+ const options = {
209
+ host: target.host,
210
+ port: target.port,
211
+ method: req.method,
212
+ path: `${parsed.rest}${stripLocalTokenQuery(parsed.search)}`,
213
+ headers: forwardHeaders(req.headers, target.hostHeader),
214
+ ...(target.secure ? { rejectUnauthorized: !(target.loopback) } : {}),
215
+ };
216
+ let upstream;
217
+ try {
218
+ upstream = request(options, (up) => {
219
+ res.writeHead(up.statusCode, responseHeaders(up.headers));
220
+ up.pipe(res);
221
+ });
222
+ } catch (_) {
223
+ log({ event: 'panel-proxy', outcome: 'error', reason: 'request-failed', cell: parsed.cellId });
224
+ if (!res.headersSent) respond(res, 502, { error: 'pannello non raggiungibile' });
225
+ return;
226
+ }
227
+ upstream.setTimeout(PANEL_TIMEOUT_MS, () => upstream.destroy(new Error('panel timeout')));
228
+ upstream.on('error', () => {
229
+ log({ event: 'panel-proxy', outcome: 'error', reason: 'upstream-error', cell: parsed.cellId });
230
+ if (!res.headersSent) respond(res, 502, { error: 'pannello non raggiungibile' });
231
+ else res.destroy();
232
+ });
233
+ req.on('aborted', () => upstream.destroy());
234
+ log({ event: 'panel-proxy', outcome: 'forwarded', cell: parsed.cellId });
235
+ req.pipe(upstream);
236
+ };
237
+ }
238
+
239
+ // Upgrade WebSocket. Un pannello che si apre senza i suoi frame e' nero: un
240
+ // inoltro solo-HTTP darebbe esattamente quello, con l'aria di funzionare.
241
+ //
242
+ // `authorize(req, url)` decide chi apre l'upgrade ed e' il posto del cookie di
243
+ // visione: le WS del pannello partono dalla pagina nel frame, con URL relativi
244
+ // e senza query — portano il cookie, non possono portare altro. Con il solo
245
+ // `verifyToken` (compatibilita' con i test) resta il vecchio contratto:
246
+ // Bearer oppure ?token=.
247
+ function handlePanelUpgrade({ req, socket, head, resolveCellPanel, verifyToken, authorize, log = () => {}, requestImpl }) {
248
+ // Un upgrade rifiutato deve RISPONDERE prima di chiudere. Chiudere e basta
249
+ // lascia il client in attesa finche' non decide lui di rinunciare: dal lato
250
+ // di chi guarda e' un pannello che non arriva mai, indistinguibile da uno
251
+ // lento. Trovato da una prova con socket veri — la suite mockata non poteva
252
+ // vederlo, perche' li' nessuno aspettava una risposta.
253
+ const kill = (code = 400, motivo = 'Bad Request') => {
254
+ try { socket.end(`HTTP/1.1 ${code} ${motivo}\r\nConnection: close\r\n\r\n`); } catch (_) {}
255
+ try { socket.destroy(); } catch (_) {}
256
+ };
257
+ let url;
258
+ try { url = new URL(req.url, 'http://127.0.0.1'); } catch (_) { return kill(400, 'Bad Request'); }
259
+ const allowed = typeof authorize === 'function'
260
+ ? authorize(req, url)
261
+ : (() => {
262
+ const bearer = (req.headers.authorization || '').replace(/^Bearer\s+/i, '');
263
+ const given = bearer || url.searchParams.get('token') || '';
264
+ return verifyToken(given);
265
+ })();
266
+ if (!allowed) {
267
+ log({ event: 'panel-proxy', outcome: 'rejected', reason: 'unauthorized', cell: '' });
268
+ return kill(401, 'Unauthorized');
269
+ }
270
+ // Il prefisso varia con la porta: /api/panel sul control plane (compat),
271
+ // /panel nudo sulla porta pannello dedicata (P0 sicurezza 2026-08-16).
272
+ const parsed = splitPanelPath(req.url.replace(/^\/(?:api\/)?panel/, ''));
273
+ if (!parsed) return kill(404, 'Not Found');
274
+ Promise.resolve(resolveTarget(resolveCellPanel, parsed.cellId)).then((target) => {
275
+ if (!target.ok) {
276
+ log({ event: 'panel-proxy', outcome: 'rejected', reason: target.reason, cell: parsed.cellId });
277
+ return kill(404, 'Not Found');
278
+ }
279
+ const request = requestImpl || (target.secure ? https.request : http.request);
280
+ const headers = forwardHeaders(req.headers, target.hostHeader);
281
+ headers.connection = 'Upgrade';
282
+ headers.upgrade = 'websocket';
283
+ let upstream;
284
+ try {
285
+ upstream = request({
286
+ host: target.host,
287
+ port: target.port,
288
+ method: 'GET',
289
+ path: `${parsed.rest}${stripLocalTokenQuery(parsed.search)}`,
290
+ headers,
291
+ ...(target.secure ? { rejectUnauthorized: !(target.loopback) } : {}),
292
+ });
293
+ } catch (_) { return kill(502, 'Bad Gateway'); }
294
+ upstream.on('upgrade', (upRes, upSocket, upHead) => {
295
+ const lines = [`HTTP/1.1 ${upRes.statusCode} ${upRes.statusMessage}`];
296
+ // La 101 e' una risposta come le altre per il browser, che ne persiste i
297
+ // cookie: `set-cookie` va tolto QUI come nel ramo HTTP. Non si puo' pero'
298
+ // riusare `responseHeaders()`, perche' toglierebbe anche gli hop-by-hop —
299
+ // e nell'handshake `connection`/`upgrade` SONO la risposta.
300
+ for (const [k, v] of Object.entries(upRes.headers)) {
301
+ if (NON_INOLTRATI_DAL_PANNELLO.has(k.toLowerCase())) continue;
302
+ // Un header ripetuto arriva come array (`set-cookie` sempre): va emesso
303
+ // su piu' righe, non unito in una sola con la virgola.
304
+ if (Array.isArray(v)) { for (const uno of v) lines.push(`${k}: ${uno}`); continue; }
305
+ lines.push(`${k}: ${v}`);
306
+ }
307
+ try {
308
+ socket.write(`${lines.join('\r\n')}\r\n\r\n`);
309
+ if (upHead && upHead.length) socket.write(upHead);
310
+ if (head && head.length) upSocket.write(head);
311
+ } catch (_) { return kill(502, 'Bad Gateway'); }
312
+ log({ event: 'panel-proxy', outcome: 'upgraded', cell: parsed.cellId });
313
+ upSocket.pipe(socket);
314
+ socket.pipe(upSocket);
315
+ const close = () => {
316
+ try { upSocket.destroy(); } catch (_) {}
317
+ try { socket.destroy(); } catch (_) {} // qui l'upgrade e' gia' avvenuto: niente risposta HTTP
318
+ };
319
+ upSocket.on('error', close); socket.on('error', close);
320
+ upSocket.on('close', close); socket.on('close', close);
321
+ });
322
+ // Il pannello ha risposto senza accettare l'upgrade: non e' un WebSocket,
323
+ // e fingere il contrario lascerebbe il frame in attesa per sempre.
324
+ upstream.on('response', () => {
325
+ log({ event: 'panel-proxy', outcome: 'rejected', reason: 'upgrade-refused', cell: parsed.cellId });
326
+ kill(502, 'Bad Gateway');
327
+ });
328
+ upstream.on('error', () => {
329
+ log({ event: 'panel-proxy', outcome: 'error', reason: 'upstream-error', cell: parsed.cellId });
330
+ kill(502, 'Bad Gateway');
331
+ });
332
+ upstream.end();
333
+ }).catch(() => kill(500, 'Internal Server Error'));
334
+ }
335
+
336
+ module.exports = { createPanelProxy, handlePanelUpgrade, splitPanelPath, resolveTarget };