@agentuity/cli 0.0.98 → 0.0.100

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 (71) hide show
  1. package/dist/auth.d.ts.map +1 -1
  2. package/dist/auth.js +5 -0
  3. package/dist/auth.js.map +1 -1
  4. package/dist/banner.d.ts.map +1 -1
  5. package/dist/banner.js +22 -6
  6. package/dist/banner.js.map +1 -1
  7. package/dist/cmd/build/entry-generator.d.ts.map +1 -1
  8. package/dist/cmd/build/entry-generator.js +84 -61
  9. package/dist/cmd/build/entry-generator.js.map +1 -1
  10. package/dist/cmd/build/vite/bun-dev-server.d.ts +3 -7
  11. package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
  12. package/dist/cmd/build/vite/bun-dev-server.js +5 -14
  13. package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
  14. package/dist/cmd/build/vite/index.d.ts +0 -1
  15. package/dist/cmd/build/vite/index.d.ts.map +1 -1
  16. package/dist/cmd/build/vite/index.js +0 -1
  17. package/dist/cmd/build/vite/index.js.map +1 -1
  18. package/dist/cmd/build/vite/server-bundler.d.ts.map +1 -1
  19. package/dist/cmd/build/vite/server-bundler.js +41 -1
  20. package/dist/cmd/build/vite/server-bundler.js.map +1 -1
  21. package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
  22. package/dist/cmd/build/vite/vite-asset-server-config.js +17 -1
  23. package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
  24. package/dist/cmd/build/vite/vite-asset-server.js +1 -1
  25. package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
  26. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  27. package/dist/cmd/build/vite/vite-builder.js +22 -4
  28. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  29. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  30. package/dist/cmd/cloud/deploy.js +32 -21
  31. package/dist/cmd/cloud/deploy.js.map +1 -1
  32. package/dist/cmd/dev/file-watcher.d.ts +24 -0
  33. package/dist/cmd/dev/file-watcher.d.ts.map +1 -0
  34. package/dist/cmd/dev/file-watcher.js +213 -0
  35. package/dist/cmd/dev/file-watcher.js.map +1 -0
  36. package/dist/cmd/dev/index.d.ts.map +1 -1
  37. package/dist/cmd/dev/index.js +61 -23
  38. package/dist/cmd/dev/index.js.map +1 -1
  39. package/dist/cmd/project/create.d.ts.map +1 -1
  40. package/dist/cmd/project/create.js +8 -2
  41. package/dist/cmd/project/create.js.map +1 -1
  42. package/dist/cmd/setup/index.d.ts.map +1 -1
  43. package/dist/cmd/setup/index.js +3 -3
  44. package/dist/cmd/setup/index.js.map +1 -1
  45. package/dist/repl.js +2 -2
  46. package/dist/repl.js.map +1 -1
  47. package/dist/types.d.ts +6 -0
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/types.js.map +1 -1
  50. package/package.json +8 -4
  51. package/src/auth.ts +6 -0
  52. package/src/banner.ts +25 -6
  53. package/src/cmd/build/entry-generator.ts +84 -62
  54. package/src/cmd/build/vite/bun-dev-server.ts +8 -18
  55. package/src/cmd/build/vite/index.ts +0 -1
  56. package/src/cmd/build/vite/server-bundler.ts +52 -1
  57. package/src/cmd/build/vite/vite-asset-server-config.ts +22 -1
  58. package/src/cmd/build/vite/vite-asset-server.ts +1 -1
  59. package/src/cmd/build/vite/vite-builder.ts +30 -4
  60. package/src/cmd/cloud/deploy.ts +41 -24
  61. package/src/cmd/dev/file-watcher.ts +264 -0
  62. package/src/cmd/dev/index.ts +69 -24
  63. package/src/cmd/project/create.ts +13 -3
  64. package/src/cmd/setup/index.ts +7 -3
  65. package/src/repl.ts +2 -2
  66. package/src/types.ts +6 -0
  67. package/dist/cmd/build/vite/patch-plugin.d.ts +0 -21
  68. package/dist/cmd/build/vite/patch-plugin.d.ts.map +0 -1
  69. package/dist/cmd/build/vite/patch-plugin.js +0 -70
  70. package/dist/cmd/build/vite/patch-plugin.js.map +0 -1
  71. package/src/cmd/build/vite/patch-plugin.ts +0 -88
@@ -76,23 +76,13 @@ export async function generateEntryFile(options: GenerateEntryOptions): Promise<
76
76
  `import { `,
