@smithers-orchestrator/server 0.17.0 → 0.19.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smithers-orchestrator/server",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
4
4
  "description": "HTTP, WebSocket, gateway, cron, webhook, and metrics servers for Smithers",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -26,28 +26,28 @@
26
26
  "effect": "^3.21.1",
27
27
  "hono": "^4.12.14",
28
28
  "ws": "^8.20.0",
29
- "@smithers-orchestrator/db": "0.17.0",
30
- "@smithers-orchestrator/driver": "0.17.0",
31
- "@smithers-orchestrator/devtools": "0.17.0",
32
- "@smithers-orchestrator/errors": "0.17.0",
33
- "@smithers-orchestrator/components": "0.17.0",
34
- "@smithers-orchestrator/engine": "0.17.0",
35
- "@smithers-orchestrator/gateway": "0.17.0",
36
- "@smithers-orchestrator/observability": "0.17.0",
37
- "@smithers-orchestrator/protocol": "0.17.0",
38
- "@smithers-orchestrator/scheduler": "0.17.0",
39
- "@smithers-orchestrator/time-travel": "0.17.0"
29
+ "@smithers-orchestrator/db": "0.19.0",
30
+ "@smithers-orchestrator/components": "0.19.0",
31
+ "@smithers-orchestrator/driver": "0.19.0",
32
+ "@smithers-orchestrator/devtools": "0.19.0",
33
+ "@smithers-orchestrator/engine": "0.19.0",
34
+ "@smithers-orchestrator/errors": "0.19.0",
35
+ "@smithers-orchestrator/observability": "0.19.0",
36
+ "@smithers-orchestrator/gateway": "0.19.0",
37
+ "@smithers-orchestrator/protocol": "0.19.0",
38
+ "@smithers-orchestrator/scheduler": "0.19.0",
39
+ "@smithers-orchestrator/time-travel": "0.19.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.17.0"
46
+ "@smithers-orchestrator/graph": "0.19.0"
47
47
  },
48
48
  "scripts": {
49
49
  "test": "bun test tests",
50
50
  "typecheck": "tsc -p tsconfig.json --noEmit",
51
- "build": "tsup --dts-only"
51
+ "build": "rm -f src/index.d.ts && node ../../node_modules/tsup/dist/cli-default.js --dts-only"
52
52
  }
53
53
  }
@@ -1,11 +1,13 @@
1
1
  import type { GatewayAuthConfig } from "./GatewayAuthConfig.js";
2
2
  import type { GatewayDefaults } from "./GatewayDefaults.js";
3
+ import type { GatewayUiConfig } from "./GatewayUiConfig.js";
3
4
 
4
5
  export type GatewayOptions = {
5
6
  protocol?: number;
6
7
  features?: string[];
7
8
  heartbeatMs?: number;
8
9
  auth?: GatewayAuthConfig;
10
+ ui?: GatewayUiConfig;
9
11
  defaults?: GatewayDefaults;
10
12
  maxBodyBytes?: number;
11
13
  maxPayload?: number;
@@ -0,0 +1,8 @@
1
+ import type { GatewayWebhookConfig } from "./GatewayWebhookConfig.js";
2
+ import type { GatewayUiConfig } from "./GatewayUiConfig.js";
3
+
4
+ export type GatewayRegisterOptions = {
5
+ schedule?: string;
6
+ webhook?: GatewayWebhookConfig;
7
+ ui?: GatewayUiConfig;
8
+ };
@@ -0,0 +1,20 @@
1
+ export type GatewayUiConfig = {
2
+ /**
3
+ * Browser entry module for the React app. Smithers bundles this with Bun and
4
+ * serves it from the Gateway origin.
5
+ */
6
+ entry: string;
7
+ /**
8
+ * URL path where the UI is mounted. Gateway-level UI defaults to `/`;
9
+ * workflow-level UI defaults to `/workflows/<workflowKey>`.
10
+ */
11
+ path?: string;
12
+ /**
13
+ * Document title for the generated HTML shell.
14
+ */
15
+ title?: string;
16
+ /**
17
+ * JSON-serializable boot data exposed to the browser.
18
+ */
19
+ props?: Record<string, unknown>;
20
+ };
package/src/gateway.js CHANGED
@@ -1,12 +1,15 @@
1
1
  // @smithers-type-exports-begin
2
2
  /** @typedef {import("./EventFrame.js").EventFrame} EventFrame */
3
3
  /** @typedef {import("./GatewayDefaults.js").GatewayDefaults} GatewayDefaults */
4
+ /** @typedef {import("./GatewayRegisterOptions.js").GatewayRegisterOptions} GatewayRegisterOptions */
4
5
  /** @typedef {import("./GatewayTokenGrant.js").GatewayTokenGrant} GatewayTokenGrant */
6
+ /** @typedef {import("./GatewayUiConfig.js").GatewayUiConfig} GatewayUiConfig */
5
7
  /** @typedef {import("./HelloResponse.js").HelloResponse} HelloResponse */
6
8
  // @smithers-type-exports-end
7
9
 
8
10
  import { createServer } from "node:http";
9
11
  import { createHash, createHmac, randomUUID, timingSafeEqual } from "node:crypto";
12
+ import { resolve } from "node:path";
10
13
  import { CronExpressionParser } from "cron-parser";
11
14
  import { Effect, Metric } from "effect";
12
15
  import { WebSocketServer } from "ws";
@@ -36,6 +39,7 @@ import { writeRewindAuditRow } from "@smithers-orchestrator/time-travel/writeRew
36
39
  import { recoverInProgressRewindAudits } from "@smithers-orchestrator/time-travel/recoverInProgressRewindAudits";
37
40
  import { GATEWAY_EVENT_WINDOW_DEFAULT, SMITHERS_API_VERSION, getRequiredScopeForGatewayMethod, } from "@smithers-orchestrator/gateway/rpc";
38
41
  import { hasGatewayScope } from "@smithers-orchestrator/gateway/auth/scopes";
42
+ import { createGatewayUiApp } from "./gatewayUi/createGatewayUiApp.js";
39
43
  /** @typedef {import("./GatewayWebhookRunConfig.js").GatewayWebhookRunConfig} GatewayWebhookRunConfig */
40
44
  /** @typedef {import("./GatewayWebhookSignalConfig.js").GatewayWebhookSignalConfig} GatewayWebhookSignalConfig */
41
45
  /** @typedef {import("./ConnectRequest.js").ConnectRequest} ConnectRequest */
@@ -90,6 +94,7 @@ import { hasGatewayScope } from "@smithers-orchestrator/gateway/auth/scopes";
90
94
  * key: string;
91
95
  * schedule?: string;
92
96
  * webhook?: GatewayWebhookConfig;
97
+ * ui?: ResolvedGatewayUiConfig | null;
93
98
  * }} RegisteredWorkflow
94
99
  */
95
100
  /**
@@ -100,6 +105,21 @@ import { hasGatewayScope } from "@smithers-orchestrator/gateway/auth/scopes";
100
105
  * adapter: SmithersDb;
101
106
  * }} ResolvedRun
102
107
  */
108
+ /**
109
+ * @typedef {{
110
+ * entry: string;
111
+ * path: string;
112
+ * title?: string;
113
+ * props?: Record<string, unknown>;
114
+ * }} ResolvedGatewayUiConfig
115
+ */
116
+ /**
117
+ * @typedef {{
118
+ * kind: "gateway" | "workflow";
119
+ * workflowKey: string | null;
120
+ * config: ResolvedGatewayUiConfig;
121
+ * }} GatewayUiMount
122
+ */
103
123
 
