@agentuity/cli 1.0.59 → 2.0.0-beta.1

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 (189) hide show
  1. package/bin/cli.ts +2 -3
  2. package/dist/cmd/build/app-config-extractor.d.ts +27 -0
  3. package/dist/cmd/build/app-config-extractor.d.ts.map +1 -0
  4. package/dist/cmd/build/app-config-extractor.js +152 -0
  5. package/dist/cmd/build/app-config-extractor.js.map +1 -0
  6. package/dist/cmd/build/app-router-detector.d.ts +2 -5
  7. package/dist/cmd/build/app-router-detector.d.ts.map +1 -1
  8. package/dist/cmd/build/app-router-detector.js +130 -154
  9. package/dist/cmd/build/app-router-detector.js.map +1 -1
  10. package/dist/cmd/build/ci.d.ts.map +1 -1
  11. package/dist/cmd/build/ci.js +5 -21
  12. package/dist/cmd/build/ci.js.map +1 -1
  13. package/dist/cmd/build/ids.d.ts +11 -0
  14. package/dist/cmd/build/ids.d.ts.map +1 -0
  15. package/dist/cmd/build/ids.js +18 -0
  16. package/dist/cmd/build/ids.js.map +1 -0
  17. package/dist/cmd/build/index.d.ts.map +1 -1
  18. package/dist/cmd/build/index.js +8 -0
  19. package/dist/cmd/build/index.js.map +1 -1
  20. package/dist/cmd/build/vite/agent-discovery.d.ts +8 -4
  21. package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
  22. package/dist/cmd/build/vite/agent-discovery.js +166 -487
  23. package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
  24. package/dist/cmd/build/vite/bun-dev-server.d.ts +43 -14
  25. package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
  26. package/dist/cmd/build/vite/bun-dev-server.js +290 -129
  27. package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
  28. package/dist/cmd/build/vite/config-loader.d.ts +15 -20
  29. package/dist/cmd/build/vite/config-loader.d.ts.map +1 -1
  30. package/dist/cmd/build/vite/config-loader.js +41 -74
  31. package/dist/cmd/build/vite/config-loader.js.map +1 -1
  32. package/dist/cmd/build/vite/docs-generator.d.ts.map +1 -1
  33. package/dist/cmd/build/vite/docs-generator.js +0 -2
  34. package/dist/cmd/build/vite/docs-generator.js.map +1 -1
  35. package/dist/cmd/build/vite/index.d.ts.map +1 -1
  36. package/dist/cmd/build/vite/index.js +0 -36
  37. package/dist/cmd/build/vite/index.js.map +1 -1
  38. package/dist/cmd/build/vite/lifecycle-generator.d.ts +10 -2
  39. package/dist/cmd/build/vite/lifecycle-generator.d.ts.map +1 -1
  40. package/dist/cmd/build/vite/lifecycle-generator.js +302 -23
  41. package/dist/cmd/build/vite/lifecycle-generator.js.map +1 -1
  42. package/dist/cmd/build/vite/route-discovery.d.ts +11 -38
  43. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
  44. package/dist/cmd/build/vite/route-discovery.js +97 -177
  45. package/dist/cmd/build/vite/route-discovery.js.map +1 -1
  46. package/dist/cmd/build/vite/server-bundler.js +1 -1
  47. package/dist/cmd/build/vite/server-bundler.js.map +1 -1
  48. package/dist/cmd/build/vite/static-renderer.d.ts +0 -2
  49. package/dist/cmd/build/vite/static-renderer.d.ts.map +1 -1
  50. package/dist/cmd/build/vite/static-renderer.js +19 -13
  51. package/dist/cmd/build/vite/static-renderer.js.map +1 -1
  52. package/dist/cmd/build/vite/vite-asset-server-config.d.ts +6 -3
  53. package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
  54. package/dist/cmd/build/vite/vite-asset-server-config.js +175 -69
  55. package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
  56. package/dist/cmd/build/vite/vite-asset-server.d.ts +8 -3
  57. package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
  58. package/dist/cmd/build/vite/vite-asset-server.js +14 -13
  59. package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
  60. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  61. package/dist/cmd/build/vite/vite-builder.js +42 -190
  62. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  63. package/dist/cmd/build/vite/ws-proxy.d.ts +53 -0
  64. package/dist/cmd/build/vite/ws-proxy.d.ts.map +1 -0
  65. package/dist/cmd/build/vite/ws-proxy.js +95 -0
  66. package/dist/cmd/build/vite/ws-proxy.js.map +1 -0
  67. package/dist/cmd/build/vite-bundler.d.ts.map +1 -1
  68. package/dist/cmd/build/vite-bundler.js +0 -3
  69. package/dist/cmd/build/vite-bundler.js.map +1 -1
  70. package/dist/cmd/cloud/deploy-fork.d.ts.map +1 -1
  71. package/dist/cmd/cloud/deploy-fork.js +15 -36
  72. package/dist/cmd/cloud/deploy-fork.js.map +1 -1
  73. package/dist/cmd/cloud/sandbox/exec.d.ts.map +1 -1
  74. package/dist/cmd/cloud/sandbox/exec.js +28 -86
  75. package/dist/cmd/cloud/sandbox/exec.js.map +1 -1
  76. package/dist/cmd/cloud/sandbox/run.d.ts.map +1 -1
  77. package/dist/cmd/cloud/sandbox/run.js +2 -9
  78. package/dist/cmd/cloud/sandbox/run.js.map +1 -1
  79. package/dist/cmd/cloud/sandbox/snapshot/build.js +2 -2
  80. package/dist/cmd/cloud/sandbox/snapshot/build.js.map +1 -1
  81. package/dist/cmd/coder/hub-url.d.ts.map +1 -1
  82. package/dist/cmd/coder/hub-url.js +1 -3
  83. package/dist/cmd/coder/hub-url.js.map +1 -1
  84. package/dist/cmd/coder/start.js +6 -6
  85. package/dist/cmd/coder/start.js.map +1 -1
  86. package/dist/cmd/coder/tui-init.d.ts +2 -2
  87. package/dist/cmd/coder/tui-init.js +2 -2
  88. package/dist/cmd/coder/tui-init.js.map +1 -1
  89. package/dist/cmd/dev/file-watcher.d.ts.map +1 -1
  90. package/dist/cmd/dev/file-watcher.js +2 -8
  91. package/dist/cmd/dev/file-watcher.js.map +1 -1
  92. package/dist/cmd/dev/index.d.ts.map +1 -1
  93. package/dist/cmd/dev/index.js +432 -752
  94. package/dist/cmd/dev/index.js.map +1 -1
  95. package/dist/cmd/dev/process-manager.d.ts +104 -0
  96. package/dist/cmd/dev/process-manager.d.ts.map +1 -0
  97. package/dist/cmd/dev/process-manager.js +204 -0
  98. package/dist/cmd/dev/process-manager.js.map +1 -0
  99. package/dist/errors.d.ts +10 -24
  100. package/dist/errors.d.ts.map +1 -1
  101. package/dist/errors.js +12 -42
  102. package/dist/errors.js.map +1 -1
  103. package/dist/schema-generator.d.ts.map +1 -1
  104. package/dist/schema-generator.js +12 -2
  105. package/dist/schema-generator.js.map +1 -1
  106. package/dist/tui.d.ts.map +1 -1
  107. package/dist/tui.js +5 -19
  108. package/dist/tui.js.map +1 -1
  109. package/dist/utils/version-mismatch.d.ts +39 -0
  110. package/dist/utils/version-mismatch.d.ts.map +1 -0
  111. package/dist/utils/version-mismatch.js +161 -0
  112. package/dist/utils/version-mismatch.js.map +1 -0
  113. package/package.json +6 -6
  114. package/src/cmd/ai/prompt/agent.md +0 -1
  115. package/src/cmd/ai/prompt/api.md +0 -7
  116. package/src/cmd/ai/prompt/web.md +51 -213
  117. package/src/cmd/build/app-config-extractor.ts +186 -0
  118. package/src/cmd/build/app-router-detector.ts +152 -182
  119. package/src/cmd/build/ci.ts +5 -21
  120. package/src/cmd/build/ids.ts +19 -0
  121. package/src/cmd/build/index.ts +10 -0
  122. package/src/cmd/build/vite/agent-discovery.ts +208 -679
  123. package/src/cmd/build/vite/bun-dev-server.ts +383 -146
  124. package/src/cmd/build/vite/config-loader.ts +45 -77
  125. package/src/cmd/build/vite/docs-generator.ts +0 -2
  126. package/src/cmd/build/vite/index.ts +1 -42
  127. package/src/cmd/build/vite/lifecycle-generator.ts +345 -21
  128. package/src/cmd/build/vite/route-discovery.ts +116 -274
  129. package/src/cmd/build/vite/server-bundler.ts +1 -1
  130. package/src/cmd/build/vite/static-renderer.ts +23 -15
  131. package/src/cmd/build/vite/vite-asset-server-config.ts +200 -70
  132. package/src/cmd/build/vite/vite-asset-server.ts +25 -15
  133. package/src/cmd/build/vite/vite-builder.ts +49 -220
  134. package/src/cmd/build/vite/ws-proxy.ts +126 -0
  135. package/src/cmd/build/vite-bundler.ts +0 -4
  136. package/src/cmd/cloud/deploy-fork.ts +16 -39
  137. package/src/cmd/cloud/sandbox/exec.ts +23 -130
  138. package/src/cmd/cloud/sandbox/run.ts +2 -9
  139. package/src/cmd/cloud/sandbox/snapshot/build.ts +2 -2
  140. package/src/cmd/coder/hub-url.ts +1 -3
  141. package/src/cmd/coder/start.ts +6 -6
  142. package/src/cmd/coder/tui-init.ts +4 -4
  143. package/src/cmd/dev/file-watcher.ts +2 -9
  144. package/src/cmd/dev/index.ts +476 -859
  145. package/src/cmd/dev/process-manager.ts +261 -0
  146. package/src/errors.ts +12 -44
  147. package/src/schema-generator.ts +12 -2
  148. package/src/tui.ts +5 -18
  149. package/src/utils/version-mismatch.ts +204 -0
  150. package/dist/cmd/build/ast.d.ts +0 -78
  151. package/dist/cmd/build/ast.d.ts.map +0 -1
  152. package/dist/cmd/build/ast.js +0 -2703
  153. package/dist/cmd/build/ast.js.map +0 -1
  154. package/dist/cmd/build/entry-generator.d.ts +0 -25
  155. package/dist/cmd/build/entry-generator.d.ts.map +0 -1
  156. package/dist/cmd/build/entry-generator.js +0 -695
  157. package/dist/cmd/build/entry-generator.js.map +0 -1
  158. package/dist/cmd/build/vite/api-mount-path.d.ts +0 -61
  159. package/dist/cmd/build/vite/api-mount-path.d.ts.map +0 -1
  160. package/dist/cmd/build/vite/api-mount-path.js +0 -83
  161. package/dist/cmd/build/vite/api-mount-path.js.map +0 -1
  162. package/dist/cmd/build/vite/registry-generator.d.ts +0 -19
  163. package/dist/cmd/build/vite/registry-generator.d.ts.map +0 -1
  164. package/dist/cmd/build/vite/registry-generator.js +0 -1108
  165. package/dist/cmd/build/vite/registry-generator.js.map +0 -1
  166. package/dist/cmd/build/webanalytics-generator.d.ts +0 -16
  167. package/dist/cmd/build/webanalytics-generator.d.ts.map +0 -1
  168. package/dist/cmd/build/webanalytics-generator.js +0 -178
  169. package/dist/cmd/build/webanalytics-generator.js.map +0 -1
  170. package/dist/cmd/build/workbench.d.ts +0 -7
  171. package/dist/cmd/build/workbench.d.ts.map +0 -1
  172. package/dist/cmd/build/workbench.js +0 -55
  173. package/dist/cmd/build/workbench.js.map +0 -1
  174. package/dist/utils/route-migration.d.ts +0 -62
  175. package/dist/utils/route-migration.d.ts.map +0 -1
  176. package/dist/utils/route-migration.js +0 -630
  177. package/dist/utils/route-migration.js.map +0 -1
  178. package/dist/utils/stream-capture.d.ts +0 -9
  179. package/dist/utils/stream-capture.d.ts.map +0 -1
  180. package/dist/utils/stream-capture.js +0 -34
  181. package/dist/utils/stream-capture.js.map +0 -1
  182. package/src/cmd/build/ast.ts +0 -3529
  183. package/src/cmd/build/entry-generator.ts +0 -760
  184. package/src/cmd/build/vite/api-mount-path.ts +0 -87
  185. package/src/cmd/build/vite/registry-generator.ts +0 -1267
  186. package/src/cmd/build/webanalytics-generator.ts +0 -197
  187. package/src/cmd/build/workbench.ts +0 -58
  188. package/src/utils/route-migration.ts +0 -757
  189. package/src/utils/stream-capture.ts +0 -39
