@smithers-orchestrator/server 0.20.4 → 0.21.0
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/package.json +13 -13
- package/src/GatewayOperatorUiConfig.ts +15 -0
- package/src/GatewayOptions.ts +6 -0
- package/src/GatewayUiConfig.ts +3 -2
- package/src/gateway.js +67 -37
- package/src/gatewayUi/auth.js +16 -0
- package/src/gatewayUi/bundle.js +36 -0
- package/src/gatewayUi/defaultConsole.js +5 -0
- package/src/gatewayUi/defaultOperatorUi.js +469 -0
- package/src/index.d.ts +30 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smithers-orchestrator/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "HTTP, WebSocket, gateway, cron, webhook, and metrics servers for Smithers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -26,24 +26,24 @@
|
|
|
26
26
|
"effect": "^3.21.1",
|
|
27
27
|
"hono": "^4.12.14",
|
|
28
28
|
"ws": "^8.20.0",
|
|
29
|
-
"@smithers-orchestrator/
|
|
30
|
-
"@smithers-orchestrator/
|
|
31
|
-
"@smithers-orchestrator/
|
|
32
|
-
"@smithers-orchestrator/
|
|
33
|
-
"@smithers-orchestrator/errors": "0.
|
|
34
|
-
"@smithers-orchestrator/
|
|
35
|
-
"@smithers-orchestrator/
|
|
36
|
-
"@smithers-orchestrator/
|
|
37
|
-
"@smithers-orchestrator/
|
|
38
|
-
"@smithers-orchestrator/
|
|
39
|
-
"@smithers-orchestrator/
|
|
29
|
+
"@smithers-orchestrator/db": "0.21.0",
|
|
30
|
+
"@smithers-orchestrator/devtools": "0.21.0",
|
|
31
|
+
"@smithers-orchestrator/components": "0.21.0",
|
|
32
|
+
"@smithers-orchestrator/driver": "0.21.0",
|
|
33
|
+
"@smithers-orchestrator/errors": "0.21.0",
|
|
34
|
+
"@smithers-orchestrator/engine": "0.21.0",
|
|
35
|
+
"@smithers-orchestrator/gateway": "0.21.0",
|
|
36
|
+
"@smithers-orchestrator/observability": "0.21.0",
|
|
37
|
+
"@smithers-orchestrator/protocol": "0.21.0",
|
|
38
|
+
"@smithers-orchestrator/scheduler": "0.21.0",
|
|
39
|
+
"@smithers-orchestrator/time-travel": "0.21.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/bun": "latest",
|
|
43
43
|
"react": "^19.2.5",
|
|
44
44
|
"typescript": "~5.9.3",
|
|
45
45
|
"zod": "^4.3.6",
|
|
46
|
-
"@smithers-orchestrator/graph": "0.
|
|
46
|
+
"@smithers-orchestrator/graph": "0.21.0"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"test": "bun test tests",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type GatewayOperatorUiConfig = {
|
|
2
|
+
/**
|
|
3
|
+
* URL path for the built-in operator console.
|
|
4
|
+
* @default "/console"
|
|
5
|
+
*/
|
|
6
|
+
path?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Document title for the generated HTML shell.
|
|
9
|
+
*/
|
|
10
|
+
title?: string;
|
|
11
|
+
/**
|
|
12
|
+
* JSON-serializable boot data exposed to the browser.
|
|
13
|
+
*/
|
|
14
|
+
props?: Record<string, unknown>;
|
|
15
|
+
};
|
package/src/GatewayOptions.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { GatewayAuthConfig } from "./GatewayAuthConfig.js";
|
|
2
2
|
import type { GatewayDefaults } from "./GatewayDefaults.js";
|
|
3
|
+
import type { GatewayOperatorUiConfig } from "./GatewayOperatorUiConfig.js";
|
|
3
4
|
import type { GatewayUiConfig } from "./GatewayUiConfig.js";
|
|
4
5
|
|
|
5
6
|
export type GatewayOptions = {
|
|
@@ -8,6 +9,11 @@ export type GatewayOptions = {
|
|
|
8
9
|
heartbeatMs?: number;
|
|
9
10
|
auth?: GatewayAuthConfig;
|
|
10
11
|
ui?: GatewayUiConfig;
|
|
12
|
+
/**
|
|
13
|
+
* Built-in browser console for operators. Set to false to disable it.
|
|
14
|
+
* @default { path: "/console" }
|
|
15
|
+
*/
|
|
16
|
+
operatorUi?: GatewayOperatorUiConfig | false;
|
|
11
17
|
defaults?: GatewayDefaults;
|
|
12
18
|
maxBodyBytes?: number;
|
|
13
19
|
maxPayload?: number;
|
package/src/GatewayUiConfig.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export type GatewayUiConfig = {
|
|
1
|
+
export type GatewayUiConfig = true | {
|
|
2
2
|
/**
|
|
3
3
|
* Browser entry module for the React app. Smithers bundles this with Bun and
|
|
4
|
-
* serves it from the Gateway origin.
|
|
4
|
+
* serves it from the Gateway origin. Pass `true` to mount the built-in
|
|
5
|
+
* operator console.
|
|
5
6
|
*/
|
|
6
7
|
entry: string;
|
|
7
8
|
/**
|
package/src/gateway.js
CHANGED
|
@@ -40,10 +40,15 @@ import { recoverInProgressRewindAudits } from "@smithers-orchestrator/time-trave
|
|
|
40
40
|
import { GATEWAY_EVENT_WINDOW_DEFAULT, SMITHERS_API_VERSION, getRequiredScopeForGatewayMethod, } from "@smithers-orchestrator/gateway/rpc";
|
|
41
41
|
import { hasGatewayScope } from "@smithers-orchestrator/gateway/auth/scopes";
|
|
42
42
|
import { createGatewayUiApp } from "./gatewayUi/createGatewayUiApp.js";
|
|
43
|
+
import { renderDefaultConsoleClient } from "./gatewayUi/defaultConsole.js";
|
|
44
|
+
import { authorizeGatewayUiRequest } from "./gatewayUi/auth.js";
|
|
45
|
+
import { bundleGatewayUiEntry } from "./gatewayUi/bundle.js";
|
|
46
|
+
import { DEFAULT_OPERATOR_UI_ENTRY } from "./gatewayUi/defaultOperatorUi.js";
|
|
43
47
|
/** @typedef {import("./GatewayWebhookRunConfig.js").GatewayWebhookRunConfig} GatewayWebhookRunConfig */
|
|
44
48
|
/** @typedef {import("./GatewayWebhookSignalConfig.js").GatewayWebhookSignalConfig} GatewayWebhookSignalConfig */
|
|
45
49
|
/** @typedef {import("./ConnectRequest.js").ConnectRequest} ConnectRequest */
|
|
46
50
|
/** @typedef {import("./GatewayAuthConfig.js").GatewayAuthConfig} GatewayAuthConfig */
|
|
51
|
+
/** @typedef {import("./GatewayOperatorUiConfig.js").GatewayOperatorUiConfig} GatewayOperatorUiConfig */
|
|
47
52
|
/** @typedef {import("./GatewayOptions.js").GatewayOptions} GatewayOptions */
|
|
48
53
|
/** @typedef {import("./GatewayWebhookConfig.js").GatewayWebhookConfig} GatewayWebhookConfig */
|
|
49
54
|
/** @typedef {import("node:http").IncomingMessage} IncomingMessage */
|
|
@@ -111,11 +116,12 @@ import { createGatewayUiApp } from "./gatewayUi/createGatewayUiApp.js";
|
|
|
111
116
|
* path: string;
|
|
112
117
|
* title?: string;
|
|
113
118
|
* props?: Record<string, unknown>;
|
|
119
|
+
* builtin?: "operator";
|
|
114
120
|
* }} ResolvedGatewayUiConfig
|
|
115
121
|
*/
|
|
116
122
|
/**
|
|
117
123
|
* @typedef {{
|
|
118
|
-
* kind: "gateway" | "workflow";
|
|
124
|
+
* kind: "gateway" | "workflow" | "operator";
|
|
119
125
|
* workflowKey: string | null;
|
|
120
126
|
* config: ResolvedGatewayUiConfig;
|
|
121
127
|
* }} GatewayUiMount
|
|
@@ -195,6 +201,15 @@ function resolveGatewayUiConfig(ui, fallbackPath) {
|
|
|
195
201
|
if (!ui) {
|
|
196
202
|
return null;
|
|
197
203
|
}
|
|
204
|
+
if (ui === true) {
|
|
205
|
+
return {
|
|
206
|
+
entry: DEFAULT_OPERATOR_UI_ENTRY,
|
|
207
|
+
path: normalizeUiMountPath(fallbackPath === "/" ? "/console" : fallbackPath, fallbackPath),
|
|
208
|
+
title: "Smithers Operator Console",
|
|
209
|
+
builtin: "operator",
|
|
210
|
+
props: {},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
198
213
|
if (typeof ui.entry !== "string" || !ui.entry.trim()) {
|
|
199
214
|
throw new SmithersError("INVALID_INPUT", "Gateway UI config requires a non-empty entry path.");
|
|
200
215
|
}
|
|
@@ -207,6 +222,25 @@ function resolveGatewayUiConfig(ui, fallbackPath) {
|
|
|
207
222
|
: {}),
|
|
208
223
|
};
|
|
209
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* @param {GatewayOperatorUiConfig | false | undefined} ui
|
|
227
|
+
* @returns {ResolvedGatewayUiConfig | null}
|
|
228
|
+
*/
|
|
229
|
+
function resolveDefaultOperatorUiConfig(ui) {
|
|
230
|
+
if (ui === false) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
const config = ui && typeof ui === "object" && !Array.isArray(ui) ? ui : {};
|
|
234
|
+
return {
|
|
235
|
+
entry: DEFAULT_OPERATOR_UI_ENTRY,
|
|
236
|
+
path: normalizeUiMountPath(config.path, "/console"),
|
|
237
|
+
title: typeof config.title === "string" ? config.title : "Smithers Operator Console",
|
|
238
|
+
props: config.props && typeof config.props === "object" && !Array.isArray(config.props)
|
|
239
|
+
? config.props
|
|
240
|
+
: {},
|
|
241
|
+
builtin: "operator",
|
|
242
|
+
};
|
|
243
|
+
}
|
|
210
244
|
|
|
211
245
|
/**
|
|
212
246
|
* @param {import("node:http").IncomingHttpHeaders} headers
|
|
@@ -1197,6 +1231,7 @@ export class Gateway {
|
|
|
1197
1231
|
requestTimeout;
|
|
1198
1232
|
auth;
|
|
1199
1233
|
ui;
|
|
1234
|
+
operatorUi;
|
|
1200
1235
|
uiApp;
|
|
1201
1236
|
defaults;
|
|
1202
1237
|
workflows = new Map();
|
|
@@ -1243,6 +1278,7 @@ export class Gateway {
|
|
|
1243
1278
|
: Math.floor(assertPositiveFiniteInteger("requestTimeout", Number(options.requestTimeout)));
|
|
1244
1279
|
this.auth = options.auth;
|
|
1245
1280
|
this.ui = resolveGatewayUiConfig(options.ui, "/");
|
|
1281
|
+
this.operatorUi = resolveDefaultOperatorUiConfig(options.operatorUi);
|
|
1246
1282
|
this.uiApp = createGatewayUiApp({
|
|
1247
1283
|
resolveMatch: (pathname) => this.resolveUiMatch(pathname),
|
|
1248
1284
|
renderIndex: (match) => this.renderUiIndex(match),
|
|
@@ -1256,7 +1292,14 @@ export class Gateway {
|
|
|
1256
1292
|
getUiMounts() {
|
|
1257
1293
|
const mounts = [];
|
|
1258
1294
|
if (this.ui) {
|
|
1259
|
-
mounts.push({
|
|
1295
|
+
mounts.push({
|
|
1296
|
+
kind: this.ui.builtin === "operator" ? "operator" : "gateway",
|
|
1297
|
+
workflowKey: null,
|
|
1298
|
+
config: this.ui,
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
if (this.operatorUi && (!this.ui || this.ui.path !== this.operatorUi.path)) {
|
|
1302
|
+
mounts.push({ kind: "operator", workflowKey: null, config: this.operatorUi });
|
|
1260
1303
|
}
|
|
1261
1304
|
for (const [workflowKey, entry] of this.workflows.entries()) {
|
|
1262
1305
|
if (entry.ui) {
|
|
@@ -1341,47 +1384,19 @@ export class Gateway {
|
|
|
1341
1384
|
if (match.assetPath !== "client.js") {
|
|
1342
1385
|
return null;
|
|
1343
1386
|
}
|
|
1344
|
-
|
|
1387
|
+
if (match.config.config.builtin === "operator") {
|
|
1388
|
+
return {
|
|
1389
|
+
body: renderDefaultConsoleClient(),
|
|
1390
|
+
contentType: "text/javascript; charset=utf-8",
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
const body = await bundleGatewayUiEntry(match.config.config, this.uiAssetCache);
|
|
1345
1394
|
return {
|
|
1346
1395
|
body,
|
|
1347
1396
|
contentType: "text/javascript; charset=utf-8",
|
|
1348
1397
|
};
|
|
1349
1398
|
}
|
|
1350
1399
|
/**
|
|
1351
|
-
* @param {ResolvedGatewayUiConfig} config
|
|
1352
|
-
* @returns {Promise<string>}
|
|
1353
|
-
*/
|
|
1354
|
-
async bundleUiEntry(config) {
|
|
1355
|
-
const cached = this.uiAssetCache.get(config.entry);
|
|
1356
|
-
if (cached) {
|
|
1357
|
-
return cached;
|
|
1358
|
-
}
|
|
1359
|
-
if (typeof Bun === "undefined" || typeof Bun.build !== "function") {
|
|
1360
|
-
throw new SmithersError("INVALID_INPUT", "Gateway UI bundling requires Bun.build.");
|
|
1361
|
-
}
|
|
1362
|
-
const result = await Bun.build({
|
|
1363
|
-
entrypoints: [config.entry],
|
|
1364
|
-
root: process.cwd(),
|
|
1365
|
-
target: "browser",
|
|
1366
|
-
format: "esm",
|
|
1367
|
-
sourcemap: "inline",
|
|
1368
|
-
minify: false,
|
|
1369
|
-
jsx: {
|
|
1370
|
-
runtime: "automatic",
|
|
1371
|
-
importSource: "react",
|
|
1372
|
-
},
|
|
1373
|
-
});
|
|
1374
|
-
if (!result.success) {
|
|
1375
|
-
const message = result.logs?.map((entry) => entry.message).filter(Boolean).join("\n")
|
|
1376
|
-
|| `Failed to build Gateway UI entry ${config.entry}`;
|
|
1377
|
-
throw new SmithersError("INVALID_INPUT", message);
|
|
1378
|
-
}
|
|
1379
|
-
const output = result.outputs.find((entry) => entry.path.endsWith(".js")) ?? result.outputs[0];
|
|
1380
|
-
const body = await output.text();
|
|
1381
|
-
this.uiAssetCache.set(config.entry, body);
|
|
1382
|
-
return body;
|
|
1383
|
-
}
|
|
1384
|
-
/**
|
|
1385
1400
|
* @param {IncomingMessage} req
|
|
1386
1401
|
* @param {ServerResponse} res
|
|
1387
1402
|
*/
|
|
@@ -1390,6 +1405,21 @@ export class Gateway {
|
|
|
1390
1405
|
return false;
|
|
1391
1406
|
}
|
|
1392
1407
|
const host = headerValue(req, "host") ?? "127.0.0.1";
|
|
1408
|
+
const url = new URL(`http://${host}${req.url ?? "/"}`);
|
|
1409
|
+
const uiMatch = this.resolveUiMatch(url.pathname);
|
|
1410
|
+
if (!uiMatch) {
|
|
1411
|
+
return false;
|
|
1412
|
+
}
|
|
1413
|
+
const uiAuthFailure = await authorizeGatewayUiRequest({
|
|
1414
|
+
match: uiMatch,
|
|
1415
|
+
authMode: gatewayAuthMode(this.auth),
|
|
1416
|
+
token: bearerTokenFromHeaders(req),
|
|
1417
|
+
authenticate: (token) => this.authenticateRequest(req, token),
|
|
1418
|
+
});
|
|
1419
|
+
if (uiAuthFailure) {
|
|
1420
|
+
sendJson(res, statusForRpcError(uiAuthFailure.code), responseError(randomUUID(), uiAuthFailure.code, uiAuthFailure.message, uiAuthFailure.details));
|
|
1421
|
+
return true;
|
|
1422
|
+
}
|
|
1393
1423
|
const request = new Request(`http://${host}${req.url ?? "/"}`, {
|
|
1394
1424
|
method: "GET",
|
|
1395
1425
|
headers: nodeHeadersToFetchHeaders(req.headers),
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {{
|
|
3
|
+
* match: { config: { kind: string; config: Record<string, unknown> } };
|
|
4
|
+
* authMode: string;
|
|
5
|
+
* token: string | null;
|
|
6
|
+
* authenticate: (token: string | null) => Promise<{ ok: true } | { ok: false; code: string; message: string; details?: Record<string, unknown> }>;
|
|
7
|
+
* }} options
|
|
8
|
+
*/
|
|
9
|
+
export async function authorizeGatewayUiRequest(options) {
|
|
10
|
+
const isBuiltinOperator = options.match.config.config.builtin === "operator";
|
|
11
|
+
if (!isBuiltinOperator || options.authMode === "none") {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const authResult = await options.authenticate(options.token);
|
|
15
|
+
return authResult.ok === false ? authResult : null;
|
|
16
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { SmithersError } from "@smithers-orchestrator/errors/SmithersError";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {Record<string, unknown>} config
|
|
5
|
+
* @param {Map<string, string>} cache
|
|
6
|
+
*/
|
|
7
|
+
export async function bundleGatewayUiEntry(config, cache) {
|
|
8
|
+
const cached = cache.get(String(config.entry));
|
|
9
|
+
if (cached) {
|
|
10
|
+
return cached;
|
|
11
|
+
}
|
|
12
|
+
if (typeof Bun === "undefined" || typeof Bun.build !== "function") {
|
|
13
|
+
throw new SmithersError("INVALID_INPUT", "Gateway UI bundling requires Bun.build.");
|
|
14
|
+
}
|
|
15
|
+
const result = await Bun.build({
|
|
16
|
+
entrypoints: [String(config.entry)],
|
|
17
|
+
root: process.cwd(),
|
|
18
|
+
target: "browser",
|
|
19
|
+
format: "esm",
|
|
20
|
+
sourcemap: "inline",
|
|
21
|
+
minify: false,
|
|
22
|
+
jsx: {
|
|
23
|
+
runtime: "automatic",
|
|
24
|
+
importSource: "react",
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
if (!result.success) {
|
|
28
|
+
const message = result.logs?.map((entry) => entry.message).filter(Boolean).join("\n")
|
|
29
|
+
|| `Failed to build Gateway UI entry ${config.entry}`;
|
|
30
|
+
throw new SmithersError("INVALID_INPUT", message);
|
|
31
|
+
}
|
|
32
|
+
const output = result.outputs.find((entry) => entry.path.endsWith(".js")) ?? result.outputs[0];
|
|
33
|
+
const body = await output.text();
|
|
34
|
+
cache.set(String(config.entry), body);
|
|
35
|
+
return body;
|
|
36
|
+
}
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
export const DEFAULT_OPERATOR_UI_ENTRY = "smithers:default-operator-ui";
|
|
2
|
+
|
|
3
|
+
function defaultOperatorUiClient() {
|
|
4
|
+
const boot = globalThis.__SMITHERS_GATEWAY_UI__ ?? {};
|
|
5
|
+
const root = document.getElementById("root");
|
|
6
|
+
const storageKey = "smithers.gateway.console.token";
|
|
7
|
+
function readStoredToken() {
|
|
8
|
+
try {
|
|
9
|
+
return sessionStorage.getItem(storageKey) ?? "";
|
|
10
|
+
} catch {
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function writeStoredToken(value) {
|
|
15
|
+
try {
|
|
16
|
+
if (value) {
|
|
17
|
+
sessionStorage.setItem(storageKey, value);
|
|
18
|
+
} else {
|
|
19
|
+
sessionStorage.removeItem(storageKey);
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
// Some embedded browsers disable Web Storage; the in-memory token remains usable.
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const state = {
|
|
26
|
+
token: readStoredToken(),
|
|
27
|
+
health: null,
|
|
28
|
+
workflows: [],
|
|
29
|
+
runs: [],
|
|
30
|
+
approvals: [],
|
|
31
|
+
selectedWorkflow: "",
|
|
32
|
+
runInput: "{}",
|
|
33
|
+
status: "Loading",
|
|
34
|
+
error: "",
|
|
35
|
+
busy: false,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const css = `
|
|
39
|
+
:root {
|
|
40
|
+
color-scheme: light;
|
|
41
|
+
--ink: #161616;
|
|
42
|
+
--muted: #6f6a61;
|
|
43
|
+
--line: #ded8ce;
|
|
44
|
+
--surface: #f7f3ec;
|
|
45
|
+
--panel: #fffaf2;
|
|
46
|
+
--accent: #235c58;
|
|
47
|
+
--accent-strong: #17413e;
|
|
48
|
+
--danger: #9f2e24;
|
|
49
|
+
--ok: #2f6b3f;
|
|
50
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
51
|
+
}
|
|
52
|
+
* { box-sizing: border-box; }
|
|
53
|
+
body {
|
|
54
|
+
margin: 0;
|
|
55
|
+
background: var(--surface);
|
|
56
|
+
color: var(--ink);
|
|
57
|
+
}
|
|
58
|
+
button, input, textarea, select { font: inherit; }
|
|
59
|
+
button {
|
|
60
|
+
border: 1px solid var(--accent);
|
|
61
|
+
background: var(--accent);
|
|
62
|
+
color: white;
|
|
63
|
+
min-height: 34px;
|
|
64
|
+
padding: 0 12px;
|
|
65
|
+
border-radius: 6px;
|
|
66
|
+
cursor: pointer;
|
|
67
|
+
}
|
|
68
|
+
button.secondary {
|
|
69
|
+
background: transparent;
|
|
70
|
+
color: var(--accent);
|
|
71
|
+
}
|
|
72
|
+
button.danger {
|
|
73
|
+
background: transparent;
|
|
74
|
+
color: var(--danger);
|
|
75
|
+
border-color: var(--danger);
|
|
76
|
+
}
|
|
77
|
+
button:disabled { opacity: 0.55; cursor: not-allowed; }
|
|
78
|
+
.shell {
|
|
79
|
+
min-height: 100vh;
|
|
80
|
+
display: grid;
|
|
81
|
+
grid-template-columns: 240px minmax(0, 1fr) 360px;
|
|
82
|
+
}
|
|
83
|
+
.nav {
|
|
84
|
+
border-right: 1px solid var(--line);
|
|
85
|
+
padding: 22px 18px;
|
|
86
|
+
}
|
|
87
|
+
.brand {
|
|
88
|
+
font-size: 24px;
|
|
89
|
+
font-weight: 760;
|
|
90
|
+
letter-spacing: 0;
|
|
91
|
+
margin-bottom: 24px;
|
|
92
|
+
}
|
|
93
|
+
.nav-section {
|
|
94
|
+
display: grid;
|
|
95
|
+
gap: 10px;
|
|
96
|
+
margin-top: 24px;
|
|
97
|
+
}
|
|
98
|
+
.label {
|
|
99
|
+
color: var(--muted);
|
|
100
|
+
font-size: 12px;
|
|
101
|
+
text-transform: uppercase;
|
|
102
|
+
letter-spacing: 0.08em;
|
|
103
|
+
}
|
|
104
|
+
.token {
|
|
105
|
+
width: 100%;
|
|
106
|
+
border: 1px solid var(--line);
|
|
107
|
+
background: white;
|
|
108
|
+
border-radius: 6px;
|
|
109
|
+
min-height: 34px;
|
|
110
|
+
padding: 0 10px;
|
|
111
|
+
}
|
|
112
|
+
.main {
|
|
113
|
+
padding: 24px;
|
|
114
|
+
display: grid;
|
|
115
|
+
grid-template-rows: auto auto minmax(0, 1fr);
|
|
116
|
+
gap: 18px;
|
|
117
|
+
}
|
|
118
|
+
.topbar {
|
|
119
|
+
display: flex;
|
|
120
|
+
justify-content: space-between;
|
|
121
|
+
align-items: center;
|
|
122
|
+
gap: 16px;
|
|
123
|
+
}
|
|
124
|
+
.title {
|
|
125
|
+
font-size: 22px;
|
|
126
|
+
font-weight: 720;
|
|
127
|
+
}
|
|
128
|
+
.meta {
|
|
129
|
+
display: flex;
|
|
130
|
+
gap: 10px;
|
|
131
|
+
flex-wrap: wrap;
|
|
132
|
+
}
|
|
133
|
+
.pill {
|
|
134
|
+
min-height: 28px;
|
|
135
|
+
display: inline-flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
border: 1px solid var(--line);
|
|
138
|
+
border-radius: 999px;
|
|
139
|
+
padding: 0 10px;
|
|
140
|
+
color: var(--muted);
|
|
141
|
+
background: rgba(255,255,255,0.5);
|
|
142
|
+
font-size: 13px;
|
|
143
|
+
}
|
|
144
|
+
.pill.ok { color: var(--ok); border-color: rgba(47,107,63,0.35); }
|
|
145
|
+
.pill.warn { color: var(--danger); border-color: rgba(159,46,36,0.35); }
|
|
146
|
+
.launch {
|
|
147
|
+
border-top: 1px solid var(--line);
|
|
148
|
+
border-bottom: 1px solid var(--line);
|
|
149
|
+
padding: 16px 0;
|
|
150
|
+
display: grid;
|
|
151
|
+
grid-template-columns: minmax(180px, 260px) minmax(260px, 1fr) auto;
|
|
152
|
+
gap: 12px;
|
|
153
|
+
align-items: end;
|
|
154
|
+
}
|
|
155
|
+
.field {
|
|
156
|
+
display: grid;
|
|
157
|
+
gap: 6px;
|
|
158
|
+
}
|
|
159
|
+
.field select, .field textarea {
|
|
160
|
+
border: 1px solid var(--line);
|
|
161
|
+
background: white;
|
|
162
|
+
border-radius: 6px;
|
|
163
|
+
padding: 9px 10px;
|
|
164
|
+
}
|
|
165
|
+
.field textarea {
|
|
166
|
+
min-height: 72px;
|
|
167
|
+
resize: vertical;
|
|
168
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
169
|
+
font-size: 13px;
|
|
170
|
+
}
|
|
171
|
+
.runs {
|
|
172
|
+
overflow: auto;
|
|
173
|
+
display: grid;
|
|
174
|
+
align-content: start;
|
|
175
|
+
}
|
|
176
|
+
.run-row {
|
|
177
|
+
display: grid;
|
|
178
|
+
grid-template-columns: minmax(160px, 1.2fr) 120px 140px 1fr;
|
|
179
|
+
gap: 12px;
|
|
180
|
+
padding: 14px 0;
|
|
181
|
+
border-bottom: 1px solid var(--line);
|
|
182
|
+
}
|
|
183
|
+
.run-id {
|
|
184
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
185
|
+
font-size: 13px;
|
|
186
|
+
}
|
|
187
|
+
.muted { color: var(--muted); }
|
|
188
|
+
.side {
|
|
189
|
+
border-left: 1px solid var(--line);
|
|
190
|
+
background: var(--panel);
|
|
191
|
+
padding: 22px 18px;
|
|
192
|
+
display: grid;
|
|
193
|
+
align-content: start;
|
|
194
|
+
gap: 18px;
|
|
195
|
+
}
|
|
196
|
+
.side h2 {
|
|
197
|
+
font-size: 15px;
|
|
198
|
+
margin: 0;
|
|
199
|
+
}
|
|
200
|
+
.approval {
|
|
201
|
+
border-top: 1px solid var(--line);
|
|
202
|
+
padding-top: 14px;
|
|
203
|
+
display: grid;
|
|
204
|
+
gap: 10px;
|
|
205
|
+
}
|
|
206
|
+
.approval-title {
|
|
207
|
+
font-weight: 680;
|
|
208
|
+
}
|
|
209
|
+
.approval-actions {
|
|
210
|
+
display: flex;
|
|
211
|
+
gap: 8px;
|
|
212
|
+
}
|
|
213
|
+
.empty {
|
|
214
|
+
color: var(--muted);
|
|
215
|
+
padding: 18px 0;
|
|
216
|
+
}
|
|
217
|
+
.error {
|
|
218
|
+
color: var(--danger);
|
|
219
|
+
min-height: 20px;
|
|
220
|
+
}
|
|
221
|
+
@media (max-width: 960px) {
|
|
222
|
+
.shell { grid-template-columns: 1fr; }
|
|
223
|
+
.nav, .side { border: 0; border-bottom: 1px solid var(--line); }
|
|
224
|
+
.main { padding: 18px; }
|
|
225
|
+
.launch { grid-template-columns: 1fr; }
|
|
226
|
+
.run-row { grid-template-columns: 1fr; }
|
|
227
|
+
}
|
|
228
|
+
`;
|
|
229
|
+
|
|
230
|
+
function installStyles() {
|
|
231
|
+
const style = document.createElement("style");
|
|
232
|
+
style.textContent = css;
|
|
233
|
+
document.head.appendChild(style);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function setToken(value) {
|
|
237
|
+
state.token = value;
|
|
238
|
+
writeStoredToken(value);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function rpc(method, params = {}) {
|
|
242
|
+
const headers = new Headers({ "content-type": "application/json" });
|
|
243
|
+
if (state.token) {
|
|
244
|
+
headers.set("authorization", "Bearer " + state.token);
|
|
245
|
+
}
|
|
246
|
+
const response = await fetch((boot.rpcPath ?? "/v1/rpc") + "/" + method, {
|
|
247
|
+
method: "POST",
|
|
248
|
+
headers,
|
|
249
|
+
body: JSON.stringify(params),
|
|
250
|
+
});
|
|
251
|
+
const frame = await response.json().catch(() => null);
|
|
252
|
+
if (!response.ok || !frame?.ok) {
|
|
253
|
+
throw new Error(frame?.error?.message ?? "Gateway request failed");
|
|
254
|
+
}
|
|
255
|
+
return frame.payload;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function statusClass(status) {
|
|
259
|
+
if (status === "finished") return "ok";
|
|
260
|
+
if (status === "failed" || status === "cancelled") return "warn";
|
|
261
|
+
return "";
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function formatAge(ms) {
|
|
265
|
+
if (!ms) return "unknown";
|
|
266
|
+
const seconds = Math.max(0, Math.floor((Date.now() - ms) / 1000));
|
|
267
|
+
if (seconds < 60) return seconds + "s ago";
|
|
268
|
+
const minutes = Math.floor(seconds / 60);
|
|
269
|
+
if (minutes < 60) return minutes + "m ago";
|
|
270
|
+
return Math.floor(minutes / 60) + "h ago";
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function escapeText(value) {
|
|
274
|
+
return String(value ?? "").replace(/[&<>"']/g, (char) => ({
|
|
275
|
+
"&": "&",
|
|
276
|
+
"<": "<",
|
|
277
|
+
">": ">",
|
|
278
|
+
'"': """,
|
|
279
|
+
"'": "'",
|
|
280
|
+
})[char]);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function renderRuns() {
|
|
284
|
+
if (state.runs.length === 0) {
|
|
285
|
+
return '<div class="empty">No runs found.</div>';
|
|
286
|
+
}
|
|
287
|
+
return state.runs.map((run) => `
|
|
288
|
+
<div class="run-row">
|
|
289
|
+
<div>
|
|
290
|
+
<div class="run-id">${escapeText(run.runId)}</div>
|
|
291
|
+
<div class="muted">${escapeText(run.workflowKey ?? run.workflowName ?? "workflow")}</div>
|
|
292
|
+
</div>
|
|
293
|
+
<div><span class="pill ${statusClass(run.status)}">${escapeText(run.status)}</span></div>
|
|
294
|
+
<div class="muted">${escapeText(formatAge(run.createdAtMs))}</div>
|
|
295
|
+
<div class="muted">${escapeText(run.triggeredBy ?? run.auth?.triggeredBy ?? "")}</div>
|
|
296
|
+
</div>
|
|
297
|
+
`).join("");
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function renderApprovals() {
|
|
301
|
+
if (state.approvals.length === 0) {
|
|
302
|
+
return '<div class="empty">No pending approvals.</div>';
|
|
303
|
+
}
|
|
304
|
+
return state.approvals.map((approval, index) => `
|
|
305
|
+
<section class="approval">
|
|
306
|
+
<div>
|
|
307
|
+
<div class="approval-title">${escapeText(approval.requestTitle ?? approval.nodeId)}</div>
|
|
308
|
+
<div class="muted">${escapeText(approval.workflowKey)} / ${escapeText(approval.runId)}</div>
|
|
309
|
+
</div>
|
|
310
|
+
<div class="approval-actions">
|
|
311
|
+
<button data-approve="${index}">Approve</button>
|
|
312
|
+
<button class="danger" data-deny="${index}">Deny</button>
|
|
313
|
+
</div>
|
|
314
|
+
</section>
|
|
315
|
+
`).join("");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function renderWorkflows() {
|
|
319
|
+
const options = state.workflows.map((workflow) => {
|
|
320
|
+
const selected = workflow.key === state.selectedWorkflow ? " selected" : "";
|
|
321
|
+
return `<option value="${escapeText(workflow.key)}"${selected}>${escapeText(workflow.readableName ?? workflow.key)}</option>`;
|
|
322
|
+
}).join("");
|
|
323
|
+
return `<select id="workflow">${options || '<option value="">No workflows</option>'}</select>`;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function render() {
|
|
327
|
+
const activeRuns = state.runs.filter((run) => ["running", "waiting-approval", "waiting-event", "waiting-timer"].includes(run.status)).length;
|
|
328
|
+
root.innerHTML = `
|
|
329
|
+
<div class="shell">
|
|
330
|
+
<aside class="nav">
|
|
331
|
+
<div class="brand">Smithers Console</div>
|
|
332
|
+
<div class="nav-section">
|
|
333
|
+
<div class="label">Gateway</div>
|
|
334
|
+
<span class="pill ${state.health ? "ok" : "warn"}">${state.health ? "online" : "checking"}</span>
|
|
335
|
+
<span class="pill">${escapeText(state.health?.features?.join(", ") ?? "features pending")}</span>
|
|
336
|
+
</div>
|
|
337
|
+
<div class="nav-section">
|
|
338
|
+
<label class="label" for="token">Bearer token</label>
|
|
339
|
+
<input class="token" id="token" value="${escapeText(state.token)}" type="password" autocomplete="off">
|
|
340
|
+
</div>
|
|
341
|
+
</aside>
|
|
342
|
+
<main class="main">
|
|
343
|
+
<div class="topbar">
|
|
344
|
+
<div>
|
|
345
|
+
<div class="title">Operations</div>
|
|
346
|
+
<div class="muted">${escapeText(state.status)}</div>
|
|
347
|
+
</div>
|
|
348
|
+
<div class="meta">
|
|
349
|
+
<span class="pill">${state.workflows.length} workflows</span>
|
|
350
|
+
<span class="pill">${activeRuns} active</span>
|
|
351
|
+
<span class="pill">${state.approvals.length} approvals</span>
|
|
352
|
+
<button class="secondary" id="refresh">Refresh</button>
|
|
353
|
+
</div>
|
|
354
|
+
</div>
|
|
355
|
+
<form class="launch" id="launch">
|
|
356
|
+
<label class="field">
|
|
357
|
+
<span class="label">Workflow</span>
|
|
358
|
+
${renderWorkflows()}
|
|
359
|
+
</label>
|
|
360
|
+
<label class="field">
|
|
361
|
+
<span class="label">Input JSON</span>
|
|
362
|
+
<textarea id="run-input" spellcheck="false">${escapeText(state.runInput)}</textarea>
|
|
363
|
+
</label>
|
|
364
|
+
<button type="submit" ${state.busy ? "disabled" : ""}>Launch</button>
|
|
365
|
+
</form>
|
|
366
|
+
<section class="runs">${renderRuns()}</section>
|
|
367
|
+
</main>
|
|
368
|
+
<aside class="side">
|
|
369
|
+
<div>
|
|
370
|
+
<h2>Pending Approvals</h2>
|
|
371
|
+
<div class="error">${escapeText(state.error)}</div>
|
|
372
|
+
</div>
|
|
373
|
+
${renderApprovals()}
|
|
374
|
+
</aside>
|
|
375
|
+
</div>
|
|
376
|
+
`;
|
|
377
|
+
bind();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function bind() {
|
|
381
|
+
document.getElementById("token")?.addEventListener("input", (event) => {
|
|
382
|
+
setToken(event.target.value);
|
|
383
|
+
});
|
|
384
|
+
document.getElementById("workflow")?.addEventListener("change", (event) => {
|
|
385
|
+
state.selectedWorkflow = event.target.value;
|
|
386
|
+
});
|
|
387
|
+
document.getElementById("run-input")?.addEventListener("input", (event) => {
|
|
388
|
+
state.runInput = event.target.value;
|
|
389
|
+
});
|
|
390
|
+
document.getElementById("refresh")?.addEventListener("click", () => refresh());
|
|
391
|
+
document.getElementById("launch")?.addEventListener("submit", async (event) => {
|
|
392
|
+
event.preventDefault();
|
|
393
|
+
await launchRun();
|
|
394
|
+
});
|
|
395
|
+
for (const button of document.querySelectorAll("[data-approve]")) {
|
|
396
|
+
button.addEventListener("click", () => decideApproval(Number(button.dataset.approve), true));
|
|
397
|
+
}
|
|
398
|
+
for (const button of document.querySelectorAll("[data-deny]")) {
|
|
399
|
+
button.addEventListener("click", () => decideApproval(Number(button.dataset.deny), false));
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async function refresh() {
|
|
404
|
+
state.error = "";
|
|
405
|
+
try {
|
|
406
|
+
const [health, workflows, runs, approvals] = await Promise.all([
|
|
407
|
+
rpc("health"),
|
|
408
|
+
rpc("listWorkflows", { limit: 100 }),
|
|
409
|
+
rpc("listRuns", { limit: 100 }),
|
|
410
|
+
rpc("listApprovals", { limit: 50 }),
|
|
411
|
+
]);
|
|
412
|
+
state.health = health;
|
|
413
|
+
state.workflows = workflows;
|
|
414
|
+
state.runs = runs;
|
|
415
|
+
state.approvals = approvals;
|
|
416
|
+
if (!state.selectedWorkflow && workflows[0]) {
|
|
417
|
+
state.selectedWorkflow = workflows[0].key;
|
|
418
|
+
}
|
|
419
|
+
state.status = "Updated " + new Date().toLocaleTimeString();
|
|
420
|
+
} catch (error) {
|
|
421
|
+
state.error = error instanceof Error ? error.message : String(error);
|
|
422
|
+
state.status = "Refresh failed";
|
|
423
|
+
}
|
|
424
|
+
render();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async function launchRun() {
|
|
428
|
+
state.busy = true;
|
|
429
|
+
state.error = "";
|
|
430
|
+
render();
|
|
431
|
+
try {
|
|
432
|
+
const input = JSON.parse(state.runInput || "{}");
|
|
433
|
+
await rpc("launchRun", { workflow: state.selectedWorkflow, input });
|
|
434
|
+
state.status = "Run launched";
|
|
435
|
+
await refresh();
|
|
436
|
+
} catch (error) {
|
|
437
|
+
state.error = error instanceof Error ? error.message : String(error);
|
|
438
|
+
state.busy = false;
|
|
439
|
+
render();
|
|
440
|
+
}
|
|
441
|
+
state.busy = false;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async function decideApproval(index, approved) {
|
|
445
|
+
const approval = state.approvals[index];
|
|
446
|
+
if (!approval) return;
|
|
447
|
+
state.error = "";
|
|
448
|
+
try {
|
|
449
|
+
await rpc("submitApproval", {
|
|
450
|
+
runId: approval.runId,
|
|
451
|
+
nodeId: approval.nodeId,
|
|
452
|
+
iteration: approval.iteration ?? 0,
|
|
453
|
+
approved,
|
|
454
|
+
decision: { approved },
|
|
455
|
+
});
|
|
456
|
+
await refresh();
|
|
457
|
+
} catch (error) {
|
|
458
|
+
state.error = error instanceof Error ? error.message : String(error);
|
|
459
|
+
render();
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
installStyles();
|
|
464
|
+
render();
|
|
465
|
+
refresh();
|
|
466
|
+
setInterval(refresh, 5000);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export const DEFAULT_OPERATOR_UI_CLIENT_JS = `(${defaultOperatorUiClient.toString()})();\n`;
|
package/src/index.d.ts
CHANGED
|
@@ -119,10 +119,27 @@ type GatewayDefaults$1 = {
|
|
|
119
119
|
cliAgentTools?: "all" | "explicit-only";
|
|
120
120
|
};
|
|
121
121
|
|
|
122
|
-
type
|
|
122
|
+
type GatewayOperatorUiConfig$1 = {
|
|
123
|
+
/**
|
|
124
|
+
* URL path for the built-in operator console.
|
|
125
|
+
* @default "/console"
|
|
126
|
+
*/
|
|
127
|
+
path?: string;
|
|
128
|
+
/**
|
|
129
|
+
* Document title for the generated HTML shell.
|
|
130
|
+
*/
|
|
131
|
+
title?: string;
|
|
132
|
+
/**
|
|
133
|
+
* JSON-serializable boot data exposed to the browser.
|
|
134
|
+
*/
|
|
135
|
+
props?: Record<string, unknown>;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
type GatewayUiConfig$1 = true | {
|
|
123
139
|
/**
|
|
124
140
|
* Browser entry module for the React app. Smithers bundles this with Bun and
|
|
125
|
-
* serves it from the Gateway origin.
|
|
141
|
+
* serves it from the Gateway origin. Pass `true` to mount the built-in
|
|
142
|
+
* operator console.
|
|
126
143
|
*/
|
|
127
144
|
entry: string;
|
|
128
145
|
/**
|
|
@@ -146,6 +163,11 @@ type GatewayOptions$1 = {
|
|
|
146
163
|
heartbeatMs?: number;
|
|
147
164
|
auth?: GatewayAuthConfig$1;
|
|
148
165
|
ui?: GatewayUiConfig$1;
|
|
166
|
+
/**
|
|
167
|
+
* Built-in browser console for operators. Set to false to disable it.
|
|
168
|
+
* @default { path: "/console" }
|
|
169
|
+
*/
|
|
170
|
+
operatorUi?: GatewayOperatorUiConfig$1 | false;
|
|
149
171
|
defaults?: GatewayDefaults$1;
|
|
150
172
|
maxBodyBytes?: number;
|
|
151
173
|
maxPayload?: number;
|
|
@@ -267,6 +289,7 @@ declare class Gateway {
|
|
|
267
289
|
requestTimeout: number;
|
|
268
290
|
auth: GatewayAuthConfig$1 | undefined;
|
|
269
291
|
ui: ResolvedGatewayUiConfig | null;
|
|
292
|
+
operatorUi: ResolvedGatewayUiConfig | null;
|
|
270
293
|
uiApp: hono.Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
|
|
271
294
|
defaults: GatewayDefaults$1 | undefined;
|
|
272
295
|
workflows: Map<any, any>;
|
|
@@ -309,7 +332,7 @@ declare class Gateway {
|
|
|
309
332
|
*/
|
|
310
333
|
uiBootConfig(mount: GatewayUiMount): {
|
|
311
334
|
apiVersion: "v1";
|
|
312
|
-
kind: "workflow" | "gateway";
|
|
335
|
+
kind: "workflow" | "gateway" | "operator";
|
|
313
336
|
workflowKey: string | null;
|
|
314
337
|
mountPath: string;
|
|
315
338
|
rpcPath: string;
|
|
@@ -334,11 +357,6 @@ declare class Gateway {
|
|
|
334
357
|
contentType: string;
|
|
335
358
|
} | null>;
|
|
336
359
|
/**
|
|
337
|
-
* @param {ResolvedGatewayUiConfig} config
|
|
338
|
-
* @returns {Promise<string>}
|
|
339
|
-
*/
|
|
340
|
-
bundleUiEntry(config: ResolvedGatewayUiConfig): Promise<string>;
|
|
341
|
-
/**
|
|
342
360
|
* @param {IncomingMessage} req
|
|
343
361
|
* @param {ServerResponse} res
|
|
344
362
|
*/
|
|
@@ -727,6 +745,7 @@ type GatewayWebhookRunConfig = GatewayWebhookRunConfig$1;
|
|
|
727
745
|
type GatewayWebhookSignalConfig = GatewayWebhookSignalConfig$1;
|
|
728
746
|
type ConnectRequest = ConnectRequest$1;
|
|
729
747
|
type GatewayAuthConfig = GatewayAuthConfig$1;
|
|
748
|
+
type GatewayOperatorUiConfig = GatewayOperatorUiConfig$1;
|
|
730
749
|
type GatewayOptions = GatewayOptions$1;
|
|
731
750
|
type GatewayWebhookConfig = GatewayWebhookConfig$1;
|
|
732
751
|
type IncomingMessage = node_http.IncomingMessage;
|
|
@@ -783,9 +802,10 @@ type ResolvedGatewayUiConfig = {
|
|
|
783
802
|
path: string;
|
|
784
803
|
title?: string;
|
|
785
804
|
props?: Record<string, unknown>;
|
|
805
|
+
builtin?: "operator";
|
|
786
806
|
};
|
|
787
807
|
type GatewayUiMount = {
|
|
788
|
-
kind: "gateway" | "workflow";
|
|
808
|
+
kind: "gateway" | "workflow" | "operator";
|
|
789
809
|
workflowKey: string | null;
|
|
790
810
|
config: ResolvedGatewayUiConfig;
|
|
791
811
|
};
|
|
@@ -1175,4 +1195,4 @@ type RunRow = _smithers_orchestrator_db_adapter_RunRow.RunRow;
|
|
|
1175
1195
|
type ServerResponse = node_http.ServerResponse;
|
|
1176
1196
|
type ServerOptions = ServerOptions$1;
|
|
1177
1197
|
|
|
1178
|
-
export { type AttemptRow, type ConnectRequest, type ConnectionState, DEVTOOLS_BACKPRESSURE_LIMIT, DEVTOOLS_EMPTY_ROOT_ID, DEVTOOLS_MAX_FRAME_NO, DEVTOOLS_POLL_INTERVAL_MS, DEVTOOLS_REBASELINE_INTERVAL, DEVTOOLS_RUN_ID_PATTERN, DEVTOOLS_TREE_MAX_DEPTH, type DevToolsEvent, type DevToolsNode, type DevToolsNodeType, DevToolsRouteError, type DiffSummary, type EventFrame, GATEWAY_FRAME_ID_MAX_LENGTH, GATEWAY_METHOD_NAME_MAX_LENGTH, GATEWAY_RPC_INPUT_MAX_BYTES, GATEWAY_RPC_INPUT_MAX_DEPTH, GATEWAY_RPC_MAX_ARRAY_LENGTH, GATEWAY_RPC_MAX_DEPTH, GATEWAY_RPC_MAX_PAYLOAD_BYTES, GATEWAY_RPC_MAX_STRING_LENGTH, Gateway, type GatewayAuthConfig, type GatewayDefaults, type GatewayMetricLabels, type GatewayOptions, type GatewayRegisterOptions, type GatewayRequestContext, type GatewayTokenGrant, type GatewayTransport, type GatewayUiConfig, type GatewayUiMount, type GatewayWebhookConfig, type GatewayWebhookRunConfig, type GatewayWebhookSignalConfig, type GetNodeDiffRouteResult, type HelloResponse, ITERATION_MAX, type IncomingMessage, type JumpResult, NODE_ID_PATTERN, NODE_OUTPUT_MAX_BYTES, NODE_OUTPUT_WARN_BYTES, type NodeOutputErrorCode, type NodeOutputResponse, NodeOutputRouteError, RUN_ID_PATTERN, type RegisteredWorkflow, type RequestFrame, type ResolvedGatewayUiConfig, type ResolvedRun, type ResponseFrame, type RunRow, type RunStartAuthContext, type ServeOptions, type ServerOptions, type ServerResponse, type SmithersWorkflow, assertGatewayInputDepthWithinBounds, createServeApp, emptyDevToolsRoot, getDevToolsSnapshotRoute, getGatewayInputDepth, getNodeDiffRoute, getNodeOutputRoute, jumpToFrameRoute, parseGatewayRequestFrame, parseXmlToDevToolsRoot, runFork, runPromise, runSync, snapshotFromFrameRow, startServer, startServerEffect, statusForRpcError, streamDevToolsRoute, summarizeBundle, validateFrameNoInput, validateFromSeqInput, validateGatewayMethodName, validateRequestedFrameNo, validateRunId };
|
|
1198
|
+
export { type AttemptRow, type ConnectRequest, type ConnectionState, DEVTOOLS_BACKPRESSURE_LIMIT, DEVTOOLS_EMPTY_ROOT_ID, DEVTOOLS_MAX_FRAME_NO, DEVTOOLS_POLL_INTERVAL_MS, DEVTOOLS_REBASELINE_INTERVAL, DEVTOOLS_RUN_ID_PATTERN, DEVTOOLS_TREE_MAX_DEPTH, type DevToolsEvent, type DevToolsNode, type DevToolsNodeType, DevToolsRouteError, type DiffSummary, type EventFrame, GATEWAY_FRAME_ID_MAX_LENGTH, GATEWAY_METHOD_NAME_MAX_LENGTH, GATEWAY_RPC_INPUT_MAX_BYTES, GATEWAY_RPC_INPUT_MAX_DEPTH, GATEWAY_RPC_MAX_ARRAY_LENGTH, GATEWAY_RPC_MAX_DEPTH, GATEWAY_RPC_MAX_PAYLOAD_BYTES, GATEWAY_RPC_MAX_STRING_LENGTH, Gateway, type GatewayAuthConfig, type GatewayDefaults, type GatewayMetricLabels, type GatewayOperatorUiConfig, type GatewayOptions, type GatewayRegisterOptions, type GatewayRequestContext, type GatewayTokenGrant, type GatewayTransport, type GatewayUiConfig, type GatewayUiMount, type GatewayWebhookConfig, type GatewayWebhookRunConfig, type GatewayWebhookSignalConfig, type GetNodeDiffRouteResult, type HelloResponse, ITERATION_MAX, type IncomingMessage, type JumpResult, NODE_ID_PATTERN, NODE_OUTPUT_MAX_BYTES, NODE_OUTPUT_WARN_BYTES, type NodeOutputErrorCode, type NodeOutputResponse, NodeOutputRouteError, RUN_ID_PATTERN, type RegisteredWorkflow, type RequestFrame, type ResolvedGatewayUiConfig, type ResolvedRun, type ResponseFrame, type RunRow, type RunStartAuthContext, type ServeOptions, type ServerOptions, type ServerResponse, type SmithersWorkflow, assertGatewayInputDepthWithinBounds, createServeApp, emptyDevToolsRoot, getDevToolsSnapshotRoute, getGatewayInputDepth, getNodeDiffRoute, getNodeOutputRoute, jumpToFrameRoute, parseGatewayRequestFrame, parseXmlToDevToolsRoot, runFork, runPromise, runSync, snapshotFromFrameRow, startServer, startServerEffect, statusForRpcError, streamDevToolsRoute, summarizeBundle, validateFrameNoInput, validateFromSeqInput, validateGatewayMethodName, validateRequestedFrameNo, validateRunId };
|