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