104
124
  const DEFAULT_PROTOCOL = 1;
105
125
  const DEFAULT_HEARTBEAT_MS = 15_000;
@@ -117,6 +137,115 @@ export const GATEWAY_FRAME_ID_MAX_LENGTH = 128;
117
137
  export const GATEWAY_RPC_INPUT_MAX_BYTES = GATEWAY_RPC_MAX_PAYLOAD_BYTES;
118
138
  export const GATEWAY_RPC_INPUT_MAX_DEPTH = GATEWAY_RPC_MAX_DEPTH;
119
139
  const GATEWAY_METHOD_NAME_PATTERN = /^[a-z][a-zA-Z0-9]*(?:\.[a-z][a-zA-Z0-9]*)*$/;
140
+ const GATEWAY_UI_ASSET_PREFIX = "__smithers_ui";
141
+
142
+ /**
143
+ * @param {string} value
144
+ * @returns {string}
145
+ */
146
+ function escapeHtml(value) {
147
+ return value
148
+ .replaceAll("&", "&amp;")
149
+ .replaceAll("<", "&lt;")
150
+ .replaceAll(">", "&gt;")
151
+ .replaceAll('"', "&quot;");
152
+ }
153
+
154
+ /**
155
+ * @param {unknown} value
156
+ * @returns {string}
157
+ */
158
+ function safeJsonScript(value) {
159
+ return JSON.stringify(value).replaceAll("<", "\\u003c");
160
+ }
161
+
162
+ /**
163
+ * @param {string | undefined} rawPath
164
+ * @param {string} fallbackPath
165
+ * @returns {string}
166
+ */
167
+ function normalizeUiMountPath(rawPath, fallbackPath) {
168
+ const candidate = (rawPath && rawPath.trim()) || fallbackPath;
169
+ const withSlash = candidate.startsWith("/") ? candidate : `/${candidate}`;
170
+ const withoutTrailing = withSlash.length > 1 ? withSlash.replace(/\/+$/, "") : withSlash;
171
+ if (!/^\/[A-Za-z0-9/_:.-]*$/.test(withoutTrailing)) {
172
+ throw new SmithersError("INVALID_INPUT", `Gateway UI path is invalid: ${candidate}`);
173
+ }
174
+ return withoutTrailing;
175
+ }
176
+
177
+ /**
178
+ * @param {string} mountPath
179
+ * @param {string} suffix
180
+ * @returns {string}
181
+ */
182
+ function joinUiPath(mountPath, suffix) {
183
+ if (mountPath === "/") {
184
+ return `/${suffix.replace(/^\/+/, "")}`;
185
+ }
186
+ return `${mountPath}/${suffix.replace(/^\/+/, "")}`;
187
+ }
188
+
189
+ /**
190
+ * @param {GatewayUiConfig | undefined} ui
191
+ * @param {string} fallbackPath
192
+ * @returns {ResolvedGatewayUiConfig | null}
193
+ */
194
+ function resolveGatewayUiConfig(ui, fallbackPath) {
195
+ if (!ui) {
196
+ return null;
197
+ }
198
+ if (typeof ui.entry !== "string" || !ui.entry.trim()) {
199
+ throw new SmithersError("INVALID_INPUT", "Gateway UI config requires a non-empty entry path.");
200
+ }
201
+ return {
202
+ entry: resolve(process.cwd(), ui.entry),
203
+ path: normalizeUiMountPath(ui.path, fallbackPath),
204
+ ...(typeof ui.title === "string" ? { title: ui.title } : {}),
205
+ ...(ui.props && typeof ui.props === "object" && !Array.isArray(ui.props)
206
+ ? { props: ui.props }
207
+ : {}),
208
+ };
209
+ }
210
+
211
+ /**
212
+ * @param {import("node:http").IncomingHttpHeaders} headers
213
+ * @returns {Headers}
214
+ */
215
+ function nodeHeadersToFetchHeaders(headers) {
216
+ const out = new Headers();
217
+ for (const [key, value] of Object.entries(headers)) {
218
+ if (value === undefined) {
219
+ continue;
220
+ }
221
+ if (Array.isArray(value)) {
222
+ for (const entry of value) {
223
+ out.append(key, entry);
224
+ }
225
+ continue;
226
+ }
227
+ out.set(key, value);
228
+ }
229
+ return out;
230
+ }
231
+
232
+ /**
233
+ * @param {ServerResponse} res
234
+ * @param {Response} response
235
+ * @param {boolean} headOnly
236
+ */
237
+ async function writeFetchResponse(res, response, headOnly = false) {
238
+ res.statusCode = response.status;
239
+ response.headers.forEach((value, key) => {
240
+ res.setHeader(key, value);
241
+ });
242
+ if (headOnly) {
243
+ res.end();
244
+ return;
245
+ }
246
+ const body = Buffer.from(await response.arrayBuffer());
247
+ res.end(body);
248
+ }
120
249
  /**
121
250
  * @template T
122
251
  * @param {string | null | undefined} value
@@ -1054,6 +1183,8 @@ export class Gateway {
1054
1183
  headersTimeout;
1055
1184
  requestTimeout;
1056
1185
  auth;
1186
+ ui;
1187
+ uiApp;
1057
1188
  defaults;
1058
1189
  workflows = new Map();
1059
1190
  connections = new Set();
@@ -1066,6 +1197,7 @@ export class Gateway {
1066
1197
  devtoolsSubscriberCounts = new Map();
1067
1198
  /** Flagged subscriber IDs that should force a snapshot on their next emit. */
1068
1199
  devtoolsInvalidateFlags = new Set();
1200
+ uiAssetCache = new Map();
1069
1201
  server = null;
1070
1202
  wsServer = null;
1071
1203
  schedulerTimer = null;
@@ -1097,8 +1229,193 @@ export class Gateway {
1097
1229
  ? DEFAULT_REQUEST_TIMEOUT
1098
1230
  : Math.floor(assertPositiveFiniteInteger("requestTimeout", Number(options.requestTimeout)));
1099
1231
  this.auth = options.auth;
1232
+ this.ui = resolveGatewayUiConfig(options.ui, "/");
1233
+ this.uiApp = createGatewayUiApp({
1234
+ resolveMatch: (pathname) => this.resolveUiMatch(pathname),
1235
+ renderIndex: (match) => this.renderUiIndex(match),
1236
+ renderAsset: (match) => this.renderUiAsset(match),
1237
+ });
1100
1238
  this.defaults = options.defaults;
1101
1239
  }
