@agentuity/cli 1.0.48 → 2.0.0-beta.0
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/app-router-detector.d.ts +2 -5
- package/dist/cmd/build/app-router-detector.d.ts.map +1 -1
- package/dist/cmd/build/app-router-detector.js +130 -154
- package/dist/cmd/build/app-router-detector.js.map +1 -1
- package/dist/cmd/build/ids.d.ts +11 -0
- package/dist/cmd/build/ids.d.ts.map +1 -0
- package/dist/cmd/build/ids.js +18 -0
- package/dist/cmd/build/ids.js.map +1 -0
- package/dist/cmd/build/vite/agent-discovery.d.ts +8 -4
- package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
- package/dist/cmd/build/vite/agent-discovery.js +166 -487
- package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
- package/dist/cmd/build/vite/bun-dev-server.d.ts +10 -16
- package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
- package/dist/cmd/build/vite/bun-dev-server.js +67 -134
- package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
- package/dist/cmd/build/vite/docs-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/docs-generator.js +0 -2
- package/dist/cmd/build/vite/docs-generator.js.map +1 -1
- package/dist/cmd/build/vite/index.d.ts.map +1 -1
- package/dist/cmd/build/vite/index.js +0 -36
- package/dist/cmd/build/vite/index.js.map +1 -1
- package/dist/cmd/build/vite/lifecycle-generator.d.ts +10 -2
- package/dist/cmd/build/vite/lifecycle-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/lifecycle-generator.js +302 -23
- package/dist/cmd/build/vite/lifecycle-generator.js.map +1 -1
- package/dist/cmd/build/vite/route-discovery.d.ts +11 -38
- package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
- package/dist/cmd/build/vite/route-discovery.js +97 -177
- package/dist/cmd/build/vite/route-discovery.js.map +1 -1
- package/dist/cmd/build/vite/server-bundler.js +1 -1
- package/dist/cmd/build/vite/server-bundler.js.map +1 -1
- package/dist/cmd/build/vite/static-renderer.d.ts.map +1 -1
- package/dist/cmd/build/vite/static-renderer.js +1 -9
- package/dist/cmd/build/vite/static-renderer.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.d.ts +6 -3
- package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.js +171 -21
- package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.d.ts +8 -3
- package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.js +14 -13
- 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 +6 -36
- package/dist/cmd/build/vite/vite-builder.js.map +1 -1
- package/dist/cmd/build/vite/ws-proxy.d.ts +53 -0
- package/dist/cmd/build/vite/ws-proxy.d.ts.map +1 -0
- package/dist/cmd/build/vite/ws-proxy.js +95 -0
- package/dist/cmd/build/vite/ws-proxy.js.map +1 -0
- package/dist/cmd/build/vite-bundler.d.ts.map +1 -1
- package/dist/cmd/build/vite-bundler.js +0 -3
- package/dist/cmd/build/vite-bundler.js.map +1 -1
- package/dist/cmd/cloud/deploy.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.js +0 -1
- package/dist/cmd/cloud/deploy.js.map +1 -1
- package/dist/cmd/dev/file-watcher.d.ts.map +1 -1
- package/dist/cmd/dev/file-watcher.js +2 -8
- package/dist/cmd/dev/file-watcher.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +369 -720
- package/dist/cmd/dev/index.js.map +1 -1
- package/package.json +6 -8
- package/src/cmd/ai/prompt/agent.md +0 -1
- package/src/cmd/ai/prompt/api.md +0 -7
- package/src/cmd/ai/prompt/web.md +51 -213
- package/src/cmd/build/app-router-detector.ts +152 -182
- package/src/cmd/build/ids.ts +19 -0
- package/src/cmd/build/vite/agent-discovery.ts +208 -679
- package/src/cmd/build/vite/bun-dev-server.ts +78 -154
- package/src/cmd/build/vite/docs-generator.ts +0 -2
- package/src/cmd/build/vite/index.ts +1 -42
- package/src/cmd/build/vite/lifecycle-generator.ts +345 -21
- package/src/cmd/build/vite/route-discovery.ts +116 -274
- package/src/cmd/build/vite/server-bundler.ts +1 -1
- package/src/cmd/build/vite/static-renderer.ts +1 -11
- package/src/cmd/build/vite/vite-asset-server-config.ts +196 -23
- package/src/cmd/build/vite/vite-asset-server.ts +25 -15
- package/src/cmd/build/vite/vite-builder.ts +6 -53
- package/src/cmd/build/vite/ws-proxy.ts +126 -0
- package/src/cmd/build/vite-bundler.ts +0 -4
- package/src/cmd/cloud/deploy.ts +0 -1
- package/src/cmd/dev/file-watcher.ts +2 -9
- package/src/cmd/dev/index.ts +409 -832
- package/dist/cmd/build/ast.d.ts +0 -78
- package/dist/cmd/build/ast.d.ts.map +0 -1
- package/dist/cmd/build/ast.js +0 -2703
- package/dist/cmd/build/ast.js.map +0 -1
- package/dist/cmd/build/entry-generator.d.ts +0 -25
- package/dist/cmd/build/entry-generator.d.ts.map +0 -1
- package/dist/cmd/build/entry-generator.js +0 -695
- package/dist/cmd/build/entry-generator.js.map +0 -1
- package/dist/cmd/build/vite/api-mount-path.d.ts +0 -61
- package/dist/cmd/build/vite/api-mount-path.d.ts.map +0 -1
- package/dist/cmd/build/vite/api-mount-path.js +0 -83
- package/dist/cmd/build/vite/api-mount-path.js.map +0 -1
- package/dist/cmd/build/vite/registry-generator.d.ts +0 -19
- package/dist/cmd/build/vite/registry-generator.d.ts.map +0 -1
- package/dist/cmd/build/vite/registry-generator.js +0 -1108
- package/dist/cmd/build/vite/registry-generator.js.map +0 -1
- package/dist/cmd/build/vite/tailwind-source-plugin.d.ts +0 -13
- package/dist/cmd/build/vite/tailwind-source-plugin.d.ts.map +0 -1
- package/dist/cmd/build/vite/tailwind-source-plugin.js +0 -44
- package/dist/cmd/build/vite/tailwind-source-plugin.js.map +0 -1
- package/dist/cmd/build/webanalytics-generator.d.ts +0 -16
- package/dist/cmd/build/webanalytics-generator.d.ts.map +0 -1
- package/dist/cmd/build/webanalytics-generator.js +0 -178
- package/dist/cmd/build/webanalytics-generator.js.map +0 -1
- package/dist/cmd/build/workbench.d.ts +0 -7
- package/dist/cmd/build/workbench.d.ts.map +0 -1
- package/dist/cmd/build/workbench.js +0 -55
- package/dist/cmd/build/workbench.js.map +0 -1
- package/dist/utils/route-migration.d.ts +0 -62
- package/dist/utils/route-migration.d.ts.map +0 -1
- package/dist/utils/route-migration.js +0 -630
- package/dist/utils/route-migration.js.map +0 -1
- package/src/cmd/build/ast.ts +0 -3529
- package/src/cmd/build/entry-generator.ts +0 -760
- package/src/cmd/build/vite/api-mount-path.ts +0 -87
- package/src/cmd/build/vite/registry-generator.ts +0 -1267
- package/src/cmd/build/vite/tailwind-source-plugin.ts +0 -54
- package/src/cmd/build/webanalytics-generator.ts +0 -197
- package/src/cmd/build/workbench.ts +0 -58
- package/src/utils/route-migration.ts +0 -757
package/src/cmd/dev/index.ts
CHANGED
|
@@ -11,20 +11,16 @@ import { generateEndpoint, type DevmodeResponse } from './api';
|
|
|
11
11
|
import { APIClient, getAPIBaseURL, getAppBaseURL, getGravityDevModeURL } from '../../api';
|
|
12
12
|
import { download } from './download';
|
|
13
13
|
import { createDevmodeSyncService } from './sync';
|
|
14
|
-
import { getDevmodeDeploymentId } from '../build/
|
|
14
|
+
import { getDevmodeDeploymentId } from '../build/ids';
|
|
15
15
|
import { getDefaultConfigDir, saveConfig, loadProjectSDKKey, getAuth } from '../../config';
|
|
16
16
|
import type { Config } from '../../types';
|
|
17
17
|
import { typecheck } from '../build/typecheck';
|
|
18
18
|
import { validateGravityRequiresUpgrade } from '../../runtime';
|
|
19
19
|
import { isTTY, hasLoggedInBefore } from '../../auth';
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
import { prepareDevLock, releaseLockSync } from './dev-lock';
|
|
22
22
|
import { checkAndUpgradeDependencies } from '../../utils/dependency-checker';
|
|
23
|
-
|
|
24
|
-
promptRouteMigration,
|
|
25
|
-
performMigration,
|
|
26
|
-
checkMigrationEligibility,
|
|
27
|
-
} from '../../utils/route-migration';
|
|
23
|
+
|
|
28
24
|
import { ErrorCode } from '../../errors';
|
|
29
25
|
|
|
30
26
|
const DEFAULT_PORT = 3500;
|
|
@@ -43,11 +39,6 @@ interface ServerLike {
|
|
|
43
39
|
close: () => void;
|
|
44
40
|
}
|
|
45
41
|
|
|
46
|
-
interface BunServer {
|
|
47
|
-
stop: (closeActiveConnections?: boolean) => void;
|
|
48
|
-
port: number;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
42
|
/**
|
|
52
43
|
* Kill any lingering gravity processes from previous dev sessions.
|
|
53
44
|
* This is a defensive measure to clean up orphaned processes.
|
|
@@ -83,90 +74,21 @@ async function killLingeringGravityProcesses(logger: {
|
|
|
83
74
|
}
|
|
84
75
|
|
|
85
76
|
/**
|
|
86
|
-
*
|
|
87
|
-
* Waits for the port to become available before returning (with timeout).
|
|
88
|
-
* Handles both in-process server and subprocess (when debugger is enabled).
|
|
77
|
+
* Kill the Bun backend subprocess if one is running.
|
|
89
78
|
*/
|
|
90
|
-
|
|
91
|
-
port: number,
|
|
92
|
-
logger: { debug: (msg: string, ...args: unknown[]) => void }
|
|
93
|
-
): Promise<void> {
|
|
79
|
+
function killBunSubprocess(logger: { debug: (msg: string, ...args: unknown[]) => void }): void {
|
|
94
80
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
95
81
|
const globalAny = globalThis as any;
|
|
96
|
-
|
|
97
|
-
// Check for subprocess first (used when debugger flags are enabled)
|
|
98
82
|
const bunSubprocess = globalAny.__AGENTUITY_BUN_SUBPROCESS__ as ProcessLike | undefined;
|
|
99
|
-
if (bunSubprocess)
|
|
100
|
-
logger.debug('Stopping Bun subprocess...');
|
|
101
|
-
try {
|
|
102
|
-
bunSubprocess.kill('SIGTERM');
|
|
103
|
-
// After SIGTERM, wait and check multiple times before giving up
|
|
104
|
-
let attempts = 0;
|
|
105
|
-
while (bunSubprocess.exitCode === null && attempts < 3) {
|
|
106
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
107
|
-
attempts++;
|
|
108
|
-
}
|
|
109
|
-
if (bunSubprocess.exitCode === null) {
|
|
110
|
-
bunSubprocess.kill('SIGKILL');
|
|
111
|
-
}
|
|
112
|
-
logger.debug('Bun subprocess killed');
|
|
113
|
-
} catch (err) {
|
|
114
|
-
logger.debug('Error killing Bun subprocess: %s', err);
|
|
115
|
-
}
|
|
116
|
-
globalAny.__AGENTUITY_BUN_SUBPROCESS__ = undefined;
|
|
117
|
-
|
|
118
|
-
// Wait for port to become available
|
|
119
|
-
const MAX_WAIT_ITERATIONS = 10;
|
|
120
|
-
for (let i = 0; i < MAX_WAIT_ITERATIONS; i++) {
|
|
121
|
-
try {
|
|
122
|
-
await fetch(`http://127.0.0.1:${port}/`, {
|
|
123
|
-
method: 'HEAD',
|
|
124
|
-
signal: AbortSignal.timeout(150),
|
|
125
|
-
});
|
|
126
|
-
// Still responding, wait a bit more
|
|
127
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
128
|
-
} catch {
|
|
129
|
-
// Connection refused or timeout => server is down
|
|
130
|
-
logger.debug('Bun subprocess stopped');
|
|
131
|
-
break;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Handle in-process server
|
|
138
|
-
const server = globalAny.__AGENTUITY_SERVER__ as BunServer | undefined;
|
|
139
|
-
if (!server) {
|
|
140
|
-
logger.debug('No Bun server to stop');
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
83
|
+
if (!bunSubprocess) return;
|
|
143
84
|
|
|
144
85
|
try {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
logger.debug('Bun server stop() called');
|
|
86
|
+
bunSubprocess.kill('SIGTERM');
|
|
87
|
+
logger.debug('Bun subprocess killed');
|
|
148
88
|
} catch (err) {
|
|
149
|
-
logger.debug('Error
|
|
89
|
+
logger.debug('Error killing Bun subprocess: %s', err);
|
|
150
90
|
}
|
|
151
|
-
|
|
152
|
-
// Wait for socket to close (max 2 seconds to avoid hanging on shutdown)
|
|
153
|
-
const MAX_WAIT_ITERATIONS = 10;
|
|
154
|
-
for (let i = 0; i < MAX_WAIT_ITERATIONS; i++) {
|
|
155
|
-
try {
|
|
156
|
-
await fetch(`http://127.0.0.1:${port}/`, {
|
|
157
|
-
method: 'HEAD',
|
|
158
|
-
signal: AbortSignal.timeout(150),
|
|
159
|
-
});
|
|
160
|
-
// Still responding, wait a bit more
|
|
161
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
162
|
-
} catch {
|
|
163
|
-
// Connection refused or timeout => server is down
|
|
164
|
-
logger.debug('Bun server stopped');
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
globalAny.__AGENTUITY_SERVER__ = undefined;
|
|
91
|
+
globalAny.__AGENTUITY_BUN_SUBPROCESS__ = undefined;
|
|
170
92
|
}
|
|
171
93
|
|
|
172
94
|
const getDefaultPort = (): number => {
|
|
@@ -227,22 +149,12 @@ export const command = createCommand({
|
|
|
227
149
|
.boolean()
|
|
228
150
|
.optional()
|
|
229
151
|
.describe('Enable bun debugger with breakpoint at first line'),
|
|
230
|
-
|
|
231
|
-
.boolean()
|
|
232
|
-
.optional()
|
|
233
|
-
.describe(
|
|
234
|
-
'[Experimental] Skip Bun.build in dev mode — run generated entry file directly'
|
|
235
|
-
),
|
|
152
|
+
|
|
236
153
|
noTypecheck: z
|
|
237
154
|
.boolean()
|
|
238
155
|
.optional()
|
|
239
156
|
.describe('Skip TypeScript type checking on startup and restarts'),
|
|
240
|
-
|
|
241
|
-
.boolean()
|
|
242
|
-
.optional()
|
|
243
|
-
.describe(
|
|
244
|
-
'Migrate file-based routes to explicit routing (src/api/index.ts root router)'
|
|
245
|
-
),
|
|
157
|
+
|
|
246
158
|
resume: z.string().optional().describe('Resume a paused Hub session by ID'),
|
|
247
159
|
}),
|
|
248
160
|
},
|
|
@@ -425,29 +337,6 @@ export const command = createCommand({
|
|
|
425
337
|
);
|
|
426
338
|
}
|
|
427
339
|
|
|
428
|
-
// Check if project can migrate to explicit routing
|
|
429
|
-
if (opts.migrateRoutes) {
|
|
430
|
-
const eligibility = checkMigrationEligibility(rootDir);
|
|
431
|
-
if (eligibility.available) {
|
|
432
|
-
const result = performMigration(rootDir, eligibility.routeFiles);
|
|
433
|
-
if (result.success) {
|
|
434
|
-
tui.success(result.message);
|
|
435
|
-
if (result.filesCreated.length > 0) {
|
|
436
|
-
tui.info(`Created: ${result.filesCreated.map((f) => tui.muted(f)).join(', ')}`);
|
|
437
|
-
}
|
|
438
|
-
tui.newline();
|
|
439
|
-
} else {
|
|
440
|
-
tui.warning(result.message);
|
|
441
|
-
tui.newline();
|
|
442
|
-
}
|
|
443
|
-
} else {
|
|
444
|
-
tui.info('No migration needed — already using explicit routing.');
|
|
445
|
-
tui.newline();
|
|
446
|
-
}
|
|
447
|
-
} else {
|
|
448
|
-
await promptRouteMigration(rootDir, logger, { interactive });
|
|
449
|
-
}
|
|
450
|
-
|
|
451
340
|
try {
|
|
452
341
|
// Setup devmode and gravity (if using public URL)
|
|
453
342
|
const useMockService = process.env.DEVMODE_SYNC_SERVICE_MOCK === 'true';
|
|
@@ -585,17 +474,70 @@ export const command = createCommand({
|
|
|
585
474
|
centerTitle: false,
|
|
586
475
|
});
|
|
587
476
|
|
|
588
|
-
//
|
|
589
|
-
//
|
|
477
|
+
// Detect user route mount paths for Vite proxy configuration
|
|
478
|
+
// This is a quick AST scan of app.ts — runs before Vite starts
|
|
479
|
+
let routePaths: string[] = ['/api']; // Default fallback
|
|
480
|
+
try {
|
|
481
|
+
const { detectExplicitRouter } = await import('../build/app-router-detector');
|
|
482
|
+
const detection = await detectExplicitRouter(rootDir, logger);
|
|
483
|
+
if (detection.detected && detection.mounts.length > 0) {
|
|
484
|
+
routePaths = detection.mounts.map((m) => m.path);
|
|
485
|
+
logger.debug('Detected route mount paths: %s', routePaths.join(', '));
|
|
486
|
+
}
|
|
487
|
+
} catch (err) {
|
|
488
|
+
logger.debug('Route detection failed, using default /api: %s', err);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Pick internal ports (neither is user-facing — the front-door proxy is)
|
|
492
|
+
const bunBackendPort = opts.port + 1;
|
|
493
|
+
const viteInternalPort = opts.port + 2;
|
|
494
|
+
|
|
495
|
+
// No-bundle dev mode guard: ensure stale bundled app artifact cannot be executed.
|
|
496
|
+
// We keep other .agentuity artifacts (metadata/workbench files) intact.
|
|
497
|
+
try {
|
|
498
|
+
const staleBundlePath = join(rootDir, '.agentuity', 'app.js');
|
|
499
|
+
if (existsSync(staleBundlePath)) {
|
|
500
|
+
await Bun.file(staleBundlePath).delete();
|
|
501
|
+
logger.debug('Removed stale dev bundle artifact: %s', staleBundlePath);
|
|
502
|
+
}
|
|
503
|
+
} catch (err) {
|
|
504
|
+
logger.debug('Failed to remove stale dev bundle artifact: %s', err);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Debug trace: locate unexpected legacy credential warnings.
|
|
508
|
+
// Enable with AGENTUITY_TRACE_CREDENTIAL_WARNINGS=true.
|
|
509
|
+
if (process.env.AGENTUITY_TRACE_CREDENTIAL_WARNINGS === 'true') {
|
|
510
|
+
const originalConsoleError = console.error.bind(console);
|
|
511
|
+
console.error = (...args: unknown[]) => {
|
|
512
|
+
try {
|
|
513
|
+
const first = typeof args[0] === 'string' ? args[0] : '';
|
|
514
|
+
if (first.includes('No credentials found for this AI provider')) {
|
|
515
|
+
const stack = new Error('Credential warning trace').stack;
|
|
516
|
+
originalConsoleError('[TRACE] Credential warning origin stack:');
|
|
517
|
+
if (stack) originalConsoleError(stack);
|
|
518
|
+
}
|
|
519
|
+
} catch {
|
|
520
|
+
// ignore tracing errors
|
|
521
|
+
}
|
|
522
|
+
originalConsoleError(...args);
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Start Vite dev server on an internal port.
|
|
527
|
+
// The user-facing port is handled by the front-door TCP proxy (ws-proxy)
|
|
528
|
+
// which routes WS upgrades to Bun and everything else to Vite.
|
|
590
529
|
let viteServer: ServerLike | null = null;
|
|
591
530
|
let vitePort: number;
|
|
592
531
|
|
|
593
532
|
try {
|
|
594
|
-
logger.debug('Starting Vite
|
|
533
|
+
logger.debug('Starting Vite dev server (internal port %d)...', viteInternalPort);
|
|
595
534
|
const viteResult = await startViteAssetServer({
|
|
596
535
|
rootDir,
|
|
597
536
|
logger,
|
|
598
537
|
workbenchPath: workbench.config?.route,
|
|
538
|
+
port: viteInternalPort,
|
|
539
|
+
backendPort: bunBackendPort,
|
|
540
|
+
routePaths,
|
|
599
541
|
});
|
|
600
542
|
viteServer = viteResult.server;
|
|
601
543
|
vitePort = viteResult.port;
|
|
@@ -604,53 +546,49 @@ export const command = createCommand({
|
|
|
604
546
|
await devLock.updatePorts({ vite: vitePort });
|
|
605
547
|
|
|
606
548
|
logger.debug(
|
|
607
|
-
`Vite
|
|
549
|
+
`Vite dev server running on port ${vitePort} (internal, proxying backend on port ${bunBackendPort})`
|
|
608
550
|
);
|
|
609
551
|
} catch (error) {
|
|
610
|
-
tui.error(`Failed to start Vite
|
|
552
|
+
tui.error(`Failed to start Vite dev server: ${error}`);
|
|
611
553
|
await devLock.release();
|
|
612
554
|
originalExit(1);
|
|
613
555
|
return;
|
|
614
556
|
}
|
|
615
557
|
|
|
616
|
-
//
|
|
617
|
-
//
|
|
618
|
-
|
|
558
|
+
// Start the front-door TCP proxy on the user-facing port.
|
|
559
|
+
// Routes WebSocket upgrades (for /api/*, /_agentuity/*) directly to Bun
|
|
560
|
+
// and everything else (HTTP, HMR WebSocket) to Vite.
|
|
561
|
+
// This works around Bun's broken node:http upgrade socket implementation.
|
|
562
|
+
let frontDoorServer: import('node:net').Server | null = null;
|
|
563
|
+
try {
|
|
564
|
+
const { startWsProxy } = await import('../build/vite/ws-proxy');
|
|
565
|
+
frontDoorServer = await startWsProxy({
|
|
566
|
+
port: opts.port,
|
|
567
|
+
vitePort,
|
|
568
|
+
backendPort: bunBackendPort,
|
|
569
|
+
routePaths,
|
|
570
|
+
logger,
|
|
571
|
+
});
|
|
572
|
+
logger.debug(
|
|
573
|
+
`Front-door proxy on port ${opts.port} (Vite:${vitePort}, Bun:${bunBackendPort})`
|
|
574
|
+
);
|
|
575
|
+
} catch (error) {
|
|
576
|
+
tui.error(`Failed to start front-door proxy: ${error}`);
|
|
577
|
+
await devLock.release();
|
|
578
|
+
originalExit(1);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// --- State for long-running processes ---
|
|
619
583
|
let gravityProcess: ProcessLike | null = null;
|
|
620
584
|
let gravityHeartbeatInterval: ReturnType<typeof setInterval> | null = null;
|
|
621
|
-
let stdinListenerRegistered = false;
|
|
622
|
-
|
|
623
|
-
const restartServer = () => {
|
|
624
|
-
shouldRestart = true;
|
|
625
|
-
};
|
|
626
|
-
|
|
627
|
-
const showWelcome = () => {
|
|
628
|
-
logger.info('DevMode ready 🚀');
|
|
629
|
-
};
|
|
630
|
-
|
|
631
|
-
// Create file watcher for backend hot reload
|
|
632
|
-
const fileWatcher = createFileWatcher({
|
|
633
|
-
rootDir,
|
|
634
|
-
logger,
|
|
635
|
-
onRestart: restartServer,
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
// Start file watcher (will be paused during builds)
|
|
639
|
-
fileWatcher.start();
|
|
640
|
-
|
|
641
|
-
// Track if cleanup is in progress to avoid duplicate cleanup
|
|
642
|
-
let cleaningUp = false;
|
|
643
|
-
// Track if shutdown was requested (SIGINT/SIGTERM) to break the main loop
|
|
644
|
-
let shutdownRequested = false;
|
|
645
|
-
// Store stdin data handler reference for cleanup
|
|
585
|
+
let stdinListenerRegistered = false;
|
|
646
586
|
let stdinDataHandler: ((data: Buffer | string) => void) | null = null;
|
|
587
|
+
let shutdownRequested = false;
|
|
588
|
+
let cleaningUp = false;
|
|
647
589
|
|
|
648
590
|
/**
|
|
649
591
|
* Centralized cleanup function for all resources.
|
|
650
|
-
* Called on restart, shutdown, and fatal errors.
|
|
651
|
-
* @param exitAfter - If true, exit the process after cleanup
|
|
652
|
-
* @param exitCode - Exit code to use if exitAfter is true
|
|
653
|
-
* @param silent - If true, don't show "Shutting down" message
|
|
654
592
|
*/
|
|
655
593
|
const cleanup = async (exitAfter = false, exitCode = 0, silent = false) => {
|
|
656
594
|
if (cleaningUp) return;
|
|
@@ -660,19 +598,15 @@ export const command = createCommand({
|
|
|
660
598
|
tui.info('Shutting down...');
|
|
661
599
|
}
|
|
662
600
|
|
|
663
|
-
// Stop
|
|
601
|
+
// Stop front-door proxy
|
|
664
602
|
try {
|
|
665
|
-
|
|
603
|
+
frontDoorServer?.close();
|
|
666
604
|
} catch (err) {
|
|
667
|
-
logger.debug('Error stopping
|
|
605
|
+
logger.debug('Error stopping front-door proxy: %s', err);
|
|
668
606
|
}
|
|
669
607
|
|
|
670
|
-
//
|
|
671
|
-
|
|
672
|
-
await stopBunServer(opts.port, logger);
|
|
673
|
-
} catch (err) {
|
|
674
|
-
logger.debug('Error stopping Bun server during cleanup: %s', err);
|
|
675
|
-
}
|
|
608
|
+
// Kill Bun subprocess
|
|
609
|
+
killBunSubprocess(logger);
|
|
676
610
|
|
|
677
611
|
// Stop gravity heartbeat interval
|
|
678
612
|
if (gravityHeartbeatInterval) {
|
|
@@ -680,62 +614,38 @@ export const command = createCommand({
|
|
|
680
614
|
gravityHeartbeatInterval = null;
|
|
681
615
|
}
|
|
682
616
|
|
|
683
|
-
// Kill gravity client
|
|
617
|
+
// Kill gravity client
|
|
684
618
|
if (gravityProcess) {
|
|
685
|
-
logger.debug('Killing gravity process...');
|
|
686
619
|
try {
|
|
687
620
|
gravityProcess.kill('SIGTERM');
|
|
688
|
-
// Give it a moment to gracefully shutdown
|
|
689
621
|
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
690
622
|
if (gravityProcess.exitCode === null) {
|
|
691
623
|
gravityProcess.kill('SIGKILL');
|
|
692
624
|
}
|
|
693
|
-
logger.debug('Gravity process killed');
|
|
694
625
|
} catch (err) {
|
|
695
|
-
logger.debug('Error killing gravity
|
|
626
|
+
logger.debug('Error killing gravity: %s', err);
|
|
696
627
|
} finally {
|
|
697
628
|
gravityProcess = null;
|
|
698
629
|
}
|
|
699
630
|
}
|
|
700
631
|
|
|
701
|
-
// Close Vite
|
|
632
|
+
// Close Vite
|
|
702
633
|
if (viteServer) {
|
|
703
|
-
logger.debug('Closing Vite server...');
|
|
704
634
|
try {
|
|
705
|
-
// Use Promise.race with timeout to prevent hanging
|
|
706
635
|
const closePromise = viteServer.close();
|
|
707
|
-
const timeoutPromise = new Promise<void>((resolve) =>
|
|
708
|
-
setTimeout(() => {
|
|
709
|
-
logger.debug('Vite server close timed out, continuing...');
|
|
710
|
-
resolve();
|
|
711
|
-
}, 2000);
|
|
712
|
-
});
|
|
636
|
+
const timeoutPromise = new Promise<void>((resolve) => setTimeout(resolve, 2000));
|
|
713
637
|
await Promise.race([closePromise, timeoutPromise]);
|
|
714
|
-
logger.debug('Vite server closed');
|
|
715
638
|
} catch (err) {
|
|
716
|
-
logger.debug('Error closing Vite
|
|
639
|
+
logger.debug('Error closing Vite: %s', err);
|
|
717
640
|
} finally {
|
|
718
641
|
viteServer = null;
|
|
719
642
|
}
|
|
720
643
|
}
|
|
721
644
|
|
|
722
|
-
|
|
723
|
-
logger.debug('Releasing dev lock...');
|
|
724
|
-
try {
|
|
725
|
-
await devLock.release();
|
|
726
|
-
logger.debug('Dev lock released');
|
|
727
|
-
} catch (err) {
|
|
728
|
-
logger.debug('Error releasing dev lock: %s', err);
|
|
729
|
-
}
|
|
730
|
-
|
|
645
|
+
await devLock.release();
|
|
731
646
|
await killLingeringGravityProcesses(logger);
|
|
732
647
|
|
|
733
|
-
|
|
734
|
-
if (!exitAfter) {
|
|
735
|
-
cleaningUp = false;
|
|
736
|
-
} else {
|
|
737
|
-
// Clean up stdin keyboard handler right before exiting
|
|
738
|
-
// This must happen AFTER all async cleanup to keep event loop alive
|
|
648
|
+
if (exitAfter) {
|
|
739
649
|
if (stdinListenerRegistered && process.stdin.isTTY) {
|
|
740
650
|
try {
|
|
741
651
|
if (stdinDataHandler) {
|
|
@@ -746,699 +656,366 @@ export const command = createCommand({
|
|
|
746
656
|
process.stdin.pause();
|
|
747
657
|
process.stdin.unref();
|
|
748
658
|
} catch {
|
|
749
|
-
// Ignore
|
|
659
|
+
// Ignore
|
|
750
660
|
}
|
|
751
661
|
}
|
|
752
|
-
logger.debug('Exiting with code %d', exitCode);
|
|
753
662
|
originalExit(exitCode);
|
|
754
663
|
}
|
|
755
664
|
};
|
|
756
665
|
|
|
757
|
-
|
|
758
|
-
* Cleanup for restart: stops Bun server and Gravity, keeps Vite running
|
|
759
|
-
*/
|
|
760
|
-
const cleanupForRestart = async () => {
|
|
761
|
-
logger.debug('Cleaning up for restart...');
|
|
762
|
-
|
|
763
|
-
// Stop Bun server
|
|
764
|
-
try {
|
|
765
|
-
await stopBunServer(opts.port, logger);
|
|
766
|
-
} catch (err) {
|
|
767
|
-
logger.debug('Error stopping Bun server for restart: %s', err);
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
// Stop gravity heartbeat interval
|
|
771
|
-
if (gravityHeartbeatInterval) {
|
|
772
|
-
clearInterval(gravityHeartbeatInterval);
|
|
773
|
-
gravityHeartbeatInterval = null;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
// Kill gravity client
|
|
777
|
-
if (gravityProcess) {
|
|
778
|
-
try {
|
|
779
|
-
gravityProcess.kill('SIGTERM');
|
|
780
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
781
|
-
if (gravityProcess.exitCode === null) {
|
|
782
|
-
gravityProcess.kill('SIGKILL');
|
|
783
|
-
}
|
|
784
|
-
} catch (err) {
|
|
785
|
-
logger.debug('Error killing gravity process for restart: %s', err);
|
|
786
|
-
} finally {
|
|
787
|
-
gravityProcess = null;
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
};
|
|
791
|
-
|
|
792
|
-
// SIGINT/SIGTERM: coordinate shutdown between bundle and dev resources
|
|
793
|
-
let signalHandlersRegistered = false;
|
|
666
|
+
// Signal handlers
|
|
794
667
|
let exitingFromSignal = false;
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
if (reason) {
|
|
804
|
-
logger.debug('DevMode terminating (%d) due to: %s', code, reason);
|
|
805
|
-
}
|
|
806
|
-
shutdownRequested = true;
|
|
807
|
-
// Run cleanup and ensure we wait for it to complete before exiting
|
|
808
|
-
cleanup(true, code).catch((err) => {
|
|
809
|
-
logger.debug('Cleanup error: %s', err);
|
|
810
|
-
originalExit(1);
|
|
811
|
-
});
|
|
812
|
-
};
|
|
813
|
-
|
|
814
|
-
process.on('SIGINT', () => {
|
|
815
|
-
safeExit(0, 'SIGINT');
|
|
816
|
-
});
|
|
817
|
-
|
|
818
|
-
process.on('SIGTERM', () => {
|
|
819
|
-
safeExit(0, 'SIGTERM');
|
|
820
|
-
});
|
|
821
|
-
|
|
822
|
-
// Handle SIGHUP (terminal closed) - same as SIGINT
|
|
823
|
-
process.on('SIGHUP', () => {
|
|
824
|
-
safeExit(0, 'SIGHUP');
|
|
825
|
-
});
|
|
826
|
-
|
|
827
|
-
// Handle uncaught exceptions - clean up and exit rather than limping on
|
|
828
|
-
process.on('uncaughtException', (err) => {
|
|
829
|
-
tui.error(
|
|
830
|
-
`Uncaught exception: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`
|
|
831
|
-
);
|
|
832
|
-
void safeExit(1, 'uncaughtException');
|
|
833
|
-
});
|
|
834
|
-
|
|
835
|
-
// Handle unhandled rejections - log but don't exit (usually recoverable)
|
|
836
|
-
process.on('unhandledRejection', (reason) => {
|
|
837
|
-
logger.warn(
|
|
838
|
-
'Unhandled promise rejection: %s',
|
|
839
|
-
reason instanceof Error ? (reason.stack ?? reason.message) : String(reason)
|
|
840
|
-
);
|
|
841
|
-
});
|
|
842
|
-
}
|
|
668
|
+
const safeExit = (code: number, reason?: string) => {
|
|
669
|
+
if (exitingFromSignal) return;
|
|
670
|
+
exitingFromSignal = true;
|
|
671
|
+
if (reason) logger.debug('DevMode terminating (%d): %s', code, reason);
|
|
672
|
+
shutdownRequested = true;
|
|
673
|
+
cleanup(true, code).catch(() => originalExit(1));
|
|
674
|
+
};
|
|
843
675
|
|
|
844
|
-
|
|
676
|
+
process.on('SIGINT', () => safeExit(0, 'SIGINT'));
|
|
677
|
+
process.on('SIGTERM', () => safeExit(0, 'SIGTERM'));
|
|
678
|
+
process.on('SIGHUP', () => safeExit(0, 'SIGHUP'));
|
|
679
|
+
process.on('uncaughtException', (err) => {
|
|
680
|
+
tui.error(
|
|
681
|
+
`Uncaught exception: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`
|
|
682
|
+
);
|
|
683
|
+
void safeExit(1, 'uncaughtException');
|
|
684
|
+
});
|
|
685
|
+
process.on('unhandledRejection', (reason) => {
|
|
686
|
+
logger.warn(
|
|
687
|
+
'Unhandled promise rejection: %s',
|
|
688
|
+
reason instanceof Error ? (reason.stack ?? reason.message) : String(reason)
|
|
689
|
+
);
|
|
690
|
+
});
|
|
845
691
|
process.on('exit', () => {
|
|
846
|
-
|
|
847
|
-
if (stdinListenerRegistered && process.stdin.isTTY) {
|
|
848
|
-
try {
|
|
849
|
-
if (stdinDataHandler) {
|
|
850
|
-
process.stdin.removeListener('data', stdinDataHandler);
|
|
851
|
-
}
|
|
852
|
-
process.stdin.setRawMode(false);
|
|
853
|
-
process.stdin.pause();
|
|
854
|
-
process.stdin.unref();
|
|
855
|
-
} catch {
|
|
856
|
-
// Ignore errors during exit cleanup
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
// Kill gravity client with SIGKILL for immediate termination
|
|
861
|
-
if (gravityProcess && gravityProcess.exitCode === null) {
|
|
692
|
+
if (gravityProcess?.exitCode === null) {
|
|
862
693
|
try {
|
|
863
694
|
gravityProcess.kill('SIGKILL');
|
|
864
695
|
} catch {
|
|
865
|
-
// Ignore
|
|
696
|
+
// Ignore
|
|
866
697
|
}
|
|
867
698
|
}
|
|
868
|
-
|
|
869
|
-
// Close Vite server synchronously if possible
|
|
870
699
|
if (viteServer) {
|
|
871
700
|
try {
|
|
872
701
|
viteServer.close();
|
|
873
702
|
} catch {
|
|
874
|
-
// Ignore
|
|
703
|
+
// Ignore
|
|
875
704
|
}
|
|
876
705
|
}
|
|
877
|
-
|
|
878
|
-
// Stop Bun server synchronously (best effort)
|
|
879
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
880
|
-
const server = (globalThis as any).__AGENTUITY_SERVER__;
|
|
881
|
-
if (server?.stop) {
|
|
882
|
-
try {
|
|
883
|
-
server.stop(true);
|
|
884
|
-
} catch {
|
|
885
|
-
// Ignore errors during exit cleanup
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
// Release the dev lockfile synchronously
|
|
706
|
+
killBunSubprocess(logger);
|
|
890
707
|
releaseLockSync(rootDir);
|
|
891
708
|
});
|
|
892
709
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
if (!opts.noTypecheck) {
|
|
912
|
-
const typeResult = await typecheck(rootDir);
|
|
913
|
-
if (!typeResult.success) {
|
|
914
|
-
typeCheckErrors = typeResult.output;
|
|
915
|
-
return;
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
// Step 1: Generate workbench files if enabled (must be done before entry generation)
|
|
920
|
-
if (workbenchConfigData.enabled) {
|
|
921
|
-
logger.debug('Workbench enabled, generating source files before bundle...');
|
|
922
|
-
const { generateWorkbenchFiles } = await import(
|
|
923
|
-
'../build/vite/workbench-generator'
|
|
924
|
-
);
|
|
925
|
-
await generateWorkbenchFiles(
|
|
926
|
-
rootDir,
|
|
927
|
-
project?.projectId ?? '',
|
|
928
|
-
workbenchConfigData,
|
|
929
|
-
logger
|
|
930
|
-
);
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
// Step 2: Discover agents and routes in parallel
|
|
934
|
-
const srcDir = join(rootDir, 'src');
|
|
935
|
-
const { discoverAgents } = await import('../build/vite/agent-discovery');
|
|
936
|
-
const { discoverRoutes } = await import('../build/vite/route-discovery');
|
|
937
|
-
const { generateAgentRegistry, generateRouteRegistry } = await import(
|
|
938
|
-
'../build/vite/registry-generator'
|
|
939
|
-
);
|
|
710
|
+
// ================================================================
|
|
711
|
+
// Step 1: Prepare dev server (once)
|
|
712
|
+
// ================================================================
|
|
713
|
+
|
|
714
|
+
await tui.spinner({
|
|
715
|
+
message: 'Preparing dev server',
|
|
716
|
+
callback: async () => {
|
|
717
|
+
// Typecheck (skip with --no-typecheck)
|
|
718
|
+
if (!opts.noTypecheck) {
|
|
719
|
+
const typeResult = await typecheck(rootDir);
|
|
720
|
+
if (!typeResult.success) {
|
|
721
|
+
// Non-fatal in dev: log errors and continue
|
|
722
|
+
console.log('');
|
|
723
|
+
console.log(typeResult.output);
|
|
724
|
+
console.log('');
|
|
725
|
+
}
|
|
726
|
+
}
|
|
940
727
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
).toString(36);
|
|
954
|
-
|
|
955
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
956
|
-
const prevFingerprint = (globalThis as any)
|
|
957
|
-
.__AGENTUITY_DISCOVERY_FINGERPRINT__ as string | undefined;
|
|
958
|
-
const discoveryChanged = discoveryFingerprint !== prevFingerprint;
|
|
959
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
960
|
-
(globalThis as any).__AGENTUITY_DISCOVERY_FINGERPRINT__ = discoveryFingerprint;
|
|
961
|
-
|
|
962
|
-
if (discoveryChanged) {
|
|
963
|
-
// Generate agent and route registries for type augmentation
|
|
964
|
-
// (TypeScript needs these files to exist for proper type inference)
|
|
965
|
-
generateAgentRegistry(srcDir, agentMetadata);
|
|
966
|
-
generateRouteRegistry(srcDir, routeInfoList);
|
|
967
|
-
logger.debug('Agent and route registries generated for dev mode');
|
|
968
|
-
|
|
969
|
-
// Step 3: Generate entry file with workbench and analytics config
|
|
970
|
-
// Pass pre-discovered routes to avoid redundant route discovery
|
|
971
|
-
const { generateEntryFile } = await import('../build/entry-generator');
|
|
972
|
-
await generateEntryFile({
|
|
973
|
-
rootDir,
|
|
974
|
-
projectId: project?.projectId ?? '',
|
|
975
|
-
deploymentId,
|
|
976
|
-
logger,
|
|
977
|
-
mode: 'dev',
|
|
978
|
-
workbench: workbenchConfigData.enabled ? workbenchConfigData : undefined,
|
|
979
|
-
analytics: agentuityConfig?.analytics,
|
|
980
|
-
noBundle: opts.experimentalNoBundle,
|
|
981
|
-
preDiscoveredRoutes: routeInfoList,
|
|
982
|
-
});
|
|
983
|
-
} else {
|
|
984
|
-
logger.debug(
|
|
985
|
-
'Discovery unchanged (fingerprint: %s), skipping codegen',
|
|
986
|
-
discoveryFingerprint
|
|
987
|
-
);
|
|
988
|
-
}
|
|
728
|
+
// Generate workbench files if enabled
|
|
729
|
+
if (workbenchConfigData.enabled) {
|
|
730
|
+
const { generateWorkbenchFiles } = await import(
|
|
731
|
+
'../build/vite/workbench-generator'
|
|
732
|
+
);
|
|
733
|
+
await generateWorkbenchFiles(
|
|
734
|
+
rootDir,
|
|
735
|
+
project?.projectId ?? '',
|
|
736
|
+
workbenchConfigData,
|
|
737
|
+
logger
|
|
738
|
+
);
|
|
739
|
+
}
|
|
989
740
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
const { installExternalsAndBuild } = await import(
|
|
995
|
-
'../build/vite/server-bundler'
|
|
996
|
-
);
|
|
997
|
-
await installExternalsAndBuild({
|
|
998
|
-
rootDir,
|
|
999
|
-
dev: true,
|
|
1000
|
-
logger,
|
|
1001
|
-
});
|
|
1002
|
-
} else {
|
|
1003
|
-
logger.debug('Skipping Bun.build (--experimental-no-bundle mode)');
|
|
1004
|
-
}
|
|
741
|
+
// Discover agents and routes in parallel
|
|
742
|
+
const srcDir = join(rootDir, 'src');
|
|
743
|
+
const { discoverAgents } = await import('../build/vite/agent-discovery');
|
|
744
|
+
const { discoverRoutes } = await import('../build/vite/route-discovery');
|
|
1005
745
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
);
|
|
746
|
+
const [agentMetadata, { routes }] = await Promise.all([
|
|
747
|
+
discoverAgents(srcDir, project?.projectId ?? '', deploymentId, logger),
|
|
748
|
+
discoverRoutes(srcDir, project?.projectId ?? '', deploymentId, logger),
|
|
749
|
+
]);
|
|
1011
750
|
|
|
1012
|
-
|
|
751
|
+
// Generate metadata file
|
|
752
|
+
const { generateMetadata, writeMetadataFile } = await import(
|
|
753
|
+
'../build/vite/metadata-generator'
|
|
754
|
+
);
|
|
1013
755
|
|
|
1014
|
-
|
|
1015
|
-
promises.push(
|
|
1016
|
-
import('../build/vite/prompt-generator')
|
|
1017
|
-
.then(({ generatePromptFiles }) => generatePromptFiles(srcDir, logger))
|
|
1018
|
-
.catch((err) =>
|
|
1019
|
-
logger.warn('Failed to generate prompt files: %s', err.message)
|
|
1020
|
-
)
|
|
1021
|
-
);
|
|
756
|
+
const promises: Promise<void>[] = [];
|
|
1022
757
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
logger,
|
|
1032
|
-
});
|
|
758
|
+
// Generate prompt files (non-blocking)
|
|
759
|
+
promises.push(
|
|
760
|
+
import('../build/vite/prompt-generator')
|
|
761
|
+
.then(({ generatePromptFiles }) => generatePromptFiles(srcDir, logger))
|
|
762
|
+
.catch((err) =>
|
|
763
|
+
logger.warn('Failed to generate prompt files: %s', err.message)
|
|
764
|
+
)
|
|
765
|
+
);
|
|
1033
766
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
deploymentId
|
|
1044
|
-
)
|
|
1045
|
-
);
|
|
1046
|
-
previousMetadata = metadata;
|
|
1047
|
-
}
|
|
1048
|
-
await Promise.all(promises);
|
|
1049
|
-
},
|
|
1050
|
-
clearOnSuccess: true,
|
|
767
|
+
const metadata = await generateMetadata({
|
|
768
|
+
rootDir,
|
|
769
|
+
projectId: project?.projectId ?? '',
|
|
770
|
+
orgId: project?.orgId ?? '',
|
|
771
|
+
deploymentId,
|
|
772
|
+
agents: agentMetadata,
|
|
773
|
+
routes,
|
|
774
|
+
dev: true,
|
|
775
|
+
logger,
|
|
1051
776
|
});
|
|
1052
777
|
|
|
1053
|
-
|
|
1054
|
-
console.log('');
|
|
1055
|
-
console.log(typeCheckErrors);
|
|
1056
|
-
console.log('');
|
|
1057
|
-
fileWatcher.resume();
|
|
1058
|
-
// wait for a file change or shutdown to trigger a recompile
|
|
1059
|
-
while (!shutdownRequested && !shouldRestart) {
|
|
1060
|
-
await tui.spinner({
|
|
1061
|
-
message: 'Waiting for changes...',
|
|
1062
|
-
clearOnSuccess: true,
|
|
1063
|
-
callback: async () => {
|
|
1064
|
-
// Check more frequently so CTRL+C is responsive
|
|
1065
|
-
for (let i = 0; i < 10; i++) {
|
|
1066
|
-
if (shutdownRequested || shouldRestart) {
|
|
1067
|
-
return;
|
|
1068
|
-
}
|
|
1069
|
-
await Bun.sleep(100);
|
|
1070
|
-
}
|
|
1071
|
-
},
|
|
1072
|
-
});
|
|
1073
|
-
}
|
|
1074
|
-
if (shutdownRequested) {
|
|
1075
|
-
return;
|
|
1076
|
-
}
|
|
1077
|
-
// Re-enter the main loop to re-typecheck and rebuild
|
|
1078
|
-
// Without this, the code falls through and tries to start the server
|
|
1079
|
-
// with the old/stale bundle instead of rebuilding first
|
|
1080
|
-
continue;
|
|
1081
|
-
}
|
|
1082
|
-
} catch (error) {
|
|
1083
|
-
tui.error(`Failed to build dev bundle: ${error}`);
|
|
1084
|
-
tui.warning('Waiting for file changes to retry...');
|
|
1085
|
-
|
|
1086
|
-
// Resume watcher to detect changes for retry
|
|
1087
|
-
fileWatcher.resume();
|
|
1088
|
-
|
|
1089
|
-
// Wait for next restart trigger or shutdown
|
|
1090
|
-
await new Promise<void>((resolve) => {
|
|
1091
|
-
const checkRestart = setInterval(() => {
|
|
1092
|
-
if (shouldRestart || shutdownRequested) {
|
|
1093
|
-
clearInterval(checkRestart);
|
|
1094
|
-
resolve();
|
|
1095
|
-
}
|
|
1096
|
-
}, 100);
|
|
1097
|
-
});
|
|
1098
|
-
if (shutdownRequested) {
|
|
1099
|
-
break;
|
|
1100
|
-
}
|
|
1101
|
-
continue;
|
|
1102
|
-
}
|
|
778
|
+
writeMetadataFile(rootDir, metadata, true, logger);
|
|
1103
779
|
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
process.env.AGENTUITY_SDK_KEY = sdkKey;
|
|
1111
|
-
} else if (project) {
|
|
1112
|
-
tui.warning(
|
|
1113
|
-
'AGENTUITY_SDK_KEY not found in .env file. Numerous features will be unavailable.'
|
|
1114
|
-
);
|
|
1115
|
-
tui.bullet(
|
|
1116
|
-
`Run "${getCommand('cloud env pull')}" to sync your SDK key, or add AGENTUITY_SDK_KEY to your .env file.`
|
|
1117
|
-
);
|
|
1118
|
-
}
|
|
780
|
+
// Sync metadata with backend
|
|
781
|
+
if (syncService && project?.projectId) {
|
|
782
|
+
promises.push(
|
|
783
|
+
syncService.sync(metadata, previousMetadata, project.projectId, deploymentId)
|
|
784
|
+
);
|
|
785
|
+
previousMetadata = metadata;
|
|
1119
786
|
}
|
|
787
|
+
await Promise.all(promises);
|
|
788
|
+
},
|
|
789
|
+
clearOnSuccess: true,
|
|
790
|
+
});
|
|
1120
791
|
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
792
|
+
// ================================================================
|
|
793
|
+
// Step 2: Set environment variables
|
|
794
|
+
// ================================================================
|
|
795
|
+
|
|
796
|
+
if (!process.env.AGENTUITY_SDK_KEY) {
|
|
797
|
+
const sdkKey = await loadProjectSDKKey(logger, rootDir);
|
|
798
|
+
if (sdkKey) {
|
|
799
|
+
process.env.AGENTUITY_SDK_KEY = sdkKey;
|
|
800
|
+
} else if (project) {
|
|
801
|
+
tui.warning(
|
|
802
|
+
'AGENTUITY_SDK_KEY not found in .env file. Numerous features will be unavailable.'
|
|
803
|
+
);
|
|
804
|
+
tui.bullet(
|
|
805
|
+
`Run "${getCommand('cloud env pull')}" to sync your SDK key, or add AGENTUITY_SDK_KEY to your .env file.`
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
1133
809
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
810
|
+
process.env.AGENTUITY_SDK_DEV_MODE = 'true';
|
|
811
|
+
process.env.AGENTUITY_RUNTIME = 'yes';
|
|
812
|
+
process.env.AGENTUITY_ENV = 'development';
|
|
813
|
+
process.env.NODE_ENV = 'development';
|
|
814
|
+
process.env.AGENTUITY_PROJECT_DIR = rootDir;
|
|
815
|
+
if (project?.region) {
|
|
816
|
+
process.env.AGENTUITY_REGION = project.region;
|
|
817
|
+
}
|
|
818
|
+
process.env.PORT = String(bunBackendPort);
|
|
819
|
+
process.env.AGENTUITY_PORT = String(bunBackendPort);
|
|
820
|
+
process.env.AGENTUITY_BASE_URL =
|
|
821
|
+
process.env.AGENTUITY_BASE_URL || `http://localhost:${vitePort}`;
|
|
822
|
+
process.env.AGENTUITY_NO_BUNDLE = 'true';
|
|
823
|
+
|
|
824
|
+
if (opts.resume) {
|
|
825
|
+
process.env.AGENTUITY_CODER_RESUME_SESSION = opts.resume;
|
|
826
|
+
}
|
|
1137
827
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
process.env.AGENTUITY_CLOUD_DEPLOYMENT_ID = deploymentId;
|
|
1151
|
-
}
|
|
828
|
+
if (project) {
|
|
829
|
+
const serviceUrls = getServiceUrls(project.region);
|
|
830
|
+
process.env.AGENTUITY_TRANSPORT_URL = serviceUrls.catalyst;
|
|
831
|
+
process.env.AGENTUITY_CATALYST_URL = serviceUrls.catalyst;
|
|
832
|
+
process.env.AGENTUITY_VECTOR_URL = serviceUrls.vector;
|
|
833
|
+
process.env.AGENTUITY_KEYVALUE_URL = serviceUrls.keyvalue;
|
|
834
|
+
process.env.AGENTUITY_SANDBOX_URL = serviceUrls.sandbox;
|
|
835
|
+
process.env.AGENTUITY_STREAM_URL = serviceUrls.stream;
|
|
836
|
+
process.env.AGENTUITY_CLOUD_ORG_ID = project.orgId;
|
|
837
|
+
process.env.AGENTUITY_CLOUD_PROJECT_ID = project.projectId;
|
|
838
|
+
process.env.AGENTUITY_CLOUD_DEPLOYMENT_ID = deploymentId;
|
|
839
|
+
}
|
|
1152
840
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
841
|
+
if (devmode?.hostname) {
|
|
842
|
+
process.env.AGENTUITY_DEVMODE_URL = `https://${devmode.hostname}`;
|
|
843
|
+
} else {
|
|
844
|
+
process.env.AGENTUITY_DEVMODE_URL = `http://localhost:${vitePort}`;
|
|
845
|
+
}
|
|
1158
846
|
|
|
1159
|
-
|
|
1160
|
-
|
|
847
|
+
// ================================================================
|
|
848
|
+
// Step 3: Start Bun backend with --hot (handles its own HMR)
|
|
849
|
+
// ================================================================
|
|
1161
850
|
|
|
1162
|
-
|
|
851
|
+
await startBunDevServer({
|
|
852
|
+
rootDir,
|
|
853
|
+
port: bunBackendPort,
|
|
854
|
+
logger,
|
|
855
|
+
vitePort,
|
|
856
|
+
inspect: opts.inspect,
|
|
857
|
+
inspectWait: opts.inspectWait,
|
|
858
|
+
inspectBrk: opts.inspectBrk,
|
|
859
|
+
});
|
|
1163
860
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
port: opts.port,
|
|
1168
|
-
projectId: project?.projectId,
|
|
1169
|
-
orgId: project?.orgId,
|
|
1170
|
-
deploymentId,
|
|
1171
|
-
logger,
|
|
1172
|
-
vitePort, // Pass port of already-running Vite server
|
|
1173
|
-
inspect: opts.inspect,
|
|
1174
|
-
inspectWait: opts.inspectWait,
|
|
1175
|
-
inspectBrk: opts.inspectBrk,
|
|
1176
|
-
noBundle: opts.experimentalNoBundle,
|
|
1177
|
-
});
|
|
861
|
+
// ================================================================
|
|
862
|
+
// Step 4: Start gravity tunnel (if public URL enabled)
|
|
863
|
+
// ================================================================
|
|
1178
864
|
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
tui.warning('Waiting for file changes to retry...');
|
|
1186
|
-
|
|
1187
|
-
// Clean up any partially started server resources
|
|
1188
|
-
await cleanupForRestart();
|
|
1189
|
-
|
|
1190
|
-
// Resume watcher to detect changes for retry
|
|
1191
|
-
fileWatcher.resume();
|
|
1192
|
-
|
|
1193
|
-
// Wait for next restart trigger or shutdown
|
|
1194
|
-
await new Promise<void>((resolve) => {
|
|
1195
|
-
const checkRestart = setInterval(() => {
|
|
1196
|
-
if (shouldRestart || shutdownRequested) {
|
|
1197
|
-
clearInterval(checkRestart);
|
|
1198
|
-
resolve();
|
|
1199
|
-
}
|
|
1200
|
-
}, 100);
|
|
1201
|
-
});
|
|
1202
|
-
if (shutdownRequested) {
|
|
1203
|
-
break;
|
|
1204
|
-
}
|
|
1205
|
-
continue;
|
|
865
|
+
if (gravityBin && gravityURL && devmode && project) {
|
|
866
|
+
const privateKeyPEM = devmode.privateKey ?? savedPrivateKey;
|
|
867
|
+
if (!privateKeyPEM) {
|
|
868
|
+
throw new Error(
|
|
869
|
+
'No private key available for gravity connection. Please re-run to generate a new key.'
|
|
870
|
+
);
|
|
1206
871
|
}
|
|
872
|
+
gravityProcess = Bun.spawn(
|
|
873
|
+
[
|
|
874
|
+
gravityBin,
|
|
875
|
+
'--endpoint-id',
|
|
876
|
+
devmode.id,
|
|
877
|
+
'--port',
|
|
878
|
+
vitePort.toString(),
|
|
879
|
+
'--url',
|
|
880
|
+
gravityURL,
|
|
881
|
+
'--log-level',
|
|
882
|
+
process.env.AGENTUITY_GRAVITY_LOG_LEVEL ?? 'error',
|
|
883
|
+
'--org-id',
|
|
884
|
+
project.orgId,
|
|
885
|
+
'--project-id',
|
|
886
|
+
project.projectId,
|
|
887
|
+
'--private-key',
|
|
888
|
+
Buffer.from(privateKeyPEM).toString('base64'),
|
|
889
|
+
'--health-check',
|
|
890
|
+
],
|
|
891
|
+
{
|
|
892
|
+
cwd: rootDir,
|
|
893
|
+
stdout: 'pipe',
|
|
894
|
+
stderr: 'pipe',
|
|
895
|
+
detached: false,
|
|
896
|
+
}
|
|
897
|
+
);
|
|
1207
898
|
|
|
1208
|
-
|
|
1209
|
-
if (
|
|
1210
|
-
|
|
899
|
+
const gravityPid = (gravityProcess as { pid?: number }).pid;
|
|
900
|
+
if (gravityPid) {
|
|
901
|
+
await devLock.registerChild({
|
|
902
|
+
pid: gravityPid,
|
|
903
|
+
type: 'gravity',
|
|
904
|
+
description: 'Gravity public URL tunnel',
|
|
905
|
+
});
|
|
1211
906
|
}
|
|
1212
907
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
'--url',
|
|
1236
|
-
gravityURL,
|
|
1237
|
-
'--log-level',
|
|
1238
|
-
process.env.AGENTUITY_GRAVITY_LOG_LEVEL ?? 'error',
|
|
1239
|
-
'--org-id',
|
|
1240
|
-
project.orgId,
|
|
1241
|
-
'--project-id',
|
|
1242
|
-
project.projectId,
|
|
1243
|
-
'--private-key',
|
|
1244
|
-
Buffer.from(privateKeyPEM).toString('base64'),
|
|
1245
|
-
'--health-check',
|
|
1246
|
-
],
|
|
1247
|
-
{
|
|
1248
|
-
cwd: rootDir,
|
|
1249
|
-
stdout: 'pipe',
|
|
1250
|
-
stderr: 'pipe',
|
|
1251
|
-
detached: false, // Ensure gravity dies with parent process
|
|
1252
|
-
}
|
|
1253
|
-
);
|
|
1254
|
-
|
|
1255
|
-
// Register gravity process in dev lock for cleanup tracking
|
|
1256
|
-
const gravityPid = (gravityProcess as { pid?: number }).pid;
|
|
1257
|
-
if (gravityPid) {
|
|
1258
|
-
await devLock.registerChild({
|
|
1259
|
-
pid: gravityPid,
|
|
1260
|
-
type: 'gravity',
|
|
1261
|
-
description: 'Gravity public URL tunnel',
|
|
1262
|
-
});
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
// Log gravity output and detect heartbeat port
|
|
1266
|
-
(async () => {
|
|
1267
|
-
try {
|
|
1268
|
-
if (gravityProcess?.stdout) {
|
|
1269
|
-
for await (const chunk of gravityProcess.stdout) {
|
|
1270
|
-
const text = new TextDecoder().decode(chunk);
|
|
1271
|
-
const trimmed = text.trim();
|
|
1272
|
-
|
|
1273
|
-
// Check for heartbeat port announcement
|
|
1274
|
-
const match = trimmed.match(/^HEARTBEAT_PORT=(\d+)$/m);
|
|
1275
|
-
if (match?.[1]) {
|
|
1276
|
-
const heartbeatPort = parseInt(match[1], 10);
|
|
1277
|
-
logger.debug('Gravity heartbeat port detected: %d', heartbeatPort);
|
|
1278
|
-
|
|
1279
|
-
// Start sending heartbeats every 5 seconds
|
|
1280
|
-
if (!gravityHeartbeatInterval) {
|
|
1281
|
-
const sendHeartbeat = async () => {
|
|
1282
|
-
try {
|
|
1283
|
-
await fetch(
|
|
1284
|
-
`http://127.0.0.1:${heartbeatPort}/heartbeat`,
|
|
1285
|
-
{
|
|
1286
|
-
method: 'POST',
|
|
1287
|
-
signal: AbortSignal.timeout(2000),
|
|
1288
|
-
}
|
|
1289
|
-
);
|
|
1290
|
-
logger.trace('Gravity heartbeat sent');
|
|
1291
|
-
} catch (err) {
|
|
1292
|
-
logger.trace('Gravity heartbeat failed: %s', err);
|
|
1293
|
-
}
|
|
1294
|
-
};
|
|
1295
|
-
|
|
1296
|
-
// Send initial heartbeat immediately
|
|
1297
|
-
sendHeartbeat();
|
|
1298
|
-
|
|
1299
|
-
// Then send every 5 seconds
|
|
1300
|
-
gravityHeartbeatInterval = setInterval(sendHeartbeat, 5000);
|
|
908
|
+
// Log gravity output and detect heartbeat port
|
|
909
|
+
(async () => {
|
|
910
|
+
try {
|
|
911
|
+
if (gravityProcess?.stdout) {
|
|
912
|
+
for await (const chunk of gravityProcess.stdout) {
|
|
913
|
+
const text = new TextDecoder().decode(chunk);
|
|
914
|
+
const trimmed = text.trim();
|
|
915
|
+
|
|
916
|
+
const match = trimmed.match(/^HEARTBEAT_PORT=(\d+)$/m);
|
|
917
|
+
if (match?.[1]) {
|
|
918
|
+
const heartbeatPort = parseInt(match[1], 10);
|
|
919
|
+
logger.debug('Gravity heartbeat port: %d', heartbeatPort);
|
|
920
|
+
|
|
921
|
+
if (!gravityHeartbeatInterval) {
|
|
922
|
+
const sendHeartbeat = async () => {
|
|
923
|
+
try {
|
|
924
|
+
await fetch(`http://127.0.0.1:${heartbeatPort}/heartbeat`, {
|
|
925
|
+
method: 'POST',
|
|
926
|
+
signal: AbortSignal.timeout(2000),
|
|
927
|
+
});
|
|
928
|
+
} catch {
|
|
929
|
+
// Ignore heartbeat failures
|
|
1301
930
|
}
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
|
|
931
|
+
};
|
|
932
|
+
sendHeartbeat();
|
|
933
|
+
gravityHeartbeatInterval = setInterval(sendHeartbeat, 5000);
|
|
1305
934
|
}
|
|
935
|
+
} else if (trimmed) {
|
|
936
|
+
logger.debug('[gravity] %s', trimmed);
|
|
1306
937
|
}
|
|
1307
|
-
} catch (err) {
|
|
1308
|
-
logger.error('Error reading gravity stdout: %s', err);
|
|
1309
938
|
}
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
|
-
(
|
|
1313
|
-
try {
|
|
1314
|
-
if (gravityProcess?.stderr) {
|
|
1315
|
-
for await (const chunk of gravityProcess.stderr) {
|
|
1316
|
-
const text = new TextDecoder().decode(chunk);
|
|
1317
|
-
logger.warn('[gravity] %s', text.trim());
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
} catch (err) {
|
|
1321
|
-
logger.error('Error reading gravity stderr: %s', err);
|
|
1322
|
-
}
|
|
1323
|
-
})();
|
|
1324
|
-
|
|
1325
|
-
logger.debug('Gravity client started');
|
|
939
|
+
}
|
|
940
|
+
} catch (err) {
|
|
941
|
+
logger.error('Error reading gravity stdout: %s', err);
|
|
1326
942
|
}
|
|
943
|
+
})();
|
|
1327
944
|
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
!stdinListenerRegistered
|
|
1334
|
-
) {
|
|
1335
|
-
stdinListenerRegistered = true;
|
|
1336
|
-
process.stdin.setRawMode(true);
|
|
1337
|
-
process.stdin.resume();
|
|
1338
|
-
process.stdin.setEncoding('utf8');
|
|
1339
|
-
|
|
1340
|
-
const showHelp = () => {
|
|
1341
|
-
console.log('\n' + tui.bold('Keyboard Shortcuts:'));
|
|
1342
|
-
console.log(tui.muted(' h') + ' - show this help');
|
|
1343
|
-
console.log(tui.muted(' c') + ' - clear console');
|
|
1344
|
-
console.log(tui.muted(' q') + ' - quit\n');
|
|
1345
|
-
};
|
|
1346
|
-
|
|
1347
|
-
// Store handler reference for cleanup
|
|
1348
|
-
stdinDataHandler = (data) => {
|
|
1349
|
-
const key = data.toString();
|
|
1350
|
-
|
|
1351
|
-
// Handle Ctrl+C or q - trigger graceful shutdown
|
|
1352
|
-
if (key === '\u0003' || key === 'q') {
|
|
1353
|
-
// Remove stdin listener immediately to prevent re-entrancy
|
|
1354
|
-
if (stdinDataHandler) {
|
|
1355
|
-
process.stdin.removeListener('data', stdinDataHandler);
|
|
1356
|
-
stdinDataHandler = null;
|
|
1357
|
-
}
|
|
1358
|
-
// Set shutdown flag and trigger cleanup directly
|
|
1359
|
-
shutdownRequested = true;
|
|
1360
|
-
cleanup(true, 0).catch((err) => {
|
|
1361
|
-
logger.debug('Cleanup error: %s', err);
|
|
1362
|
-
originalExit(1);
|
|
1363
|
-
});
|
|
1364
|
-
return;
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
switch (key) {
|
|
1368
|
-
case 'h':
|
|
1369
|
-
showHelp();
|
|
1370
|
-
break;
|
|
1371
|
-
case 'c':
|
|
1372
|
-
console.clear();
|
|
1373
|
-
tui.banner('⨺ Agentuity DevMode', devmodebody, {
|
|
1374
|
-
padding: 2,
|
|
1375
|
-
topSpacer: false,
|
|
1376
|
-
bottomSpacer: false,
|
|
1377
|
-
centerTitle: false,
|
|
1378
|
-
});
|
|
1379
|
-
break;
|
|
1380
|
-
default:
|
|
1381
|
-
process.stdout.write(data);
|
|
1382
|
-
break;
|
|
945
|
+
(async () => {
|
|
946
|
+
try {
|
|
947
|
+
if (gravityProcess?.stderr) {
|
|
948
|
+
for await (const chunk of gravityProcess.stderr) {
|
|
949
|
+
logger.warn('[gravity] %s', new TextDecoder().decode(chunk).trim());
|
|
1383
950
|
}
|
|
1384
|
-
}
|
|
1385
|
-
|
|
951
|
+
}
|
|
952
|
+
} catch (err) {
|
|
953
|
+
logger.error('Error reading gravity stderr: %s', err);
|
|
1386
954
|
}
|
|
955
|
+
})();
|
|
956
|
+
}
|
|
1387
957
|
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
958
|
+
// ================================================================
|
|
959
|
+
// Step 5: Keyboard shortcuts + wait for shutdown
|
|
960
|
+
// ================================================================
|
|
961
|
+
|
|
962
|
+
if (interactive && process.stdin.isTTY && process.stdout.isTTY) {
|
|
963
|
+
stdinListenerRegistered = true;
|
|
964
|
+
process.stdin.setRawMode(true);
|
|
965
|
+
process.stdin.resume();
|
|
966
|
+
process.stdin.setEncoding('utf8');
|
|
967
|
+
|
|
968
|
+
const showHelp = () => {
|
|
969
|
+
console.log('\n' + tui.bold('Keyboard Shortcuts:'));
|
|
970
|
+
console.log(tui.muted(' h') + ' - show this help');
|
|
971
|
+
console.log(tui.muted(' c') + ' - clear console');
|
|
972
|
+
console.log(tui.muted(' q') + ' - quit\n');
|
|
973
|
+
};
|
|
1402
974
|
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
975
|
+
stdinDataHandler = (data) => {
|
|
976
|
+
const key = data.toString();
|
|
977
|
+
if (key === '\u0003' || key === 'q') {
|
|
978
|
+
if (stdinDataHandler) {
|
|
979
|
+
process.stdin.removeListener('data', stdinDataHandler);
|
|
980
|
+
stdinDataHandler = null;
|
|
981
|
+
}
|
|
982
|
+
shutdownRequested = true;
|
|
983
|
+
cleanup(true, 0).catch(() => originalExit(1));
|
|
984
|
+
return;
|
|
1406
985
|
}
|
|
986
|
+
switch (key) {
|
|
987
|
+
case 'h':
|
|
988
|
+
showHelp();
|
|
989
|
+
break;
|
|
990
|
+
case 'c':
|
|
991
|
+
console.clear();
|
|
992
|
+
tui.banner('⨺ Agentuity DevMode', devmodebody, {
|
|
993
|
+
padding: 2,
|
|
994
|
+
topSpacer: false,
|
|
995
|
+
bottomSpacer: false,
|
|
996
|
+
centerTitle: false,
|
|
997
|
+
});
|
|
998
|
+
break;
|
|
999
|
+
default:
|
|
1000
|
+
process.stdout.write(data);
|
|
1001
|
+
break;
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
process.stdin.on('data', stdinDataHandler);
|
|
1005
|
+
}
|
|
1407
1006
|
|
|
1408
|
-
|
|
1409
|
-
logger.debug('Restarting backend server...');
|
|
1410
|
-
|
|
1411
|
-
// Clean up Bun server and Gravity (Vite stays running)
|
|
1412
|
-
await cleanupForRestart();
|
|
1413
|
-
|
|
1414
|
-
// Brief pause before restart
|
|
1415
|
-
await Bun.sleep(500);
|
|
1416
|
-
} catch (error) {
|
|
1417
|
-
tui.error(`Error during server operation: ${error}`);
|
|
1418
|
-
tui.warning('Waiting for file changes to retry...');
|
|
1419
|
-
|
|
1420
|
-
// Cleanup on error (Vite stays running)
|
|
1421
|
-
await cleanupForRestart();
|
|
1007
|
+
logger.info('DevMode ready 🚀');
|
|
1422
1008
|
|
|
1423
|
-
|
|
1009
|
+
// Block until shutdown — bun --hot handles backend HMR,
|
|
1010
|
+
// Vite handles frontend HMR. Nothing to restart.
|
|
1011
|
+
await new Promise<void>((resolve) => {
|
|
1012
|
+
const check = setInterval(() => {
|
|
1424
1013
|
if (shutdownRequested) {
|
|
1425
|
-
|
|
1014
|
+
clearInterval(check);
|
|
1015
|
+
resolve();
|
|
1426
1016
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
fileWatcher.resume();
|
|
1430
|
-
|
|
1431
|
-
// Wait for next restart trigger or shutdown
|
|
1432
|
-
await new Promise<void>((resolve) => {
|
|
1433
|
-
const checkRestart = setInterval(() => {
|
|
1434
|
-
if (shouldRestart || shutdownRequested) {
|
|
1435
|
-
clearInterval(checkRestart);
|
|
1436
|
-
resolve();
|
|
1437
|
-
}
|
|
1438
|
-
}, 100);
|
|
1439
|
-
});
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1017
|
+
}, 200);
|
|
1018
|
+
});
|
|
1442
1019
|
} finally {
|
|
1443
1020
|
/* brute force clean up */
|
|
1444
1021
|
await devLock.release();
|