77
77
  ...runtimeImports,
78
78
  `} from '@agentuity/runtime';`,
79
+ `import type { Context } from 'hono';`,
79
80
  `import { websocket } from 'hono/bun';`, // Always use Bun WebSocket (dev and prod)
80
81
  !isDev && hasWebFrontend ? `import { serveStatic } from 'hono/bun';` : '',
81
82
  ].filter(Boolean);
82
83
 
83
84
  imports.push(`import { type LogLevel } from '@agentuity/core';`);
84
-
85
- // HMR setup (dev only)
86
- const hmrSetup = isDev
87
- ? `
88
- // HMR restart handler
89
- if (typeof (globalThis as any).__AGENTUITY_RESTART__ === 'undefined') {
90
- (globalThis as any).__AGENTUITY_RESTART__ = () => {
91
- console.log('[HMR] Restart triggered but handler not ready yet');
92
- };
93
- }
94
- `
95
- : '';
85
+ imports.push(`import { bootstrapRuntimeEnv } from '@agentuity/cli/runtime-bootstrap';`);
96
86
 
97
87
  // Generate route mounting code for all discovered routes
98
88
  const routeImportsAndMounts: string[] = [];
@@ -146,7 +136,7 @@ app.route('/', workbenchRouter);
146
136
  // Asset proxy routes - Forward Vite-specific requests to asset server
147
137
  const VITE_ASSET_PORT = ${vitePort};
148
138
 