1240
+ /**
1241
+ * @returns {GatewayUiMount[]}
1242
+ */
1243
+ getUiMounts() {
1244
+ const mounts = [];
1245
+ if (this.ui) {
1246
+ mounts.push({ kind: "gateway", workflowKey: null, config: this.ui });
1247
+ }
1248
+ for (const [workflowKey, entry] of this.workflows.entries()) {
1249
+ if (entry.ui) {
1250
+ mounts.push({ kind: "workflow", workflowKey, config: entry.ui });
1251
+ }
1252
+ }
1253
+ return mounts.sort((left, right) => right.config.path.length - left.config.path.length);
1254
+ }
1255
+ /**
1256
+ * @param {string} pathname
1257
+ * @returns {GatewayUiMount | null}
1258
+ */
1259
+ findUiMount(pathname) {
1260
+ for (const mount of this.getUiMounts()) {
1261
+ const mountPath = mount.config.path;
1262
+ if (mountPath === "/" || pathname === mountPath || pathname.startsWith(`${mountPath}/`)) {
1263
+ return mount;
1264
+ }
1265
+ }
1266
+ return null;
1267
+ }
1268
+ /**
1269
+ * @param {string} pathname
1270
+ */
1271
+ resolveUiMatch(pathname) {
1272
+ const mount = this.findUiMount(pathname);
1273
+ if (!mount) {
1274
+ return null;
1275
+ }
1276
+ const assetBase = joinUiPath(mount.config.path, `${GATEWAY_UI_ASSET_PREFIX}/`);
1277
+ const assetPath = pathname.startsWith(assetBase)
1278
+ ? pathname.slice(assetBase.length)
1279
+ : null;
1280
+ return {
1281
+ pathname,
1282
+ mountPath: mount.config.path,
1283
+ assetPath,
1284
+ config: mount,
1285
+ };
1286
+ }
1287
+ /**
1288
+ * @param {GatewayUiMount} mount
1289
+ */
1290
+ uiBootConfig(mount) {
1291
+ return {
1292
+ apiVersion: SMITHERS_API_VERSION,
1293
+ kind: mount.kind,
1294
+ workflowKey: mount.workflowKey,
1295
+ mountPath: mount.config.path,
1296
+ rpcPath: "/v1/rpc",
1297
+ wsPath: "/",
1298
+ assetBasePath: joinUiPath(mount.config.path, `${GATEWAY_UI_ASSET_PREFIX}/`),
1299
+ props: mount.config.props ?? {},
1300
+ };
1301
+ }
1302
+ /**
1303
+ * @param {{ config: GatewayUiMount }} match
1304
+ */
1305
+ renderUiIndex(match) {
1306
+ const mount = match.config;
1307
+ const title = mount.config.title ?? (mount.workflowKey ? `${mount.workflowKey} | Smithers` : "Smithers");
1308
+ const boot = this.uiBootConfig(mount);
1309
+ const assetSrc = joinUiPath(mount.config.path, `${GATEWAY_UI_ASSET_PREFIX}/client.js`);
1310
+ return `<!doctype html>
1311
+ <html lang="en">
1312
+ <head>
1313
+ <meta charset="utf-8">
1314
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1315
+ <title>${escapeHtml(title)}</title>
1316
+ </head>
1317
+ <body>
1318
+ <div id="root"></div>
1319
+ <script>globalThis.__SMITHERS_GATEWAY_UI__=${safeJsonScript(boot)};</script>
1320
+ <script type="module" src="${escapeHtml(assetSrc)}"></script>
1321
+ </body>
1322
+ </html>`;
1323
+ }
1324
+ /**
1325
+ * @param {{ config: GatewayUiMount; assetPath: string | null }} match
1326
+ */
1327
+ async renderUiAsset(match) {
1328
+ if (match.assetPath !== "client.js") {
1329
+ return null;
1330
+ }
1331
+ const body = await this.bundleUiEntry(match.config.config);
1332
+ return {
1333
+ body,
1334
+ contentType: "text/javascript; charset=utf-8",
1335
+ };
1336
+ }
1337
+ /**
1338
+ * @param {ResolvedGatewayUiConfig} config
1339
+ * @returns {Promise<string>}
1340
+ */
1341
+ async bundleUiEntry(config) {
1342
+ const cached = this.uiAssetCache.get(config.entry);
1343
+ if (cached) {
1344
+ return cached;
1345
+ }
1346
+ if (typeof Bun === "undefined" || typeof Bun.build !== "function") {
1347
+ throw new SmithersError("INVALID_INPUT", "Gateway UI bundling requires Bun.build.");
1348
+ }
1349
+ const result = await Bun.build({
1350
+ entrypoints: [config.entry],
1351
+ root: process.cwd(),
1352
+ target: "browser",
1353
+ format: "esm",
1354
+ sourcemap: "inline",
1355
+ minify: false,
1356
+ jsx: {
1357
+ runtime: "automatic",
1358
+ importSource: "react",
1359
+ },
1360
+ });
1361
+ if (!result.success) {
1362
+ const message = result.logs?.map((entry) => entry.message).filter(Boolean).join("\n")
1363
+ || `Failed to build Gateway UI entry ${config.entry}`;
1364
+ throw new SmithersError("INVALID_INPUT", message);
1365
+ }
1366
+ const output = result.outputs.find((entry) => entry.path.endsWith(".js")) ?? result.outputs[0];
1367
+ const body = await output.text();
1368
+ this.uiAssetCache.set(config.entry, body);
1369
+ return body;
1370
+ }
1371
+ /**
1372
+ * @param {IncomingMessage} req
1373
+ * @param {ServerResponse} res
1374
+ */
1375
+ async handleUiHttp(req, res) {
1376
+ if ((req.method ?? "GET") !== "GET" && (req.method ?? "GET") !== "HEAD") {
1377
+ return false;
1378
+ }
1379
+ const host = headerValue(req, "host") ?? "127.0.0.1";
1380
+ const request = new Request(`http://${host}${req.url ?? "/"}`, {
1381
+ method: "GET",
1382
+ headers: nodeHeadersToFetchHeaders(req.headers),
1383
+ });
1384
+ const response = await this.uiApp.fetch(request);
1385
+ if (response.status === 404 && response.headers.get("x-smithers-ui-miss") === "1") {
1386
+ return false;
1387
+ }
1388
+ await writeFetchResponse(res, response, (req.method ?? "GET") === "HEAD");
1389
+ return true;
1390
+ }
1391
+ /**
1392
+ * @param {string} key
1393
+ * @param {RegisteredWorkflow} entry
1394
+ */
1395
+ workflowSummary(key, entry) {
1396
+ return {
1397
+ key,
1398
+ ...(entry.workflow.readableName ? { readableName: entry.workflow.readableName } : {}),
1399
+ ...(entry.workflow.description ? { description: entry.workflow.description } : {}),
1400
+ hasUi: Boolean(entry.ui),
1401
+ uiPath: entry.ui?.path ?? null,
1402
+ };
1403
+ }
1404
+ /**
1405
+ * @param {boolean | undefined} hasUi
1406
+ */
1407
+ listWorkflowSummaries(hasUi) {
1408
+ const rows = [];
1409
+ for (const [key, entry] of this.workflows.entries()) {
1410
+ const summary = this.workflowSummary(key, entry);
1411
+ if (hasUi !== undefined && summary.hasUi !== hasUi) {
1412
+ continue;
1413
+ }
1414
+ rows.push(summary);
1415
+ }
1416
+ rows.sort((left, right) => left.key.localeCompare(right.key));
1417
+ return rows;
1418
+ }
1102
1419
  authModeLabel() {
1103
1420
  return gatewayAuthMode(this.auth);
1104
1421
  }
