import path from "node:path"; import { createCloudWorkbenchApplication } from "./cloud-application.ts"; import { createWorkbenchDispatcher } from "./dispatcher.ts"; import { createNativeTestWorkbenchApplication } from "./native-application.ts"; import { createNativeAgentRunWorkbenchApplication } from "./native-agentrun-application.ts"; import { createNativeTestTemporalGateway } from "./native-temporal.ts"; import { createWorkbenchTemporalGateway } from "./temporal.ts"; import { createWorkbenchKafkaEventPublisher } from "./kafka-event-publisher.ts"; import type { WorkbenchEventPublisher } from "./contracts.ts"; export function workbenchRuntime(env: Record = process.env, options: { eventPublisher?: WorkbenchEventPublisher | null } = {}) { const mode = String(env.WORKBENCH_MODE ?? "").trim(); if (mode !== "native-test" && mode !== "agentrun-native" && mode !== "temporal") throw codedError("workbench_mode_required", "WORKBENCH_MODE must be native-test, agentrun-native, or temporal"); const stateFile = path.resolve(String(env.WORKBENCH_NATIVE_STATE_FILE ?? ".state/workbench-native/state.json")); const eventPublisher = mode === "native-test" ? null : Object.hasOwn(options, "eventPublisher") ? options.eventPublisher ?? null : createWorkbenchKafkaEventPublisher({ env }); const application = mode === "native-test" ? createNativeTestWorkbenchApplication({ stateFile }) : mode === "agentrun-native" ? createNativeAgentRunWorkbenchApplication({ stateFile, env, eventPublisher }) : createCloudWorkbenchApplication({ baseUrl: String(env.WORKBENCH_CLOUD_API_URL ?? ""), authorization: String(env.WORKBENCH_CLOUD_API_AUTHORIZATION ?? ""), eventPublisher }); const temporalAddress = String(env.WORKBENCH_TEMPORAL_ADDRESS ?? env.TEMPORAL_ADDRESS ?? ""); const temporalNamespace = String(env.WORKBENCH_TEMPORAL_NAMESPACE ?? "unidesk"); const taskQueue = String(env.WORKBENCH_TEMPORAL_TASK_QUEUE ?? "hwlab-v03-workbench"); const activity = mode === "native-test" ? null : { startToCloseTimeoutMs: requiredPositiveInteger(env.WORKBENCH_ACTIVITY_START_TO_CLOSE_TIMEOUT_MS, "WORKBENCH_ACTIVITY_START_TO_CLOSE_TIMEOUT_MS"), retryInitialIntervalMs: requiredPositiveInteger(env.WORKBENCH_ACTIVITY_RETRY_INITIAL_INTERVAL_MS, "WORKBENCH_ACTIVITY_RETRY_INITIAL_INTERVAL_MS"), retryMaximumIntervalMs: requiredPositiveInteger(env.WORKBENCH_ACTIVITY_RETRY_MAXIMUM_INTERVAL_MS, "WORKBENCH_ACTIVITY_RETRY_MAXIMUM_INTERVAL_MS"), retryMaximumAttempts: requiredPositiveInteger(env.WORKBENCH_ACTIVITY_RETRY_MAXIMUM_ATTEMPTS, "WORKBENCH_ACTIVITY_RETRY_MAXIMUM_ATTEMPTS") }; const temporal = mode === "native-test" ? createNativeTestTemporalGateway(application) : createWorkbenchTemporalGateway({ address: temporalAddress, namespace: temporalNamespace, taskQueue, activity: activity! }); return { mode, stateFile, temporalAddress, temporalNamespace, taskQueue, activity, eventPublisher, application, temporal, dispatch: createWorkbenchDispatcher({ application, temporal, mode }), async close() { await temporal.close(); await application.close?.(); } }; } function codedError(code: string, message: string) { return Object.assign(new Error(message), { code }); } function requiredPositiveInteger(value: string | undefined, name: string) { const parsed = Number.parseInt(String(value ?? ""), 10); if (!Number.isSafeInteger(parsed) || parsed <= 0) throw codedError("workbench_temporal_config_invalid", `${name} must be a positive integer`); return parsed; }