@agentuity/cli 0.0.107 → 0.0.108
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.
- package/dist/cmd/build/entry-generator.d.ts.map +1 -1
- package/dist/cmd/build/entry-generator.js +43 -50
- package/dist/cmd/build/entry-generator.js.map +1 -1
- package/dist/cmd/build/index.d.ts.map +1 -1
- package/dist/cmd/build/index.js +9 -9
- package/dist/cmd/build/index.js.map +1 -1
- package/dist/cmd/build/typecheck.d.ts +23 -0
- package/dist/cmd/build/typecheck.d.ts.map +1 -0
- package/dist/cmd/build/typecheck.js +38 -0
- package/dist/cmd/build/typecheck.js.map +1 -0
- package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.js +15 -8
- package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.js +6 -2
- package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
- package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-builder.js +14 -2
- package/dist/cmd/build/vite/vite-builder.js.map +1 -1
- package/dist/cmd/cloud/deploy.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.js +67 -6
- package/dist/cmd/cloud/deploy.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +623 -578
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/schema-parser.d.ts.map +1 -1
- package/dist/schema-parser.js +17 -3
- package/dist/schema-parser.js.map +1 -1
- package/dist/tsc-output-parser.d.ts +54 -0
- package/dist/tsc-output-parser.d.ts.map +1 -0
- package/dist/tsc-output-parser.js +926 -0
- package/dist/tsc-output-parser.js.map +1 -0
- package/dist/tui.d.ts +77 -0
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +27 -2
- package/dist/tui.js.map +1 -1
- package/dist/typescript-errors.d.ts +26 -0
- package/dist/typescript-errors.d.ts.map +1 -0
- package/dist/typescript-errors.js +249 -0
- package/dist/typescript-errors.js.map +1 -0
- package/package.json +4 -4
- package/src/cmd/build/entry-generator.ts +43 -51
- package/src/cmd/build/index.ts +13 -10
- package/src/cmd/build/typecheck.ts +55 -0
- package/src/cmd/build/vite/vite-asset-server-config.ts +17 -8
- package/src/cmd/build/vite/vite-asset-server.ts +6 -2
- package/src/cmd/build/vite/vite-builder.ts +13 -2
- package/src/cmd/cloud/deploy.ts +80 -8
- package/src/cmd/dev/index.ts +713 -657
- package/src/schema-parser.ts +17 -3
- package/src/tsc-output-parser.ts +1115 -0
- package/src/tui.ts +40 -2
- package/src/typescript-errors.ts +382 -0
package/dist/cmd/dev/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { download } from './download';
|
|
|
13
13
|
import { createDevmodeSyncService } from './sync';
|
|
14
14
|
import { getDevmodeDeploymentId } from '../build/ast';
|
|
15
15
|
import { getDefaultConfigDir, saveConfig, loadProjectSDKKey } from '../../config';
|
|
16
|
+
import { typecheck } from '../build/typecheck';
|
|
16
17
|
import { createFileWatcher } from './file-watcher';
|
|
17
18
|
import { regenerateSkillsAsync } from './skills';
|
|
18
19
|
import { prepareDevLock, releaseLockSync } from './dev-lock';
|
|
@@ -42,6 +43,9 @@ async function killLingeringGravityProcesses(logger) {
|
|
|
42
43
|
// Brief pause to let processes fully terminate
|
|
43
44
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
44
45
|
}
|
|
46
|
+
else if (result.exitCode === 1) {
|
|
47
|
+
logger.debug('no lingering gravity processes found');
|
|
48
|
+
}
|
|
45
49
|
}
|
|
46
50
|
catch {
|
|
47
51
|
// pkill not available or failed - not critical, continue
|
|
@@ -169,638 +173,679 @@ export const command = createCommand({
|
|
|
169
173
|
// Kill any lingering gravity processes from previous dev sessions
|
|
170
174
|
// This is a fallback for cases where the lockfile was corrupted
|
|
171
175
|
await killLingeringGravityProcesses(logger);
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
_config.devmode = {
|
|
199
|
-
hostname: endpoint.hostname,
|
|
200
|
-
};
|
|
201
|
-
await saveConfig(_config);
|
|
202
|
-
config = _config;
|
|
203
|
-
devmode = endpoint;
|
|
204
|
-
gravityURL = getGravityDevModeURL(project.region, config);
|
|
205
|
-
appURL = `${getAppBaseURL(config)}/r/${project.projectId}`;
|
|
206
|
-
// Download gravity client
|
|
207
|
-
const configDir = getDefaultConfigDir();
|
|
208
|
-
const gravityDir = join(configDir, 'gravity');
|
|
209
|
-
let mustCheck = true;
|
|
210
|
-
if (config?.gravity?.version &&
|
|
211
|
-
existsSync(join(gravityDir, config.gravity.version, 'gravity')) &&
|
|
212
|
-
config?.gravity?.checked) {
|
|
213
|
-
if (Date.now() - config.gravity.checked < 3.6e6) {
|
|
214
|
-
mustCheck = false;
|
|
215
|
-
gravityBin = join(gravityDir, config.gravity.version, 'gravity');
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
if (mustCheck) {
|
|
219
|
-
const res = await download(gravityDir);
|
|
220
|
-
gravityBin = res.filename;
|
|
176
|
+
try {
|
|
177
|
+
// Setup devmode and gravity (if using public URL)
|
|
178
|
+
const useMockService = process.env.DEVMODE_SYNC_SERVICE_MOCK === 'true';
|
|
179
|
+
const apiClient = auth ? new APIClient(getAPIBaseURL(config), logger, config) : null;
|
|
180
|
+
const syncService = apiClient
|
|
181
|
+
? createDevmodeSyncService({
|
|
182
|
+
logger,
|
|
183
|
+
apiClient,
|
|
184
|
+
mock: useMockService,
|
|
185
|
+
})
|
|
186
|
+
: null;
|
|
187
|
+
// Track previous metadata for sync diffing
|
|
188
|
+
let previousMetadata;
|
|
189
|
+
let devmode;
|
|
190
|
+
let gravityBin;
|
|
191
|
+
let gravityURL;
|
|
192
|
+
let appURL;
|
|
193
|
+
if (auth && project && opts.public) {
|
|
194
|
+
// Generate devmode endpoint for public URL
|
|
195
|
+
const endpoint = await tui.spinner({
|
|
196
|
+
message: 'Connecting to Gravity',
|
|
197
|
+
callback: () => {
|
|
198
|
+
return generateEndpoint(apiClient, project.projectId, config?.devmode?.hostname);
|
|
199
|
+
},
|
|
200
|
+
clearOnSuccess: true,
|
|
201
|
+
});
|
|
221
202
|
const _config = { ...config };
|
|
222
|
-
_config.
|
|
223
|
-
|
|
224
|
-
version: res.version,
|
|
203
|
+
_config.devmode = {
|
|
204
|
+
hostname: endpoint.hostname,
|
|
225
205
|
};
|
|
226
206
|
await saveConfig(_config);
|
|
227
207
|
config = _config;
|
|
208
|
+
devmode = endpoint;
|
|
209
|
+
gravityURL = getGravityDevModeURL(project.region, config);
|
|
210
|
+
appURL = `${getAppBaseURL(config)}/r/${project.projectId}`;
|
|
211
|
+
// Download gravity client
|
|
212
|
+
const configDir = getDefaultConfigDir();
|
|
213
|
+
const gravityDir = join(configDir, 'gravity');
|
|
214
|
+
let mustCheck = true;
|
|
215
|
+
if (config?.gravity?.version &&
|
|
216
|
+
existsSync(join(gravityDir, config.gravity.version, 'gravity')) &&
|
|
217
|
+
config?.gravity?.checked) {
|
|
218
|
+
if (Date.now() - config.gravity.checked < 3.6e6) {
|
|
219
|
+
mustCheck = false;
|
|
220
|
+
gravityBin = join(gravityDir, config.gravity.version, 'gravity');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (mustCheck) {
|
|
224
|
+
const res = await download(gravityDir);
|
|
225
|
+
gravityBin = res.filename;
|
|
226
|
+
const _config = { ...config };
|
|
227
|
+
_config.gravity = {
|
|
228
|
+
checked: Date.now(),
|
|
229
|
+
version: res.version,
|
|
230
|
+
};
|
|
231
|
+
await saveConfig(_config);
|
|
232
|
+
config = _config;
|
|
233
|
+
}
|
|
228
234
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
centerTitle: false,
|
|
266
|
-
});
|
|
267
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
268
|
-
const cliVersion = global.__CLI_SCHEMA__?.version ?? '';
|
|
269
|
-
if (cliVersion) {
|
|
270
|
-
regenerateSkillsAsync(rootDir, cliVersion, logger).catch(() => { });
|
|
271
|
-
}
|
|
272
|
-
// Start Vite asset server ONCE before restart loop
|
|
273
|
-
// Vite handles frontend HMR independently and stays running across backend restarts
|
|
274
|
-
let viteServer = null;
|
|
275
|
-
let vitePort;
|
|
276
|
-
try {
|
|
277
|
-
logger.debug('Starting Vite asset server...');
|
|
278
|
-
const viteResult = await startViteAssetServer({
|
|
279
|
-
rootDir,
|
|
280
|
-
logger,
|
|
281
|
-
workbenchPath: workbench.config?.route,
|
|
235
|
+
// Get workbench info from config (new Vite approach)
|
|
236
|
+
const { loadAgentuityConfig, getWorkbenchConfig } = await import('../build/vite/config-loader');
|
|
237
|
+
const agentuityConfig = await loadAgentuityConfig(rootDir, ctx.logger);
|
|
238
|
+
const workbenchConfigData = getWorkbenchConfig(agentuityConfig, true); // dev mode
|
|
239
|
+
const workbench = {
|
|
240
|
+
hasWorkbench: workbenchConfigData.enabled,
|
|
241
|
+
config: workbenchConfigData.enabled
|
|
242
|
+
? { route: workbenchConfigData.route, headers: workbenchConfigData.headers }
|
|
243
|
+
: null,
|
|
244
|
+
};
|
|
245
|
+
const deploymentId = getDevmodeDeploymentId(project?.projectId ?? '', devmode?.id ?? '');
|
|
246
|
+
// Calculate URLs for banner
|
|
247
|
+
const padding = 12;
|
|
248
|
+
const workbenchUrl = auth && project?.projectId
|
|
249
|
+
? `${getAppBaseURL(config)}/w/${project.projectId}`
|
|
250
|
+
: `http://127.0.0.1:${opts.port}${workbench.config?.route ?? '/workbench'}`;
|
|
251
|
+
const devmodebody = tui.muted(tui.padRight('Local:', padding)) +
|
|
252
|
+
tui.link(`http://127.0.0.1:${opts.port}`) +
|
|
253
|
+
'\n' +
|
|
254
|
+
tui.muted(tui.padRight('Public:', padding)) +
|
|
255
|
+
(devmode?.hostname ? tui.link(`https://${devmode.hostname}`) : tui.warn('Disabled')) +
|
|
256
|
+
'\n' +
|
|
257
|
+
tui.muted(tui.padRight('Workbench:', padding)) +
|
|
258
|
+
(workbench.hasWorkbench ? tui.link(workbenchUrl) : tui.warn('Disabled')) +
|
|
259
|
+
'\n' +
|
|
260
|
+
tui.muted(tui.padRight('Dashboard:', padding)) +
|
|
261
|
+
(appURL ? tui.link(appURL) : tui.warn('Disabled')) +
|
|
262
|
+
'\n' +
|
|
263
|
+
(interactive
|
|
264
|
+
? '\n' + tui.muted('Press ') + tui.bold('h') + tui.muted(' for keyboard shortcuts')
|
|
265
|
+
: '');
|
|
266
|
+
tui.banner('⨺ Agentuity DevMode', devmodebody, {
|
|
267
|
+
padding: 2,
|
|
268
|
+
topSpacer: false,
|
|
269
|
+
bottomSpacer: false,
|
|
270
|
+
centerTitle: false,
|
|
282
271
|
});
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
logger.debug(`Vite asset server running on port ${vitePort} (stays running across backend restarts)`);
|
|
288
|
-
}
|
|
289
|
-
catch (error) {
|
|
290
|
-
tui.error(`Failed to start Vite asset server: ${error}`);
|
|
291
|
-
await devLock.release();
|
|
292
|
-
originalExit(1);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
// Restart loop - allows BACKEND server to restart on file changes
|
|
296
|
-
// Vite stays running and handles frontend changes via HMR
|
|
297
|
-
let shouldRestart = false;
|
|
298
|
-
let gravityProcess = null;
|
|
299
|
-
let stdinListenerRegistered = false; // Track if stdin listener is already registered
|
|
300
|
-
const restartServer = () => {
|
|
301
|
-
shouldRestart = true;
|
|
302
|
-
};
|
|
303
|
-
const showWelcome = () => {
|
|
304
|
-
logger.info('DevMode ready 🚀');
|
|
305
|
-
};
|
|
306
|
-
// Create file watcher for backend hot reload
|
|
307
|
-
const fileWatcher = createFileWatcher({
|
|
308
|
-
rootDir,
|
|
309
|
-
logger,
|
|
310
|
-
onRestart: restartServer,
|
|
311
|
-
});
|
|
312
|
-
// Start file watcher (will be paused during builds)
|
|
313
|
-
fileWatcher.start();
|
|
314
|
-
// Track if cleanup is in progress to avoid duplicate cleanup
|
|
315
|
-
let cleaningUp = false;
|
|
316
|
-
// Track if shutdown was requested (SIGINT/SIGTERM) to break the main loop
|
|
317
|
-
let shutdownRequested = false;
|
|
318
|
-
/**
|
|
319
|
-
* Centralized cleanup function for all resources.
|
|
320
|
-
* Called on restart, shutdown, and fatal errors.
|
|
321
|
-
* @param exitAfter - If true, exit the process after cleanup
|
|
322
|
-
* @param exitCode - Exit code to use if exitAfter is true
|
|
323
|
-
* @param silent - If true, don't show "Shutting down" message
|
|
324
|
-
*/
|
|
325
|
-
const cleanup = async (exitAfter = false, exitCode = 0, silent = false) => {
|
|
326
|
-
if (cleaningUp)
|
|
327
|
-
return;
|
|
328
|
-
cleaningUp = true;
|
|
329
|
-
if (!silent) {
|
|
330
|
-
tui.info('Shutting down...');
|
|
331
|
-
}
|
|
332
|
-
// Stop file watcher first to prevent restart triggers during cleanup
|
|
333
|
-
try {
|
|
334
|
-
fileWatcher.stop();
|
|
335
|
-
}
|
|
336
|
-
catch (err) {
|
|
337
|
-
logger.debug('Error stopping file watcher: %s', err);
|
|
272
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
273
|
+
const cliVersion = global.__CLI_SCHEMA__?.version ?? '';
|
|
274
|
+
if (cliVersion) {
|
|
275
|
+
regenerateSkillsAsync(rootDir, cliVersion, logger).catch(() => { });
|
|
338
276
|
}
|
|
339
|
-
//
|
|
277
|
+
// Start Vite asset server ONCE before restart loop
|
|
278
|
+
// Vite handles frontend HMR independently and stays running across backend restarts
|
|
279
|
+
let viteServer = null;
|
|
280
|
+
let vitePort;
|
|
340
281
|
try {
|
|
341
|
-
|
|
282
|
+
logger.debug('Starting Vite asset server...');
|
|
283
|
+
const viteResult = await startViteAssetServer({
|
|
284
|
+
rootDir,
|
|
285
|
+
logger,
|
|
286
|
+
workbenchPath: workbench.config?.route,
|
|
287
|
+
});
|
|
288
|
+
viteServer = viteResult.server;
|
|
289
|
+
vitePort = viteResult.port;
|
|
290
|
+
// Update dev lock with actual Vite port
|
|
291
|
+
await devLock.updatePorts({ vite: vitePort });
|
|
292
|
+
logger.debug(`Vite asset server running on port ${vitePort} (stays running across backend restarts)`);
|
|
342
293
|
}
|
|
343
|
-
catch (
|
|
344
|
-
|
|
294
|
+
catch (error) {
|
|
295
|
+
tui.error(`Failed to start Vite asset server: ${error}`);
|
|
296
|
+
await devLock.release();
|
|
297
|
+
originalExit(1);
|
|
298
|
+
return;
|
|
345
299
|
}
|
|
346
|
-
//
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
300
|
+
// Restart loop - allows BACKEND server to restart on file changes
|
|
301
|
+
// Vite stays running and handles frontend changes via HMR
|
|
302
|
+
let shouldRestart = false;
|
|
303
|
+
let gravityProcess = null;
|
|
304
|
+
let stdinListenerRegistered = false; // Track if stdin listener is already registered
|
|
305
|
+
const restartServer = () => {
|
|
306
|
+
shouldRestart = true;
|
|
307
|
+
};
|
|
308
|
+
const showWelcome = () => {
|
|
309
|
+
logger.info('DevMode ready 🚀');
|
|
310
|
+
};
|
|
311
|
+
// Create file watcher for backend hot reload
|
|
312
|
+
const fileWatcher = createFileWatcher({
|
|
313
|
+
rootDir,
|
|
314
|
+
logger,
|
|
315
|
+
onRestart: restartServer,
|
|
316
|
+
});
|
|
317
|
+
// Start file watcher (will be paused during builds)
|
|
318
|
+
fileWatcher.start();
|
|
319
|
+
// Track if cleanup is in progress to avoid duplicate cleanup
|
|
320
|
+
let cleaningUp = false;
|
|
321
|
+
// Track if shutdown was requested (SIGINT/SIGTERM) to break the main loop
|
|
322
|
+
let shutdownRequested = false;
|
|
323
|
+
/**
|
|
324
|
+
* Centralized cleanup function for all resources.
|
|
325
|
+
* Called on restart, shutdown, and fatal errors.
|
|
326
|
+
* @param exitAfter - If true, exit the process after cleanup
|
|
327
|
+
* @param exitCode - Exit code to use if exitAfter is true
|
|
328
|
+
* @param silent - If true, don't show "Shutting down" message
|
|
329
|
+
*/
|
|
330
|
+
const cleanup = async (exitAfter = false, exitCode = 0, silent = false) => {
|
|
331
|
+
if (cleaningUp)
|
|
332
|
+
return;
|
|
333
|
+
cleaningUp = true;
|
|
334
|
+
if (!silent) {
|
|
335
|
+
tui.info('Shutting down...');
|
|
363
336
|
}
|
|
364
|
-
|
|
365
|
-
// Close Vite asset server with timeout to prevent hanging
|
|
366
|
-
if (viteServer) {
|
|
367
|
-
logger.debug('Closing Vite server...');
|
|
337
|
+
// Stop file watcher first to prevent restart triggers during cleanup
|
|
368
338
|
try {
|
|
369
|
-
|
|
370
|
-
const closePromise = viteServer.close();
|
|
371
|
-
const timeoutPromise = new Promise((resolve) => {
|
|
372
|
-
setTimeout(() => {
|
|
373
|
-
logger.debug('Vite server close timed out, continuing...');
|
|
374
|
-
resolve();
|
|
375
|
-
}, 2000);
|
|
376
|
-
});
|
|
377
|
-
await Promise.race([closePromise, timeoutPromise]);
|
|
378
|
-
logger.debug('Vite server closed');
|
|
339
|
+
fileWatcher.stop();
|
|
379
340
|
}
|
|
380
341
|
catch (err) {
|
|
381
|
-
logger.debug('Error
|
|
382
|
-
}
|
|
383
|
-
finally {
|
|
384
|
-
viteServer = null;
|
|
342
|
+
logger.debug('Error stopping file watcher: %s', err);
|
|
385
343
|
}
|
|
386
|
-
|
|
387
|
-
// Release the dev lockfile
|
|
388
|
-
logger.debug('Releasing dev lock...');
|
|
389
|
-
try {
|
|
390
|
-
await devLock.release();
|
|
391
|
-
logger.debug('Dev lock released');
|
|
392
|
-
}
|
|
393
|
-
catch (err) {
|
|
394
|
-
logger.debug('Error releasing dev lock: %s', err);
|
|
395
|
-
}
|
|
396
|
-
// Reset cleanup flag if not exiting (allows restart)
|
|
397
|
-
if (!exitAfter) {
|
|
398
|
-
cleaningUp = false;
|
|
399
|
-
}
|
|
400
|
-
else {
|
|
401
|
-
logger.debug('Exiting with code %d', exitCode);
|
|
402
|
-
originalExit(exitCode);
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
/**
|
|
406
|
-
* Cleanup for restart: stops Bun server and Gravity, keeps Vite running
|
|
407
|
-
*/
|
|
408
|
-
const cleanupForRestart = async () => {
|
|
409
|
-
logger.debug('Cleaning up for restart...');
|
|
410
|
-
// Stop Bun server
|
|
411
|
-
try {
|
|
412
|
-
await stopBunServer(opts.port, logger);
|
|
413
|
-
}
|
|
414
|
-
catch (err) {
|
|
415
|
-
logger.debug('Error stopping Bun server for restart: %s', err);
|
|
416
|
-
}
|
|
417
|
-
// Kill gravity client
|
|
418
|
-
if (gravityProcess) {
|
|
344
|
+
// Stop Bun server
|
|
419
345
|
try {
|
|
420
|
-
|
|
421
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
422
|
-
if (gravityProcess.exitCode === null) {
|
|
423
|
-
gravityProcess.kill('SIGKILL');
|
|
424
|
-
}
|
|
346
|
+
await stopBunServer(opts.port, logger);
|
|
425
347
|
}
|
|
426
348
|
catch (err) {
|
|
427
|
-
logger.debug('Error
|
|
349
|
+
logger.debug('Error stopping Bun server during cleanup: %s', err);
|
|
428
350
|
}
|
|
429
|
-
|
|
430
|
-
|
|
351
|
+
// Kill gravity client with SIGTERM first, then SIGKILL as fallback
|
|
352
|
+
if (gravityProcess) {
|
|
353
|
+
logger.debug('Killing gravity process...');
|
|
354
|
+
try {
|
|
355
|
+
gravityProcess.kill('SIGTERM');
|
|
356
|
+
// Give it a moment to gracefully shutdown
|
|
357
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
358
|
+
if (gravityProcess.exitCode === null) {
|
|
359
|
+
gravityProcess.kill('SIGKILL');
|
|
360
|
+
}
|
|
361
|
+
logger.debug('Gravity process killed');
|
|
362
|
+
}
|
|
363
|
+
catch (err) {
|
|
364
|
+
logger.debug('Error killing gravity process: %s', err);
|
|
365
|
+
}
|
|
366
|
+
finally {
|
|
367
|
+
gravityProcess = null;
|
|
368
|
+
}
|
|
431
369
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
370
|
+
// Close Vite asset server with timeout to prevent hanging
|
|
371
|
+
if (viteServer) {
|
|
372
|
+
logger.debug('Closing Vite server...');
|
|
373
|
+
try {
|
|
374
|
+
// Use Promise.race with timeout to prevent hanging
|
|
375
|
+
const closePromise = viteServer.close();
|
|
376
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
377
|
+
setTimeout(() => {
|
|
378
|
+
logger.debug('Vite server close timed out, continuing...');
|
|
379
|
+
resolve();
|
|
380
|
+
}, 2000);
|
|
381
|
+
});
|
|
382
|
+
await Promise.race([closePromise, timeoutPromise]);
|
|
383
|
+
logger.debug('Vite server closed');
|
|
384
|
+
}
|
|
385
|
+
catch (err) {
|
|
386
|
+
logger.debug('Error closing Vite server: %s', err);
|
|
387
|
+
}
|
|
388
|
+
finally {
|
|
389
|
+
viteServer = null;
|
|
390
|
+
}
|
|
441
391
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
};
|
|
445
|
-
process.on('SIGINT', () => {
|
|
446
|
-
void safeExit(0, 'SIGINT');
|
|
447
|
-
});
|
|
448
|
-
process.on('SIGTERM', () => {
|
|
449
|
-
void safeExit(0, 'SIGTERM');
|
|
450
|
-
});
|
|
451
|
-
// Handle uncaught exceptions - clean up and exit rather than limping on
|
|
452
|
-
process.on('uncaughtException', (err) => {
|
|
453
|
-
tui.error(`Uncaught exception: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`);
|
|
454
|
-
void safeExit(1, 'uncaughtException');
|
|
455
|
-
});
|
|
456
|
-
// Handle unhandled rejections - log but don't exit (usually recoverable)
|
|
457
|
-
process.on('unhandledRejection', (reason) => {
|
|
458
|
-
logger.warn('Unhandled promise rejection: %s', reason instanceof Error ? (reason.stack ?? reason.message) : String(reason));
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
// Ensure resources are always cleaned up on exit (synchronous fallback)
|
|
462
|
-
process.on('exit', () => {
|
|
463
|
-
// Kill gravity client with SIGKILL for immediate termination
|
|
464
|
-
if (gravityProcess && gravityProcess.exitCode === null) {
|
|
392
|
+
// Release the dev lockfile
|
|
393
|
+
logger.debug('Releasing dev lock...');
|
|
465
394
|
try {
|
|
466
|
-
|
|
395
|
+
await devLock.release();
|
|
396
|
+
logger.debug('Dev lock released');
|
|
467
397
|
}
|
|
468
|
-
catch {
|
|
469
|
-
|
|
398
|
+
catch (err) {
|
|
399
|
+
logger.debug('Error releasing dev lock: %s', err);
|
|
470
400
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
viteServer.close();
|
|
401
|
+
await killLingeringGravityProcesses(logger);
|
|
402
|
+
// Reset cleanup flag if not exiting (allows restart)
|
|
403
|
+
if (!exitAfter) {
|
|
404
|
+
cleaningUp = false;
|
|
476
405
|
}
|
|
477
|
-
|
|
478
|
-
|
|
406
|
+
else {
|
|
407
|
+
logger.debug('Exiting with code %d', exitCode);
|
|
408
|
+
originalExit(exitCode);
|
|
479
409
|
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
410
|
+
};
|
|
411
|
+
/**
|
|
412
|
+
* Cleanup for restart: stops Bun server and Gravity, keeps Vite running
|
|
413
|
+
*/
|
|
414
|
+
const cleanupForRestart = async () => {
|
|
415
|
+
logger.debug('Cleaning up for restart...');
|
|
416
|
+
// Stop Bun server
|
|
485
417
|
try {
|
|
486
|
-
|
|
418
|
+
await stopBunServer(opts.port, logger);
|
|
487
419
|
}
|
|
488
|
-
catch {
|
|
489
|
-
|
|
420
|
+
catch (err) {
|
|
421
|
+
logger.debug('Error stopping Bun server for restart: %s', err);
|
|
490
422
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
fileWatcher.pause();
|
|
499
|
-
try {
|
|
500
|
-
// Generate entry file and bundle for dev server (with LLM patches)
|
|
501
|
-
await tui.spinner({
|
|
502
|
-
message: 'Building dev bundle',
|
|
503
|
-
callback: async () => {
|
|
504
|
-
// Step 1: Generate workbench files if enabled (must be done before entry generation)
|
|
505
|
-
if (workbenchConfigData.enabled) {
|
|
506
|
-
logger.debug('Workbench enabled, generating source files before bundle...');
|
|
507
|
-
const { generateWorkbenchFiles } = await import('../build/vite/workbench-generator');
|
|
508
|
-
await generateWorkbenchFiles(rootDir, project?.projectId ?? '', workbenchConfigData, logger);
|
|
509
|
-
}
|
|
510
|
-
// Step 2: Generate entry file with workbench config
|
|
511
|
-
// Note: vitePort is NOT passed here - the app reads process.env.VITE_PORT at runtime
|
|
512
|
-
const { generateEntryFile } = await import('../build/entry-generator');
|
|
513
|
-
await generateEntryFile({
|
|
514
|
-
rootDir,
|
|
515
|
-
projectId: project?.projectId ?? '',
|
|
516
|
-
deploymentId,
|
|
517
|
-
logger,
|
|
518
|
-
mode: 'dev',
|
|
519
|
-
workbench: workbenchConfigData.enabled ? workbenchConfigData : undefined,
|
|
520
|
-
});
|
|
521
|
-
// Step 3: Bundle the app with LLM patches (dev mode = no minification)
|
|
522
|
-
// This produces .agentuity/app.js with AI Gateway routing patches applied
|
|
523
|
-
const { installExternalsAndBuild } = await import('../build/vite/server-bundler');
|
|
524
|
-
await installExternalsAndBuild({
|
|
525
|
-
rootDir,
|
|
526
|
-
dev: true, // DevMode: no minification, inline sourcemaps
|
|
527
|
-
logger,
|
|
528
|
-
});
|
|
529
|
-
// Generate metadata file (needed for eval ID lookup at runtime)
|
|
530
|
-
const { discoverAgents } = await import('../build/vite/agent-discovery');
|
|
531
|
-
const { discoverRoutes } = await import('../build/vite/route-discovery');
|
|
532
|
-
const { generateMetadata, writeMetadataFile } = await import('../build/vite/metadata-generator');
|
|
533
|
-
const srcDir = join(rootDir, 'src');
|
|
534
|
-
const promises = [];
|
|
535
|
-
// Generate/update prompt files (non-blocking)
|
|
536
|
-
promises.push(import('../build/vite/prompt-generator')
|
|
537
|
-
.then(({ generatePromptFiles }) => generatePromptFiles(srcDir, logger))
|
|
538
|
-
.catch((err) => logger.warn('Failed to generate prompt files: %s', err.message)));
|
|
539
|
-
const agents = await discoverAgents(srcDir, project?.projectId ?? '', deploymentId, logger);
|
|
540
|
-
const { routes } = await discoverRoutes(srcDir, project?.projectId ?? '', deploymentId, logger);
|
|
541
|
-
const metadata = await generateMetadata({
|
|
542
|
-
rootDir,
|
|
543
|
-
projectId: project?.projectId ?? '',
|
|
544
|
-
orgId: project?.orgId ?? '',
|
|
545
|
-
deploymentId,
|
|
546
|
-
agents,
|
|
547
|
-
routes,
|
|
548
|
-
dev: true,
|
|
549
|
-
logger,
|
|
550
|
-
});
|
|
551
|
-
writeMetadataFile(rootDir, metadata, true, logger);
|
|
552
|
-
// Sync metadata with backend (creates agents and evals in the database)
|
|
553
|
-
if (syncService && project?.projectId) {
|
|
554
|
-
promises.push(syncService.sync(metadata, previousMetadata, project.projectId, deploymentId));
|
|
555
|
-
previousMetadata = metadata;
|
|
423
|
+
// Kill gravity client
|
|
424
|
+
if (gravityProcess) {
|
|
425
|
+
try {
|
|
426
|
+
gravityProcess.kill('SIGTERM');
|
|
427
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
428
|
+
if (gravityProcess.exitCode === null) {
|
|
429
|
+
gravityProcess.kill('SIGKILL');
|
|
556
430
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
431
|
+
}
|
|
432
|
+
catch (err) {
|
|
433
|
+
logger.debug('Error killing gravity process for restart: %s', err);
|
|
434
|
+
}
|
|
435
|
+
finally {
|
|
436
|
+
gravityProcess = null;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
// SIGINT/SIGTERM: coordinate shutdown between bundle and dev resources
|
|
441
|
+
let signalHandlersRegistered = false;
|
|
442
|
+
if (!signalHandlersRegistered) {
|
|
443
|
+
signalHandlersRegistered = true;
|
|
444
|
+
const safeExit = async (code, reason) => {
|
|
445
|
+
if (reason) {
|
|
446
|
+
logger.debug('DevMode terminating (%d) due to: %s', code, reason);
|
|
447
|
+
}
|
|
448
|
+
shutdownRequested = true;
|
|
449
|
+
await cleanup(true, code);
|
|
450
|
+
};
|
|
451
|
+
process.on('SIGINT', () => {
|
|
452
|
+
void safeExit(0, 'SIGINT');
|
|
560
453
|
});
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
resolve();
|
|
573
|
-
}
|
|
574
|
-
}, 100);
|
|
454
|
+
process.on('SIGTERM', () => {
|
|
455
|
+
void safeExit(0, 'SIGTERM');
|
|
456
|
+
});
|
|
457
|
+
// Handle uncaught exceptions - clean up and exit rather than limping on
|
|
458
|
+
process.on('uncaughtException', (err) => {
|
|
459
|
+
tui.error(`Uncaught exception: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`);
|
|
460
|
+
void safeExit(1, 'uncaughtException');
|
|
461
|
+
});
|
|
462
|
+
// Handle unhandled rejections - log but don't exit (usually recoverable)
|
|
463
|
+
process.on('unhandledRejection', (reason) => {
|
|
464
|
+
logger.warn('Unhandled promise rejection: %s', reason instanceof Error ? (reason.stack ?? reason.message) : String(reason));
|
|
575
465
|
});
|
|
576
|
-
continue;
|
|
577
466
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
//
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
if (!process.env.AGENTUITY_SDK_KEY) {
|
|
585
|
-
const sdkKey = await loadProjectSDKKey(logger, rootDir);
|
|
586
|
-
if (sdkKey) {
|
|
587
|
-
process.env.AGENTUITY_SDK_KEY = sdkKey;
|
|
467
|
+
// Ensure resources are always cleaned up on exit (synchronous fallback)
|
|
468
|
+
process.on('exit', () => {
|
|
469
|
+
// Kill gravity client with SIGKILL for immediate termination
|
|
470
|
+
if (gravityProcess && gravityProcess.exitCode === null) {
|
|
471
|
+
try {
|
|
472
|
+
gravityProcess.kill('SIGKILL');
|
|
588
473
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
tui.bullet(`Run "${getCommand('cloud env pull')}" to sync your SDK key, or add AGENTUITY_SDK_KEY to your .env file.`);
|
|
474
|
+
catch {
|
|
475
|
+
// Ignore errors during exit cleanup
|
|
592
476
|
}
|
|
593
477
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
if (project) {
|
|
603
|
-
process.env.AGENTUITY_TRANSPORT_URL = serviceUrls.catalyst;
|
|
604
|
-
process.env.AGENTUITY_CATALYST_URL = serviceUrls.catalyst;
|
|
605
|
-
process.env.AGENTUITY_VECTOR_URL = serviceUrls.vector;
|
|
606
|
-
process.env.AGENTUITY_KEYVALUE_URL = serviceUrls.keyvalue;
|
|
607
|
-
process.env.AGENTUITY_SANDBOX_URL = serviceUrls.sandbox;
|
|
608
|
-
process.env.AGENTUITY_STREAM_URL = serviceUrls.stream;
|
|
609
|
-
process.env.AGENTUITY_CLOUD_ORG_ID = project.orgId;
|
|
610
|
-
process.env.AGENTUITY_CLOUD_PROJECT_ID = project.projectId;
|
|
611
|
-
}
|
|
612
|
-
// Set Vite port for asset proxying in bundled app
|
|
613
|
-
process.env.VITE_PORT = String(vitePort);
|
|
614
|
-
logger.debug('Set VITE_PORT=%s for asset proxying', process.env.VITE_PORT);
|
|
615
|
-
// Start Bun dev server (Vite already running, just start backend)
|
|
616
|
-
await startBunDevServer({
|
|
617
|
-
rootDir,
|
|
618
|
-
port: opts.port,
|
|
619
|
-
projectId: project?.projectId,
|
|
620
|
-
orgId: project?.orgId,
|
|
621
|
-
deploymentId,
|
|
622
|
-
logger,
|
|
623
|
-
vitePort, // Pass port of already-running Vite server
|
|
624
|
-
});
|
|
625
|
-
// Wait for app.ts to finish loading (Vite is ready but app may still be initializing)
|
|
626
|
-
// Give it 2 seconds to ensure app initialization completes
|
|
627
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
628
|
-
// Check if shutdown was requested during startup
|
|
629
|
-
if (shutdownRequested) {
|
|
630
|
-
break;
|
|
478
|
+
// Close Vite server synchronously if possible
|
|
479
|
+
if (viteServer) {
|
|
480
|
+
try {
|
|
481
|
+
viteServer.close();
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
// Ignore errors during exit cleanup
|
|
485
|
+
}
|
|
631
486
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
}
|
|
643
|
-
}, 100);
|
|
644
|
-
});
|
|
645
|
-
if (shutdownRequested) {
|
|
646
|
-
break;
|
|
487
|
+
// Stop Bun server synchronously (best effort)
|
|
488
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
489
|
+
const server = globalThis.__AGENTUITY_SERVER__;
|
|
490
|
+
if (server?.stop) {
|
|
491
|
+
try {
|
|
492
|
+
server.stop(true);
|
|
493
|
+
}
|
|
494
|
+
catch {
|
|
495
|
+
// Ignore errors during exit cleanup
|
|
496
|
+
}
|
|
647
497
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
498
|
+
// Release the dev lockfile synchronously
|
|
499
|
+
releaseLockSync(rootDir);
|
|
500
|
+
});
|
|
501
|
+
while (!shutdownRequested) {
|
|
502
|
+
shouldRestart = false;
|
|
503
|
+
// Pause file watcher during build to avoid loops
|
|
504
|
+
fileWatcher.pause();
|
|
505
|
+
try {
|
|
506
|
+
let typeCheckErrors;
|
|
507
|
+
// Generate entry file and bundle for dev server (with LLM patches)
|
|
508
|
+
await tui.spinner({
|
|
509
|
+
message: 'Building dev bundle',
|
|
510
|
+
callback: async () => {
|
|
511
|
+
// Step 0: typecheck
|
|
512
|
+
typeCheckErrors = undefined;
|
|
513
|
+
const typeResult = await typecheck(rootDir);
|
|
514
|
+
if (!typeResult.success) {
|
|
515
|
+
typeCheckErrors = typeResult.output;
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
// Step 1: Generate workbench files if enabled (must be done before entry generation)
|
|
519
|
+
if (workbenchConfigData.enabled) {
|
|
520
|
+
logger.debug('Workbench enabled, generating source files before bundle...');
|
|
521
|
+
const { generateWorkbenchFiles } = await import('../build/vite/workbench-generator');
|
|
522
|
+
await generateWorkbenchFiles(rootDir, project?.projectId ?? '', workbenchConfigData, logger);
|
|
523
|
+
}
|
|
524
|
+
// Step 2: Generate entry file with workbench config
|
|
525
|
+
// Note: vitePort is NOT passed here - the app reads process.env.VITE_PORT at runtime
|
|
526
|
+
const { generateEntryFile } = await import('../build/entry-generator');
|
|
527
|
+
await generateEntryFile({
|
|
528
|
+
rootDir,
|
|
529
|
+
projectId: project?.projectId ?? '',
|
|
530
|
+
deploymentId,
|
|
531
|
+
logger,
|
|
532
|
+
mode: 'dev',
|
|
533
|
+
workbench: workbenchConfigData.enabled ? workbenchConfigData : undefined,
|
|
534
|
+
});
|
|
535
|
+
// Step 3: Bundle the app with LLM patches (dev mode = no minification)
|
|
536
|
+
// This produces .agentuity/app.js with AI Gateway routing patches applied
|
|
537
|
+
const { installExternalsAndBuild } = await import('../build/vite/server-bundler');
|
|
538
|
+
await installExternalsAndBuild({
|
|
539
|
+
rootDir,
|
|
540
|
+
dev: true, // DevMode: no minification, inline sourcemaps
|
|
541
|
+
logger,
|
|
542
|
+
});
|
|
543
|
+
// Generate metadata file (needed for eval ID lookup at runtime)
|
|
544
|
+
const { discoverAgents } = await import('../build/vite/agent-discovery');
|
|
545
|
+
const { discoverRoutes } = await import('../build/vite/route-discovery');
|
|
546
|
+
const { generateMetadata, writeMetadataFile } = await import('../build/vite/metadata-generator');
|
|
547
|
+
const srcDir = join(rootDir, 'src');
|
|
548
|
+
const promises = [];
|
|
549
|
+
// Generate/update prompt files (non-blocking)
|
|
550
|
+
promises.push(import('../build/vite/prompt-generator')
|
|
551
|
+
.then(({ generatePromptFiles }) => generatePromptFiles(srcDir, logger))
|
|
552
|
+
.catch((err) => logger.warn('Failed to generate prompt files: %s', err.message)));
|
|
553
|
+
const agents = await discoverAgents(srcDir, project?.projectId ?? '', deploymentId, logger);
|
|
554
|
+
const { routes } = await discoverRoutes(srcDir, project?.projectId ?? '', deploymentId, logger);
|
|
555
|
+
const metadata = await generateMetadata({
|
|
556
|
+
rootDir,
|
|
557
|
+
projectId: project?.projectId ?? '',
|
|
558
|
+
orgId: project?.orgId ?? '',
|
|
559
|
+
deploymentId,
|
|
560
|
+
agents,
|
|
561
|
+
routes,
|
|
562
|
+
dev: true,
|
|
563
|
+
logger,
|
|
564
|
+
});
|
|
565
|
+
writeMetadataFile(rootDir, metadata, true, logger);
|
|
566
|
+
// Sync metadata with backend (creates agents and evals in the database)
|
|
567
|
+
if (syncService && project?.projectId) {
|
|
568
|
+
promises.push(syncService.sync(metadata, previousMetadata, project.projectId, deploymentId));
|
|
569
|
+
previousMetadata = metadata;
|
|
570
|
+
}
|
|
571
|
+
await Promise.all(promises);
|
|
572
|
+
},
|
|
573
|
+
clearOnSuccess: true,
|
|
673
574
|
});
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
// Log gravity output
|
|
684
|
-
(async () => {
|
|
685
|
-
try {
|
|
686
|
-
if (gravityProcess?.stdout) {
|
|
687
|
-
for await (const chunk of gravityProcess.stdout) {
|
|
688
|
-
const text = new TextDecoder().decode(chunk);
|
|
689
|
-
logger.debug('[gravity] %s', text.trim());
|
|
690
|
-
}
|
|
575
|
+
if (typeCheckErrors) {
|
|
576
|
+
console.log('');
|
|
577
|
+
console.log(typeCheckErrors);
|
|
578
|
+
console.log('');
|
|
579
|
+
fileWatcher.resume();
|
|
580
|
+
// wait for a file change or shutdown to trigger a recompile
|
|
581
|
+
while (true) {
|
|
582
|
+
if (shutdownRequested) {
|
|
583
|
+
return;
|
|
691
584
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
logger.error('Error reading gravity stdout: %s', err);
|
|
695
|
-
}
|
|
696
|
-
})();
|
|
697
|
-
(async () => {
|
|
698
|
-
try {
|
|
699
|
-
if (gravityProcess?.stderr) {
|
|
700
|
-
for await (const chunk of gravityProcess.stderr) {
|
|
701
|
-
const text = new TextDecoder().decode(chunk);
|
|
702
|
-
logger.warn('[gravity] %s', text.trim());
|
|
703
|
-
}
|
|
585
|
+
if (shouldRestart) {
|
|
586
|
+
break;
|
|
704
587
|
}
|
|
588
|
+
await tui.spinner({
|
|
589
|
+
message: 'Waiting for changes...',
|
|
590
|
+
clearOnSuccess: true,
|
|
591
|
+
callback: () => Bun.sleep(1000),
|
|
592
|
+
});
|
|
705
593
|
}
|
|
706
|
-
|
|
707
|
-
logger.error('Error reading gravity stderr: %s', err);
|
|
708
|
-
}
|
|
709
|
-
})();
|
|
710
|
-
logger.debug('Gravity client started');
|
|
594
|
+
}
|
|
711
595
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
596
|
+
catch (error) {
|
|
597
|
+
tui.error(`Failed to build dev bundle: ${error}`);
|
|
598
|
+
tui.warn('Waiting for file changes to retry...');
|
|
599
|
+
// Resume watcher to detect changes for retry
|
|
600
|
+
fileWatcher.resume();
|
|
601
|
+
// Wait for next restart trigger
|
|
602
|
+
await new Promise((resolve) => {
|
|
603
|
+
const checkRestart = setInterval(() => {
|
|
604
|
+
if (shouldRestart) {
|
|
605
|
+
clearInterval(checkRestart);
|
|
606
|
+
resolve();
|
|
607
|
+
}
|
|
608
|
+
}, 100);
|
|
609
|
+
});
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
try {
|
|
613
|
+
// Set environment variables for LLM provider patches BEFORE starting server
|
|
614
|
+
// These must be set so the bundled patches can route LLM calls through AI Gateway
|
|
615
|
+
const serviceUrls = getServiceUrls(project?.region);
|
|
616
|
+
// Load SDK key from project .env files for AI Gateway routing
|
|
617
|
+
// This must be set so the bundled AI SDK patches can inject the API key
|
|
618
|
+
if (!process.env.AGENTUITY_SDK_KEY) {
|
|
619
|
+
const sdkKey = await loadProjectSDKKey(logger, rootDir);
|
|
620
|
+
if (sdkKey) {
|
|
621
|
+
process.env.AGENTUITY_SDK_KEY = sdkKey;
|
|
736
622
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
break;
|
|
741
|
-
case 'c':
|
|
742
|
-
console.clear();
|
|
743
|
-
tui.banner('⨺ Agentuity DevMode', devmodebody, {
|
|
744
|
-
padding: 2,
|
|
745
|
-
topSpacer: false,
|
|
746
|
-
bottomSpacer: false,
|
|
747
|
-
centerTitle: false,
|
|
748
|
-
});
|
|
749
|
-
break;
|
|
750
|
-
case 'q':
|
|
751
|
-
void cleanup(true, 0);
|
|
752
|
-
break;
|
|
753
|
-
default:
|
|
754
|
-
process.stdout.write(data);
|
|
755
|
-
break;
|
|
623
|
+
else if (project) {
|
|
624
|
+
tui.warn('AGENTUITY_SDK_KEY not found in .env file. Numerous features will be unavailable.');
|
|
625
|
+
tui.bullet(`Run "${getCommand('cloud env pull')}" to sync your SDK key, or add AGENTUITY_SDK_KEY to your .env file.`);
|
|
756
626
|
}
|
|
627
|
+
}
|
|
628
|
+
process.env.AGENTUITY_SDK_DEV_MODE = 'true';
|
|
629
|
+
process.env.AGENTUITY_ENV = 'development';
|
|
630
|
+
process.env.NODE_ENV = 'development';
|
|
631
|
+
process.env.AGENTUITY_PROJECT_DIR = rootDir;
|
|
632
|
+
if (project?.region) {
|
|
633
|
+
process.env.AGENTUITY_REGION = project.region;
|
|
634
|
+
}
|
|
635
|
+
process.env.PORT = String(opts.port);
|
|
636
|
+
process.env.AGENTUITY_PORT = process.env.PORT;
|
|
637
|
+
if (project) {
|
|
638
|
+
process.env.AGENTUITY_TRANSPORT_URL = serviceUrls.catalyst;
|
|
639
|
+
process.env.AGENTUITY_CATALYST_URL = serviceUrls.catalyst;
|
|
640
|
+
process.env.AGENTUITY_VECTOR_URL = serviceUrls.vector;
|
|
641
|
+
process.env.AGENTUITY_KEYVALUE_URL = serviceUrls.keyvalue;
|
|
642
|
+
process.env.AGENTUITY_SANDBOX_URL = serviceUrls.sandbox;
|
|
643
|
+
process.env.AGENTUITY_STREAM_URL = serviceUrls.stream;
|
|
644
|
+
process.env.AGENTUITY_CLOUD_ORG_ID = project.orgId;
|
|
645
|
+
process.env.AGENTUITY_CLOUD_PROJECT_ID = project.projectId;
|
|
646
|
+
}
|
|
647
|
+
// Set Vite port for asset proxying in bundled app
|
|
648
|
+
process.env.VITE_PORT = String(vitePort);
|
|
649
|
+
logger.debug('Set VITE_PORT=%s for asset proxying', process.env.VITE_PORT);
|
|
650
|
+
// Start Bun dev server (Vite already running, just start backend)
|
|
651
|
+
await startBunDevServer({
|
|
652
|
+
rootDir,
|
|
653
|
+
port: opts.port,
|
|
654
|
+
projectId: project?.projectId,
|
|
655
|
+
orgId: project?.orgId,
|
|
656
|
+
deploymentId,
|
|
657
|
+
logger,
|
|
658
|
+
vitePort, // Pass port of already-running Vite server
|
|
757
659
|
});
|
|
660
|
+
// Wait for app.ts to finish loading (Vite is ready but app may still be initializing)
|
|
661
|
+
// Give it 2 seconds to ensure app initialization completes
|
|
662
|
+
await Bun.sleep(2000);
|
|
663
|
+
// Check if shutdown was requested during startup
|
|
664
|
+
if (shutdownRequested) {
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
758
667
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
668
|
+
catch (error) {
|
|
669
|
+
tui.error(`Failed to start dev server: ${error}`);
|
|
670
|
+
tui.warn('Waiting for file changes to retry...');
|
|
671
|
+
// Wait for next restart trigger or shutdown
|
|
672
|
+
await new Promise((resolve) => {
|
|
673
|
+
const checkRestart = setInterval(() => {
|
|
674
|
+
if (shouldRestart || shutdownRequested) {
|
|
675
|
+
clearInterval(checkRestart);
|
|
676
|
+
resolve();
|
|
677
|
+
}
|
|
678
|
+
}, 100);
|
|
679
|
+
});
|
|
680
|
+
if (shutdownRequested) {
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
continue;
|
|
774
684
|
}
|
|
775
|
-
//
|
|
776
|
-
logger.debug('Restarting backend server...');
|
|
777
|
-
// Clean up Bun server and Gravity (Vite stays running)
|
|
778
|
-
await cleanupForRestart();
|
|
779
|
-
// Brief pause before restart
|
|
780
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
781
|
-
}
|
|
782
|
-
catch (error) {
|
|
783
|
-
tui.error(`Error during server operation: ${error}`);
|
|
784
|
-
tui.warn('Waiting for file changes to retry...');
|
|
785
|
-
// Cleanup on error (Vite stays running)
|
|
786
|
-
await cleanupForRestart();
|
|
787
|
-
// Exit if shutdown was requested during error handling
|
|
685
|
+
// Exit early if shutdown was requested
|
|
788
686
|
if (shutdownRequested) {
|
|
789
687
|
break;
|
|
790
688
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
689
|
+
try {
|
|
690
|
+
// Start gravity client if we have devmode
|
|
691
|
+
if (gravityBin && gravityURL && devmode && project) {
|
|
692
|
+
logger.trace('Starting gravity client: %s (cwd: %s, id: %s)', gravityBin, rootDir, devmode.id);
|
|
693
|
+
gravityProcess = Bun.spawn([
|
|
694
|
+
gravityBin,
|
|
695
|
+
'--endpoint-id',
|
|
696
|
+
devmode.id,
|
|
697
|
+
'--port',
|
|
698
|
+
opts.port.toString(),
|
|
699
|
+
'--url',
|
|
700
|
+
gravityURL,
|
|
701
|
+
'--log-level',
|
|
702
|
+
process.env.AGENTUITY_GRAVITY_LOG_LEVEL ?? 'error',
|
|
703
|
+
'--org-id',
|
|
704
|
+
project.orgId,
|
|
705
|
+
'--project-id',
|
|
706
|
+
project.projectId,
|
|
707
|
+
'--token',
|
|
708
|
+
process.env.AGENTUITY_SDK_KEY,
|
|
709
|
+
], {
|
|
710
|
+
cwd: rootDir,
|
|
711
|
+
stdout: 'pipe',
|
|
712
|
+
stderr: 'pipe',
|
|
713
|
+
detached: false, // Ensure gravity dies with parent process
|
|
714
|
+
});
|
|
715
|
+
// Register gravity process in dev lock for cleanup tracking
|
|
716
|
+
const gravityPid = gravityProcess.pid;
|
|
717
|
+
if (gravityPid) {
|
|
718
|
+
await devLock.registerChild({
|
|
719
|
+
pid: gravityPid,
|
|
720
|
+
type: 'gravity',
|
|
721
|
+
description: 'Gravity public URL tunnel',
|
|
722
|
+
});
|
|
799
723
|
}
|
|
800
|
-
|
|
801
|
-
|
|
724
|
+
// Log gravity output
|
|
725
|
+
(async () => {
|
|
726
|
+
try {
|
|
727
|
+
if (gravityProcess?.stdout) {
|
|
728
|
+
for await (const chunk of gravityProcess.stdout) {
|
|
729
|
+
const text = new TextDecoder().decode(chunk);
|
|
730
|
+
logger.debug('[gravity] %s', text.trim());
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
catch (err) {
|
|
735
|
+
logger.error('Error reading gravity stdout: %s', err);
|
|
736
|
+
}
|
|
737
|
+
})();
|
|
738
|
+
(async () => {
|
|
739
|
+
try {
|
|
740
|
+
if (gravityProcess?.stderr) {
|
|
741
|
+
for await (const chunk of gravityProcess.stderr) {
|
|
742
|
+
const text = new TextDecoder().decode(chunk);
|
|
743
|
+
logger.warn('[gravity] %s', text.trim());
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
catch (err) {
|
|
748
|
+
logger.error('Error reading gravity stderr: %s', err);
|
|
749
|
+
}
|
|
750
|
+
})();
|
|
751
|
+
logger.debug('Gravity client started');
|
|
752
|
+
}
|
|
753
|
+
// Handle keyboard shortcuts - only register listener once
|
|
754
|
+
if (interactive &&
|
|
755
|
+
process.stdin.isTTY &&
|
|
756
|
+
process.stdout.isTTY &&
|
|
757
|
+
!stdinListenerRegistered) {
|
|
758
|
+
stdinListenerRegistered = true;
|
|
759
|
+
process.stdin.setRawMode(true);
|
|
760
|
+
process.stdin.resume();
|
|
761
|
+
process.stdin.setEncoding('utf8');
|
|
762
|
+
const showHelp = () => {
|
|
763
|
+
console.log('\n' + tui.bold('Keyboard Shortcuts:'));
|
|
764
|
+
console.log(tui.muted(' h') + ' - show this help');
|
|
765
|
+
console.log(tui.muted(' c') + ' - clear console');
|
|
766
|
+
console.log(tui.muted(' q') + ' - quit\n');
|
|
767
|
+
};
|
|
768
|
+
process.stdin.on('data', (data) => {
|
|
769
|
+
const key = data.toString();
|
|
770
|
+
// Handle Ctrl+C - send SIGINT to trigger graceful shutdown
|
|
771
|
+
if (key === '\u0003') {
|
|
772
|
+
process.kill(process.pid, 'SIGINT');
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
switch (key) {
|
|
776
|
+
case 'h':
|
|
777
|
+
showHelp();
|
|
778
|
+
break;
|
|
779
|
+
case 'c':
|
|
780
|
+
console.clear();
|
|
781
|
+
tui.banner('⨺ Agentuity DevMode', devmodebody, {
|
|
782
|
+
padding: 2,
|
|
783
|
+
topSpacer: false,
|
|
784
|
+
bottomSpacer: false,
|
|
785
|
+
centerTitle: false,
|
|
786
|
+
});
|
|
787
|
+
break;
|
|
788
|
+
case 'q':
|
|
789
|
+
void cleanup(true, 0);
|
|
790
|
+
break;
|
|
791
|
+
default:
|
|
792
|
+
process.stdout.write(data);
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
showWelcome();
|
|
798
|
+
// Start/resume file watcher now that server is ready
|
|
799
|
+
fileWatcher.resume();
|
|
800
|
+
// Wait for restart signal or shutdown
|
|
801
|
+
await new Promise((resolve) => {
|
|
802
|
+
const checkRestart = setInterval(() => {
|
|
803
|
+
if (shouldRestart || shutdownRequested) {
|
|
804
|
+
clearInterval(checkRestart);
|
|
805
|
+
resolve();
|
|
806
|
+
}
|
|
807
|
+
}, 100);
|
|
808
|
+
});
|
|
809
|
+
// Exit loop if shutdown was requested
|
|
810
|
+
if (shutdownRequested) {
|
|
811
|
+
break;
|
|
812
|
+
}
|
|
813
|
+
// Restart triggered - cleanup and loop (Vite stays running)
|
|
814
|
+
logger.debug('Restarting backend server...');
|
|
815
|
+
// Clean up Bun server and Gravity (Vite stays running)
|
|
816
|
+
await cleanupForRestart();
|
|
817
|
+
// Brief pause before restart
|
|
818
|
+
await Bun.sleep(500);
|
|
819
|
+
}
|
|
820
|
+
catch (error) {
|
|
821
|
+
tui.error(`Error during server operation: ${error}`);
|
|
822
|
+
tui.warn('Waiting for file changes to retry...');
|
|
823
|
+
// Cleanup on error (Vite stays running)
|
|
824
|
+
await cleanupForRestart();
|
|
825
|
+
// Exit if shutdown was requested during error handling
|
|
826
|
+
if (shutdownRequested) {
|
|
827
|
+
break;
|
|
828
|
+
}
|
|
829
|
+
// Resume file watcher to detect changes for retry
|
|
830
|
+
fileWatcher.resume();
|
|
831
|
+
// Wait for next restart trigger or shutdown
|
|
832
|
+
await new Promise((resolve) => {
|
|
833
|
+
const checkRestart = setInterval(() => {
|
|
834
|
+
if (shouldRestart || shutdownRequested) {
|
|
835
|
+
clearInterval(checkRestart);
|
|
836
|
+
resolve();
|
|
837
|
+
}
|
|
838
|
+
}, 100);
|
|
839
|
+
});
|
|
840
|
+
}
|
|
802
841
|
}
|
|
803
842
|
}
|
|
843
|
+
finally {
|
|
844
|
+
/* brute force clean up */
|
|
845
|
+
await devLock.release();
|
|
846
|
+
await killLingeringGravityProcesses(logger);
|
|
847
|
+
releaseLockSync(rootDir);
|
|
848
|
+
}
|
|
804
849
|
},
|
|
805
850
|
});
|
|
806
851
|
//# sourceMappingURL=index.js.map
|