@tonyclaw/agent-inspector 3.0.46 → 3.0.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.output/backend/nitro.json +1 -1
  2. package/.output/cli.js +6791 -6006
  3. package/.output/server/_ssr/index.mjs +1 -1
  4. package/.output/server/_ssr/{router-D18yUq36.mjs → router-Cj10mv-Z.mjs} +334 -5
  5. package/.output/server/index.mjs +1 -1
  6. package/.output/ui/assets/{CompareDrawer-DAUuIJ6G.js → CompareDrawer-uRiXFOCp.js} +1 -1
  7. package/.output/ui/assets/{InspectorPet-BRBVjOWI.js → InspectorPet-j-r6xk0V.js} +1 -1
  8. package/.output/ui/assets/{ProxyViewerContainer-D7Sq0ctc.js → ProxyViewerContainer-DnUrkMJF.js} +23 -23
  9. package/.output/ui/assets/{ReplayDialog-nDLsjOhs.js → ReplayDialog-B2URyHMv.js} +1 -1
  10. package/.output/ui/assets/{RequestAnatomy-zd4BDgL0.js → RequestAnatomy-CK0M0CJO.js} +1 -1
  11. package/.output/ui/assets/{ResponseView-AdIXGcSn.js → ResponseView-CYSVA4lT.js} +1 -1
  12. package/.output/ui/assets/{StreamingChunkSequence-CesTz8He.js → StreamingChunkSequence-CHf54ZUV.js} +1 -1
  13. package/.output/ui/assets/{_sessionId-QnBomgPD.js → _sessionId-BXULpTNP.js} +1 -1
  14. package/.output/ui/assets/{_sessionId-BAfzrhSU.js → _sessionId-E4lbj5-I.js} +1 -1
  15. package/.output/ui/assets/{index-D0rtCN9V.js → index-B4ixI3kG.js} +1 -1
  16. package/.output/ui/assets/{index-BcsdxBAd.js → index-BJ-71WLq.js} +1 -1
  17. package/.output/ui/assets/{index-DHpwr08Z.js → index-CW0ab9r3.js} +2 -2
  18. package/.output/ui/assets/{index-dNuk2dsU.js → index-Ce-lO51c.js} +1 -1
  19. package/.output/ui/assets/{json-viewer-BvFHglMb.js → json-viewer-C6E8aLaV.js} +1 -1
  20. package/.output/ui/assets/{jszip.min-B5Z7gNZB.js → jszip.min-DjWdTBbp.js} +1 -1
  21. package/.output/ui/index.html +1 -1
  22. package/README.md +53 -0
  23. package/package.json +17 -3
  24. package/src/backend/routes/api/-instances.ts +7 -109
  25. package/src/cli/instance.ts +21 -0
  26. package/src/cli/instanceApi.ts +213 -0
  27. package/src/cli/instanceArgs.ts +16 -0
  28. package/src/cli/instanceControl.ts +10 -0
  29. package/src/cli/instanceModel.ts +9 -0
  30. package/src/cli/rustBackendPackage.ts +458 -0
  31. package/src/cli.ts +171 -14
  32. package/src/components/ProxyViewer.tsx +4 -13
  33. package/src/components/proxy-viewer/proxyViewerLogic.ts +9 -0
  34. package/src/lib/backendImplementation.ts +16 -0
  35. package/src/lib/instanceContract.ts +3 -0
  36. package/src/lib/managedInstance.ts +9 -1
  37. package/src/mcp/instanceHandlers.ts +1 -0
  38. package/src/proxy/identityProxy.ts +148 -0
  39. package/src/proxy/sessionArchive.ts +0 -8
  40. package/src/proxy/sqliteLogIndex.ts +0 -40
@@ -1,11 +1,13 @@
1
1
  import { spawn, type ChildProcess } from "node:child_process";
2
2
  import { resolve as resolvePath } from "node:path";