@@ -1,25 +1,30 @@
1
1
  /**
2
2
  * Bun Dev Server
3
3
  *
4
- * Runs Bun server that handles ALL app logic (HTTP + WebSocket) and proxies
5
- * frontend asset requests to Vite asset server for HMR support.
4
+ * Spawns Bun with --hot as a subprocess. Bun's --hot mode re-evaluates changed
5
+ * modules and hot-swaps the default export's `fetch` handler on the running
6
+ * server — no process restart, no port rebind, no dropped connections.
7
+ *
8
+ * The user's app.ts exports the result of createApp() which includes `fetch`
9
+ * and `port` properties that Bun uses to manage the server lifecycle.
10
+ *
11
+ * Key requirements for bun --hot:
12
+ * - app.ts MUST have `export default` with { fetch, port } properties
13
+ * - Without export default, Bun runs the code but never starts an HTTP server
6
14
  */
7
15
 
8
16
  import type { Logger } from '../../../types';
9
17
  import { getAgentEnv } from '../../../agent-detection';
18
+ import { createServer as createNetServer } from 'node:net';
10
19
 
11
20
  export interface BunDevServerOptions {
12
21
  rootDir: string;
13
22
  port?: number;
14
- projectId?: string;
15
- orgId?: string;
16
- deploymentId?: string;
17
23
  logger: Logger;
18
- vitePort: number; // Port of already-running Vite asset server
19
- inspect?: boolean; // Enable bun debugger
20
- inspectWait?: boolean; // Enable bun debugger and wait for connection
21
- inspectBrk?: boolean; // Enable bun debugger with breakpoint at first line
22
- noBundle?: boolean; // Run src/generated/app.ts directly without bundling
24
+ vitePort: number;
25
+ inspect?: boolean;
26
+ inspectWait?: boolean;
27
+ inspectBrk?: boolean;
23
28
  }