149
- const proxyToVite = async (c) => {
139
+ const proxyToVite = async (c: Context) => {
150
140
  const viteUrl = \`http://127.0.0.1:\${VITE_ASSET_PORT}\${c.req.path}\`;
151
141
  const controller = new AbortController();
152
142
  const timeout = setTimeout(() => controller.abort(), 10000); // 10s timeout
@@ -206,54 +196,82 @@ app.get('/*.css', proxyToVite);
206
196
  let webRoutes = '';
207
197
  if (hasWebFrontend) {
208
198
  if (isDev) {
209
- const htmlPath = join(srcDir, 'web', 'index.html');
210
199
  webRoutes = `
211
200
  // Web routes (dev mode with Vite HMR via proxy)
212
- const devHtmlHandler = async (c) => {
213
- const html = await Bun.file('${htmlPath}').text();
214
- const withHmr = html
215
- // Fix relative paths to use proxy routes (with or without ./)
216
- .replace(/src=["'](?:\\.\\/)?([^"'\\/]+\\.tsx?)["']/g, 'src="/src/web/$1"')
217
- // Inject Vite HMR scripts - point directly to Vite asset server for WebSocket
218
- .replace(
219
- '</head>',
220
- \`<script type="module">
221
- import RefreshRuntime from '/@react-refresh'
222
- RefreshRuntime.injectIntoGlobalHook(window)
223
- window.$RefreshReg$ = () => {}
224
- window.$RefreshSig$ = () => (type) => type
225
- </script>
226
- <script type="module">
227
- // Configure Vite client to connect to asset server for HMR WebSocket
228
- window.__VITE_HMR_BASE_URL__ = 'http://127.0.0.1:${vitePort}';
229
- </script>
230
- <script type="module" src="http://127.0.0.1:${vitePort}/@vite/client"></script>
231
- </head>\`
232
- );
233
- return c.html(withHmr);
201
+ // Proxy HTML from Vite to let @vitejs/plugin-react handle React Fast Refresh preamble
202
+ const devHtmlHandler = async (c: Context) => {
203
+ const viteUrl = \`http://127.0.0.1:${vitePort}/src/web/index.html\`;
204
+
205
+ try {
206
+ otel.logger.debug('[Proxy] GET /src/web/index.html -> Vite:%d', ${vitePort});
207
+ const res = await fetch(viteUrl, { signal: AbortSignal.timeout(10000) });
208
+
209
+ // Get HTML text and transform relative paths to absolute
210
+ const html = await res.text();
211
+ const transformedHtml = html
212
+ .replace(/src="\\.\\//g, 'src="/src/web/')
213
+ .replace(/href="\\.\\//g, 'href="/src/web/');
214
+
215
+ return new Response(transformedHtml, {
216
+ status: res.status,
217
+ headers: res.headers,
218
+ });
219
+ } catch (err) {
220
+ otel.logger.error('Failed to proxy HTML to Vite: %s', err instanceof Error ? err.message : String(err));
221
+ return c.text('Vite asset server error (HTML)', 500);
222
+ }
234
223
  };
235
224
  app.get('/', devHtmlHandler);
236
225
  // 404 for unmatched API/system routes
237
- app.all('/_agentuity/*', (c) => c.notFound());
238
- app.all('/api/*', (c) => c.notFound());
239
- ${hasWorkbench ? '' : `app.all('/workbench/*', (c) => c.notFound());`}
240
- // SPA fallback - serve index.html for all other GET requests
241
- // This is last so user routes, API routes, and workbench routes match first
242
- app.get('*', devHtmlHandler);
226
+ app.all('/_agentuity/*', (c: Context) => c.notFound());
227
+ app.all('/api/*', (c: Context) => c.notFound());
228
+ ${hasWorkbench ? '' : `app.all('/workbench/*', (c: Context) => c.notFound());`}
229
+ // SPA fallback - serve index.html for client-side routing
230
+ // Asset requests (/*.js, /*.tsx, /*.css, etc.) are handled by Vite proxy routes if present,
231
+ // otherwise we check for file extensions to avoid returning HTML for missing assets
232
+ app.get('*', (c: Context) => {
233
+ const path = c.req.path;
234
+ // If path has a file extension and Vite proxy isn't handling it, return 404
235
+ // This prevents returning HTML for missing assets like /foo.js
236
+ if (${!vitePort} && /\\.[a-zA-Z0-9]+$/.test(path)) {
237
+ return c.notFound();
238
+ }
239
+ return devHtmlHandler(c);
240
+ });
243
241
  `;
244
242
  } else {
245
243
  webRoutes = `
246
244
  // Web routes (production - static files)
247
245
  import { readFileSync } from 'node:fs';
248
246
  const indexHtml = readFileSync(import.meta.dir + '/client/index.html', 'utf-8');
247
+
248
+ app.get('/', (c: Context) => c.html(indexHtml));
249
+
250
+ // Serve static assets from /assets/* (Vite bundled output)
249
251
  app.use('/assets/*', serveStatic({ root: import.meta.dir + '/client' }));
250
- app.get('/', (c) => c.html(indexHtml));
251
- // 404 for unmatched API/system routes
252
- app.all('/_agentuity/*', (c) => c.notFound());
253
- app.all('/api/*', (c) => c.notFound());
254
- ${hasWorkbench ? '' : `app.all('/workbench/*', (c) => c.notFound());`}
255
- // SPA fallback - serve index.html for all other GET requests
256
- app.get('*', (c) => c.html(indexHtml));
252
+
253
+ // Serve static public assets (favicon.ico, robots.txt, etc. from Vite's public folder)
254
+ app.use('/*', serveStatic({ root: import.meta.dir + '/client', rewriteRequestPath: (path) => path }));
255
+
256
+ // 404 for unmatched API/system routes (IMPORTANT: comes before SPA fallback)
257
+ app.all('/_agentuity/*', (c: Context) => c.notFound());
258
+ app.all('/api/*', (c: Context) => c.notFound());
259
+ ${hasWorkbench ? '' : `app.all('/workbench/*', (c: Context) => c.notFound());`}
260
+
261
+ // SPA fallback with asset protection
262
+ // In production, we need to distinguish between:
263
+ // - SPA routes like /dashboard, /users/123 (should return HTML)
264
+ // - Missing assets like /foo.js, /bar.css (should return 404)
265
+ // We check for file extensions to detect asset requests
266
+ app.get('*', (c: Context) => {
267
+ const path = c.req.path;
268
+ // If path has a file extension, it's likely an asset request
269
+ // Return 404 instead of serving HTML
270
+ if (/\\.[a-zA-Z0-9]+$/.test(path)) {
271
+ return c.notFound();
272
+ }
273
+ return c.html(indexHtml);
274
+ });
257
275
  `;
258
276
  }
259
277
  }
@@ -265,7 +283,7 @@ app.get('*', (c) => c.html(indexHtml));
265
283
  ? isDev
266
284
  ? `
267
285
  // Workbench route (dev mode - let Vite serve source files with HMR)
268
- app.get('${workbenchRoute}', async (c) => {
286
+ app.get('${workbenchRoute}', async (c: Context) => {
269
287
  const html = await Bun.file('${workbenchSrcDir}/index.html').text();
270
288
  // Rewrite script/css paths to use Vite's @fs protocol
271
289
  const withVite = html
@@ -281,7 +299,7 @@ import { readFileSync, existsSync } from 'node:fs';
281
299
  const workbenchIndexPath = import.meta.dir + '/workbench/index.html';
282
300
  if (existsSync(workbenchIndexPath)) {
283
301
  const workbenchIndex = readFileSync(workbenchIndexPath, 'utf-8');
284
- app.get('${workbenchRoute}', (c) => c.html(workbenchIndex));
302
+ app.get('${workbenchRoute}', (c: Context) => c.html(workbenchIndex));
285
303
  app.get('${workbenchRoute}/*', serveStatic({ root: import.meta.dir + '/workbench' }));
286
304
  }
287
305
  `
@@ -311,16 +329,25 @@ if (typeof Bun !== 'undefined') {
311
329
 
312
330
  const code = `// Auto-generated by Agentuity for ${mode} mode
313
331
  // DO NOT EDIT - This file is regenerated on every build
314
- // NOTE: Bun auto-loads .env files from CWD before executing JavaScript
315
-
316
332
  ${imports.join('\n')}
317
333
 
318
- ${hmrSetup}
334
+ // Step 0: Bootstrap runtime environment (load profile-specific .env files)
335
+ // Only in development - production env vars are injected by platform
336
+ // This must happen BEFORE any imports that depend on environment variables
337
+ if (process.env.NODE_ENV !== 'production') {
338
+ // Pass project directory (parent of .agentuity/) so .env files are loaded correctly
339
+ await bootstrapRuntimeEnv({ projectDir: import.meta.dir + '/..' });
340
+ }
319
341
 
320
342
  // Step 1: Initialize telemetry and services
321
343
  const serverUrl = \`http://127.0.0.1:\${process.env.PORT || '3500'}\`;
322
344
  const otel = register({ processors: [], logLevel: (process.env.AGENTUITY_LOG_LEVEL || 'info') as LogLevel });
323
- const servicesResult = createServices(otel.logger, undefined, serverUrl);
345
+
346
+ // Get app state and config for use below
347
+ const appState = getAppState();
348
+ const appConfig = getAppConfig();
349
+
350
+ createServices(otel.logger, appConfig, serverUrl);
324
351
 
325
352
  // Make logger and tracer globally available for user's app.ts
326
353
  setGlobalLogger(otel.logger);
@@ -350,10 +377,6 @@ app.use('/api/*', createAgentMiddleware(''));
350
377
  // Step 4: Import user's app.ts (runs createApp, gets state/config)
351
378
  await import('../app.ts');
352
379
 
353
- // Get app state and config for use below
354
- const appState = getAppState();
355
- const appConfig = getAppConfig();
356
-
357
380
  // Step 5: Initialize providers
358
381
  const threadProvider = getThreadProvider();
359
382
  const sessionProvider = getSessionProvider();
@@ -364,8 +387,8 @@ await sessionProvider.initialize(appState);
364
387
  // Step 6: Mount routes (AFTER middleware is applied)
365
388
 
366
389
  // System health/idle endpoints
367
- const healthHandler = (c: any) => c.text('OK');
368
- const idleHandler = (c: any) => {
390
+ const healthHandler = (c: Context) => c.text('OK');
391
+ const idleHandler = (c: Context) => {
369
392
  // Check if server is idle (no pending requests/connections)
370
393
  const server = (globalThis as any).__AGENTUITY_SERVER__;
371
394
  if (!server) return c.text('NO', { status: 200 });
@@ -379,7 +402,6 @@ const idleHandler = (c: any) => {
379
402
  return c.text('OK', { status: 200 });
380
403
  };
381
404
 
382
- // Mount on both /_agentuity/* and /* for backwards compatibility
383
405
  app.get('/_agentuity/health', healthHandler);
384
406
  app.get('/_health', healthHandler);
385
407
  app.get('/_agentuity/idle', idleHandler);
@@ -6,7 +6,6 @@
6
6
  */
7
7
 
8
8
  import type { Logger } from '../../../types';
9
- import { startViteAssetServer } from './vite-asset-server';
10
9
 
11
10
  export interface BunDevServerOptions {
12
11
  rootDir: string;
@@ -15,20 +14,21 @@ export interface BunDevServerOptions {
15
14
  orgId?: string;
16
15
  deploymentId?: string;
17
16
  logger: Logger;
17
+ vitePort: number; // Port of already-running Vite asset server
18
18
  }
19
19
 
20
20
  export interface BunDevServerResult {
21
- viteAssetServer: { server: { close: () => void | Promise<void> }; port: number };
22
21
  bunServerPort: number;
23
22
  }
24
23
 
25
24
  /**
26
- * Start Bun dev server with Vite asset server for HMR
25
+ * Start Bun dev server (Vite asset server must already be running)
26
+ * Generates entry file with proxy routes pointing to Vite
27
27
  */
28
28
  export async function startBunDevServer(options: BunDevServerOptions): Promise<BunDevServerResult> {
29
- const { rootDir, port = 3500, projectId = '', deploymentId = '', logger } = options;
29
+ const { rootDir, port = 3500, projectId = '', deploymentId = '', logger, vitePort } = options;
30
30
 
31
- logger.debug('Starting Bun dev server with Vite asset server for HMR...');
31
+ logger.debug('Starting Bun dev server (Vite already running on port %d)...', vitePort);
32
32
 
33
33
  // Generate workbench source files if enabled (dev mode)
34
34
  const { loadAgentuityConfig, getWorkbenchConfig } = await import('./config-loader');
@@ -41,15 +41,6 @@ export async function startBunDevServer(options: BunDevServerOptions): Promise<B
41
41
  await generateWorkbenchFiles(rootDir, projectId, workbenchConfig, logger);
42
42
  }
43
43
 
44
- // Step 1: Start Vite asset server FIRST and get its dynamic port
45
- logger.debug('🎨 Starting Vite asset server for HMR...');
46
- const viteAssetServer = await startViteAssetServer({
47
- rootDir,
48
- logger,
49
- workbenchPath: workbenchConfig.enabled ? workbenchConfig.route : undefined,
50
- });
51
- const vitePort = viteAssetServer.port;
52
-
53
44
  // Step 2: Generate entry file with Vite port for asset proxying
54
45
  logger.debug('📝 Generating entry file with asset proxy configuration...');
55
46
  const { generateEntryFile } = await import('../entry-generator');
@@ -66,10 +57,10 @@ export async function startBunDevServer(options: BunDevServerOptions): Promise<B
66
57
  // Step 3: Load the generated app - this will start Bun.serve() internally
67
58
  logger.debug('📦 Loading generated app (Bun server will start)...');
68
59
  const appPath = `${rootDir}/.agentuity/app.generated.ts`;
69
-
60
+
70
61
  // Set PORT env var so the generated app uses the correct port
71
62
  process.env.PORT = String(port);
72
-
63
+
73
64
  await import(appPath);
74
65
 
75
66
  // Wait for server to actually start listening
@@ -105,11 +96,10 @@ export async function startBunDevServer(options: BunDevServerOptions): Promise<B
105
96
  );
106
97
  }
107
98
 
108
- logger.info(`✅ Bun dev server started on http://127.0.0.1:${port}`);
99
+ logger.debug(`Bun dev server started on http://127.0.0.1:${port}`);
109
100
  logger.debug(`Asset requests (/@vite/*, /src/web/*, etc.) proxied to Vite:${vitePort}`);
110
101
 
111
102
  return {
112
- viteAssetServer,
113
103
  bunServerPort: port,
114
104
  };
115
105
  }
@@ -11,7 +11,6 @@ import { generateEntryFile } from '../entry-generator';
11
11
  import { loadAgentuityConfig, getWorkbenchConfig } from './config-loader';
12
12
 
13
13
  // Re-export plugins
14
- export { patchPlugin } from './patch-plugin';
15
14
  export { browserEnvPlugin } from './browser-env-plugin';
16
15
 
17
16
  export interface AgentuityPluginOptions {
@@ -6,6 +6,8 @@
6
6
  import { join } from 'node:path';
7
7
  import { readdir, stat } from 'node:fs/promises';
8
8
  import type { Logger } from '../../../types';
9
+ import type { BunPlugin } from 'bun';
10
+ import { generatePatches, applyPatch } from '../patch';
9
11
 
10
12
  export interface ServerBundleOptions {
11
13
  rootDir: string;
@@ -35,18 +37,32 @@ export async function installExternalsAndBuild(options: ServerBundleOptions): Pr
35
37
  // These are devDependencies that may exist in node_modules but aren't needed at runtime
36
38
  const buildToolExternals = ['@babel/*', 'lightningcss', '@vitejs/*', 'vite', 'esbuild'];
37
39
 
38
- // Load custom externals from agentuity.config.ts if it exists
40
+ // Load custom externals and define from agentuity.config.ts if it exists
39
41
  const customExternals: string[] = [];
42
+ let userDefine: Record<string, string> = {};
40
43
  const configPath = join(rootDir, 'agentuity.config.ts');
41
44
  if (await Bun.file(configPath).exists()) {
42
45
  try {
43
46
  const config = await import(configPath);
44
47
  const userConfig = config.default;
48
+
49
+ // Load custom externals (legacy build.external support)
45
50
  if (userConfig?.build?.external && Array.isArray(userConfig.build.external)) {
46
51
  customExternals.push(
47
52
  ...userConfig.build.external.filter((e: unknown) => typeof e === 'string')
48
53
  );
49
54
  }
55
+
56
+ // Load custom define values
57
+ if (userConfig?.define && typeof userConfig.define === 'object') {
58
+ userDefine = userConfig.define;
59
+ if (Object.keys(userDefine).length > 0) {
60
+ logger.debug(
61
+ 'Loaded %d custom define(s) from agentuity.config.ts for server bundle',
62
+ Object.keys(userDefine).length
63
+ );
64
+ }
65
+ }
50
66
  } catch (error) {
51
67
  logger.info('Failed to load agentuity.config.ts for externals:', error);
52
68
  }
@@ -188,6 +204,39 @@ export async function installExternalsAndBuild(options: ServerBundleOptions): Pr
188
204
  logger.debug('Building server with Bun.build...');
189
205
  logger.debug(`External packages (${external.length}): ${external.join(', ')}`);
190
206
 
207
+ // Create Bun plugin to apply LLM patches during bundling
208
+ const patches = generatePatches();
209
+ logger.debug(`Loaded ${patches.size} patch(es) for LLM providers`);
210
+
211
+ const patchPlugin: BunPlugin = {
212
+ name: 'agentuity:patch',
213
+ setup(build) {
214
+ for (const [, patch] of patches) {
215
+ let modulePath = join('node_modules', patch.module, '.*');
216
+ if (patch.filename) {
217
+ modulePath = join('node_modules', patch.module, patch.filename + '.*');
218
+ }
219
+ build.onLoad(
220
+ {
221
+ filter: new RegExp(modulePath),
222
+ namespace: 'file',
223
+ },
224
+ async (args) => {
225
+ if (build.config.target !== 'bun') {
226
+ return;
227
+ }
228
+ logger.trace(`Applying patch to: ${args.path}`);
229
+ const [contents, loader] = await applyPatch(args.path, patch);
230
+ return {
231
+ contents,
232
+ loader,
233
+ };
234
+ }
235
+ );
236
+ }
237
+ },
238
+ };
239
+
191
240
  const buildConfig = {
192
241
  entrypoints: [entryPath],
193
242
  outdir: outDir, // Output to .agentuity/ directly (not .agentuity/server/)
@@ -197,6 +246,8 @@ export async function installExternalsAndBuild(options: ServerBundleOptions): Pr
197
246
  minify: !dev,
198
247
  sourcemap: (dev ? 'inline' : 'external') as 'inline' | 'external',
199
248
  external,
249
+ define: userDefine, // Include custom define values from agentuity.config.ts
250
+ plugins: [patchPlugin],
200
251
  naming: {
201
252
  entry: 'app.js', // Output as app.js (not app.generated.js)
202
253
  },
@@ -24,6 +24,22 @@ export async function generateAssetServerConfig(
24
24
  ): Promise<InlineConfig> {
25
25
  const { rootDir, logger, workbenchPath, port } = options;
26
26
 
27
+ // Load custom user config for define values and plugins
28
+ const { loadAgentuityConfig } = await import('./config-loader');
29
+ const userConfig = await loadAgentuityConfig(rootDir, logger);
30
+ const userDefine = userConfig?.define || {};
31
+ const userPlugins = userConfig?.plugins || [];
32
+
33
+ if (Object.keys(userDefine).length > 0) {
34
+ logger.debug(
35
+ 'Loaded %d custom define(s) from agentuity.config.ts',
36
+ Object.keys(userDefine).length
37
+ );
38
+ }
39
+ if (userPlugins.length > 0) {
40
+ logger.debug('Loaded %d custom plugin(s) from agentuity.config.ts', userPlugins.length);
41
+ }
42
+
27
43
  // Load path aliases from tsconfig.json if available
28
44
  const tsconfigPath = join(rootDir, 'tsconfig.json');
29
45
  let alias = {};
@@ -82,6 +98,9 @@ export async function generateAssetServerConfig(
82
98
 
83
99
  // Define environment variables for browser
84
100
  define: {
101
+ // Merge user-defined constants first
102
+ ...userDefine,
103
+ // Then add default defines (these will override any user-defined protected keys)
85
104
  ...(workbenchPath
86
105
  ? { 'import.meta.env.AGENTUITY_PUBLIC_WORKBENCH_PATH': JSON.stringify(workbenchPath) }
87
106
  : {}),
@@ -91,8 +110,10 @@ export async function generateAssetServerConfig(
91
110
  'process.env.NODE_ENV': JSON.stringify('development'),
92
111
  },
93
112
 
94
- // Minimal plugins - just React and HMR
113
+ // Plugins: User plugins first (e.g., Tailwind), then React and browser env
95
114
  plugins: [
115
+ // User-defined plugins from agentuity.config.ts (e.g., Tailwind CSS)
116
+ ...userPlugins,
96
117
  // React plugin for JSX/TSX transformation and Fast Refresh
97
118
  (await import('@vitejs/plugin-react')).default(),
98
119
  // Browser env plugin to map process.env to import.meta.env
@@ -55,7 +55,7 @@ export async function startViteAssetServer(
55
55
  // Get the actual port Vite is using (may differ from preferred if port was taken)
56
56
  const actualPort = server.config.server.port || preferredPort;
57
57
 
58
- logger.info(`✅ Vite asset server started on port ${actualPort}`);
58
+ logger.debug(`✅ Vite asset server started on port ${actualPort}`);
59
59
  if (actualPort !== preferredPort) {
60
60
  logger.debug(`Port ${preferredPort} was taken, using ${actualPort} instead`);
61
61
  }
@@ -7,7 +7,6 @@
7
7
  import { join } from 'node:path';
8
8
  import type { InlineConfig } from 'vite';
9
9
  import type { Logger } from '../../../types';
10
- import { patchPlugin } from './patch-plugin';
11
10
  import { browserEnvPlugin } from './browser-env-plugin';
12
11
 
13
12
  export interface ViteBuildOptions {
@@ -54,7 +53,7 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
54
53
  logger,
55
54
  });
56
55
  } catch (error) {
57
- logger.error('server-bundler import or execution failed:', error);
56
+ logger.error('server-bundler import or execution failed: %s', error);
58
57
  throw error;
59
58
  }
60
59
  return;
@@ -76,7 +75,7 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
76
75
  const { workbenchEnabled = false, workbenchRoute = '/workbench' } = options;
77
76
 
78
77
  // Load custom user plugins from agentuity.config.ts if it exists
79
- const plugins = [react(), browserEnvPlugin(), patchPlugin({ logger, dev })];
78
+ const plugins = [react(), browserEnvPlugin()];
80
79
  const { loadAgentuityConfig } = await import('./config-loader');
81
80
  const userConfig = await loadAgentuityConfig(rootDir, logger);
82
81
  const userPlugins = userConfig?.plugins || [];
@@ -85,6 +84,15 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
85
84
  logger.debug('Loaded %d custom plugin(s) from agentuity.config.ts', userPlugins.length);
86
85
  }
87
86
 
87
+ // Merge custom define values from user config
88
+ const userDefine = userConfig?.define || {};
89
+ if (Object.keys(userDefine).length > 0) {
90
+ logger.debug(
91
+ 'Loaded %d custom define(s) from agentuity.config.ts',
92
+ Object.keys(userDefine).length
93
+ );
94
+ }
95
+
88
96
  // Determine CDN base URL for production builds
89
97
  const isLocalRegion = options.region === 'local';
90
98
  const cdnBaseUrl =
@@ -99,6 +107,9 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
99
107
  publicDir: join(rootDir, 'src', 'web', 'public'),
100
108
  base: cdnBaseUrl, // CDN URL for production assets
101
109
  define: {
110
+ // Merge user-defined constants first
111
+ ...userDefine,
112
+ // Then add default defines (these will override any user-defined protected keys)
102
113
  // Set workbench path if enabled (use import.meta.env for client code)
103
114
  'import.meta.env.AGENTUITY_PUBLIC_WORKBENCH_PATH': workbenchEnabled
104
115
  ? JSON.stringify(workbenchRoute)
@@ -121,11 +132,26 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
121
132
  // Ensure route ends with / for Vite base
122
133
  const base = workbenchRoute.endsWith('/') ? workbenchRoute : `${workbenchRoute}/`;
123
134
 
135
+ // Load custom user config for define values (same as client mode)
136
+ const { loadAgentuityConfig } = await import('./config-loader');
137
+ const userConfig = await loadAgentuityConfig(rootDir, logger);
138
+ const userDefine = userConfig?.define || {};
139
+ if (Object.keys(userDefine).length > 0) {
140
+ logger.debug(
141
+ 'Loaded %d custom define(s) from agentuity.config.ts for workbench',
142
+ Object.keys(userDefine).length
143
+ );
144
+ }
145
+
124
146
  viteConfig = {
125
147
  root: join(rootDir, '.agentuity/workbench-src'), // Use generated workbench source
126
148
  base, // All workbench assets are under the configured route
127
- plugins: [react(), patchPlugin({ logger, dev })],
149
+ plugins: [react()],
128
150
  envPrefix: ['VITE_', 'AGENTUITY_PUBLIC_', 'PUBLIC_'],
151
+ define: {
152
+ // Merge user-defined constants
153
+ ...userDefine,
154
+ },
129
155
  build: {
130
156
  outDir: join(rootDir, '.agentuity/workbench'),
131
157
  rollupOptions: {
@@ -4,6 +4,7 @@ import { createPublicKey } from 'node:crypto';
4
4
  import { createReadStream, createWriteStream, existsSync, mkdirSync, writeFileSync } from 'node:fs';
5
5
  import { tmpdir } from 'node:os';
6
6
  import { StructuredError } from '@agentuity/core';
7
+ import { isRunningFromExecutable } from '../upgrade';
7
8
  import { createSubcommand } from '../../types';
8
9
  import * as tui from '../../tui';
9
10
  import { saveProjectDir, getDefaultConfigDir } from '../../config';
@@ -335,33 +336,49 @@ export const deploySubcommand = createSubcommand({
335
336
  );
336
337
  }
337
338
 
338
- const promises: Promise<Response>[] = [];
339
- for (const asset of build.assets) {
340
- const assetUrl = instructions.assets[asset.filename];
341
- if (!assetUrl) {
342
- return stepError(
343
- `server did not provide upload URL for asset "${asset.filename}"; upload aborted`
344
- );
345
- }
339
+ // Workaround for Bun crash in compiled executables (https://github.com/agentuity/sdk/issues/191)
340
+ // Use limited concurrency (2 at a time) for executables to avoid parallel fetch crash
341
+ const isExecutable = isRunningFromExecutable();
342
+ const concurrency = isExecutable ? 2 : build.assets.length;
346
343
 
347
- // Asset filename already includes the subdirectory (e.g., "client/assets/main-abc123.js")
348
- const file = Bun.file(join(projectDir, '.agentuity', asset.filename));
349
- promises.push(
350
- fetch(assetUrl, {
351
- method: 'PUT',
352
- duplex: 'half',
353
- headers: {
354
- 'Content-Type': asset.contentType,
355
- },
356
- body: file,
357
- })
344
+ if (isExecutable) {
345
+ ctx.logger.trace(
346
+ `Running from executable - using limited concurrency (${concurrency} uploads at a time)`
358
347
  );
359
348
  }
360
- ctx.logger.trace('Waiting for asset uploads');
361
- const resps = await Promise.all(promises);
362
- for (const r of resps) {
363
- if (!r.ok) {
364
- return stepError(`error uploading asset: ${await r.text()}`);
349
+
350
+ // Process assets in batches with limited concurrency
351
+ for (let i = 0; i < build.assets.length; i += concurrency) {
352
+ const batch = build.assets.slice(i, i + concurrency);
353
+ const promises: Promise<Response>[] = [];
354
+
355
+ for (const asset of batch) {
356
+ const assetUrl = instructions.assets[asset.filename];
357
+ if (!assetUrl) {
358
+ return stepError(
359
+ `server did not provide upload URL for asset "${asset.filename}"; upload aborted`
360
+ );
361
+ }
362
+
363
+ // Asset filename already includes the subdirectory (e.g., "client/assets/main-abc123.js")
364
+ const file = Bun.file(join(projectDir, '.agentuity', asset.filename));
365
+ promises.push(
366
+ fetch(assetUrl, {
367
+ method: 'PUT',
368
+ duplex: 'half',
369
+ headers: {
370
+ 'Content-Type': asset.contentType,
371
+ },
372
+ body: file,
373
+ })
374
+ );
375
+ }
376
+
377
+ const resps = await Promise.all(promises);
378
+ for (const r of resps) {
379
+ if (!r.ok) {
380
+ return stepError(`error uploading asset: ${await r.text()}`);
381
+ }
365
382
  }
366
383
  }
367
384
  ctx.logger.trace('Asset uploads complete');