life
Version:
Life.js is the first fullstack framework to build agentic web applications. It is minimal, extensible, and typesafe. Well, everything you love.
1 lines • 328 kB
Source Map (JSON)
{"version":3,"sources":["../cli/run.ts","../telemetry/helpers/formatting/terminal.ts","../cli/utils/theme.ts","../cli/commands/build/index.ts","../cli/utils/header.ts","../cli/utils/version.ts","../compiler/index.ts","../compiler/helpers/dependencies-map.ts","../compiler/options.ts","../cli/commands/build/action.ts","../cli/commands/dev/index.ts","../cli/commands/dev/action.ts","../cli/commands/dev/ui/index.tsx","../cli/commands/dev/components/conditional-mouse-provider.tsx","../cli/commands/dev/components/divider.tsx","../cli/commands/dev/components/fullscreen-box.tsx","../cli/commands/dev/hooks/use-screen-size.ts","../cli/commands/dev/components/link.tsx","../cli/commands/dev/lib/inkui-theme.ts","../cli/commands/dev/lib/tabs.ts","../cli/commands/dev/tasks/exit.ts","../cli/commands/dev/tasks/init.ts","../server/index.ts","../transport/providers/livekit/auth.ts","../transport/auth.ts","../server/agent-process/client.ts","../server/api/index.ts","../server/api/definition.ts","../server/api/handlers.ts","../server/api/types.ts","../server/options.ts","../cli/commands/dev/lib/check-livekit-install.ts","../cli/commands/dev/lib/clean-std-data.ts","../cli/commands/dev/ui/content.tsx","../cli/commands/dev/components/scroll-text-box.tsx","../cli/commands/dev/lib/filter-tab-logs.ts","../cli/commands/dev/ui/footer.tsx","../cli/commands/dev/ui/loader.tsx","../cli/commands/dev/ui/sidebar.tsx","../cli/commands/init/index.ts","../cli/commands/init/action.ts","../cli/commands/start/index.ts","../cli/commands/start/action.ts","../cli/utils/help-formatter.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { TelemetryClient } from \"@/telemetry/clients/base\";\nimport { createTelemetryClient } from \"@/telemetry/clients/node\";\nimport { formatLogForTerminal } from \"@/telemetry/helpers/formatting/terminal\";\nimport { pipeConsoleToTelemetryClient } from \"@/telemetry/helpers/patch-console\";\nimport type { TelemetryLog, TelemetryLogLevel } from \"@/telemetry/types\";\nimport { createBuildCommand } from \"./commands/build\";\nimport { createDevCommand } from \"./commands/dev\";\nimport { createInitCommand } from \"./commands/init\";\nimport { createStartCommand } from \"./commands/start\";\nimport { applyHelpFormatting } from \"./utils/help-formatter\";\nimport { formatVersion, getVersion } from \"./utils/version\";\n\nasync function main() {\n // Capture initial telemetry logs, and expose a callback to listen for new logs\n let logLevel = process.argv.includes(\"--debug\") ? \"debug\" : undefined;\n logLevel = logLevel ?? (process.env.LOG_LEVEL as TelemetryLogLevel) ?? \"info\";\n const logs: TelemetryLog[] = [];\n const logsListeners: ((log: TelemetryLog) => void)[] = [];\n const onTelemetryLog = (callback: (log: TelemetryLog) => void) => {\n logsListeners.push(callback);\n return () => {\n logsListeners.splice(logsListeners.indexOf(callback), 1);\n };\n };\n TelemetryClient.registerGlobalConsumer({\n async start(queue) {\n for await (const item of queue) {\n if (item.type !== \"log\") continue;\n try {\n logs.push(item);\n for (const listener of logsListeners) listener(item);\n } catch {\n console.error(\"Failed to forward telemetry log to listeners.\");\n }\n }\n },\n });\n\n // Stream formatted telemetry logs to the terminal (except for dev command without --no-tui flag)\n const originalConsoleLog = console.log;\n const isDevCommand = process.argv.includes(\"dev\") && !process.argv.includes(\"--no-tui\");\n if (!isDevCommand) {\n TelemetryClient.registerGlobalConsumer({\n start: async (queue) => {\n for await (const item of queue) {\n if (item.type !== \"log\") continue;\n originalConsoleLog(formatLogForTerminal(item));\n }\n },\n });\n }\n\n // Create the CLI telemety client\n const cliTelemetry = createTelemetryClient(\"cli\", {\n command: process.argv.at(2) ?? \"unknown\",\n args: process.argv.slice(3) ?? [],\n });\n\n // Forward console.* methods to the CLI telemetry client\n pipeConsoleToTelemetryClient(cliTelemetry);\n\n // Set up cleanup function to run before process exits\n let cleanupDone = false;\n const cleanup = async () => {\n if (cleanupDone) return;\n cleanupDone = true;\n await TelemetryClient.flushAllConsumers();\n originalConsoleLog(\"\"); // Newline for better readability\n };\n process.on(\"SIGINT\", () => setImmediate(cleanup));\n process.on(\"SIGTERM\", () => setImmediate(cleanup));\n process.on(\"beforeExit\", cleanup);\n process.on(\"exit\", cleanup);\n\n // Initialize program\n const program = new Command();\n const version = await getVersion();\n\n // Configure the main program\n program\n .name(\"life\")\n .version(formatVersion(version).output, \"-v, --version\", \"Display version number.\")\n .helpOption(\"-h, --help\", \"Display help for command.\");\n\n // Register commands\n const commands = [\n createDevCommand(cliTelemetry, logs, onTelemetryLog),\n createBuildCommand(cliTelemetry),\n createStartCommand(cliTelemetry),\n createInitCommand(cliTelemetry),\n ];\n\n // Apply formatting to commands\n await Promise.all([\n applyHelpFormatting(program, true),\n ...commands.map((c) => applyHelpFormatting(c, false)),\n ]);\n\n // Add commands to program\n for (const command of commands) program.addCommand(command);\n\n // Parse command line arguments\n program.parse();\n\n // Show help if no command provided\n if (!process.argv.slice(2).length) program.outputHelp();\n}\n\nmain();\n","import path from \"node:path\";\nimport chalk from \"chalk\";\nimport esbuild, { type BuildFailure, type PartialMessage } from \"esbuild\";\nimport z from \"zod\";\nimport { themeChalk } from \"@/cli/utils/theme\";\nimport { isLifeError } from \"@/shared/error\";\nimport { telemetryBrowserScopesDefinition } from \"@/telemetry/clients/browser\";\nimport { telemetryNodeScopesDefinition } from \"@/telemetry/clients/node\";\nimport type { TelemetryLog } from \"@/telemetry/types\";\n\nconst isEsbuildError = (error: Error | unknown): error is BuildFailure => {\n if (error instanceof Error && \"errors\" in error) {\n const errorsSchema = z.array(\n z.object({\n id: z.string().optional(),\n pluginName: z.string().optional(),\n text: z.string().optional(),\n }),\n );\n const { success } = errorsSchema.safeParse(error.errors);\n return success;\n }\n return false;\n};\n\nfunction formatErrorForTerminal(error: Error | unknown): string {\n let code = \"\";\n let message = \"\";\n let stack = \"\";\n let after = \"\";\n let processed = false;\n\n // Format LifeError\n if (isLifeError(error)) {\n code = `LifeError (${chalk.bold(error.code)})`;\n message = error.message;\n stack = error.stack ? error.stack.split(\"\\n\").slice(3).join(\"\\n\") : \"\";\n\n if (error.cause) {\n // Append the error after the LifeError\n after += formatErrorForTerminal(error.cause);\n\n // If the cause has a stack and the error is \"Unknown\", hide the error stack (redundant)\n const typedCause = error.cause as { stack?: string };\n if (error.code === \"Unknown\" && typedCause?.stack) stack = \"\";\n }\n\n processed = true;\n }\n\n // Format ZodError\n else if (error instanceof z.ZodError) {\n code = \"ZodError\";\n message = z.prettifyError(error);\n stack = error.stack ?? \"\";\n if (stack.includes(\" at \")) {\n stack = ` ${\n stack\n .split(\" at \")\n .slice(1)\n .map((line) => ` at ${line}`)\n .join(\"\") ?? \"\"\n }`;\n }\n processed = true;\n }\n\n // Format ESBuild errors\n else if (isEsbuildError(error)) {\n const formatEsbuildMessage = (msg: PartialMessage) =>\n esbuild\n .formatMessagesSync([msg], { kind: \"error\", color: true })?.[0]\n ?.replace(\"\\x1B[31m✘ \\x1B[41;31m[\\x1B[41;97mERROR\\x1B[41;31m]\\x1B[0m \\x1B[1m\", \"\")\n ?.trim() ?? \"\";\n try {\n const esbuildError = error as BuildFailure;\n const formattedMessages = esbuildError.errors.map(formatEsbuildMessage);\n message = `BuildError: ${formattedMessages.join(\"\\n\\n\")}`;\n processed = true;\n } catch (_) {\n /* Ignore, that wasn't an ESBuild error */\n }\n }\n\n // Format other errors\n if (!processed && error instanceof Error) {\n // Try to infer the code\n if (\"name\" in error && typeof error.name === \"string\") code = error.name;\n else if (\"code\" in error && typeof error.code === \"string\") code = error.code;\n\n // Try to infer the message\n if (\"message\" in error && typeof error.message === \"string\") message = error.message;\n else if (\"reason\" in error && typeof error.reason === \"string\") message = error.reason;\n\n // Try to infer the stack\n if (\"stack\" in error && typeof error.stack === \"string\") stack = error.stack;\n\n // Remove first line of stack if it includes the error message\n stack =\n stack\n ?.split(\"\\n\")\n ?.filter((line) => !line.includes(error.message.trim()))\n ?.join(\"\\n\") ?? \"\";\n\n // If no code, message, or stack is present, use the default\n if (!code) code = \"Unknown Error\";\n if (!message) message = \"An unknown error occurred.\";\n if (!stack) stack = \"\";\n }\n\n // If a cause is present, format it as well (unless already processed)\n // Note: LifeError already handles its cause above, so we skip it here\n if (error instanceof Error && error.cause && !isLifeError(error)) {\n after += `${formatErrorForTerminal(error.cause)}`;\n }\n\n // Replace all the absolute paths in stack with relative paths (if shorter)\n stack = stack.replace(/\\/[^\\s\\n\\r:;,()[\\]{}'\"<>]+/g, (match) => {\n try {\n if (path.isAbsolute(match)) {\n const relativePath = path.relative(process.cwd(), match);\n // Use relative path if it's shorter than the absolute path\n if (relativePath.length < match.length) return relativePath;\n }\n return match;\n } catch {\n return match;\n }\n });\n\n return `${code}${code ? \": \" : \"\"}${message}${message ? \" \" : \"\"}${stack ? `\\n${stack}` : \"\"}${after ? `\\n\\n${after}` : \"\"}`;\n}\n\nexport function formatLogForTerminal(log: TelemetryLog) {\n // Get prefix and color based on level\n let style: { prefix: string; color?: (m: string) => string };\n if (log.level === \"fatal\")\n style = { prefix: themeChalk.level.fatal.bold(\"✘\"), color: themeChalk.level.fatal };\n else if (log.level === \"error\")\n style = { prefix: themeChalk.level.error.bold(\"✘\"), color: themeChalk.level.error };\n else if (log.level === \"warn\")\n style = { prefix: themeChalk.level.warn.bold(\"▲\"), color: themeChalk.level.warn };\n else if (log.level === \"info\")\n style = { prefix: themeChalk.level.info.bold(\"⦿\"), color: themeChalk.level.info };\n else style = { prefix: themeChalk.level.debug.bold(\"∴\"), color: themeChalk.level.debug };\n\n // Format the log scope\n const scopeDefinition =\n telemetryNodeScopesDefinition?.[log.scope as keyof typeof telemetryNodeScopesDefinition] ??\n telemetryBrowserScopesDefinition?.[log.scope as keyof typeof telemetryBrowserScopesDefinition];\n const scopeDisplayName =\n scopeDefinition?.displayName instanceof Function\n ? // biome-ignore lint/suspicious/noExplicitAny: fine here\n scopeDefinition.displayName(log.attributes as any)\n : scopeDefinition?.displayName;\n const scope = `${chalk.gray(`[${chalk.italic(scopeDisplayName ?? \"Unknown\")}]`)} `;\n\n // Format the log message\n const message = log.message || \"\";\n\n // Build the log header\n const header = `${style.prefix} ${scope}${style.color ? style.color(message) : message}`;\n\n // Format the log error content (if any)\n const error = formatErrorForTerminal(log.error);\n const errorColor = [\"error\", \"fatal\"].includes(log.level)\n ? themeChalk.level.error\n : themeChalk.level.warn;\n\n // Build the output (if an error is present, add it with padding)\n let output = header;\n if (error)\n output += `\\n${errorColor.dim(\"-----\")}\\n${errorColor(error)}\\n${errorColor.dim(\"-----\")}`;\n\n // Otherwise, just return the header\n return output;\n}\n","import chalk from \"chalk\";\n\n// To be used in Ink.js components' color properties\nexport const theme = {\n orange: \"#E77823\",\n gray: {\n light: \"#bbbbbb\",\n medium: \"#888888\",\n dark: \"#444444\",\n },\n level: {\n fatal: \"red\",\n error: \"red\",\n warn: \"#FFA500\",\n info: \"cyan\",\n debug: \"gray\",\n },\n} as const;\n\n// To be used in plain-text manipulation\nexport const themeChalk = {\n orange: chalk.hex(theme.orange),\n gray: {\n light: chalk.hex(theme.gray.light),\n medium: chalk.hex(theme.gray.medium),\n dark: chalk.hex(theme.gray.dark),\n },\n level: {\n fatal: chalk.bgRed,\n error: chalk.red,\n info: chalk.cyan,\n debug: chalk.gray,\n warn: chalk.hex(theme.level.warn),\n },\n} as const;\n","import { resolve } from \"node:path\";\nimport { Command } from \"commander\";\nimport { TelemetryClient } from \"@/telemetry/clients/base\";\nimport { BuildOptions, executeBuild } from \"./action\";\n\nexport function createBuildCommand(telemetry: TelemetryClient) {\n const command = new Command(\"build\")\n .description(\"Build agents for production deployment.\")\n .helpOption(\"--help\", \"Display help for command.\")\n .option(\"-o, --output <dir>\", \"Output directory.\", \".life\")\n .option(\"-r, --root <dir>\", \"Project root directory.\", resolve(process.cwd()))\n .option(\"-w, --watch\", \"Watch for changes and rebuild automatically.\")\n .option(\n \"--no-optimize\",\n \"Disable build optimization, e.g., for faster builds during development.\",\n )\n .option(\"--debug\", \"Enable debug mode logs, same as LOG_LEVEL=debug.\")\n .action(async (options: BuildOptions) => await executeBuild(telemetry, options));\n\n return command;\n}\n","import chalk from \"chalk\";\nimport { themeChalk } from \"./theme\";\nimport { formatVersion, getVersion } from \"./version\";\n\nexport async function generateHeader(name: string) {\n const nameLength = `Life.js ${name}`.length;\n const formattedVersion = formatVersion(await getVersion());\n const gap = 13;\n const padding = 1;\n const headerSeparator = chalk.gray(\n \"─\".repeat(nameLength + gap + formattedVersion.raw.length + padding * 2),\n );\n return `\n${headerSeparator}\n${\" \".repeat(padding)}${themeChalk.gray.medium(\"Life.js\")} ${themeChalk.orange(chalk.italic(name))}${\" \".repeat(gap)}${formattedVersion.output}${\" \".repeat(padding)}\n${headerSeparator}\n`;\n}\n","import chalk from \"chalk\";\nimport { getLatestVersion } from \"fast-npm-meta\";\nimport packageJson from \"../../package.json\" with { type: \"json\" };\nimport { themeChalk } from \"./theme\";\n\nexport interface VersionInfo {\n current: string;\n latest?: string;\n hasUpdate: boolean;\n}\n\n/**\n * Check if a new version is available from npm\n */\nexport async function getVersion(): Promise<VersionInfo> {\n const currentVersion = packageJson.version;\n try {\n const latestVersionData = await getLatestVersion(\"life\");\n const latestVersion = latestVersionData.version;\n return {\n current: currentVersion,\n latest: latestVersion ?? undefined,\n hasUpdate: currentVersion !== latestVersion,\n };\n } catch {\n return {\n current: currentVersion,\n hasUpdate: false,\n };\n }\n}\n\nexport function formatVersion(versionInfo: VersionInfo) {\n const hasUpdate = versionInfo.hasUpdate && versionInfo.latest;\n const raw = hasUpdate ? `${versionInfo.current} (↑ ${versionInfo.latest})` : versionInfo.current;\n const output = hasUpdate\n ? `${themeChalk.gray.medium(versionInfo.current)} ${chalk.green(chalk.bold(`(↑ ${versionInfo.latest})`))}`\n : themeChalk.gray.medium(versionInfo.current);\n return { raw, output };\n}\n","import { createHash } from \"node:crypto\";\nimport { access, mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path, { dirname, join, relative } from \"node:path\";\nimport { Lang, parseAsync } from \"@ast-grep/napi\";\nimport chalk from \"chalk\";\nimport chokidar, { type FSWatcher } from \"chokidar\";\nimport esbuild from \"esbuild\";\nimport { globbySync } from \"globby\";\nimport ts from \"typescript\";\nimport { deepClone } from \"@/shared/deep-clone\";\nimport { ns } from \"@/shared/nanoseconds\";\nimport * as op from \"@/shared/operation\";\nimport type { TelemetryClient } from \"@/telemetry/clients/base\";\nimport { createTelemetryClient } from \"@/telemetry/clients/node\";\nimport { getDependenciesMap } from \"./helpers/dependencies-map\";\nimport { type CompilerOptions, compilerOptionsSchema } from \"./options\";\nimport type { CompilerPathType } from \"./types\";\n\nconst EXCLUDED_DEFAULTS = [\"**/node_modules/**\", \"**/build/**\", \"**/generated/**\", \"**/dist/**\"];\n\nexport class LifeCompiler {\n options: CompilerOptions<\"output\">;\n hashes = new Map<string, string>();\n watcher: FSWatcher | null = null;\n\n paths = {\n configs: new Set<string>(),\n servers: new Set<string>(),\n clients: new Set<string>(),\n dependencies: new Map<string, Set<string>>(), // entryPath -> dependencies\n serverBuilds: new Map<string, string>(), // entryPath -> buildPath\n clientBuilds: new Map<string, string>(), // entryPath -> buildPath\n };\n\n // Language Service for faster type extraction\n telemetry: TelemetryClient;\n #serverBundleContext: esbuild.BuildContext | null = null;\n #languageService?: ts.LanguageService;\n #serviceHost?: ts.LanguageServiceHost;\n readonly #virtualFiles = new Map<string, string>();\n readonly #pluginNamesCache = new Map<string, { hash: string; names: string[] }>();\n\n constructor(options: CompilerOptions<\"input\">) {\n this.options = compilerOptionsSchema.parse(options);\n\n // Default stopOnError to false if watch mode and stopOnError is not provided\n if (this.options.watch && options?.stopOnError === undefined) {\n this.options.stopOnError = false;\n }\n\n // Initialize telemetry\n this.telemetry = createTelemetryClient(\"compiler\", {\n watch: this.options.watch,\n });\n\n // Ensure outputDir is absolute\n this.options.outputDirectory = this.options.outputDirectory.startsWith(\"/\")\n ? this.options.outputDirectory\n : join(this.options.projectDirectory, this.options.outputDirectory);\n }\n\n async start() {\n return await this.telemetry.trace(\"start()\", async (span) => {\n try {\n span.log.info({ message: \"Starting compiler.\" });\n span.log.debug({ message: `Project directory: ${this.options.projectDirectory}` });\n span.log.debug({ message: `Output directory: ${this.options.outputDirectory}` });\n this.telemetry.counter(\"compiler_started\").increment();\n\n await this.telemetry.trace(\"initial-compilation\", async (spanCompilation) => {\n // 1. Ensure the build directory exists and link the build directory output to life/build\n await Promise.all([this.ensureBuildDirectory(), this.linkBuildDirectory()]);\n\n // 2. Find all Life.js agents and config files, and their dependencies\n const entryPaths = globbySync(\n [\n \"**/agent/{server.ts,client.ts}\",\n \"**/agents/*/{server.ts,client.ts}\",\n \"**/life.config.ts\",\n ],\n {\n cwd: this.options.projectDirectory,\n ignore: EXCLUDED_DEFAULTS,\n dot: false,\n onlyFiles: true,\n absolute: true,\n gitignore: true,\n unique: true,\n },\n );\n await Promise.all(entryPaths.map(async (p) => this.refreshEntryPathDependencies(p)));\n\n // 3. Perform an initial compilation on entry paths\n // Compile configs first, so that agents can use them\n const configResults = await Promise.all(\n entryPaths\n .filter((p) => p.endsWith(\"life.config.ts\"))\n .map(\n async (absPath) =>\n await this.processFileEvent({\n action: \"added\",\n absPath,\n type: \"config\",\n noTimingLogs: true,\n }),\n ),\n );\n // Compile agents servers and clients\n const agentResults = await Promise.all([\n ...entryPaths\n .filter((p) => p.endsWith(\"server.ts\"))\n .map(\n async (absPath) =>\n await this.processFileEvent({\n action: \"added\",\n absPath,\n type: \"server\",\n noTimingLogs: true,\n }),\n ),\n ...entryPaths\n .filter((p) => p.endsWith(\"client.ts\"))\n .map(\n async (absPath) =>\n await this.processFileEvent({\n action: \"added\",\n absPath,\n type: \"client\",\n noTimingLogs: true,\n }),\n ),\n ]);\n\n // 4. If some agents have been compiled, show the results\n const results = [...configResults, ...agentResults];\n spanCompilation.end();\n const duration = spanCompilation.getData().duration;\n const errorsCount = results.filter((r) => Boolean(r?.[0])).length;\n const hasAgents = entryPaths.filter((p) => p.endsWith(\"server.ts\")).length > 0;\n if (hasAgents) {\n span.log.info({\n message: `Initial compilation in ${chalk.bold(`${ns.toMs(duration)}ms`)}. ${errorsCount > 0 ? chalk.red(`(${chalk.bold(errorsCount)} error${errorsCount > 1 ? \"s\" : \"\"})`) : chalk.dim(\"(no errors)\")}`,\n });\n }\n // Else show a helpful message if no agent server paths are found\n else\n span.log.info({\n message:\n \"No agent server to compile yet. Create a first `agent/server.ts` file in your project.\",\n });\n });\n\n // 5. (if watch mode) Watch for changes in agents / configs files\n if (this.options.watch) {\n this.watchEntryPaths();\n span.log.info({ message: \"Watching for changes...\" });\n\n // Listen for SIGINT and SIGTERM to gracefully stop the compiler\n const handleShutdown = async (signal: string) => {\n console.log(\"\");\n this.telemetry.log.info({ message: `Received ${signal}, shutting down gracefully...` });\n await this.stop();\n };\n process.once(\"SIGINT\", () => handleShutdown(\"SIGINT\"));\n process.once(\"SIGTERM\", () => handleShutdown(\"SIGTERM\"));\n }\n\n // 6. Or stop the compiler\n else await this.stop();\n\n // 7. Return success\n return op.success();\n } catch (error) {\n return op.failure({ code: \"Unknown\", cause: error });\n }\n });\n }\n\n #stopStarted = false;\n async stop() {\n return await this.telemetry.trace(\"stop()\", async (span) => {\n if (this.#stopStarted) return;\n this.#stopStarted = true;\n span.log.info({ message: \"Stopping compiler.\" });\n\n // Dispose all ESBuild contexts\n this.#serverBundleContext?.dispose();\n this.#serverBundleContext = null;\n\n // Stop the watcher\n await this.watcher?.close();\n this.watcher = null;\n\n // Ensure telemetry consumers have finished processing\n await this.telemetry.flushConsumers();\n });\n }\n\n async ensureBuildDirectory() {\n await Promise.all([\n mkdir(path.join(this.options.outputDirectory, \"server\", \"raw\"), { recursive: true }),\n mkdir(path.join(this.options.outputDirectory, \"server\", \"dist\"), { recursive: true }),\n mkdir(path.join(this.options.outputDirectory, \"server\", \"signal\"), { recursive: true }),\n mkdir(path.join(this.options.outputDirectory, \"client\"), { recursive: true }),\n ]);\n }\n\n async linkBuildDirectory() {\n // Paths to generated build files in .life/build/\n const generatedClientPath = join(this.options.outputDirectory, \"client\", \"index.ts\");\n const generatedServerPath = join(this.options.outputDirectory, \"server\", \"dist\", \"index.js\");\n\n //\n const [errDistDir, distDir] = await this.findDistDirectory();\n if (errDistDir) return op.failure(errDistDir);\n\n // Get all files in the dist directory recursively\n const distFiles = globbySync(\"**/*\", {\n cwd: distDir,\n onlyFiles: true,\n absolute: false,\n });\n\n const CODE_FILE_EXTENSIONS = [\".js\", \".mjs\", \".cjs\", \".ts\", \".mts\", \".cts\", \".jsx\", \".tsx\"];\n const codeFiles = distFiles.filter((file) => {\n const ext = path.extname(file).toLowerCase();\n return CODE_FILE_EXTENSIONS.includes(ext);\n });\n\n // Process all files concurrently, replacing placeholders\n await Promise.all(\n codeFiles.map(async (file) => {\n const filePath = join(distDir, file);\n const content = await readFile(filePath, \"utf-8\");\n\n // Replace placeholders\n // @dev '()' empty groups are used in regexes to avoid those mathching themselves.\n const clientPath = relative(dirname(filePath), generatedClientPath);\n const serverPath = relative(dirname(filePath), generatedServerPath);\n const updatedContent = content\n .replaceAll(/\"LIFE()_CLIENT_BUILD_PATH\"/g, `\"${clientPath}\"`)\n .replaceAll(/String\\(\"LIFE()_CLIENT_BUILD_MODULE\"\\)/g, `import(\"${clientPath}\")`)\n .replaceAll(/\"LIFE()_CLIENT_BUILD_MODULE\"/g, `import(\"${clientPath}\")`)\n .replaceAll(/\"LIFE()_SERVER_BUILD_PATH\"/g, `\"${serverPath}\"`)\n .replaceAll(/\"LIFE()_BUILD_MODE\"/g, '\"production\"')\n .replaceAll(/'LIFE()_CLIENT_BUILD_PATH'/g, `'${clientPath}'`)\n .replaceAll(/String\\('LIFE()_CLIENT_BUILD_MODULE'\\)/g, `import(\"${clientPath}\")`)\n .replaceAll(/'LIFE()_CLIENT_BUILD_MODULE'/g, `import(\"${clientPath}\")`)\n .replaceAll(/'LIFE()_SERVER_BUILD_PATH'/g, `'${serverPath}'`)\n .replaceAll(/'LIFE()_BUILD_MODE'/g, \"'production'\");\n\n // Write back if content changed\n if (updatedContent !== content) await writeFile(filePath, updatedContent, \"utf-8\");\n }),\n );\n }\n\n async getPathDisplayName(\n _path: string,\n type: \"config\" | \"server\" | \"client\" | \"dependency\" | \"unknown\",\n ) {\n const relativePath = path.relative(this.options.projectDirectory, _path);\n if ([\"config\", \"dependency\", \"unknown\"].includes(type)) return relativePath;\n\n // Else try to extract the agent name from the file\n const clientContent = await readFile(_path, \"utf-8\");\n const ast = await parseAsync(Lang.TypeScript, clientContent);\n const root = ast.root();\n const defineCall = root.find({\n rule: {\n kind: \"call_expression\",\n any: [\n { pattern: `defineAgent${type === \"client\" ? \"Client\" : \"\"}<$$$>($ARG)` },\n { pattern: `defineAgent${type === \"client\" ? \"Client\" : \"\"}($ARG)` },\n ],\n },\n });\n if (!defineCall) return relativePath;\n let name = defineCall?.getMatch(\"ARG\")?.text() ?? null;\n if (name) name = JSON.parse(name) as string;\n else return relativePath;\n return name;\n }\n\n /**\n * Main compiler entry point. Used to process a file.\n * @param params - The parameters for the file event.\n * @returns The result of the file event.\n */\n async processFileEvent({\n type,\n action,\n absPath,\n noCache = false,\n noTimingLogs = false,\n }: {\n type: CompilerPathType;\n action: \"added\" | \"removed\" | \"changed\";\n absPath: string;\n noCache?: boolean;\n noTimingLogs?: boolean;\n }) {\n const result = await this.telemetry.trace(\"processFileEvent()\", async (span) => {\n span.setAttributes({ action, relPath: absPath });\n\n // Helper to emit timing logs\n const emitTimingLogs = async (recompiled: boolean) => {\n if (noTimingLogs) return;\n if (![\"server\", \"client\"].includes(type)) return;\n span.end();\n const name = await this.getPathDisplayName(absPath, type);\n this.telemetry.log.info({\n message: `Agent ${type} '${chalk.bold.italic(name)}' ${recompiled ? \"re-compiled\" : \"compiled\"} in ${chalk.bold(`${ns.toMs(span.getData().duration)}ms`)}.`,\n attributes: { type, name },\n });\n };\n\n // Ensure the path is absolute\n absPath = this.ensureAbsolute(absPath);\n\n // Ignore unknown path type\n if (type === \"unknown\") return op.success();\n\n // Process the event\n // - Added\n if (action === \"added\") {\n // Compute the path hash\n this.hashes.set(absPath, await this.hashFile(absPath));\n // Telemetry\n span.log.debug({\n message: `Added '${type}' path: ${absPath}`,\n attributes: { path: absPath },\n });\n // Handle the path type\n if (type === \"config\") return await this.onAddedConfig(absPath);\n else if (type === \"server\") {\n const res = await this.onAddedServer(absPath);\n if (!res?.[0]) await emitTimingLogs(false);\n return res;\n } else if (type === \"client\") {\n const res = await this.onAddedClient(absPath);\n if (!res?.[0]) await emitTimingLogs(false);\n return res;\n } else if (type === \"dependency\")\n return op.failure({\n code: \"Conflict\",\n message: \"'added' action is not supported for dependency paths. Shouldn't happen.\",\n });\n }\n // - Removed\n else if (action === \"removed\") {\n // Clean up the path hash\n this.hashes.delete(absPath);\n // Telemetry\n span.log.debug({\n message: `Removed '${type}' path: ${absPath}`,\n attributes: { path: absPath },\n });\n // Handle the path type\n if (type === \"config\") return await this.onRemovedConfig(absPath);\n else if (type === \"server\") return await this.onRemovedServer(absPath);\n else if (type === \"client\") return await this.onRemovedClient(absPath);\n else if (type === \"dependency\") return await this.onRemovedDependency(absPath);\n }\n // - Changed\n else if (action === \"changed\") {\n // Return early if the hash hasn't changed\n const newHash = await this.hashFile(absPath);\n if (newHash === this.hashes.get(absPath) && !noCache) return op.success();\n this.hashes.set(absPath, newHash);\n // Telemetry\n span.log.debug({\n message: `Changed '${type}' path: ${absPath}`,\n attributes: { path: absPath },\n });\n // If not a dependency, refresh the dependencies\n if (type !== \"dependency\") await this.refreshEntryPathDependencies(absPath);\n // Handle the path type\n if (type === \"config\") return await this.onChangedConfig(absPath);\n else if (type === \"server\") {\n const res = await this.onChangedServer(absPath);\n if (!res?.[0]) await emitTimingLogs(true);\n return res;\n } else if (type === \"client\") {\n const res = await this.onChangedClient(absPath);\n if (!res?.[0]) await emitTimingLogs(true);\n return res;\n } else if (type === \"dependency\") return await this.onChangedDependency(absPath);\n }\n\n throw new Error(\"Invalid action. Shouldn't happen.\");\n });\n\n // Handle any error\n const [error] = result;\n if (error) {\n // Build the base error message\n const displayName = await this.getPathDisplayName(absPath, type);\n let baseMessage = \"Failed to compile \";\n if ([\"client\", \"server\"].includes(type))\n baseMessage += `agent ${type} '${chalk.bold.italic(displayName)}'.`;\n else baseMessage += `'${chalk.bold.italic(displayName)}' ${type} file.`;\n const relativePath = path.relative(this.options.projectDirectory, absPath);\n\n // If requested, log and stop the compiler on error\n if (this.options.stopOnError) {\n this.telemetry.log.error({ message: `${baseMessage}\\nPath: ${relativePath}`, error });\n await this.stop();\n }\n // Else gracefully warn and ignore the file\n else\n this.telemetry.log.warn({\n message: `${baseMessage} It has been ignored.\\nPath: ${relativePath}`,\n error,\n });\n }\n return result;\n }\n\n // Configs Events Handlers\n\n async onAddedConfig(configPath: string) {\n return await this.telemetry.trace(\n \"onAddedConfig()\",\n async () => {\n try {\n // Add the config to the configs paths array\n this.paths.configs.add(configPath);\n\n // Call the change handler\n return await this.onChangedConfig(configPath);\n } catch (error) {\n return op.failure({ code: \"Unknown\", cause: error });\n }\n },\n { attributes: { configPath } },\n );\n }\n\n async onRemovedConfig(configPath: string) {\n return await this.telemetry.trace(\n \"onRemovedConfig()\",\n async () => {\n try {\n // Remove the config from the configs paths array\n this.paths.configs.delete(configPath);\n\n // Call the change handler\n return await this.onChangedConfig(configPath);\n } catch (error) {\n return op.failure({ code: \"Unknown\", cause: error });\n }\n },\n { attributes: { configPath } },\n );\n }\n\n async onChangedConfig(configPath: string) {\n return await this.telemetry.trace(\n \"onChangedConfig()\",\n async () => {\n try {\n // 1. Ensure the config contains a defineConfig() call\n const configContent = await readFile(configPath, \"utf-8\");\n const ast = await parseAsync(Lang.TypeScript, configContent);\n const root = ast.root();\n const defineConfigCall = root.find({\n rule: { kind: \"call_expression\", pattern: \"defineConfig($ARG)\" },\n });\n if (!defineConfigCall) {\n return op.failure({\n code: \"Validation\",\n message: \"Config file does not contain a defineConfig() call.\",\n attributes: { path: configPath },\n });\n }\n\n // 2. Ensure the defineConfig() call is exported as default\n const exportDefaultStatement = root.find({\n rule: {\n kind: \"export_statement\",\n pattern: \"export default\",\n has: {\n regex: \"defineConfig(.*)\",\n },\n },\n });\n if (!exportDefaultStatement) {\n return op.failure({\n code: \"Validation\",\n message: `Config file has defineConfig() but doesn't export it as default. Use \\`export default defineConfig(...)\\` instead.`,\n attributes: { path: configPath },\n });\n }\n\n // 4. Recompiling affected agents servers\n const affected = Array.from(this.paths.servers).filter((p) =>\n this.isEntryPathTouchedByConfig(p, configPath),\n );\n await Promise.all(\n affected.map(async (serverPath) => {\n const absPath = this.ensureAbsolute(serverPath);\n return await this.processFileEvent({\n action: \"changed\",\n absPath,\n type: \"server\",\n noCache: true,\n });\n }),\n );\n\n return op.success();\n } catch (error) {\n return op.failure({ code: \"Unknown\", cause: error });\n }\n },\n { attributes: { configPath } },\n );\n }\n\n // Agent Servers Events Handlers\n\n async onAddedServer(serverPath: string) {\n return await this.telemetry.trace(\n \"onAddedServer()\",\n async () => {\n try {\n // Add the server to the servers paths array\n this.paths.servers.add(serverPath);\n\n // Call the change handler\n return await this.onChangedServer(serverPath);\n } catch (error) {\n return op.failure({ code: \"Unknown\", cause: error });\n }\n },\n { attributes: { serverPath } },\n );\n }\n\n async onRemovedServer(serverPath: string) {\n return await this.telemetry.trace(\n \"onRemovedServer()\",\n async () => {\n try {\n // Remove the server from the servers paths array\n this.paths.servers.delete(serverPath);\n\n // Find and remove the server build path\n const buildPath = this.paths.serverBuilds.get(serverPath);\n if (buildPath) {\n this.paths.serverBuilds.delete(serverPath);\n await rm(buildPath);\n }\n\n // Request a rebuild of the server bundle\n const res = await this.generateServerBundle();\n if (!res?.[0]) return res;\n\n // Signal the server removal\n const name = this.getPathDisplayName(serverPath, \"server\");\n const signalPath = path.join(\n this.options.outputDirectory,\n \"server\",\n \"signal\",\n `${name}.txt`,\n );\n if (await this.fileExists(signalPath)) {\n await writeFile(signalPath, \"removed\", \"utf-8\");\n // await rm(signalPath);\n }\n\n return op.success();\n } catch (error) {\n return op.failure({ code: \"Unknown\", cause: error });\n }\n },\n { attributes: { serverPath } },\n );\n }\n\n async onChangedServer(serverPath: string) {\n return await this.telemetry.trace(\n \"onChangedServer()\",\n async (span) => {\n try {\n // 1. Ensure the server file contains a defineAgent() call\n const serverContent = await readFile(serverPath, \"utf-8\");\n const ast = await parseAsync(Lang.TypeScript, serverContent);\n const root = ast.root();\n const defineAgentCall = root.find({\n rule: { kind: \"call_expression\", pattern: \"defineAgent($ARG)\" },\n });\n if (!defineAgentCall) {\n return op.failure({\n code: \"Validation\",\n message: `Agent server '${chalk.bold.italic(path.relative(this.options.projectDirectory, serverPath))}' doesn't contain a ${chalk.bold.italic(\"defineAgent(...)\")} call.`,\n attributes: { serverPath },\n });\n }\n\n // 2. Ensure the defineAgent() call is exported as default\n const exportDefaultStatement = root.find({\n rule: {\n kind: \"export_statement\",\n pattern: \"export default\",\n has: {\n regex: \"defineAgent(.*)\",\n },\n },\n });\n if (!exportDefaultStatement) {\n return op.failure({\n code: \"Validation\",\n message: `Agent server '${chalk.bold.italic(path.relative(this.options.projectDirectory, serverPath))}' doesn't export ${chalk.bold.italic(\"defineAgent(...)\")} call as default. Use ${chalk.bold.italic(\"export default defineAgent(...)\")}.`,\n attributes: { serverPath },\n });\n }\n\n // 3. Retrieve the agent name\n let name = defineAgentCall?.getMatch(\"ARG\")?.text() ?? null;\n if (name) name = JSON.parse(name) as string;\n if (!name) {\n return op.failure({\n code: \"Validation\",\n message: `Agent server '${chalk.bold.italic(name)}' has ${chalk.bold.italic(\"defineAgent()\")} but doesn't provide a name. Use ${chalk.bold.italic(\"defineAgent(<name>)\")}.`,\n attributes: { serverPath },\n });\n }\n\n // 4. Retrieve all the configs touching this server\n const configPaths: string[] = [];\n for (const configPath of this.paths.configs) {\n if (this.isEntryPathTouchedByConfig(serverPath, configPath))\n configPaths.push(configPath);\n }\n configPaths.sort((a, b) => b.length - a.length);\n\n // 5. Obtain a unified sha of the server file dependencies tree\n const treeFiles = new Set<string>([serverPath, ...configPaths]);\n // - Add all dependencies from the dependenciesMap\n for (const file of deepClone(treeFiles)) {\n const deps = this.paths.dependencies.get(file);\n if (deps) for (const dep of deps) treeFiles.add(dep);\n }\n // - Obtain the hashes for all tree files\n const treeHashes = Array.from(treeFiles).map((file) => this.hashes.get(file));\n const filteredTreeHashes = treeHashes.filter((hash) => hash !== undefined);\n\n if (treeHashes.length !== filteredTreeHashes.length) {\n span.log.warn({\n message: \"Some tree files have no hash. Shouldn't happen.\",\n attributes: { treeFiles },\n });\n }\n // - Compute the unified hash from all tree hashes\n const sha = createHash(\"md5\").update(treeHashes.join(\":\")).digest(\"hex\");\n\n // 6. Generate agent server build content\n const buildPath = path.join(this.options.outputDirectory, \"server\", \"raw\", `${name}.ts`);\n const relServerPath = path.relative(path.dirname(buildPath), serverPath);\n const relConfigPaths = configPaths.map((configPath) =>\n path.relative(path.dirname(buildPath), configPath),\n );\n const content = `\n ${relConfigPaths.map((configPath, i) => `import config${i} from \"${configPath}\";`).join(\"\\n\")}\nimport agent from \"${relServerPath}\";\nexport default {\n definition: agent.def,\n globalConfigs: [${configPaths.map((_, i) => `config${i}`).join(\", \")}],\n sha: \"${sha}\"\n} as const;\n `.trim();\n\n // 7. Write the agent server build content\n await writeFile(buildPath, content, \"utf-8\");\n this.paths.serverBuilds.set(serverPath, buildPath);\n\n // 8. Re-bundle the server index\n const [errBundle] = await this.generateServerBundle();\n if (errBundle) return op.failure(errBundle);\n\n // 9. If everything went well, reflect the new sha in the signal/ folder\n const signalPath = path.join(\n this.options.outputDirectory,\n \"server\",\n \"signal\",\n `${name}.txt`,\n );\n await writeFile(signalPath, sha, \"utf-8\");\n\n return op.success();\n } catch (error) {\n return op.failure({ code: \"Unknown\", cause: error });\n }\n },\n { attributes: { serverPath } },\n );\n }\n\n // Agent Clients Events Handlers\n\n async onAddedClient(clientPath: string) {\n return await this.telemetry.trace(\n \"onAddedClient()\",\n async () => {\n try {\n // Add the client to the clients paths array\n this.paths.clients.add(clientPath);\n\n // Call the change handler\n return await this.onChangedClient(clientPath);\n } catch (error) {\n return op.failure({ code: \"Unknown\", cause: error });\n }\n },\n { attributes: { clientPath } },\n );\n }\n\n async onRemovedClient(clientPath: string) {\n return await this.telemetry.trace(\n \"onRemovedClient()\",\n async () => {\n try {\n // Remove the client from the clients paths array\n this.paths.clients.delete(clientPath);\n\n // Find and remove the client build path\n const buildPath = this.paths.clientBuilds.get(clientPath);\n if (buildPath) {\n this.paths.clientBuilds.delete(clientPath);\n await rm(buildPath);\n }\n\n // Request a rebuild of the client bundle\n return await this.onChangedClient(clientPath);\n } catch (error) {\n return op.failure({ code: \"Unknown\", cause: error });\n }\n },\n { attributes: { clientPath } },\n );\n }\n\n async onChangedClient(clientPath: string) {\n return await this.telemetry.trace(\n \"onChangedClient()\",\n async () => {\n try {\n // 1. Ensure the client file contains a defineAgentClient() call\n const clientContent = await readFile(clientPath, \"utf-8\");\n const ast = await parseAsync(Lang.TypeScript, clientContent);\n const root = ast.root();\n const defineAgentClientCall = root.find({\n rule: {\n kind: \"call_expression\",\n any: [\n { pattern: \"defineAgentClient<$$$>($ARG)\" },\n { pattern: \"defineAgentClient($ARG)\" },\n ],\n },\n });\n if (!defineAgentClientCall) {\n return op.failure({\n code: \"Validation\",\n message: `Agent client '${chalk.bold.italic(path.relative(this.options.projectDirectory, clientPath))}' doesn't contain a ${chalk.bold.italic(\"defineAgentClient(...)\")} call. It has been ignored.`,\n attributes: { clientPath },\n });\n }\n\n // 2. Ensure the defineAgentClient() call is exported as default\n const exportDefaultStatement = root.find({\n rule: {\n kind: \"export_statement\",\n pattern: \"export default\",\n has: {\n regex: \"defineAgentClient(<.*>)?(.*)\",\n },\n },\n });\n if (!exportDefaultStatement) {\n return op.failure({\n code: \"Validation\",\n message: `Agent client '${chalk.bold.italic(path.relative(this.options.projectDirectory, clientPath))}' doesn't export ${chalk.bold.italic(\"defineAgentClient(...)\")} call as default. It has been ignored. Use ${chalk.bold.italic(\"export default defineAgentClient(...)\")}.`,\n attributes: { clientPath },\n });\n }\n\n // 3. Retrieve the agent name\n let name = defineAgentClientCall?.getMatch(\"ARG\")?.text() ?? null;\n if (name) name = JSON.parse(name) as string;\n if (!name) {\n return op.failure({\n code: \"Validation\",\n message: `Agent client '${chalk.bold.italic(path.relative(this.options.projectDirectory, clientPath))}' has ${chalk.bold.italic(\"defineAgentClient()\")} but doesn't provide a name. It has been ignored. Use ${chalk.bold.italic(\"defineAgentClient(<name>)\")}.`,\n attributes: { clientPath },\n });\n }\n\n // 4. Retrieve all the names of the plugins registered on this agent client\n const pluginNames = await this.extractClientPluginNames(clientPath);\n\n // 5. Generate agent client build content\n const plugins = pluginNames\n .map(\n (pluginName) => ` \"${pluginName}\": {\n def: p[\"${pluginName}\"],\n $types: {\n atoms: (mock as typeof p[\"${pluginName}\"][\"atoms\"])<PC[\"${pluginName}\"]>(null as any),\n class: (mock as typeof p[\"${pluginName}\"][\"class\"])<PC[\"${pluginName}\"]>(null as any),\n clientConfig: {} as PC[\"${pluginName}\"][\"client\"],\n serverConfig: {} as PC[\"${pluginName}\"][\"server\"],\n }\n }`,\n )\n .join(\",\\n\");\n\n const buildPath = path.join(this.options.outputDirectory, \"client\", `${name}.ts`);\n const relClientPath = path.relative(path.dirname(buildPath), clientPath);\n const content = `import agentClient from \"${relClientPath.replace(\".ts\", \"\")}\";\ntype SC = typeof agentClient[\"def\"][\"$serverDef\"][\"pluginConfigs\"];\ntype CC = typeof agentClient[\"def\"][\"pluginConfigs\"];\ntype PC = {\n${pluginNames.map((pluginName) => ` \"${pluginName}\": { client: CC[\"${pluginName}\"], server: SC extends { \"${pluginName}\": unknown } ? SC[\"${pluginName}\"] : never }`).join(\",\\n\")}\n}\nconst p = {\n${pluginNames.map((pluginName) => ` \"${pluginName}\": agentClient.def.plugins.find(p => p.name === \"${pluginName}\")!`).join(\",\\n\")}\n} as const;\nconst mock = (() => void 0) as any;\nexport default {\n definition: agentClient.def,\n plugins: {\n${plugins}\n }\n} as const;\n `.trim();\n\n // 6. Write the agent client build content\n await writeFile(buildPath, content, \"utf-8\");\n this.paths.clientBuilds.set(clientPath, buildPath);\n\n // 7. Re-bundle the client index\n const [errBundle] = await this.generateClientBundle();\n if (errBundle) return op.failure(errBundle);\n\n return op.success();\n } catch (error) {\n re