24
29
 
25
30
  export interface BunDevServerResult {
@@ -27,173 +32,405 @@ export interface BunDevServerResult {
27
32
  }
28
33
 
29
34
  /**
30
- * Start Bun dev server (Vite asset server must already be running)
35
+ * Check if a port is available for binding.
36
+ * Returns true if the port is free, false if in use.
37
+ */
38
+ function isPortAvailable(port: number, host: string = '127.0.0.1'): Promise<boolean> {
39
+ return new Promise((resolve) => {
40
+ const server = createNetServer();
41
+ server.once('error', () => {
42
+ resolve(false);
43
+ });
44
+ server.listen(port, host, () => {
45
+ server.close(() => {
46
+ resolve(true);
47
+ });
48
+ });
49
+ });
50
+ }
51
+
52
+ /**
53
+ * Kill any process listening on the specified port.
54
+ * Uses lsof on Unix systems to find and kill the process.
55
+ */
56
+ async function killProcessOnPort(
57
+ port: number,
58
+ logger: { debug: (msg: string, ...args: unknown[]) => void }
59
+ ): Promise<boolean> {
60
+ if (process.platform === 'win32') {
61
+ // Windows: use netstat to find PID, then taskkill
62
+ // This is more complex and less reliable, skip for now
63
+ return false;
64
+ }
65
+
66
+ try {
67
+ // Find PIDs listening on the port
68
+ const result = Bun.spawnSync(['lsof', '-t', '-i', `:${port}`], {
69
+ stdout: 'pipe',
70
+ stderr: 'ignore',
71
+ });
72
+
73
+ if (result.exitCode !== 0 || !result.stdout) {
74
+ return false;
75
+ }
76
+
77
+ const pids = new TextDecoder()
78
+ .decode(result.stdout)
79
+ .trim()
80
+ .split('\n')
81
+ .filter((line) => line && /^\d+$/.test(line));
82
+
83
+ if (pids.length === 0) {
84
+ return false;
85
+ }
86
+
87
+ // Kill each process
88
+ for (const pid of pids) {
89
+ try {
90
+ // Use SIGKILL to ensure cleanup
91
+ const killResult = Bun.spawnSync(['kill', '-9', pid], {
92
+ stdout: 'ignore',
93
+ stderr: 'ignore',
94
+ });
95
+ if (killResult.exitCode === 0) {
96
+ logger.debug('Killed orphan process %s on port %d', pid, port);
97
+ }
98
+ } catch {
99
+ // Ignore kill errors
100
+ }
101
+ }
102
+
103
+ // Brief pause to let the port be released
104
+ await new Promise((resolve) => setTimeout(resolve, 100));
105
+ return true;
106
+ } catch {
107
+ return false;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Ensure the port is available, cleaning up any orphan processes if needed.
113
+ */
114
+ async function ensurePortAvailable(
115
+ port: number,
116
+ logger: { debug: (msg: string, ...args: unknown[]) => void }
117
+ ): Promise<void> {
118
+ const available = await isPortAvailable(port);
119
+ if (available) {
120
+ return;
121
+ }
122
+
123
+ logger.debug('Port %d is in use, attempting to clean up orphan process...', port);
124
+
125
+ const killed = await killProcessOnPort(port, logger);
126
+ if (killed) {
127
+ // Verify the port is now free
128
+ const nowAvailable = await isPortAvailable(port);
129
+ if (!nowAvailable) {
130
+ throw new Error(
131
+ `Port ${port} is still in use after cleanup. Another process may be holding it.\n` +
132
+ `Run 'lsof -i :${port}' to identify the process.`
133
+ );
134
+ }
135
+ logger.debug('Port %d is now available', port);
136
+ } else {
137
+ throw new Error(
138
+ `Port ${port} is already in use.\n` +
139
+ `Run 'lsof -i :${port}' to identify the process, or kill it manually.`
140
+ );
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Validation result for app.ts entry point.
146
+ */
147
+ interface AppValidationResult {
148
+ /** Whether app.ts has a default export */
149
+ hasDefaultExport: boolean;
150
+ /** Whether app.ts calls createApp() */
151
+ hasCreateApp: boolean;
152
+ /** Whether it's the v1 pattern (destructuring without export) */
153
+ isV1Pattern: boolean;
154
+ /** Any validation hints to show */
155
+ hints: string[];
156
+ }
157
+
158
+ /**
159
+ * Validate app.ts for common issues that prevent Bun --hot from starting.
31
160
  *
32
- * IMPORTANT: This function assumes that the dev bundle has already been built:
33
- * - Entry file generated at src/generated/app.ts (with workbench config if enabled)
34
- * - Bundled to .agentuity/app.js with LLM patches applied
161
+ * Bun --hot requires `export default { fetch, port }` to start a server.
162
+ * Common mistakes:
163
+ * - No `export default` (v1 pattern: destructuring result without exporting)
164
+ * - Calling createApp() but not exporting it
165
+ *
166
+ * @internal Exported for testing only
167
+ */
168
+ export async function validateAppTs(appPath: string): Promise<AppValidationResult> {
169
+ const result: AppValidationResult = {
170
+ hasDefaultExport: false,
171
+ hasCreateApp: false,
172
+ isV1Pattern: false,
173
+ hints: [],
174
+ };
175
+
176
+ const file = Bun.file(appPath);
177
+ if (!(await file.exists())) {
178
+ return result;
179
+ }
180
+
181
+ const content = await file.text();
182
+
183
+ // Strip comments to avoid false positives from commented-out code
184
+ // Simple approach: remove single-line and multi-line comments
185
+ const codeWithoutComments = content
186
+ .replace(/\/\/.*$/gm, '') // Single-line comments
187
+ .replace(/\/\*[\s\S]*?\*\//g, ''); // Multi-line comments
188
+
189
+ // Check for default export patterns (only in actual code, not comments)
190
+ // Matches: export default createApp(...), export default await createApp(...),
191
+ // export default { fetch, port }, const { ... } = await createApp(...) then export default result
192
+ result.hasDefaultExport = /\bexport\s+default\b/.test(codeWithoutComments);
193
+
194
+ // Check for createApp call
195
+ result.hasCreateApp = /\bcreateApp\s*\(/.test(content);
196
+
197
+ // Detect v1 pattern: destructuring createApp result without export default
198
+ // e.g., const { server, logger } = await createApp({...});
199
+ const hasDestructuring = /const\s*\{[^}]*\}\s*=\s*(?:await\s+)?createApp/.test(content);
200
+ if (hasDestructuring && !result.hasDefaultExport) {
201
+ result.isV1Pattern = true;
202
+ result.hints.push(
203
+ 'app.ts calls createApp() but does not export it. Bun --hot requires `export default` to start a server.',
204
+ '',
205
+ 'Fix: Change your app.ts to export the createApp() result:',
206
+ '',
207
+ ' import { createApp } from "@agentuity/runtime";',
208
+ ' import agents from "@agent/index";',
209
+ '',
210
+ ' export default createApp({',
211
+ ' agents,',
212
+ ' router: { path: "/api", router: api },',
213
+ ' });',
214
+ '',
215
+ 'Or if you need the logger:',
216
+ '',
217
+ ' const app = await createApp({ agents });',
218
+ ' app.logger.debug("Running %s", app.server.url);',
219
+ ' export default app;'
220
+ );
221
+ }
222
+
223
+ // Check for missing createApp entirely
224
+ if (!result.hasCreateApp && !content.includes('Bun.serve')) {
225
+ result.hints.push(
226
+ 'app.ts does not call createApp(). This is required for Agentuity apps.',
227
+ '',
228
+ 'Example:',
229
+ '',
230
+ ' import { createApp } from "@agentuity/runtime";',
231
+ ' export default createApp({ agents });'
232
+ );
233
+ }
234
+
235
+ return result;
236
+ }
237
+
238
+ /**
239
+ * Build a detailed error message with validation hints and captured output.
35
240
  *
36
- * The bundle is loaded here to ensure AI Gateway routing patches are active.
37
- * Vite port is read from process.env.VITE_PORT at runtime.
241
+ * @internal Exported for testing only
242
+ */
243
+ export function buildStartupErrorMessage(
244
+ port: number,
245
+ timeoutMs: number,
246
+ stderr: string,
247
+ validation: AppValidationResult
248
+ ): string {
249
+ const lines: string[] = [];
250
+
251
+ lines.push(`Bun server failed to start on port ${port} after ${timeoutMs}ms`);
252
+ lines.push('');
253
+
254
+ // Show captured stderr if any
255
+ if (stderr.trim()) {
256
+ lines.push('Bun output:');
257
+ lines.push('');
258
+ // Indent stderr lines for readability
259
+ for (const line of stderr.trim().split('\n').slice(0, 20)) {
260
+ lines.push(` ${line}`);
261
+ }
262
+ if (stderr.split('\n').length > 20) {
263
+ lines.push(' ... (truncated)');
264
+ }
265
+ lines.push('');
266
+ }
267
+
268
+ // Show validation hints
269
+ if (validation.hints.length > 0) {
270
+ lines.push('Possible issue:');
271
+ lines.push('');
272
+ for (const hint of validation.hints) {
273
+ lines.push(` ${hint}`);
274
+ }
275
+ lines.push('');
276
+ }
277
+
278
+ // Generic troubleshooting if no specific hints
279
+ if (validation.hints.length === 0) {
280
+ lines.push('Troubleshooting:');
281
+ lines.push('');
282
+ lines.push(' 1. Check app.ts exports `export default createApp({...})`');
283
+ lines.push(' 2. Check for TypeScript/syntax errors in your code');
284
+ lines.push(` 3. Check if port ${port} is already in use: lsof -i :${port}`);
285
+ lines.push(' 4. Try running manually: bun run --hot app.ts');
286
+ lines.push('');
287
+ }
288
+
289
+ return lines.join('\n');
290
+ }
291
+
292
+ /**
293
+ * Start Bun dev server with --hot (Vite asset server must already be running).
38
294
  *
39
- * When debugger flags (inspect, inspectWait, inspectBrk) are passed, bun is spawned
40
- * as a subprocess to enable passing the debugger CLI flags.
295
+ * Uses `bun --hot` so Bun watches all imported files and hot-swaps the fetch
296
+ * handler on the running server. The server stays up — only the changed modules
297
+ * are re-evaluated.
41
298
  */
42
299
  export async function startBunDevServer(options: BunDevServerOptions): Promise<BunDevServerResult> {
43
- const {
44
- rootDir,
45
- port = 3500,
46
- logger,
47
- vitePort,
48
- inspect,
49
- inspectWait,
50
- inspectBrk,
51
- noBundle,
52
- } = options;
300
+ const { rootDir, port = 3500, logger, vitePort, inspect, inspectWait, inspectBrk } = options;
53
301
 
54
302
  logger.debug('Starting Bun dev server (Vite already running on port %d)...', vitePort);
55
303
 
56
- const appPath = noBundle ? `${rootDir}/src/generated/app.ts` : `${rootDir}/.agentuity/app.js`;
304
+ const appPath = `${rootDir}/app.ts`;
57
305
 
58
- // Verify entry file exists before attempting to load
59
306
  const appFile = Bun.file(appPath);
60
307
  if (!(await appFile.exists())) {
61
- throw new Error(
62
- noBundle
63
- ? `Generated entry not found at ${appPath}. Run the dev command to generate it.`
64
- : `Dev bundle not found at ${appPath}. The bundle must be generated before starting the dev server.`
65
- );
308
+ throw new Error(`App entry not found at ${appPath}.`);
66
309
  }
67
310
 
68
- // Set PORT env var so the generated app uses the correct port
311
+ // Pre-validate app.ts for common issues
312
+ const validation = await validateAppTs(appPath);
313
+ if (validation.isV1Pattern) {
314
+ logger.warn('');
315
+ logger.warn('⚠️ app.ts may have a v1-style pattern that prevents Bun --hot from starting.');
316
+ for (const hint of validation.hints) {
317
+ logger.warn(' %s', hint);
318
+ }
319
+ logger.warn('');
320
+ }
321
+
322
+ // Ensure the port is available, cleaning up any orphan processes
323
+ await ensurePortAvailable(port, logger);
324
+
69
325
  process.env.PORT = String(port);
70
326
 
71
- // Check if any debugger flag is enabled
72
- const useDebugger = inspect || inspectWait || inspectBrk;
73
-
74
- if (useDebugger) {
75
- // Spawn bun as subprocess with debugger flag
76
- logger.debug('📦 Spawning bun with debugger enabled...');
77
-
78
- // Determine which debugger flag to use (priority: inspectBrk > inspectWait > inspect)
79
- let debugFlag: string;
80
- if (inspectBrk) {
81
- debugFlag = '--inspect-brk';
82
- } else if (inspectWait) {
83
- debugFlag = '--inspect-wait';
84
- } else {
85
- debugFlag = '--inspect';
86
- }
327
+ const args: string[] = ['bun'];
87
328
 
88
- logger.debug('Using debugger flag: %s', debugFlag);
89
-
90
- const bunProcess = Bun.spawn(['bun', debugFlag, 'run', appPath], {
91
- cwd: rootDir,
92
- stdout: 'inherit',
93
- stderr: 'inherit',
94
- env: {
95
- ...process.env,
96
- ...getAgentEnv(),
97
- PORT: String(port),
98
- },
99
- });
329
+ // --hot: in-process hot reload — re-evaluates changed modules and swaps
330
+ // the default export's fetch handler without restarting the process.
331
+ // --no-clear-screen: don't clear terminal on reload (CLI manages output)
332
+ args.push('--hot', '--no-clear-screen');
100
333
 
101
- // Store the process globally so it can be killed on shutdown
102
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
103
- (globalThis as any).__AGENTUITY_BUN_SUBPROCESS__ = bunProcess;
334
+ if (inspectBrk) {
335
+ args.push('--inspect-brk');
336
+ } else if (inspectWait) {
337
+ args.push('--inspect-wait');
338
+ } else if (inspect) {
339
+ args.push('--inspect');
340
+ }
104
341
 
105
- // Wait for server to actually start listening
106
- const maxRetries = 50;
107
- const retryDelay = 100;
108
- let serverReady = false;
342
+ args.push('run', appPath);
109
343
 
110
- for (let i = 0; i < maxRetries; i++) {
111
- try {
112
- await fetch(`http://127.0.0.1:${port}/`, {
113
- method: 'HEAD',
114
- signal: AbortSignal.timeout(1000),
115
- });
116
- // Any response (even 404) means server is listening
117
- serverReady = true;
118
- break;
119
- } catch {
120
- // Connection refused or timeout - server not ready yet
344
+ logger.debug('Spawning bun subprocess: %s', args.join(' '));
345
+
346
+ // Capture stderr for error reporting while still showing it in real-time
347
+ const stderrChunks: string[] = [];
348
+ const stdoutChunks: string[] = [];
349
+
350
+ // Helper to read a stream, capture output, and forward to parent
351
+ const captureStream = async (
352
+ stream: ReadableStream<Uint8Array>,
353
+ chunks: string[],
354
+ output: typeof process.stdout | typeof process.stderr
355
+ ) => {
356
+ const reader = stream.getReader();
357
+ try {
358
+ while (true) {
359
+ const { done, value } = await reader.read();
360
+ if (done) break;
361
+ const str = new TextDecoder().decode(value);
362
+ chunks.push(str);
363
+ output.write(value); // Forward to parent for real-time visibility
121
364
  }
122
- // Wait before next check
123
- await new Promise((resolve) => setTimeout(resolve, retryDelay));
365
+ } catch {
366
+ // Stream may be closed unexpectedly when process exits
124
367
  }
368
+ };
125
369
 
126
- if (!serverReady) {
127
- // Kill the subprocess if server didn't start
128
- try {
129
- bunProcess.kill();
130
- } catch (err) {
131
- logger.debug('Error killing subprocess during startup failure: %s', err);
132
- }
370
+ const bunProcess = Bun.spawn(args, {
371
+ cwd: rootDir,
372
+ stdout: 'pipe',
373
+ stderr: 'pipe',
374
+ env: {
375
+ ...process.env,
376
+ ...getAgentEnv(),
377
+ PORT: String(port),
378
+ },
379
+ });
380
+
381
+ // Start capturing streams in the background (don't await, we need to check server readiness)
382
+ if (bunProcess.stdout) {
383
+ captureStream(bunProcess.stdout, stdoutChunks, process.stdout).catch(() => {});
384
+ }
385
+ if (bunProcess.stderr) {
386
+ captureStream(bunProcess.stderr, stderrChunks, process.stderr).catch(() => {});
387
+ }
388
+
389
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
390
+ (globalThis as any).__AGENTUITY_BUN_SUBPROCESS__ = bunProcess;
391
+
392
+ // Wait for server to start listening
393
+ const maxRetries = 50;
394
+ const retryDelay = 100;
395
+ const timeoutMs = maxRetries * retryDelay;
396
+ let serverReady = false;
397
+
398
+ for (let i = 0; i < maxRetries; i++) {
399
+ if (bunProcess.exitCode !== null) {
400
+ const stderr = stderrChunks.join('');
133
401
  throw new Error(
134
- `Bun server failed to start on port ${port} after ${maxRetries * retryDelay}ms`
402
+ `Bun subprocess exited with code ${bunProcess.exitCode} during startup\n\n${stderr}`
135
403
  );
136
404
  }
137
405
 
138
- logger.debug(`Bun dev server started on http://127.0.0.1:${port} with debugger enabled`);
139
- logger.debug(`Asset requests (/@vite/*, /src/web/*, etc.) proxied to Vite:${vitePort}`);
140
- } else {
141
- // Load the app entry - this will start Bun.serve() internally
142
- // In bundle mode: imports .agentuity/app.js (with build-time LLM patches)
143
- // In no-bundle mode: imports src/generated/app.ts directly (with runtime patches)
144
- logger.debug('Loading app from: %s (noBundle: %s)', appPath, !!noBundle);
145
- logger.debug('📦 Loading app entry (Bun server will start)...');
146
-
147
- // Import the generated app with cache-busting query parameter.
148
- // Bun's module cache is keyed by the full specifier including query string,
149
- // so adding a unique timestamp forces a fresh import on each reload.
150
- const cacheBuster = `?t=${Date.now()}`;
151
406
  try {
152
- await import(appPath + cacheBuster);
153
- } catch (err) {
154
- const errorMessage = err instanceof Error ? err.message : String(err);
155
- logger.error('Failed to import generated app from %s: %s', appPath, errorMessage);
156
- throw new Error(`Failed to load generated app: ${errorMessage}`);
157
- }
158
-
159
- // Wait for server to actually start listening
160
- // The generated app sets (globalThis as any).__AGENTUITY_SERVER__ when server starts
161
- const maxRetries = 50; // Increased retries for slower systems
162
- const retryDelay = 100; // ms
163
- let serverReady = false;
164
-
165
- for (let i = 0; i < maxRetries; i++) {
166
- // Check if global server object exists
167
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
168
- if ((globalThis as any).__AGENTUITY_SERVER__) {
169
- // Server object exists, now verify it's actually listening by making a request
170
- try {
171
- await fetch(`http://127.0.0.1:${port}/`, {
172
- method: 'HEAD',
173
- signal: AbortSignal.timeout(1000),
174
- });
175
- // Any response (even 404) means server is listening
176
- serverReady = true;
177
- break;
178
- } catch {
179
- // Connection refused or timeout - server not ready yet
180
- }
181
- }
182
- // Wait before next check
183
- await new Promise((resolve) => setTimeout(resolve, retryDelay));
407
+ await fetch(`http://127.0.0.1:${port}/`, {
408
+ method: 'HEAD',
409
+ signal: AbortSignal.timeout(1000),
410
+ });
411
+ serverReady = true;
412
+ break;
413
+ } catch {
414
+ // Not ready yet
184
415
  }
416
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
417
+ }
185
418
 
186
- if (!serverReady) {
187
- throw new Error(
188
- `Bun server failed to start on port ${port} after ${maxRetries * retryDelay}ms`
189
- );
419
+ if (!serverReady) {
420
+ try {
421
+ bunProcess.kill();
422
+ } catch (err) {
423
+ logger.debug('Error killing subprocess during startup failure: %s', err);
190
424
  }
425
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
426
+ (globalThis as any).__AGENTUITY_BUN_SUBPROCESS__ = undefined;
191
427
 
192
- logger.debug(`Bun dev server started on http://127.0.0.1:${port}`);
193
- logger.debug(`Asset requests (/@vite/*, /src/web/*, etc.) proxied to Vite:${vitePort}`);
428
+ const stderr = stderrChunks.join('');
429
+ throw new Error(buildStartupErrorMessage(port, timeoutMs, stderr, validation));
194
430
  }
195
431
 
196
- return {
197
- bunServerPort: port,
198
- };
432
+ logger.debug(`Bun dev server started on http://127.0.0.1:${port} (--hot mode)`);
433
+ logger.debug(`Proxied to Vite:${vitePort}`);
434
+
435
+ return { bunServerPort: port };
199
436
  }