@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/src/cmd/dev/index.ts
CHANGED
|
@@ -11,20 +11,18 @@ 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
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
checkMigrationEligibility,
|
|
27
|
-
} from '../../utils/route-migration';
|
|
23
|
+
import { initProcessManager } from './process-manager';
|
|
24
|
+
import { detectVersionMismatch, formatVersionMismatchWarning } from '../../utils/version-mismatch';
|
|
25
|
+
|
|
28
26
|
import { ErrorCode } from '../../errors';
|
|
29
27
|
|
|
30
28
|
const DEFAULT_PORT = 3500;
|
|
@@ -35,17 +33,13 @@ const MAX_PORT = 65535;
|
|
|
35
33
|
interface ProcessLike {
|
|
36
34
|
kill: (signal?: number | NodeJS.Signals) => void;
|
|
37
35
|
exitCode: number | null;
|
|
36
|
+
pid?: number;
|
|
38
37
|
stdout?: AsyncIterable<Uint8Array>;
|
|
39
38
|
stderr?: AsyncIterable<Uint8Array>;
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
interface ServerLike {
|
|
43
|
-
close: () => void
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface BunServer {
|
|
47
|
-
stop: (closeActiveConnections?: boolean) => void;
|
|
48
|
-
port: number;
|
|
42
|
+
close: () => void | Promise<void>;
|
|
49
43
|
}
|
|
50
44
|
|
|
51
45
|
/**
|
|
@@ -83,90 +77,21 @@ async function killLingeringGravityProcesses(logger: {
|
|
|
83
77
|
}
|
|
84
78
|
|
|
85
79
|
/**
|
|
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).
|
|
80
|
+
* Kill the Bun backend subprocess if one is running.
|
|
89
81
|
*/
|
|
90
|
-
|
|
91
|
-
port: number,
|
|
92
|
-
logger: { debug: (msg: string, ...args: unknown[]) => void }
|
|
93
|
-
): Promise<void> {
|
|
82
|
+
function killBunSubprocess(logger: { debug: (msg: string, ...args: unknown[]) => void }): void {
|
|
94
83
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
95
84
|
const globalAny = globalThis as any;
|
|
96
|
-
|
|
97
|
-
// Check for subprocess first (used when debugger flags are enabled)
|
|
98
85
|
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
|
-
}
|
|
86
|
+
if (!bunSubprocess) return;
|
|
143
87
|
|
|
144
88
|
try {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
logger.debug('Bun server stop() called');
|
|
89
|
+
bunSubprocess.kill('SIGTERM');
|
|
90
|
+
logger.debug('Bun subprocess killed');
|
|
148
91
|
} catch (err) {
|
|
149
|
-
logger.debug('Error
|
|
92
|
+
logger.debug('Error killing Bun subprocess: %s', err);
|
|
150
93
|
}
|
|
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;
|
|
94
|
+
globalAny.__AGENTUITY_BUN_SUBPROCESS__ = undefined;
|
|
170
95
|
}
|
|
171
96
|
|
|
172
97
|
const getDefaultPort = (): number => {
|
|
@@ -227,22 +152,12 @@ export const command = createCommand({
|
|
|
227
152
|
.boolean()
|
|
228
153
|
.optional()
|
|
229
154
|
.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
|
-
),
|
|
155
|
+
|
|
236
156
|
noTypecheck: z
|
|
237
157
|
.boolean()
|
|
238
158
|
.optional()
|
|
239
159
|
.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
|
-
),
|
|
160
|
+
|
|
246
161
|
resume: z.string().optional().describe('Resume a paused Hub session by ID'),
|
|
247
162
|
}),
|
|
248
163
|
},
|
|
@@ -425,27 +340,12 @@ export const command = createCommand({
|
|
|
425
340
|
);
|
|
426
341
|
}
|
|
427
342
|
|
|
428
|
-
// Check
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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 });
|
|
343
|
+
// Check for version mismatches (v1 vs v2 SDK packages)
|
|
344
|
+
const versionMismatch = detectVersionMismatch(rootDir, logger);
|
|
345
|
+
if (versionMismatch.hasV1Packages || versionMismatch.hasMajorMismatches) {
|
|
346
|
+
tui.newline();
|
|
347
|
+
tui.warning(formatVersionMismatchWarning(versionMismatch));
|
|
348
|
+
tui.newline();
|
|
449
349
|
}
|
|
450
350
|
|
|
451
351
|
try {
|
|
@@ -539,12 +439,12 @@ export const command = createCommand({
|
|
|
539
439
|
}
|
|
540
440
|
}
|
|
541
441
|
|
|
542
|
-
// Get workbench info from
|
|
543
|
-
const {
|
|
442
|
+
// Get workbench info from createApp() in app.ts (v2 approach)
|
|
443
|
+
const { getWorkbenchConfig, loadRuntimeConfig } = await import(
|
|
544
444
|
'../build/vite/config-loader'
|
|
545
445
|
);
|
|
546
|
-
const
|
|
547
|
-
const workbenchConfigData = getWorkbenchConfig(
|
|
446
|
+
const runtimeConfig = await loadRuntimeConfig(rootDir, logger);
|
|
447
|
+
const workbenchConfigData = getWorkbenchConfig(true, runtimeConfig); // dev mode
|
|
548
448
|
const workbench = {
|
|
549
449
|
hasWorkbench: workbenchConfigData.enabled,
|
|
550
450
|
config: workbenchConfigData.enabled
|
|
@@ -585,157 +485,170 @@ export const command = createCommand({
|
|
|
585
485
|
centerTitle: false,
|
|
586
486
|
});
|
|
587
487
|
|
|
588
|
-
//
|
|
589
|
-
//
|
|
488
|
+
// Detect user route mount paths for Vite proxy configuration
|
|
489
|
+
// This is a quick AST scan of app.ts — runs before Vite starts
|
|
490
|
+
let routePaths: string[] = ['/api']; // Default fallback
|
|
491
|
+
try {
|
|
492
|
+
const { detectExplicitRouter } = await import('../build/app-router-detector');
|
|
493
|
+
const detection = await detectExplicitRouter(rootDir, logger);
|
|
494
|
+
if (detection.detected && detection.mounts.length > 0) {
|
|
495
|
+
routePaths = detection.mounts.map((m) => m.path);
|
|
496
|
+
logger.debug('Detected route mount paths: %s', routePaths.join(', '));
|
|
497
|
+
}
|
|
498
|
+
} catch (err) {
|
|
499
|
+
logger.debug('Route detection failed, using default /api: %s', err);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Pick internal ports (neither is user-facing — the front-door proxy is)
|
|
503
|
+
const bunBackendPort = opts.port + 1;
|
|
504
|
+
const viteInternalPort = opts.port + 2;
|
|
505
|
+
|
|
506
|
+
// No-bundle dev mode guard: ensure stale bundled app artifact cannot be executed.
|
|
507
|
+
// We keep other .agentuity artifacts (metadata/workbench files) intact.
|
|
508
|
+
try {
|
|
509
|
+
const staleBundlePath = join(rootDir, '.agentuity', 'app.js');
|
|
510
|
+
if (existsSync(staleBundlePath)) {
|
|
511
|
+
await Bun.file(staleBundlePath).delete();
|
|
512
|
+
logger.debug('Removed stale dev bundle artifact: %s', staleBundlePath);
|
|
513
|
+
}
|
|
514
|
+
} catch (err) {
|
|
515
|
+
logger.debug('Failed to remove stale dev bundle artifact: %s', err);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Debug trace: locate unexpected legacy credential warnings.
|
|
519
|
+
// Enable with AGENTUITY_TRACE_CREDENTIAL_WARNINGS=true.
|
|
520
|
+
if (process.env.AGENTUITY_TRACE_CREDENTIAL_WARNINGS === 'true') {
|
|
521
|
+
const originalConsoleError = console.error.bind(console);
|
|
522
|
+
console.error = (...args: unknown[]) => {
|
|
523
|
+
try {
|
|
524
|
+
const first = typeof args[0] === 'string' ? args[0] : '';
|
|
525
|
+
if (first.includes('No credentials found for this AI provider')) {
|
|
526
|
+
const stack = new Error('Credential warning trace').stack;
|
|
527
|
+
originalConsoleError('[TRACE] Credential warning origin stack:');
|
|
528
|
+
if (stack) originalConsoleError(stack);
|
|
529
|
+
}
|
|
530
|
+
} catch {
|
|
531
|
+
// ignore tracing errors
|
|
532
|
+
}
|
|
533
|
+
originalConsoleError(...args);
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Start Vite dev server on an internal port.
|
|
538
|
+
// The user-facing port is handled by the front-door TCP proxy (ws-proxy)
|
|
539
|
+
// which routes WS upgrades to Bun and everything else to Vite.
|
|
590
540
|
let viteServer: ServerLike | null = null;
|
|
591
541
|
let vitePort: number;
|
|
592
542
|
|
|
543
|
+
// Initialize process manager to track all servers/processes
|
|
544
|
+
const procManager = initProcessManager(logger);
|
|
545
|
+
|
|
593
546
|
try {
|
|
594
|
-
logger.debug('Starting Vite
|
|
547
|
+
logger.debug('Starting Vite dev server (internal port %d)...', viteInternalPort);
|
|
595
548
|
const viteResult = await startViteAssetServer({
|
|
596
549
|
rootDir,
|
|
597
550
|
logger,
|
|
598
551
|
workbenchPath: workbench.config?.route,
|
|
552
|
+
port: viteInternalPort,
|
|
553
|
+
backendPort: bunBackendPort,
|
|
554
|
+
routePaths,
|
|
599
555
|
});
|
|
600
556
|
viteServer = viteResult.server;
|
|
601
557
|
vitePort = viteResult.port;
|
|
602
558
|
|
|
559
|
+
// Register Vite server with process manager
|
|
560
|
+
procManager.registerServer({
|
|
561
|
+
id: 'vite',
|
|
562
|
+
server: viteServer,
|
|
563
|
+
description: 'Vite dev server (frontend assets)',
|
|
564
|
+
port: vitePort,
|
|
565
|
+
});
|
|
566
|
+
|
|
603
567
|
// Update dev lock with actual Vite port
|
|
604
568
|
await devLock.updatePorts({ vite: vitePort });
|
|
605
569
|
|
|
606
570
|
logger.debug(
|
|
607
|
-
`Vite
|
|
571
|
+
`Vite dev server running on port ${vitePort} (internal, proxying backend on port ${bunBackendPort})`
|
|
608
572
|
);
|
|
609
573
|
} catch (error) {
|
|
610
|
-
tui.error(`Failed to start Vite
|
|
574
|
+
tui.error(`Failed to start Vite dev server: ${error}`);
|
|
575
|
+
await procManager.cleanup('vite startup failure');
|
|
611
576
|
await devLock.release();
|
|
612
577
|
originalExit(1);
|
|
613
578
|
return;
|
|
614
579
|
}
|
|
615
580
|
|
|
616
|
-
//
|
|
617
|
-
//
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
let
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
581
|
+
// Start the front-door TCP proxy on the user-facing port.
|
|
582
|
+
// Routes WebSocket upgrades (for /api/*, /_agentuity/*) directly to Bun
|
|
583
|
+
// and everything else (HTTP, HMR WebSocket) to Vite.
|
|
584
|
+
// This works around Bun's broken node:http upgrade socket implementation.
|
|
585
|
+
let frontDoorServer: import('node:net').Server | null = null;
|
|
586
|
+
try {
|
|
587
|
+
const { startWsProxy } = await import('../build/vite/ws-proxy');
|
|
588
|
+
frontDoorServer = await startWsProxy({
|
|
589
|
+
port: opts.port,
|
|
590
|
+
vitePort,
|
|
591
|
+
backendPort: bunBackendPort,
|
|
592
|
+
routePaths,
|
|
593
|
+
logger,
|
|
594
|
+
});
|
|
630
595
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
596
|
+
// Register front-door proxy with process manager
|
|
597
|
+
procManager.registerServer({
|
|
598
|
+
id: 'front-door-proxy',
|
|
599
|
+
server: {
|
|
600
|
+
close: () => {
|
|
601
|
+
frontDoorServer?.close();
|
|
602
|
+
},
|
|
603
|
+
},
|
|
604
|
+
description: 'Front-door TCP proxy (WS routing)',
|
|
605
|
+
port: opts.port,
|
|
606
|
+
});
|
|
637
607
|
|
|
638
|
-
|
|
639
|
-
|
|
608
|
+
logger.debug(
|
|
609
|
+
`Front-door proxy on port ${opts.port} (Vite:${vitePort}, Bun:${bunBackendPort})`
|
|
610
|
+
);
|
|
611
|
+
} catch (error) {
|
|
612
|
+
tui.error(`Failed to start front-door proxy: ${error}`);
|
|
613
|
+
await procManager.cleanup('front-door proxy startup failure');
|
|
614
|
+
await devLock.release();
|
|
615
|
+
originalExit(1);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
640
618
|
|
|
641
|
-
//
|
|
642
|
-
let
|
|
643
|
-
|
|
644
|
-
let
|
|
645
|
-
// Store stdin data handler reference for cleanup
|
|
619
|
+
// --- State for long-running processes ---
|
|
620
|
+
let gravityProcess: ProcessLike | null = null;
|
|
621
|
+
let gravityHeartbeatInterval: ReturnType<typeof setInterval> | null = null;
|
|
622
|
+
let stdinListenerRegistered = false;
|
|
646
623
|
let stdinDataHandler: ((data: Buffer | string) => void) | null = null;
|
|
624
|
+
let shutdownRequested = false;
|
|
647
625
|
|
|
648
626
|
/**
|
|
649
627
|
* Centralized cleanup function for all resources.
|
|
650
|
-
*
|
|
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
|
|
628
|
+
* Uses the process manager for tracked servers/processes.
|
|
654
629
|
*/
|
|
655
630
|
const cleanup = async (exitAfter = false, exitCode = 0, silent = false) => {
|
|
656
|
-
if (
|
|
657
|
-
|
|
631
|
+
if (shutdownRequested) return;
|
|
632
|
+
shutdownRequested = true;
|
|
658
633
|
|
|
659
634
|
if (!silent) {
|
|
660
635
|
tui.info('Shutting down...');
|
|
661
636
|
}
|
|
662
637
|
|
|
663
|
-
// Stop
|
|
664
|
-
try {
|
|
665
|
-
fileWatcher.stop();
|
|
666
|
-
} catch (err) {
|
|
667
|
-
logger.debug('Error stopping file watcher: %s', err);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
// Stop Bun server
|
|
671
|
-
try {
|
|
672
|
-
await stopBunServer(opts.port, logger);
|
|
673
|
-
} catch (err) {
|
|
674
|
-
logger.debug('Error stopping Bun server during cleanup: %s', err);
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
// Stop gravity heartbeat interval
|
|
638
|
+
// Stop gravity heartbeat interval first
|
|
678
639
|
if (gravityHeartbeatInterval) {
|
|
679
640
|
clearInterval(gravityHeartbeatInterval);
|
|
680
641
|
gravityHeartbeatInterval = null;
|
|
681
642
|
}
|
|
682
643
|
|
|
683
|
-
//
|
|
684
|
-
|
|
685
|
-
logger.debug('Killing gravity process...');
|
|
686
|
-
try {
|
|
687
|
-
gravityProcess.kill('SIGTERM');
|
|
688
|
-
// Give it a moment to gracefully shutdown
|
|
689
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
690
|
-
if (gravityProcess.exitCode === null) {
|
|
691
|
-
gravityProcess.kill('SIGKILL');
|
|
692
|
-
}
|
|
693
|
-
logger.debug('Gravity process killed');
|
|
694
|
-
} catch (err) {
|
|
695
|
-
logger.debug('Error killing gravity process: %s', err);
|
|
696
|
-
} finally {
|
|
697
|
-
gravityProcess = null;
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
// Close Vite asset server with timeout to prevent hanging
|
|
702
|
-
if (viteServer) {
|
|
703
|
-
logger.debug('Closing Vite server...');
|
|
704
|
-
try {
|
|
705
|
-
// Use Promise.race with timeout to prevent hanging
|
|
706
|
-
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
|
-
});
|
|
713
|
-
await Promise.race([closePromise, timeoutPromise]);
|
|
714
|
-
logger.debug('Vite server closed');
|
|
715
|
-
} catch (err) {
|
|
716
|
-
logger.debug('Error closing Vite server: %s', err);
|
|
717
|
-
} finally {
|
|
718
|
-
viteServer = null;
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
// Release the dev lockfile
|
|
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
|
-
}
|
|
644
|
+
// Use process manager for tracked cleanup
|
|
645
|
+
await procManager.cleanup('shutdown');
|
|
730
646
|
|
|
647
|
+
// Additional cleanup for non-tracked resources
|
|
648
|
+
await devLock.release();
|
|
731
649
|
await killLingeringGravityProcesses(logger);
|
|
732
650
|
|
|
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
|
|
651
|
+
if (exitAfter) {
|
|
739
652
|
if (stdinListenerRegistered && process.stdin.isTTY) {
|
|
740
653
|
try {
|
|
741
654
|
if (stdinDataHandler) {
|
|
@@ -746,699 +659,403 @@ export const command = createCommand({
|
|
|
746
659
|
process.stdin.pause();
|
|
747
660
|
process.stdin.unref();
|
|
748
661
|
} catch {
|
|
749
|
-
// Ignore
|
|
662
|
+
// Ignore
|
|
750
663
|
}
|
|
751
664
|
}
|
|
752
|
-
logger.debug('Exiting with code %d', exitCode);
|
|
753
665
|
originalExit(exitCode);
|
|
754
666
|
}
|
|
755
667
|
};
|
|
756
668
|
|
|
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;
|
|
669
|
+
// Signal handlers
|
|
794
670
|
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
|
-
}
|
|
671
|
+
const safeExit = (code: number, reason?: string) => {
|
|
672
|
+
if (exitingFromSignal) return;
|
|
673
|
+
exitingFromSignal = true;
|
|
674
|
+
if (reason) logger.debug('DevMode terminating (%d): %s', code, reason);
|
|
675
|
+
shutdownRequested = true;
|
|
676
|
+
cleanup(true, code).catch(() => originalExit(1));
|
|
677
|
+
};
|
|
843
678
|
|
|
844
|
-
|
|
679
|
+
process.on('SIGINT', () => safeExit(0, 'SIGINT'));
|
|
680
|
+
process.on('SIGTERM', () => safeExit(0, 'SIGTERM'));
|
|
681
|
+
process.on('SIGHUP', () => safeExit(0, 'SIGHUP'));
|
|
682
|
+
process.on('uncaughtException', (err) => {
|
|
683
|
+
tui.error(
|
|
684
|
+
`Uncaught exception: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`
|
|
685
|
+
);
|
|
686
|
+
void safeExit(1, 'uncaughtException');
|
|
687
|
+
});
|
|
688
|
+
process.on('unhandledRejection', (reason) => {
|
|
689
|
+
logger.warn(
|
|
690
|
+
'Unhandled promise rejection: %s',
|
|
691
|
+
reason instanceof Error ? (reason.stack ?? reason.message) : String(reason)
|
|
692
|
+
);
|
|
693
|
+
});
|
|
845
694
|
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) {
|
|
695
|
+
if (gravityProcess?.exitCode === null) {
|
|
862
696
|
try {
|
|
863
697
|
gravityProcess.kill('SIGKILL');
|
|
864
698
|
} catch {
|
|
865
|
-
// Ignore
|
|
699
|
+
// Ignore
|
|
866
700
|
}
|
|
867
701
|
}
|
|
868
|
-
|
|
869
|
-
// Close Vite server synchronously if possible
|
|
870
702
|
if (viteServer) {
|
|
871
703
|
try {
|
|
872
704
|
viteServer.close();
|
|
873
705
|
} catch {
|
|
874
|
-
// Ignore
|
|
706
|
+
// Ignore
|
|
875
707
|
}
|
|
876
708
|
}
|
|
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
|
|
709
|
+
killBunSubprocess(logger);
|
|
890
710
|
releaseLockSync(rootDir);
|
|
891
711
|
});
|
|
892
712
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
// Step 0: typecheck (skip with --no-typecheck)
|
|
909
|
-
typeCheckErrors = undefined;
|
|
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
|
-
);
|
|
940
|
-
|
|
941
|
-
const [agentMetadata, { routes, routeInfoList }] = await Promise.all([
|
|
942
|
-
discoverAgents(srcDir, project?.projectId ?? '', deploymentId, logger),
|
|
943
|
-
discoverRoutes(srcDir, project?.projectId ?? '', deploymentId, logger),
|
|
944
|
-
]);
|
|
945
|
-
|
|
946
|
-
// Step 2.5: Compute a hash of discovery results to skip codegen when unchanged
|
|
947
|
-
// This avoids rewriting identical files on every restart
|
|
948
|
-
const discoveryFingerprint = Bun.hash(
|
|
949
|
-
JSON.stringify({
|
|
950
|
-
agents: agentMetadata.map((a) => a.id + a.filename),
|
|
951
|
-
routes: routeInfoList.map((r) => r.method + r.path + r.filename),
|
|
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
|
-
}
|
|
989
|
-
|
|
990
|
-
// Step 4: Bundle the app with LLM patches (skip in --experimental-no-bundle mode)
|
|
991
|
-
if (!opts.experimentalNoBundle) {
|
|
992
|
-
// This produces .agentuity/app.js with AI Gateway routing patches applied
|
|
993
|
-
// Must re-bundle even if discovery unchanged (user code may have changed)
|
|
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
|
-
}
|
|
1005
|
-
|
|
1006
|
-
// Generate metadata file (needed for eval ID lookup at runtime)
|
|
1007
|
-
// Reuse agentMetadata and routes from Step 2
|
|
1008
|
-
const { generateMetadata, writeMetadataFile } = await import(
|
|
1009
|
-
'../build/vite/metadata-generator'
|
|
1010
|
-
);
|
|
1011
|
-
|
|
1012
|
-
const promises: Promise<void>[] = [];
|
|
1013
|
-
|
|
1014
|
-
// Generate/update prompt files (non-blocking)
|
|
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
|
-
);
|
|
1022
|
-
|
|
1023
|
-
const metadata = await generateMetadata({
|
|
1024
|
-
rootDir,
|
|
1025
|
-
projectId: project?.projectId ?? '',
|
|
1026
|
-
orgId: project?.orgId ?? '',
|
|
1027
|
-
deploymentId,
|
|
1028
|
-
agents: agentMetadata,
|
|
1029
|
-
routes,
|
|
1030
|
-
dev: true,
|
|
1031
|
-
logger,
|
|
1032
|
-
});
|
|
1033
|
-
|
|
1034
|
-
writeMetadataFile(rootDir, metadata, true, logger);
|
|
1035
|
-
|
|
1036
|
-
// Sync metadata with backend (creates agents and evals in the database)
|
|
1037
|
-
if (syncService && project?.projectId) {
|
|
1038
|
-
promises.push(
|
|
1039
|
-
syncService.sync(
|
|
1040
|
-
metadata,
|
|
1041
|
-
previousMetadata,
|
|
1042
|
-
project.projectId,
|
|
1043
|
-
deploymentId
|
|
1044
|
-
)
|
|
1045
|
-
);
|
|
1046
|
-
previousMetadata = metadata;
|
|
1047
|
-
}
|
|
1048
|
-
await Promise.all(promises);
|
|
1049
|
-
},
|
|
1050
|
-
clearOnSuccess: true,
|
|
1051
|
-
});
|
|
1052
|
-
|
|
1053
|
-
if (typeCheckErrors) {
|
|
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;
|
|
713
|
+
// ================================================================
|
|
714
|
+
// Step 1: Prepare dev server (once)
|
|
715
|
+
// ================================================================
|
|
716
|
+
|
|
717
|
+
await tui.spinner({
|
|
718
|
+
message: 'Preparing dev server',
|
|
719
|
+
callback: async () => {
|
|
720
|
+
// Typecheck (skip with --no-typecheck)
|
|
721
|
+
if (!opts.noTypecheck) {
|
|
722
|
+
const typeResult = await typecheck(rootDir);
|
|
723
|
+
if (!typeResult.success) {
|
|
724
|
+
// Non-fatal in dev: log errors and continue
|
|
725
|
+
console.log('');
|
|
726
|
+
console.log(typeResult.output);
|
|
727
|
+
console.log('');
|
|
1076
728
|
}
|
|
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
729
|
}
|
|
1101
|
-
continue;
|
|
1102
|
-
}
|
|
1103
730
|
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
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
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
process.env.AGENTUITY_SDK_DEV_MODE = 'true';
|
|
1122
|
-
process.env.AGENTUITY_RUNTIME = 'yes';
|
|
1123
|
-
process.env.AGENTUITY_ENV = 'development';
|
|
1124
|
-
process.env.NODE_ENV = 'development';
|
|
1125
|
-
process.env.AGENTUITY_PROJECT_DIR = rootDir;
|
|
1126
|
-
if (project?.region) {
|
|
1127
|
-
process.env.AGENTUITY_REGION = project.region;
|
|
731
|
+
// Generate workbench files if enabled
|
|
732
|
+
if (workbenchConfigData.enabled) {
|
|
733
|
+
const { generateWorkbenchFiles } = await import(
|
|
734
|
+
'../build/vite/workbench-generator'
|
|
735
|
+
);
|
|
736
|
+
await generateWorkbenchFiles(
|
|
737
|
+
rootDir,
|
|
738
|
+
project?.projectId ?? '',
|
|
739
|
+
workbenchConfigData,
|
|
740
|
+
logger
|
|
741
|
+
);
|
|
1128
742
|
}
|
|
1129
|
-
process.env.PORT = String(opts.port);
|
|
1130
|
-
process.env.AGENTUITY_PORT = process.env.PORT;
|
|
1131
|
-
process.env.AGENTUITY_BASE_URL =
|
|
1132
|
-
process.env.AGENTUITY_BASE_URL || `http://localhost:${opts.port}`;
|
|
1133
743
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
}
|
|
744
|
+
// Discover agents and routes in parallel
|
|
745
|
+
const srcDir = join(rootDir, 'src');
|
|
746
|
+
const { discoverAgents } = await import('../build/vite/agent-discovery');
|
|
747
|
+
const { discoverRoutes } = await import('../build/vite/route-discovery');
|
|
1137
748
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
process.env.AGENTUITY_TRANSPORT_URL = serviceUrls.catalyst;
|
|
1143
|
-
process.env.AGENTUITY_CATALYST_URL = serviceUrls.catalyst;
|
|
1144
|
-
process.env.AGENTUITY_VECTOR_URL = serviceUrls.vector;
|
|
1145
|
-
process.env.AGENTUITY_KEYVALUE_URL = serviceUrls.keyvalue;
|
|
1146
|
-
process.env.AGENTUITY_SANDBOX_URL = serviceUrls.sandbox;
|
|
1147
|
-
process.env.AGENTUITY_STREAM_URL = serviceUrls.stream;
|
|
1148
|
-
process.env.AGENTUITY_CLOUD_ORG_ID = project.orgId;
|
|
1149
|
-
process.env.AGENTUITY_CLOUD_PROJECT_ID = project.projectId;
|
|
1150
|
-
process.env.AGENTUITY_CLOUD_DEPLOYMENT_ID = deploymentId;
|
|
1151
|
-
}
|
|
749
|
+
const [agentMetadata, { routes }] = await Promise.all([
|
|
750
|
+
discoverAgents(srcDir, project?.projectId ?? '', deploymentId, logger),
|
|
751
|
+
discoverRoutes(srcDir, project?.projectId ?? '', deploymentId, logger),
|
|
752
|
+
]);
|
|
1152
753
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
}
|
|
754
|
+
// Generate metadata file
|
|
755
|
+
const { generateMetadata, writeMetadataFile } = await import(
|
|
756
|
+
'../build/vite/metadata-generator'
|
|
757
|
+
);
|
|
1158
758
|
|
|
1159
|
-
|
|
1160
|
-
process.env.VITE_PORT = String(vitePort);
|
|
759
|
+
const promises: Promise<void>[] = [];
|
|
1161
760
|
|
|
1162
|
-
|
|
761
|
+
// Generate prompt files (non-blocking)
|
|
762
|
+
promises.push(
|
|
763
|
+
import('../build/vite/prompt-generator')
|
|
764
|
+
.then(({ generatePromptFiles }) => generatePromptFiles(srcDir, logger))
|
|
765
|
+
.catch((err) =>
|
|
766
|
+
logger.warn('Failed to generate prompt files: %s', err.message)
|
|
767
|
+
)
|
|
768
|
+
);
|
|
1163
769
|
|
|
1164
|
-
|
|
1165
|
-
await startBunDevServer({
|
|
770
|
+
const metadata = await generateMetadata({
|
|
1166
771
|
rootDir,
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
orgId: project?.orgId,
|
|
772
|
+
projectId: project?.projectId ?? '',
|
|
773
|
+
orgId: project?.orgId ?? '',
|
|
1170
774
|
deploymentId,
|
|
775
|
+
agents: agentMetadata,
|
|
776
|
+
routes,
|
|
777
|
+
dev: true,
|
|
1171
778
|
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
779
|
});
|
|
1178
780
|
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
781
|
+
writeMetadataFile(rootDir, metadata, true, logger);
|
|
782
|
+
|
|
783
|
+
// Sync metadata with backend
|
|
784
|
+
if (syncService && project?.projectId) {
|
|
785
|
+
promises.push(
|
|
786
|
+
syncService.sync(metadata, previousMetadata, project.projectId, deploymentId)
|
|
787
|
+
);
|
|
788
|
+
previousMetadata = metadata;
|
|
1182
789
|
}
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
790
|
+
await Promise.all(promises);
|
|
791
|
+
},
|
|
792
|
+
clearOnSuccess: true,
|
|
793
|
+
});
|
|
1186
794
|
|
|
1187
|
-
|
|
1188
|
-
|
|
795
|
+
// ================================================================
|
|
796
|
+
// Step 2: Set environment variables
|
|
797
|
+
// ================================================================
|
|
798
|
+
|
|
799
|
+
if (!process.env.AGENTUITY_SDK_KEY) {
|
|
800
|
+
const sdkKey = await loadProjectSDKKey(logger, rootDir);
|
|
801
|
+
if (sdkKey) {
|
|
802
|
+
process.env.AGENTUITY_SDK_KEY = sdkKey;
|
|
803
|
+
} else if (project) {
|
|
804
|
+
tui.warning(
|
|
805
|
+
'AGENTUITY_SDK_KEY not found in .env file. Numerous features will be unavailable.'
|
|
806
|
+
);
|
|
807
|
+
tui.bullet(
|
|
808
|
+
`Run "${getCommand('cloud env pull')}" to sync your SDK key, or add AGENTUITY_SDK_KEY to your .env file.`
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
1189
812
|
|
|
1190
|
-
|
|
1191
|
-
|
|
813
|
+
process.env.AGENTUITY_SDK_DEV_MODE = 'true';
|
|
814
|
+
process.env.AGENTUITY_RUNTIME = 'yes';
|
|
815
|
+
process.env.AGENTUITY_ENV = 'development';
|
|
816
|
+
process.env.NODE_ENV = 'development';
|
|
817
|
+
process.env.AGENTUITY_PROJECT_DIR = rootDir;
|
|
818
|
+
if (project?.region) {
|
|
819
|
+
process.env.AGENTUITY_REGION = project.region;
|
|
820
|
+
}
|
|
821
|
+
process.env.PORT = String(bunBackendPort);
|
|
822
|
+
process.env.AGENTUITY_PORT = String(bunBackendPort);
|
|
823
|
+
process.env.AGENTUITY_BASE_URL =
|
|
824
|
+
process.env.AGENTUITY_BASE_URL || `http://localhost:${vitePort}`;
|
|
825
|
+
process.env.AGENTUITY_NO_BUNDLE = 'true';
|
|
826
|
+
|
|
827
|
+
if (opts.resume) {
|
|
828
|
+
process.env.AGENTUITY_CODER_RESUME_SESSION = opts.resume;
|
|
829
|
+
}
|
|
1192
830
|
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
831
|
+
if (project) {
|
|
832
|
+
const serviceUrls = getServiceUrls(project.region);
|
|
833
|
+
process.env.AGENTUITY_TRANSPORT_URL = serviceUrls.catalyst;
|
|
834
|
+
process.env.AGENTUITY_CATALYST_URL = serviceUrls.catalyst;
|
|
835
|
+
process.env.AGENTUITY_VECTOR_URL = serviceUrls.vector;
|
|
836
|
+
process.env.AGENTUITY_KEYVALUE_URL = serviceUrls.keyvalue;
|
|
837
|
+
process.env.AGENTUITY_SANDBOX_URL = serviceUrls.sandbox;
|
|
838
|
+
process.env.AGENTUITY_STREAM_URL = serviceUrls.stream;
|
|
839
|
+
process.env.AGENTUITY_CLOUD_ORG_ID = project.orgId;
|
|
840
|
+
process.env.AGENTUITY_CLOUD_PROJECT_ID = project.projectId;
|
|
841
|
+
process.env.AGENTUITY_CLOUD_DEPLOYMENT_ID = deploymentId;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if (devmode?.hostname) {
|
|
845
|
+
process.env.AGENTUITY_DEVMODE_URL = `https://${devmode.hostname}`;
|
|
846
|
+
} else {
|
|
847
|
+
process.env.AGENTUITY_DEVMODE_URL = `http://localhost:${vitePort}`;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// ================================================================
|
|
851
|
+
// Step 3: Start Bun backend with --hot (handles its own HMR)
|
|
852
|
+
// ================================================================
|
|
853
|
+
|
|
854
|
+
try {
|
|
855
|
+
await startBunDevServer({
|
|
856
|
+
rootDir,
|
|
857
|
+
port: bunBackendPort,
|
|
858
|
+
logger,
|
|
859
|
+
vitePort,
|
|
860
|
+
inspect: opts.inspect,
|
|
861
|
+
inspectWait: opts.inspectWait,
|
|
862
|
+
inspectBrk: opts.inspectBrk,
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
// Register Bun subprocess with process manager
|
|
866
|
+
// The subprocess is stored in globalThis.__AGENTUITY_BUN_SUBPROCESS__
|
|
867
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
868
|
+
const bunSubprocess = (globalThis as any).__AGENTUITY_BUN_SUBPROCESS__ as ProcessLike;
|
|
869
|
+
if (bunSubprocess) {
|
|
870
|
+
procManager.registerProcess({
|
|
871
|
+
id: 'bun-backend',
|
|
872
|
+
process: bunSubprocess,
|
|
873
|
+
description: 'Bun backend server (--hot)',
|
|
874
|
+
port: bunBackendPort,
|
|
875
|
+
critical: true,
|
|
1201
876
|
});
|
|
1202
|
-
if (shutdownRequested) {
|
|
1203
|
-
break;
|
|
1204
|
-
}
|
|
1205
|
-
continue;
|
|
1206
877
|
}
|
|
878
|
+
} catch (error) {
|
|
879
|
+
tui.error(`Failed to start Bun backend server: ${error}`);
|
|
880
|
+
await cleanup(true, 1, true);
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// ================================================================
|
|
885
|
+
// Step 4: Start gravity tunnel (if public URL enabled)
|
|
886
|
+
// ================================================================
|
|
1207
887
|
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
888
|
+
if (gravityBin && gravityURL && devmode && project) {
|
|
889
|
+
const privateKeyPEM = devmode.privateKey ?? savedPrivateKey;
|
|
890
|
+
if (!privateKeyPEM) {
|
|
891
|
+
tui.error(
|
|
892
|
+
'No private key available for gravity connection. Please re-run to generate a new key.'
|
|
893
|
+
);
|
|
894
|
+
await cleanup(true, 1, true);
|
|
895
|
+
return;
|
|
1211
896
|
}
|
|
1212
897
|
|
|
1213
898
|
try {
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
logger.trace(
|
|
1217
|
-
'Starting gravity client: %s (cwd: %s, id: %s)',
|
|
899
|
+
gravityProcess = Bun.spawn(
|
|
900
|
+
[
|
|
1218
901
|
gravityBin,
|
|
1219
|
-
|
|
1220
|
-
devmode.id
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
902
|
+
'--endpoint-id',
|
|
903
|
+
devmode.id,
|
|
904
|
+
'--port',
|
|
905
|
+
vitePort.toString(),
|
|
906
|
+
'--url',
|
|
907
|
+
gravityURL,
|
|
908
|
+
'--log-level',
|
|
909
|
+
process.env.AGENTUITY_GRAVITY_LOG_LEVEL ?? 'error',
|
|
910
|
+
'--org-id',
|
|
911
|
+
project.orgId,
|
|
912
|
+
'--project-id',
|
|
913
|
+
project.projectId,
|
|
914
|
+
'--private-key',
|
|
915
|
+
Buffer.from(privateKeyPEM).toString('base64'),
|
|
916
|
+
'--health-check',
|
|
917
|
+
],
|
|
918
|
+
{
|
|
919
|
+
cwd: rootDir,
|
|
920
|
+
stdout: 'pipe',
|
|
921
|
+
stderr: 'pipe',
|
|
922
|
+
detached: false,
|
|
1227
923
|
}
|
|
1228
|
-
|
|
1229
|
-
[
|
|
1230
|
-
gravityBin,
|
|
1231
|
-
'--endpoint-id',
|
|
1232
|
-
devmode.id,
|
|
1233
|
-
'--port',
|
|
1234
|
-
opts.port.toString(),
|
|
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
|
-
);
|
|
924
|
+
);
|
|
1254
925
|
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
});
|
|
1263
|
-
}
|
|
926
|
+
const gravityPid = (gravityProcess as { pid?: number }).pid;
|
|
927
|
+
if (gravityPid) {
|
|
928
|
+
await devLock.registerChild({
|
|
929
|
+
pid: gravityPid,
|
|
930
|
+
type: 'gravity',
|
|
931
|
+
description: 'Gravity public URL tunnel',
|
|
932
|
+
});
|
|
1264
933
|
|
|
1265
|
-
//
|
|
1266
|
-
(
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
gravityHeartbeatInterval = setInterval(sendHeartbeat, 5000);
|
|
1301
|
-
}
|
|
1302
|
-
} else if (trimmed) {
|
|
1303
|
-
logger.debug('[gravity] %s', trimmed);
|
|
934
|
+
// Register with process manager
|
|
935
|
+
procManager.registerProcess({
|
|
936
|
+
id: 'gravity',
|
|
937
|
+
process: gravityProcess,
|
|
938
|
+
description: 'Gravity public URL tunnel',
|
|
939
|
+
critical: false,
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// Log gravity output and detect heartbeat port
|
|
944
|
+
(async () => {
|
|
945
|
+
try {
|
|
946
|
+
if (gravityProcess?.stdout) {
|
|
947
|
+
for await (const chunk of gravityProcess.stdout) {
|
|
948
|
+
const text = new TextDecoder().decode(chunk);
|
|
949
|
+
const trimmed = text.trim();
|
|
950
|
+
|
|
951
|
+
const match = trimmed.match(/^HEARTBEAT_PORT=(\d+)$/m);
|
|
952
|
+
if (match?.[1]) {
|
|
953
|
+
const heartbeatPort = parseInt(match[1], 10);
|
|
954
|
+
logger.debug('Gravity heartbeat port: %d', heartbeatPort);
|
|
955
|
+
|
|
956
|
+
if (!gravityHeartbeatInterval) {
|
|
957
|
+
const sendHeartbeat = async () => {
|
|
958
|
+
try {
|
|
959
|
+
await fetch(`http://127.0.0.1:${heartbeatPort}/heartbeat`, {
|
|
960
|
+
method: 'POST',
|
|
961
|
+
signal: AbortSignal.timeout(2000),
|
|
962
|
+
});
|
|
963
|
+
} catch {
|
|
964
|
+
// Ignore heartbeat failures
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
sendHeartbeat();
|
|
968
|
+
gravityHeartbeatInterval = setInterval(sendHeartbeat, 5000);
|
|
1304
969
|
}
|
|
970
|
+
} else if (trimmed) {
|
|
971
|
+
logger.debug('[gravity] %s', trimmed);
|
|
1305
972
|
}
|
|
1306
973
|
}
|
|
1307
|
-
} catch (err) {
|
|
1308
|
-
logger.error('Error reading gravity stdout: %s', err);
|
|
1309
974
|
}
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
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');
|
|
1326
|
-
}
|
|
975
|
+
} catch (err) {
|
|
976
|
+
logger.error('Error reading gravity stdout: %s', err);
|
|
977
|
+
}
|
|
978
|
+
})();
|
|
1327
979
|
|
|
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;
|
|
980
|
+
(async () => {
|
|
981
|
+
try {
|
|
982
|
+
if (gravityProcess?.stderr) {
|
|
983
|
+
for await (const chunk of gravityProcess.stderr) {
|
|
984
|
+
logger.warn('[gravity] %s', new TextDecoder().decode(chunk).trim());
|
|
1357
985
|
}
|
|
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
986
|
}
|
|
987
|
+
} catch (err) {
|
|
988
|
+
logger.error('Error reading gravity stderr: %s', err);
|
|
989
|
+
}
|
|
990
|
+
})();
|
|
991
|
+
} catch (error) {
|
|
992
|
+
tui.error(`Failed to start gravity tunnel: ${error}`);
|
|
993
|
+
await cleanup(true, 1, true);
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
1366
997
|
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
}
|
|
1384
|
-
};
|
|
1385
|
-
process.stdin.on('data', stdinDataHandler);
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
showWelcome();
|
|
1389
|
-
|
|
1390
|
-
// Start/resume file watcher now that server is ready
|
|
1391
|
-
fileWatcher.resume();
|
|
1392
|
-
|
|
1393
|
-
// Wait for restart signal or shutdown
|
|
1394
|
-
await new Promise<void>((resolve) => {
|
|
1395
|
-
const checkRestart = setInterval(() => {
|
|
1396
|
-
if (shouldRestart || shutdownRequested) {
|
|
1397
|
-
clearInterval(checkRestart);
|
|
1398
|
-
resolve();
|
|
1399
|
-
}
|
|
1400
|
-
}, 100);
|
|
1401
|
-
});
|
|
998
|
+
// ================================================================
|
|
999
|
+
// Step 5: Keyboard shortcuts + wait for shutdown
|
|
1000
|
+
// ================================================================
|
|
1001
|
+
|
|
1002
|
+
if (interactive && process.stdin.isTTY && process.stdout.isTTY) {
|
|
1003
|
+
stdinListenerRegistered = true;
|
|
1004
|
+
process.stdin.setRawMode(true);
|
|
1005
|
+
process.stdin.resume();
|
|
1006
|
+
process.stdin.setEncoding('utf8');
|
|
1007
|
+
|
|
1008
|
+
const showHelp = () => {
|
|
1009
|
+
console.log('\n' + tui.bold('Keyboard Shortcuts:'));
|
|
1010
|
+
console.log(tui.muted(' h') + ' - show this help');
|
|
1011
|
+
console.log(tui.muted(' c') + ' - clear console');
|
|
1012
|
+
console.log(tui.muted(' q') + ' - quit\n');
|
|
1013
|
+
};
|
|
1402
1014
|
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1015
|
+
stdinDataHandler = (data) => {
|
|
1016
|
+
const key = data.toString();
|
|
1017
|
+
if (key === '\u0003' || key === 'q') {
|
|
1018
|
+
if (stdinDataHandler) {
|
|
1019
|
+
process.stdin.removeListener('data', stdinDataHandler);
|
|
1020
|
+
stdinDataHandler = null;
|
|
1021
|
+
}
|
|
1022
|
+
shutdownRequested = true;
|
|
1023
|
+
cleanup(true, 0).catch(() => originalExit(1));
|
|
1024
|
+
return;
|
|
1406
1025
|
}
|
|
1026
|
+
switch (key) {
|
|
1027
|
+
case 'h':
|
|
1028
|
+
showHelp();
|
|
1029
|
+
break;
|
|
1030
|
+
case 'c':
|
|
1031
|
+
console.clear();
|
|
1032
|
+
tui.banner('⨺ Agentuity DevMode', devmodebody, {
|
|
1033
|
+
padding: 2,
|
|
1034
|
+
topSpacer: false,
|
|
1035
|
+
bottomSpacer: false,
|
|
1036
|
+
centerTitle: false,
|
|
1037
|
+
});
|
|
1038
|
+
break;
|
|
1039
|
+
default:
|
|
1040
|
+
process.stdout.write(data);
|
|
1041
|
+
break;
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
process.stdin.on('data', stdinDataHandler);
|
|
1045
|
+
}
|
|
1407
1046
|
|
|
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();
|
|
1047
|
+
logger.info('DevMode ready 🚀');
|
|
1422
1048
|
|
|
1423
|
-
|
|
1049
|
+
// Block until shutdown — bun --hot handles backend HMR,
|
|
1050
|
+
// Vite handles frontend HMR. Nothing to restart.
|
|
1051
|
+
await new Promise<void>((resolve) => {
|
|
1052
|
+
const check = setInterval(() => {
|
|
1424
1053
|
if (shutdownRequested) {
|
|
1425
|
-
|
|
1054
|
+
clearInterval(check);
|
|
1055
|
+
resolve();
|
|
1426
1056
|
}
|
|
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
|
-
}
|
|
1057
|
+
}, 200);
|
|
1058
|
+
});
|
|
1442
1059
|
} finally {
|
|
1443
1060
|
/* brute force clean up */
|
|
1444
1061
|
await devLock.release();
|