@agentuity/cli 1.0.59 → 2.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.ts +2 -3
- package/dist/cmd/build/app-config-extractor.d.ts +27 -0
- package/dist/cmd/build/app-config-extractor.d.ts.map +1 -0
- package/dist/cmd/build/app-config-extractor.js +152 -0
- package/dist/cmd/build/app-config-extractor.js.map +1 -0
- 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/ci.d.ts.map +1 -1
- package/dist/cmd/build/ci.js +5 -21
- package/dist/cmd/build/ci.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/index.d.ts.map +1 -1
- package/dist/cmd/build/index.js +8 -0
- package/dist/cmd/build/index.js.map +1 -1
- 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 +43 -14
- package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
- package/dist/cmd/build/vite/bun-dev-server.js +290 -129
- package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
- package/dist/cmd/build/vite/config-loader.d.ts +15 -20
- package/dist/cmd/build/vite/config-loader.d.ts.map +1 -1
- package/dist/cmd/build/vite/config-loader.js +41 -74
- package/dist/cmd/build/vite/config-loader.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 +0 -2
- package/dist/cmd/build/vite/static-renderer.d.ts.map +1 -1
- package/dist/cmd/build/vite/static-renderer.js +19 -13
- 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 +175 -69
- 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 +42 -190
- 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-fork.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy-fork.js +15 -36
- package/dist/cmd/cloud/deploy-fork.js.map +1 -1
- package/dist/cmd/cloud/sandbox/exec.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/exec.js +28 -86
- package/dist/cmd/cloud/sandbox/exec.js.map +1 -1
- package/dist/cmd/cloud/sandbox/run.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/run.js +2 -9
- package/dist/cmd/cloud/sandbox/run.js.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/build.js +2 -2
- package/dist/cmd/cloud/sandbox/snapshot/build.js.map +1 -1
- package/dist/cmd/coder/hub-url.d.ts.map +1 -1
- package/dist/cmd/coder/hub-url.js +1 -3
- package/dist/cmd/coder/hub-url.js.map +1 -1
- package/dist/cmd/coder/start.js +6 -6
- package/dist/cmd/coder/start.js.map +1 -1
- package/dist/cmd/coder/tui-init.d.ts +2 -2
- package/dist/cmd/coder/tui-init.js +2 -2
- package/dist/cmd/coder/tui-init.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 +432 -752
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/dev/process-manager.d.ts +104 -0
- package/dist/cmd/dev/process-manager.d.ts.map +1 -0
- package/dist/cmd/dev/process-manager.js +204 -0
- package/dist/cmd/dev/process-manager.js.map +1 -0
- package/dist/errors.d.ts +10 -24
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +12 -42
- package/dist/errors.js.map +1 -1
- package/dist/schema-generator.d.ts.map +1 -1
- package/dist/schema-generator.js +12 -2
- package/dist/schema-generator.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +5 -19
- package/dist/tui.js.map +1 -1
- package/dist/utils/version-mismatch.d.ts +39 -0
- package/dist/utils/version-mismatch.d.ts.map +1 -0
- package/dist/utils/version-mismatch.js +161 -0
- package/dist/utils/version-mismatch.js.map +1 -0
- package/package.json +6 -6
- 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-config-extractor.ts +186 -0
- package/src/cmd/build/app-router-detector.ts +152 -182
- package/src/cmd/build/ci.ts +5 -21
- package/src/cmd/build/ids.ts +19 -0
- package/src/cmd/build/index.ts +10 -0
- package/src/cmd/build/vite/agent-discovery.ts +208 -679
- package/src/cmd/build/vite/bun-dev-server.ts +383 -146
- package/src/cmd/build/vite/config-loader.ts +45 -77
- 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 +23 -15
- package/src/cmd/build/vite/vite-asset-server-config.ts +200 -70
- package/src/cmd/build/vite/vite-asset-server.ts +25 -15
- package/src/cmd/build/vite/vite-builder.ts +49 -220
- package/src/cmd/build/vite/ws-proxy.ts +126 -0
- package/src/cmd/build/vite-bundler.ts +0 -4
- package/src/cmd/cloud/deploy-fork.ts +16 -39
- package/src/cmd/cloud/sandbox/exec.ts +23 -130
- package/src/cmd/cloud/sandbox/run.ts +2 -9
- package/src/cmd/cloud/sandbox/snapshot/build.ts +2 -2
- package/src/cmd/coder/hub-url.ts +1 -3
- package/src/cmd/coder/start.ts +6 -6
- package/src/cmd/coder/tui-init.ts +4 -4
- package/src/cmd/dev/file-watcher.ts +2 -9
- package/src/cmd/dev/index.ts +476 -859
- package/src/cmd/dev/process-manager.ts +261 -0
- package/src/errors.ts +12 -44
- package/src/schema-generator.ts +12 -2
- package/src/tui.ts +5 -18
- package/src/utils/version-mismatch.ts +204 -0
- 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/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/dist/utils/stream-capture.d.ts +0 -9
- package/dist/utils/stream-capture.d.ts.map +0 -1
- package/dist/utils/stream-capture.js +0 -34
- package/dist/utils/stream-capture.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/webanalytics-generator.ts +0 -197
- package/src/cmd/build/workbench.ts +0 -58
- package/src/utils/route-migration.ts +0 -757
- package/src/utils/stream-capture.ts +0 -39
package/dist/cmd/dev/index.js
CHANGED
|
@@ -11,15 +11,15 @@ import { generateEndpoint } 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 { typecheck } from '../build/typecheck';
|
|
17
17
|
import { validateGravityRequiresUpgrade } from '../../runtime';
|
|
18
18
|
import { isTTY, hasLoggedInBefore } from '../../auth';
|
|
19
|
-
import { createFileWatcher } from './file-watcher';
|
|
20
19
|
import { prepareDevLock, releaseLockSync } from './dev-lock';
|
|
21
20
|
import { checkAndUpgradeDependencies } from '../../utils/dependency-checker';
|
|
22
|
-
import {
|
|
21
|
+
import { initProcessManager } from './process-manager';
|
|
22
|
+
import { detectVersionMismatch, formatVersionMismatchWarning } from '../../utils/version-mismatch';
|
|
23
23
|
import { ErrorCode } from '../../errors';
|
|
24
24
|
const DEFAULT_PORT = 3500;
|
|
25
25
|
const MIN_PORT = 1024;
|
|
@@ -56,85 +56,22 @@ async function killLingeringGravityProcesses(logger) {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
|
-
*
|
|
60
|
-
* Waits for the port to become available before returning (with timeout).
|
|
61
|
-
* Handles both in-process server and subprocess (when debugger is enabled).
|
|
59
|
+
* Kill the Bun backend subprocess if one is running.
|
|
62
60
|
*/
|
|
63
|
-
|
|
61
|
+
function killBunSubprocess(logger) {
|
|
64
62
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
65
63
|
const globalAny = globalThis;
|
|
66
|
-
// Check for subprocess first (used when debugger flags are enabled)
|
|
67
64
|
const bunSubprocess = globalAny.__AGENTUITY_BUN_SUBPROCESS__;
|
|
68
|
-
if (bunSubprocess)
|
|
69
|
-
logger.debug('Stopping Bun subprocess...');
|
|
70
|
-
try {
|
|
71
|
-
bunSubprocess.kill('SIGTERM');
|
|
72
|
-
// After SIGTERM, wait and check multiple times before giving up
|
|
73
|
-
let attempts = 0;
|
|
74
|
-
while (bunSubprocess.exitCode === null && attempts < 3) {
|
|
75
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
76
|
-
attempts++;
|
|
77
|
-
}
|
|
78
|
-
if (bunSubprocess.exitCode === null) {
|
|
79
|
-
bunSubprocess.kill('SIGKILL');
|
|
80
|
-
}
|
|
81
|
-
logger.debug('Bun subprocess killed');
|
|
82
|
-
}
|
|
83
|
-
catch (err) {
|
|
84
|
-
logger.debug('Error killing Bun subprocess: %s', err);
|
|
85
|
-
}
|
|
86
|
-
globalAny.__AGENTUITY_BUN_SUBPROCESS__ = undefined;
|
|
87
|
-
// Wait for port to become available
|
|
88
|
-
const MAX_WAIT_ITERATIONS = 10;
|
|
89
|
-
for (let i = 0; i < MAX_WAIT_ITERATIONS; i++) {
|
|
90
|
-
try {
|
|
91
|
-
await fetch(`http://127.0.0.1:${port}/`, {
|
|
92
|
-
method: 'HEAD',
|
|
93
|
-
signal: AbortSignal.timeout(150),
|
|
94
|
-
});
|
|
95
|
-
// Still responding, wait a bit more
|
|
96
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
// Connection refused or timeout => server is down
|
|
100
|
-
logger.debug('Bun subprocess stopped');
|
|
101
|
-
break;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
// Handle in-process server
|
|
107
|
-
const server = globalAny.__AGENTUITY_SERVER__;
|
|
108
|
-
if (!server) {
|
|
109
|
-
logger.debug('No Bun server to stop');
|
|
65
|
+
if (!bunSubprocess)
|
|
110
66
|
return;
|
|
111
|
-
}
|
|
112
67
|
try {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
logger.debug('Bun server stop() called');
|
|
68
|
+
bunSubprocess.kill('SIGTERM');
|
|
69
|
+
logger.debug('Bun subprocess killed');
|
|
116
70
|
}
|
|
117
71
|
catch (err) {
|
|
118
|
-
logger.debug('Error
|
|
119
|
-
}
|
|
120
|
-
// Wait for socket to close (max 2 seconds to avoid hanging on shutdown)
|
|
121
|
-
const MAX_WAIT_ITERATIONS = 10;
|
|
122
|
-
for (let i = 0; i < MAX_WAIT_ITERATIONS; i++) {
|
|
123
|
-
try {
|
|
124
|
-
await fetch(`http://127.0.0.1:${port}/`, {
|
|
125
|
-
method: 'HEAD',
|
|
126
|
-
signal: AbortSignal.timeout(150),
|
|
127
|
-
});
|
|
128
|
-
// Still responding, wait a bit more
|
|
129
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
130
|
-
}
|
|
131
|
-
catch {
|
|
132
|
-
// Connection refused or timeout => server is down
|
|
133
|
-
logger.debug('Bun server stopped');
|
|
134
|
-
break;
|
|
135
|
-
}
|
|
72
|
+
logger.debug('Error killing Bun subprocess: %s', err);
|
|
136
73
|
}
|
|
137
|
-
globalAny.
|
|
74
|
+
globalAny.__AGENTUITY_BUN_SUBPROCESS__ = undefined;
|
|
138
75
|
}
|
|
139
76
|
const getDefaultPort = () => {
|
|
140
77
|
const envPort = process.env.PORT;
|
|
@@ -192,18 +129,10 @@ export const command = createCommand({
|
|
|
192
129
|
.boolean()
|
|
193
130
|
.optional()
|
|
194
131
|
.describe('Enable bun debugger with breakpoint at first line'),
|
|
195
|
-
experimentalNoBundle: z
|
|
196
|
-
.boolean()
|
|
197
|
-
.optional()
|
|
198
|
-
.describe('[Experimental] Skip Bun.build in dev mode — run generated entry file directly'),
|
|
199
132
|
noTypecheck: z
|
|
200
133
|
.boolean()
|
|
201
134
|
.optional()
|
|
202
135
|
.describe('Skip TypeScript type checking on startup and restarts'),
|
|
203
|
-
migrateRoutes: z
|
|
204
|
-
.boolean()
|
|
205
|
-
.optional()
|
|
206
|
-
.describe('Migrate file-based routes to explicit routing (src/api/index.ts root router)'),
|
|
207
136
|
resume: z.string().optional().describe('Resume a paused Hub session by ID'),
|
|
208
137
|
}),
|
|
209
138
|
},
|
|
@@ -357,30 +286,12 @@ export const command = createCommand({
|
|
|
357
286
|
devLock.release();
|
|
358
287
|
tui.fatal(`Failed to upgrade dependencies: ${upgradeResult.failed.join(', ')}`, ErrorCode.BUILD_FAILED);
|
|
359
288
|
}
|
|
360
|
-
// Check
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
tui.success(result.message);
|
|
367
|
-
if (result.filesCreated.length > 0) {
|
|
368
|
-
tui.info(`Created: ${result.filesCreated.map((f) => tui.muted(f)).join(', ')}`);
|
|
369
|
-
}
|
|
370
|
-
tui.newline();
|
|
371
|
-
}
|
|
372
|
-
else {
|
|
373
|
-
tui.warning(result.message);
|
|
374
|
-
tui.newline();
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
else {
|
|
378
|
-
tui.info('No migration needed — already using explicit routing.');
|
|
379
|
-
tui.newline();
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
await promptRouteMigration(rootDir, logger, { interactive });
|
|
289
|
+
// Check for version mismatches (v1 vs v2 SDK packages)
|
|
290
|
+
const versionMismatch = detectVersionMismatch(rootDir, logger);
|
|
291
|
+
if (versionMismatch.hasV1Packages || versionMismatch.hasMajorMismatches) {
|
|
292
|
+
tui.newline();
|
|
293
|
+
tui.warning(formatVersionMismatchWarning(versionMismatch));
|
|
294
|
+
tui.newline();
|
|
384
295
|
}
|
|
385
296
|
try {
|
|
386
297
|
// Setup devmode and gravity (if using public URL)
|
|
@@ -454,10 +365,10 @@ export const command = createCommand({
|
|
|
454
365
|
config = _config;
|
|
455
366
|
}
|
|
456
367
|
}
|
|
457
|
-
// Get workbench info from
|
|
458
|
-
const {
|
|
459
|
-
const
|
|
460
|
-
const workbenchConfigData = getWorkbenchConfig(
|
|
368
|
+
// Get workbench info from createApp() in app.ts (v2 approach)
|
|
369
|
+
const { getWorkbenchConfig, loadRuntimeConfig } = await import('../build/vite/config-loader');
|
|
370
|
+
const runtimeConfig = await loadRuntimeConfig(rootDir, logger);
|
|
371
|
+
const workbenchConfigData = getWorkbenchConfig(true, runtimeConfig); // dev mode
|
|
461
372
|
const workbench = {
|
|
462
373
|
hasWorkbench: workbenchConfigData.enabled,
|
|
463
374
|
config: workbenchConfigData.enabled
|
|
@@ -491,146 +402,154 @@ export const command = createCommand({
|
|
|
491
402
|
bottomSpacer: false,
|
|
492
403
|
centerTitle: false,
|
|
493
404
|
});
|
|
494
|
-
//
|
|
495
|
-
//
|
|
405
|
+
// Detect user route mount paths for Vite proxy configuration
|
|
406
|
+
// This is a quick AST scan of app.ts — runs before Vite starts
|
|
407
|
+
let routePaths = ['/api']; // Default fallback
|
|
408
|
+
try {
|
|
409
|
+
const { detectExplicitRouter } = await import('../build/app-router-detector');
|
|
410
|
+
const detection = await detectExplicitRouter(rootDir, logger);
|
|
411
|
+
if (detection.detected && detection.mounts.length > 0) {
|
|
412
|
+
routePaths = detection.mounts.map((m) => m.path);
|
|
413
|
+
logger.debug('Detected route mount paths: %s', routePaths.join(', '));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
catch (err) {
|
|
417
|
+
logger.debug('Route detection failed, using default /api: %s', err);
|
|
418
|
+
}
|
|
419
|
+
// Pick internal ports (neither is user-facing — the front-door proxy is)
|
|
420
|
+
const bunBackendPort = opts.port + 1;
|
|
421
|
+
const viteInternalPort = opts.port + 2;
|
|
422
|
+
// No-bundle dev mode guard: ensure stale bundled app artifact cannot be executed.
|
|
423
|
+
// We keep other .agentuity artifacts (metadata/workbench files) intact.
|
|
424
|
+
try {
|
|
425
|
+
const staleBundlePath = join(rootDir, '.agentuity', 'app.js');
|
|
426
|
+
if (existsSync(staleBundlePath)) {
|
|
427
|
+
await Bun.file(staleBundlePath).delete();
|
|
428
|
+
logger.debug('Removed stale dev bundle artifact: %s', staleBundlePath);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
catch (err) {
|
|
432
|
+
logger.debug('Failed to remove stale dev bundle artifact: %s', err);
|
|
433
|
+
}
|
|
434
|
+
// Debug trace: locate unexpected legacy credential warnings.
|
|
435
|
+
// Enable with AGENTUITY_TRACE_CREDENTIAL_WARNINGS=true.
|
|
436
|
+
if (process.env.AGENTUITY_TRACE_CREDENTIAL_WARNINGS === 'true') {
|
|
437
|
+
const originalConsoleError = console.error.bind(console);
|
|
438
|
+
console.error = (...args) => {
|
|
439
|
+
try {
|
|
440
|
+
const first = typeof args[0] === 'string' ? args[0] : '';
|
|
441
|
+
if (first.includes('No credentials found for this AI provider')) {
|
|
442
|
+
const stack = new Error('Credential warning trace').stack;
|
|
443
|
+
originalConsoleError('[TRACE] Credential warning origin stack:');
|
|
444
|
+
if (stack)
|
|
445
|
+
originalConsoleError(stack);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
// ignore tracing errors
|
|
450
|
+
}
|
|
451
|
+
originalConsoleError(...args);
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
// Start Vite dev server on an internal port.
|
|
455
|
+
// The user-facing port is handled by the front-door TCP proxy (ws-proxy)
|
|
456
|
+
// which routes WS upgrades to Bun and everything else to Vite.
|
|
496
457
|
let viteServer = null;
|
|
497
458
|
let vitePort;
|
|
459
|
+
// Initialize process manager to track all servers/processes
|
|
460
|
+
const procManager = initProcessManager(logger);
|
|
498
461
|
try {
|
|
499
|
-
logger.debug('Starting Vite
|
|
462
|
+
logger.debug('Starting Vite dev server (internal port %d)...', viteInternalPort);
|
|
500
463
|
const viteResult = await startViteAssetServer({
|
|
501
464
|
rootDir,
|
|
502
465
|
logger,
|
|
503
466
|
workbenchPath: workbench.config?.route,
|
|
467
|
+
port: viteInternalPort,
|
|
468
|
+
backendPort: bunBackendPort,
|
|
469
|
+
routePaths,
|
|
504
470
|
});
|
|
505
471
|
viteServer = viteResult.server;
|
|
506
472
|
vitePort = viteResult.port;
|
|
473
|
+
// Register Vite server with process manager
|
|
474
|
+
procManager.registerServer({
|
|
475
|
+
id: 'vite',
|
|
476
|
+
server: viteServer,
|
|
477
|
+
description: 'Vite dev server (frontend assets)',
|
|
478
|
+
port: vitePort,
|
|
479
|
+
});
|
|
507
480
|
// Update dev lock with actual Vite port
|
|
508
481
|
await devLock.updatePorts({ vite: vitePort });
|
|
509
|
-
logger.debug(`Vite
|
|
482
|
+
logger.debug(`Vite dev server running on port ${vitePort} (internal, proxying backend on port ${bunBackendPort})`);
|
|
510
483
|
}
|
|
511
484
|
catch (error) {
|
|
512
|
-
tui.error(`Failed to start Vite
|
|
485
|
+
tui.error(`Failed to start Vite dev server: ${error}`);
|
|
486
|
+
await procManager.cleanup('vite startup failure');
|
|
513
487
|
await devLock.release();
|
|
514
488
|
originalExit(1);
|
|
515
489
|
return;
|
|
516
490
|
}
|
|
517
|
-
//
|
|
518
|
-
//
|
|
519
|
-
|
|
491
|
+
// Start the front-door TCP proxy on the user-facing port.
|
|
492
|
+
// Routes WebSocket upgrades (for /api/*, /_agentuity/*) directly to Bun
|
|
493
|
+
// and everything else (HTTP, HMR WebSocket) to Vite.
|
|
494
|
+
// This works around Bun's broken node:http upgrade socket implementation.
|
|
495
|
+
let frontDoorServer = null;
|
|
496
|
+
try {
|
|
497
|
+
const { startWsProxy } = await import('../build/vite/ws-proxy');
|
|
498
|
+
frontDoorServer = await startWsProxy({
|
|
499
|
+
port: opts.port,
|
|
500
|
+
vitePort,
|
|
501
|
+
backendPort: bunBackendPort,
|
|
502
|
+
routePaths,
|
|
503
|
+
logger,
|
|
504
|
+
});
|
|
505
|
+
// Register front-door proxy with process manager
|
|
506
|
+
procManager.registerServer({
|
|
507
|
+
id: 'front-door-proxy',
|
|
508
|
+
server: {
|
|
509
|
+
close: () => {
|
|
510
|
+
frontDoorServer?.close();
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
description: 'Front-door TCP proxy (WS routing)',
|
|
514
|
+
port: opts.port,
|
|
515
|
+
});
|
|
516
|
+
logger.debug(`Front-door proxy on port ${opts.port} (Vite:${vitePort}, Bun:${bunBackendPort})`);
|
|
517
|
+
}
|
|
518
|
+
catch (error) {
|
|
519
|
+
tui.error(`Failed to start front-door proxy: ${error}`);
|
|
520
|
+
await procManager.cleanup('front-door proxy startup failure');
|
|
521
|
+
await devLock.release();
|
|
522
|
+
originalExit(1);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
// --- State for long-running processes ---
|
|
520
526
|
let gravityProcess = null;
|
|
521
527
|
let gravityHeartbeatInterval = null;
|
|
522
|
-
let stdinListenerRegistered = false;
|
|
523
|
-
const restartServer = () => {
|
|
524
|
-
shouldRestart = true;
|
|
525
|
-
};
|
|
526
|
-
const showWelcome = () => {
|
|
527
|
-
logger.info('DevMode ready 🚀');
|
|
528
|
-
};
|
|
529
|
-
// Create file watcher for backend hot reload
|
|
530
|
-
const fileWatcher = createFileWatcher({
|
|
531
|
-
rootDir,
|
|
532
|
-
logger,
|
|
533
|
-
onRestart: restartServer,
|
|
534
|
-
});
|
|
535
|
-
// Start file watcher (will be paused during builds)
|
|
536
|
-
fileWatcher.start();
|
|
537
|
-
// Track if cleanup is in progress to avoid duplicate cleanup
|
|
538
|
-
let cleaningUp = false;
|
|
539
|
-
// Track if shutdown was requested (SIGINT/SIGTERM) to break the main loop
|
|
540
|
-
let shutdownRequested = false;
|
|
541
|
-
// Store stdin data handler reference for cleanup
|
|
528
|
+
let stdinListenerRegistered = false;
|
|
542
529
|
let stdinDataHandler = null;
|
|
530
|
+
let shutdownRequested = false;
|
|
543
531
|
/**
|
|
544
532
|
* Centralized cleanup function for all resources.
|
|
545
|
-
*
|
|
546
|
-
* @param exitAfter - If true, exit the process after cleanup
|
|
547
|
-
* @param exitCode - Exit code to use if exitAfter is true
|
|
548
|
-
* @param silent - If true, don't show "Shutting down" message
|
|
533
|
+
* Uses the process manager for tracked servers/processes.
|
|
549
534
|
*/
|
|
550
535
|
const cleanup = async (exitAfter = false, exitCode = 0, silent = false) => {
|
|
551
|
-
if (
|
|
536
|
+
if (shutdownRequested)
|
|
552
537
|
return;
|
|
553
|
-
|
|
538
|
+
shutdownRequested = true;
|
|
554
539
|
if (!silent) {
|
|
555
540
|
tui.info('Shutting down...');
|
|
556
541
|
}
|
|
557
|
-
// Stop
|
|
558
|
-
try {
|
|
559
|
-
fileWatcher.stop();
|
|
560
|
-
}
|
|
561
|
-
catch (err) {
|
|
562
|
-
logger.debug('Error stopping file watcher: %s', err);
|
|
563
|
-
}
|
|
564
|
-
// Stop Bun server
|
|
565
|
-
try {
|
|
566
|
-
await stopBunServer(opts.port, logger);
|
|
567
|
-
}
|
|
568
|
-
catch (err) {
|
|
569
|
-
logger.debug('Error stopping Bun server during cleanup: %s', err);
|
|
570
|
-
}
|
|
571
|
-
// Stop gravity heartbeat interval
|
|
542
|
+
// Stop gravity heartbeat interval first
|
|
572
543
|
if (gravityHeartbeatInterval) {
|
|
573
544
|
clearInterval(gravityHeartbeatInterval);
|
|
574
545
|
gravityHeartbeatInterval = null;
|
|
575
546
|
}
|
|
576
|
-
//
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
gravityProcess.kill('SIGTERM');
|
|
581
|
-
// Give it a moment to gracefully shutdown
|
|
582
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
583
|
-
if (gravityProcess.exitCode === null) {
|
|
584
|
-
gravityProcess.kill('SIGKILL');
|
|
585
|
-
}
|
|
586
|
-
logger.debug('Gravity process killed');
|
|
587
|
-
}
|
|
588
|
-
catch (err) {
|
|
589
|
-
logger.debug('Error killing gravity process: %s', err);
|
|
590
|
-
}
|
|
591
|
-
finally {
|
|
592
|
-
gravityProcess = null;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
// Close Vite asset server with timeout to prevent hanging
|
|
596
|
-
if (viteServer) {
|
|
597
|
-
logger.debug('Closing Vite server...');
|
|
598
|
-
try {
|
|
599
|
-
// Use Promise.race with timeout to prevent hanging
|
|
600
|
-
const closePromise = viteServer.close();
|
|
601
|
-
const timeoutPromise = new Promise((resolve) => {
|
|
602
|
-
setTimeout(() => {
|
|
603
|
-
logger.debug('Vite server close timed out, continuing...');
|
|
604
|
-
resolve();
|
|
605
|
-
}, 2000);
|
|
606
|
-
});
|
|
607
|
-
await Promise.race([closePromise, timeoutPromise]);
|
|
608
|
-
logger.debug('Vite server closed');
|
|
609
|
-
}
|
|
610
|
-
catch (err) {
|
|
611
|
-
logger.debug('Error closing Vite server: %s', err);
|
|
612
|
-
}
|
|
613
|
-
finally {
|
|
614
|
-
viteServer = null;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
// Release the dev lockfile
|
|
618
|
-
logger.debug('Releasing dev lock...');
|
|
619
|
-
try {
|
|
620
|
-
await devLock.release();
|
|
621
|
-
logger.debug('Dev lock released');
|
|
622
|
-
}
|
|
623
|
-
catch (err) {
|
|
624
|
-
logger.debug('Error releasing dev lock: %s', err);
|
|
625
|
-
}
|
|
547
|
+
// Use process manager for tracked cleanup
|
|
548
|
+
await procManager.cleanup('shutdown');
|
|
549
|
+
// Additional cleanup for non-tracked resources
|
|
550
|
+
await devLock.release();
|
|
626
551
|
await killLingeringGravityProcesses(logger);
|
|
627
|
-
|
|
628
|
-
if (!exitAfter) {
|
|
629
|
-
cleaningUp = false;
|
|
630
|
-
}
|
|
631
|
-
else {
|
|
632
|
-
// Clean up stdin keyboard handler right before exiting
|
|
633
|
-
// This must happen AFTER all async cleanup to keep event loop alive
|
|
552
|
+
if (exitAfter) {
|
|
634
553
|
if (stdinListenerRegistered && process.stdin.isTTY) {
|
|
635
554
|
try {
|
|
636
555
|
if (stdinDataHandler) {
|
|
@@ -642,588 +561,349 @@ export const command = createCommand({
|
|
|
642
561
|
process.stdin.unref();
|
|
643
562
|
}
|
|
644
563
|
catch {
|
|
645
|
-
// Ignore
|
|
564
|
+
// Ignore
|
|
646
565
|
}
|
|
647
566
|
}
|
|
648
|
-
logger.debug('Exiting with code %d', exitCode);
|
|
649
567
|
originalExit(exitCode);
|
|
650
568
|
}
|
|
651
569
|
};
|
|
652
|
-
|
|
653
|
-
* Cleanup for restart: stops Bun server and Gravity, keeps Vite running
|
|
654
|
-
*/
|
|
655
|
-
const cleanupForRestart = async () => {
|
|
656
|
-
logger.debug('Cleaning up for restart...');
|
|
657
|
-
// Stop Bun server
|
|
658
|
-
try {
|
|
659
|
-
await stopBunServer(opts.port, logger);
|
|
660
|
-
}
|
|
661
|
-
catch (err) {
|
|
662
|
-
logger.debug('Error stopping Bun server for restart: %s', err);
|
|
663
|
-
}
|
|
664
|
-
// Stop gravity heartbeat interval
|
|
665
|
-
if (gravityHeartbeatInterval) {
|
|
666
|
-
clearInterval(gravityHeartbeatInterval);
|
|
667
|
-
gravityHeartbeatInterval = null;
|
|
668
|
-
}
|
|
669
|
-
// Kill gravity client
|
|
670
|
-
if (gravityProcess) {
|
|
671
|
-
try {
|
|
672
|
-
gravityProcess.kill('SIGTERM');
|
|
673
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
674
|
-
if (gravityProcess.exitCode === null) {
|
|
675
|
-
gravityProcess.kill('SIGKILL');
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
catch (err) {
|
|
679
|
-
logger.debug('Error killing gravity process for restart: %s', err);
|
|
680
|
-
}
|
|
681
|
-
finally {
|
|
682
|
-
gravityProcess = null;
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
};
|
|
686
|
-
// SIGINT/SIGTERM: coordinate shutdown between bundle and dev resources
|
|
687
|
-
let signalHandlersRegistered = false;
|
|
570
|
+
// Signal handlers
|
|
688
571
|
let exitingFromSignal = false;
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
});
|
|
709
|
-
process.on('SIGTERM', () => {
|
|
710
|
-
safeExit(0, 'SIGTERM');
|
|
711
|
-
});
|
|
712
|
-
// Handle SIGHUP (terminal closed) - same as SIGINT
|
|
713
|
-
process.on('SIGHUP', () => {
|
|
714
|
-
safeExit(0, 'SIGHUP');
|
|
715
|
-
});
|
|
716
|
-
// Handle uncaught exceptions - clean up and exit rather than limping on
|
|
717
|
-
process.on('uncaughtException', (err) => {
|
|
718
|
-
tui.error(`Uncaught exception: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`);
|
|
719
|
-
void safeExit(1, 'uncaughtException');
|
|
720
|
-
});
|
|
721
|
-
// Handle unhandled rejections - log but don't exit (usually recoverable)
|
|
722
|
-
process.on('unhandledRejection', (reason) => {
|
|
723
|
-
logger.warn('Unhandled promise rejection: %s', reason instanceof Error ? (reason.stack ?? reason.message) : String(reason));
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
// Ensure resources are always cleaned up on exit (synchronous fallback)
|
|
572
|
+
const safeExit = (code, reason) => {
|
|
573
|
+
if (exitingFromSignal)
|
|
574
|
+
return;
|
|
575
|
+
exitingFromSignal = true;
|
|
576
|
+
if (reason)
|
|
577
|
+
logger.debug('DevMode terminating (%d): %s', code, reason);
|
|
578
|
+
shutdownRequested = true;
|
|
579
|
+
cleanup(true, code).catch(() => originalExit(1));
|
|
580
|
+
};
|
|
581
|
+
process.on('SIGINT', () => safeExit(0, 'SIGINT'));
|
|
582
|
+
process.on('SIGTERM', () => safeExit(0, 'SIGTERM'));
|
|
583
|
+
process.on('SIGHUP', () => safeExit(0, 'SIGHUP'));
|
|
584
|
+
process.on('uncaughtException', (err) => {
|
|
585
|
+
tui.error(`Uncaught exception: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`);
|
|
586
|
+
void safeExit(1, 'uncaughtException');
|
|
587
|
+
});
|
|
588
|
+
process.on('unhandledRejection', (reason) => {
|
|
589
|
+
logger.warn('Unhandled promise rejection: %s', reason instanceof Error ? (reason.stack ?? reason.message) : String(reason));
|
|
590
|
+
});
|
|
727
591
|
process.on('exit', () => {
|
|
728
|
-
|
|
729
|
-
if (stdinListenerRegistered && process.stdin.isTTY) {
|
|
730
|
-
try {
|
|
731
|
-
if (stdinDataHandler) {
|
|
732
|
-
process.stdin.removeListener('data', stdinDataHandler);
|
|
733
|
-
}
|
|
734
|
-
process.stdin.setRawMode(false);
|
|
735
|
-
process.stdin.pause();
|
|
736
|
-
process.stdin.unref();
|
|
737
|
-
}
|
|
738
|
-
catch {
|
|
739
|
-
// Ignore errors during exit cleanup
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
// Kill gravity client with SIGKILL for immediate termination
|
|
743
|
-
if (gravityProcess && gravityProcess.exitCode === null) {
|
|
592
|
+
if (gravityProcess?.exitCode === null) {
|
|
744
593
|
try {
|
|
745
594
|
gravityProcess.kill('SIGKILL');
|
|
746
595
|
}
|
|
747
596
|
catch {
|
|
748
|
-
// Ignore
|
|
597
|
+
// Ignore
|
|
749
598
|
}
|
|
750
599
|
}
|
|
751
|
-
// Close Vite server synchronously if possible
|
|
752
600
|
if (viteServer) {
|
|
753
601
|
try {
|
|
754
602
|
viteServer.close();
|
|
755
603
|
}
|
|
756
604
|
catch {
|
|
757
|
-
// Ignore
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
// Stop Bun server synchronously (best effort)
|
|
761
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
762
|
-
const server = globalThis.__AGENTUITY_SERVER__;
|
|
763
|
-
if (server?.stop) {
|
|
764
|
-
try {
|
|
765
|
-
server.stop(true);
|
|
766
|
-
}
|
|
767
|
-
catch {
|
|
768
|
-
// Ignore errors during exit cleanup
|
|
605
|
+
// Ignore
|
|
769
606
|
}
|
|
770
607
|
}
|
|
771
|
-
|
|
608
|
+
killBunSubprocess(logger);
|
|
772
609
|
releaseLockSync(rootDir);
|
|
773
610
|
});
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
//
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
if (!opts.noTypecheck) {
|
|
789
|
-
const typeResult = await typecheck(rootDir);
|
|
790
|
-
if (!typeResult.success) {
|
|
791
|
-
typeCheckErrors = typeResult.output;
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
// Step 1: Generate workbench files if enabled (must be done before entry generation)
|
|
796
|
-
if (workbenchConfigData.enabled) {
|
|
797
|
-
logger.debug('Workbench enabled, generating source files before bundle...');
|
|
798
|
-
const { generateWorkbenchFiles } = await import('../build/vite/workbench-generator');
|
|
799
|
-
await generateWorkbenchFiles(rootDir, project?.projectId ?? '', workbenchConfigData, logger);
|
|
800
|
-
}
|
|
801
|
-
// Step 2: Discover agents and routes in parallel
|
|
802
|
-
const srcDir = join(rootDir, 'src');
|
|
803
|
-
const { discoverAgents } = await import('../build/vite/agent-discovery');
|
|
804
|
-
const { discoverRoutes } = await import('../build/vite/route-discovery');
|
|
805
|
-
const { generateAgentRegistry, generateRouteRegistry } = await import('../build/vite/registry-generator');
|
|
806
|
-
const [agentMetadata, { routes, routeInfoList }] = await Promise.all([
|
|
807
|
-
discoverAgents(srcDir, project?.projectId ?? '', deploymentId, logger),
|
|
808
|
-
discoverRoutes(srcDir, project?.projectId ?? '', deploymentId, logger),
|
|
809
|
-
]);
|
|
810
|
-
// Step 2.5: Compute a hash of discovery results to skip codegen when unchanged
|
|
811
|
-
// This avoids rewriting identical files on every restart
|
|
812
|
-
const discoveryFingerprint = Bun.hash(JSON.stringify({
|
|
813
|
-
agents: agentMetadata.map((a) => a.id + a.filename),
|
|
814
|
-
routes: routeInfoList.map((r) => r.method + r.path + r.filename),
|
|
815
|
-
})).toString(36);
|
|
816
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
817
|
-
const prevFingerprint = globalThis
|
|
818
|
-
.__AGENTUITY_DISCOVERY_FINGERPRINT__;
|
|
819
|
-
const discoveryChanged = discoveryFingerprint !== prevFingerprint;
|
|
820
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
821
|
-
globalThis.__AGENTUITY_DISCOVERY_FINGERPRINT__ = discoveryFingerprint;
|
|
822
|
-
if (discoveryChanged) {
|
|
823
|
-
// Generate agent and route registries for type augmentation
|
|
824
|
-
// (TypeScript needs these files to exist for proper type inference)
|
|
825
|
-
generateAgentRegistry(srcDir, agentMetadata);
|
|
826
|
-
generateRouteRegistry(srcDir, routeInfoList);
|
|
827
|
-
logger.debug('Agent and route registries generated for dev mode');
|
|
828
|
-
// Step 3: Generate entry file with workbench and analytics config
|
|
829
|
-
// Pass pre-discovered routes to avoid redundant route discovery
|
|
830
|
-
const { generateEntryFile } = await import('../build/entry-generator');
|
|
831
|
-
await generateEntryFile({
|
|
832
|
-
rootDir,
|
|
833
|
-
projectId: project?.projectId ?? '',
|
|
834
|
-
deploymentId,
|
|
835
|
-
logger,
|
|
836
|
-
mode: 'dev',
|
|
837
|
-
workbench: workbenchConfigData.enabled ? workbenchConfigData : undefined,
|
|
838
|
-
analytics: agentuityConfig?.analytics,
|
|
839
|
-
noBundle: opts.experimentalNoBundle,
|
|
840
|
-
preDiscoveredRoutes: routeInfoList,
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
else {
|
|
844
|
-
logger.debug('Discovery unchanged (fingerprint: %s), skipping codegen', discoveryFingerprint);
|
|
845
|
-
}
|
|
846
|
-
// Step 4: Bundle the app with LLM patches (skip in --experimental-no-bundle mode)
|
|
847
|
-
if (!opts.experimentalNoBundle) {
|
|
848
|
-
// This produces .agentuity/app.js with AI Gateway routing patches applied
|
|
849
|
-
// Must re-bundle even if discovery unchanged (user code may have changed)
|
|
850
|
-
const { installExternalsAndBuild } = await import('../build/vite/server-bundler');
|
|
851
|
-
await installExternalsAndBuild({
|
|
852
|
-
rootDir,
|
|
853
|
-
dev: true,
|
|
854
|
-
logger,
|
|
855
|
-
});
|
|
856
|
-
}
|
|
857
|
-
else {
|
|
858
|
-
logger.debug('Skipping Bun.build (--experimental-no-bundle mode)');
|
|
859
|
-
}
|
|
860
|
-
// Generate metadata file (needed for eval ID lookup at runtime)
|
|
861
|
-
// Reuse agentMetadata and routes from Step 2
|
|
862
|
-
const { generateMetadata, writeMetadataFile } = await import('../build/vite/metadata-generator');
|
|
863
|
-
const promises = [];
|
|
864
|
-
// Generate/update prompt files (non-blocking)
|
|
865
|
-
promises.push(import('../build/vite/prompt-generator')
|
|
866
|
-
.then(({ generatePromptFiles }) => generatePromptFiles(srcDir, logger))
|
|
867
|
-
.catch((err) => logger.warn('Failed to generate prompt files: %s', err.message)));
|
|
868
|
-
const metadata = await generateMetadata({
|
|
869
|
-
rootDir,
|
|
870
|
-
projectId: project?.projectId ?? '',
|
|
871
|
-
orgId: project?.orgId ?? '',
|
|
872
|
-
deploymentId,
|
|
873
|
-
agents: agentMetadata,
|
|
874
|
-
routes,
|
|
875
|
-
dev: true,
|
|
876
|
-
logger,
|
|
877
|
-
});
|
|
878
|
-
writeMetadataFile(rootDir, metadata, true, logger);
|
|
879
|
-
// Sync metadata with backend (creates agents and evals in the database)
|
|
880
|
-
if (syncService && project?.projectId) {
|
|
881
|
-
promises.push(syncService.sync(metadata, previousMetadata, project.projectId, deploymentId));
|
|
882
|
-
previousMetadata = metadata;
|
|
883
|
-
}
|
|
884
|
-
await Promise.all(promises);
|
|
885
|
-
},
|
|
886
|
-
clearOnSuccess: true,
|
|
887
|
-
});
|
|
888
|
-
if (typeCheckErrors) {
|
|
889
|
-
console.log('');
|
|
890
|
-
console.log(typeCheckErrors);
|
|
891
|
-
console.log('');
|
|
892
|
-
fileWatcher.resume();
|
|
893
|
-
// wait for a file change or shutdown to trigger a recompile
|
|
894
|
-
while (!shutdownRequested && !shouldRestart) {
|
|
895
|
-
await tui.spinner({
|
|
896
|
-
message: 'Waiting for changes...',
|
|
897
|
-
clearOnSuccess: true,
|
|
898
|
-
callback: async () => {
|
|
899
|
-
// Check more frequently so CTRL+C is responsive
|
|
900
|
-
for (let i = 0; i < 10; i++) {
|
|
901
|
-
if (shutdownRequested || shouldRestart) {
|
|
902
|
-
return;
|
|
903
|
-
}
|
|
904
|
-
await Bun.sleep(100);
|
|
905
|
-
}
|
|
906
|
-
},
|
|
907
|
-
});
|
|
908
|
-
}
|
|
909
|
-
if (shutdownRequested) {
|
|
910
|
-
return;
|
|
911
|
-
}
|
|
912
|
-
// Re-enter the main loop to re-typecheck and rebuild
|
|
913
|
-
// Without this, the code falls through and tries to start the server
|
|
914
|
-
// with the old/stale bundle instead of rebuilding first
|
|
915
|
-
continue;
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
catch (error) {
|
|
919
|
-
tui.error(`Failed to build dev bundle: ${error}`);
|
|
920
|
-
tui.warning('Waiting for file changes to retry...');
|
|
921
|
-
// Resume watcher to detect changes for retry
|
|
922
|
-
fileWatcher.resume();
|
|
923
|
-
// Wait for next restart trigger or shutdown
|
|
924
|
-
await new Promise((resolve) => {
|
|
925
|
-
const checkRestart = setInterval(() => {
|
|
926
|
-
if (shouldRestart || shutdownRequested) {
|
|
927
|
-
clearInterval(checkRestart);
|
|
928
|
-
resolve();
|
|
929
|
-
}
|
|
930
|
-
}, 100);
|
|
931
|
-
});
|
|
932
|
-
if (shutdownRequested) {
|
|
933
|
-
break;
|
|
934
|
-
}
|
|
935
|
-
continue;
|
|
936
|
-
}
|
|
937
|
-
try {
|
|
938
|
-
// Load SDK key from project .env files for AI Gateway routing
|
|
939
|
-
// This must be set so the bundled AI SDK patches can inject the API key
|
|
940
|
-
if (!process.env.AGENTUITY_SDK_KEY) {
|
|
941
|
-
const sdkKey = await loadProjectSDKKey(logger, rootDir);
|
|
942
|
-
if (sdkKey) {
|
|
943
|
-
process.env.AGENTUITY_SDK_KEY = sdkKey;
|
|
944
|
-
}
|
|
945
|
-
else if (project) {
|
|
946
|
-
tui.warning('AGENTUITY_SDK_KEY not found in .env file. Numerous features will be unavailable.');
|
|
947
|
-
tui.bullet(`Run "${getCommand('cloud env pull')}" to sync your SDK key, or add AGENTUITY_SDK_KEY to your .env file.`);
|
|
611
|
+
// ================================================================
|
|
612
|
+
// Step 1: Prepare dev server (once)
|
|
613
|
+
// ================================================================
|
|
614
|
+
await tui.spinner({
|
|
615
|
+
message: 'Preparing dev server',
|
|
616
|
+
callback: async () => {
|
|
617
|
+
// Typecheck (skip with --no-typecheck)
|
|
618
|
+
if (!opts.noTypecheck) {
|
|
619
|
+
const typeResult = await typecheck(rootDir);
|
|
620
|
+
if (!typeResult.success) {
|
|
621
|
+
// Non-fatal in dev: log errors and continue
|
|
622
|
+
console.log('');
|
|
623
|
+
console.log(typeResult.output);
|
|
624
|
+
console.log('');
|
|
948
625
|
}
|
|
949
626
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
process.env.AGENTUITY_VECTOR_URL = serviceUrls.vector;
|
|
972
|
-
process.env.AGENTUITY_KEYVALUE_URL = serviceUrls.keyvalue;
|
|
973
|
-
process.env.AGENTUITY_SANDBOX_URL = serviceUrls.sandbox;
|
|
974
|
-
process.env.AGENTUITY_STREAM_URL = serviceUrls.stream;
|
|
975
|
-
process.env.AGENTUITY_CLOUD_ORG_ID = project.orgId;
|
|
976
|
-
process.env.AGENTUITY_CLOUD_PROJECT_ID = project.projectId;
|
|
977
|
-
process.env.AGENTUITY_CLOUD_DEPLOYMENT_ID = deploymentId;
|
|
978
|
-
}
|
|
979
|
-
if (devmode?.hostname) {
|
|
980
|
-
process.env.AGENTUITY_DEVMODE_URL = `https://${devmode.hostname}`;
|
|
981
|
-
}
|
|
982
|
-
else {
|
|
983
|
-
process.env.AGENTUITY_DEVMODE_URL = `http://localhost:${opts.port}`;
|
|
984
|
-
}
|
|
985
|
-
// Set Vite port for asset proxying in bundled app
|
|
986
|
-
process.env.VITE_PORT = String(vitePort);
|
|
987
|
-
logger.debug('Set VITE_PORT=%s for asset proxying', process.env.VITE_PORT);
|
|
988
|
-
// Start Bun dev server (Vite already running, just start backend)
|
|
989
|
-
await startBunDevServer({
|
|
627
|
+
// Generate workbench files if enabled
|
|
628
|
+
if (workbenchConfigData.enabled) {
|
|
629
|
+
const { generateWorkbenchFiles } = await import('../build/vite/workbench-generator');
|
|
630
|
+
await generateWorkbenchFiles(rootDir, project?.projectId ?? '', workbenchConfigData, logger);
|
|
631
|
+
}
|
|
632
|
+
// Discover agents and routes in parallel
|
|
633
|
+
const srcDir = join(rootDir, 'src');
|
|
634
|
+
const { discoverAgents } = await import('../build/vite/agent-discovery');
|
|
635
|
+
const { discoverRoutes } = await import('../build/vite/route-discovery');
|
|
636
|
+
const [agentMetadata, { routes }] = await Promise.all([
|
|
637
|
+
discoverAgents(srcDir, project?.projectId ?? '', deploymentId, logger),
|
|
638
|
+
discoverRoutes(srcDir, project?.projectId ?? '', deploymentId, logger),
|
|
639
|
+
]);
|
|
640
|
+
// Generate metadata file
|
|
641
|
+
const { generateMetadata, writeMetadataFile } = await import('../build/vite/metadata-generator');
|
|
642
|
+
const promises = [];
|
|
643
|
+
// Generate prompt files (non-blocking)
|
|
644
|
+
promises.push(import('../build/vite/prompt-generator')
|
|
645
|
+
.then(({ generatePromptFiles }) => generatePromptFiles(srcDir, logger))
|
|
646
|
+
.catch((err) => logger.warn('Failed to generate prompt files: %s', err.message)));
|
|
647
|
+
const metadata = await generateMetadata({
|
|
990
648
|
rootDir,
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
orgId: project?.orgId,
|
|
649
|
+
projectId: project?.projectId ?? '',
|
|
650
|
+
orgId: project?.orgId ?? '',
|
|
994
651
|
deploymentId,
|
|
652
|
+
agents: agentMetadata,
|
|
653
|
+
routes,
|
|
654
|
+
dev: true,
|
|
995
655
|
logger,
|
|
996
|
-
vitePort, // Pass port of already-running Vite server
|
|
997
|
-
inspect: opts.inspect,
|
|
998
|
-
inspectWait: opts.inspectWait,
|
|
999
|
-
inspectBrk: opts.inspectBrk,
|
|
1000
|
-
noBundle: opts.experimentalNoBundle,
|
|
1001
656
|
});
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
657
|
+
writeMetadataFile(rootDir, metadata, true, logger);
|
|
658
|
+
// Sync metadata with backend
|
|
659
|
+
if (syncService && project?.projectId) {
|
|
660
|
+
promises.push(syncService.sync(metadata, previousMetadata, project.projectId, deploymentId));
|
|
661
|
+
previousMetadata = metadata;
|
|
662
|
+
}
|
|
663
|
+
await Promise.all(promises);
|
|
664
|
+
},
|
|
665
|
+
clearOnSuccess: true,
|
|
666
|
+
});
|
|
667
|
+
// ================================================================
|
|
668
|
+
// Step 2: Set environment variables
|
|
669
|
+
// ================================================================
|
|
670
|
+
if (!process.env.AGENTUITY_SDK_KEY) {
|
|
671
|
+
const sdkKey = await loadProjectSDKKey(logger, rootDir);
|
|
672
|
+
if (sdkKey) {
|
|
673
|
+
process.env.AGENTUITY_SDK_KEY = sdkKey;
|
|
674
|
+
}
|
|
675
|
+
else if (project) {
|
|
676
|
+
tui.warning('AGENTUITY_SDK_KEY not found in .env file. Numerous features will be unavailable.');
|
|
677
|
+
tui.bullet(`Run "${getCommand('cloud env pull')}" to sync your SDK key, or add AGENTUITY_SDK_KEY to your .env file.`);
|
|
1006
678
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
679
|
+
}
|
|
680
|
+
process.env.AGENTUITY_SDK_DEV_MODE = 'true';
|
|
681
|
+
process.env.AGENTUITY_RUNTIME = 'yes';
|
|
682
|
+
process.env.AGENTUITY_ENV = 'development';
|
|
683
|
+
process.env.NODE_ENV = 'development';
|
|
684
|
+
process.env.AGENTUITY_PROJECT_DIR = rootDir;
|
|
685
|
+
if (project?.region) {
|
|
686
|
+
process.env.AGENTUITY_REGION = project.region;
|
|
687
|
+
}
|
|
688
|
+
process.env.PORT = String(bunBackendPort);
|
|
689
|
+
process.env.AGENTUITY_PORT = String(bunBackendPort);
|
|
690
|
+
process.env.AGENTUITY_BASE_URL =
|
|
691
|
+
process.env.AGENTUITY_BASE_URL || `http://localhost:${vitePort}`;
|
|
692
|
+
process.env.AGENTUITY_NO_BUNDLE = 'true';
|
|
693
|
+
if (opts.resume) {
|
|
694
|
+
process.env.AGENTUITY_CODER_RESUME_SESSION = opts.resume;
|
|
695
|
+
}
|
|
696
|
+
if (project) {
|
|
697
|
+
const serviceUrls = getServiceUrls(project.region);
|
|
698
|
+
process.env.AGENTUITY_TRANSPORT_URL = serviceUrls.catalyst;
|
|
699
|
+
process.env.AGENTUITY_CATALYST_URL = serviceUrls.catalyst;
|
|
700
|
+
process.env.AGENTUITY_VECTOR_URL = serviceUrls.vector;
|
|
701
|
+
process.env.AGENTUITY_KEYVALUE_URL = serviceUrls.keyvalue;
|
|
702
|
+
process.env.AGENTUITY_SANDBOX_URL = serviceUrls.sandbox;
|
|
703
|
+
process.env.AGENTUITY_STREAM_URL = serviceUrls.stream;
|
|
704
|
+
process.env.AGENTUITY_CLOUD_ORG_ID = project.orgId;
|
|
705
|
+
process.env.AGENTUITY_CLOUD_PROJECT_ID = project.projectId;
|
|
706
|
+
process.env.AGENTUITY_CLOUD_DEPLOYMENT_ID = deploymentId;
|
|
707
|
+
}
|
|
708
|
+
if (devmode?.hostname) {
|
|
709
|
+
process.env.AGENTUITY_DEVMODE_URL = `https://${devmode.hostname}`;
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
process.env.AGENTUITY_DEVMODE_URL = `http://localhost:${vitePort}`;
|
|
713
|
+
}
|
|
714
|
+
// ================================================================
|
|
715
|
+
// Step 3: Start Bun backend with --hot (handles its own HMR)
|
|
716
|
+
// ================================================================
|
|
717
|
+
try {
|
|
718
|
+
await startBunDevServer({
|
|
719
|
+
rootDir,
|
|
720
|
+
port: bunBackendPort,
|
|
721
|
+
logger,
|
|
722
|
+
vitePort,
|
|
723
|
+
inspect: opts.inspect,
|
|
724
|
+
inspectWait: opts.inspectWait,
|
|
725
|
+
inspectBrk: opts.inspectBrk,
|
|
726
|
+
});
|
|
727
|
+
// Register Bun subprocess with process manager
|
|
728
|
+
// The subprocess is stored in globalThis.__AGENTUITY_BUN_SUBPROCESS__
|
|
729
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
730
|
+
const bunSubprocess = globalThis.__AGENTUITY_BUN_SUBPROCESS__;
|
|
731
|
+
if (bunSubprocess) {
|
|
732
|
+
procManager.registerProcess({
|
|
733
|
+
id: 'bun-backend',
|
|
734
|
+
process: bunSubprocess,
|
|
735
|
+
description: 'Bun backend server (--hot)',
|
|
736
|
+
port: bunBackendPort,
|
|
737
|
+
critical: true,
|
|
1022
738
|
});
|
|
1023
|
-
if (shutdownRequested) {
|
|
1024
|
-
break;
|
|
1025
|
-
}
|
|
1026
|
-
continue;
|
|
1027
739
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
740
|
+
}
|
|
741
|
+
catch (error) {
|
|
742
|
+
tui.error(`Failed to start Bun backend server: ${error}`);
|
|
743
|
+
await cleanup(true, 1, true);
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
// ================================================================
|
|
747
|
+
// Step 4: Start gravity tunnel (if public URL enabled)
|
|
748
|
+
// ================================================================
|
|
749
|
+
if (gravityBin && gravityURL && devmode && project) {
|
|
750
|
+
const privateKeyPEM = devmode.privateKey ?? savedPrivateKey;
|
|
751
|
+
if (!privateKeyPEM) {
|
|
752
|
+
tui.error('No private key available for gravity connection. Please re-run to generate a new key.');
|
|
753
|
+
await cleanup(true, 1, true);
|
|
754
|
+
return;
|
|
1031
755
|
}
|
|
1032
756
|
try {
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
757
|
+
gravityProcess = Bun.spawn([
|
|
758
|
+
gravityBin,
|
|
759
|
+
'--endpoint-id',
|
|
760
|
+
devmode.id,
|
|
761
|
+
'--port',
|
|
762
|
+
vitePort.toString(),
|
|
763
|
+
'--url',
|
|
764
|
+
gravityURL,
|
|
765
|
+
'--log-level',
|
|
766
|
+
process.env.AGENTUITY_GRAVITY_LOG_LEVEL ?? 'error',
|
|
767
|
+
'--org-id',
|
|
768
|
+
project.orgId,
|
|
769
|
+
'--project-id',
|
|
770
|
+
project.projectId,
|
|
771
|
+
'--private-key',
|
|
772
|
+
Buffer.from(privateKeyPEM).toString('base64'),
|
|
773
|
+
'--health-check',
|
|
774
|
+
], {
|
|
775
|
+
cwd: rootDir,
|
|
776
|
+
stdout: 'pipe',
|
|
777
|
+
stderr: 'pipe',
|
|
778
|
+
detached: false,
|
|
779
|
+
});
|
|
780
|
+
const gravityPid = gravityProcess.pid;
|
|
781
|
+
if (gravityPid) {
|
|
782
|
+
await devLock.registerChild({
|
|
783
|
+
pid: gravityPid,
|
|
784
|
+
type: 'gravity',
|
|
785
|
+
description: 'Gravity public URL tunnel',
|
|
1062
786
|
});
|
|
1063
|
-
// Register
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
const
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
}
|
|
1097
|
-
};
|
|
1098
|
-
// Send initial heartbeat immediately
|
|
1099
|
-
sendHeartbeat();
|
|
1100
|
-
// Then send every 5 seconds
|
|
1101
|
-
gravityHeartbeatInterval = setInterval(sendHeartbeat, 5000);
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
else if (trimmed) {
|
|
1105
|
-
logger.debug('[gravity] %s', trimmed);
|
|
787
|
+
// Register with process manager
|
|
788
|
+
procManager.registerProcess({
|
|
789
|
+
id: 'gravity',
|
|
790
|
+
process: gravityProcess,
|
|
791
|
+
description: 'Gravity public URL tunnel',
|
|
792
|
+
critical: false,
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
// Log gravity output and detect heartbeat port
|
|
796
|
+
(async () => {
|
|
797
|
+
try {
|
|
798
|
+
if (gravityProcess?.stdout) {
|
|
799
|
+
for await (const chunk of gravityProcess.stdout) {
|
|
800
|
+
const text = new TextDecoder().decode(chunk);
|
|
801
|
+
const trimmed = text.trim();
|
|
802
|
+
const match = trimmed.match(/^HEARTBEAT_PORT=(\d+)$/m);
|
|
803
|
+
if (match?.[1]) {
|
|
804
|
+
const heartbeatPort = parseInt(match[1], 10);
|
|
805
|
+
logger.debug('Gravity heartbeat port: %d', heartbeatPort);
|
|
806
|
+
if (!gravityHeartbeatInterval) {
|
|
807
|
+
const sendHeartbeat = async () => {
|
|
808
|
+
try {
|
|
809
|
+
await fetch(`http://127.0.0.1:${heartbeatPort}/heartbeat`, {
|
|
810
|
+
method: 'POST',
|
|
811
|
+
signal: AbortSignal.timeout(2000),
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
catch {
|
|
815
|
+
// Ignore heartbeat failures
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
sendHeartbeat();
|
|
819
|
+
gravityHeartbeatInterval = setInterval(sendHeartbeat, 5000);
|
|
1106
820
|
}
|
|
1107
821
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
catch (err) {
|
|
1111
|
-
logger.error('Error reading gravity stdout: %s', err);
|
|
1112
|
-
}
|
|
1113
|
-
})();
|
|
1114
|
-
(async () => {
|
|
1115
|
-
try {
|
|
1116
|
-
if (gravityProcess?.stderr) {
|
|
1117
|
-
for await (const chunk of gravityProcess.stderr) {
|
|
1118
|
-
const text = new TextDecoder().decode(chunk);
|
|
1119
|
-
logger.warn('[gravity] %s', text.trim());
|
|
822
|
+
else if (trimmed) {
|
|
823
|
+
logger.debug('[gravity] %s', trimmed);
|
|
1120
824
|
}
|
|
1121
825
|
}
|
|
1122
826
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
!stdinListenerRegistered) {
|
|
1134
|
-
stdinListenerRegistered = true;
|
|
1135
|
-
process.stdin.setRawMode(true);
|
|
1136
|
-
process.stdin.resume();
|
|
1137
|
-
process.stdin.setEncoding('utf8');
|
|
1138
|
-
const showHelp = () => {
|
|
1139
|
-
console.log('\n' + tui.bold('Keyboard Shortcuts:'));
|
|
1140
|
-
console.log(tui.muted(' h') + ' - show this help');
|
|
1141
|
-
console.log(tui.muted(' c') + ' - clear console');
|
|
1142
|
-
console.log(tui.muted(' q') + ' - quit\n');
|
|
1143
|
-
};
|
|
1144
|
-
// Store handler reference for cleanup
|
|
1145
|
-
stdinDataHandler = (data) => {
|
|
1146
|
-
const key = data.toString();
|
|
1147
|
-
// Handle Ctrl+C or q - trigger graceful shutdown
|
|
1148
|
-
if (key === '\u0003' || key === 'q') {
|
|
1149
|
-
// Remove stdin listener immediately to prevent re-entrancy
|
|
1150
|
-
if (stdinDataHandler) {
|
|
1151
|
-
process.stdin.removeListener('data', stdinDataHandler);
|
|
1152
|
-
stdinDataHandler = null;
|
|
827
|
+
}
|
|
828
|
+
catch (err) {
|
|
829
|
+
logger.error('Error reading gravity stdout: %s', err);
|
|
830
|
+
}
|
|
831
|
+
})();
|
|
832
|
+
(async () => {
|
|
833
|
+
try {
|
|
834
|
+
if (gravityProcess?.stderr) {
|
|
835
|
+
for await (const chunk of gravityProcess.stderr) {
|
|
836
|
+
logger.warn('[gravity] %s', new TextDecoder().decode(chunk).trim());
|
|
1153
837
|
}
|
|
1154
|
-
// Set shutdown flag and trigger cleanup directly
|
|
1155
|
-
shutdownRequested = true;
|
|
1156
|
-
cleanup(true, 0).catch((err) => {
|
|
1157
|
-
logger.debug('Cleanup error: %s', err);
|
|
1158
|
-
originalExit(1);
|
|
1159
|
-
});
|
|
1160
|
-
return;
|
|
1161
|
-
}
|
|
1162
|
-
switch (key) {
|
|
1163
|
-
case 'h':
|
|
1164
|
-
showHelp();
|
|
1165
|
-
break;
|
|
1166
|
-
case 'c':
|
|
1167
|
-
console.clear();
|
|
1168
|
-
tui.banner('⨺ Agentuity DevMode', devmodebody, {
|
|
1169
|
-
padding: 2,
|
|
1170
|
-
topSpacer: false,
|
|
1171
|
-
bottomSpacer: false,
|
|
1172
|
-
centerTitle: false,
|
|
1173
|
-
});
|
|
1174
|
-
break;
|
|
1175
|
-
default:
|
|
1176
|
-
process.stdout.write(data);
|
|
1177
|
-
break;
|
|
1178
838
|
}
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
fileWatcher.resume();
|
|
1185
|
-
// Wait for restart signal or shutdown
|
|
1186
|
-
await new Promise((resolve) => {
|
|
1187
|
-
const checkRestart = setInterval(() => {
|
|
1188
|
-
if (shouldRestart || shutdownRequested) {
|
|
1189
|
-
clearInterval(checkRestart);
|
|
1190
|
-
resolve();
|
|
1191
|
-
}
|
|
1192
|
-
}, 100);
|
|
1193
|
-
});
|
|
1194
|
-
// Exit loop if shutdown was requested
|
|
1195
|
-
if (shutdownRequested) {
|
|
1196
|
-
break;
|
|
1197
|
-
}
|
|
1198
|
-
// Restart triggered - cleanup and loop (Vite stays running)
|
|
1199
|
-
logger.debug('Restarting backend server...');
|
|
1200
|
-
// Clean up Bun server and Gravity (Vite stays running)
|
|
1201
|
-
await cleanupForRestart();
|
|
1202
|
-
// Brief pause before restart
|
|
1203
|
-
await Bun.sleep(500);
|
|
839
|
+
}
|
|
840
|
+
catch (err) {
|
|
841
|
+
logger.error('Error reading gravity stderr: %s', err);
|
|
842
|
+
}
|
|
843
|
+
})();
|
|
1204
844
|
}
|
|
1205
845
|
catch (error) {
|
|
1206
|
-
tui.error(`
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
await cleanupForRestart();
|
|
1210
|
-
// Exit if shutdown was requested during error handling
|
|
1211
|
-
if (shutdownRequested) {
|
|
1212
|
-
break;
|
|
1213
|
-
}
|
|
1214
|
-
// Resume file watcher to detect changes for retry
|
|
1215
|
-
fileWatcher.resume();
|
|
1216
|
-
// Wait for next restart trigger or shutdown
|
|
1217
|
-
await new Promise((resolve) => {
|
|
1218
|
-
const checkRestart = setInterval(() => {
|
|
1219
|
-
if (shouldRestart || shutdownRequested) {
|
|
1220
|
-
clearInterval(checkRestart);
|
|
1221
|
-
resolve();
|
|
1222
|
-
}
|
|
1223
|
-
}, 100);
|
|
1224
|
-
});
|
|
846
|
+
tui.error(`Failed to start gravity tunnel: ${error}`);
|
|
847
|
+
await cleanup(true, 1, true);
|
|
848
|
+
return;
|
|
1225
849
|
}
|
|
1226
850
|
}
|
|
851
|
+
// ================================================================
|
|
852
|
+
// Step 5: Keyboard shortcuts + wait for shutdown
|
|
853
|
+
// ================================================================
|
|
854
|
+
if (interactive && process.stdin.isTTY && process.stdout.isTTY) {
|
|
855
|
+
stdinListenerRegistered = true;
|
|
856
|
+
process.stdin.setRawMode(true);
|
|
857
|
+
process.stdin.resume();
|
|
858
|
+
process.stdin.setEncoding('utf8');
|
|
859
|
+
const showHelp = () => {
|
|
860
|
+
console.log('\n' + tui.bold('Keyboard Shortcuts:'));
|
|
861
|
+
console.log(tui.muted(' h') + ' - show this help');
|
|
862
|
+
console.log(tui.muted(' c') + ' - clear console');
|
|
863
|
+
console.log(tui.muted(' q') + ' - quit\n');
|
|
864
|
+
};
|
|
865
|
+
stdinDataHandler = (data) => {
|
|
866
|
+
const key = data.toString();
|
|
867
|
+
if (key === '\u0003' || key === 'q') {
|
|
868
|
+
if (stdinDataHandler) {
|
|
869
|
+
process.stdin.removeListener('data', stdinDataHandler);
|
|
870
|
+
stdinDataHandler = null;
|
|
871
|
+
}
|
|
872
|
+
shutdownRequested = true;
|
|
873
|
+
cleanup(true, 0).catch(() => originalExit(1));
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
switch (key) {
|
|
877
|
+
case 'h':
|
|
878
|
+
showHelp();
|
|
879
|
+
break;
|
|
880
|
+
case 'c':
|
|
881
|
+
console.clear();
|
|
882
|
+
tui.banner('⨺ Agentuity DevMode', devmodebody, {
|
|
883
|
+
padding: 2,
|
|
884
|
+
topSpacer: false,
|
|
885
|
+
bottomSpacer: false,
|
|
886
|
+
centerTitle: false,
|
|
887
|
+
});
|
|
888
|
+
break;
|
|
889
|
+
default:
|
|
890
|
+
process.stdout.write(data);
|
|
891
|
+
break;
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
process.stdin.on('data', stdinDataHandler);
|
|
895
|
+
}
|
|
896
|
+
logger.info('DevMode ready 🚀');
|
|
897
|
+
// Block until shutdown — bun --hot handles backend HMR,
|
|
898
|
+
// Vite handles frontend HMR. Nothing to restart.
|
|
899
|
+
await new Promise((resolve) => {
|
|
900
|
+
const check = setInterval(() => {
|
|
901
|
+
if (shutdownRequested) {
|
|
902
|
+
clearInterval(check);
|
|
903
|
+
resolve();
|
|
904
|
+
}
|
|
905
|
+
}, 200);
|
|
906
|
+
});
|
|
1227
907
|
}
|
|
1228
908
|
finally {
|
|
1229
909
|
/* brute force clean up */
|