@mindstudio-ai/remy 0.1.26 → 0.1.28
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/README.md +149 -41
- package/dist/compiled/tables.md +53 -1
- package/dist/headless.d.ts +10 -2
- package/dist/headless.js +531 -271
- package/dist/index.js +574 -301
- package/dist/prompt/.notes.md +0 -1
- package/dist/prompt/compiled/tables.md +53 -1
- package/dist/prompt/static/authoring.md +10 -0
- package/dist/prompt/static/instructions.md +2 -1
- package/dist/prompt/static/team.md +1 -1
- package/dist/static/authoring.md +10 -0
- package/dist/static/instructions.md +2 -1
- package/dist/static/team.md +1 -1
- package/dist/subagents/.notes-background-agents.md +80 -0
- package/dist/subagents/browserAutomation/prompt.md +37 -2
- package/dist/subagents/codeSanityCheck/prompt.md +5 -0
- package/dist/subagents/designExpert/.notes.md +2 -2
- package/dist/subagents/designExpert/data/compile-font-descriptions.sh +125 -0
- package/dist/subagents/designExpert/data/compile-inspiration.sh +6 -1
- package/dist/subagents/designExpert/data/fonts.json +497 -869
- package/dist/subagents/designExpert/data/inspiration.json +97 -245
- package/dist/subagents/designExpert/data/inspiration.raw.json +1 -12
- package/dist/subagents/designExpert/prompts/animation.md +1 -1
- package/dist/subagents/designExpert/prompts/identity.md +4 -2
- package/dist/subagents/designExpert/prompts/instructions.md +2 -3
- package/dist/subagents/designExpert/prompts/layout.md +1 -13
- package/dist/subagents/designExpert/prompts/tool-prompts/design-analysis.md +22 -0
- package/dist/subagents/designExpert/prompts/tool-prompts/font-analysis.md +17 -0
- package/dist/subagents/productVision/prompt.md +1 -1
- package/package.json +1 -1
package/dist/headless.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/headless.ts
|
|
2
2
|
import { createInterface } from "readline";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import fs21 from "fs";
|
|
4
|
+
import path14 from "path";
|
|
5
5
|
|
|
6
6
|
// src/config.ts
|
|
7
7
|
import fs2 from "fs";
|
|
@@ -300,14 +300,11 @@ function buildSystemPrompt(onboardingState, viewContext) {
|
|
|
300
300
|
loadSpecFileMetadata(),
|
|
301
301
|
loadProjectFileListing()
|
|
302
302
|
].filter(Boolean).join("\n");
|
|
303
|
-
const now = (/* @__PURE__ */ new Date()).
|
|
304
|
-
dateStyle: "full",
|
|
305
|
-
timeStyle: "long"
|
|
306
|
-
});
|
|
303
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC");
|
|
307
304
|
const template = `
|
|
308
305
|
{{static/identity.md}}
|
|
309
306
|
|
|
310
|
-
|
|
307
|
+
Current date/time: ${now}
|
|
311
308
|
|
|
312
309
|
<platform_docs>
|
|
313
310
|
<platform>
|
|
@@ -1142,7 +1139,7 @@ var promptUserTool = {
|
|
|
1142
1139
|
}
|
|
1143
1140
|
]
|
|
1144
1141
|
},
|
|
1145
|
-
description: "Options for select and checklist types. Each can be a string or { label, description }."
|
|
1142
|
+
description: "Options for select and checklist types. Each can be a string or { label, description }. Image URLs (e.g. https://i.mscdn.ai/...) are rendered as visual previews, so use the URL directly as the option string when presenting images for the user to choose between."
|
|
1146
1143
|
},
|
|
1147
1144
|
multiple: {
|
|
1148
1145
|
type: "boolean",
|
|
@@ -1243,27 +1240,75 @@ var confirmDestructiveActionTool = {
|
|
|
1243
1240
|
};
|
|
1244
1241
|
|
|
1245
1242
|
// src/subagents/common/runCli.ts
|
|
1246
|
-
import {
|
|
1243
|
+
import { spawn } from "child_process";
|
|
1247
1244
|
function runCli(cmd, options) {
|
|
1248
1245
|
return new Promise((resolve) => {
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1246
|
+
const timeout = options?.timeout ?? 6e4;
|
|
1247
|
+
const maxBuffer = options?.maxBuffer ?? 1024 * 1024;
|
|
1248
|
+
const cmdWithLogs = options?.jsonLogs && !cmd.includes("--json-logs") ? cmd.replace(/^(mindstudio\s+\S+)/, "$1 --json-logs") : cmd;
|
|
1249
|
+
const child = spawn("sh", ["-c", cmdWithLogs], {
|
|
1250
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1251
|
+
});
|
|
1252
|
+
const logs = [];
|
|
1253
|
+
let stdout = "";
|
|
1254
|
+
let stderr = "";
|
|
1255
|
+
let stdoutSize = 0;
|
|
1256
|
+
let stderrSize = 0;
|
|
1257
|
+
let killed = false;
|
|
1258
|
+
child.stdout.on("data", (chunk) => {
|
|
1259
|
+
stdoutSize += chunk.length;
|
|
1260
|
+
if (stdoutSize <= maxBuffer) {
|
|
1261
|
+
stdout += chunk.toString();
|
|
1262
|
+
} else if (!killed) {
|
|
1263
|
+
killed = true;
|
|
1264
|
+
child.kill();
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
child.stderr.on("data", (chunk) => {
|
|
1268
|
+
stderrSize += chunk.length;
|
|
1269
|
+
if (stderrSize > maxBuffer) {
|
|
1270
|
+
if (!killed) {
|
|
1271
|
+
killed = true;
|
|
1272
|
+
child.kill();
|
|
1259
1273
|
}
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
const text = chunk.toString();
|
|
1277
|
+
stderr += text;
|
|
1278
|
+
for (const line of text.split("\n")) {
|
|
1279
|
+
const trimmed = line.trim();
|
|
1280
|
+
if (!trimmed || trimmed[0] !== "{") {
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1283
|
+
try {
|
|
1284
|
+
const entry = JSON.parse(trimmed);
|
|
1285
|
+
if (entry.type === "log" && entry.value) {
|
|
1286
|
+
const prefix = entry.tag ? `[${entry.tag}]` : "[log]";
|
|
1287
|
+
logs.push(`${prefix} ${entry.value}`);
|
|
1288
|
+
}
|
|
1289
|
+
} catch {
|
|
1263
1290
|
}
|
|
1264
|
-
resolve("(no response)");
|
|
1265
1291
|
}
|
|
1266
|
-
);
|
|
1292
|
+
});
|
|
1293
|
+
const timer = setTimeout(() => {
|
|
1294
|
+
killed = true;
|
|
1295
|
+
child.kill();
|
|
1296
|
+
}, timeout);
|
|
1297
|
+
child.on("close", (code) => {
|
|
1298
|
+
clearTimeout(timer);
|
|
1299
|
+
const logBlock = logs.length > 0 ? logs.join("\n") + "\n\n" : "";
|
|
1300
|
+
const out = stdout.trim();
|
|
1301
|
+
if (out) {
|
|
1302
|
+
resolve(logBlock + out);
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
if (code !== 0 || killed) {
|
|
1306
|
+
const errMsg = stderr.trim() || (killed ? "Process timed out" : `Exit code ${code}`);
|
|
1307
|
+
resolve(logBlock + `Error: ${errMsg}`);
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
resolve(logBlock + "(no response)");
|
|
1311
|
+
});
|
|
1267
1312
|
});
|
|
1268
1313
|
}
|
|
1269
1314
|
|
|
@@ -1670,7 +1715,7 @@ ${unifiedDiff(input.path, content, updated)}`;
|
|
|
1670
1715
|
};
|
|
1671
1716
|
|
|
1672
1717
|
// src/tools/code/bash.ts
|
|
1673
|
-
import { exec
|
|
1718
|
+
import { exec } from "child_process";
|
|
1674
1719
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
1675
1720
|
var DEFAULT_MAX_LINES3 = 500;
|
|
1676
1721
|
var bashTool = {
|
|
@@ -1704,7 +1749,7 @@ var bashTool = {
|
|
|
1704
1749
|
const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES3;
|
|
1705
1750
|
const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
|
|
1706
1751
|
return new Promise((resolve) => {
|
|
1707
|
-
|
|
1752
|
+
exec(
|
|
1708
1753
|
input.command,
|
|
1709
1754
|
{
|
|
1710
1755
|
timeout: timeoutMs,
|
|
@@ -1744,7 +1789,7 @@ var bashTool = {
|
|
|
1744
1789
|
};
|
|
1745
1790
|
|
|
1746
1791
|
// src/tools/code/grep.ts
|
|
1747
|
-
import { exec as
|
|
1792
|
+
import { exec as exec2 } from "child_process";
|
|
1748
1793
|
var DEFAULT_MAX = 50;
|
|
1749
1794
|
function formatResults(stdout, max) {
|
|
1750
1795
|
const lines = stdout.trim().split("\n");
|
|
@@ -1791,12 +1836,12 @@ var grepTool = {
|
|
|
1791
1836
|
const rgCmd = `rg -n --no-heading --max-count=${max}${globFlag} '${escaped}' ${searchPath}`;
|
|
1792
1837
|
const grepCmd = `grep -rn --max-count=${max} '${escaped}' ${searchPath} --include='*.ts' --include='*.tsx' --include='*.js' --include='*.json' --include='*.md'`;
|
|
1793
1838
|
return new Promise((resolve) => {
|
|
1794
|
-
|
|
1839
|
+
exec2(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
|
|
1795
1840
|
if (stdout?.trim()) {
|
|
1796
1841
|
resolve(formatResults(stdout, max));
|
|
1797
1842
|
return;
|
|
1798
1843
|
}
|
|
1799
|
-
|
|
1844
|
+
exec2(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
|
|
1800
1845
|
if (grepStdout?.trim()) {
|
|
1801
1846
|
resolve(formatResults(grepStdout, max));
|
|
1802
1847
|
} else {
|
|
@@ -2027,36 +2072,64 @@ var runMethodTool = {
|
|
|
2027
2072
|
}
|
|
2028
2073
|
};
|
|
2029
2074
|
|
|
2075
|
+
// src/tools/_helpers/screenshot.ts
|
|
2076
|
+
var SCREENSHOT_ANALYSIS_PROMPT = "Describe everything visible on screen from top to bottom \u2014 every element, its position, its size relative to the viewport, its colors, its content. Be thorough and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components).";
|
|
2077
|
+
async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
2078
|
+
let prompt;
|
|
2079
|
+
let fullPage = false;
|
|
2080
|
+
if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
|
|
2081
|
+
prompt = promptOrOptions.prompt;
|
|
2082
|
+
fullPage = promptOrOptions.fullPage ?? false;
|
|
2083
|
+
} else {
|
|
2084
|
+
prompt = promptOrOptions;
|
|
2085
|
+
}
|
|
2086
|
+
const ssResult = await sidecarRequest(
|
|
2087
|
+
"/screenshot",
|
|
2088
|
+
{ fullPage },
|
|
2089
|
+
{ timeout: 12e4 }
|
|
2090
|
+
);
|
|
2091
|
+
log.debug("Screenshot response", { ssResult });
|
|
2092
|
+
const url = ssResult?.url || ssResult?.screenshotUrl;
|
|
2093
|
+
if (!url) {
|
|
2094
|
+
throw new Error(
|
|
2095
|
+
`No URL in sidecar response. The browser may not be ready yet. Response: ${JSON.stringify(ssResult)}`
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
2098
|
+
if (prompt === false) {
|
|
2099
|
+
return url;
|
|
2100
|
+
}
|
|
2101
|
+
const analysisPrompt = prompt || SCREENSHOT_ANALYSIS_PROMPT;
|
|
2102
|
+
const analysis = await runCli(
|
|
2103
|
+
`mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`
|
|
2104
|
+
);
|
|
2105
|
+
return JSON.stringify({ url, analysis });
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2030
2108
|
// src/tools/code/screenshot.ts
|
|
2031
|
-
var DEFAULT_PROMPT = "Describe this app screenshot for a developer who cannot see it. What is visible on screen: the layout, content, interactive elements, any loading or error states. Be concise and factual.";
|
|
2032
2109
|
var screenshotTool = {
|
|
2033
2110
|
definition: {
|
|
2034
2111
|
name: "screenshot",
|
|
2035
|
-
description: "Capture a screenshot of the app preview and get a description of what's on screen. Optionally provide a specific question about what you're looking for.",
|
|
2112
|
+
description: "Capture a screenshot of the app preview and get a description of what's on screen. Optionally provide a specific question about what you're looking for. By default captures the viewport (what the user sees). Set fullPage to capture the entire scrollable page.",
|
|
2036
2113
|
inputSchema: {
|
|
2037
2114
|
type: "object",
|
|
2038
2115
|
properties: {
|
|
2039
2116
|
prompt: {
|
|
2040
2117
|
type: "string",
|
|
2041
2118
|
description: "Optional question about the screenshot. If omitted, returns a general description of what's visible."
|
|
2119
|
+
},
|
|
2120
|
+
fullPage: {
|
|
2121
|
+
type: "boolean",
|
|
2122
|
+
description: "Capture the full scrollable page instead of just the viewport. Use when you need to see below-the-fold content."
|
|
2042
2123
|
}
|
|
2043
2124
|
}
|
|
2044
2125
|
}
|
|
2045
2126
|
},
|
|
2046
2127
|
async execute(input) {
|
|
2047
2128
|
try {
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
);
|
|
2053
|
-
const analysisPrompt = input.prompt || DEFAULT_PROMPT;
|
|
2054
|
-
const analysis = await runCli(
|
|
2055
|
-
`mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`
|
|
2056
|
-
);
|
|
2057
|
-
return `Screenshot: ${url}
|
|
2058
|
-
|
|
2059
|
-
${analysis}`;
|
|
2129
|
+
return await captureAndAnalyzeScreenshot({
|
|
2130
|
+
prompt: input.prompt,
|
|
2131
|
+
fullPage: input.fullPage
|
|
2132
|
+
});
|
|
2060
2133
|
} catch (err) {
|
|
2061
2134
|
return `Error taking screenshot: ${err.message}`;
|
|
2062
2135
|
}
|
|
@@ -2124,7 +2197,9 @@ async function runSubAgent(config) {
|
|
|
2124
2197
|
...apiConfig,
|
|
2125
2198
|
model,
|
|
2126
2199
|
subAgentId,
|
|
2127
|
-
system
|
|
2200
|
+
system: `${system}
|
|
2201
|
+
|
|
2202
|
+
Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC")}`,
|
|
2128
2203
|
messages: cleanMessagesForApi(messages),
|
|
2129
2204
|
tools,
|
|
2130
2205
|
signal
|
|
@@ -2219,7 +2294,7 @@ async function runSubAgent(config) {
|
|
|
2219
2294
|
if (externalTools.has(tc.name) && resolveExternalTool) {
|
|
2220
2295
|
result = await resolveExternalTool(tc.id, tc.name, tc.input);
|
|
2221
2296
|
} else {
|
|
2222
|
-
result = await executeTool2(tc.name, tc.input);
|
|
2297
|
+
result = await executeTool2(tc.name, tc.input, tc.id);
|
|
2223
2298
|
}
|
|
2224
2299
|
const isError = result.startsWith("Error");
|
|
2225
2300
|
emit2({
|
|
@@ -2244,6 +2319,13 @@ async function runSubAgent(config) {
|
|
|
2244
2319
|
})
|
|
2245
2320
|
);
|
|
2246
2321
|
for (const r of results) {
|
|
2322
|
+
const block = contentBlocks.find(
|
|
2323
|
+
(b) => b.type === "tool" && b.id === r.id
|
|
2324
|
+
);
|
|
2325
|
+
if (block?.type === "tool") {
|
|
2326
|
+
block.result = r.result;
|
|
2327
|
+
block.isError = r.isError;
|
|
2328
|
+
}
|
|
2247
2329
|
messages.push({
|
|
2248
2330
|
role: "user",
|
|
2249
2331
|
content: r.result,
|
|
@@ -2269,8 +2351,18 @@ var BROWSER_TOOLS = [
|
|
|
2269
2351
|
properties: {
|
|
2270
2352
|
command: {
|
|
2271
2353
|
type: "string",
|
|
2272
|
-
enum: [
|
|
2273
|
-
|
|
2354
|
+
enum: [
|
|
2355
|
+
"snapshot",
|
|
2356
|
+
"click",
|
|
2357
|
+
"type",
|
|
2358
|
+
"select",
|
|
2359
|
+
"wait",
|
|
2360
|
+
"navigate",
|
|
2361
|
+
"evaluate",
|
|
2362
|
+
"styles",
|
|
2363
|
+
"screenshot"
|
|
2364
|
+
],
|
|
2365
|
+
description: "snapshot: accessibility tree of the page (waits for network to settle). click: click an element (animated cursor, full event sequence). type: type text into input (one char at a time, works with React/Vue/Svelte). select: select a dropdown option by text. wait: wait for an element to appear (polls 100ms, waits for network). navigate: navigate to a URL within the app (waits for load, subsequent steps run on new page). evaluate: run JS in the page. styles: read computed CSS styles from elements (pass properties array with camelCase names, or omit for defaults). screenshot: full-page viewport-stitched screenshot (returns base64 JPEG with dimensions)."
|
|
2274
2366
|
},
|
|
2275
2367
|
ref: {
|
|
2276
2368
|
type: "string",
|
|
@@ -2292,6 +2384,10 @@ var BROWSER_TOOLS = [
|
|
|
2292
2384
|
type: "string",
|
|
2293
2385
|
description: "CSS selector fallback (last resort)."
|
|
2294
2386
|
},
|
|
2387
|
+
option: {
|
|
2388
|
+
type: "string",
|
|
2389
|
+
description: "For select: the option text to select from a dropdown."
|
|
2390
|
+
},
|
|
2295
2391
|
clear: {
|
|
2296
2392
|
type: "boolean",
|
|
2297
2393
|
description: "For type: clear the field before typing."
|
|
@@ -2303,6 +2399,15 @@ var BROWSER_TOOLS = [
|
|
|
2303
2399
|
script: {
|
|
2304
2400
|
type: "string",
|
|
2305
2401
|
description: "For evaluate: JavaScript to run in the page."
|
|
2402
|
+
},
|
|
2403
|
+
url: {
|
|
2404
|
+
type: "string",
|
|
2405
|
+
description: 'For navigate: the URL to navigate to (e.g., "/quiz", "/settings").'
|
|
2406
|
+
},
|
|
2407
|
+
properties: {
|
|
2408
|
+
type: "array",
|
|
2409
|
+
items: { type: "string" },
|
|
2410
|
+
description: 'For styles: camelCase CSS property names to read (e.g., ["backgroundColor", "borderRadius", "fontSize"]). Omit for a default set.'
|
|
2306
2411
|
}
|
|
2307
2412
|
},
|
|
2308
2413
|
required: ["command"]
|
|
@@ -2329,7 +2434,7 @@ var BROWSER_TOOLS = [
|
|
|
2329
2434
|
}
|
|
2330
2435
|
}
|
|
2331
2436
|
];
|
|
2332
|
-
var BROWSER_EXTERNAL_TOOLS = /* @__PURE__ */ new Set(["browserCommand"
|
|
2437
|
+
var BROWSER_EXTERNAL_TOOLS = /* @__PURE__ */ new Set(["browserCommand"]);
|
|
2333
2438
|
|
|
2334
2439
|
// src/subagents/browserAutomation/prompt.ts
|
|
2335
2440
|
import fs13 from "fs";
|
|
@@ -2355,13 +2460,13 @@ ${appSpec}
|
|
|
2355
2460
|
var browserAutomationTool = {
|
|
2356
2461
|
definition: {
|
|
2357
2462
|
name: "runAutomatedBrowserTest",
|
|
2358
|
-
description: "Run an automated browser test against the live preview.
|
|
2463
|
+
description: "Run an automated browser test against the live preview. Describe what to test \u2014 the agent figures out how. Use after writing or modifying frontend code, to reproduce user-reported issues, or to test end-to-end flows.",
|
|
2359
2464
|
inputSchema: {
|
|
2360
2465
|
type: "object",
|
|
2361
2466
|
properties: {
|
|
2362
2467
|
task: {
|
|
2363
2468
|
type: "string",
|
|
2364
|
-
description: "What to test, in natural language.
|
|
2469
|
+
description: "What to test, in natural language. Keep it brief \u2014 the agent reads the spec and figures out navigation, data setup, and test strategy on its own."
|
|
2365
2470
|
}
|
|
2366
2471
|
},
|
|
2367
2472
|
required: ["task"]
|
|
@@ -2389,6 +2494,13 @@ var browserAutomationTool = {
|
|
|
2389
2494
|
tools: BROWSER_TOOLS,
|
|
2390
2495
|
externalTools: BROWSER_EXTERNAL_TOOLS,
|
|
2391
2496
|
executeTool: async (name) => {
|
|
2497
|
+
if (name === "screenshot") {
|
|
2498
|
+
try {
|
|
2499
|
+
return await captureAndAnalyzeScreenshot();
|
|
2500
|
+
} catch (err) {
|
|
2501
|
+
return `Error taking screenshot: ${err.message}`;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2392
2504
|
if (name === "resetBrowser") {
|
|
2393
2505
|
try {
|
|
2394
2506
|
await sidecarRequest("/reset-browser", {}, { timeout: 5e3 });
|
|
@@ -2405,7 +2517,50 @@ var browserAutomationTool = {
|
|
|
2405
2517
|
signal: context.signal,
|
|
2406
2518
|
parentToolId: context.toolCallId,
|
|
2407
2519
|
onEvent: context.onEvent,
|
|
2408
|
-
resolveExternalTool:
|
|
2520
|
+
resolveExternalTool: async (id, name, input2) => {
|
|
2521
|
+
if (!context.resolveExternalTool) {
|
|
2522
|
+
return "Error: no external tool resolver";
|
|
2523
|
+
}
|
|
2524
|
+
const result2 = await context.resolveExternalTool(id, name, input2);
|
|
2525
|
+
if (name === "browserCommand") {
|
|
2526
|
+
try {
|
|
2527
|
+
const parsed = JSON.parse(result2);
|
|
2528
|
+
const screenshotSteps = (parsed.steps || []).filter(
|
|
2529
|
+
(s) => s.command === "screenshot" && s.result?.url
|
|
2530
|
+
);
|
|
2531
|
+
if (screenshotSteps.length > 0) {
|
|
2532
|
+
const batchInput = screenshotSteps.map((s) => ({
|
|
2533
|
+
stepType: "analyzeImage",
|
|
2534
|
+
step: {
|
|
2535
|
+
imageUrl: s.result.url,
|
|
2536
|
+
prompt: SCREENSHOT_ANALYSIS_PROMPT
|
|
2537
|
+
}
|
|
2538
|
+
}));
|
|
2539
|
+
const batchResult = await runCli(
|
|
2540
|
+
`mindstudio batch --no-meta ${JSON.stringify(JSON.stringify(batchInput))}`,
|
|
2541
|
+
{ timeout: 12e4 }
|
|
2542
|
+
);
|
|
2543
|
+
try {
|
|
2544
|
+
const analyses = JSON.parse(batchResult);
|
|
2545
|
+
let ai = 0;
|
|
2546
|
+
for (const step of parsed.steps) {
|
|
2547
|
+
if (step.command === "screenshot" && step.result?.url && ai < analyses.length) {
|
|
2548
|
+
step.result.analysis = analyses[ai]?.output?.analysis || analyses[ai]?.output || "";
|
|
2549
|
+
ai++;
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
} catch {
|
|
2553
|
+
log.debug("Failed to parse batch analysis result", {
|
|
2554
|
+
batchResult
|
|
2555
|
+
});
|
|
2556
|
+
}
|
|
2557
|
+
return JSON.stringify(parsed);
|
|
2558
|
+
}
|
|
2559
|
+
} catch {
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
return result2;
|
|
2563
|
+
}
|
|
2409
2564
|
});
|
|
2410
2565
|
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
2411
2566
|
return result.text;
|
|
@@ -2413,13 +2568,14 @@ var browserAutomationTool = {
|
|
|
2413
2568
|
};
|
|
2414
2569
|
|
|
2415
2570
|
// src/subagents/designExpert/tools.ts
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2571
|
+
import fs14 from "fs";
|
|
2572
|
+
import path8 from "path";
|
|
2573
|
+
var base2 = import.meta.dirname ?? path8.dirname(new URL(import.meta.url).pathname);
|
|
2574
|
+
function resolvePath(filename) {
|
|
2575
|
+
const local4 = path8.join(base2, filename);
|
|
2576
|
+
return fs14.existsSync(local4) ? local4 : path8.join(base2, "subagents", "designExpert", filename);
|
|
2577
|
+
}
|
|
2578
|
+
var DESIGN_REFERENCE_PROMPT = fs14.readFileSync(resolvePath("prompts/tool-prompts/design-analysis.md"), "utf-8").trim();
|
|
2423
2579
|
var DESIGN_EXPERT_TOOLS = [
|
|
2424
2580
|
{
|
|
2425
2581
|
name: "searchGoogle",
|
|
@@ -2473,24 +2629,33 @@ var DESIGN_EXPERT_TOOLS = [
|
|
|
2473
2629
|
},
|
|
2474
2630
|
{
|
|
2475
2631
|
name: "screenshot",
|
|
2476
|
-
description: "Capture a screenshot of the app preview. Returns a CDN URL. Use to review the current state of the UI being built.",
|
|
2632
|
+
description: "Capture a screenshot of the app preview. Returns a CDN URL with visual analysis. Use to review the current state of the UI being built. By default captures the viewport. Set fullPage to capture the entire scrollable page.",
|
|
2477
2633
|
inputSchema: {
|
|
2478
2634
|
type: "object",
|
|
2479
|
-
properties: {
|
|
2635
|
+
properties: {
|
|
2636
|
+
prompt: {
|
|
2637
|
+
type: "string",
|
|
2638
|
+
description: "Optional specific question about the screenshot."
|
|
2639
|
+
},
|
|
2640
|
+
fullPage: {
|
|
2641
|
+
type: "boolean",
|
|
2642
|
+
description: "Capture the full scrollable page instead of just the viewport. Use when you need to see below-the-fold content."
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2480
2645
|
}
|
|
2481
2646
|
},
|
|
2482
2647
|
{
|
|
2483
|
-
name: "
|
|
2484
|
-
description:
|
|
2648
|
+
name: "runBrowserTest",
|
|
2649
|
+
description: "Run an automated browser test against the live app preview. Use to verify visual implementation: check computed styles, navigate between pages, take analyzed screenshots. Describe what you want to verify and the browser agent handles the interaction.",
|
|
2485
2650
|
inputSchema: {
|
|
2486
2651
|
type: "object",
|
|
2487
2652
|
properties: {
|
|
2488
|
-
|
|
2653
|
+
task: {
|
|
2489
2654
|
type: "string",
|
|
2490
|
-
description: '
|
|
2655
|
+
description: 'What to verify, in natural language. E.g., "Check that the hero section cards have border-radius: 24px and the correct rotation angles" or "Navigate to /about and screenshot it".'
|
|
2491
2656
|
}
|
|
2492
2657
|
},
|
|
2493
|
-
required: ["
|
|
2658
|
+
required: ["task"]
|
|
2494
2659
|
}
|
|
2495
2660
|
},
|
|
2496
2661
|
{
|
|
@@ -2519,22 +2684,14 @@ var DESIGN_EXPERT_TOOLS = [
|
|
|
2519
2684
|
}
|
|
2520
2685
|
}
|
|
2521
2686
|
];
|
|
2522
|
-
async function executeDesignExpertTool(name, input) {
|
|
2687
|
+
async function executeDesignExpertTool(name, input, context, toolCallId) {
|
|
2523
2688
|
switch (name) {
|
|
2524
2689
|
case "screenshot": {
|
|
2525
2690
|
try {
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
);
|
|
2531
|
-
const analysisPrompt = input.prompt || "Describe this app screenshot for a visual designer reviewing the current state. What is visible: layout, typography, colors, spacing, imagery. Note anything that looks broken or off. Be concise.";
|
|
2532
|
-
const analysis = await runCli(
|
|
2533
|
-
`mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`
|
|
2534
|
-
);
|
|
2535
|
-
return `Screenshot: ${url}
|
|
2536
|
-
|
|
2537
|
-
${analysis}`;
|
|
2691
|
+
return await captureAndAnalyzeScreenshot({
|
|
2692
|
+
prompt: input.prompt,
|
|
2693
|
+
fullPage: input.fullPage
|
|
2694
|
+
});
|
|
2538
2695
|
} catch (err) {
|
|
2539
2696
|
return `Error taking screenshot: ${err.message}`;
|
|
2540
2697
|
}
|
|
@@ -2573,12 +2730,6 @@ ${analysis}`;
|
|
|
2573
2730
|
|
|
2574
2731
|
${analysis}`;
|
|
2575
2732
|
}
|
|
2576
|
-
case "searchProductScreenshots": {
|
|
2577
|
-
const query = `${input.product} product screenshot UI 2026`;
|
|
2578
|
-
return runCli(
|
|
2579
|
-
`mindstudio search-google-images --query ${JSON.stringify(query)} --export-type json --output-key images --no-meta`
|
|
2580
|
-
);
|
|
2581
|
-
}
|
|
2582
2733
|
case "generateImages": {
|
|
2583
2734
|
const prompts = input.prompts;
|
|
2584
2735
|
const width = input.width || 2048;
|
|
@@ -2594,7 +2745,8 @@ ${analysis}`;
|
|
|
2594
2745
|
}
|
|
2595
2746
|
});
|
|
2596
2747
|
const url = await runCli(
|
|
2597
|
-
`mindstudio generate-image '${step}' --output-key imageUrl --no-meta
|
|
2748
|
+
`mindstudio generate-image '${step}' --output-key imageUrl --no-meta`,
|
|
2749
|
+
{ jsonLogs: true }
|
|
2598
2750
|
);
|
|
2599
2751
|
imageUrls = [url];
|
|
2600
2752
|
} else {
|
|
@@ -2609,7 +2761,8 @@ ${analysis}`;
|
|
|
2609
2761
|
}
|
|
2610
2762
|
}));
|
|
2611
2763
|
const batchResult = await runCli(
|
|
2612
|
-
`mindstudio batch '${JSON.stringify(steps)}' --no-meta
|
|
2764
|
+
`mindstudio batch '${JSON.stringify(steps)}' --no-meta`,
|
|
2765
|
+
{ jsonLogs: true }
|
|
2613
2766
|
);
|
|
2614
2767
|
try {
|
|
2615
2768
|
const parsed = JSON.parse(batchResult);
|
|
@@ -2620,20 +2773,30 @@ ${analysis}`;
|
|
|
2620
2773
|
return batchResult;
|
|
2621
2774
|
}
|
|
2622
2775
|
}
|
|
2623
|
-
const
|
|
2776
|
+
const images = await Promise.all(
|
|
2624
2777
|
imageUrls.map(async (url, i) => {
|
|
2625
2778
|
if (url.startsWith("Error")) {
|
|
2626
|
-
return
|
|
2779
|
+
return { prompt: prompts[i], error: url };
|
|
2627
2780
|
}
|
|
2628
2781
|
const analysis = await runCli(
|
|
2629
2782
|
`mindstudio analyze-image --prompt ${JSON.stringify(ANALYZE_PROMPT)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`
|
|
2630
2783
|
);
|
|
2631
|
-
return
|
|
2632
|
-
Prompt: ${prompts[i]}
|
|
2633
|
-
Analysis: ${analysis}`;
|
|
2784
|
+
return { url, prompt: prompts[i], analysis, width, height };
|
|
2634
2785
|
})
|
|
2635
2786
|
);
|
|
2636
|
-
return
|
|
2787
|
+
return `%%JSON%%${JSON.stringify({ images })}`;
|
|
2788
|
+
}
|
|
2789
|
+
case "runBrowserTest": {
|
|
2790
|
+
if (!context) {
|
|
2791
|
+
return "Error: browser testing requires execution context (only available in headless mode)";
|
|
2792
|
+
}
|
|
2793
|
+
return browserAutomationTool.execute(
|
|
2794
|
+
{ task: input.task },
|
|
2795
|
+
{
|
|
2796
|
+
...context,
|
|
2797
|
+
toolCallId: toolCallId || context.toolCallId
|
|
2798
|
+
}
|
|
2799
|
+
);
|
|
2637
2800
|
}
|
|
2638
2801
|
default:
|
|
2639
2802
|
return `Error: unknown tool "${name}"`;
|
|
@@ -2641,17 +2804,17 @@ Analysis: ${analysis}`;
|
|
|
2641
2804
|
}
|
|
2642
2805
|
|
|
2643
2806
|
// src/subagents/designExpert/prompt.ts
|
|
2644
|
-
import
|
|
2645
|
-
import
|
|
2807
|
+
import fs16 from "fs";
|
|
2808
|
+
import path10 from "path";
|
|
2646
2809
|
|
|
2647
2810
|
// src/subagents/common/context.ts
|
|
2648
|
-
import
|
|
2649
|
-
import
|
|
2811
|
+
import fs15 from "fs";
|
|
2812
|
+
import path9 from "path";
|
|
2650
2813
|
function walkMdFiles2(dir, skip) {
|
|
2651
2814
|
const files = [];
|
|
2652
2815
|
try {
|
|
2653
|
-
for (const entry of
|
|
2654
|
-
const full =
|
|
2816
|
+
for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
|
|
2817
|
+
const full = path9.join(dir, entry.name);
|
|
2655
2818
|
if (entry.isDirectory()) {
|
|
2656
2819
|
if (!skip?.has(entry.name)) {
|
|
2657
2820
|
files.push(...walkMdFiles2(full, skip));
|
|
@@ -2671,7 +2834,7 @@ function loadFilesAsXml(dir, tag, skip) {
|
|
|
2671
2834
|
}
|
|
2672
2835
|
const sections = files.map((f) => {
|
|
2673
2836
|
try {
|
|
2674
|
-
const content =
|
|
2837
|
+
const content = fs15.readFileSync(f, "utf-8").trim();
|
|
2675
2838
|
return `<file path="${f}">
|
|
2676
2839
|
${content}
|
|
2677
2840
|
</file>`;
|
|
@@ -2741,17 +2904,17 @@ The first-party SDK (@mindstudio-ai/agent) provides access to 200+ AI models (Op
|
|
|
2741
2904
|
}
|
|
2742
2905
|
|
|
2743
2906
|
// src/subagents/designExpert/prompt.ts
|
|
2744
|
-
var
|
|
2745
|
-
function
|
|
2746
|
-
const local4 =
|
|
2747
|
-
return
|
|
2907
|
+
var base3 = import.meta.dirname ?? path10.dirname(new URL(import.meta.url).pathname);
|
|
2908
|
+
function resolvePath2(filename) {
|
|
2909
|
+
const local4 = path10.join(base3, filename);
|
|
2910
|
+
return fs16.existsSync(local4) ? local4 : path10.join(base3, "subagents", "designExpert", filename);
|
|
2748
2911
|
}
|
|
2749
2912
|
function readFile(filename) {
|
|
2750
|
-
return
|
|
2913
|
+
return fs16.readFileSync(resolvePath2(filename), "utf-8").trim();
|
|
2751
2914
|
}
|
|
2752
2915
|
function readJson(filename, fallback) {
|
|
2753
2916
|
try {
|
|
2754
|
-
return JSON.parse(
|
|
2917
|
+
return JSON.parse(fs16.readFileSync(resolvePath2(filename), "utf-8"));
|
|
2755
2918
|
} catch {
|
|
2756
2919
|
return fallback;
|
|
2757
2920
|
}
|
|
@@ -2788,7 +2951,6 @@ function getDesignExpertPrompt() {
|
|
|
2788
2951
|
const pairings = sample(fontData.pairings, 20);
|
|
2789
2952
|
const images = sample(inspirationImages, 15);
|
|
2790
2953
|
const fontList = fonts.map((f) => {
|
|
2791
|
-
const tags = f.tags.length ? ` (${f.tags.join(", ")})` : "";
|
|
2792
2954
|
let cssInfo = "";
|
|
2793
2955
|
if (f.source === "fontshare") {
|
|
2794
2956
|
cssInfo = ` CSS: ${fontData.cssUrlPattern.replace("{slug}", f.slug).replace("{weights}", f.weights.join(","))}`;
|
|
@@ -2797,7 +2959,8 @@ function getDesignExpertPrompt() {
|
|
|
2797
2959
|
} else if (f.source === "open-foundry") {
|
|
2798
2960
|
cssInfo = " (self-host required)";
|
|
2799
2961
|
}
|
|
2800
|
-
|
|
2962
|
+
const desc = f.description ? ` ${f.description}` : "";
|
|
2963
|
+
return `- **${f.name}** \u2014 ${f.category}. Weights: ${f.weights.join(", ")}.${f.variable ? " Variable." : ""}${f.italics ? " Has italics." : ""}${cssInfo}${desc}`;
|
|
2801
2964
|
}).join("\n");
|
|
2802
2965
|
const pairingList = pairings.map(
|
|
2803
2966
|
(p) => `- **${p.heading.font}** (${p.heading.weight}) heading + **${p.body.font}** (${p.body.weight}) body`
|
|
@@ -2815,13 +2978,13 @@ ${fontList}
|
|
|
2815
2978
|
${pairingList}
|
|
2816
2979
|
</fonts_to_consider>` : "";
|
|
2817
2980
|
const imageList = images.map((img) => `- ${img.analysis}`).join("\n\n");
|
|
2818
|
-
const inspirationSection = images.length ? `<
|
|
2981
|
+
const inspirationSection = images.length ? `<design_inspiration>
|
|
2819
2982
|
## Design inspiration
|
|
2820
2983
|
|
|
2821
|
-
This is what the bar looks like. These are real sites that made it onto curated design galleries because they did something bold, intentional, and memorable.
|
|
2984
|
+
This is what the bar looks like. These are real sites that made it onto curated design galleries because they did something bold, intentional, and memorable. Use them as inspiration and let the takeaways guide your work. Your designs should feel like they belong in this company.
|
|
2822
2985
|
|
|
2823
2986
|
${imageList}
|
|
2824
|
-
</
|
|
2987
|
+
</design_inspiration>` : "";
|
|
2825
2988
|
const specContext = loadSpecContext();
|
|
2826
2989
|
let prompt = PROMPT_TEMPLATE.replace(
|
|
2827
2990
|
"{{fonts_to_consider}}",
|
|
@@ -2863,7 +3026,7 @@ var designExpertTool = {
|
|
|
2863
3026
|
task: input.task,
|
|
2864
3027
|
tools: DESIGN_EXPERT_TOOLS,
|
|
2865
3028
|
externalTools: /* @__PURE__ */ new Set(),
|
|
2866
|
-
executeTool: executeDesignExpertTool,
|
|
3029
|
+
executeTool: (name, input2, toolCallId) => executeDesignExpertTool(name, input2, context, toolCallId),
|
|
2867
3030
|
apiConfig: context.apiConfig,
|
|
2868
3031
|
model: context.model,
|
|
2869
3032
|
subAgentId: "visualDesignExpert",
|
|
@@ -2976,8 +3139,8 @@ var VISION_TOOLS = [
|
|
|
2976
3139
|
];
|
|
2977
3140
|
|
|
2978
3141
|
// src/subagents/productVision/executor.ts
|
|
2979
|
-
import
|
|
2980
|
-
import
|
|
3142
|
+
import fs17 from "fs";
|
|
3143
|
+
import path11 from "path";
|
|
2981
3144
|
var ROADMAP_DIR = "src/roadmap";
|
|
2982
3145
|
function formatRequires(requires) {
|
|
2983
3146
|
return requires.length === 0 ? "[]" : `[${requires.map((r) => `"${r}"`).join(", ")}]`;
|
|
@@ -2993,9 +3156,10 @@ async function executeVisionTool(name, input) {
|
|
|
2993
3156
|
requires,
|
|
2994
3157
|
body
|
|
2995
3158
|
} = input;
|
|
2996
|
-
const filePath =
|
|
3159
|
+
const filePath = path11.join(ROADMAP_DIR, `${slug}.md`);
|
|
2997
3160
|
try {
|
|
2998
|
-
|
|
3161
|
+
fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
3162
|
+
const oldContent = fs17.existsSync(filePath) ? fs17.readFileSync(filePath, "utf-8") : "";
|
|
2999
3163
|
const content = `---
|
|
3000
3164
|
name: ${itemName}
|
|
3001
3165
|
type: roadmap
|
|
@@ -3007,20 +3171,24 @@ requires: ${formatRequires(requires)}
|
|
|
3007
3171
|
|
|
3008
3172
|
${body}
|
|
3009
3173
|
`;
|
|
3010
|
-
|
|
3011
|
-
|
|
3174
|
+
fs17.writeFileSync(filePath, content, "utf-8");
|
|
3175
|
+
const lineCount = content.split("\n").length;
|
|
3176
|
+
const label = oldContent ? "Updated" : "Wrote";
|
|
3177
|
+
return `${label} ${filePath} (${lineCount} lines)
|
|
3178
|
+
${unifiedDiff(filePath, oldContent, content)}`;
|
|
3012
3179
|
} catch (err) {
|
|
3013
3180
|
return `Error writing ${filePath}: ${err.message}`;
|
|
3014
3181
|
}
|
|
3015
3182
|
}
|
|
3016
3183
|
case "updateRoadmapItem": {
|
|
3017
3184
|
const { slug } = input;
|
|
3018
|
-
const filePath =
|
|
3185
|
+
const filePath = path11.join(ROADMAP_DIR, `${slug}.md`);
|
|
3019
3186
|
try {
|
|
3020
|
-
if (!
|
|
3187
|
+
if (!fs17.existsSync(filePath)) {
|
|
3021
3188
|
return `Error: ${filePath} does not exist`;
|
|
3022
3189
|
}
|
|
3023
|
-
|
|
3190
|
+
const oldContent = fs17.readFileSync(filePath, "utf-8");
|
|
3191
|
+
let content = oldContent;
|
|
3024
3192
|
if (input.status) {
|
|
3025
3193
|
content = content.replace(
|
|
3026
3194
|
/^status:\s*.+$/m,
|
|
@@ -3072,21 +3240,25 @@ ${input.appendHistory}
|
|
|
3072
3240
|
`;
|
|
3073
3241
|
}
|
|
3074
3242
|
}
|
|
3075
|
-
|
|
3076
|
-
|
|
3243
|
+
fs17.writeFileSync(filePath, content, "utf-8");
|
|
3244
|
+
const lineCount = content.split("\n").length;
|
|
3245
|
+
return `Updated ${filePath} (${lineCount} lines)
|
|
3246
|
+
${unifiedDiff(filePath, oldContent, content)}`;
|
|
3077
3247
|
} catch (err) {
|
|
3078
3248
|
return `Error updating ${filePath}: ${err.message}`;
|
|
3079
3249
|
}
|
|
3080
3250
|
}
|
|
3081
3251
|
case "deleteRoadmapItem": {
|
|
3082
3252
|
const { slug } = input;
|
|
3083
|
-
const filePath =
|
|
3253
|
+
const filePath = path11.join(ROADMAP_DIR, `${slug}.md`);
|
|
3084
3254
|
try {
|
|
3085
|
-
if (!
|
|
3255
|
+
if (!fs17.existsSync(filePath)) {
|
|
3086
3256
|
return `Error: ${filePath} does not exist`;
|
|
3087
3257
|
}
|
|
3088
|
-
|
|
3089
|
-
|
|
3258
|
+
const oldContent = fs17.readFileSync(filePath, "utf-8");
|
|
3259
|
+
fs17.unlinkSync(filePath);
|
|
3260
|
+
return `Deleted ${filePath}
|
|
3261
|
+
${unifiedDiff(filePath, oldContent, "")}`;
|
|
3090
3262
|
} catch (err) {
|
|
3091
3263
|
return `Error deleting ${filePath}: ${err.message}`;
|
|
3092
3264
|
}
|
|
@@ -3097,12 +3269,12 @@ ${input.appendHistory}
|
|
|
3097
3269
|
}
|
|
3098
3270
|
|
|
3099
3271
|
// src/subagents/productVision/prompt.ts
|
|
3100
|
-
import
|
|
3101
|
-
import
|
|
3102
|
-
var
|
|
3103
|
-
var local2 =
|
|
3104
|
-
var PROMPT_PATH2 =
|
|
3105
|
-
var BASE_PROMPT2 =
|
|
3272
|
+
import fs18 from "fs";
|
|
3273
|
+
import path12 from "path";
|
|
3274
|
+
var base4 = import.meta.dirname ?? path12.dirname(new URL(import.meta.url).pathname);
|
|
3275
|
+
var local2 = path12.join(base4, "prompt.md");
|
|
3276
|
+
var PROMPT_PATH2 = fs18.existsSync(local2) ? local2 : path12.join(base4, "subagents", "productVision", "prompt.md");
|
|
3277
|
+
var BASE_PROMPT2 = fs18.readFileSync(PROMPT_PATH2, "utf-8").trim();
|
|
3106
3278
|
function getProductVisionPrompt() {
|
|
3107
3279
|
const specContext = loadSpecContext();
|
|
3108
3280
|
const roadmapContext = loadRoadmapContext();
|
|
@@ -3156,8 +3328,8 @@ var productVisionTool = {
|
|
|
3156
3328
|
};
|
|
3157
3329
|
|
|
3158
3330
|
// src/subagents/codeSanityCheck/index.ts
|
|
3159
|
-
import
|
|
3160
|
-
import
|
|
3331
|
+
import fs19 from "fs";
|
|
3332
|
+
import path13 from "path";
|
|
3161
3333
|
|
|
3162
3334
|
// src/subagents/codeSanityCheck/tools.ts
|
|
3163
3335
|
var SANITY_CHECK_TOOLS = [
|
|
@@ -3251,10 +3423,10 @@ var SANITY_CHECK_TOOLS = [
|
|
|
3251
3423
|
];
|
|
3252
3424
|
|
|
3253
3425
|
// src/subagents/codeSanityCheck/index.ts
|
|
3254
|
-
var
|
|
3255
|
-
var local3 =
|
|
3256
|
-
var PROMPT_PATH3 =
|
|
3257
|
-
var BASE_PROMPT3 =
|
|
3426
|
+
var base5 = import.meta.dirname ?? path13.dirname(new URL(import.meta.url).pathname);
|
|
3427
|
+
var local3 = path13.join(base5, "prompt.md");
|
|
3428
|
+
var PROMPT_PATH3 = fs19.existsSync(local3) ? local3 : path13.join(base5, "subagents", "codeSanityCheck", "prompt.md");
|
|
3429
|
+
var BASE_PROMPT3 = fs19.readFileSync(PROMPT_PATH3, "utf-8").trim();
|
|
3258
3430
|
var codeSanityCheckTool = {
|
|
3259
3431
|
definition: {
|
|
3260
3432
|
name: "codeSanityCheck",
|
|
@@ -3381,11 +3553,11 @@ function executeTool(name, input, context) {
|
|
|
3381
3553
|
}
|
|
3382
3554
|
|
|
3383
3555
|
// src/session.ts
|
|
3384
|
-
import
|
|
3556
|
+
import fs20 from "fs";
|
|
3385
3557
|
var SESSION_FILE = ".remy-session.json";
|
|
3386
3558
|
function loadSession(state) {
|
|
3387
3559
|
try {
|
|
3388
|
-
const raw =
|
|
3560
|
+
const raw = fs20.readFileSync(SESSION_FILE, "utf-8");
|
|
3389
3561
|
const data = JSON.parse(raw);
|
|
3390
3562
|
if (Array.isArray(data.messages) && data.messages.length > 0) {
|
|
3391
3563
|
state.messages = sanitizeMessages(data.messages);
|
|
@@ -3433,7 +3605,7 @@ function sanitizeMessages(messages) {
|
|
|
3433
3605
|
}
|
|
3434
3606
|
function saveSession(state) {
|
|
3435
3607
|
try {
|
|
3436
|
-
|
|
3608
|
+
fs20.writeFileSync(
|
|
3437
3609
|
SESSION_FILE,
|
|
3438
3610
|
JSON.stringify({ messages: state.messages }, null, 2),
|
|
3439
3611
|
"utf-8"
|
|
@@ -3444,7 +3616,7 @@ function saveSession(state) {
|
|
|
3444
3616
|
function clearSession(state) {
|
|
3445
3617
|
state.messages = [];
|
|
3446
3618
|
try {
|
|
3447
|
-
|
|
3619
|
+
fs20.unlinkSync(SESSION_FILE);
|
|
3448
3620
|
} catch {
|
|
3449
3621
|
}
|
|
3450
3622
|
}
|
|
@@ -4151,13 +4323,46 @@ async function runTurn(params) {
|
|
|
4151
4323
|
}
|
|
4152
4324
|
|
|
4153
4325
|
// src/headless.ts
|
|
4154
|
-
var BASE_DIR = import.meta.dirname ??
|
|
4155
|
-
var ACTIONS_DIR =
|
|
4326
|
+
var BASE_DIR = import.meta.dirname ?? path14.dirname(new URL(import.meta.url).pathname);
|
|
4327
|
+
var ACTIONS_DIR = path14.join(BASE_DIR, "actions");
|
|
4156
4328
|
function loadActionPrompt(name) {
|
|
4157
|
-
return
|
|
4329
|
+
return fs21.readFileSync(path14.join(ACTIONS_DIR, `${name}.md`), "utf-8").trim();
|
|
4158
4330
|
}
|
|
4159
|
-
function emit(event, data) {
|
|
4160
|
-
|
|
4331
|
+
function emit(event, data, requestId) {
|
|
4332
|
+
const payload = { event, ...data };
|
|
4333
|
+
if (requestId) {
|
|
4334
|
+
payload.requestId = requestId;
|
|
4335
|
+
}
|
|
4336
|
+
process.stdout.write(JSON.stringify(payload) + "\n");
|
|
4337
|
+
}
|
|
4338
|
+
function handleGetHistory(state) {
|
|
4339
|
+
return { messages: state.messages };
|
|
4340
|
+
}
|
|
4341
|
+
function handleClear(state) {
|
|
4342
|
+
clearSession(state);
|
|
4343
|
+
return {};
|
|
4344
|
+
}
|
|
4345
|
+
function handleCancel(currentAbort, pendingTools) {
|
|
4346
|
+
if (currentAbort) {
|
|
4347
|
+
currentAbort.abort();
|
|
4348
|
+
}
|
|
4349
|
+
for (const [id, pending] of pendingTools) {
|
|
4350
|
+
clearTimeout(pending.timeout);
|
|
4351
|
+
pending.resolve("Error: cancelled");
|
|
4352
|
+
pendingTools.delete(id);
|
|
4353
|
+
}
|
|
4354
|
+
return {};
|
|
4355
|
+
}
|
|
4356
|
+
function dispatchSimple(requestId, eventName, handler) {
|
|
4357
|
+
try {
|
|
4358
|
+
const data = handler();
|
|
4359
|
+
if (eventName) {
|
|
4360
|
+
emit(eventName, data, requestId);
|
|
4361
|
+
}
|
|
4362
|
+
emit("completed", { success: true }, requestId);
|
|
4363
|
+
} catch (err) {
|
|
4364
|
+
emit("completed", { success: false, error: err.message }, requestId);
|
|
4365
|
+
}
|
|
4161
4366
|
}
|
|
4162
4367
|
async function startHeadless(opts = {}) {
|
|
4163
4368
|
const stderrWrite = (...args) => {
|
|
@@ -4176,72 +4381,15 @@ async function startHeadless(opts = {}) {
|
|
|
4176
4381
|
const state = createAgentState();
|
|
4177
4382
|
const resumed = loadSession(state);
|
|
4178
4383
|
if (resumed) {
|
|
4179
|
-
emit("session_restored", {
|
|
4180
|
-
messageCount: state.messages.length
|
|
4181
|
-
});
|
|
4384
|
+
emit("session_restored", { messageCount: state.messages.length });
|
|
4182
4385
|
}
|
|
4183
4386
|
let running = false;
|
|
4184
4387
|
let currentAbort = null;
|
|
4388
|
+
let currentRequestId;
|
|
4389
|
+
let completedEmitted = false;
|
|
4185
4390
|
const EXTERNAL_TOOL_TIMEOUT_MS = 3e5;
|
|
4186
4391
|
const pendingTools = /* @__PURE__ */ new Map();
|
|
4187
4392
|
const earlyResults = /* @__PURE__ */ new Map();
|
|
4188
|
-
function onEvent(e) {
|
|
4189
|
-
switch (e.type) {
|
|
4190
|
-
case "text":
|
|
4191
|
-
emit("text", {
|
|
4192
|
-
text: e.text,
|
|
4193
|
-
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
4194
|
-
});
|
|
4195
|
-
break;
|
|
4196
|
-
case "thinking":
|
|
4197
|
-
emit("thinking", {
|
|
4198
|
-
text: e.text,
|
|
4199
|
-
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
4200
|
-
});
|
|
4201
|
-
break;
|
|
4202
|
-
case "tool_input_delta":
|
|
4203
|
-
emit("tool_input_delta", {
|
|
4204
|
-
id: e.id,
|
|
4205
|
-
name: e.name,
|
|
4206
|
-
result: e.result,
|
|
4207
|
-
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
4208
|
-
});
|
|
4209
|
-
break;
|
|
4210
|
-
case "tool_start":
|
|
4211
|
-
emit("tool_start", {
|
|
4212
|
-
id: e.id,
|
|
4213
|
-
name: e.name,
|
|
4214
|
-
input: e.input,
|
|
4215
|
-
...e.partial && { partial: true },
|
|
4216
|
-
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
4217
|
-
});
|
|
4218
|
-
break;
|
|
4219
|
-
case "tool_done":
|
|
4220
|
-
emit("tool_done", {
|
|
4221
|
-
id: e.id,
|
|
4222
|
-
name: e.name,
|
|
4223
|
-
result: e.result,
|
|
4224
|
-
isError: e.isError,
|
|
4225
|
-
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
4226
|
-
});
|
|
4227
|
-
break;
|
|
4228
|
-
case "turn_started":
|
|
4229
|
-
emit("turn_started");
|
|
4230
|
-
break;
|
|
4231
|
-
case "turn_done":
|
|
4232
|
-
emit("turn_done");
|
|
4233
|
-
break;
|
|
4234
|
-
case "turn_cancelled":
|
|
4235
|
-
emit("turn_cancelled");
|
|
4236
|
-
break;
|
|
4237
|
-
case "error":
|
|
4238
|
-
emit("error", { error: e.error });
|
|
4239
|
-
break;
|
|
4240
|
-
case "status":
|
|
4241
|
-
emit("status", { message: e.message });
|
|
4242
|
-
break;
|
|
4243
|
-
}
|
|
4244
|
-
}
|
|
4245
4393
|
const USER_FACING_TOOLS = /* @__PURE__ */ new Set([
|
|
4246
4394
|
"promptUser",
|
|
4247
4395
|
"confirmDestructiveAction",
|
|
@@ -4272,6 +4420,158 @@ async function startHeadless(opts = {}) {
|
|
|
4272
4420
|
});
|
|
4273
4421
|
});
|
|
4274
4422
|
}
|
|
4423
|
+
function onEvent(e) {
|
|
4424
|
+
const rid = currentRequestId;
|
|
4425
|
+
switch (e.type) {
|
|
4426
|
+
// Suppressed — caller already knows the request started
|
|
4427
|
+
case "turn_started":
|
|
4428
|
+
return;
|
|
4429
|
+
// Terminal events — translate to `completed`
|
|
4430
|
+
case "turn_done":
|
|
4431
|
+
completedEmitted = true;
|
|
4432
|
+
emit("completed", { success: true }, rid);
|
|
4433
|
+
return;
|
|
4434
|
+
case "turn_cancelled":
|
|
4435
|
+
completedEmitted = true;
|
|
4436
|
+
emit("completed", { success: false, error: "cancelled" }, rid);
|
|
4437
|
+
return;
|
|
4438
|
+
// Streaming events — forward with requestId
|
|
4439
|
+
case "text":
|
|
4440
|
+
emit(
|
|
4441
|
+
"text",
|
|
4442
|
+
{
|
|
4443
|
+
text: e.text,
|
|
4444
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
4445
|
+
},
|
|
4446
|
+
rid
|
|
4447
|
+
);
|
|
4448
|
+
return;
|
|
4449
|
+
case "thinking":
|
|
4450
|
+
emit(
|
|
4451
|
+
"thinking",
|
|
4452
|
+
{
|
|
4453
|
+
text: e.text,
|
|
4454
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
4455
|
+
},
|
|
4456
|
+
rid
|
|
4457
|
+
);
|
|
4458
|
+
return;
|
|
4459
|
+
case "tool_input_delta":
|
|
4460
|
+
emit(
|
|
4461
|
+
"tool_input_delta",
|
|
4462
|
+
{
|
|
4463
|
+
id: e.id,
|
|
4464
|
+
name: e.name,
|
|
4465
|
+
result: e.result,
|
|
4466
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
4467
|
+
},
|
|
4468
|
+
rid
|
|
4469
|
+
);
|
|
4470
|
+
return;
|
|
4471
|
+
case "tool_start":
|
|
4472
|
+
emit(
|
|
4473
|
+
"tool_start",
|
|
4474
|
+
{
|
|
4475
|
+
id: e.id,
|
|
4476
|
+
name: e.name,
|
|
4477
|
+
input: e.input,
|
|
4478
|
+
...e.partial && { partial: true },
|
|
4479
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
4480
|
+
},
|
|
4481
|
+
rid
|
|
4482
|
+
);
|
|
4483
|
+
return;
|
|
4484
|
+
case "tool_done":
|
|
4485
|
+
emit(
|
|
4486
|
+
"tool_done",
|
|
4487
|
+
{
|
|
4488
|
+
id: e.id,
|
|
4489
|
+
name: e.name,
|
|
4490
|
+
result: e.result,
|
|
4491
|
+
isError: e.isError,
|
|
4492
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
4493
|
+
},
|
|
4494
|
+
rid
|
|
4495
|
+
);
|
|
4496
|
+
return;
|
|
4497
|
+
case "status":
|
|
4498
|
+
emit("status", { message: e.message }, rid);
|
|
4499
|
+
return;
|
|
4500
|
+
case "error":
|
|
4501
|
+
emit("error", { error: e.error }, rid);
|
|
4502
|
+
return;
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
async function handleMessage(parsed, requestId) {
|
|
4506
|
+
if (running) {
|
|
4507
|
+
emit(
|
|
4508
|
+
"error",
|
|
4509
|
+
{ error: "Agent is already processing a message" },
|
|
4510
|
+
requestId
|
|
4511
|
+
);
|
|
4512
|
+
emit(
|
|
4513
|
+
"completed",
|
|
4514
|
+
{ success: false, error: "Agent is already processing a message" },
|
|
4515
|
+
requestId
|
|
4516
|
+
);
|
|
4517
|
+
return;
|
|
4518
|
+
}
|
|
4519
|
+
running = true;
|
|
4520
|
+
currentRequestId = requestId;
|
|
4521
|
+
currentAbort = new AbortController();
|
|
4522
|
+
completedEmitted = false;
|
|
4523
|
+
const attachments = parsed.attachments;
|
|
4524
|
+
if (attachments?.length) {
|
|
4525
|
+
console.warn(
|
|
4526
|
+
`[headless] Message has ${attachments.length} attachment(s):`,
|
|
4527
|
+
attachments.map((a) => a.url)
|
|
4528
|
+
);
|
|
4529
|
+
}
|
|
4530
|
+
let userMessage = parsed.text ?? "";
|
|
4531
|
+
const isCommand = !!parsed.runCommand;
|
|
4532
|
+
if (parsed.runCommand === "sync") {
|
|
4533
|
+
userMessage = loadActionPrompt("sync");
|
|
4534
|
+
} else if (parsed.runCommand === "publish") {
|
|
4535
|
+
userMessage = loadActionPrompt("publish");
|
|
4536
|
+
} else if (parsed.runCommand === "buildFromInitialSpec") {
|
|
4537
|
+
userMessage = loadActionPrompt("buildFromInitialSpec");
|
|
4538
|
+
}
|
|
4539
|
+
const onboardingState = parsed.onboardingState ?? "onboardingFinished";
|
|
4540
|
+
const system = buildSystemPrompt(
|
|
4541
|
+
onboardingState,
|
|
4542
|
+
parsed.viewContext
|
|
4543
|
+
);
|
|
4544
|
+
try {
|
|
4545
|
+
await runTurn({
|
|
4546
|
+
state,
|
|
4547
|
+
userMessage,
|
|
4548
|
+
attachments,
|
|
4549
|
+
apiConfig: config,
|
|
4550
|
+
system,
|
|
4551
|
+
model: opts.model,
|
|
4552
|
+
onboardingState,
|
|
4553
|
+
signal: currentAbort.signal,
|
|
4554
|
+
onEvent,
|
|
4555
|
+
resolveExternalTool,
|
|
4556
|
+
hidden: isCommand
|
|
4557
|
+
});
|
|
4558
|
+
if (!completedEmitted) {
|
|
4559
|
+
emit(
|
|
4560
|
+
"completed",
|
|
4561
|
+
{ success: false, error: "Turn ended unexpectedly" },
|
|
4562
|
+
requestId
|
|
4563
|
+
);
|
|
4564
|
+
}
|
|
4565
|
+
} catch (err) {
|
|
4566
|
+
if (!completedEmitted) {
|
|
4567
|
+
emit("error", { error: err.message }, requestId);
|
|
4568
|
+
emit("completed", { success: false, error: err.message }, requestId);
|
|
4569
|
+
}
|
|
4570
|
+
}
|
|
4571
|
+
currentAbort = null;
|
|
4572
|
+
currentRequestId = void 0;
|
|
4573
|
+
running = false;
|
|
4574
|
+
}
|
|
4275
4575
|
const rl = createInterface({ input: process.stdin });
|
|
4276
4576
|
rl.on("line", async (line) => {
|
|
4277
4577
|
let parsed;
|
|
@@ -4281,82 +4581,42 @@ async function startHeadless(opts = {}) {
|
|
|
4281
4581
|
emit("error", { error: "Invalid JSON on stdin" });
|
|
4282
4582
|
return;
|
|
4283
4583
|
}
|
|
4284
|
-
|
|
4285
|
-
|
|
4584
|
+
const { action, requestId } = parsed;
|
|
4585
|
+
if (action === "tool_result" && parsed.id) {
|
|
4586
|
+
const id = parsed.id;
|
|
4587
|
+
const result = parsed.result ?? "";
|
|
4588
|
+
const pending = pendingTools.get(id);
|
|
4286
4589
|
if (pending) {
|
|
4287
|
-
pendingTools.delete(
|
|
4288
|
-
pending.resolve(
|
|
4590
|
+
pendingTools.delete(id);
|
|
4591
|
+
pending.resolve(result);
|
|
4289
4592
|
} else {
|
|
4290
|
-
earlyResults.set(
|
|
4593
|
+
earlyResults.set(id, result);
|
|
4291
4594
|
}
|
|
4292
4595
|
return;
|
|
4293
4596
|
}
|
|
4294
|
-
if (
|
|
4295
|
-
|
|
4296
|
-
messages: state.messages
|
|
4297
|
-
});
|
|
4597
|
+
if (action === "get_history") {
|
|
4598
|
+
dispatchSimple(requestId, "history", () => handleGetHistory(state));
|
|
4298
4599
|
return;
|
|
4299
4600
|
}
|
|
4300
|
-
if (
|
|
4301
|
-
|
|
4302
|
-
emit("session_cleared");
|
|
4601
|
+
if (action === "clear") {
|
|
4602
|
+
dispatchSimple(requestId, "session_cleared", () => handleClear(state));
|
|
4303
4603
|
return;
|
|
4304
4604
|
}
|
|
4305
|
-
if (
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
}
|
|
4309
|
-
for (const [id, pending] of pendingTools) {
|
|
4310
|
-
clearTimeout(pending.timeout);
|
|
4311
|
-
pending.resolve("Error: cancelled");
|
|
4312
|
-
pendingTools.delete(id);
|
|
4313
|
-
}
|
|
4605
|
+
if (action === "cancel") {
|
|
4606
|
+
handleCancel(currentAbort, pendingTools);
|
|
4607
|
+
emit("completed", { success: true }, requestId);
|
|
4314
4608
|
return;
|
|
4315
4609
|
}
|
|
4316
|
-
if (
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
return;
|
|
4320
|
-
}
|
|
4321
|
-
running = true;
|
|
4322
|
-
currentAbort = new AbortController();
|
|
4323
|
-
if (parsed.attachments?.length) {
|
|
4324
|
-
console.warn(
|
|
4325
|
-
`[headless] Message has ${parsed.attachments.length} attachment(s):`,
|
|
4326
|
-
parsed.attachments.map((a) => a.url)
|
|
4327
|
-
);
|
|
4328
|
-
}
|
|
4329
|
-
let userMessage = parsed.text ?? "";
|
|
4330
|
-
const isCommand = !!parsed.runCommand;
|
|
4331
|
-
if (parsed.runCommand === "sync") {
|
|
4332
|
-
userMessage = loadActionPrompt("sync");
|
|
4333
|
-
} else if (parsed.runCommand === "publish") {
|
|
4334
|
-
userMessage = loadActionPrompt("publish");
|
|
4335
|
-
} else if (parsed.runCommand === "buildFromInitialSpec") {
|
|
4336
|
-
userMessage = loadActionPrompt("buildFromInitialSpec");
|
|
4337
|
-
}
|
|
4338
|
-
const onboardingState = parsed.onboardingState ?? "onboardingFinished";
|
|
4339
|
-
const system = buildSystemPrompt(onboardingState, parsed.viewContext);
|
|
4340
|
-
try {
|
|
4341
|
-
await runTurn({
|
|
4342
|
-
state,
|
|
4343
|
-
userMessage,
|
|
4344
|
-
attachments: parsed.attachments,
|
|
4345
|
-
apiConfig: config,
|
|
4346
|
-
system,
|
|
4347
|
-
model: opts.model,
|
|
4348
|
-
onboardingState,
|
|
4349
|
-
signal: currentAbort.signal,
|
|
4350
|
-
onEvent,
|
|
4351
|
-
resolveExternalTool,
|
|
4352
|
-
hidden: isCommand
|
|
4353
|
-
});
|
|
4354
|
-
} catch (err) {
|
|
4355
|
-
emit("error", { error: err.message });
|
|
4356
|
-
}
|
|
4357
|
-
currentAbort = null;
|
|
4358
|
-
running = false;
|
|
4610
|
+
if (action === "message") {
|
|
4611
|
+
await handleMessage(parsed, requestId);
|
|
4612
|
+
return;
|
|
4359
4613
|
}
|
|
4614
|
+
emit("error", { error: `Unknown action: ${action}` }, requestId);
|
|
4615
|
+
emit(
|
|
4616
|
+
"completed",
|
|
4617
|
+
{ success: false, error: `Unknown action: ${action}` },
|
|
4618
|
+
requestId
|
|
4619
|
+
);
|
|
4360
4620
|
});
|
|
4361
4621
|
rl.on("close", () => {
|
|
4362
4622
|
emit("stopping");
|