@mindstudio-ai/remy 0.1.26 → 0.1.27
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 +524 -270
- package/dist/index.js +567 -300
- 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 +1 -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 viewportOnly = false;
|
|
2080
|
+
if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
|
|
2081
|
+
prompt = promptOrOptions.prompt;
|
|
2082
|
+
viewportOnly = promptOrOptions.viewportOnly ?? false;
|
|
2083
|
+
} else {
|
|
2084
|
+
prompt = promptOrOptions;
|
|
2085
|
+
}
|
|
2086
|
+
const ssResult = await sidecarRequest(
|
|
2087
|
+
"/screenshot",
|
|
2088
|
+
{ fullPage: !viewportOnly },
|
|
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. Set viewportOnly to capture just what the user sees on screen.",
|
|
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
|
+
viewportOnly: {
|
|
2121
|
+
type: "boolean",
|
|
2122
|
+
description: "Capture only the visible viewport instead of the full scrollable page. Use when checking above-the-fold layout or viewport-relative sizing like 100vh."
|
|
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
|
+
viewportOnly: input.viewportOnly
|
|
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
|
|
@@ -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. Set viewportOnly to capture just what the user sees on screen.",
|
|
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
|
+
viewportOnly: {
|
|
2641
|
+
type: "boolean",
|
|
2642
|
+
description: "Capture only the visible viewport instead of the full scrollable page. Use when checking above-the-fold layout or viewport-relative sizing like 100vh."
|
|
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) {
|
|
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
|
+
viewportOnly: input.viewportOnly
|
|
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,24 @@ ${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({ task: input.task }, context);
|
|
2637
2794
|
}
|
|
2638
2795
|
default:
|
|
2639
2796
|
return `Error: unknown tool "${name}"`;
|
|
@@ -2641,17 +2798,17 @@ Analysis: ${analysis}`;
|
|
|
2641
2798
|
}
|
|
2642
2799
|
|
|
2643
2800
|
// src/subagents/designExpert/prompt.ts
|
|
2644
|
-
import
|
|
2645
|
-
import
|
|
2801
|
+
import fs16 from "fs";
|
|
2802
|
+
import path10 from "path";
|
|
2646
2803
|
|
|
2647
2804
|
// src/subagents/common/context.ts
|
|
2648
|
-
import
|
|
2649
|
-
import
|
|
2805
|
+
import fs15 from "fs";
|
|
2806
|
+
import path9 from "path";
|
|
2650
2807
|
function walkMdFiles2(dir, skip) {
|
|
2651
2808
|
const files = [];
|
|
2652
2809
|
try {
|
|
2653
|
-
for (const entry of
|
|
2654
|
-
const full =
|
|
2810
|
+
for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
|
|
2811
|
+
const full = path9.join(dir, entry.name);
|
|
2655
2812
|
if (entry.isDirectory()) {
|
|
2656
2813
|
if (!skip?.has(entry.name)) {
|
|
2657
2814
|
files.push(...walkMdFiles2(full, skip));
|
|
@@ -2671,7 +2828,7 @@ function loadFilesAsXml(dir, tag, skip) {
|
|
|
2671
2828
|
}
|
|
2672
2829
|
const sections = files.map((f) => {
|
|
2673
2830
|
try {
|
|
2674
|
-
const content =
|
|
2831
|
+
const content = fs15.readFileSync(f, "utf-8").trim();
|
|
2675
2832
|
return `<file path="${f}">
|
|
2676
2833
|
${content}
|
|
2677
2834
|
</file>`;
|
|
@@ -2741,17 +2898,17 @@ The first-party SDK (@mindstudio-ai/agent) provides access to 200+ AI models (Op
|
|
|
2741
2898
|
}
|
|
2742
2899
|
|
|
2743
2900
|
// src/subagents/designExpert/prompt.ts
|
|
2744
|
-
var
|
|
2745
|
-
function
|
|
2746
|
-
const local4 =
|
|
2747
|
-
return
|
|
2901
|
+
var base3 = import.meta.dirname ?? path10.dirname(new URL(import.meta.url).pathname);
|
|
2902
|
+
function resolvePath2(filename) {
|
|
2903
|
+
const local4 = path10.join(base3, filename);
|
|
2904
|
+
return fs16.existsSync(local4) ? local4 : path10.join(base3, "subagents", "designExpert", filename);
|
|
2748
2905
|
}
|
|
2749
2906
|
function readFile(filename) {
|
|
2750
|
-
return
|
|
2907
|
+
return fs16.readFileSync(resolvePath2(filename), "utf-8").trim();
|
|
2751
2908
|
}
|
|
2752
2909
|
function readJson(filename, fallback) {
|
|
2753
2910
|
try {
|
|
2754
|
-
return JSON.parse(
|
|
2911
|
+
return JSON.parse(fs16.readFileSync(resolvePath2(filename), "utf-8"));
|
|
2755
2912
|
} catch {
|
|
2756
2913
|
return fallback;
|
|
2757
2914
|
}
|
|
@@ -2788,7 +2945,6 @@ function getDesignExpertPrompt() {
|
|
|
2788
2945
|
const pairings = sample(fontData.pairings, 20);
|
|
2789
2946
|
const images = sample(inspirationImages, 15);
|
|
2790
2947
|
const fontList = fonts.map((f) => {
|
|
2791
|
-
const tags = f.tags.length ? ` (${f.tags.join(", ")})` : "";
|
|
2792
2948
|
let cssInfo = "";
|
|
2793
2949
|
if (f.source === "fontshare") {
|
|
2794
2950
|
cssInfo = ` CSS: ${fontData.cssUrlPattern.replace("{slug}", f.slug).replace("{weights}", f.weights.join(","))}`;
|
|
@@ -2797,7 +2953,8 @@ function getDesignExpertPrompt() {
|
|
|
2797
2953
|
} else if (f.source === "open-foundry") {
|
|
2798
2954
|
cssInfo = " (self-host required)";
|
|
2799
2955
|
}
|
|
2800
|
-
|
|
2956
|
+
const desc = f.description ? ` ${f.description}` : "";
|
|
2957
|
+
return `- **${f.name}** \u2014 ${f.category}. Weights: ${f.weights.join(", ")}.${f.variable ? " Variable." : ""}${f.italics ? " Has italics." : ""}${cssInfo}${desc}`;
|
|
2801
2958
|
}).join("\n");
|
|
2802
2959
|
const pairingList = pairings.map(
|
|
2803
2960
|
(p) => `- **${p.heading.font}** (${p.heading.weight}) heading + **${p.body.font}** (${p.body.weight}) body`
|
|
@@ -2815,13 +2972,13 @@ ${fontList}
|
|
|
2815
2972
|
${pairingList}
|
|
2816
2973
|
</fonts_to_consider>` : "";
|
|
2817
2974
|
const imageList = images.map((img) => `- ${img.analysis}`).join("\n\n");
|
|
2818
|
-
const inspirationSection = images.length ? `<
|
|
2975
|
+
const inspirationSection = images.length ? `<design_inspiration>
|
|
2819
2976
|
## Design inspiration
|
|
2820
2977
|
|
|
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.
|
|
2978
|
+
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
2979
|
|
|
2823
2980
|
${imageList}
|
|
2824
|
-
</
|
|
2981
|
+
</design_inspiration>` : "";
|
|
2825
2982
|
const specContext = loadSpecContext();
|
|
2826
2983
|
let prompt = PROMPT_TEMPLATE.replace(
|
|
2827
2984
|
"{{fonts_to_consider}}",
|
|
@@ -2863,7 +3020,7 @@ var designExpertTool = {
|
|
|
2863
3020
|
task: input.task,
|
|
2864
3021
|
tools: DESIGN_EXPERT_TOOLS,
|
|
2865
3022
|
externalTools: /* @__PURE__ */ new Set(),
|
|
2866
|
-
executeTool: executeDesignExpertTool,
|
|
3023
|
+
executeTool: (name, input2) => executeDesignExpertTool(name, input2, context),
|
|
2867
3024
|
apiConfig: context.apiConfig,
|
|
2868
3025
|
model: context.model,
|
|
2869
3026
|
subAgentId: "visualDesignExpert",
|
|
@@ -2976,8 +3133,8 @@ var VISION_TOOLS = [
|
|
|
2976
3133
|
];
|
|
2977
3134
|
|
|
2978
3135
|
// src/subagents/productVision/executor.ts
|
|
2979
|
-
import
|
|
2980
|
-
import
|
|
3136
|
+
import fs17 from "fs";
|
|
3137
|
+
import path11 from "path";
|
|
2981
3138
|
var ROADMAP_DIR = "src/roadmap";
|
|
2982
3139
|
function formatRequires(requires) {
|
|
2983
3140
|
return requires.length === 0 ? "[]" : `[${requires.map((r) => `"${r}"`).join(", ")}]`;
|
|
@@ -2993,9 +3150,10 @@ async function executeVisionTool(name, input) {
|
|
|
2993
3150
|
requires,
|
|
2994
3151
|
body
|
|
2995
3152
|
} = input;
|
|
2996
|
-
const filePath =
|
|
3153
|
+
const filePath = path11.join(ROADMAP_DIR, `${slug}.md`);
|
|
2997
3154
|
try {
|
|
2998
|
-
|
|
3155
|
+
fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
3156
|
+
const oldContent = fs17.existsSync(filePath) ? fs17.readFileSync(filePath, "utf-8") : "";
|
|
2999
3157
|
const content = `---
|
|
3000
3158
|
name: ${itemName}
|
|
3001
3159
|
type: roadmap
|
|
@@ -3007,20 +3165,24 @@ requires: ${formatRequires(requires)}
|
|
|
3007
3165
|
|
|
3008
3166
|
${body}
|
|
3009
3167
|
`;
|
|
3010
|
-
|
|
3011
|
-
|
|
3168
|
+
fs17.writeFileSync(filePath, content, "utf-8");
|
|
3169
|
+
const lineCount = content.split("\n").length;
|
|
3170
|
+
const label = oldContent ? "Updated" : "Wrote";
|
|
3171
|
+
return `${label} ${filePath} (${lineCount} lines)
|
|
3172
|
+
${unifiedDiff(filePath, oldContent, content)}`;
|
|
3012
3173
|
} catch (err) {
|
|
3013
3174
|
return `Error writing ${filePath}: ${err.message}`;
|
|
3014
3175
|
}
|
|
3015
3176
|
}
|
|
3016
3177
|
case "updateRoadmapItem": {
|
|
3017
3178
|
const { slug } = input;
|
|
3018
|
-
const filePath =
|
|
3179
|
+
const filePath = path11.join(ROADMAP_DIR, `${slug}.md`);
|
|
3019
3180
|
try {
|
|
3020
|
-
if (!
|
|
3181
|
+
if (!fs17.existsSync(filePath)) {
|
|
3021
3182
|
return `Error: ${filePath} does not exist`;
|
|
3022
3183
|
}
|
|
3023
|
-
|
|
3184
|
+
const oldContent = fs17.readFileSync(filePath, "utf-8");
|
|
3185
|
+
let content = oldContent;
|
|
3024
3186
|
if (input.status) {
|
|
3025
3187
|
content = content.replace(
|
|
3026
3188
|
/^status:\s*.+$/m,
|
|
@@ -3072,21 +3234,25 @@ ${input.appendHistory}
|
|
|
3072
3234
|
`;
|
|
3073
3235
|
}
|
|
3074
3236
|
}
|
|
3075
|
-
|
|
3076
|
-
|
|
3237
|
+
fs17.writeFileSync(filePath, content, "utf-8");
|
|
3238
|
+
const lineCount = content.split("\n").length;
|
|
3239
|
+
return `Updated ${filePath} (${lineCount} lines)
|
|
3240
|
+
${unifiedDiff(filePath, oldContent, content)}`;
|
|
3077
3241
|
} catch (err) {
|
|
3078
3242
|
return `Error updating ${filePath}: ${err.message}`;
|
|
3079
3243
|
}
|
|
3080
3244
|
}
|
|
3081
3245
|
case "deleteRoadmapItem": {
|
|
3082
3246
|
const { slug } = input;
|
|
3083
|
-
const filePath =
|
|
3247
|
+
const filePath = path11.join(ROADMAP_DIR, `${slug}.md`);
|
|
3084
3248
|
try {
|
|
3085
|
-
if (!
|
|
3249
|
+
if (!fs17.existsSync(filePath)) {
|
|
3086
3250
|
return `Error: ${filePath} does not exist`;
|
|
3087
3251
|
}
|
|
3088
|
-
|
|
3089
|
-
|
|
3252
|
+
const oldContent = fs17.readFileSync(filePath, "utf-8");
|
|
3253
|
+
fs17.unlinkSync(filePath);
|
|
3254
|
+
return `Deleted ${filePath}
|
|
3255
|
+
${unifiedDiff(filePath, oldContent, "")}`;
|
|
3090
3256
|
} catch (err) {
|
|
3091
3257
|
return `Error deleting ${filePath}: ${err.message}`;
|
|
3092
3258
|
}
|
|
@@ -3097,12 +3263,12 @@ ${input.appendHistory}
|
|
|
3097
3263
|
}
|
|
3098
3264
|
|
|
3099
3265
|
// src/subagents/productVision/prompt.ts
|
|
3100
|
-
import
|
|
3101
|
-
import
|
|
3102
|
-
var
|
|
3103
|
-
var local2 =
|
|
3104
|
-
var PROMPT_PATH2 =
|
|
3105
|
-
var BASE_PROMPT2 =
|
|
3266
|
+
import fs18 from "fs";
|
|
3267
|
+
import path12 from "path";
|
|
3268
|
+
var base4 = import.meta.dirname ?? path12.dirname(new URL(import.meta.url).pathname);
|
|
3269
|
+
var local2 = path12.join(base4, "prompt.md");
|
|
3270
|
+
var PROMPT_PATH2 = fs18.existsSync(local2) ? local2 : path12.join(base4, "subagents", "productVision", "prompt.md");
|
|
3271
|
+
var BASE_PROMPT2 = fs18.readFileSync(PROMPT_PATH2, "utf-8").trim();
|
|
3106
3272
|
function getProductVisionPrompt() {
|
|
3107
3273
|
const specContext = loadSpecContext();
|
|
3108
3274
|
const roadmapContext = loadRoadmapContext();
|
|
@@ -3156,8 +3322,8 @@ var productVisionTool = {
|
|
|
3156
3322
|
};
|
|
3157
3323
|
|
|
3158
3324
|
// src/subagents/codeSanityCheck/index.ts
|
|
3159
|
-
import
|
|
3160
|
-
import
|
|
3325
|
+
import fs19 from "fs";
|
|
3326
|
+
import path13 from "path";
|
|
3161
3327
|
|
|
3162
3328
|
// src/subagents/codeSanityCheck/tools.ts
|
|
3163
3329
|
var SANITY_CHECK_TOOLS = [
|
|
@@ -3251,10 +3417,10 @@ var SANITY_CHECK_TOOLS = [
|
|
|
3251
3417
|
];
|
|
3252
3418
|
|
|
3253
3419
|
// src/subagents/codeSanityCheck/index.ts
|
|
3254
|
-
var
|
|
3255
|
-
var local3 =
|
|
3256
|
-
var PROMPT_PATH3 =
|
|
3257
|
-
var BASE_PROMPT3 =
|
|
3420
|
+
var base5 = import.meta.dirname ?? path13.dirname(new URL(import.meta.url).pathname);
|
|
3421
|
+
var local3 = path13.join(base5, "prompt.md");
|
|
3422
|
+
var PROMPT_PATH3 = fs19.existsSync(local3) ? local3 : path13.join(base5, "subagents", "codeSanityCheck", "prompt.md");
|
|
3423
|
+
var BASE_PROMPT3 = fs19.readFileSync(PROMPT_PATH3, "utf-8").trim();
|
|
3258
3424
|
var codeSanityCheckTool = {
|
|
3259
3425
|
definition: {
|
|
3260
3426
|
name: "codeSanityCheck",
|
|
@@ -3381,11 +3547,11 @@ function executeTool(name, input, context) {
|
|
|
3381
3547
|
}
|
|
3382
3548
|
|
|
3383
3549
|
// src/session.ts
|
|
3384
|
-
import
|
|
3550
|
+
import fs20 from "fs";
|
|
3385
3551
|
var SESSION_FILE = ".remy-session.json";
|
|
3386
3552
|
function loadSession(state) {
|
|
3387
3553
|
try {
|
|
3388
|
-
const raw =
|
|
3554
|
+
const raw = fs20.readFileSync(SESSION_FILE, "utf-8");
|
|
3389
3555
|
const data = JSON.parse(raw);
|
|
3390
3556
|
if (Array.isArray(data.messages) && data.messages.length > 0) {
|
|
3391
3557
|
state.messages = sanitizeMessages(data.messages);
|
|
@@ -3433,7 +3599,7 @@ function sanitizeMessages(messages) {
|
|
|
3433
3599
|
}
|
|
3434
3600
|
function saveSession(state) {
|
|
3435
3601
|
try {
|
|
3436
|
-
|
|
3602
|
+
fs20.writeFileSync(
|
|
3437
3603
|
SESSION_FILE,
|
|
3438
3604
|
JSON.stringify({ messages: state.messages }, null, 2),
|
|
3439
3605
|
"utf-8"
|
|
@@ -3444,7 +3610,7 @@ function saveSession(state) {
|
|
|
3444
3610
|
function clearSession(state) {
|
|
3445
3611
|
state.messages = [];
|
|
3446
3612
|
try {
|
|
3447
|
-
|
|
3613
|
+
fs20.unlinkSync(SESSION_FILE);
|
|
3448
3614
|
} catch {
|
|
3449
3615
|
}
|
|
3450
3616
|
}
|
|
@@ -4151,13 +4317,46 @@ async function runTurn(params) {
|
|
|
4151
4317
|
}
|
|
4152
4318
|
|
|
4153
4319
|
// src/headless.ts
|
|
4154
|
-
var BASE_DIR = import.meta.dirname ??
|
|
4155
|
-
var ACTIONS_DIR =
|
|
4320
|
+
var BASE_DIR = import.meta.dirname ?? path14.dirname(new URL(import.meta.url).pathname);
|
|
4321
|
+
var ACTIONS_DIR = path14.join(BASE_DIR, "actions");
|
|
4156
4322
|
function loadActionPrompt(name) {
|
|
4157
|
-
return
|
|
4323
|
+
return fs21.readFileSync(path14.join(ACTIONS_DIR, `${name}.md`), "utf-8").trim();
|
|
4324
|
+
}
|
|
4325
|
+
function emit(event, data, requestId) {
|
|
4326
|
+
const payload = { event, ...data };
|
|
4327
|
+
if (requestId) {
|
|
4328
|
+
payload.requestId = requestId;
|
|
4329
|
+
}
|
|
4330
|
+
process.stdout.write(JSON.stringify(payload) + "\n");
|
|
4158
4331
|
}
|
|
4159
|
-
function
|
|
4160
|
-
|
|
4332
|
+
function handleGetHistory(state) {
|
|
4333
|
+
return { messages: state.messages };
|
|
4334
|
+
}
|
|
4335
|
+
function handleClear(state) {
|
|
4336
|
+
clearSession(state);
|
|
4337
|
+
return {};
|
|
4338
|
+
}
|
|
4339
|
+
function handleCancel(currentAbort, pendingTools) {
|
|
4340
|
+
if (currentAbort) {
|
|
4341
|
+
currentAbort.abort();
|
|
4342
|
+
}
|
|
4343
|
+
for (const [id, pending] of pendingTools) {
|
|
4344
|
+
clearTimeout(pending.timeout);
|
|
4345
|
+
pending.resolve("Error: cancelled");
|
|
4346
|
+
pendingTools.delete(id);
|
|
4347
|
+
}
|
|
4348
|
+
return {};
|
|
4349
|
+
}
|
|
4350
|
+
function dispatchSimple(requestId, eventName, handler) {
|
|
4351
|
+
try {
|
|
4352
|
+
const data = handler();
|
|
4353
|
+
if (eventName) {
|
|
4354
|
+
emit(eventName, data, requestId);
|
|
4355
|
+
}
|
|
4356
|
+
emit("completed", { success: true }, requestId);
|
|
4357
|
+
} catch (err) {
|
|
4358
|
+
emit("completed", { success: false, error: err.message }, requestId);
|
|
4359
|
+
}
|
|
4161
4360
|
}
|
|
4162
4361
|
async function startHeadless(opts = {}) {
|
|
4163
4362
|
const stderrWrite = (...args) => {
|
|
@@ -4176,72 +4375,15 @@ async function startHeadless(opts = {}) {
|
|
|
4176
4375
|
const state = createAgentState();
|
|
4177
4376
|
const resumed = loadSession(state);
|
|
4178
4377
|
if (resumed) {
|
|
4179
|
-
emit("session_restored", {
|
|
4180
|
-
messageCount: state.messages.length
|
|
4181
|
-
});
|
|
4378
|
+
emit("session_restored", { messageCount: state.messages.length });
|
|
4182
4379
|
}
|
|
4183
4380
|
let running = false;
|
|
4184
4381
|
let currentAbort = null;
|
|
4382
|
+
let currentRequestId;
|
|
4383
|
+
let completedEmitted = false;
|
|
4185
4384
|
const EXTERNAL_TOOL_TIMEOUT_MS = 3e5;
|
|
4186
4385
|
const pendingTools = /* @__PURE__ */ new Map();
|
|
4187
4386
|
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
4387
|
const USER_FACING_TOOLS = /* @__PURE__ */ new Set([
|
|
4246
4388
|
"promptUser",
|
|
4247
4389
|
"confirmDestructiveAction",
|
|
@@ -4272,6 +4414,158 @@ async function startHeadless(opts = {}) {
|
|
|
4272
4414
|
});
|
|
4273
4415
|
});
|
|
4274
4416
|
}
|
|
4417
|
+
function onEvent(e) {
|
|
4418
|
+
const rid = currentRequestId;
|
|
4419
|
+
switch (e.type) {
|
|
4420
|
+
// Suppressed — caller already knows the request started
|
|
4421
|
+
case "turn_started":
|
|
4422
|
+
return;
|
|
4423
|
+
// Terminal events — translate to `completed`
|
|
4424
|
+
case "turn_done":
|
|
4425
|
+
completedEmitted = true;
|
|
4426
|
+
emit("completed", { success: true }, rid);
|
|
4427
|
+
return;
|
|
4428
|
+
case "turn_cancelled":
|
|
4429
|
+
completedEmitted = true;
|
|
4430
|
+
emit("completed", { success: false, error: "cancelled" }, rid);
|
|
4431
|
+
return;
|
|
4432
|
+
// Streaming events — forward with requestId
|
|
4433
|
+
case "text":
|
|
4434
|
+
emit(
|
|
4435
|
+
"text",
|
|
4436
|
+
{
|
|
4437
|
+
text: e.text,
|
|
4438
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
4439
|
+
},
|
|
4440
|
+
rid
|
|
4441
|
+
);
|
|
4442
|
+
return;
|
|
4443
|
+
case "thinking":
|
|
4444
|
+
emit(
|
|
4445
|
+
"thinking",
|
|
4446
|
+
{
|
|
4447
|
+
text: e.text,
|
|
4448
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
4449
|
+
},
|
|
4450
|
+
rid
|
|
4451
|
+
);
|
|
4452
|
+
return;
|
|
4453
|
+
case "tool_input_delta":
|
|
4454
|
+
emit(
|
|
4455
|
+
"tool_input_delta",
|
|
4456
|
+
{
|
|
4457
|
+
id: e.id,
|
|
4458
|
+
name: e.name,
|
|
4459
|
+
result: e.result,
|
|
4460
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
4461
|
+
},
|
|
4462
|
+
rid
|
|
4463
|
+
);
|
|
4464
|
+
return;
|
|
4465
|
+
case "tool_start":
|
|
4466
|
+
emit(
|
|
4467
|
+
"tool_start",
|
|
4468
|
+
{
|
|
4469
|
+
id: e.id,
|
|
4470
|
+
name: e.name,
|
|
4471
|
+
input: e.input,
|
|
4472
|
+
...e.partial && { partial: true },
|
|
4473
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
4474
|
+
},
|
|
4475
|
+
rid
|
|
4476
|
+
);
|
|
4477
|
+
return;
|
|
4478
|
+
case "tool_done":
|
|
4479
|
+
emit(
|
|
4480
|
+
"tool_done",
|
|
4481
|
+
{
|
|
4482
|
+
id: e.id,
|
|
4483
|
+
name: e.name,
|
|
4484
|
+
result: e.result,
|
|
4485
|
+
isError: e.isError,
|
|
4486
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
4487
|
+
},
|
|
4488
|
+
rid
|
|
4489
|
+
);
|
|
4490
|
+
return;
|
|
4491
|
+
case "status":
|
|
4492
|
+
emit("status", { message: e.message }, rid);
|
|
4493
|
+
return;
|
|
4494
|
+
case "error":
|
|
4495
|
+
emit("error", { error: e.error }, rid);
|
|
4496
|
+
return;
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4499
|
+
async function handleMessage(parsed, requestId) {
|
|
4500
|
+
if (running) {
|
|
4501
|
+
emit(
|
|
4502
|
+
"error",
|
|
4503
|
+
{ error: "Agent is already processing a message" },
|
|
4504
|
+
requestId
|
|
4505
|
+
);
|
|
4506
|
+
emit(
|
|
4507
|
+
"completed",
|
|
4508
|
+
{ success: false, error: "Agent is already processing a message" },
|
|
4509
|
+
requestId
|
|
4510
|
+
);
|
|
4511
|
+
return;
|
|
4512
|
+
}
|
|
4513
|
+
running = true;
|
|
4514
|
+
currentRequestId = requestId;
|
|
4515
|
+
currentAbort = new AbortController();
|
|
4516
|
+
completedEmitted = false;
|
|
4517
|
+
const attachments = parsed.attachments;
|
|
4518
|
+
if (attachments?.length) {
|
|
4519
|
+
console.warn(
|
|
4520
|
+
`[headless] Message has ${attachments.length} attachment(s):`,
|
|
4521
|
+
attachments.map((a) => a.url)
|
|
4522
|
+
);
|
|
4523
|
+
}
|
|
4524
|
+
let userMessage = parsed.text ?? "";
|
|
4525
|
+
const isCommand = !!parsed.runCommand;
|
|
4526
|
+
if (parsed.runCommand === "sync") {
|
|
4527
|
+
userMessage = loadActionPrompt("sync");
|
|
4528
|
+
} else if (parsed.runCommand === "publish") {
|
|
4529
|
+
userMessage = loadActionPrompt("publish");
|
|
4530
|
+
} else if (parsed.runCommand === "buildFromInitialSpec") {
|
|
4531
|
+
userMessage = loadActionPrompt("buildFromInitialSpec");
|
|
4532
|
+
}
|
|
4533
|
+
const onboardingState = parsed.onboardingState ?? "onboardingFinished";
|
|
4534
|
+
const system = buildSystemPrompt(
|
|
4535
|
+
onboardingState,
|
|
4536
|
+
parsed.viewContext
|
|
4537
|
+
);
|
|
4538
|
+
try {
|
|
4539
|
+
await runTurn({
|
|
4540
|
+
state,
|
|
4541
|
+
userMessage,
|
|
4542
|
+
attachments,
|
|
4543
|
+
apiConfig: config,
|
|
4544
|
+
system,
|
|
4545
|
+
model: opts.model,
|
|
4546
|
+
onboardingState,
|
|
4547
|
+
signal: currentAbort.signal,
|
|
4548
|
+
onEvent,
|
|
4549
|
+
resolveExternalTool,
|
|
4550
|
+
hidden: isCommand
|
|
4551
|
+
});
|
|
4552
|
+
if (!completedEmitted) {
|
|
4553
|
+
emit(
|
|
4554
|
+
"completed",
|
|
4555
|
+
{ success: false, error: "Turn ended unexpectedly" },
|
|
4556
|
+
requestId
|
|
4557
|
+
);
|
|
4558
|
+
}
|
|
4559
|
+
} catch (err) {
|
|
4560
|
+
if (!completedEmitted) {
|
|
4561
|
+
emit("error", { error: err.message }, requestId);
|
|
4562
|
+
emit("completed", { success: false, error: err.message }, requestId);
|
|
4563
|
+
}
|
|
4564
|
+
}
|
|
4565
|
+
currentAbort = null;
|
|
4566
|
+
currentRequestId = void 0;
|
|
4567
|
+
running = false;
|
|
4568
|
+
}
|
|
4275
4569
|
const rl = createInterface({ input: process.stdin });
|
|
4276
4570
|
rl.on("line", async (line) => {
|
|
4277
4571
|
let parsed;
|
|
@@ -4281,82 +4575,42 @@ async function startHeadless(opts = {}) {
|
|
|
4281
4575
|
emit("error", { error: "Invalid JSON on stdin" });
|
|
4282
4576
|
return;
|
|
4283
4577
|
}
|
|
4284
|
-
|
|
4285
|
-
|
|
4578
|
+
const { action, requestId } = parsed;
|
|
4579
|
+
if (action === "tool_result" && parsed.id) {
|
|
4580
|
+
const id = parsed.id;
|
|
4581
|
+
const result = parsed.result ?? "";
|
|
4582
|
+
const pending = pendingTools.get(id);
|
|
4286
4583
|
if (pending) {
|
|
4287
|
-
pendingTools.delete(
|
|
4288
|
-
pending.resolve(
|
|
4584
|
+
pendingTools.delete(id);
|
|
4585
|
+
pending.resolve(result);
|
|
4289
4586
|
} else {
|
|
4290
|
-
earlyResults.set(
|
|
4587
|
+
earlyResults.set(id, result);
|
|
4291
4588
|
}
|
|
4292
4589
|
return;
|
|
4293
4590
|
}
|
|
4294
|
-
if (
|
|
4295
|
-
|
|
4296
|
-
messages: state.messages
|
|
4297
|
-
});
|
|
4591
|
+
if (action === "get_history") {
|
|
4592
|
+
dispatchSimple(requestId, "history", () => handleGetHistory(state));
|
|
4298
4593
|
return;
|
|
4299
4594
|
}
|
|
4300
|
-
if (
|
|
4301
|
-
|
|
4302
|
-
emit("session_cleared");
|
|
4595
|
+
if (action === "clear") {
|
|
4596
|
+
dispatchSimple(requestId, "session_cleared", () => handleClear(state));
|
|
4303
4597
|
return;
|
|
4304
4598
|
}
|
|
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
|
-
}
|
|
4599
|
+
if (action === "cancel") {
|
|
4600
|
+
handleCancel(currentAbort, pendingTools);
|
|
4601
|
+
emit("completed", { success: true }, requestId);
|
|
4314
4602
|
return;
|
|
4315
4603
|
}
|
|
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;
|
|
4604
|
+
if (action === "message") {
|
|
4605
|
+
await handleMessage(parsed, requestId);
|
|
4606
|
+
return;
|
|
4359
4607
|
}
|
|
4608
|
+
emit("error", { error: `Unknown action: ${action}` }, requestId);
|
|
4609
|
+
emit(
|
|
4610
|
+
"completed",
|
|
4611
|
+
{ success: false, error: `Unknown action: ${action}` },
|
|
4612
|
+
requestId
|
|
4613
|
+
);
|
|
4360
4614
|
});
|
|
4361
4615
|
rl.on("close", () => {
|
|
4362
4616
|
emit("stopping");
|