3
+ import packageJson from "../../package.json";
3
4
  import {
4
5
  AGENT_INSPECTOR_BASE_PATH_ENV,
5
6
  appendBasePathToOrigin,
6
7
  getConfiguredBasePath,
7
8
  normalizePublicBasePath,
8
9
  } from "../lib/basePath";
10
+ import type { BackendImplementation } from "../lib/backendImplementation";
9
11
  import {
10
12
  type InstanceConnectionOutput,
11
13
  type InstanceOperationOutcome,
@@ -50,6 +52,7 @@ import {
50
52
  type PortAvailabilityCheck,
51
53
  } from "./instanceRegistry";
52
54
  import { probeHostForBindHost, urlForHost } from "./networkHints";
55
+ import { resolvePreferredBackendImplementation } from "./rustBackendPackage";
53
56
 
54
57
  const DEFAULT_INSTANCE_HOST = "127.0.0.1";
55
58
  const DEFAULT_INSTANCE_CAPTURE_MODE = "simple";
@@ -101,6 +104,7 @@ export type InstanceLifecycleDependencies = {
101
104
  isPortAvailable?: PortAvailabilityCheck;
102
105
  launch?: (record: InstanceRecord, open: boolean) => Promise<boolean>;
103
106
  openUrl?: (url: string) => void;
107
+ resolveDefaultBackend?: () => BackendImplementation;
104
108
  sleep?: (milliseconds: number) => Promise<void>;
105
109
  readyTimeoutMs?: number;
106
110
  stopTimeoutMs?: number;
@@ -171,6 +175,7 @@ function statusOutput(
171
175
  upstreamPort: record.upstreamPort,
172
176
  basePath: displayBasePath(record.basePath),
173
177
  captureMode: record.captureMode,
178
+ backend: record.backend,
174
179
  uiEnabled: record.uiEnabled,
175
180
  dataDir: record.dataDir,
176
181
  supervisorPid: live.supervisorPid,
@@ -228,6 +233,7 @@ function connectionOutput(instance: InstanceStatusOutput): InstanceConnectionOut
228
233
 
229
234
  function expectedIdentity(record: InstanceRecord): ExpectedInstanceControlIdentity {
230
235
  return {
236
+ backend: record.backend,
231
237
  instanceId: record.instanceId,
232
238
  launchId: record.launch.launchId,
233
239
  controlToken: record.launch.controlToken,
@@ -285,6 +291,8 @@ export function buildInstanceLaunchArgs(record: InstanceRecord, open: boolean):
285
291
  const args = [
286
292
  "--background",
287
293
  "--no-open",
294
+ "--backend",
295
+ record.backend,
288
296
  "--port",
289
297
  String(record.publicPort),
290
298
  "--host",
@@ -310,6 +318,7 @@ async function defaultLaunch(
310
318
  [AGENT_INSPECTOR_CLI_ENTRY_ENV]: cliEntry,
311
319
  AGENT_INSPECTOR_DATA_DIR: record.dataDir,
312
320
  [AGENT_INSPECTOR_BASE_PATH_ENV]: displayBasePath(record.basePath),
321
+ [MANAGED_INSTANCE_ENV.backend]: record.backend,
313
322
  [MANAGED_INSTANCE_ENV.name]: record.name,
314
323
  [MANAGED_INSTANCE_ENV.instanceId]: record.instanceId,
315
324
  [MANAGED_INSTANCE_ENV.launchId]: record.launch.launchId,
@@ -475,6 +484,7 @@ function overridesDiffer(
475
484
  action: Extract<InstanceAction, { kind: "start" | "restart" }>,
476
485
  ): boolean {
477
486
  const { overrides } = action;
487
+ if (overrides.backend !== null && overrides.backend !== record.backend) return true;
478
488
  if (overrides.port !== null && overrides.port !== record.publicPort) return true;
479
489
  if (overrides.basePath !== null && overrides.basePath !== record.basePath) return true;
480
490
  if (overrides.host !== null && overrides.host !== record.host) return true;
@@ -507,6 +517,7 @@ function updatedRecord(
507
517
  upstreamPort: pair.upstreamPort,
508
518
  basePath: action.overrides.basePath ?? record.basePath,
509
519
  captureMode: action.overrides.captureMode ?? record.captureMode,
520
+ backend: action.overrides.backend ?? record.backend,
510
521
  uiEnabled: action.overrides.uiEnabled ?? record.uiEnabled,
511
522
  dataDir,
512
523
  };
@@ -542,6 +553,13 @@ function validateInstanceDataDirectory(
542
553
  return success(assigned.value.path);
543
554
  }
544
555
 
556
+ function resolveDefaultInstanceBackend(deps: InstanceLifecycleDependencies): BackendImplementation {
557
+ return (
558
+ deps.resolveDefaultBackend?.() ??
559
+ resolvePreferredBackendImplementation({ rootVersion: packageJson.version })
560
+ );
561
+ }
562
+
545
563
  async function createNewRecord(
546
564
  root: string,
547
565
  action: Extract<InstanceAction, { kind: "start" }>,
@@ -569,6 +587,7 @@ async function createNewRecord(
569
587
  upstreamPort: pairResult.value.upstreamPort,
570
588
  basePath: action.overrides.basePath ?? getConfiguredBasePath(process.env),
571
589
  captureMode: action.overrides.captureMode ?? DEFAULT_INSTANCE_CAPTURE_MODE,
590
+ backend: action.overrides.backend ?? resolveDefaultInstanceBackend(deps),
572
591
  uiEnabled: action.overrides.uiEnabled ?? true,
573
592
  dataDir: dataDirResult.value,
574
593
  });
@@ -808,6 +827,7 @@ function requestActionOverrides(
808
827
  input: InstanceStartRequest,
809
828
  ): Extract<InstanceAction, { kind: "start" }>["overrides"] {
810
829
  return {
830
+ backend: input.backend ?? null,
811
831
  port: input.port ?? null,
812
832
  basePath: input.basePath === undefined ? null : normalizePublicBasePath(input.basePath),
813
833
  host: input.host ?? null,
@@ -901,6 +921,7 @@ function formatStatus(output: InstanceStatusOutput): string {
901
921
  Port pair: ${String(output.publicPort)} / ${String(output.upstreamPort)} (private)
902
922
  Base Path: ${output.basePath}
903
923
  Capture mode: ${output.captureMode}
924
+ Backend: ${output.backend}
904
925
  Data directory: ${output.dataDir}
905
926
  Supervisor PID: ${pid}
906
927
  State reason: ${reason}
@@ -0,0 +1,213 @@
1
+ import { type z } from "zod";
2
+ import {
3
+ getNamedInstance,
4
+ getNamedInstanceConnection,
5
+ type InstanceLifecycleError,
6
+ type InstanceLifecycleResult,
7
+ listNamedInstances,
8
+ restartNamedInstance,
9
+ startNamedInstance,
10
+ stopNamedInstance,
11
+ } from "./instance";
12
+ import {
13
+ type InstancePublicErrorCode,
14
+ INSTANCE_API_SCHEMA_VERSION,
15
+ InstanceEmptyRequestSchema,
16
+ InstanceStartRequestSchema,
17
+ instanceErrorOutput,
18
+ } from "../lib/instanceContract";
19
+ import { readManagedInstanceLaunch } from "../lib/managedInstance";
20
+
21
+ const NO_STORE_HEADERS = { "cache-control": "no-store" };
22
+
23
+ type ParsedBody<T> = { ok: true; value: T } | { ok: false; response: Response };
24
+
25
+ function errorStatus(code: InstancePublicErrorCode): number {
26
+ switch (code) {
27
+ case "argument_invalid":
28
+ case "data_dir_invalid":
29
+ case "name_invalid":
30
+ case "port_invalid":
31
+ case "record_invalid":
32
+ return 400;
33
+ case "instance_not_found":
34
+ return 404;
35
+ case "config_mismatch":
36
+ case "data_dir_conflict":
37
+ case "identity_mismatch":
38
+ case "port_conflict":
39
+ case "self_lifecycle_unsupported":
40
+ return 409;
41
+ case "lock_not_owned":
42
+ case "lock_timeout":
43
+ return 423;
44
+ case "launch_failed":
45
+ case "launch_unavailable":
46
+ case "port_exhausted":
47
+ case "registry_io":
48
+ case "restart_failed_degraded":
49
+ case "restart_failed_rolled_back":
50
+ case "shutdown_failed":
51
+ return 503;
52
+ }
53
+ }
54
+
55
+ export function instanceErrorResponse(error: InstanceLifecycleError): Response {
56
+ return Response.json(instanceErrorOutput(error), {
57
+ status: errorStatus(error.code),
58
+ headers: NO_STORE_HEADERS,
59
+ });
60
+ }
61
+
62
+ export function instanceResultResponse<T>(
63
+ result: InstanceLifecycleResult<T>,
64
+ successStatus = 200,
65
+ ): Response {
66
+ return result.kind === "error"
67
+ ? instanceErrorResponse(result.error)
68
+ : Response.json(result.value, { status: successStatus, headers: NO_STORE_HEADERS });
69
+ }
70
+
71
+ export async function parseInstanceBody<T extends z.ZodType>(
72
+ request: Request,
73
+ schema: T,
74
+ ): Promise<ParsedBody<z.output<T>>> {
75
+ let value: unknown;
76
+ try {
77
+ const text = await request.text();
78
+ value = text.trim().length === 0 ? {} : JSON.parse(text);
79
+ } catch {
80
+ return {
81
+ ok: false,
82
+ response: instanceErrorResponse({
83
+ code: "argument_invalid",
84
+ message: "Request body must be valid JSON.",
85
+ retryable: false,
86
+ }),
87
+ };
88
+ }
89
+ const parsed = schema.safeParse(value);
90
+ if (!parsed.success) {
91
+ return {
92
+ ok: false,
93
+ response: instanceErrorResponse({
94
+ code: "argument_invalid",
95
+ message: `Invalid instance request: ${parsed.error.issues[0]?.message ?? "invalid body"}`,
96
+ retryable: false,
97
+ }),
98
+ };
99
+ }
100
+ return { ok: true, value: parsed.data };
101
+ }
102
+
103
+ export function selfLifecycleError(rawName: string): InstanceLifecycleError | null {
104
+ const current = readManagedInstanceLaunch(process.env);
105
+ return current.kind === "managed" && current.launch.name.toLowerCase() === rawName.toLowerCase()
106
+ ? {
107
+ code: "self_lifecycle_unsupported",
108
+ message:
109
+ "A managed runtime cannot stop or restart itself through its own REST/MCP transport; use the CLI or a different controller instance.",
110
+ retryable: false,
111
+ }
112
+ : null;
113
+ }
114
+
115
+ export function instanceListResponse(instances: readonly unknown[]): Response {
116
+ return Response.json(
117
+ { schemaVersion: INSTANCE_API_SCHEMA_VERSION, instances },
118
+ { headers: NO_STORE_HEADERS },
119
+ );
120
+ }
121
+
122
+ function methodNotAllowed(allow: string): Response {
123
+ return Response.json(
124
+ { error: "Method not allowed" },
125
+ { status: 405, headers: { ...NO_STORE_HEADERS, allow } },
126
+ );
127
+ }
128
+
129
+ function endpointNotFound(): Response {
130
+ return Response.json(
131
+ { error: "Instance endpoint not found" },
132
+ { status: 404, headers: NO_STORE_HEADERS },
133
+ );
134
+ }
135
+
136
+ function decodedName(value: string): string | null {
137
+ try {
138
+ return decodeURIComponent(value);
139
+ } catch {
140
+ return null;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Framework-neutral named-instance API owned by the Node supervisor plane.
146
+ *
147
+ * The protected ingress uses this when a Rust backend is selected, keeping process lifecycle and
148
+ * OS-specific background behavior in the existing CLI instead of reimplementing it in Rust.
149
+ */
150
+ export async function handleNamedInstanceApi(request: Request): Promise<Response> {
151
+ const url = new URL(request.url);
152
+ const segments = url.pathname.split("/").filter((segment) => segment.length > 0);
153
+ if (segments.length === 2 && segments[0] === "api" && segments[1] === "instances") {
154
+ if (request.method !== "GET") return methodNotAllowed("GET");
155
+ const result = await listNamedInstances();
156
+ return result.kind === "error"
157
+ ? instanceResultResponse(result)
158
+ : instanceListResponse(result.value);
159
+ }
160
+ if (segments.length < 3 || segments[0] !== "api" || segments[1] !== "instances") {
161
+ return endpointNotFound();
162
+ }
163
+ const name = decodedName(segments[2] ?? "");
164
+ if (name === null) {
165
+ return instanceErrorResponse({
166
+ code: "name_invalid",
167
+ message: "Instance name is not valid URL encoding.",
168
+ retryable: false,
169
+ });
170
+ }
171
+ if (segments.length === 3) {
172
+ return request.method === "GET"
173
+ ? instanceResultResponse(await getNamedInstance(name))
174
+ : methodNotAllowed("GET");
175
+ }
176
+ if (segments.length !== 4) return endpointNotFound();
177
+ const action = segments[3];
178
+ if (action === undefined) return endpointNotFound();
179
+ switch (action) {
180
+ case "connection":
181
+ return request.method === "GET"
182
+ ? instanceResultResponse(await getNamedInstanceConnection(name))
183
+ : methodNotAllowed("GET");
184
+ case "start": {
185
+ if (request.method !== "POST") return methodNotAllowed("POST");
186
+ const body = await parseInstanceBody(request, InstanceStartRequestSchema);
187
+ if (!body.ok) return body.response;
188
+ const result = await startNamedInstance(name, body.value);
189
+ const status = result.kind === "success" && result.value.outcome === "created" ? 201 : 200;
190
+ return instanceResultResponse(result, status);
191
+ }
192
+ case "stop": {
193
+ if (request.method !== "POST") return methodNotAllowed("POST");
194
+ const body = await parseInstanceBody(request, InstanceEmptyRequestSchema);
195
+ if (!body.ok) return body.response;
196
+ const selfError = selfLifecycleError(name);
197
+ return selfError === null
198
+ ? instanceResultResponse(await stopNamedInstance(name))
199
+ : instanceErrorResponse(selfError);
200
+ }
201
+ case "restart": {
202
+ if (request.method !== "POST") return methodNotAllowed("POST");
203
+ const body = await parseInstanceBody(request, InstanceStartRequestSchema);
204
+ if (!body.ok) return body.response;
205
+ const selfError = selfLifecycleError(name);
206
+ return selfError === null
207
+ ? instanceResultResponse(await restartNamedInstance(name, body.value))
208
+ : instanceErrorResponse(selfError);
209
+ }
210
+ default:
211
+ return endpointNotFound();
212
+ }
213
+ }
@@ -1,9 +1,14 @@
1
1
  import { normalizePublicBasePath } from "../lib/basePath";
2
+ import {
3
+ BackendImplementationSchema,
4
+ type BackendImplementation,
5
+ } from "../lib/backendImplementation";
2
6
  import { isValidBindHost } from "./networkHints";
3
7
 
4
8
  export type InstanceCaptureMode = "simple" | "full";
5
9
 
6
10
  export type InstanceStartOverrides = {
11
+ backend: BackendImplementation | null;
7
12
  port: number | null;
8
13
  basePath: string | null;
9
14
  host: string | null;
@@ -49,6 +54,7 @@ function optionWithEquals(arg: string): { name: string; value: string } | null {
49
54
 
50
55
  function emptyOverrides(): InstanceStartOverrides {
51
56
  return {
57
+ backend: null,
52
58
  port: null,
53
59
  basePath: null,
54
60
  host: null,
@@ -72,6 +78,12 @@ function setValueOption(
72
78
  value: string | undefined,
73
79
  ): string | null {
74
80
  switch (name) {
81
+ case "--backend": {
82
+ const backend = BackendImplementationSchema.safeParse(value?.trim().toLowerCase());
83
+ if (!backend.success) return "instance: --backend must be typescript or rust";
84
+ options.overrides.backend = backend.data;
85
+ return null;
86
+ }
75
87
  case "--port":
76
88
  case "-p": {
77
89
  const port = parsePort(value);
@@ -162,6 +174,7 @@ export function parseInstanceArgs(argv: readonly string[]): InstanceArgsResult {
162
174
  break;
163
175
  case "--port":
164
176
  case "-p":
177
+ case "--backend":
165
178
  case "--base-path":
166
179
  case "--host":
167
180
  case "-H":
@@ -188,6 +201,7 @@ export function parseInstanceArgs(argv: readonly string[]): InstanceArgsResult {
188
201
  return { kind: "error", message: "instance: list does not accept an instance name" };
189
202
  }
190
203
  if (
204
+ options.overrides.backend !== null ||
191
205
  options.overrides.port !== null ||
192
206
  options.overrides.basePath !== null ||
193
207
  options.overrides.host !== null ||
@@ -211,6 +225,7 @@ export function parseInstanceArgs(argv: readonly string[]): InstanceArgsResult {
211
225
  return { kind: "error", message: `instance: ${command} requires exactly one instance name` };
212
226
  }
213
227
  if (
228
+ options.overrides.backend !== null ||
214
229
  options.overrides.port !== null ||
215
230
  options.overrides.basePath !== null ||
216
231
  options.overrides.host !== null ||
@@ -263,6 +278,7 @@ Usage:
263
278
  agent-inspector instance restart <name> [options]
264
279
 
265
280
  Start/restart options:
281
+ --backend <typescript|rust> Persist the backend implementation (default: rust when native package is available)
266
282
  --port, -p <port> Public ingress port (auto-allocated on first start)
267
283
  --base-path <path> Public Base Path (default: /inspector; use / for root)
268
284
  --host, -H <host> Bind host (default: 127.0.0.1)
@@ -3,12 +3,17 @@ import {
3
3
  MANAGED_INSTANCE_SHUTDOWN_PATH,
4
4
  MANAGED_INSTANCE_TOKEN_HEADER,
5
5
  } from "../lib/managedInstance";
6
+ import {
7
+ BackendImplementationSchema,
8
+ type BackendImplementation,
9
+ } from "../lib/backendImplementation";
6
10
 
7
11
  const DEFAULT_CONTROL_TIMEOUT_MS = 2_000;
8
12
 
9
13
  export type InstanceControlFetch = (input: string, init?: RequestInit) => Promise<Response>;
10
14
 
11
15
  export type ExpectedInstanceControlIdentity = {
16
+ backend?: BackendImplementation;
12
17
  instanceId: string;
13
18
  launchId: string;
14
19
  controlToken: string;
@@ -18,6 +23,7 @@ export type ExpectedInstanceControlIdentity = {
18
23
 
19
24
  export type InstanceControlIdentity = {
20
25
  schemaVersion: 1;
26
+ backend: BackendImplementation;
21
27
  instanceId: string;
22
28
  launchId: string;
23
29
  supervisorPid: number;
@@ -45,6 +51,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
45
51
  function parseIdentity(value: unknown): InstanceControlIdentity | null {
46
52
  if (!isRecord(value)) return null;
47
53
  const schemaVersion = value["schemaVersion"];
54
+ const backend = BackendImplementationSchema.safeParse(value["backend"] ?? "typescript");
48
55
  const instanceId = value["instanceId"];
49
56
  const launchId = value["launchId"];
50
57
  const supervisorPid = value["supervisorPid"];
@@ -53,6 +60,7 @@ function parseIdentity(value: unknown): InstanceControlIdentity | null {
53
60
  const upstreamPort = value["upstreamPort"];
54
61
  if (
55
62
  schemaVersion !== 1 ||
63
+ !backend.success ||
56
64
  typeof instanceId !== "string" ||
57
65
  instanceId.length === 0 ||
58
66
  typeof launchId !== "string" ||
@@ -75,6 +83,7 @@ function parseIdentity(value: unknown): InstanceControlIdentity | null {
75
83
  }
76
84
  return {
77
85
  schemaVersion: 1,
86
+ backend: backend.data,
78
87
  instanceId,
79
88
  launchId,
80
89
  supervisorPid,
@@ -136,6 +145,7 @@ export async function probeInstanceControl(
136
145
  if (
137
146
  identity.instanceId !== expected.instanceId ||
138
147
  identity.launchId !== expected.launchId ||
148
+ identity.backend !== (expected.backend ?? "typescript") ||
139
149
  identity.publicPort !== expected.publicPort
140
150
  ) {
141
151
  return { kind: "identity-mismatch" };
@@ -1,6 +1,10 @@
1
1
  import { randomBytes, randomUUID } from "node:crypto";
2
2
  import { isAbsolute } from "node:path";
3
3
  import { z } from "zod";
4
+ import {
5
+ BackendImplementationSchema,
6
+ type BackendImplementation,
7
+ } from "../lib/backendImplementation";
4
8
  import { normalizePublicBasePath } from "../lib/basePath";
5
9
  import { MANAGED_INSTANCE_CONTROL_PATH } from "../lib/managedInstance";
6
10
  import { CaptureModeSchema, type CaptureMode } from "../lib/runtimeConfig";
@@ -124,6 +128,7 @@ export const InstanceRecordSchema = z
124
128
  upstreamPort: PortSchema,
125
129
  basePath: NormalizedBasePathSchema,
126
130
  captureMode: CaptureModeSchema,
131
+ backend: BackendImplementationSchema.default("typescript"),
127
132
  uiEnabled: z.boolean().default(true),
128
133
  dataDir: z
129
134
  .string()
@@ -156,6 +161,7 @@ export type InstanceRecordOutput = {
156
161
  upstreamPort: number;
157
162
  basePath: string;
158
163
  captureMode: CaptureMode;
164
+ backend: BackendImplementation;
159
165
  uiEnabled: boolean;
160
166
  dataDir: string;
161
167
  launch: {
@@ -171,6 +177,7 @@ export type CreateInstanceRecordInput = {
171
177
  upstreamPort: number;
172
178
  basePath?: string;
173
179
  captureMode: CaptureMode;
180
+ backend?: BackendImplementation;
174
181
  uiEnabled?: boolean;
175
182
  dataDir: string;
176
183
  };
@@ -212,6 +219,7 @@ export function createInstanceRecord(
212
219
  upstreamPort: input.upstreamPort,
213
220
  basePath: basePath.value,
214
221
  captureMode: input.captureMode,
222
+ backend: input.backend ?? "typescript",
215
223
  uiEnabled: input.uiEnabled ?? true,
216
224
  dataDir: input.dataDir,
217
225
  launch: createLaunch(factory, now),
@@ -257,6 +265,7 @@ export function toInstanceRecordOutput(record: InstanceRecord): InstanceRecordOu
257
265
  upstreamPort: record.upstreamPort,
258
266
  basePath: record.basePath,
259
267
  captureMode: record.captureMode,
268
+ backend: record.backend,
260
269
  uiEnabled: record.uiEnabled,
261
270
  dataDir: record.dataDir,
262
271
  launch: {