@askexenow/exe-os 0.9.81 → 0.9.82
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.
- package/deploy/stack-manifests/v0.9.json +138 -5
- package/dist/bin/cli.js +7 -6
- package/dist/bin/exe-gateway.js +123 -10
- package/dist/bin/registry-proxy.js +1 -1
- package/dist/bin/stack-update.js +163 -41
- package/dist/lib/exe-daemon.js +167 -76
- package/dist/mcp/server.js +159 -76
- package/package.json +1 -1
- package/stack.release.json +5 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"latest": "0.9.
|
|
3
|
+
"latest": "0.9.5",
|
|
4
4
|
"stacks": {
|
|
5
5
|
"0.9.0": {
|
|
6
6
|
"version": "0.9.0",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"id": "whatsapp_relink_required",
|
|
12
12
|
"title": "WhatsApp QR re-link required for Baileys v7",
|
|
13
13
|
"description": "exe-gateway uses Baileys v7. Existing WhatsApp 6.x linked-device auth state must be backed up and re-linked once with a new QR code.",
|
|
14
|
-
"requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp
|
|
14
|
+
"requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp → Linked Devices after the update.",
|
|
15
15
|
"expectedDowntimeMinutes": "2-5",
|
|
16
16
|
"requiresConfirmation": true
|
|
17
17
|
}
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"id": "whatsapp_relink_required",
|
|
66
66
|
"title": "WhatsApp QR re-link required for Baileys v7",
|
|
67
67
|
"description": "exe-gateway uses Baileys v7. Existing WhatsApp 6.x linked-device auth state must be backed up and re-linked once with a new QR code.",
|
|
68
|
-
"requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp
|
|
68
|
+
"requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp → Linked Devices after the update.",
|
|
69
69
|
"expectedDowntimeMinutes": "2-5",
|
|
70
70
|
"requiresConfirmation": true
|
|
71
71
|
}
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"id": "whatsapp_relink_required",
|
|
120
120
|
"title": "WhatsApp QR re-link required for Baileys v7",
|
|
121
121
|
"description": "exe-gateway uses Baileys v7. Existing WhatsApp 6.x linked-device auth state must be backed up and re-linked once with a new QR code.",
|
|
122
|
-
"requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp
|
|
122
|
+
"requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp → Linked Devices after the update.",
|
|
123
123
|
"expectedDowntimeMinutes": "2-5",
|
|
124
124
|
"requiresConfirmation": true
|
|
125
125
|
}
|
|
@@ -173,7 +173,7 @@
|
|
|
173
173
|
"id": "whatsapp_relink_required",
|
|
174
174
|
"title": "WhatsApp QR re-link required for Baileys v7",
|
|
175
175
|
"description": "exe-gateway uses Baileys v7. Existing WhatsApp 6.x linked-device auth state must be backed up and re-linked once with a new QR code.",
|
|
176
|
-
"requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp
|
|
176
|
+
"requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp → Linked Devices after the update.",
|
|
177
177
|
"expectedDowntimeMinutes": "2-5",
|
|
178
178
|
"requiresConfirmation": true
|
|
179
179
|
}
|
|
@@ -228,6 +228,139 @@
|
|
|
228
228
|
"sourceRef": "v0.9.3"
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
|
+
},
|
|
232
|
+
"0.9.4": {
|
|
233
|
+
"version": "0.9.4",
|
|
234
|
+
"releasedAt": "2026-05-12T00:00:00Z",
|
|
235
|
+
"notes": "Hygo customer stack release with exed refreshed to include npm @askexenow/exe-os@0.9.81 support/MCP parity fixes. Other service images remain pinned to the already-approved v0.9.3 artifacts.",
|
|
236
|
+
"breakingChanges": [
|
|
237
|
+
{
|
|
238
|
+
"id": "whatsapp_relink_required",
|
|
239
|
+
"title": "WhatsApp QR re-link required for Baileys v7",
|
|
240
|
+
"description": "exe-gateway uses Baileys v7. Existing WhatsApp 6.x linked-device auth state must be backed up and re-linked once with a new QR code.",
|
|
241
|
+
"requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp → Linked Devices after the update.",
|
|
242
|
+
"expectedDowntimeMinutes": "2-5",
|
|
243
|
+
"requiresConfirmation": true
|
|
244
|
+
}
|
|
245
|
+
],
|
|
246
|
+
"services": {
|
|
247
|
+
"crm": {
|
|
248
|
+
"env": "CRM_IMAGE_TAG",
|
|
249
|
+
"image": "registry.askexe.com/askexe/exe-crm:v0.9.3",
|
|
250
|
+
"healthUrl": "http://127.0.0.1:3000/healthz",
|
|
251
|
+
"deploymentScope": "customer"
|
|
252
|
+
},
|
|
253
|
+
"wiki": {
|
|
254
|
+
"env": "WIKI_IMAGE_TAG",
|
|
255
|
+
"image": "registry.askexe.com/askexe/exe-wiki:v0.9.3",
|
|
256
|
+
"healthUrl": "http://127.0.0.1:3001/api/ping",
|
|
257
|
+
"deploymentScope": "customer"
|
|
258
|
+
},
|
|
259
|
+
"exed": {
|
|
260
|
+
"env": "EXED_IMAGE_TAG",
|
|
261
|
+
"image": "registry.askexe.com/askexe/exed:v0.9.4",
|
|
262
|
+
"healthUrl": "http://127.0.0.1:8765/health",
|
|
263
|
+
"deploymentScope": "customer"
|
|
264
|
+
},
|
|
265
|
+
"gateway": {
|
|
266
|
+
"env": "GATEWAY_IMAGE_TAG",
|
|
267
|
+
"image": "registry.askexe.com/askexe/exe-gateway:v0.9.3",
|
|
268
|
+
"healthUrl": "http://127.0.0.1:3100/health",
|
|
269
|
+
"deploymentScope": "customer"
|
|
270
|
+
},
|
|
271
|
+
"monitorAgent": {
|
|
272
|
+
"env": "MONITOR_AGENT_IMAGE_TAG",
|
|
273
|
+
"image": "registry.askexe.com/askexe/exe-monitor-agent:v0.9.3",
|
|
274
|
+
"deploymentScope": "customer"
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
"releaseDescriptors": {
|
|
278
|
+
"exed": "AskExe/exe-os@stack-v0.9.4:stack.release.json",
|
|
279
|
+
"crm": "AskExe/exe-crm@0.9.3:stack.release.json",
|
|
280
|
+
"wiki": "AskExe/exe-wiki@0.9.3:stack.release.json",
|
|
281
|
+
"gateway": "AskExe/exe-gateway@0.9.3:stack.release.json",
|
|
282
|
+
"db": "AskExe/exe-db@0.9.3:stack.release.json",
|
|
283
|
+
"monitorAgent": "AskExe/exe-monitor@0.9.3:stack.release.json"
|
|
284
|
+
},
|
|
285
|
+
"componentVersions": {
|
|
286
|
+
"exe-os": {
|
|
287
|
+
"repo": "AskExe/exe-os",
|
|
288
|
+
"npmPackage": "@askexenow/exe-os",
|
|
289
|
+
"npmVersion": "0.9.81",
|
|
290
|
+
"image": "registry.askexe.com/askexe/exed:v0.9.4",
|
|
291
|
+
"imageVersion": "0.9.4",
|
|
292
|
+
"note": "Customer stack image tag is 0.9.4; bundled npm package is @askexenow/exe-os@0.9.81. Hygo pulls through registry.askexe.com, which proxies the AskExe-owned GHCR artifact server-side.",
|
|
293
|
+
"sourceRef": "stack-v0.9.4"
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
"0.9.5": {
|
|
298
|
+
"version": "0.9.5",
|
|
299
|
+
"releasedAt": "2026-05-12T00:00:00Z",
|
|
300
|
+
"notes": "Hygo cold-start fix: exed refreshed to include npm @askexenow/exe-os@0.9.82, fixing exe-os stack-update CLI routing and preserving support/MCP parity tools. Other services remain v0.9.3.",
|
|
301
|
+
"breakingChanges": [
|
|
302
|
+
{
|
|
303
|
+
"id": "whatsapp_relink_required",
|
|
304
|
+
"title": "WhatsApp QR re-link required for Baileys v7",
|
|
305
|
+
"description": "exe-gateway uses Baileys v7. Existing WhatsApp 6.x linked-device auth state must be backed up and re-linked once with a new QR code.",
|
|
306
|
+
"requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp → Linked Devices after the update.",
|
|
307
|
+
"expectedDowntimeMinutes": "2-5",
|
|
308
|
+
"requiresConfirmation": true
|
|
309
|
+
}
|
|
310
|
+
],
|
|
311
|
+
"services": {
|
|
312
|
+
"crm": {
|
|
313
|
+
"env": "CRM_IMAGE_TAG",
|
|
314
|
+
"image": "registry.askexe.com/askexe/exe-crm:v0.9.3",
|
|
315
|
+
"healthUrl": "http://127.0.0.1:3000/healthz",
|
|
316
|
+
"deploymentScope": "customer"
|
|
317
|
+
},
|
|
318
|
+
"wiki": {
|
|
319
|
+
"env": "WIKI_IMAGE_TAG",
|
|
320
|
+
"image": "registry.askexe.com/askexe/exe-wiki:v0.9.3",
|
|
321
|
+
"healthUrl": "http://127.0.0.1:3001/api/ping",
|
|
322
|
+
"deploymentScope": "customer"
|
|
323
|
+
},
|
|
324
|
+
"exed": {
|
|
325
|
+
"env": "EXED_IMAGE_TAG",
|
|
326
|
+
"image": "registry.askexe.com/askexe/exed:v0.9.5",
|
|
327
|
+
"healthUrl": "http://127.0.0.1:8765/health",
|
|
328
|
+
"deploymentScope": "customer"
|
|
329
|
+
},
|
|
330
|
+
"gateway": {
|
|
331
|
+
"env": "GATEWAY_IMAGE_TAG",
|
|
332
|
+
"image": "registry.askexe.com/askexe/exe-gateway:v0.9.3",
|
|
333
|
+
"healthUrl": "http://127.0.0.1:3100/health",
|
|
334
|
+
"deploymentScope": "customer"
|
|
335
|
+
},
|
|
336
|
+
"monitorAgent": {
|
|
337
|
+
"env": "MONITOR_AGENT_IMAGE_TAG",
|
|
338
|
+
"image": "registry.askexe.com/askexe/exe-monitor-agent:v0.9.3",
|
|
339
|
+
"deploymentScope": "customer"
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
"releaseDescriptors": {
|
|
343
|
+
"exed": "AskExe/exe-os@stack-v0.9.4:stack.release.json",
|
|
344
|
+
"crm": "AskExe/exe-crm@0.9.3:stack.release.json",
|
|
345
|
+
"wiki": "AskExe/exe-wiki@0.9.3:stack.release.json",
|
|
346
|
+
"gateway": "AskExe/exe-gateway@0.9.3:stack.release.json",
|
|
347
|
+
"db": "AskExe/exe-db@0.9.3:stack.release.json",
|
|
348
|
+
"monitorAgent": "AskExe/exe-monitor@0.9.3:stack.release.json"
|
|
349
|
+
},
|
|
350
|
+
"componentVersions": {
|
|
351
|
+
"exe-os": {
|
|
352
|
+
"repo": "AskExe/exe-os",
|
|
353
|
+
"npmPackage": "@askexenow/exe-os",
|
|
354
|
+
"npmVersion": "0.9.82",
|
|
355
|
+
"image": "registry.askexe.com/askexe/exed:v0.9.4",
|
|
356
|
+
"imageVersion": "0.9.5",
|
|
357
|
+
"note": "Customer stack image tag is 0.9.4; bundled npm package is @askexenow/exe-os@0.9.81. Hygo pulls through registry.askexe.com, which proxies the AskExe-owned GHCR artifact server-side.",
|
|
358
|
+
"sourceRef": "stack-v0.9.4",
|
|
359
|
+
"commit": "main@stack-v0.9.5"
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
"date": "2026-05-12",
|
|
363
|
+
"sourceRef": "stack-v0.9.5"
|
|
231
364
|
}
|
|
232
365
|
}
|
|
233
366
|
}
|
package/dist/bin/cli.js
CHANGED
|
@@ -20162,7 +20162,7 @@ function defaultStackPaths() {
|
|
|
20162
20162
|
manifestRef: process.env.EXE_STACK_MANIFEST || "https://update.askexe.com/stack-manifest.json",
|
|
20163
20163
|
auditUrl: process.env.EXE_STACK_AUDIT_URL || "https://update.askexe.com/v1/deploy-audits",
|
|
20164
20164
|
imageCredentialsUrl: process.env.EXE_STACK_IMAGE_CREDENTIALS_URL || "https://update.askexe.com/v1/image-credentials",
|
|
20165
|
-
manifestAuthToken: process.env.EXE_STACK_UPDATE_TOKEN,
|
|
20165
|
+
manifestAuthToken: process.env.EXE_STACK_UPDATE_TOKEN || process.env.EXE_LICENSE_KEY || loadLicense() || void 0,
|
|
20166
20166
|
manifestPublicKey: loadDefaultPublicKey()
|
|
20167
20167
|
};
|
|
20168
20168
|
}
|
|
@@ -20177,6 +20177,7 @@ var ASKEXE_GHCR_IMAGE;
|
|
|
20177
20177
|
var init_stack_update = __esm({
|
|
20178
20178
|
"src/lib/stack-update.ts"() {
|
|
20179
20179
|
"use strict";
|
|
20180
|
+
init_license();
|
|
20180
20181
|
ASKEXE_GHCR_IMAGE = /^(?:ghcr\.io\/askexe|registry\.askexe\.com\/askexe)\/[a-z0-9._/-]+(?::[^:@$/{]+|@sha256:[a-f0-9]{64})$/i;
|
|
20181
20182
|
}
|
|
20182
20183
|
});
|
|
@@ -20310,8 +20311,8 @@ function printBreaking(changes) {
|
|
|
20310
20311
|
if (c.expectedDowntimeMinutes) console.log(` Expected downtime: ${c.expectedDowntimeMinutes} minutes`);
|
|
20311
20312
|
}
|
|
20312
20313
|
}
|
|
20313
|
-
async function main7() {
|
|
20314
|
-
const opts = parseArgs4(
|
|
20314
|
+
async function main7(args2 = process.argv.slice(2)) {
|
|
20315
|
+
const opts = parseArgs4(args2);
|
|
20315
20316
|
if (opts.rollback) {
|
|
20316
20317
|
if (!opts.yes) {
|
|
20317
20318
|
console.error("Refusing to rollback without --yes.");
|
|
@@ -20354,7 +20355,7 @@ var init_stack_update2 = __esm({
|
|
|
20354
20355
|
"use strict";
|
|
20355
20356
|
init_is_main();
|
|
20356
20357
|
init_stack_update();
|
|
20357
|
-
if (isMainModule(import.meta.url)) {
|
|
20358
|
+
if (isMainModule(import.meta.url) && (process.argv[1] ?? "").includes("stack-update")) {
|
|
20358
20359
|
main7().catch((err) => {
|
|
20359
20360
|
console.error(err instanceof Error ? err.message : String(err));
|
|
20360
20361
|
process.exit(1);
|
|
@@ -20558,7 +20559,7 @@ var init_registry_proxy2 = __esm({
|
|
|
20558
20559
|
"use strict";
|
|
20559
20560
|
init_is_main();
|
|
20560
20561
|
init_registry_proxy();
|
|
20561
|
-
if (isMainModule(import.meta.url)) {
|
|
20562
|
+
if (isMainModule(import.meta.url) && (process.argv[1] ?? "").includes("registry-proxy")) {
|
|
20562
20563
|
main8().catch((err) => {
|
|
20563
20564
|
console.error(err instanceof Error ? err.message : String(err));
|
|
20564
20565
|
process.exit(1);
|
|
@@ -36351,7 +36352,7 @@ ID: ${result.id}`);
|
|
|
36351
36352
|
await runUpdate2(args.slice(1));
|
|
36352
36353
|
} else if (args[0] === "stack-update") {
|
|
36353
36354
|
const { runStackUpdateCli } = await Promise.resolve().then(() => (init_stack_update2(), stack_update_exports));
|
|
36354
|
-
await runStackUpdateCli();
|
|
36355
|
+
await runStackUpdateCli(args.slice(1));
|
|
36355
36356
|
} else if (args[0] === "registry-proxy") {
|
|
36356
36357
|
const { main: runRegistryProxy2 } = await Promise.resolve().then(() => (init_registry_proxy2(), registry_proxy_exports));
|
|
36357
36358
|
await runRegistryProxy2(args.slice(1));
|
package/dist/bin/exe-gateway.js
CHANGED
|
@@ -8098,11 +8098,11 @@ __export(signal_exports, {
|
|
|
8098
8098
|
SignalAdapter: () => SignalAdapter
|
|
8099
8099
|
});
|
|
8100
8100
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
8101
|
-
var
|
|
8101
|
+
var DEFAULT_TIMEOUT_MS2, POLL_INTERVAL_MS, SignalAdapter;
|
|
8102
8102
|
var init_signal = __esm({
|
|
8103
8103
|
"src/gateway/adapters/signal.ts"() {
|
|
8104
8104
|
"use strict";
|
|
8105
|
-
|
|
8105
|
+
DEFAULT_TIMEOUT_MS2 = 1e4;
|
|
8106
8106
|
POLL_INTERVAL_MS = 2e3;
|
|
8107
8107
|
SignalAdapter = class {
|
|
8108
8108
|
platform = "signal";
|
|
@@ -8163,7 +8163,7 @@ var init_signal = __esm({
|
|
|
8163
8163
|
method: "POST",
|
|
8164
8164
|
headers: { "Content-Type": "application/json" },
|
|
8165
8165
|
body: JSON.stringify(body),
|
|
8166
|
-
signal: AbortSignal.timeout(
|
|
8166
|
+
signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS2)
|
|
8167
8167
|
});
|
|
8168
8168
|
if (!res.ok) {
|
|
8169
8169
|
const errText = await res.text().catch(() => "");
|
|
@@ -8181,7 +8181,7 @@ var init_signal = __esm({
|
|
|
8181
8181
|
recipient: isGroup ? void 0 : channelId,
|
|
8182
8182
|
group_id: isGroup ? channelId.slice("group:".length) : void 0
|
|
8183
8183
|
}),
|
|
8184
|
-
signal: AbortSignal.timeout(
|
|
8184
|
+
signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS2)
|
|
8185
8185
|
});
|
|
8186
8186
|
} catch {
|
|
8187
8187
|
}
|
|
@@ -8192,7 +8192,7 @@ var init_signal = __esm({
|
|
|
8192
8192
|
try {
|
|
8193
8193
|
const res = await fetch(`${this.baseUrl}/v1/about`, {
|
|
8194
8194
|
method: "GET",
|
|
8195
|
-
signal: AbortSignal.timeout(
|
|
8195
|
+
signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS2)
|
|
8196
8196
|
});
|
|
8197
8197
|
this._connected = res.ok;
|
|
8198
8198
|
return {
|
|
@@ -8223,7 +8223,7 @@ var init_signal = __esm({
|
|
|
8223
8223
|
`${this.baseUrl}/v1/receive/${encodeURIComponent(this.account)}`,
|
|
8224
8224
|
{
|
|
8225
8225
|
method: "GET",
|
|
8226
|
-
signal: AbortSignal.timeout(
|
|
8226
|
+
signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS2)
|
|
8227
8227
|
}
|
|
8228
8228
|
);
|
|
8229
8229
|
if (!res.ok) {
|
|
@@ -8366,7 +8366,7 @@ var init_signal = __esm({
|
|
|
8366
8366
|
if (!this.messageHandler || !this.account) return;
|
|
8367
8367
|
const res = await fetch(
|
|
8368
8368
|
`${this.baseUrl}/v1/contacts/${encodeURIComponent(this.account)}`,
|
|
8369
|
-
{ signal: AbortSignal.timeout(
|
|
8369
|
+
{ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS2) }
|
|
8370
8370
|
);
|
|
8371
8371
|
if (!res.ok) return;
|
|
8372
8372
|
const contacts = await res.json();
|
|
@@ -8401,7 +8401,7 @@ var init_signal = __esm({
|
|
|
8401
8401
|
if (!this.messageHandler || !this.account) return;
|
|
8402
8402
|
const res = await fetch(
|
|
8403
8403
|
`${this.baseUrl}/v1/groups/${encodeURIComponent(this.account)}`,
|
|
8404
|
-
{ signal: AbortSignal.timeout(
|
|
8404
|
+
{ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS2) }
|
|
8405
8405
|
);
|
|
8406
8406
|
if (!res.ok) return;
|
|
8407
8407
|
const groups = await res.json();
|
|
@@ -8619,12 +8619,12 @@ var init_discord = __esm({
|
|
|
8619
8619
|
messageHandler = null;
|
|
8620
8620
|
connected = false;
|
|
8621
8621
|
async connect(config2) {
|
|
8622
|
-
const { Client, GatewayIntentBits } = await import("discord.js");
|
|
8622
|
+
const { Client: Client2, GatewayIntentBits } = await import("discord.js");
|
|
8623
8623
|
const token = config2.credentials.bot_token ?? config2.credentials.token;
|
|
8624
8624
|
if (!token) {
|
|
8625
8625
|
throw new Error("Discord requires bot_token in credentials");
|
|
8626
8626
|
}
|
|
8627
|
-
const discordClient = new
|
|
8627
|
+
const discordClient = new Client2({
|
|
8628
8628
|
intents: [
|
|
8629
8629
|
GatewayIntentBits.Guilds,
|
|
8630
8630
|
GatewayIntentBits.GuildMessages,
|
|
@@ -13843,6 +13843,91 @@ function toIsoTimestamp(value) {
|
|
|
13843
13843
|
return UNKNOWN_TIMESTAMP;
|
|
13844
13844
|
}
|
|
13845
13845
|
|
|
13846
|
+
// src/gateway/read-only-sql.ts
|
|
13847
|
+
import { Client } from "pg";
|
|
13848
|
+
var DEFAULT_MAX_ROWS = 100;
|
|
13849
|
+
var HARD_MAX_ROWS = 1e3;
|
|
13850
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
13851
|
+
var BLOCKED_SQL = /\b(insert|update|delete|merge|upsert|drop|alter|create|truncate|grant|revoke|copy|call|do|listen|notify|vacuum|analyze|comment|security|set\s+role|reset|begin|commit|rollback|savepoint|lock)\b/i;
|
|
13852
|
+
var ReadOnlySqlValidationError = class extends Error {
|
|
13853
|
+
constructor(message) {
|
|
13854
|
+
super(message);
|
|
13855
|
+
this.name = "ReadOnlySqlValidationError";
|
|
13856
|
+
}
|
|
13857
|
+
};
|
|
13858
|
+
var ReadOnlySqlRunner = class {
|
|
13859
|
+
constructor(config2 = {}) {
|
|
13860
|
+
this.config = config2;
|
|
13861
|
+
}
|
|
13862
|
+
async run(sql, maxRows) {
|
|
13863
|
+
const databaseUrl = this.config.databaseUrl ?? process.env.EXE_GATEWAY_SQL_DATABASE_URL ?? process.env.EXE_COMPANY_BRAIN_SQL_DATABASE_URL ?? process.env.DATABASE_URL;
|
|
13864
|
+
if (!databaseUrl) {
|
|
13865
|
+
throw new Error("Read-only SQL is not configured. Set EXE_GATEWAY_SQL_DATABASE_URL or DATABASE_URL.");
|
|
13866
|
+
}
|
|
13867
|
+
const cleaned = validateSql(sql);
|
|
13868
|
+
const limit = clampRows(maxRows ?? this.config.maxRows ?? DEFAULT_MAX_ROWS);
|
|
13869
|
+
const timeoutMs = this.config.statementTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
13870
|
+
const wrappedSql = `SELECT * FROM (${cleaned}) AS exe_readonly_sql LIMIT ${limit + 1}`;
|
|
13871
|
+
const client = new Client({ connectionString: databaseUrl });
|
|
13872
|
+
const start = Date.now();
|
|
13873
|
+
try {
|
|
13874
|
+
await client.connect();
|
|
13875
|
+
await client.query("BEGIN READ ONLY");
|
|
13876
|
+
await client.query("SET LOCAL transaction_read_only = on");
|
|
13877
|
+
await client.query("SET LOCAL statement_timeout = $1", [timeoutMs]);
|
|
13878
|
+
const result = await client.query(wrappedSql);
|
|
13879
|
+
await client.query("COMMIT");
|
|
13880
|
+
const rows = result.rows.slice(0, limit);
|
|
13881
|
+
return {
|
|
13882
|
+
columns: result.fields.map((field) => field.name),
|
|
13883
|
+
rows,
|
|
13884
|
+
row_count: rows.length,
|
|
13885
|
+
max_rows: limit,
|
|
13886
|
+
truncated: result.rows.length > limit,
|
|
13887
|
+
execution_ms: Date.now() - start
|
|
13888
|
+
};
|
|
13889
|
+
} catch (err) {
|
|
13890
|
+
try {
|
|
13891
|
+
await client.query("ROLLBACK");
|
|
13892
|
+
} catch {
|
|
13893
|
+
}
|
|
13894
|
+
throw err;
|
|
13895
|
+
} finally {
|
|
13896
|
+
await client.end().catch(() => void 0);
|
|
13897
|
+
}
|
|
13898
|
+
}
|
|
13899
|
+
};
|
|
13900
|
+
function validateSql(sql) {
|
|
13901
|
+
const trimmed = sql.trim().replace(/;\s*$/, "");
|
|
13902
|
+
if (!trimmed) throw new ReadOnlySqlValidationError("sql is required");
|
|
13903
|
+
if (!/^(select|with)\b/i.test(trimmed)) {
|
|
13904
|
+
throw new ReadOnlySqlValidationError("Only SELECT/WITH read-only SQL is allowed");
|
|
13905
|
+
}
|
|
13906
|
+
if (hasMultipleStatements(trimmed)) {
|
|
13907
|
+
throw new ReadOnlySqlValidationError("Multiple SQL statements are not allowed");
|
|
13908
|
+
}
|
|
13909
|
+
if (BLOCKED_SQL.test(trimmed)) {
|
|
13910
|
+
throw new ReadOnlySqlValidationError("SQL contains a blocked write/admin keyword");
|
|
13911
|
+
}
|
|
13912
|
+
return trimmed;
|
|
13913
|
+
}
|
|
13914
|
+
function hasMultipleStatements(sql) {
|
|
13915
|
+
let single = false;
|
|
13916
|
+
let double = false;
|
|
13917
|
+
for (let i = 0; i < sql.length; i += 1) {
|
|
13918
|
+
const ch = sql[i];
|
|
13919
|
+
const prev = sql[i - 1];
|
|
13920
|
+
if (ch === "'" && !double && prev !== "\\") single = !single;
|
|
13921
|
+
if (ch === '"' && !single && prev !== "\\") double = !double;
|
|
13922
|
+
if (ch === ";" && !single && !double) return true;
|
|
13923
|
+
}
|
|
13924
|
+
return false;
|
|
13925
|
+
}
|
|
13926
|
+
function clampRows(value) {
|
|
13927
|
+
if (!Number.isFinite(value) || value <= 0) return DEFAULT_MAX_ROWS;
|
|
13928
|
+
return Math.min(Math.floor(value), HARD_MAX_ROWS);
|
|
13929
|
+
}
|
|
13930
|
+
|
|
13846
13931
|
// src/gateway/webhook-server.ts
|
|
13847
13932
|
var DEFAULT_HOST = process.env.EXE_WEBHOOK_HOST || "127.0.0.1";
|
|
13848
13933
|
var BODY_SIZE_LIMIT = 1048576;
|
|
@@ -13859,6 +13944,7 @@ var WebhookServer = class {
|
|
|
13859
13944
|
server = null;
|
|
13860
13945
|
handlers = /* @__PURE__ */ new Map();
|
|
13861
13946
|
startedAt = 0;
|
|
13947
|
+
readOnlySqlRunner = new ReadOnlySqlRunner();
|
|
13862
13948
|
/** Register a handler for a platform: POST /webhook/:platform */
|
|
13863
13949
|
onPlatform(platform, handler) {
|
|
13864
13950
|
this.handlers.set(platform, handler);
|
|
@@ -13905,6 +13991,10 @@ var WebhookServer = class {
|
|
|
13905
13991
|
await this.handleQuery(req, res);
|
|
13906
13992
|
return;
|
|
13907
13993
|
}
|
|
13994
|
+
if (method === "POST" && isRoute(url, "/query/sql")) {
|
|
13995
|
+
await this.handleReadOnlySql(req, res);
|
|
13996
|
+
return;
|
|
13997
|
+
}
|
|
13908
13998
|
if (method === "GET" && url.startsWith("/webhook/whatsapp")) {
|
|
13909
13999
|
this.handleWhatsAppVerification(req, res);
|
|
13910
14000
|
return;
|
|
@@ -13947,6 +14037,29 @@ var WebhookServer = class {
|
|
|
13947
14037
|
sendJson(res, 503, { error: "Database unavailable" });
|
|
13948
14038
|
}
|
|
13949
14039
|
}
|
|
14040
|
+
async handleReadOnlySql(req, res) {
|
|
14041
|
+
if ((this.config.authToken || this.config.queryAuthToken) && !this.verifyQueryAuth(req)) {
|
|
14042
|
+
sendJson(res, 401, { error: "Unauthorized" });
|
|
14043
|
+
return;
|
|
14044
|
+
}
|
|
14045
|
+
try {
|
|
14046
|
+
const body = await readBody(req);
|
|
14047
|
+
const sql = typeof body.sql === "string" ? body.sql : "";
|
|
14048
|
+
const maxRows = typeof body.max_rows === "number" ? body.max_rows : void 0;
|
|
14049
|
+
const result = await this.readOnlySqlRunner.run(sql, maxRows);
|
|
14050
|
+
sendJson(res, 200, result);
|
|
14051
|
+
} catch (err) {
|
|
14052
|
+
if (err instanceof ReadOnlySqlValidationError) {
|
|
14053
|
+
sendJson(res, 400, { error: err.message });
|
|
14054
|
+
return;
|
|
14055
|
+
}
|
|
14056
|
+
console.error(
|
|
14057
|
+
"[webhook-server] Read-only SQL error:",
|
|
14058
|
+
err instanceof Error ? err.message : err
|
|
14059
|
+
);
|
|
14060
|
+
sendJson(res, 503, { error: err instanceof Error ? err.message : "Database unavailable" });
|
|
14061
|
+
}
|
|
14062
|
+
}
|
|
13950
14063
|
handleWhatsAppVerification(req, res) {
|
|
13951
14064
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
13952
14065
|
const mode = url.searchParams.get("hub.mode");
|
|
@@ -196,7 +196,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
196
196
|
}
|
|
197
197
|
await runRegistryProxy(registryProxyOptionsFromEnv());
|
|
198
198
|
}
|
|
199
|
-
if (isMainModule(import.meta.url)) {
|
|
199
|
+
if (isMainModule(import.meta.url) && (process.argv[1] ?? "").includes("registry-proxy")) {
|
|
200
200
|
main().catch((err) => {
|
|
201
201
|
console.error(err instanceof Error ? err.message : String(err));
|
|
202
202
|
process.exit(1);
|