rivet-design 0.10.8 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +47 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +277 -123
- package/dist/index.js.map +1 -1
- package/dist/mcp/agent-variants/SessionStore.d.ts +28 -4
- package/dist/mcp/agent-variants/SessionStore.d.ts.map +1 -1
- package/dist/mcp/agent-variants/SessionStore.js +356 -123
- package/dist/mcp/agent-variants/SessionStore.js.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts +84 -4
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js +744 -145
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.d.ts +8 -3
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.d.ts.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js +8 -3
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js.map +1 -1
- package/dist/mcp/agent-variants/contracts.d.ts +7984 -1625
- package/dist/mcp/agent-variants/contracts.d.ts.map +1 -1
- package/dist/mcp/agent-variants/contracts.js +312 -154
- package/dist/mcp/agent-variants/contracts.js.map +1 -1
- package/dist/mcp/agent-variants/createZeroToOneTool.d.ts +2 -3
- package/dist/mcp/agent-variants/createZeroToOneTool.d.ts.map +1 -1
- package/dist/mcp/agent-variants/createZeroToOneTool.js +49 -39
- package/dist/mcp/agent-variants/createZeroToOneTool.js.map +1 -1
- package/dist/mcp/agent-variants/designCritique.d.ts +167 -0
- package/dist/mcp/agent-variants/designCritique.d.ts.map +1 -0
- package/dist/mcp/agent-variants/designCritique.js +717 -0
- package/dist/mcp/agent-variants/designCritique.js.map +1 -0
- package/dist/mcp/agent-variants/diffQa.d.ts +7 -0
- package/dist/mcp/agent-variants/diffQa.d.ts.map +1 -0
- package/dist/mcp/agent-variants/diffQa.js +67 -0
- package/dist/mcp/agent-variants/diffQa.js.map +1 -0
- package/dist/mcp/agent-variants/index.d.ts +3 -3
- package/dist/mcp/agent-variants/index.d.ts.map +1 -1
- package/dist/mcp/agent-variants/index.js +2 -1
- package/dist/mcp/agent-variants/index.js.map +1 -1
- package/dist/mcp/agent-variants/pinterestSourceContext.d.ts +4 -2
- package/dist/mcp/agent-variants/pinterestSourceContext.d.ts.map +1 -1
- package/dist/mcp/agent-variants/pinterestSourceContext.js +7 -6
- package/dist/mcp/agent-variants/pinterestSourceContext.js.map +1 -1
- package/dist/mcp/agent-variants/previewQa.d.ts +6 -4
- package/dist/mcp/agent-variants/previewQa.d.ts.map +1 -1
- package/dist/mcp/agent-variants/previewQa.js +140 -13
- package/dist/mcp/agent-variants/previewQa.js.map +1 -1
- package/dist/mcp/agent-variants/sourceContext.d.ts +20 -5
- package/dist/mcp/agent-variants/sourceContext.d.ts.map +1 -1
- package/dist/mcp/agent-variants/sourceContext.js +99 -115
- package/dist/mcp/agent-variants/sourceContext.js.map +1 -1
- package/dist/mcp/agent-variants/tools.d.ts +7 -0
- package/dist/mcp/agent-variants/tools.d.ts.map +1 -1
- package/dist/mcp/agent-variants/tools.js +216 -15
- package/dist/mcp/agent-variants/tools.js.map +1 -1
- package/dist/mcp/agent-variants/variantContext.d.ts +19 -0
- package/dist/mcp/agent-variants/variantContext.d.ts.map +1 -0
- package/dist/mcp/agent-variants/variantContext.js +355 -0
- package/dist/mcp/agent-variants/variantContext.js.map +1 -0
- package/dist/mcp/auth/httpOAuthProvider.d.ts +103 -0
- package/dist/mcp/auth/httpOAuthProvider.d.ts.map +1 -0
- package/dist/mcp/auth/httpOAuthProvider.js +454 -0
- package/dist/mcp/auth/httpOAuthProvider.js.map +1 -0
- package/dist/mcp/auth/tools.d.ts +2 -0
- package/dist/mcp/auth/tools.d.ts.map +1 -1
- package/dist/mcp/auth/tools.js +12 -5
- package/dist/mcp/auth/tools.js.map +1 -1
- package/dist/mcp/httpServer.d.ts +36 -0
- package/dist/mcp/httpServer.d.ts.map +1 -0
- package/dist/mcp/httpServer.js +307 -0
- package/dist/mcp/httpServer.js.map +1 -0
- package/dist/mcp/server.d.ts +17 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +41 -19
- package/dist/mcp/server.js.map +1 -1
- package/dist/proxy-middleware/proxy-config.d.ts.map +1 -1
- package/dist/proxy-middleware/proxy-config.js +5 -2
- package/dist/proxy-middleware/proxy-config.js.map +1 -1
- package/dist/routes/agentVariants.d.ts.map +1 -1
- package/dist/routes/agentVariants.js +6 -4
- package/dist/routes/agentVariants.js.map +1 -1
- package/dist/routes/mcp.d.ts.map +1 -1
- package/dist/routes/mcp.js +2 -1
- package/dist/routes/mcp.js.map +1 -1
- package/dist/server.d.ts +9 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +13 -5
- package/dist/server.js.map +1 -1
- package/dist/services/AuthService.d.ts +1 -0
- package/dist/services/AuthService.d.ts.map +1 -1
- package/dist/services/AuthService.js +11 -1
- package/dist/services/AuthService.js.map +1 -1
- package/dist/services/BrowserAgentClient.d.ts +54 -0
- package/dist/services/BrowserAgentClient.d.ts.map +1 -0
- package/dist/services/BrowserAgentClient.js +126 -0
- package/dist/services/BrowserAgentClient.js.map +1 -0
- package/dist/services/ConfigManager.d.ts +5 -0
- package/dist/services/ConfigManager.d.ts.map +1 -1
- package/dist/services/ConfigManager.js +25 -3
- package/dist/services/ConfigManager.js.map +1 -1
- package/dist/services/DevServerRuntimeService.d.ts +119 -0
- package/dist/services/DevServerRuntimeService.d.ts.map +1 -0
- package/dist/services/DevServerRuntimeService.js +657 -0
- package/dist/services/DevServerRuntimeService.js.map +1 -0
- package/dist/services/GatewayClient.d.ts +25 -0
- package/dist/services/GatewayClient.d.ts.map +1 -1
- package/dist/services/GatewayClient.js +70 -11
- package/dist/services/GatewayClient.js.map +1 -1
- package/dist/services/InlineVariantGenerationService.d.ts +2 -0
- package/dist/services/InlineVariantGenerationService.d.ts.map +1 -1
- package/dist/services/InlineVariantGenerationService.js +70 -3
- package/dist/services/InlineVariantGenerationService.js.map +1 -1
- package/dist/services/RequestAuthContext.d.ts +7 -1
- package/dist/services/RequestAuthContext.d.ts.map +1 -1
- package/dist/services/RequestAuthContext.js +15 -2
- package/dist/services/RequestAuthContext.js.map +1 -1
- package/dist/services/SessionBridgeService.d.ts +1 -0
- package/dist/services/SessionBridgeService.d.ts.map +1 -1
- package/dist/services/SessionBridgeService.js +16 -1
- package/dist/services/SessionBridgeService.js.map +1 -1
- package/dist/services/VariantRunService.d.ts +1 -0
- package/dist/services/VariantRunService.d.ts.map +1 -1
- package/dist/services/VariantRunService.js +1 -0
- package/dist/services/VariantRunService.js.map +1 -1
- package/dist/services/VariantsRuntime.d.ts.map +1 -1
- package/dist/services/VariantsRuntime.js +1 -0
- package/dist/services/VariantsRuntime.js.map +1 -1
- package/dist/services/WorktreeManager.d.ts +1 -8
- package/dist/services/WorktreeManager.d.ts.map +1 -1
- package/dist/services/WorktreeManager.js +1 -28
- package/dist/services/WorktreeManager.js.map +1 -1
- package/dist/services/createAgentVariantsOrchestrator.d.ts.map +1 -1
- package/dist/services/createAgentVariantsOrchestrator.js +7 -0
- package/dist/services/createAgentVariantsOrchestrator.js.map +1 -1
- package/dist/utils/skills/describe-motion-protocol.d.ts +2 -3
- package/dist/utils/skills/describe-motion-protocol.d.ts.map +1 -1
- package/dist/utils/skills/describe-motion-protocol.js +50 -35
- package/dist/utils/skills/describe-motion-protocol.js.map +1 -1
- package/dist/utils/skills/shared-variants-protocol.d.ts +1 -1
- package/dist/utils/skills/shared-variants-protocol.d.ts.map +1 -1
- package/dist/utils/skills/shared-variants-protocol.js +21 -15
- package/dist/utils/skills/shared-variants-protocol.js.map +1 -1
- package/dist/utils/variantSessionStart.d.ts +3 -0
- package/dist/utils/variantSessionStart.d.ts.map +1 -0
- package/dist/utils/variantSessionStart.js +7 -0
- package/dist/utils/variantSessionStart.js.map +1 -0
- package/package.json +2 -1
- package/src/ui/dist/assets/{main-WqlDU4Ou.js → main-Cw6Pd8ye.js} +204 -204
- package/src/ui/dist/assets/main-DkCj7b2K.css +1 -0
- package/src/ui/dist/index.html +2 -2
- package/dist/mcp/agent-variants/designContextStore.d.ts +0 -160
- package/dist/mcp/agent-variants/designContextStore.d.ts.map +0 -1
- package/dist/mcp/agent-variants/designContextStore.js +0 -295
- package/dist/mcp/agent-variants/designContextStore.js.map +0 -1
- package/dist/mcp/agent-variants/inspirationDesignContext.d.ts +0 -440
- package/dist/mcp/agent-variants/inspirationDesignContext.d.ts.map +0 -1
- package/dist/mcp/agent-variants/inspirationDesignContext.js +0 -2467
- package/dist/mcp/agent-variants/inspirationDesignContext.js.map +0 -1
- package/src/ui/dist/assets/main-auZA25j4.css +0 -1
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createDevServerRuntimeService = exports.DevServerRuntimeService = exports.parsePortFromDevServerOutput = void 0;
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const net_1 = __importDefault(require("net"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const util_1 = require("util");
|
|
12
|
+
const logger_1 = require("../utils/logger");
|
|
13
|
+
const devServerCommand_1 = require("../utils/devServerCommand");
|
|
14
|
+
const log = (0, logger_1.createLogger)('DevServerRuntimeService');
|
|
15
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
16
|
+
const COMMON_DEV_SERVER_PORTS = [3000, 3001, 3002, 5173, 5174, 5175, 8080, 4200];
|
|
17
|
+
const DEFAULT_START_TIMEOUT_MS = 30_000;
|
|
18
|
+
const DEFAULT_PORT_CHECK_INTERVAL_MS = 500;
|
|
19
|
+
const DEFAULT_STABILITY_ATTEMPTS = 4;
|
|
20
|
+
const DEFAULT_STABILITY_INTERVAL_MS = 500;
|
|
21
|
+
const STARTUP_ERROR_OUTPUT_TAIL_CHARS = 300;
|
|
22
|
+
const MAX_EXCLUSIVE_VALID_TCP_PORT = 65_536;
|
|
23
|
+
const MIN_VALID_TCP_PORT = 1;
|
|
24
|
+
const STARTUP_SCRIPT_CANDIDATES = ['dev', 'start', 'serve'];
|
|
25
|
+
const SAFE_SCRIPT_NAME_PATTERN = '[A-Za-z0-9:_-]+';
|
|
26
|
+
const SAFE_START_COMMAND_PATTERN = new RegExp(`^(?:npm run ${SAFE_SCRIPT_NAME_PATTERN}(?: -- --port \\d+)?|yarn ${SAFE_SCRIPT_NAME_PATTERN}(?: --port \\d+)?|pnpm (?:run )?${SAFE_SCRIPT_NAME_PATTERN}(?: --port \\d+)?)$`);
|
|
27
|
+
const SAFE_INSTALL_COMMAND_PATTERN = /^(?:npm install|yarn install|pnpm install)$/;
|
|
28
|
+
const PORT_PLACEHOLDER_PATTERN = /\$\{?RIVET_PORT\}?|__RIVET_PORT__|\{\{RIVET_PORT\}\}/;
|
|
29
|
+
const PORT_PLACEHOLDER_REPLACE_PATTERN = /\$\{?RIVET_PORT\}?|__RIVET_PORT__|\{\{RIVET_PORT\}\}/g;
|
|
30
|
+
const DEV_SERVER_ENV_ALLOWLIST = [
|
|
31
|
+
'APPDATA',
|
|
32
|
+
'CI',
|
|
33
|
+
'HOME',
|
|
34
|
+
'LANG',
|
|
35
|
+
'LC_ALL',
|
|
36
|
+
'LOCALAPPDATA',
|
|
37
|
+
'LOGNAME',
|
|
38
|
+
'PATH',
|
|
39
|
+
'SHELL',
|
|
40
|
+
'SystemRoot',
|
|
41
|
+
'TEMP',
|
|
42
|
+
'TMP',
|
|
43
|
+
'TMPDIR',
|
|
44
|
+
'USER',
|
|
45
|
+
'USERPROFILE',
|
|
46
|
+
];
|
|
47
|
+
/**
|
|
48
|
+
* Extracts the last valid localhost/dev-server port from process output.
|
|
49
|
+
*/
|
|
50
|
+
const parsePortFromDevServerOutput = (text) => {
|
|
51
|
+
const matches = [];
|
|
52
|
+
const patterns = [
|
|
53
|
+
/Local:\s*https?:\/\/[^\s]*:(\d+)/gi,
|
|
54
|
+
/(?:localhost|127\.0\.0\.1):(\d+)/gi,
|
|
55
|
+
/(?:on port|listening on port)\s+(\d+)/gi,
|
|
56
|
+
];
|
|
57
|
+
for (const pattern of patterns) {
|
|
58
|
+
for (const match of text.matchAll(pattern)) {
|
|
59
|
+
const port = Number(match[1]);
|
|
60
|
+
if (Number.isInteger(port) &&
|
|
61
|
+
port >= MIN_VALID_TCP_PORT &&
|
|
62
|
+
port < MAX_EXCLUSIVE_VALID_TCP_PORT) {
|
|
63
|
+
matches.push({ index: match.index ?? 0, port });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
matches.sort((a, b) => a.index - b.index);
|
|
68
|
+
return matches[matches.length - 1]?.port ?? null;
|
|
69
|
+
};
|
|
70
|
+
exports.parsePortFromDevServerOutput = parsePortFromDevServerOutput;
|
|
71
|
+
const inferMissingToolsFromError = (text) => {
|
|
72
|
+
const lower = text.toLowerCase();
|
|
73
|
+
const missingTools = [];
|
|
74
|
+
if (lower.includes('yarn: command not found') || lower.includes('missing yarn')) {
|
|
75
|
+
missingTools.push('yarn');
|
|
76
|
+
}
|
|
77
|
+
if (lower.includes('node: command not found') || lower.includes('missing node')) {
|
|
78
|
+
missingTools.push('node');
|
|
79
|
+
}
|
|
80
|
+
if (lower.includes('git: command not found') || lower.includes('missing git')) {
|
|
81
|
+
missingTools.push('git');
|
|
82
|
+
}
|
|
83
|
+
return missingTools;
|
|
84
|
+
};
|
|
85
|
+
const defaultIsPortResponding = (port) => new Promise((resolve) => {
|
|
86
|
+
const socket = net_1.default.connect(port, 'localhost');
|
|
87
|
+
socket.on('connect', () => {
|
|
88
|
+
socket.destroy();
|
|
89
|
+
resolve(true);
|
|
90
|
+
});
|
|
91
|
+
socket.on('error', () => {
|
|
92
|
+
socket.destroy();
|
|
93
|
+
resolve(false);
|
|
94
|
+
});
|
|
95
|
+
socket.setTimeout(DEFAULT_PORT_CHECK_INTERVAL_MS);
|
|
96
|
+
socket.on('timeout', () => {
|
|
97
|
+
socket.destroy();
|
|
98
|
+
resolve(false);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
const defaultFindAvailablePort = async (startPort) => {
|
|
102
|
+
for (let candidate = startPort; candidate < MAX_EXCLUSIVE_VALID_TCP_PORT; candidate++) {
|
|
103
|
+
if (!(await defaultIsPortResponding(candidate))) {
|
|
104
|
+
return candidate;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
throw new Error(`No available port found from ${startPort}`);
|
|
108
|
+
};
|
|
109
|
+
const defaultDependenciesInstalled = async (projectPath) => fs_1.default.existsSync(path_1.default.join(projectPath, 'node_modules')) ||
|
|
110
|
+
fs_1.default.existsSync(path_1.default.join(projectPath, '.pnp.cjs')) ||
|
|
111
|
+
fs_1.default.existsSync(path_1.default.join(projectPath, '.pnp.loader.mjs')) ||
|
|
112
|
+
fs_1.default.existsSync(path_1.default.join(projectPath, '.yarn/install-state.gz'));
|
|
113
|
+
/**
|
|
114
|
+
* Discovers a project startup command from standard package scripts.
|
|
115
|
+
*/
|
|
116
|
+
const discoverStartupCommandFromProjectScripts = async (options) => {
|
|
117
|
+
const packageJsonPath = path_1.default.join(options.projectPath, 'package.json');
|
|
118
|
+
try {
|
|
119
|
+
const parsed = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf8'));
|
|
120
|
+
const scriptName = STARTUP_SCRIPT_CANDIDATES.find((candidate) => typeof parsed.scripts?.[candidate] === 'string');
|
|
121
|
+
if (!scriptName) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const startCommand = options.packageManager === 'npm'
|
|
125
|
+
? `npm run ${scriptName}`
|
|
126
|
+
: `${options.packageManager} ${scriptName}`;
|
|
127
|
+
return {
|
|
128
|
+
startCommand,
|
|
129
|
+
installCommand: buildInstallCommand(options.packageManager),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const isToolAvailableOnPath = async (tool) => {
|
|
137
|
+
try {
|
|
138
|
+
await execAsync(`${tool} --version`);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const normalizeProjectPath = (projectPath) => {
|
|
146
|
+
const resolved = path_1.default.resolve(projectPath);
|
|
147
|
+
try {
|
|
148
|
+
return fs_1.default.realpathSync(resolved);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return resolved;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const isSafeCachedStartupCommand = (entry) => {
|
|
155
|
+
if (entry.lastFailureAt) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
if (!SAFE_START_COMMAND_PATTERN.test(entry.startCommand.trim())) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
if (!entry.installCommand) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
return SAFE_INSTALL_COMMAND_PATTERN.test(entry.installCommand.trim());
|
|
165
|
+
};
|
|
166
|
+
const buildDevServerProcessEnv = (extraEnv = {}) => {
|
|
167
|
+
const env = {};
|
|
168
|
+
for (const key of DEV_SERVER_ENV_ALLOWLIST) {
|
|
169
|
+
const value = process.env[key];
|
|
170
|
+
if (value !== undefined) {
|
|
171
|
+
env[key] = value;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return { ...env, ...extraEnv };
|
|
175
|
+
};
|
|
176
|
+
class ProjectStartupCommandCache {
|
|
177
|
+
/**
|
|
178
|
+
* Reads a project-local command cache without mutating the project.
|
|
179
|
+
*/
|
|
180
|
+
async read(projectPath) {
|
|
181
|
+
const cachePath = this.resolveCachePath(projectPath);
|
|
182
|
+
try {
|
|
183
|
+
if (!fs_1.default.existsSync(cachePath)) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
const parsed = JSON.parse(fs_1.default.readFileSync(cachePath, 'utf8'));
|
|
187
|
+
if (typeof parsed.startCommand !== 'string') {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
const entry = {
|
|
191
|
+
startCommand: parsed.startCommand,
|
|
192
|
+
installCommand: typeof parsed.installCommand === 'string'
|
|
193
|
+
? parsed.installCommand
|
|
194
|
+
: undefined,
|
|
195
|
+
lastFailureAt: typeof parsed.lastFailureAt === 'string'
|
|
196
|
+
? parsed.lastFailureAt
|
|
197
|
+
: undefined,
|
|
198
|
+
};
|
|
199
|
+
if (!isSafeCachedStartupCommand(entry)) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
return entry;
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
log.debug(`Failed to read startup command cache: ${error instanceof Error ? error.message : String(error)}`);
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Writes a successful startup command into the project-local Rivet cache.
|
|
211
|
+
*/
|
|
212
|
+
async write(projectPath, entry) {
|
|
213
|
+
const cachePath = this.resolveCachePath(projectPath);
|
|
214
|
+
try {
|
|
215
|
+
fs_1.default.mkdirSync(path_1.default.dirname(cachePath), { recursive: true, mode: 0o700 });
|
|
216
|
+
fs_1.default.writeFileSync(cachePath, JSON.stringify({
|
|
217
|
+
...entry,
|
|
218
|
+
lastUsedAt: new Date().toISOString(),
|
|
219
|
+
}, null, 2), { mode: 0o600 });
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
log.debug(`Failed to write startup command cache: ${error instanceof Error ? error.message : String(error)}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Records cache failures best-effort so repeated bad cached commands can be diagnosed.
|
|
227
|
+
*/
|
|
228
|
+
async markFailed(projectPath, reason) {
|
|
229
|
+
const cachePath = this.resolveCachePath(projectPath);
|
|
230
|
+
try {
|
|
231
|
+
const existing = (await this.read(projectPath)) ?? { startCommand: '' };
|
|
232
|
+
fs_1.default.mkdirSync(path_1.default.dirname(cachePath), { recursive: true, mode: 0o700 });
|
|
233
|
+
fs_1.default.writeFileSync(cachePath, JSON.stringify({
|
|
234
|
+
...existing,
|
|
235
|
+
lastFailureAt: new Date().toISOString(),
|
|
236
|
+
lastFailureReason: reason,
|
|
237
|
+
}, null, 2), { mode: 0o600 });
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// Best-effort diagnostic cache only.
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
resolveCachePath(projectPath) {
|
|
244
|
+
return path_1.default.join(projectPath, '.rivet', 'agent-dev-server-cache.json');
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const defaultRuntimeDeps = () => ({
|
|
248
|
+
spawn: child_process_1.spawn,
|
|
249
|
+
exec: execAsync,
|
|
250
|
+
isPortResponding: defaultIsPortResponding,
|
|
251
|
+
findAvailablePort: defaultFindAvailablePort,
|
|
252
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
253
|
+
platform: process.platform,
|
|
254
|
+
shell: process.env.SHELL || '/bin/zsh',
|
|
255
|
+
cache: new ProjectStartupCommandCache(),
|
|
256
|
+
isToolAvailable: isToolAvailableOnPath,
|
|
257
|
+
dependenciesInstalled: defaultDependenciesInstalled,
|
|
258
|
+
discoverStartupCommand: discoverStartupCommandFromProjectScripts,
|
|
259
|
+
});
|
|
260
|
+
const splitCommand = (command) => {
|
|
261
|
+
const [cmd, ...args] = command.trim().split(/\s+/);
|
|
262
|
+
return { cmd, args };
|
|
263
|
+
};
|
|
264
|
+
const buildInstallCommand = (packageManager) => {
|
|
265
|
+
if (packageManager === 'yarn') {
|
|
266
|
+
return 'yarn install';
|
|
267
|
+
}
|
|
268
|
+
if (packageManager === 'pnpm') {
|
|
269
|
+
return 'pnpm install';
|
|
270
|
+
}
|
|
271
|
+
return 'npm install';
|
|
272
|
+
};
|
|
273
|
+
const bindPortToStartCommand = (options) => {
|
|
274
|
+
const { framework, selectedPort } = options;
|
|
275
|
+
const trimmedCommand = options.startCommand.trim();
|
|
276
|
+
if (!trimmedCommand || framework === 'static') {
|
|
277
|
+
return { command: trimmedCommand, mode: 'none' };
|
|
278
|
+
}
|
|
279
|
+
if (PORT_PLACEHOLDER_PATTERN.test(trimmedCommand)) {
|
|
280
|
+
return {
|
|
281
|
+
command: trimmedCommand.replace(PORT_PLACEHOLDER_REPLACE_PATTERN, String(selectedPort)),
|
|
282
|
+
env: { PORT: String(selectedPort) },
|
|
283
|
+
mode: 'placeholder',
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
const withoutExplicitPort = trimmedCommand
|
|
287
|
+
.replace(/\s--\s--port\s+\d+\b/g, '')
|
|
288
|
+
.replace(/\s--\s-p\s+\d+\b/g, '')
|
|
289
|
+
.replace(/\s--port\s+\d+\b/g, '')
|
|
290
|
+
.replace(/\s--port=\d+\b/g, '')
|
|
291
|
+
.replace(/\s-p\s+\d+\b/g, '')
|
|
292
|
+
.replace(/\s-p=\d+\b/g, '')
|
|
293
|
+
.replace(/\s-p\d+\b/g, '')
|
|
294
|
+
.trim();
|
|
295
|
+
if (framework === 'cra') {
|
|
296
|
+
return {
|
|
297
|
+
command: withoutExplicitPort.replace(/^PORT=\d+\s+/, ''),
|
|
298
|
+
env: { PORT: String(selectedPort) },
|
|
299
|
+
mode: 'cra_env',
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
if (/^npm\s+run\s+\S+/.test(withoutExplicitPort)) {
|
|
303
|
+
return {
|
|
304
|
+
command: `${withoutExplicitPort} -- --port ${selectedPort}`,
|
|
305
|
+
env: { PORT: String(selectedPort) },
|
|
306
|
+
mode: 'npm_flag',
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
if (/^yarn\s+\S+/.test(withoutExplicitPort)) {
|
|
310
|
+
return {
|
|
311
|
+
command: `${withoutExplicitPort} --port ${selectedPort}`,
|
|
312
|
+
env: { PORT: String(selectedPort) },
|
|
313
|
+
mode: 'yarn_flag',
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
if (/^pnpm\s+(run\s+)?\S+/.test(withoutExplicitPort)) {
|
|
317
|
+
return {
|
|
318
|
+
command: `${withoutExplicitPort} --port ${selectedPort}`,
|
|
319
|
+
env: { PORT: String(selectedPort) },
|
|
320
|
+
mode: 'pnpm_flag',
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
command: `${withoutExplicitPort} --port ${selectedPort}`,
|
|
325
|
+
env: { PORT: String(selectedPort) },
|
|
326
|
+
mode: 'fallback_flag',
|
|
327
|
+
};
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* Starts and owns a target project's dev server for local agent mode.
|
|
331
|
+
*/
|
|
332
|
+
class DevServerRuntimeService {
|
|
333
|
+
deps;
|
|
334
|
+
process = null;
|
|
335
|
+
port = null;
|
|
336
|
+
projectPath = null;
|
|
337
|
+
constructor(deps = defaultRuntimeDeps()) {
|
|
338
|
+
this.deps = deps;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Resolves the startup command using cache, optional discovery, then framework fallback.
|
|
342
|
+
*/
|
|
343
|
+
async resolveStartupCommand(options) {
|
|
344
|
+
const cached = await this.deps.cache.read(options.projectPath);
|
|
345
|
+
if (cached?.startCommand.trim() && isSafeCachedStartupCommand(cached)) {
|
|
346
|
+
return { ...cached, source: 'cache' };
|
|
347
|
+
}
|
|
348
|
+
const discovered = await this.deps.discoverStartupCommand?.({
|
|
349
|
+
projectPath: options.projectPath,
|
|
350
|
+
framework: options.framework,
|
|
351
|
+
packageManager: options.packageManager,
|
|
352
|
+
});
|
|
353
|
+
if (discovered?.startCommand.trim()) {
|
|
354
|
+
return { ...discovered, source: 'discovered' };
|
|
355
|
+
}
|
|
356
|
+
const devConfig = devServerCommand_1.FRAMEWORK_DEV_CONFIG[options.framework] ?? {
|
|
357
|
+
devCommand: 'dev',
|
|
358
|
+
defaultPort: 3000,
|
|
359
|
+
};
|
|
360
|
+
const command = (0, devServerCommand_1.buildDevServerCommand)(options.framework, options.packageManager, devConfig.devCommand, options.preferredPort);
|
|
361
|
+
return {
|
|
362
|
+
startCommand: [command.cmd, ...command.args].join(' '),
|
|
363
|
+
installCommand: buildInstallCommand(options.packageManager),
|
|
364
|
+
source: 'deterministic',
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Installs project dependencies when the startup path requires it.
|
|
369
|
+
*/
|
|
370
|
+
async installDependenciesIfNeeded(options) {
|
|
371
|
+
const installCommand = options.installCommand?.trim();
|
|
372
|
+
if (!installCommand) {
|
|
373
|
+
return { success: true };
|
|
374
|
+
}
|
|
375
|
+
const { cmd } = splitCommand(installCommand);
|
|
376
|
+
const missingTools = await this.findMissingTools([cmd]);
|
|
377
|
+
if (missingTools.length > 0) {
|
|
378
|
+
return {
|
|
379
|
+
success: false,
|
|
380
|
+
error: `Required tools not found: ${missingTools.join(', ')}.`,
|
|
381
|
+
missingTools,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
const dependenciesInstalled = (await this.deps.dependenciesInstalled?.(options.projectPath)) ?? true;
|
|
385
|
+
if (dependenciesInstalled) {
|
|
386
|
+
return { success: true };
|
|
387
|
+
}
|
|
388
|
+
try {
|
|
389
|
+
await this.deps.exec(installCommand, {
|
|
390
|
+
cwd: options.projectPath,
|
|
391
|
+
env: buildDevServerProcessEnv(),
|
|
392
|
+
});
|
|
393
|
+
return { success: true };
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
397
|
+
const inferredTools = inferMissingToolsFromError(message);
|
|
398
|
+
return {
|
|
399
|
+
success: false,
|
|
400
|
+
error: message,
|
|
401
|
+
...(inferredTools.length > 0 && { missingTools: inferredTools }),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Starts a target app dev server and returns an owner-safe cleanup handle.
|
|
407
|
+
*/
|
|
408
|
+
async start(options) {
|
|
409
|
+
await this.stop();
|
|
410
|
+
const selectedPort = await this.deps.findAvailablePort(options.preferredPort);
|
|
411
|
+
const commandResolution = options.startCommand
|
|
412
|
+
? {
|
|
413
|
+
startCommand: options.startCommand,
|
|
414
|
+
installCommand: options.installCommand,
|
|
415
|
+
source: 'deterministic',
|
|
416
|
+
}
|
|
417
|
+
: await this.resolveStartupCommand({
|
|
418
|
+
projectPath: options.projectPath,
|
|
419
|
+
framework: options.framework,
|
|
420
|
+
packageManager: options.packageManager,
|
|
421
|
+
preferredPort: selectedPort,
|
|
422
|
+
});
|
|
423
|
+
const installResult = await this.installDependenciesIfNeeded({
|
|
424
|
+
projectPath: options.projectPath,
|
|
425
|
+
installCommand: commandResolution.installCommand,
|
|
426
|
+
});
|
|
427
|
+
if (!installResult.success) {
|
|
428
|
+
if (commandResolution.source === 'cache') {
|
|
429
|
+
await this.deps.cache.markFailed(options.projectPath, installResult.error);
|
|
430
|
+
}
|
|
431
|
+
return installResult;
|
|
432
|
+
}
|
|
433
|
+
const boundCommand = bindPortToStartCommand({
|
|
434
|
+
startCommand: commandResolution.startCommand,
|
|
435
|
+
framework: options.framework,
|
|
436
|
+
selectedPort,
|
|
437
|
+
});
|
|
438
|
+
const preExistingPorts = await this.collectPreExistingPorts(selectedPort);
|
|
439
|
+
let output = '';
|
|
440
|
+
let portFromOutput = null;
|
|
441
|
+
const spawnSpec = this.deps.platform === 'win32'
|
|
442
|
+
? splitCommand(boundCommand.command)
|
|
443
|
+
: {
|
|
444
|
+
cmd: this.deps.shell || '/bin/zsh',
|
|
445
|
+
args: ['-l', '-c', boundCommand.command],
|
|
446
|
+
};
|
|
447
|
+
this.projectPath = options.projectPath;
|
|
448
|
+
this.process = this.deps.spawn(spawnSpec.cmd, spawnSpec.args, {
|
|
449
|
+
cwd: options.projectPath,
|
|
450
|
+
env: buildDevServerProcessEnv(boundCommand.env),
|
|
451
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
452
|
+
detached: false,
|
|
453
|
+
});
|
|
454
|
+
const child = this.process;
|
|
455
|
+
const pid = child.pid;
|
|
456
|
+
const recordOutput = (text) => {
|
|
457
|
+
output += text;
|
|
458
|
+
portFromOutput = (0, exports.parsePortFromDevServerOutput)(output);
|
|
459
|
+
};
|
|
460
|
+
child.stdout?.on('data', (data) => recordOutput(data.toString()));
|
|
461
|
+
child.stderr?.on('data', (data) => recordOutput(data.toString()));
|
|
462
|
+
child.on('exit', () => {
|
|
463
|
+
if (this.process === child) {
|
|
464
|
+
this.process = null;
|
|
465
|
+
}
|
|
466
|
+
options.onExit?.();
|
|
467
|
+
});
|
|
468
|
+
child.on('error', (error) => {
|
|
469
|
+
log.warn(`Dev server process error: ${error instanceof Error ? error.message : String(error)}`);
|
|
470
|
+
});
|
|
471
|
+
const detectedPort = await this.waitForServerAndDetectPort({
|
|
472
|
+
expectedPort: selectedPort,
|
|
473
|
+
preExistingPorts,
|
|
474
|
+
getPortFromOutput: () => portFromOutput,
|
|
475
|
+
});
|
|
476
|
+
if (!detectedPort) {
|
|
477
|
+
const outputTail = output.trim().slice(-STARTUP_ERROR_OUTPUT_TAIL_CHARS);
|
|
478
|
+
await this.stop();
|
|
479
|
+
const missingTools = inferMissingToolsFromError(output);
|
|
480
|
+
const error = outputTail
|
|
481
|
+
? `Server did not start within timeout. Last output: ${outputTail}`
|
|
482
|
+
: 'Server did not start within timeout. Check logs for details.';
|
|
483
|
+
if (commandResolution.source === 'cache') {
|
|
484
|
+
await this.deps.cache.markFailed(options.projectPath, error);
|
|
485
|
+
}
|
|
486
|
+
return {
|
|
487
|
+
success: false,
|
|
488
|
+
error,
|
|
489
|
+
...(missingTools.length > 0 && { missingTools }),
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
this.port = detectedPort;
|
|
493
|
+
const stability = await this.waitForStableServer(detectedPort);
|
|
494
|
+
if (!stability.success) {
|
|
495
|
+
const outputTail = output.trim().slice(-STARTUP_ERROR_OUTPUT_TAIL_CHARS);
|
|
496
|
+
await this.stop();
|
|
497
|
+
const error = `${stability.error}${outputTail ? ` Last output: ${outputTail}` : ''}`;
|
|
498
|
+
if (commandResolution.source === 'cache') {
|
|
499
|
+
await this.deps.cache.markFailed(options.projectPath, error);
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
success: false,
|
|
503
|
+
error,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
await this.deps.cache.write(options.projectPath, {
|
|
507
|
+
startCommand: commandResolution.startCommand,
|
|
508
|
+
installCommand: commandResolution.installCommand,
|
|
509
|
+
});
|
|
510
|
+
return {
|
|
511
|
+
success: true,
|
|
512
|
+
host: 'localhost',
|
|
513
|
+
port: detectedPort,
|
|
514
|
+
pid,
|
|
515
|
+
ownership: 'rivet_owned',
|
|
516
|
+
startedByRivet: true,
|
|
517
|
+
stop: () => this.stop(),
|
|
518
|
+
process: child,
|
|
519
|
+
commandSource: commandResolution.source,
|
|
520
|
+
portBindingMode: boundCommand.mode,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Stops the owned child and owner-matched listener processes.
|
|
525
|
+
*/
|
|
526
|
+
async stop() {
|
|
527
|
+
const ownedProcess = this.process;
|
|
528
|
+
const ownedPort = this.port;
|
|
529
|
+
const ownedProjectPath = this.projectPath;
|
|
530
|
+
if (ownedProcess && !ownedProcess.killed) {
|
|
531
|
+
try {
|
|
532
|
+
ownedProcess.kill('SIGTERM');
|
|
533
|
+
}
|
|
534
|
+
catch {
|
|
535
|
+
// The child may already be gone.
|
|
536
|
+
}
|
|
537
|
+
await this.deps.sleep(1_000);
|
|
538
|
+
if (!ownedProcess.killed) {
|
|
539
|
+
try {
|
|
540
|
+
ownedProcess.kill('SIGKILL');
|
|
541
|
+
}
|
|
542
|
+
catch {
|
|
543
|
+
// The child may already be gone.
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (ownedPort && ownedProjectPath) {
|
|
548
|
+
await this.killProjectListenerOnPort(ownedPort, ownedProjectPath);
|
|
549
|
+
}
|
|
550
|
+
this.process = null;
|
|
551
|
+
this.port = null;
|
|
552
|
+
this.projectPath = null;
|
|
553
|
+
}
|
|
554
|
+
async collectPreExistingPorts(preferredPort) {
|
|
555
|
+
const ports = [
|
|
556
|
+
preferredPort,
|
|
557
|
+
...COMMON_DEV_SERVER_PORTS.filter((port) => port !== preferredPort),
|
|
558
|
+
];
|
|
559
|
+
const preExistingPorts = new Set();
|
|
560
|
+
for (const port of ports) {
|
|
561
|
+
if (await this.deps.isPortResponding(port)) {
|
|
562
|
+
preExistingPorts.add(port);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return preExistingPorts;
|
|
566
|
+
}
|
|
567
|
+
async waitForServerAndDetectPort(options) {
|
|
568
|
+
const maxAttempts = Math.ceil(DEFAULT_START_TIMEOUT_MS / DEFAULT_PORT_CHECK_INTERVAL_MS) + 1;
|
|
569
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
570
|
+
const outputPort = options.getPortFromOutput();
|
|
571
|
+
if (outputPort &&
|
|
572
|
+
!options.preExistingPorts.has(outputPort) &&
|
|
573
|
+
(await this.deps.isPortResponding(outputPort))) {
|
|
574
|
+
return outputPort;
|
|
575
|
+
}
|
|
576
|
+
if (!this.process || this.process.killed) {
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
if (!options.preExistingPorts.has(options.expectedPort) &&
|
|
580
|
+
(await this.deps.isPortResponding(options.expectedPort))) {
|
|
581
|
+
return options.expectedPort;
|
|
582
|
+
}
|
|
583
|
+
await this.deps.sleep(DEFAULT_PORT_CHECK_INTERVAL_MS);
|
|
584
|
+
}
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
async waitForStableServer(port) {
|
|
588
|
+
for (let attempt = 0; attempt < DEFAULT_STABILITY_ATTEMPTS; attempt++) {
|
|
589
|
+
if (!this.process || this.process.killed) {
|
|
590
|
+
return {
|
|
591
|
+
success: false,
|
|
592
|
+
error: 'Dev server process exited shortly after startup.',
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
if (!(await this.deps.isPortResponding(port))) {
|
|
596
|
+
return {
|
|
597
|
+
success: false,
|
|
598
|
+
error: `Dev server on localhost:${port} stopped responding during startup.`,
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
if (attempt < DEFAULT_STABILITY_ATTEMPTS - 1) {
|
|
602
|
+
await this.deps.sleep(DEFAULT_STABILITY_INTERVAL_MS);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return { success: true };
|
|
606
|
+
}
|
|
607
|
+
async findMissingTools(commands) {
|
|
608
|
+
const missingTools = [];
|
|
609
|
+
for (const command of commands) {
|
|
610
|
+
if ((command === 'node' || command === 'yarn' || command === 'git') &&
|
|
611
|
+
this.deps.isToolAvailable &&
|
|
612
|
+
!(await this.deps.isToolAvailable(command))) {
|
|
613
|
+
missingTools.push(command);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return missingTools;
|
|
617
|
+
}
|
|
618
|
+
async killProjectListenerOnPort(port, projectPath) {
|
|
619
|
+
try {
|
|
620
|
+
const { stdout } = await this.deps.exec(`lsof -nP -iTCP:${port} -sTCP:LISTEN -Fp`);
|
|
621
|
+
const pids = stdout
|
|
622
|
+
.split('\n')
|
|
623
|
+
.map((line) => line.trim())
|
|
624
|
+
.filter((line) => line.startsWith('p'))
|
|
625
|
+
.map((line) => Number(line.slice(1)))
|
|
626
|
+
.filter((pid) => Number.isInteger(pid) && pid > 0);
|
|
627
|
+
const expectedPath = normalizeProjectPath(projectPath);
|
|
628
|
+
for (const pid of [...new Set(pids)]) {
|
|
629
|
+
const cwd = await this.getProcessCwd(pid);
|
|
630
|
+
if (!cwd || normalizeProjectPath(cwd) !== expectedPath) {
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
await this.deps.exec(`kill -9 ${pid}`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
catch {
|
|
637
|
+
// No listener, no lsof, or process already exited.
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
async getProcessCwd(pid) {
|
|
641
|
+
try {
|
|
642
|
+
const { stdout } = await this.deps.exec(`lsof -a -p ${pid} -d cwd -Fn`);
|
|
643
|
+
const cwdLine = stdout
|
|
644
|
+
.split('\n')
|
|
645
|
+
.map((line) => line.trim())
|
|
646
|
+
.find((line) => line.startsWith('n'));
|
|
647
|
+
return cwdLine ? cwdLine.slice(1) : null;
|
|
648
|
+
}
|
|
649
|
+
catch {
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
exports.DevServerRuntimeService = DevServerRuntimeService;
|
|
655
|
+
const createDevServerRuntimeService = () => new DevServerRuntimeService();
|
|
656
|
+
exports.createDevServerRuntimeService = createDevServerRuntimeService;
|
|
657
|
+
//# sourceMappingURL=DevServerRuntimeService.js.map
|