@smithers-orchestrator/server 0.17.0 → 0.18.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.18.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.18.0",
30
+ "@smithers-orchestrator/devtools": "0.18.0",
31
+ "@smithers-orchestrator/engine": "0.18.0",
32
+ "@smithers-orchestrator/errors": "0.18.0",
33
+ "@smithers-orchestrator/components": "0.18.0",
34
+ "@smithers-orchestrator/driver": "0.18.0",
35
+ "@smithers-orchestrator/observability": "0.18.0",
36
+ "@smithers-orchestrator/gateway": "0.18.0",
37
+ "@smithers-orchestrator/protocol": "0.18.0",
38
+ "@smithers-orchestrator/scheduler": "0.18.0",
39
+ "@smithers-orchestrator/time-travel": "0.18.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.18.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
@@ -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;
@@ -2698,6 +3020,7 @@ export class Gateway {
2698
3020
  const request = parseApprovalRequest(parseJson(approval.requestJson), node?.label ?? approval.nodeId);
2699
3021
  approvals.push({
2700
3022
  runId: approval.runId,
3023
+ workflowKey: entry.key,
2701
3024
  nodeId: approval.nodeId,
2702
3025
  iteration: approval.iteration ?? 0,
2703
3026
  requestTitle: request.title ?? node?.label ?? approval.nodeId,
@@ -2945,6 +3268,12 @@ export class Gateway {
2945
3268
  const status = asString(params.status) ?? asString(filter.status);
2946
3269
  return responseOk(frame.id, await this.listRunsAcrossWorkflows(limit, status));
2947
3270
  }
3271
+ case "workflows.list":
3272
+ case "listWorkflows": {
3273
+ const filter = asObject(params.filter) ?? {};
3274
+ const hasUi = asBoolean(params.hasUi) ?? asBoolean(filter.hasUi);
3275
+ return responseOk(frame.id, this.listWorkflowSummaries(hasUi));
3276
+ }
2948
3277
  case "runs.create":
2949
3278
  case "launchRun": {
2950
3279
  const workflowKey = asString(params.workflow);
@@ -3548,7 +3877,23 @@ export class Gateway {
3548
3877
  return responseOk(frame.id, diffRawSnapshots(leftSnapshot, rightSnapshot));
3549
3878
  }
3550
3879
  case "approvals.list":
3551
- return responseOk(frame.id, await this.listPendingApprovals());
3880
+ case "listApprovals": {
3881
+ const filter = asObject(params.filter) ?? {};
3882
+ const runId = asString(params.runId) ?? asString(filter.runId);
3883
+ const workflow = asString(params.workflow) ?? asString(filter.workflow);
3884
+ const limit = asOptionalPositiveInt(params.limit ?? filter.limit, "limit");
3885
+ let approvals = await this.listPendingApprovals();
3886
+ if (runId) {
3887
+ approvals = approvals.filter((approval) => approval.runId === runId);
3888
+ }
3889
+ if (workflow) {
3890
+ approvals = approvals.filter((approval) => approval.workflowKey === workflow);
3891
+ }
3892
+ if (limit !== undefined) {
3893
+ approvals = approvals.slice(0, limit);
3894
+ }
3895
+ return responseOk(frame.id, approvals);
3896
+ }
3552
3897
  case "approvals.decide":
3553
3898
  case "submitApproval": {
3554
3899
  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,17 +534,14 @@ 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
546
  * @param {{ port?: number; host?: string }} [options]
343
547
  */
@@ -419,8 +623,9 @@ declare class Gateway {
419
623
  /**
420
624
  * @param {IncomingMessage} req
421
625
  * @param {ServerResponse} res
626
+ * @param {string} [forcedMethod]
422
627
  */
423
- handleHttpRpc(req: IncomingMessage, res: ServerResponse): Promise<void>;
628
+ handleHttpRpc(req: IncomingMessage, res: ServerResponse$1, forcedMethod?: string): Promise<void>;
424
629
  /**
425
630
  * @param {ConnectionState} connection
426
631
  * @param {ResponseFrame} frame
@@ -441,6 +646,7 @@ declare class Gateway {
441
646
  runs: any[];
442
647
  approvals: {
443
648
  runId: any;
649
+ workflowKey: any;
444
650
  nodeId: any;
445
651
  iteration: any;
446
652
  requestTitle: any;
@@ -465,6 +671,7 @@ declare class Gateway {
465
671
  listRunsAcrossWorkflows(limit?: number, status?: string): Promise<any[]>;
466
672
  listPendingApprovals(): Promise<{
467
673
  runId: any;
674
+ workflowKey: any;
468
675
  nodeId: any;
469
676
  iteration: any;
470
677
  requestTitle: any;
@@ -511,7 +718,9 @@ declare class Gateway {
511
718
  }
512
719
  type EventFrame = EventFrame$1;
513
720
  type GatewayDefaults = GatewayDefaults$1;
721
+ type GatewayRegisterOptions = GatewayRegisterOptions$1;
514
722
  type GatewayTokenGrant = GatewayTokenGrant$1;
723
+ type GatewayUiConfig = GatewayUiConfig$1;
515
724
  type HelloResponse = HelloResponse$1;
516
725
  type GatewayWebhookRunConfig = GatewayWebhookRunConfig$1;
517
726
  type GatewayWebhookSignalConfig = GatewayWebhookSignalConfig$1;
@@ -522,9 +731,9 @@ type GatewayWebhookConfig = GatewayWebhookConfig$1;
522
731
  type IncomingMessage = node_http.IncomingMessage;
523
732
  type RequestFrame = RequestFrame$1;
524
733
  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;
734
+ type ServerResponse$1 = node_http.ServerResponse;
735
+ type SmithersWorkflow = _smithers_orchestrator_components_SmithersWorkflow.SmithersWorkflow<unknown>;
736
+ type SmithersEvent$1 = _smithers_orchestrator_observability_SmithersEvent.SmithersEvent;
528
737
  type GatewayMetricLabels = Record<string, string | number | null | undefined>;
529
738
  type GatewayTransport = "ws" | "http";
530
739
  type GatewayRequestContext = {
@@ -532,6 +741,7 @@ type GatewayRequestContext = {
532
741
  role?: string;
533
742
  scopes?: string[];
534
743
  userId?: string | null;
744
+ tokenId?: string | null;
535
745
  origin?: string;
536
746
  transport?: GatewayTransport;
537
747
  };
@@ -550,6 +760,7 @@ type RunStartAuthContext = {
550
760
  role: string;
551
761
  scopes: string[];
552
762
  userId?: string | null;
763
+ tokenId?: string | null;
553
764
  connectionId?: string;
554
765
  };
555
766
  type RegisteredWorkflow = {
@@ -558,6 +769,7 @@ type RegisteredWorkflow = {
558
769
  key: string;
559
770
  schedule?: string;
560
771
  webhook?: GatewayWebhookConfig;
772
+ ui?: ResolvedGatewayUiConfig | null;
561
773
  };
562
774
  type ResolvedRun = {
563
775
  runId: string;
@@ -565,6 +777,17 @@ type ResolvedRun = {
565
777
  workflow: SmithersWorkflow;
566
778
  adapter: SmithersDb$4;
567
779
  };
780
+ type ResolvedGatewayUiConfig = {
781
+ entry: string;
782
+ path: string;
783
+ title?: string;
784
+ props?: Record<string, unknown>;
785
+ };
786
+ type GatewayUiMount = {
787
+ kind: "gateway" | "workflow";
788
+ workflowKey: string | null;
789
+ config: ResolvedGatewayUiConfig;
790
+ };
568
791
 
569
792
  type ServeOptions$1 = {
570
793
  workflow: SmithersWorkflow$1<unknown>;
@@ -614,7 +837,7 @@ declare class NodeOutputRouteError extends Error {
614
837
  /** @type {NodeOutputErrorCode} */
615
838
  code: NodeOutputErrorCode;
616
839
  }
617
- type NodeOutputErrorCode = _smithers_protocol_errors.NodeOutputErrorCode;
840
+ type NodeOutputErrorCode = _smithers_orchestrator_protocol_errors.NodeOutputErrorCode;
618
841
 
619
842
  /**
620
843
  * @returns {DevToolsNode}
@@ -701,11 +924,11 @@ declare class DevToolsRouteError extends Error {
701
924
  hint: string | undefined;
702
925
  }
703
926
  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;
927
+ type SmithersDb$3 = _smithers_orchestrator_db_adapter.SmithersDb;
928
+ type DevToolsNode = _smithers_orchestrator_protocol_devtools.DevToolsNode;
929
+ type DevToolsSnapshot = _smithers_orchestrator_protocol_devtools.DevToolsSnapshot;
930
+ type DevToolsNodeType = _smithers_orchestrator_protocol_devtools.DevToolsNodeType;
931
+ type SnapshotSerializerWarning$1 = _smithers_orchestrator_devtools_snapshotSerializer.SnapshotSerializerWarning;
709
932
 
710
933
  type DiffSummary$1 = {
711
934
  filesChanged: number;
@@ -752,7 +975,7 @@ type GetNodeDiffRouteResult$1 = {
752
975
  * }} opts
753
976
  * @returns {Promise<GetNodeDiffRouteResult>}
754
977
  */
755
- declare function getNodeDiffRoute({ runId: rawRunId, nodeId: rawNodeId, iteration: rawIteration, resolveRun, emitEffect, computeDiffBundleImpl, computeDiffBundleBetweenRefsImpl, getCurrentPointerImpl, resolveCommitPointerImpl, restorePointerImpl, nowMs, stat, }: {
978
+ declare function getNodeDiffRoute({ runId: rawRunId, nodeId: rawNodeId, iteration: rawIteration, resolveRun, emitEffect, computeDiffBundleImpl, computeDiffBundleBetweenRefsImpl, getCurrentPointerImpl: _getCurrentPointerImpl, resolveCommitPointerImpl, restorePointerImpl: _restorePointerImpl, nowMs, stat, }: {
756
979
  runId: unknown;
757
980
  nodeId: unknown;
758
981
  iteration: unknown;
@@ -760,8 +983,8 @@ declare function getNodeDiffRoute({ runId: rawRunId, nodeId: rawNodeId, iteratio
760
983
  adapter: SmithersDb$2;
761
984
  } | null>;
762
985
  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>;
986
+ computeDiffBundleImpl?: (baseRef: string, cwd: string, seq?: number) => Promise<_smithers_orchestrator_engine_effect_DiffBundle.DiffBundle>;
987
+ computeDiffBundleBetweenRefsImpl?: (baseRef: string, targetRef: string, cwd: string, seq?: number) => Promise<_smithers_orchestrator_engine_effect_DiffBundle.DiffBundle>;
765
988
  getCurrentPointerImpl?: (cwd: string) => Promise<string | null>;
766
989
  resolveCommitPointerImpl?: (pointer: string, cwd: string) => Promise<string | null>;
767
990
  restorePointerImpl?: (pointer: string, cwd: string) => Promise<{
@@ -771,8 +994,8 @@ declare function getNodeDiffRoute({ runId: rawRunId, nodeId: rawNodeId, iteratio
771
994
  nowMs?: () => number;
772
995
  stat?: boolean;
773
996
  }): Promise<GetNodeDiffRouteResult>;
774
- type SmithersDb$2 = _smithers_db_adapter.SmithersDb;
775
- type AttemptRow = _smithers_db_adapter.AttemptRow;
997
+ type SmithersDb$2 = _smithers_orchestrator_db_adapter.SmithersDb;
998
+ type AttemptRow = _smithers_orchestrator_db_adapter.AttemptRow;
776
999
  type GetNodeDiffRouteResult = GetNodeDiffRouteResult$1;
777
1000
  type DiffSummary = DiffSummary$1;
778
1001
 
@@ -832,8 +1055,8 @@ declare function getNodeOutputRoute(params: {
832
1055
  nodeId: unknown;
833
1056
  iteration: unknown;
834
1057
  resolveRun: (runId: string) => Promise<{
835
- workflow: _smithers_components_SmithersWorkflow.SmithersWorkflow<unknown>;
836
- adapter: _smithers_db_adapter.SmithersDb;
1058
+ workflow: _smithers_orchestrator_components_SmithersWorkflow.SmithersWorkflow<unknown>;
1059
+ adapter: _smithers_orchestrator_db_adapter.SmithersDb;
837
1060
  } | null>;
838
1061
  selectOutputRowImpl?: typeof selectOutputRow;
839
1062
  emitEffect?: (effect: Effect.Effect<void>) => Promise<unknown>;
@@ -883,9 +1106,9 @@ declare function jumpToFrameRoute(input: {
883
1106
  onLog?: (level: "info" | "warn" | "error", message: string, fields?: Record<string, unknown>) => Promise<void> | void;
884
1107
  }): Promise<JumpResult>;
885
1108
 
886
- type SmithersDb$1 = _smithers_db_adapter.SmithersDb;
887
- type SmithersEvent = _smithers_observability_SmithersEvent.SmithersEvent;
888
- type JumpResult = _smithers_time_travel_jumpToFrame.JumpResult;
1109
+ type SmithersDb$1 = _smithers_orchestrator_db_adapter.SmithersDb;
1110
+ type SmithersEvent = _smithers_orchestrator_observability_SmithersEvent.SmithersEvent;
1111
+ type JumpResult = _smithers_orchestrator_time_travel_jumpToFrame.JumpResult;
889
1112
 
890
1113
  /**
891
1114
  * @param {{
@@ -934,20 +1157,21 @@ declare function streamDevToolsRoute(input: {
934
1157
  declare const DEVTOOLS_REBASELINE_INTERVAL: 50;
935
1158
  declare const DEVTOOLS_BACKPRESSURE_LIMIT: 1000;
936
1159
  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;
1160
+ type SmithersDb = _smithers_orchestrator_db_adapter.SmithersDb;
1161
+ type DevToolsEvent = _smithers_orchestrator_protocol_devtools.DevToolsEvent;
1162
+ type SnapshotSerializerWarning = _smithers_orchestrator_devtools_snapshotSerializer.SnapshotSerializerWarning;
940
1163
 
941
1164
  /**
942
1165
  * @param {ServerOptions} [opts]
943
1166
  */
944
- declare function startServerEffect(opts?: ServerOptions): Effect.Effect<node_http.Server<typeof IncomingMessage$1, typeof ServerResponse$1>, never, never>;
1167
+ declare function startServerEffect(opts?: ServerOptions): Effect.Effect<node_http.Server<typeof node_http.IncomingMessage, typeof node_http.ServerResponse>, never, never>;
945
1168
  /**
946
1169
  * @param {ServerOptions} [opts]
947
1170
  */
948
- declare function startServer(opts?: ServerOptions): node_http.Server<typeof IncomingMessage$1, typeof ServerResponse$1>;
1171
+ declare function startServer(opts?: ServerOptions): node_http.Server<typeof node_http.IncomingMessage, typeof node_http.ServerResponse>;
949
1172
 
950
- type RunRow = _smithers_db_adapter_RunRow.RunRow;
1173
+ type RunRow = _smithers_orchestrator_db_adapter_RunRow.RunRow;
1174
+ type ServerResponse = node_http.ServerResponse;
951
1175
  type ServerOptions = ServerOptions$1;
952
1176
 
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 };
1177
+ 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 };