@@ -1770,16 +2087,18 @@ export class Gateway {
1770
2087
  /**
1771
2088
  * @param {string} key
1772
2089
  * @param {SmithersWorkflow} workflow
1773
- * @param {{ schedule?: string; webhook?: GatewayWebhookConfig }} [options]
2090
+ * @param {GatewayRegisterOptions} [options]
1774
2091
  * @returns {this}
1775
2092
  */
1776
2093
  register(key, workflow, options) {
1777
2094
  ensureSmithersTables(workflow.db);
2095
+ const ui = resolveGatewayUiConfig(options?.ui, `/workflows/${encodeURIComponent(key)}`);
1778
2096
  this.workflows.set(key, {
1779
2097
  key,
1780
2098
  workflow,
1781
2099
  schedule: options?.schedule,
1782
2100
  webhook: options?.webhook,
2101
+ ui,
1783
2102
  });
1784
2103
  // Startup recovery: any audit row left in `in_progress` from a prior
1785
2104
  // crash is flipped to `partial` and the associated run is flagged as
@@ -1795,7 +2114,7 @@ export class Gateway {
1795
2114
  return this;
1796
2115
  }
1797
2116
  /**
1798
- * @param {{ port?: number; host?: string }} [options]
2117
+ * @param {{ port?: number; host?: string; path?: string }} [options]
1799
2118
  */
1800
2119
  async listen(options = {}) {
1801
2120
  if (this.server) {
@@ -1832,6 +2151,9 @@ export class Gateway {
1832
2151
  if ((req.method ?? "GET") === "POST" && (req.url ?? "/") === "/rpc") {
1833
2152
  return this.handleHttpRpc(req, res);
1834
2153
  }
2154
+ if (await this.handleUiHttp(req, res)) {
2155
+ return;
2156
+ }
1835
2157
  return sendJson(res, 404, { error: { code: "NOT_FOUND", message: "Route not found" } });
1836
2158
  });
1837
2159
  server.headersTimeout = this.headersTimeout;
@@ -1863,6 +2185,10 @@ export class Gateway {
1863
2185
  });
1864
2186
  });
1865
2187
  await new Promise((resolve) => {
2188
+ if (options.path !== undefined) {
2189
+ server.listen(options.path, () => resolve());
2190
+ return;
2191
+ }
1866
2192
  if (options.host === undefined) {
1867
2193
  server.listen(options.port ?? 7331, () => resolve());
1868
2194
  return;
@@ -2698,6 +3024,7 @@ export class Gateway {
2698
3024
  const request = parseApprovalRequest(parseJson(approval.requestJson), node?.label ?? approval.nodeId);
2699
3025
  approvals.push({
2700
3026
  runId: approval.runId,
3027
+ workflowKey: entry.key,
2701
3028
  nodeId: approval.nodeId,
2702
3029
  iteration: approval.iteration ?? 0,
2703
3030
  requestTitle: request.title ?? node?.label ?? approval.nodeId,
@@ -2945,6 +3272,12 @@ export class Gateway {
2945
3272
  const status = asString(params.status) ?? asString(filter.status);
2946
3273
  return responseOk(frame.id, await this.listRunsAcrossWorkflows(limit, status));
2947
3274
  }
3275
+ case "workflows.list":
3276
+ case "listWorkflows": {
3277
+ const filter = asObject(params.filter) ?? {};
3278
+ const hasUi = asBoolean(params.hasUi) ?? asBoolean(filter.hasUi);
3279
+ return responseOk(frame.id, this.listWorkflowSummaries(hasUi));
3280
+ }
2948
3281
  case "runs.create":
2949
3282
  case "launchRun": {
2950
3283
  const workflowKey = asString(params.workflow);
@@ -3548,7 +3881,23 @@ export class Gateway {
3548
3881
  return responseOk(frame.id, diffRawSnapshots(leftSnapshot, rightSnapshot));
3549
3882
  }
3550
3883
  case "approvals.list":
3551
- return responseOk(frame.id, await this.listPendingApprovals());
3884
+ case "listApprovals": {
3885
+ const filter = asObject(params.filter) ?? {};
3886
+ const runId = asString(params.runId) ?? asString(filter.runId);
3887
+ const workflow = asString(params.workflow) ?? asString(filter.workflow);
3888
+ const limit = asOptionalPositiveInt(params.limit ?? filter.limit, "limit");
3889
+ let approvals = await this.listPendingApprovals();
3890
+ if (runId) {
3891
+ approvals = approvals.filter((approval) => approval.runId === runId);
3892
+ }
3893
+ if (workflow) {
3894
+ approvals = approvals.filter((approval) => approval.workflowKey === workflow);
3895
+ }
3896
+ if (limit !== undefined) {
3897
+ approvals = approvals.slice(0, limit);
3898
+ }
3899
+ return responseOk(frame.id, approvals);
3900
+ }
3552
3901
  case "approvals.decide":
3553
3902
  case "submitApproval": {
3554
3903
  const runId = asString(params.runId);
@@ -0,0 +1,47 @@
1
+ import { Hono } from "hono";
2
+
3
+ /**
4
+ * @typedef {{
5
+ * pathname: string;
6
+ * mountPath: string;
7
+ * assetPath: string | null;
8
+ * config: Record<string, unknown>;
9
+ * }} GatewayUiMatch
10
+ */
11
+
12
+ /**
13
+ * @param {{
14
+ * resolveMatch: (pathname: string) => GatewayUiMatch | null;
15
+ * renderIndex: (match: GatewayUiMatch) => string;
16
+ * renderAsset: (match: GatewayUiMatch) => Promise<{ body: string; contentType: string } | null>;
17
+ * }} options
18
+ */
19
+ export function createGatewayUiApp(options) {
20
+ const app = new Hono();
21
+ app.get("*", async (c) => {
22
+ const url = new URL(c.req.url);
23
+ const match = options.resolveMatch(url.pathname);
24
+ if (!match) {
25
+ return new Response("Not Found", {
26
+ status: 404,
27
+ headers: { "x-smithers-ui-miss": "1" },
28
+ });
29
+ }
30
+ if (match.assetPath) {
31
+ const asset = await options.renderAsset(match);
32
+ if (!asset) {
33
+ return c.text("Not Found", 404);
34
+ }
35
+ return c.body(asset.body, 200, {
36
+ "Content-Type": asset.contentType,
37
+ "Cache-Control": "no-store",
38
+ "X-Content-Type-Options": "nosniff",
39
+ });
40
+ }
41
+ return c.html(options.renderIndex(match), 200, {
42
+ "Cache-Control": "no-store",
43
+ "X-Content-Type-Options": "nosniff",
44
+ });
45
+ });
46
+ return app;
47
+ }
package/src/index.d.ts CHANGED
@@ -1,22 +1,22 @@
1
- import * as _smithers_db_adapter_RunRow from '@smithers-orchestrator/db/adapter/RunRow';
1
+ import * as _smithers_orchestrator_db_adapter_RunRow from '@smithers-orchestrator/db/adapter/RunRow';
2
2
  import * as node_http from 'node:http';
3
- import { IncomingMessage as IncomingMessage$1, ServerResponse as ServerResponse$1 } from 'node:http';
4
- import * as _smithers_observability_SmithersEvent from '@smithers-orchestrator/observability/SmithersEvent';
5
- import * as _smithers_components_SmithersWorkflow from '@smithers-orchestrator/components/SmithersWorkflow';
3
+ import * as _smithers_orchestrator_observability_SmithersEvent from '@smithers-orchestrator/observability/SmithersEvent';
4
+ import * as _smithers_orchestrator_components_SmithersWorkflow from '@smithers-orchestrator/components/SmithersWorkflow';
6
5
  import { SmithersWorkflow as SmithersWorkflow$1 } from '@smithers-orchestrator/components/SmithersWorkflow';
6
+ import * as hono from 'hono';
7
+ import { Hono } from 'hono';
8
+ import * as hono_types from 'hono/types';
7
9
  import { Effect } from 'effect';
8
- import * as _smithers_db_adapter from '@smithers-orchestrator/db/adapter';
10
+ import * as _smithers_orchestrator_db_adapter from '@smithers-orchestrator/db/adapter';
9
11
  import { SmithersDb as SmithersDb$4 } from '@smithers-orchestrator/db/adapter';
10
- import * as hono_types from 'hono/types';
11
- import { Hono } from 'hono';
12
12
  import * as effect_Fiber from 'effect/Fiber';
13
- import * as _smithers_protocol_errors from '@smithers-orchestrator/protocol/errors';
14
- import * as _smithers_devtools_snapshotSerializer from '@smithers-orchestrator/devtools/snapshotSerializer';
15
- import * as _smithers_protocol_devtools from '@smithers-orchestrator/protocol/devtools';
16
- import * as _smithers_engine_effect_DiffBundle from '@smithers-orchestrator/engine/effect/DiffBundle';
13
+ import * as _smithers_orchestrator_protocol_errors from '@smithers-orchestrator/protocol/errors';
14
+ import * as _smithers_orchestrator_devtools_snapshotSerializer from '@smithers-orchestrator/devtools/snapshotSerializer';
15
+ import * as _smithers_orchestrator_protocol_devtools from '@smithers-orchestrator/protocol/devtools';
16
+ import * as _smithers_orchestrator_engine_effect_DiffBundle from '@smithers-orchestrator/engine/effect/DiffBundle';
17
17
  import { DiffBundle } from '@smithers-orchestrator/engine/effect/DiffBundle';
18
18
  import { selectOutputRow } from '@smithers-orchestrator/db/output';
19
- import * as _smithers_time_travel_jumpToFrame from '@smithers-orchestrator/time-travel/jumpToFrame';
19
+ import * as _smithers_orchestrator_time_travel_jumpToFrame from '@smithers-orchestrator/time-travel/jumpToFrame';
20
20
  export { JumpToFrameError } from '@smithers-orchestrator/time-travel/jumpToFrame';
21
21
 
22
22
  type ServerOptions$1 = {
@@ -26,16 +26,33 @@ type ServerOptions$1 = {
26
26
  maxBodyBytes?: number;
27
27
  rootDir?: string;
28
28
  allowNetwork?: boolean;
29
+ /**
30
+ * Maximum time (in milliseconds) allowed for the HTTP parser to receive the
31
+ * complete headers of a single request. Helps mitigate slowloris attacks.
32
+ * @default 30000
33
+ */
34
+ headersTimeout?: number;
35
+ /**
36
+ * Maximum time (in milliseconds) allowed for a single request to be received
37
+ * and parsed, including the body. Helps mitigate slowloris attacks.
38
+ * @default 60000
39
+ */
40
+ requestTimeout?: number;
29
41
  };
30
42
 
31
43
  type ResponseFrame$1 = {
32
44
  type: "res";
33
45
  id: string;
34
46
  ok: boolean;
47
+ apiVersion?: "v1";
35
48
  payload?: unknown;
36
49
  error?: {
50
+ version?: "v1";
37
51
  code: string;
38
52
  message: string;
53
+ requiredScope?: string;
54
+ refresh?: string;
55
+ details?: unknown;
39
56
  };
40
57
  };
41
58
 
@@ -70,6 +87,10 @@ type GatewayTokenGrant$1 = {
70
87
  role: string;
71
88
  scopes: string[];
72
89
  userId?: string;
90
+ tokenId?: string;
91
+ issuedAtMs?: number;
92
+ expiresAtMs?: number;
93
+ revokedAtMs?: number;
73
94
  };
74
95
 
75
96
  type GatewayAuthConfig$1 = {
@@ -98,15 +119,54 @@ type GatewayDefaults$1 = {
98
119
  cliAgentTools?: "all" | "explicit-only";
99
120
  };
100
121
 
122
+ type GatewayUiConfig$1 = {
123
+ /**
124
+ * Browser entry module for the React app. Smithers bundles this with Bun and
125
+ * serves it from the Gateway origin.
126
+ */
127
+ entry: string;
128
+ /**
129
+ * URL path where the UI is mounted. Gateway-level UI defaults to `/`;
130
+ * workflow-level UI defaults to `/workflows/<workflowKey>`.
131
+ */
132
+ path?: string;
133
+ /**
134
+ * Document title for the generated HTML shell.
135
+ */
136
+ title?: string;
137
+ /**
138
+ * JSON-serializable boot data exposed to the browser.
139
+ */
140
+ props?: Record<string, unknown>;
141
+ };
142
+
101
143
  type GatewayOptions$1 = {
102
144
  protocol?: number;
103
145
  features?: string[];
104
146
  heartbeatMs?: number;
105
147
  auth?: GatewayAuthConfig$1;
148
+ ui?: GatewayUiConfig$1;
106
149
  defaults?: GatewayDefaults$1;
107
150
  maxBodyBytes?: number;
108
151
  maxPayload?: number;
109
152
  maxConnections?: number;
153
+ /**
154
+ * Per-run replay window for Gateway run event streams.
155
+ * @default 10000
156
+ */
157
+ eventWindowSize?: number;
158
+ /**
159
+ * Maximum time (in milliseconds) allowed for the HTTP parser to receive the
160
+ * complete headers of a single request. Helps mitigate slowloris attacks.
161
+ * @default 30000
162
+ */
163
+ headersTimeout?: number;
164
+ /**
165
+ * Maximum time (in milliseconds) allowed for a single request to be received
166
+ * and parsed, including the body. Helps mitigate slowloris attacks.
167
+ * @default 60000
168
+ */
169
+ requestTimeout?: number;
110
170
  };
111
171
 
112
172
  type ConnectRequest$1 = {
@@ -144,12 +204,19 @@ type HelloResponse$1 = {
144
204
  };
145
205
  };
146
206
 
207
+ type GatewayRegisterOptions$1 = {
208
+ schedule?: string;
209
+ webhook?: GatewayWebhookConfig$1;
210
+ ui?: GatewayUiConfig$1;
211
+ };
212
+
147
213
  type EventFrame$1 = {
148
214
  type: "event";
149
215
  event: string;
150
216
  payload?: unknown;
151
217
  seq: number;
152
218
  stateVersion: number;
219
+ apiVersion?: "v1";
153
220
  };
154
221
 
155
222
  /**
@@ -175,7 +242,7 @@ declare function assertGatewayInputDepthWithinBounds(value: unknown, maxDepth?:
175
242
  /**
176
243
  * @param {string | undefined} code
177
244
  */
178
- declare function statusForRpcError(code: string | undefined): 401 | 403 | 404 | 400 | 409 | 413 | 429 | 501 | 500;
245
+ declare function statusForRpcError(code: string | undefined): 400 | 401 | 403 | 404 | 409 | 429 | 413 | 501 | 500;
179
246
  declare const GATEWAY_RPC_MAX_PAYLOAD_BYTES: 1048576;
180
247
  declare const GATEWAY_RPC_MAX_DEPTH: 32;
181
248
  declare const GATEWAY_RPC_MAX_ARRAY_LENGTH: 256;
@@ -195,7 +262,12 @@ declare class Gateway {
195
262
  maxBodyBytes: number;
196
263
  maxPayload: number;
197
264
  maxConnections: number;
265
+ eventWindowSize: number;
266
+ headersTimeout: number;
267
+ requestTimeout: number;
198
268
  auth: GatewayAuthConfig$1 | undefined;
269
+ ui: ResolvedGatewayUiConfig | null;
270
+ uiApp: hono.Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
199
271
  defaults: GatewayDefaults$1 | undefined;
200
272
  workflows: Map<any, any>;
201
273
  connections: Set<any>;
@@ -203,15 +275,95 @@ declare class Gateway {
203
275
  activeRuns: Map<any, any>;
204
276
  inflightRuns: Map<any, any>;
205
277
  devtoolsSubscribers: Map<any, any>;
278
+ runEventWindows: Map<any, any>;
206
279
  /** Absolute active subscriber count per runId (gauge source of truth). */
207
280
  devtoolsSubscriberCounts: Map<any, any>;
208
281
  /** Flagged subscriber IDs that should force a snapshot on their next emit. */
209
282
  devtoolsInvalidateFlags: Set<any>;
283
+ uiAssetCache: Map<any, any>;
210
284
  server: null;
211
285
  wsServer: null;
212
286
  schedulerTimer: null;
213
287
  stateVersion: number;
214
288
  startedAtMs: number;
289
+ /**
290
+ * @returns {GatewayUiMount[]}
291
+ */
292
+ getUiMounts(): GatewayUiMount[];
293
+ /**
294
+ * @param {string} pathname
295
+ * @returns {GatewayUiMount | null}
296
+ */
297
+ findUiMount(pathname: string): GatewayUiMount | null;
298
+ /**
299
+ * @param {string} pathname
300
+ */
301
+ resolveUiMatch(pathname: string): {
302
+ pathname: string;
303
+ mountPath: string;
304
+ assetPath: string | null;
305
+ config: GatewayUiMount;
306
+ } | null;
307
+ /**
308
+ * @param {GatewayUiMount} mount
309
+ */
310
+ uiBootConfig(mount: GatewayUiMount): {
311
+ apiVersion: "v1";
312
+ kind: "workflow" | "gateway";
313
+ workflowKey: string | null;
314
+ mountPath: string;
315
+ rpcPath: string;
316
+ wsPath: string;
317
+ assetBasePath: string;
318
+ props: Record<string, unknown>;
319
+ };
320
+ /**
321
+ * @param {{ config: GatewayUiMount }} match
322
+ */
323
+ renderUiIndex(match: {
324
+ config: GatewayUiMount;
325
+ }): string;
326
+ /**
327
+ * @param {{ config: GatewayUiMount; assetPath: string | null }} match
328
+ */
329
+ renderUiAsset(match: {
330
+ config: GatewayUiMount;
331
+ assetPath: string | null;
332
+ }): Promise<{
333
+ body: string;
334
+ contentType: string;
335
+ } | null>;
336
+ /**
337
+ * @param {ResolvedGatewayUiConfig} config
338
+ * @returns {Promise<string>}
339
+ */
340
+ bundleUiEntry(config: ResolvedGatewayUiConfig): Promise<string>;
341
+ /**
342
+ * @param {IncomingMessage} req
343
+ * @param {ServerResponse} res
344
+ */
345
+ handleUiHttp(req: IncomingMessage, res: ServerResponse$1): Promise<boolean>;
346
+ /**
347
+ * @param {string} key
348
+ * @param {RegisteredWorkflow} entry
349
+ */
350
+ workflowSummary(key: string, entry: RegisteredWorkflow): {
351
+ hasUi: boolean;
352
+ uiPath: string | null;
353
+ description?: string | undefined;
354
+ readableName?: string | undefined;
355
+ key: string;
356
+ };
357
+ /**
358
+ * @param {boolean | undefined} hasUi
359
+ */
360
+ listWorkflowSummaries(hasUi: boolean | undefined): {
361
+ hasUi: boolean;
362
+ uiPath: string | null;
363
+ description?: string | undefined;
364
+ readableName?: string | undefined;
365
+ key: string;
366
+ }[];
215
367
  authModeLabel(): string;
216
368
  /**
217
369
  * @param {string} [runId]
@@ -269,6 +421,61 @@ declare class Gateway {
269
421
  */
270
422
  cleanupDevToolsSubscribers(connection: ConnectionState): void;
271
423
  /**
424
+ * @param {string} runId
425
+ * @returns {{ nextSeq: number; window: Array<Record<string, unknown>> }}
426
+ */
427
+ getRunEventWindow(runId: string): {
428
+ nextSeq: number;
429
+ window: Array<Record<string, unknown>>;
430
+ };
431
+ /**
432
+ * @param {string} event
433
+ * @param {unknown} payload
434
+ * @param {number} stateVersion
435
+ * @returns {Record<string, unknown> | null}
436
+ */
437
+ appendRunEventWindow(event: string, payload: unknown, stateVersion: number): Record<string, unknown> | null;
438
+ /**
439
+ * @param {string} runId
440
+ * @returns {number}
441
+ */
442
+ getRunEventCurrentSeq(runId: string): number;
443
+ /**
444
+ * @param {ConnectionState} connection
445
+ * @param {string} streamId
446
+ * @param {string} runId
447
+ * @returns {() => void}
448
+ */
449
+ registerRunEventSubscriber(connection: ConnectionState, streamId: string, runId: string): () => void;
450
+ /**
451
+ * @param {ConnectionState} connection
452
+ * @param {string} streamId
453
+ */
454
+ unregisterRunEventSubscriber(connection: ConnectionState, streamId: string): void;
455
+ /**
456
+ * @param {ConnectionState} connection
457
+ */
458
+ cleanupRunEventSubscribers(connection: ConnectionState): void;
459
+ /**
460
+ * @param {ConnectionState} connection
461
+ * @param {string} streamId
462
+ * @param {Record<string, unknown>} frame
463
+ */
464
+ sendRunEventStreamFrame(connection: ConnectionState, streamId: string, frame: Record<string, unknown>): void;
465
+ /**
466
+ * @param {ConnectionState} connection
467
+ * @param {string} streamId
468
+ * @param {string} runId
469
+ * @param {number} fromSeq
470
+ * @param {number} toSeq
471
+ * @param {unknown} snapshot
472
+ */
473
+ sendRunGapResync(connection: ConnectionState, streamId: string, runId: string, fromSeq: number, toSeq: number, snapshot: unknown): void;
474
+ /**
475
+ * @param {string} runId
476
+ */
477
+ buildRunSnapshot(runId: string): Promise<any>;
478
+ /**
272
479
  * @param {GatewayTransport} transport
273
480
  * @param {string} frameType
274
481
  * @param {GatewayMetricLabels} [labels]
@@ -307,7 +514,7 @@ declare class Gateway {
307
514
  * @param {number} status
308
515
  * @param {ResponseFrame} response
309
516
  */
310
- sendHttpRpcResponse(res: ServerResponse, status: number, response: ResponseFrame): void;
517
+ sendHttpRpcResponse(res: ServerResponse$1, status: number, response: ResponseFrame): void;
311
518
  /**
312
519
  * @param {SmithersDb} adapter
313
520
  * @param {string} runId
@@ -327,23 +534,21 @@ declare class Gateway {
327
534
  * @param {ServerResponse} res
328
535
  * @param {string} workflowKey
329
536
  */
330
- handleWebhook(req: IncomingMessage, res: ServerResponse, workflowKey: string): Promise<void>;
537
+ handleWebhook(req: IncomingMessage, res: ServerResponse$1, workflowKey: string): Promise<void>;
331
538
  /**
332
539
  * @param {string} key
333
540
  * @param {SmithersWorkflow} workflow
334
- * @param {{ schedule?: string; webhook?: GatewayWebhookConfig }} [options]
541
+ * @param {GatewayRegisterOptions} [options]
335
542
  * @returns {this}
336
543
  */
337
- register(key: string, workflow: SmithersWorkflow, options?: {
338
- schedule?: string;
339
- webhook?: GatewayWebhookConfig;
340
- }): this;
544
+ register(key: string, workflow: SmithersWorkflow, options?: GatewayRegisterOptions): this;
341
545
  /**
342
- * @param {{ port?: number; host?: string }} [options]
546
+ * @param {{ port?: number; host?: string; path?: string }} [options]
343
547
  */
344
548
  listen(options?: {
345
549
  port?: number;
346
550
  host?: string;
551
+ path?: string;
347
552
  }): Promise<node_http.Server<typeof node_http.IncomingMessage, typeof node_http.ServerResponse>>;
348
553
  close(): Promise<void>;
349
554
  startScheduler(): void;
@@ -419,8 +624,9 @@ declare class Gateway {
419
624
  /**
420
625
  * @param {IncomingMessage} req
421
626
  * @param {ServerResponse} res
627
+ * @param {string} [forcedMethod]
422
628
  */
423
- handleHttpRpc(req: IncomingMessage, res: ServerResponse): Promise<void>;
629
+ handleHttpRpc(req: IncomingMessage, res: ServerResponse$1, forcedMethod?: string): Promise<void>;
424
630
  /**
425
631
  * @param {ConnectionState} connection
426
632
  * @param {ResponseFrame} frame
@@ -441,6 +647,7 @@ declare class Gateway {
441
647
  runs: any[];
442
648
  approvals: {
443
649
  runId: any;
650
+ workflowKey: any;
444
651
  nodeId: any;
445
652
  iteration: any;
446
653
  requestTitle: any;
@@ -465,6 +672,7 @@ declare class Gateway {
465
672
  listRunsAcrossWorkflows(limit?: number, status?: string): Promise<any[]>;
466
673
  listPendingApprovals(): Promise<{
467
674
  runId: any;
675
+ workflowKey: any;
468
676
  nodeId: any;
469
677
  iteration: any;
470
678
  requestTitle: any;
@@ -511,7 +719,9 @@ declare class Gateway {
511
719
  }
512
720
  type EventFrame = EventFrame$1;
513
721
  type GatewayDefaults = GatewayDefaults$1;
722
+ type GatewayRegisterOptions = GatewayRegisterOptions$1;
514
723
  type GatewayTokenGrant = GatewayTokenGrant$1;
724
+ type GatewayUiConfig = GatewayUiConfig$1;
515
725
  type HelloResponse = HelloResponse$1;
516
726
  type GatewayWebhookRunConfig = GatewayWebhookRunConfig$1;
517
727
  type GatewayWebhookSignalConfig = GatewayWebhookSignalConfig$1;
@@ -522,9 +732,9 @@ type GatewayWebhookConfig = GatewayWebhookConfig$1;
522
732
  type IncomingMessage = node_http.IncomingMessage;
523
733
  type RequestFrame = RequestFrame$1;
524
734
  type ResponseFrame = ResponseFrame$1;
525
- type ServerResponse = node_http.ServerResponse;
526
- type SmithersWorkflow = _smithers_components_SmithersWorkflow.SmithersWorkflow<unknown>;
527
- type SmithersEvent$1 = _smithers_observability_SmithersEvent.SmithersEvent;
735
+ type ServerResponse$1 = node_http.ServerResponse;
736
+ type SmithersWorkflow = _smithers_orchestrator_components_SmithersWorkflow.SmithersWorkflow<unknown>;
737
+ type SmithersEvent$1 = _smithers_orchestrator_observability_SmithersEvent.SmithersEvent;
528
738
  type GatewayMetricLabels = Record<string, string | number | null | undefined>;
529
739
  type GatewayTransport = "ws" | "http";
530
740
  type GatewayRequestContext = {
@@ -532,6 +742,7 @@ type GatewayRequestContext = {
532
742
  role?: string;
533
743
  scopes?: string[];
534
744
  userId?: string | null;
745
+ tokenId?: string | null;
535
746
  origin?: string;
536
747
  transport?: GatewayTransport;
537
748
  };
@@ -550,6 +761,7 @@ type RunStartAuthContext = {
550
761
  role: string;
551
762
  scopes: string[];
552
763
  userId?: string | null;
764
+ tokenId?: string | null;
553
765
  connectionId?: string;
554
766
  };
555
767
  type RegisteredWorkflow = {
@@ -558,6 +770,7 @@ type RegisteredWorkflow = {
558
770
  key: string;
559
771
  schedule?: string;
560
772
  webhook?: GatewayWebhookConfig;
773
+ ui?: ResolvedGatewayUiConfig | null;
561
774
  };
562
775
  type ResolvedRun = {
563
776
  runId: string;
@@ -565,6 +778,17 @@ type ResolvedRun = {
565
778
  workflow: SmithersWorkflow;
566
779
  adapter: SmithersDb$4;
567
780
  };
781
+ type ResolvedGatewayUiConfig = {
782
+ entry: string;
783
+ path: string;
784
+ title?: string;
785
+ props?: Record<string, unknown>;
786
+ };
787
+ type GatewayUiMount = {
788
+ kind: "gateway" | "workflow";
789
+ workflowKey: string | null;
790
+ config: ResolvedGatewayUiConfig;
791
+ };
568
792
 
569
793
  type ServeOptions$1 = {
570
794
  workflow: SmithersWorkflow$1<unknown>;
@@ -614,7 +838,7 @@ declare class NodeOutputRouteError extends Error {
614
838
  /** @type {NodeOutputErrorCode} */
615
839
  code: NodeOutputErrorCode;
616
840
  }
617
- type NodeOutputErrorCode = _smithers_protocol_errors.NodeOutputErrorCode;
841
+ type NodeOutputErrorCode = _smithers_orchestrator_protocol_errors.NodeOutputErrorCode;
618
842
 
619
843
  /**
620
844
  * @returns {DevToolsNode}
@@ -701,11 +925,11 @@ declare class DevToolsRouteError extends Error {
701
925
  hint: string | undefined;
702
926
  }
703
927
  declare const DEVTOOLS_EMPTY_ROOT_ID: 0;
704
- type SmithersDb$3 = _smithers_db_adapter.SmithersDb;
705
- type DevToolsNode = _smithers_protocol_devtools.DevToolsNode;
706
- type DevToolsSnapshot = _smithers_protocol_devtools.DevToolsSnapshot;
707
- type DevToolsNodeType = _smithers_protocol_devtools.DevToolsNodeType;
708
- type SnapshotSerializerWarning$1 = _smithers_devtools_snapshotSerializer.SnapshotSerializerWarning;
928
+ type SmithersDb$3 = _smithers_orchestrator_db_adapter.SmithersDb;
929
+ type DevToolsNode = _smithers_orchestrator_protocol_devtools.DevToolsNode;
930
+ type DevToolsSnapshot = _smithers_orchestrator_protocol_devtools.DevToolsSnapshot;
931
+ type DevToolsNodeType = _smithers_orchestrator_protocol_devtools.DevToolsNodeType;
932
+ type SnapshotSerializerWarning$1 = _smithers_orchestrator_devtools_snapshotSerializer.SnapshotSerializerWarning;
709
933
 
710
934
  type DiffSummary$1 = {
711
935
  filesChanged: number;
@@ -752,7 +976,7 @@ type GetNodeDiffRouteResult$1 = {
752
976
  * }} opts
753
977
  * @returns {Promise<GetNodeDiffRouteResult>}
754
978
  */
755
- declare function getNodeDiffRoute({ runId: rawRunId, nodeId: rawNodeId, iteration: rawIteration, resolveRun, emitEffect, computeDiffBundleImpl, computeDiffBundleBetweenRefsImpl, getCurrentPointerImpl, resolveCommitPointerImpl, restorePointerImpl, nowMs, stat, }: {
979
+ declare function getNodeDiffRoute({ runId: rawRunId, nodeId: rawNodeId, iteration: rawIteration, resolveRun, emitEffect, computeDiffBundleImpl, computeDiffBundleBetweenRefsImpl, getCurrentPointerImpl: _getCurrentPointerImpl, resolveCommitPointerImpl, restorePointerImpl: _restorePointerImpl, nowMs, stat, }: {
756
980
  runId: unknown;
757
981
  nodeId: unknown;
758
982
  iteration: unknown;
@@ -760,8 +984,8 @@ declare function getNodeDiffRoute({ runId: rawRunId, nodeId: rawNodeId, iteratio
760
984
  adapter: SmithersDb$2;
761
985
  } | null>;
762
986
  emitEffect?: (effect: Effect.Effect<void>) => Promise<unknown>;
763
- computeDiffBundleImpl?: (baseRef: string, cwd: string, seq?: number) => Promise<_smithers_engine_effect_DiffBundle.DiffBundle>;
764
- computeDiffBundleBetweenRefsImpl?: (baseRef: string, targetRef: string, cwd: string, seq?: number) => Promise<_smithers_engine_effect_DiffBundle.DiffBundle>;
987
+ computeDiffBundleImpl?: (baseRef: string, cwd: string, seq?: number) => Promise<_smithers_orchestrator_engine_effect_DiffBundle.DiffBundle>;
988
+ computeDiffBundleBetweenRefsImpl?: (baseRef: string, targetRef: string, cwd: string, seq?: number) => Promise<_smithers_orchestrator_engine_effect_DiffBundle.DiffBundle>;
765
989
  getCurrentPointerImpl?: (cwd: string) => Promise<string | null>;
766
990
  resolveCommitPointerImpl?: (pointer: string, cwd: string) => Promise<string | null>;
767
991
  restorePointerImpl?: (pointer: string, cwd: string) => Promise<{
@@ -771,8 +995,8 @@ declare function getNodeDiffRoute({ runId: rawRunId, nodeId: rawNodeId, iteratio
771
995
  nowMs?: () => number;
772
996
  stat?: boolean;
773
997
  }): Promise<GetNodeDiffRouteResult>;
774
- type SmithersDb$2 = _smithers_db_adapter.SmithersDb;
775
- type AttemptRow = _smithers_db_adapter.AttemptRow;
998
+ type SmithersDb$2 = _smithers_orchestrator_db_adapter.SmithersDb;
999
+ type AttemptRow = _smithers_orchestrator_db_adapter.AttemptRow;
776
1000
  type GetNodeDiffRouteResult = GetNodeDiffRouteResult$1;
777
1001
  type DiffSummary = DiffSummary$1;
778
1002
 
@@ -832,8 +1056,8 @@ declare function getNodeOutputRoute(params: {
832
1056
  nodeId: unknown;
833
1057
  iteration: unknown;
834
1058
  resolveRun: (runId: string) => Promise<{
835
- workflow: _smithers_components_SmithersWorkflow.SmithersWorkflow<unknown>;
836
- adapter: _smithers_db_adapter.SmithersDb;
1059
+ workflow: _smithers_orchestrator_components_SmithersWorkflow.SmithersWorkflow<unknown>;
1060
+ adapter: _smithers_orchestrator_db_adapter.SmithersDb;
837
1061
  } | null>;
838
1062
  selectOutputRowImpl?: typeof selectOutputRow;
839
1063
  emitEffect?: (effect: Effect.Effect<void>) => Promise<unknown>;
@@ -883,9 +1107,9 @@ declare function jumpToFrameRoute(input: {
883
1107
  onLog?: (level: "info" | "warn" | "error", message: string, fields?: Record<string, unknown>) => Promise<void> | void;
884
1108
  }): Promise<JumpResult>;
885
1109
 
886
- type SmithersDb$1 = _smithers_db_adapter.SmithersDb;
887
- type SmithersEvent = _smithers_observability_SmithersEvent.SmithersEvent;
888
- type JumpResult = _smithers_time_travel_jumpToFrame.JumpResult;
1110
+ type SmithersDb$1 = _smithers_orchestrator_db_adapter.SmithersDb;
1111
+ type SmithersEvent = _smithers_orchestrator_observability_SmithersEvent.SmithersEvent;
1112
+ type JumpResult = _smithers_orchestrator_time_travel_jumpToFrame.JumpResult;
889
1113
 
890
1114
  /**
891
1115
  * @param {{
@@ -934,20 +1158,21 @@ declare function streamDevToolsRoute(input: {
934
1158
  declare const DEVTOOLS_REBASELINE_INTERVAL: 50;
935
1159
  declare const DEVTOOLS_BACKPRESSURE_LIMIT: 1000;
936
1160
  declare const DEVTOOLS_POLL_INTERVAL_MS: 25;
937
- type SmithersDb = _smithers_db_adapter.SmithersDb;
938
- type DevToolsEvent = _smithers_protocol_devtools.DevToolsEvent;
939
- type SnapshotSerializerWarning = _smithers_devtools_snapshotSerializer.SnapshotSerializerWarning;
1161
+ type SmithersDb = _smithers_orchestrator_db_adapter.SmithersDb;
1162
+ type DevToolsEvent = _smithers_orchestrator_protocol_devtools.DevToolsEvent;
1163
+ type SnapshotSerializerWarning = _smithers_orchestrator_devtools_snapshotSerializer.SnapshotSerializerWarning;
940
1164
 
941
1165
  /**
942
1166
  * @param {ServerOptions} [opts]
943
1167
  */
944
- declare function startServerEffect(opts?: ServerOptions): Effect.Effect<node_http.Server<typeof IncomingMessage$1, typeof ServerResponse$1>, never, never>;
1168
+ declare function startServerEffect(opts?: ServerOptions): Effect.Effect<node_http.Server<typeof node_http.IncomingMessage, typeof node_http.ServerResponse>, never, never>;
945
1169
  /**
946
1170
  * @param {ServerOptions} [opts]
947
1171
  */
948
- declare function startServer(opts?: ServerOptions): node_http.Server<typeof IncomingMessage$1, typeof ServerResponse$1>;
1172
+ declare function startServer(opts?: ServerOptions): node_http.Server<typeof node_http.IncomingMessage, typeof node_http.ServerResponse>;
949
1173
 
950
- type RunRow = _smithers_db_adapter_RunRow.RunRow;
1174
+ type RunRow = _smithers_orchestrator_db_adapter_RunRow.RunRow;
1175
+ type ServerResponse = node_http.ServerResponse;
951
1176
  type ServerOptions = ServerOptions$1;
952
1177
 
953
- 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 GatewayRequestContext, type GatewayTokenGrant, type GatewayTransport, 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 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 };
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 };