@mindstudio-ai/remy 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/buildFromInitialSpec.md +11 -3
- package/dist/compiled/design.md +2 -1
- package/dist/compiled/msfm.md +1 -0
- package/dist/compiled/sdk-actions.md +1 -3
- package/dist/headless.js +838 -306
- package/dist/index.js +952 -358
- package/dist/prompt/.notes.md +54 -0
- package/dist/prompt/actions/buildFromInitialSpec.md +11 -3
- package/dist/prompt/compiled/design.md +2 -1
- package/dist/prompt/compiled/msfm.md +1 -0
- package/dist/prompt/compiled/sdk-actions.md +1 -3
- package/dist/prompt/sources/frontend-design-notes.md +1 -0
- package/dist/prompt/static/authoring.md +4 -4
- package/dist/prompt/static/coding.md +5 -5
- package/dist/prompt/static/team.md +39 -0
- package/dist/static/authoring.md +4 -4
- package/dist/static/coding.md +5 -5
- package/dist/static/team.md +39 -0
- package/dist/subagents/browserAutomation/prompt.md +2 -0
- package/dist/subagents/codeSanityCheck/.notes.md +44 -0
- package/dist/subagents/codeSanityCheck/prompt.md +43 -0
- package/dist/subagents/designExpert/.notes.md +16 -4
- package/dist/subagents/designExpert/data/compile-inspiration.sh +2 -2
- package/dist/subagents/designExpert/prompts/frontend-design-notes.md +1 -0
- package/dist/subagents/designExpert/prompts/icons.md +18 -7
- package/dist/subagents/designExpert/prompts/identity.md +4 -4
- package/dist/subagents/designExpert/prompts/images.md +3 -2
- package/dist/subagents/designExpert/prompts/instructions.md +2 -2
- package/dist/subagents/designExpert/prompts/layout.md +4 -2
- package/dist/subagents/productVision/.notes.md +79 -0
- package/dist/subagents/productVision/prompt.md +29 -22
- 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 fs20 from "fs";
|
|
4
|
+
import path13 from "path";
|
|
5
5
|
|
|
6
6
|
// src/config.ts
|
|
7
7
|
import fs2 from "fs";
|
|
@@ -371,6 +371,8 @@ ${projectContext}
|
|
|
371
371
|
{{static/authoring.md}}
|
|
372
372
|
</spec_authoring_instructions>
|
|
373
373
|
|
|
374
|
+
{{static/team.md}}
|
|
375
|
+
|
|
374
376
|
<code_authoring_instructions>
|
|
375
377
|
{{static/coding.md}}
|
|
376
378
|
${isLspConfigured() ? `<typescript_lsp>
|
|
@@ -1240,19 +1242,36 @@ var confirmDestructiveActionTool = {
|
|
|
1240
1242
|
}
|
|
1241
1243
|
};
|
|
1242
1244
|
|
|
1243
|
-
// src/
|
|
1245
|
+
// src/subagents/common/runCli.ts
|
|
1244
1246
|
import { exec } from "child_process";
|
|
1247
|
+
function runCli(cmd, options) {
|
|
1248
|
+
return new Promise((resolve) => {
|
|
1249
|
+
exec(
|
|
1250
|
+
cmd,
|
|
1251
|
+
{
|
|
1252
|
+
timeout: options?.timeout ?? 6e4,
|
|
1253
|
+
maxBuffer: options?.maxBuffer ?? 1024 * 1024
|
|
1254
|
+
},
|
|
1255
|
+
(err, stdout, stderr) => {
|
|
1256
|
+
if (stdout.trim()) {
|
|
1257
|
+
resolve(stdout.trim());
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
if (err) {
|
|
1261
|
+
resolve(`Error: ${stderr.trim() || err.message}`);
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
resolve("(no response)");
|
|
1265
|
+
}
|
|
1266
|
+
);
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// src/subagents/sdkConsultant/index.ts
|
|
1245
1271
|
var askMindStudioSdkTool = {
|
|
1246
1272
|
definition: {
|
|
1247
1273
|
name: "askMindStudioSdk",
|
|
1248
|
-
description:
|
|
1249
|
-
|
|
1250
|
-
- Describe what you're trying to build at the method level ("I need a method that takes user text, generates a summary with GPT, extracts entities, and returns structured JSON") and get back architectural guidance + working code.
|
|
1251
|
-
- Ask about AI orchestration patterns: structured output, chaining model calls, batch processing, streaming, error handling.
|
|
1252
|
-
- Ask about connectors and integrations: what's available, whether the user has configured it, how to use it.
|
|
1253
|
-
- Always use this before writing SDK code. Model IDs, config options, and action signatures change frequently. Don't guess.
|
|
1254
|
-
|
|
1255
|
-
Batch related questions into a single query. This runs its own LLM call so it has a few seconds of latency.`,
|
|
1274
|
+
description: "MindStudio SDK expert. Knows every action, model, connector, and configuration option. Returns architectural guidance and working code. Describe what you want to build, not just what API method you need. Batch related questions into a single query.",
|
|
1256
1275
|
inputSchema: {
|
|
1257
1276
|
type: "object",
|
|
1258
1277
|
properties: {
|
|
@@ -1266,28 +1285,13 @@ Batch related questions into a single query. This runs its own LLM call so it ha
|
|
|
1266
1285
|
},
|
|
1267
1286
|
async execute(input) {
|
|
1268
1287
|
const query = input.query;
|
|
1269
|
-
return
|
|
1270
|
-
|
|
1271
|
-
`mindstudio ask ${JSON.stringify(query)}`,
|
|
1272
|
-
{ timeout: 6e4, maxBuffer: 512 * 1024 },
|
|
1273
|
-
(err, stdout, stderr) => {
|
|
1274
|
-
if (stdout.trim()) {
|
|
1275
|
-
resolve(stdout.trim());
|
|
1276
|
-
return;
|
|
1277
|
-
}
|
|
1278
|
-
if (err) {
|
|
1279
|
-
resolve(`Error: ${stderr.trim() || err.message}`);
|
|
1280
|
-
return;
|
|
1281
|
-
}
|
|
1282
|
-
resolve("(no response)");
|
|
1283
|
-
}
|
|
1284
|
-
);
|
|
1288
|
+
return runCli(`mindstudio ask ${JSON.stringify(query)}`, {
|
|
1289
|
+
maxBuffer: 512 * 1024
|
|
1285
1290
|
});
|
|
1286
1291
|
}
|
|
1287
1292
|
};
|
|
1288
1293
|
|
|
1289
1294
|
// src/tools/common/fetchUrl.ts
|
|
1290
|
-
import { exec as exec2 } from "child_process";
|
|
1291
1295
|
var fetchUrlTool = {
|
|
1292
1296
|
definition: {
|
|
1293
1297
|
name: "scapeWebUrl",
|
|
@@ -1314,29 +1318,13 @@ var fetchUrlTool = {
|
|
|
1314
1318
|
if (screenshot) {
|
|
1315
1319
|
pageOptions.screenshot = true;
|
|
1316
1320
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
cmd,
|
|
1321
|
-
{ timeout: 6e4, maxBuffer: 1024 * 1024 },
|
|
1322
|
-
(err, stdout, stderr) => {
|
|
1323
|
-
if (stdout.trim()) {
|
|
1324
|
-
resolve(stdout.trim());
|
|
1325
|
-
return;
|
|
1326
|
-
}
|
|
1327
|
-
if (err) {
|
|
1328
|
-
resolve(`Error: ${stderr.trim() || err.message}`);
|
|
1329
|
-
return;
|
|
1330
|
-
}
|
|
1331
|
-
resolve("(no response)");
|
|
1332
|
-
}
|
|
1333
|
-
);
|
|
1334
|
-
});
|
|
1321
|
+
return runCli(
|
|
1322
|
+
`mindstudio scrape-url --url ${JSON.stringify(url)} --page-options ${JSON.stringify(JSON.stringify(pageOptions))} --no-meta`
|
|
1323
|
+
);
|
|
1335
1324
|
}
|
|
1336
1325
|
};
|
|
1337
1326
|
|
|
1338
1327
|
// src/tools/common/searchGoogle.ts
|
|
1339
|
-
import { exec as exec3 } from "child_process";
|
|
1340
1328
|
var searchGoogleTool = {
|
|
1341
1329
|
definition: {
|
|
1342
1330
|
name: "searchGoogle",
|
|
@@ -1354,24 +1342,10 @@ var searchGoogleTool = {
|
|
|
1354
1342
|
},
|
|
1355
1343
|
async execute(input) {
|
|
1356
1344
|
const query = input.query;
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
{ timeout: 6e4, maxBuffer: 512 * 1024 },
|
|
1362
|
-
(err, stdout, stderr) => {
|
|
1363
|
-
if (stdout.trim()) {
|
|
1364
|
-
resolve(stdout.trim());
|
|
1365
|
-
return;
|
|
1366
|
-
}
|
|
1367
|
-
if (err) {
|
|
1368
|
-
resolve(`Error: ${stderr.trim() || err.message}`);
|
|
1369
|
-
return;
|
|
1370
|
-
}
|
|
1371
|
-
resolve("(no response)");
|
|
1372
|
-
}
|
|
1373
|
-
);
|
|
1374
|
-
});
|
|
1345
|
+
return runCli(
|
|
1346
|
+
`mindstudio search-google --query ${JSON.stringify(query)} --export-type json --output-key results --no-meta`,
|
|
1347
|
+
{ maxBuffer: 512 * 1024 }
|
|
1348
|
+
);
|
|
1375
1349
|
}
|
|
1376
1350
|
};
|
|
1377
1351
|
|
|
@@ -1696,7 +1670,7 @@ ${unifiedDiff(input.path, content, updated)}`;
|
|
|
1696
1670
|
};
|
|
1697
1671
|
|
|
1698
1672
|
// src/tools/code/bash.ts
|
|
1699
|
-
import { exec as
|
|
1673
|
+
import { exec as exec2 } from "child_process";
|
|
1700
1674
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
1701
1675
|
var DEFAULT_MAX_LINES3 = 500;
|
|
1702
1676
|
var bashTool = {
|
|
@@ -1730,7 +1704,7 @@ var bashTool = {
|
|
|
1730
1704
|
const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES3;
|
|
1731
1705
|
const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
|
|
1732
1706
|
return new Promise((resolve) => {
|
|
1733
|
-
|
|
1707
|
+
exec2(
|
|
1734
1708
|
input.command,
|
|
1735
1709
|
{
|
|
1736
1710
|
timeout: timeoutMs,
|
|
@@ -1770,7 +1744,7 @@ var bashTool = {
|
|
|
1770
1744
|
};
|
|
1771
1745
|
|
|
1772
1746
|
// src/tools/code/grep.ts
|
|
1773
|
-
import { exec as
|
|
1747
|
+
import { exec as exec3 } from "child_process";
|
|
1774
1748
|
var DEFAULT_MAX = 50;
|
|
1775
1749
|
function formatResults(stdout, max) {
|
|
1776
1750
|
const lines = stdout.trim().split("\n");
|
|
@@ -1817,12 +1791,12 @@ var grepTool = {
|
|
|
1817
1791
|
const rgCmd = `rg -n --no-heading --max-count=${max}${globFlag} '${escaped}' ${searchPath}`;
|
|
1818
1792
|
const grepCmd = `grep -rn --max-count=${max} '${escaped}' ${searchPath} --include='*.ts' --include='*.tsx' --include='*.js' --include='*.json' --include='*.md'`;
|
|
1819
1793
|
return new Promise((resolve) => {
|
|
1820
|
-
|
|
1794
|
+
exec3(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
|
|
1821
1795
|
if (stdout?.trim()) {
|
|
1822
1796
|
resolve(formatResults(stdout, max));
|
|
1823
1797
|
return;
|
|
1824
1798
|
}
|
|
1825
|
-
|
|
1799
|
+
exec3(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
|
|
1826
1800
|
if (grepStdout?.trim()) {
|
|
1827
1801
|
resolve(formatResults(grepStdout, max));
|
|
1828
1802
|
} else {
|
|
@@ -2054,20 +2028,70 @@ var runMethodTool = {
|
|
|
2054
2028
|
};
|
|
2055
2029
|
|
|
2056
2030
|
// 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.";
|
|
2057
2032
|
var screenshotTool = {
|
|
2058
2033
|
definition: {
|
|
2059
2034
|
name: "screenshot",
|
|
2060
|
-
description: "Capture a screenshot of the app preview
|
|
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.",
|
|
2061
2036
|
inputSchema: {
|
|
2062
2037
|
type: "object",
|
|
2063
|
-
properties: {
|
|
2038
|
+
properties: {
|
|
2039
|
+
prompt: {
|
|
2040
|
+
type: "string",
|
|
2041
|
+
description: "Optional question about the screenshot. If omitted, returns a general description of what's visible."
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2064
2044
|
}
|
|
2065
2045
|
},
|
|
2066
|
-
async execute() {
|
|
2067
|
-
|
|
2046
|
+
async execute(input) {
|
|
2047
|
+
try {
|
|
2048
|
+
const { url } = await sidecarRequest(
|
|
2049
|
+
"/screenshot",
|
|
2050
|
+
{},
|
|
2051
|
+
{ timeout: 3e4 }
|
|
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}`;
|
|
2060
|
+
} catch (err) {
|
|
2061
|
+
return `Error taking screenshot: ${err.message}`;
|
|
2062
|
+
}
|
|
2068
2063
|
}
|
|
2069
2064
|
};
|
|
2070
2065
|
|
|
2066
|
+
// src/subagents/common/cleanMessages.ts
|
|
2067
|
+
function cleanMessagesForApi(messages) {
|
|
2068
|
+
return messages.map((msg) => {
|
|
2069
|
+
if (!Array.isArray(msg.content)) {
|
|
2070
|
+
return msg;
|
|
2071
|
+
}
|
|
2072
|
+
const blocks = msg.content;
|
|
2073
|
+
const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
2074
|
+
const toolCalls = blocks.filter((b) => b.type === "tool").map((b) => ({ id: b.id, name: b.name, input: b.input }));
|
|
2075
|
+
const thinking = blocks.filter(
|
|
2076
|
+
(b) => b.type === "thinking"
|
|
2077
|
+
).map((b) => ({ thinking: b.thinking, signature: b.signature }));
|
|
2078
|
+
const cleaned = {
|
|
2079
|
+
role: msg.role,
|
|
2080
|
+
content: text
|
|
2081
|
+
};
|
|
2082
|
+
if (toolCalls.length > 0) {
|
|
2083
|
+
cleaned.toolCalls = toolCalls;
|
|
2084
|
+
}
|
|
2085
|
+
if (thinking.length > 0) {
|
|
2086
|
+
cleaned.thinking = thinking;
|
|
2087
|
+
}
|
|
2088
|
+
if (msg.hidden) {
|
|
2089
|
+
cleaned.hidden = true;
|
|
2090
|
+
}
|
|
2091
|
+
return cleaned;
|
|
2092
|
+
});
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2071
2095
|
// src/subagents/runner.ts
|
|
2072
2096
|
async function runSubAgent(config) {
|
|
2073
2097
|
const {
|
|
@@ -2078,6 +2102,7 @@ async function runSubAgent(config) {
|
|
|
2078
2102
|
executeTool: executeTool2,
|
|
2079
2103
|
apiConfig,
|
|
2080
2104
|
model,
|
|
2105
|
+
subAgentId,
|
|
2081
2106
|
signal,
|
|
2082
2107
|
parentToolId,
|
|
2083
2108
|
onEvent,
|
|
@@ -2089,17 +2114,18 @@ async function runSubAgent(config) {
|
|
|
2089
2114
|
const messages = [{ role: "user", content: task }];
|
|
2090
2115
|
while (true) {
|
|
2091
2116
|
if (signal?.aborted) {
|
|
2092
|
-
return "Error: cancelled";
|
|
2117
|
+
return { text: "Error: cancelled", messages };
|
|
2093
2118
|
}
|
|
2094
|
-
|
|
2095
|
-
|
|
2119
|
+
const contentBlocks = [];
|
|
2120
|
+
let thinkingStartedAt = 0;
|
|
2096
2121
|
let stopReason = "end_turn";
|
|
2097
2122
|
try {
|
|
2098
2123
|
for await (const event of streamChatWithRetry({
|
|
2099
2124
|
...apiConfig,
|
|
2100
2125
|
model,
|
|
2126
|
+
subAgentId,
|
|
2101
2127
|
system,
|
|
2102
|
-
messages,
|
|
2128
|
+
messages: cleanMessagesForApi(messages),
|
|
2103
2129
|
tools,
|
|
2104
2130
|
signal
|
|
2105
2131
|
})) {
|
|
@@ -2107,18 +2133,43 @@ async function runSubAgent(config) {
|
|
|
2107
2133
|
break;
|
|
2108
2134
|
}
|
|
2109
2135
|
switch (event.type) {
|
|
2110
|
-
case "text":
|
|
2111
|
-
|
|
2136
|
+
case "text": {
|
|
2137
|
+
const lastBlock = contentBlocks.at(-1);
|
|
2138
|
+
if (lastBlock?.type === "text") {
|
|
2139
|
+
lastBlock.text += event.text;
|
|
2140
|
+
} else {
|
|
2141
|
+
contentBlocks.push({
|
|
2142
|
+
type: "text",
|
|
2143
|
+
text: event.text,
|
|
2144
|
+
startedAt: event.ts
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2112
2147
|
emit2({ type: "text", text: event.text });
|
|
2113
2148
|
break;
|
|
2149
|
+
}
|
|
2114
2150
|
case "thinking":
|
|
2151
|
+
if (!thinkingStartedAt) {
|
|
2152
|
+
thinkingStartedAt = event.ts;
|
|
2153
|
+
}
|
|
2115
2154
|
emit2({ type: "thinking", text: event.text });
|
|
2116
2155
|
break;
|
|
2156
|
+
case "thinking_complete":
|
|
2157
|
+
contentBlocks.push({
|
|
2158
|
+
type: "thinking",
|
|
2159
|
+
thinking: event.thinking,
|
|
2160
|
+
signature: event.signature,
|
|
2161
|
+
startedAt: thinkingStartedAt,
|
|
2162
|
+
completedAt: event.ts
|
|
2163
|
+
});
|
|
2164
|
+
thinkingStartedAt = 0;
|
|
2165
|
+
break;
|
|
2117
2166
|
case "tool_use":
|
|
2118
|
-
|
|
2167
|
+
contentBlocks.push({
|
|
2168
|
+
type: "tool",
|
|
2119
2169
|
id: event.id,
|
|
2120
2170
|
name: event.name,
|
|
2121
|
-
input: event.input
|
|
2171
|
+
input: event.input,
|
|
2172
|
+
startedAt: Date.now()
|
|
2122
2173
|
});
|
|
2123
2174
|
emit2({
|
|
2124
2175
|
type: "tool_start",
|
|
@@ -2131,7 +2182,7 @@ async function runSubAgent(config) {
|
|
|
2131
2182
|
stopReason = event.stopReason;
|
|
2132
2183
|
break;
|
|
2133
2184
|
case "error":
|
|
2134
|
-
return `Error: ${event.error}
|
|
2185
|
+
return { text: `Error: ${event.error}`, messages };
|
|
2135
2186
|
}
|
|
2136
2187
|
}
|
|
2137
2188
|
} catch (err) {
|
|
@@ -2140,15 +2191,18 @@ async function runSubAgent(config) {
|
|
|
2140
2191
|
}
|
|
2141
2192
|
}
|
|
2142
2193
|
if (signal?.aborted) {
|
|
2143
|
-
return "Error: cancelled";
|
|
2194
|
+
return { text: "Error: cancelled", messages };
|
|
2144
2195
|
}
|
|
2145
2196
|
messages.push({
|
|
2146
2197
|
role: "assistant",
|
|
2147
|
-
content:
|
|
2148
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : void 0
|
|
2198
|
+
content: contentBlocks
|
|
2149
2199
|
});
|
|
2200
|
+
const toolCalls = contentBlocks.filter(
|
|
2201
|
+
(b) => b.type === "tool"
|
|
2202
|
+
);
|
|
2150
2203
|
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
2151
|
-
|
|
2204
|
+
const text = contentBlocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
2205
|
+
return { text, messages };
|
|
2152
2206
|
}
|
|
2153
2207
|
log.info("Sub-agent executing tools", {
|
|
2154
2208
|
parentToolId,
|
|
@@ -2283,7 +2337,19 @@ import path7 from "path";
|
|
|
2283
2337
|
var base = import.meta.dirname ?? path7.dirname(new URL(import.meta.url).pathname);
|
|
2284
2338
|
var local = path7.join(base, "prompt.md");
|
|
2285
2339
|
var PROMPT_PATH = fs13.existsSync(local) ? local : path7.join(base, "subagents", "browserAutomation", "prompt.md");
|
|
2286
|
-
var
|
|
2340
|
+
var BASE_PROMPT = fs13.readFileSync(PROMPT_PATH, "utf-8").trim();
|
|
2341
|
+
function getBrowserAutomationPrompt() {
|
|
2342
|
+
try {
|
|
2343
|
+
const appSpec = fs13.readFileSync("src/app.md", "utf-8").trim();
|
|
2344
|
+
return `${BASE_PROMPT}
|
|
2345
|
+
|
|
2346
|
+
<app_context>
|
|
2347
|
+
${appSpec}
|
|
2348
|
+
</app_context>`;
|
|
2349
|
+
} catch {
|
|
2350
|
+
return BASE_PROMPT;
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2287
2353
|
|
|
2288
2354
|
// src/subagents/browserAutomation/index.ts
|
|
2289
2355
|
var browserAutomationTool = {
|
|
@@ -2317,8 +2383,8 @@ var browserAutomationTool = {
|
|
|
2317
2383
|
} catch {
|
|
2318
2384
|
return "Error: could not check browser status. The dev environment may not be running.";
|
|
2319
2385
|
}
|
|
2320
|
-
|
|
2321
|
-
system:
|
|
2386
|
+
const result = await runSubAgent({
|
|
2387
|
+
system: getBrowserAutomationPrompt(),
|
|
2322
2388
|
task: input.task,
|
|
2323
2389
|
tools: BROWSER_TOOLS,
|
|
2324
2390
|
externalTools: BROWSER_EXTERNAL_TOOLS,
|
|
@@ -2335,16 +2401,18 @@ var browserAutomationTool = {
|
|
|
2335
2401
|
},
|
|
2336
2402
|
apiConfig: context.apiConfig,
|
|
2337
2403
|
model: context.model,
|
|
2404
|
+
subAgentId: "browserAutomation",
|
|
2338
2405
|
signal: context.signal,
|
|
2339
2406
|
parentToolId: context.toolCallId,
|
|
2340
2407
|
onEvent: context.onEvent,
|
|
2341
2408
|
resolveExternalTool: context.resolveExternalTool
|
|
2342
2409
|
});
|
|
2410
|
+
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
2411
|
+
return result.text;
|
|
2343
2412
|
}
|
|
2344
2413
|
};
|
|
2345
2414
|
|
|
2346
2415
|
// src/subagents/designExpert/tools.ts
|
|
2347
|
-
import { exec as exec6 } from "child_process";
|
|
2348
2416
|
var DESIGN_REFERENCE_PROMPT = `Analyze this website/app screenshot as a design reference. Assess:
|
|
2349
2417
|
1) Mood/aesthetic
|
|
2350
2418
|
2) Color palette with approximate hex values and palette strategy
|
|
@@ -2352,7 +2420,7 @@ var DESIGN_REFERENCE_PROMPT = `Analyze this website/app screenshot as a design r
|
|
|
2352
2420
|
4) Layout composition (symmetric/asymmetric, grid structure, whitespace usage, content density)
|
|
2353
2421
|
5) What makes it distinctive and interesting vs generic AI-generated interfaces
|
|
2354
2422
|
Be specific and concise.`;
|
|
2355
|
-
var
|
|
2423
|
+
var DESIGN_EXPERT_TOOLS = [
|
|
2356
2424
|
{
|
|
2357
2425
|
name: "searchGoogle",
|
|
2358
2426
|
description: "Search Google for web results. Use for finding design inspiration, font recommendations, UI patterns, real products in a domain, and reference material.",
|
|
@@ -2386,53 +2454,29 @@ var DESIGN_RESEARCH_TOOLS = [
|
|
|
2386
2454
|
}
|
|
2387
2455
|
},
|
|
2388
2456
|
{
|
|
2389
|
-
name: "
|
|
2390
|
-
description:
|
|
2457
|
+
name: "analyzeReferenceImageOrUrl",
|
|
2458
|
+
description: "Analyze any visual \u2014 pass an image URL or a website URL. Websites are automatically screenshotted first. If no prompt is provided, performs a full design reference analysis (mood, color, typography, layout, distinctiveness). Provide a custom prompt to ask a specific question instead.",
|
|
2391
2459
|
inputSchema: {
|
|
2392
2460
|
type: "object",
|
|
2393
2461
|
properties: {
|
|
2394
|
-
|
|
2462
|
+
url: {
|
|
2395
2463
|
type: "string",
|
|
2396
|
-
description: "
|
|
2464
|
+
description: "URL to analyze. Can be an image URL or a website URL (will be screenshotted)."
|
|
2397
2465
|
},
|
|
2398
|
-
|
|
2399
|
-
type: "string",
|
|
2400
|
-
description: "URL of the image to analyze."
|
|
2401
|
-
}
|
|
2402
|
-
},
|
|
2403
|
-
required: ["prompt", "imageUrl"]
|
|
2404
|
-
}
|
|
2405
|
-
},
|
|
2406
|
-
{
|
|
2407
|
-
name: "analyzeDesignReference",
|
|
2408
|
-
description: "Analyze a screenshot or design image for design inspiration. Returns a structured analysis: mood/aesthetic, color palette with hex values, typography style, layout composition, and what makes it distinctive. Use this instead of analyzeImage when studying a design reference.",
|
|
2409
|
-
inputSchema: {
|
|
2410
|
-
type: "object",
|
|
2411
|
-
properties: {
|
|
2412
|
-
imageUrl: {
|
|
2466
|
+
prompt: {
|
|
2413
2467
|
type: "string",
|
|
2414
|
-
description: "
|
|
2468
|
+
description: "Optional custom analysis prompt. If omitted, performs the standard design reference analysis."
|
|
2415
2469
|
}
|
|
2416
2470
|
},
|
|
2417
|
-
required: ["
|
|
2471
|
+
required: ["url"]
|
|
2418
2472
|
}
|
|
2419
2473
|
},
|
|
2420
2474
|
{
|
|
2421
|
-
name: "
|
|
2422
|
-
description: "
|
|
2475
|
+
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.",
|
|
2423
2477
|
inputSchema: {
|
|
2424
2478
|
type: "object",
|
|
2425
|
-
properties: {
|
|
2426
|
-
url: {
|
|
2427
|
-
type: "string",
|
|
2428
|
-
description: "The URL to screenshot."
|
|
2429
|
-
},
|
|
2430
|
-
prompt: {
|
|
2431
|
-
type: "string",
|
|
2432
|
-
description: "Optional custom analysis prompt. If omitted, performs the standard design reference analysis."
|
|
2433
|
-
}
|
|
2434
|
-
},
|
|
2435
|
-
required: ["url"]
|
|
2479
|
+
properties: {}
|
|
2436
2480
|
}
|
|
2437
2481
|
},
|
|
2438
2482
|
{
|
|
@@ -2451,7 +2495,7 @@ var DESIGN_RESEARCH_TOOLS = [
|
|
|
2451
2495
|
},
|
|
2452
2496
|
{
|
|
2453
2497
|
name: "generateImages",
|
|
2454
|
-
description: "Generate images using AI (Seedream). Returns CDN URLs. Produces high-quality results for both photorealistic images and abstract/creative visuals. Pass multiple prompts to generate in parallel.",
|
|
2498
|
+
description: "Generate images using AI (Seedream). Returns CDN URLs with a quality analysis for each image. Produces high-quality results for both photorealistic images and abstract/creative visuals. Pass multiple prompts to generate in parallel. No need to analyze images separately after generating \u2014 the analysis is included.",
|
|
2455
2499
|
inputSchema: {
|
|
2456
2500
|
type: "object",
|
|
2457
2501
|
properties: {
|
|
@@ -2475,27 +2519,26 @@ var DESIGN_RESEARCH_TOOLS = [
|
|
|
2475
2519
|
}
|
|
2476
2520
|
}
|
|
2477
2521
|
];
|
|
2478
|
-
function
|
|
2479
|
-
return new Promise((resolve) => {
|
|
2480
|
-
exec6(
|
|
2481
|
-
cmd,
|
|
2482
|
-
{ timeout: 6e4, maxBuffer: 1024 * 1024 },
|
|
2483
|
-
(err, stdout, stderr) => {
|
|
2484
|
-
if (stdout.trim()) {
|
|
2485
|
-
resolve(stdout.trim());
|
|
2486
|
-
return;
|
|
2487
|
-
}
|
|
2488
|
-
if (err) {
|
|
2489
|
-
resolve(`Error: ${stderr.trim() || err.message}`);
|
|
2490
|
-
return;
|
|
2491
|
-
}
|
|
2492
|
-
resolve("(no response)");
|
|
2493
|
-
}
|
|
2494
|
-
);
|
|
2495
|
-
});
|
|
2496
|
-
}
|
|
2497
|
-
async function executeDesignTool(name, input) {
|
|
2522
|
+
async function executeDesignExpertTool(name, input) {
|
|
2498
2523
|
switch (name) {
|
|
2524
|
+
case "screenshot": {
|
|
2525
|
+
try {
|
|
2526
|
+
const { url } = await sidecarRequest(
|
|
2527
|
+
"/screenshot",
|
|
2528
|
+
{},
|
|
2529
|
+
{ timeout: 3e4 }
|
|
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}`;
|
|
2538
|
+
} catch (err) {
|
|
2539
|
+
return `Error taking screenshot: ${err.message}`;
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2499
2542
|
case "searchGoogle":
|
|
2500
2543
|
return runCli(
|
|
2501
2544
|
`mindstudio search-google --query ${JSON.stringify(input.query)} --export-type json --output-key results --no-meta`
|
|
@@ -2509,26 +2552,24 @@ async function executeDesignTool(name, input) {
|
|
|
2509
2552
|
`mindstudio scrape-url --url ${JSON.stringify(input.url)} --page-options ${JSON.stringify(JSON.stringify(pageOptions))} --no-meta`
|
|
2510
2553
|
);
|
|
2511
2554
|
}
|
|
2512
|
-
case "
|
|
2513
|
-
|
|
2514
|
-
`mindstudio analyze-image --prompt ${JSON.stringify(input.prompt)} --image-url ${JSON.stringify(input.imageUrl)} --no-meta`
|
|
2515
|
-
);
|
|
2516
|
-
case "analyzeDesignReference":
|
|
2517
|
-
return runCli(
|
|
2518
|
-
`mindstudio analyze-image --prompt ${JSON.stringify(DESIGN_REFERENCE_PROMPT)} --image-url ${JSON.stringify(input.imageUrl)} --no-meta`
|
|
2519
|
-
);
|
|
2520
|
-
case "screenshotAndAnalyze": {
|
|
2521
|
-
const ssUrl = await runCli(
|
|
2522
|
-
`mindstudio screenshot-url --url ${JSON.stringify(input.url)} --mode viewport --width 1440 --delay 2000 --output-key screenshotUrl --no-meta`
|
|
2523
|
-
);
|
|
2524
|
-
if (ssUrl.startsWith("Error")) {
|
|
2525
|
-
return `Could not screenshot ${input.url}: ${ssUrl}`;
|
|
2526
|
-
}
|
|
2555
|
+
case "analyzeReferenceImageOrUrl": {
|
|
2556
|
+
const url = input.url;
|
|
2527
2557
|
const analysisPrompt = input.prompt || DESIGN_REFERENCE_PROMPT;
|
|
2558
|
+
const isImageUrl = /\.(png|jpe?g|webp|gif|svg|avif)(\?|$)/i.test(url);
|
|
2559
|
+
let imageUrl = url;
|
|
2560
|
+
if (!isImageUrl) {
|
|
2561
|
+
const ssUrl = await runCli(
|
|
2562
|
+
`mindstudio screenshot-url --url ${JSON.stringify(url)} --mode viewport --width 1440 --delay 2000 --output-key screenshotUrl --no-meta`
|
|
2563
|
+
);
|
|
2564
|
+
if (ssUrl.startsWith("Error")) {
|
|
2565
|
+
return `Could not screenshot ${url}: ${ssUrl}`;
|
|
2566
|
+
}
|
|
2567
|
+
imageUrl = ssUrl;
|
|
2568
|
+
}
|
|
2528
2569
|
const analysis = await runCli(
|
|
2529
|
-
`mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(
|
|
2570
|
+
`mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(imageUrl)} --output-key analysis --no-meta`
|
|
2530
2571
|
);
|
|
2531
|
-
return `Screenshot: ${
|
|
2572
|
+
return isImageUrl ? analysis : `Screenshot: ${imageUrl}
|
|
2532
2573
|
|
|
2533
2574
|
${analysis}`;
|
|
2534
2575
|
}
|
|
@@ -2542,6 +2583,8 @@ ${analysis}`;
|
|
|
2542
2583
|
const prompts = input.prompts;
|
|
2543
2584
|
const width = input.width || 2048;
|
|
2544
2585
|
const height = input.height || 2048;
|
|
2586
|
+
const ANALYZE_PROMPT = "You are reviewing this image for a visual designer sourcing assets for a project. Describe: what the image depicts, the mood and color palette, how the lighting and composition work, whether there are any issues (unwanted text, artifacts, distortions), and how it could be used in a layout (hero background, feature section, card texture, etc). Be concise and practical.";
|
|
2587
|
+
let imageUrls;
|
|
2545
2588
|
if (prompts.length === 1) {
|
|
2546
2589
|
const step = JSON.stringify({
|
|
2547
2590
|
prompt: prompts[0],
|
|
@@ -2550,21 +2593,47 @@ ${analysis}`;
|
|
|
2550
2593
|
config: { width, height }
|
|
2551
2594
|
}
|
|
2552
2595
|
});
|
|
2553
|
-
|
|
2596
|
+
const url = await runCli(
|
|
2554
2597
|
`mindstudio generate-image '${step}' --output-key imageUrl --no-meta`
|
|
2555
2598
|
);
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2599
|
+
imageUrls = [url];
|
|
2600
|
+
} else {
|
|
2601
|
+
const steps = prompts.map((prompt) => ({
|
|
2602
|
+
stepType: "generateImage",
|
|
2603
|
+
step: {
|
|
2604
|
+
prompt,
|
|
2605
|
+
imageModelOverride: {
|
|
2606
|
+
model: "seedream-4.5",
|
|
2607
|
+
config: { width, height }
|
|
2608
|
+
}
|
|
2564
2609
|
}
|
|
2610
|
+
}));
|
|
2611
|
+
const batchResult = await runCli(
|
|
2612
|
+
`mindstudio batch '${JSON.stringify(steps)}' --no-meta`
|
|
2613
|
+
);
|
|
2614
|
+
try {
|
|
2615
|
+
const parsed = JSON.parse(batchResult);
|
|
2616
|
+
imageUrls = parsed.results.map(
|
|
2617
|
+
(r) => r.output?.imageUrl ?? `Error: ${r.error}`
|
|
2618
|
+
);
|
|
2619
|
+
} catch {
|
|
2620
|
+
return batchResult;
|
|
2565
2621
|
}
|
|
2566
|
-
}
|
|
2567
|
-
|
|
2622
|
+
}
|
|
2623
|
+
const analyses = await Promise.all(
|
|
2624
|
+
imageUrls.map(async (url, i) => {
|
|
2625
|
+
if (url.startsWith("Error")) {
|
|
2626
|
+
return `Image ${i + 1}: ${url}`;
|
|
2627
|
+
}
|
|
2628
|
+
const analysis = await runCli(
|
|
2629
|
+
`mindstudio analyze-image --prompt ${JSON.stringify(ANALYZE_PROMPT)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`
|
|
2630
|
+
);
|
|
2631
|
+
return `**Image ${i + 1}:** ${url}
|
|
2632
|
+
Prompt: ${prompts[i]}
|
|
2633
|
+
Analysis: ${analysis}`;
|
|
2634
|
+
})
|
|
2635
|
+
);
|
|
2636
|
+
return analyses.join("\n\n");
|
|
2568
2637
|
}
|
|
2569
2638
|
default:
|
|
2570
2639
|
return `Error: unknown tool "${name}"`;
|
|
@@ -2572,19 +2641,117 @@ ${analysis}`;
|
|
|
2572
2641
|
}
|
|
2573
2642
|
|
|
2574
2643
|
// src/subagents/designExpert/prompt.ts
|
|
2644
|
+
import fs15 from "fs";
|
|
2645
|
+
import path9 from "path";
|
|
2646
|
+
|
|
2647
|
+
// src/subagents/common/context.ts
|
|
2575
2648
|
import fs14 from "fs";
|
|
2576
2649
|
import path8 from "path";
|
|
2577
|
-
|
|
2650
|
+
function walkMdFiles2(dir, skip) {
|
|
2651
|
+
const files = [];
|
|
2652
|
+
try {
|
|
2653
|
+
for (const entry of fs14.readdirSync(dir, { withFileTypes: true })) {
|
|
2654
|
+
const full = path8.join(dir, entry.name);
|
|
2655
|
+
if (entry.isDirectory()) {
|
|
2656
|
+
if (!skip?.has(entry.name)) {
|
|
2657
|
+
files.push(...walkMdFiles2(full, skip));
|
|
2658
|
+
}
|
|
2659
|
+
} else if (entry.name.endsWith(".md")) {
|
|
2660
|
+
files.push(full);
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
} catch {
|
|
2664
|
+
}
|
|
2665
|
+
return files;
|
|
2666
|
+
}
|
|
2667
|
+
function loadFilesAsXml(dir, tag, skip) {
|
|
2668
|
+
const files = walkMdFiles2(dir, skip);
|
|
2669
|
+
if (files.length === 0) {
|
|
2670
|
+
return "";
|
|
2671
|
+
}
|
|
2672
|
+
const sections = files.map((f) => {
|
|
2673
|
+
try {
|
|
2674
|
+
const content = fs14.readFileSync(f, "utf-8").trim();
|
|
2675
|
+
return `<file path="${f}">
|
|
2676
|
+
${content}
|
|
2677
|
+
</file>`;
|
|
2678
|
+
} catch {
|
|
2679
|
+
return "";
|
|
2680
|
+
}
|
|
2681
|
+
}).filter(Boolean);
|
|
2682
|
+
return `<${tag}>
|
|
2683
|
+
${sections.join("\n\n")}
|
|
2684
|
+
</${tag}>`;
|
|
2685
|
+
}
|
|
2686
|
+
function loadSpecContext() {
|
|
2687
|
+
return loadFilesAsXml("src", "spec_files", /* @__PURE__ */ new Set(["roadmap"]));
|
|
2688
|
+
}
|
|
2689
|
+
function loadRoadmapContext() {
|
|
2690
|
+
return loadFilesAsXml("src/roadmap", "current_roadmap");
|
|
2691
|
+
}
|
|
2692
|
+
function loadPlatformBrief() {
|
|
2693
|
+
return `<platform_brief>
|
|
2694
|
+
## What is a MindStudio app?
|
|
2695
|
+
|
|
2696
|
+
A MindStudio app is a managed TypeScript project with three layers: a spec (natural language in src/), a backend contract (methods, tables, roles in dist/), and one or more interfaces (web, API, bots, cron, etc.). The spec is the source of truth; code is derived from it.
|
|
2697
|
+
|
|
2698
|
+
## What people build
|
|
2699
|
+
|
|
2700
|
+
- Business tools \u2014 dashboards, admin panels, approval workflows, data entry apps, internal tools with role-based access
|
|
2701
|
+
- AI-powered apps \u2014 chatbots, content generators, document processors, image/video tools, AI agents that take actions
|
|
2702
|
+
- Automations with no UI \u2014 cron jobs, webhook handlers, email processors, data sync pipelines
|
|
2703
|
+
- Bots \u2014 Discord slash-command bots, Telegram bots, MCP tool servers for AI assistants
|
|
2704
|
+
- Creative/interactive projects \u2014 games, interactive visualizations, generative art, portfolio sites
|
|
2705
|
+
- API services \u2014 backend logic exposed as REST endpoints
|
|
2706
|
+
- Simple static sites \u2014 no backend needed, just a web interface with a build step
|
|
2707
|
+
|
|
2708
|
+
An app can be any combination of these.
|
|
2709
|
+
|
|
2710
|
+
## Interfaces
|
|
2711
|
+
|
|
2712
|
+
Each interface type invokes the same backend methods. Methods don't know which interface called them.
|
|
2713
|
+
|
|
2714
|
+
- Web \u2014 any TypeScript project with a build command. Framework-agnostic (React, Vue, Svelte, vanilla, anything). The frontend SDK provides typed RPC to backend methods.
|
|
2715
|
+
- API \u2014 auto-generated REST endpoints for every method
|
|
2716
|
+
- Cron \u2014 scheduled jobs on a configurable interval
|
|
2717
|
+
- Webhook \u2014 HTTP endpoints that trigger methods
|
|
2718
|
+
- Discord \u2014 slash-command bots
|
|
2719
|
+
- Telegram \u2014 message-handling bots
|
|
2720
|
+
- Email \u2014 inbound email processing
|
|
2721
|
+
- MCP \u2014 tool servers for AI assistants
|
|
2722
|
+
|
|
2723
|
+
## Backend
|
|
2724
|
+
|
|
2725
|
+
TypeScript running in a sandboxed environment. Any npm package can be installed. Key capabilities:
|
|
2726
|
+
|
|
2727
|
+
- Managed SQLite database with typed schemas and automatic migrations. Define a TypeScript interface, push, and the platform handles diffing and migrating.
|
|
2728
|
+
- Built-in role-based auth. Define roles in the manifest, gate methods with auth.requireRole(). Platform handles sessions, tokens, user resolution.
|
|
2729
|
+
- Sandboxed execution with npm packages pre-installed.
|
|
2730
|
+
- Git-native deployment. Push to default branch to deploy.
|
|
2731
|
+
|
|
2732
|
+
## MindStudio SDK
|
|
2733
|
+
|
|
2734
|
+
The first-party SDK (@mindstudio-ai/agent) provides access to 200+ AI models (OpenAI, Anthropic, Google, Meta, Mistral, and more) and 1000+ integrations (email, SMS, Slack, HubSpot, Google Workspace, web scraping, image/video generation, media processing, and much more) with zero configuration \u2014 credentials are handled automatically in the execution environment. No API keys needed.
|
|
2735
|
+
|
|
2736
|
+
## What MindStudio apps are NOT good for
|
|
2737
|
+
|
|
2738
|
+
- Native mobile apps (iOS/Android). Mobile-responsive web apps are fine.
|
|
2739
|
+
- Real-time multiplayer with persistent connections (no WebSocket support). Turn-based or async patterns work.
|
|
2740
|
+
</platform_brief>`;
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
// src/subagents/designExpert/prompt.ts
|
|
2744
|
+
var base2 = import.meta.dirname ?? path9.dirname(new URL(import.meta.url).pathname);
|
|
2578
2745
|
function resolvePath(filename) {
|
|
2579
|
-
const
|
|
2580
|
-
return
|
|
2746
|
+
const local4 = path9.join(base2, filename);
|
|
2747
|
+
return fs15.existsSync(local4) ? local4 : path9.join(base2, "subagents", "designExpert", filename);
|
|
2581
2748
|
}
|
|
2582
2749
|
function readFile(filename) {
|
|
2583
|
-
return
|
|
2750
|
+
return fs15.readFileSync(resolvePath(filename), "utf-8").trim();
|
|
2584
2751
|
}
|
|
2585
2752
|
function readJson(filename, fallback) {
|
|
2586
2753
|
try {
|
|
2587
|
-
return JSON.parse(
|
|
2754
|
+
return JSON.parse(fs15.readFileSync(resolvePath(filename), "utf-8"));
|
|
2588
2755
|
} catch {
|
|
2589
2756
|
return fallback;
|
|
2590
2757
|
}
|
|
@@ -2616,7 +2783,7 @@ function sample(arr, n) {
|
|
|
2616
2783
|
}
|
|
2617
2784
|
return copy.slice(0, n);
|
|
2618
2785
|
}
|
|
2619
|
-
function
|
|
2786
|
+
function getDesignExpertPrompt() {
|
|
2620
2787
|
const fonts = sample(fontData.fonts, 30);
|
|
2621
2788
|
const pairings = sample(fontData.pairings, 20);
|
|
2622
2789
|
const images = sample(inspirationImages, 15);
|
|
@@ -2655,31 +2822,22 @@ This is what the bar looks like. These are real sites that made it onto curated
|
|
|
2655
2822
|
|
|
2656
2823
|
${imageList}
|
|
2657
2824
|
</inspiration_images>` : "";
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2825
|
+
const specContext = loadSpecContext();
|
|
2826
|
+
let prompt = PROMPT_TEMPLATE.replace(
|
|
2827
|
+
"{{fonts_to_consider}}",
|
|
2828
|
+
fontsSection
|
|
2829
|
+
).replace("{{inspiration_images}}", inspirationSection);
|
|
2830
|
+
if (specContext) {
|
|
2831
|
+
prompt += `
|
|
2832
|
+
|
|
2833
|
+
${specContext}`;
|
|
2834
|
+
}
|
|
2835
|
+
return prompt;
|
|
2662
2836
|
}
|
|
2663
2837
|
|
|
2664
2838
|
// src/subagents/designExpert/index.ts
|
|
2665
2839
|
var DESCRIPTION = `
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
The visual design expert can be used for all things visual design, from quick questions to comprehensive plans:
|
|
2669
|
-
- Font selection and pairings ("suggest fonts for a <x> app")
|
|
2670
|
-
- Color palettes from a mood, domain, or seed color ("earthy tones for a <x> brand")
|
|
2671
|
-
- Gradient, animation, and visual effect recommendations
|
|
2672
|
-
- Layout and composition ideas that go beyond generic AI defaults
|
|
2673
|
-
- Analyzing a reference site or screenshot for design insights (it can take screenshots and do research on its own)
|
|
2674
|
-
- Beautiful layout images or photos
|
|
2675
|
-
- Icon recommendations or AI image editing
|
|
2676
|
-
- Proposing full visual design and layout directions during intake
|
|
2677
|
-
|
|
2678
|
-
**How to write the task:**
|
|
2679
|
-
Include context about the app \u2014 what it does, who uses it, what mood or feeling the interface should convey. If the user has any specific requirements, be sure to include them. The agent can not see your conversation with the user, so you need to include all details. More context produces better results. For quick questions ("three font pairings for a <x> app"), brief is fine. You can ask for multiple topics, multiple options, etc.
|
|
2680
|
-
|
|
2681
|
-
**What it returns:**
|
|
2682
|
-
Concrete resources: hex values, font names with CSS URLs, image URLs, layout descriptions. Use the results directly in brand spec files or as guidance when building the interface.
|
|
2840
|
+
Visual design expert. Describe the situation and what you need \u2014 the agent decides what to deliver. It reads the spec files automatically. Include relevant user requirements and context it can't get from the spec, but do not list specific deliverables or tell it how to do its job.
|
|
2683
2841
|
`.trim();
|
|
2684
2842
|
var designExpertTool = {
|
|
2685
2843
|
definition: {
|
|
@@ -2698,71 +2856,32 @@ var designExpertTool = {
|
|
|
2698
2856
|
},
|
|
2699
2857
|
async execute(input, context) {
|
|
2700
2858
|
if (!context) {
|
|
2701
|
-
return "Error: design
|
|
2859
|
+
return "Error: visual design expert requires execution context";
|
|
2702
2860
|
}
|
|
2703
|
-
|
|
2704
|
-
system:
|
|
2861
|
+
const result = await runSubAgent({
|
|
2862
|
+
system: getDesignExpertPrompt(),
|
|
2705
2863
|
task: input.task,
|
|
2706
|
-
tools:
|
|
2864
|
+
tools: DESIGN_EXPERT_TOOLS,
|
|
2707
2865
|
externalTools: /* @__PURE__ */ new Set(),
|
|
2708
|
-
executeTool:
|
|
2866
|
+
executeTool: executeDesignExpertTool,
|
|
2709
2867
|
apiConfig: context.apiConfig,
|
|
2710
2868
|
model: context.model,
|
|
2869
|
+
subAgentId: "visualDesignExpert",
|
|
2711
2870
|
signal: context.signal,
|
|
2712
2871
|
parentToolId: context.toolCallId,
|
|
2713
2872
|
onEvent: context.onEvent,
|
|
2714
2873
|
resolveExternalTool: context.resolveExternalTool
|
|
2715
2874
|
});
|
|
2875
|
+
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
2876
|
+
return result.text;
|
|
2716
2877
|
}
|
|
2717
2878
|
};
|
|
2718
2879
|
|
|
2719
|
-
// src/subagents/productVision/
|
|
2720
|
-
import fs15 from "fs";
|
|
2721
|
-
import path9 from "path";
|
|
2722
|
-
var base3 = import.meta.dirname ?? path9.dirname(new URL(import.meta.url).pathname);
|
|
2723
|
-
var local2 = path9.join(base3, "prompt.md");
|
|
2724
|
-
var PROMPT_PATH2 = fs15.existsSync(local2) ? local2 : path9.join(base3, "subagents", "productVision", "prompt.md");
|
|
2725
|
-
var BASE_PROMPT = fs15.readFileSync(PROMPT_PATH2, "utf-8").trim();
|
|
2726
|
-
function loadSpecContext() {
|
|
2727
|
-
const specDir = "src";
|
|
2728
|
-
const files = [];
|
|
2729
|
-
function walk(dir) {
|
|
2730
|
-
try {
|
|
2731
|
-
for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
|
|
2732
|
-
const full = path9.join(dir, entry.name);
|
|
2733
|
-
if (entry.isDirectory()) {
|
|
2734
|
-
if (entry.name !== "roadmap") {
|
|
2735
|
-
walk(full);
|
|
2736
|
-
}
|
|
2737
|
-
} else if (entry.name.endsWith(".md")) {
|
|
2738
|
-
files.push(full);
|
|
2739
|
-
}
|
|
2740
|
-
}
|
|
2741
|
-
} catch {
|
|
2742
|
-
}
|
|
2743
|
-
}
|
|
2744
|
-
walk(specDir);
|
|
2745
|
-
if (files.length === 0) {
|
|
2746
|
-
return "";
|
|
2747
|
-
}
|
|
2748
|
-
const sections = files.map((f) => {
|
|
2749
|
-
try {
|
|
2750
|
-
const content = fs15.readFileSync(f, "utf-8").trim();
|
|
2751
|
-
return `<file path="${f}">
|
|
2752
|
-
${content}
|
|
2753
|
-
</file>`;
|
|
2754
|
-
} catch {
|
|
2755
|
-
return "";
|
|
2756
|
-
}
|
|
2757
|
-
}).filter(Boolean);
|
|
2758
|
-
return `<spec_files>
|
|
2759
|
-
${sections.join("\n\n")}
|
|
2760
|
-
</spec_files>`;
|
|
2761
|
-
}
|
|
2880
|
+
// src/subagents/productVision/tools.ts
|
|
2762
2881
|
var VISION_TOOLS = [
|
|
2763
2882
|
{
|
|
2764
2883
|
name: "writeRoadmapItem",
|
|
2765
|
-
description: "
|
|
2884
|
+
description: "Create a new roadmap item in src/roadmap/.",
|
|
2766
2885
|
inputSchema: {
|
|
2767
2886
|
type: "object",
|
|
2768
2887
|
properties: {
|
|
@@ -2789,50 +2908,225 @@ var VISION_TOOLS = [
|
|
|
2789
2908
|
},
|
|
2790
2909
|
body: {
|
|
2791
2910
|
type: "string",
|
|
2792
|
-
description: "Full MSFM body: prose description for the user, followed by ~~~annotation~~~ with technical implementation notes
|
|
2911
|
+
description: "Full MSFM body: prose description for the user, followed by ~~~annotation~~~ with technical implementation notes."
|
|
2793
2912
|
}
|
|
2794
2913
|
},
|
|
2795
2914
|
required: ["slug", "name", "description", "effort", "requires", "body"]
|
|
2796
2915
|
}
|
|
2916
|
+
},
|
|
2917
|
+
{
|
|
2918
|
+
name: "updateRoadmapItem",
|
|
2919
|
+
description: "Update an existing roadmap item. Only include the fields you want to change.",
|
|
2920
|
+
inputSchema: {
|
|
2921
|
+
type: "object",
|
|
2922
|
+
properties: {
|
|
2923
|
+
slug: {
|
|
2924
|
+
type: "string",
|
|
2925
|
+
description: "The slug of the item to update (filename without .md)."
|
|
2926
|
+
},
|
|
2927
|
+
status: {
|
|
2928
|
+
type: "string",
|
|
2929
|
+
enum: ["done", "in-progress", "not-started"],
|
|
2930
|
+
description: "New status."
|
|
2931
|
+
},
|
|
2932
|
+
name: {
|
|
2933
|
+
type: "string",
|
|
2934
|
+
description: "Updated feature name."
|
|
2935
|
+
},
|
|
2936
|
+
description: {
|
|
2937
|
+
type: "string",
|
|
2938
|
+
description: "Updated summary."
|
|
2939
|
+
},
|
|
2940
|
+
effort: {
|
|
2941
|
+
type: "string",
|
|
2942
|
+
enum: ["quick", "small", "medium", "large"],
|
|
2943
|
+
description: "Updated effort level."
|
|
2944
|
+
},
|
|
2945
|
+
requires: {
|
|
2946
|
+
type: "array",
|
|
2947
|
+
items: { type: "string" },
|
|
2948
|
+
description: "Updated prerequisites."
|
|
2949
|
+
},
|
|
2950
|
+
body: {
|
|
2951
|
+
type: "string",
|
|
2952
|
+
description: "Full replacement body (overwrites existing body)."
|
|
2953
|
+
},
|
|
2954
|
+
appendHistory: {
|
|
2955
|
+
type: "string",
|
|
2956
|
+
description: 'A history entry to append. Format: "- **2026-03-22** \u2014 Description of what was done."'
|
|
2957
|
+
}
|
|
2958
|
+
},
|
|
2959
|
+
required: ["slug"]
|
|
2960
|
+
}
|
|
2961
|
+
},
|
|
2962
|
+
{
|
|
2963
|
+
name: "deleteRoadmapItem",
|
|
2964
|
+
description: "Remove a roadmap item. Use when an idea is no longer relevant or has been absorbed into another item.",
|
|
2965
|
+
inputSchema: {
|
|
2966
|
+
type: "object",
|
|
2967
|
+
properties: {
|
|
2968
|
+
slug: {
|
|
2969
|
+
type: "string",
|
|
2970
|
+
description: "The slug of the item to delete (filename without .md)."
|
|
2971
|
+
}
|
|
2972
|
+
},
|
|
2973
|
+
required: ["slug"]
|
|
2974
|
+
}
|
|
2797
2975
|
}
|
|
2798
2976
|
];
|
|
2977
|
+
|
|
2978
|
+
// src/subagents/productVision/executor.ts
|
|
2979
|
+
import fs16 from "fs";
|
|
2980
|
+
import path10 from "path";
|
|
2981
|
+
var ROADMAP_DIR = "src/roadmap";
|
|
2982
|
+
function formatRequires(requires) {
|
|
2983
|
+
return requires.length === 0 ? "[]" : `[${requires.map((r) => `"${r}"`).join(", ")}]`;
|
|
2984
|
+
}
|
|
2799
2985
|
async function executeVisionTool(name, input) {
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2986
|
+
switch (name) {
|
|
2987
|
+
case "writeRoadmapItem": {
|
|
2988
|
+
const {
|
|
2989
|
+
slug,
|
|
2990
|
+
name: itemName,
|
|
2991
|
+
description,
|
|
2992
|
+
effort,
|
|
2993
|
+
requires,
|
|
2994
|
+
body
|
|
2995
|
+
} = input;
|
|
2996
|
+
const filePath = path10.join(ROADMAP_DIR, `${slug}.md`);
|
|
2997
|
+
try {
|
|
2998
|
+
fs16.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
2999
|
+
const content = `---
|
|
2810
3000
|
name: ${itemName}
|
|
2811
3001
|
type: roadmap
|
|
2812
3002
|
status: ${slug === "mvp" ? "in-progress" : "not-started"}
|
|
2813
3003
|
description: ${description}
|
|
2814
3004
|
effort: ${effort}
|
|
2815
|
-
requires: ${
|
|
3005
|
+
requires: ${formatRequires(requires)}
|
|
2816
3006
|
---
|
|
2817
3007
|
|
|
2818
3008
|
${body}
|
|
2819
3009
|
`;
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
3010
|
+
fs16.writeFileSync(filePath, content, "utf-8");
|
|
3011
|
+
return `Wrote ${filePath}`;
|
|
3012
|
+
} catch (err) {
|
|
3013
|
+
return `Error writing ${filePath}: ${err.message}`;
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
case "updateRoadmapItem": {
|
|
3017
|
+
const { slug } = input;
|
|
3018
|
+
const filePath = path10.join(ROADMAP_DIR, `${slug}.md`);
|
|
3019
|
+
try {
|
|
3020
|
+
if (!fs16.existsSync(filePath)) {
|
|
3021
|
+
return `Error: ${filePath} does not exist`;
|
|
3022
|
+
}
|
|
3023
|
+
let content = fs16.readFileSync(filePath, "utf-8");
|
|
3024
|
+
if (input.status) {
|
|
3025
|
+
content = content.replace(
|
|
3026
|
+
/^status:\s*.+$/m,
|
|
3027
|
+
`status: ${input.status}`
|
|
3028
|
+
);
|
|
3029
|
+
}
|
|
3030
|
+
if (input.name) {
|
|
3031
|
+
content = content.replace(/^name:\s*.+$/m, `name: ${input.name}`);
|
|
3032
|
+
}
|
|
3033
|
+
if (input.description) {
|
|
3034
|
+
content = content.replace(
|
|
3035
|
+
/^description:\s*.+$/m,
|
|
3036
|
+
`description: ${input.description}`
|
|
3037
|
+
);
|
|
3038
|
+
}
|
|
3039
|
+
if (input.effort) {
|
|
3040
|
+
content = content.replace(
|
|
3041
|
+
/^effort:\s*.+$/m,
|
|
3042
|
+
`effort: ${input.effort}`
|
|
3043
|
+
);
|
|
3044
|
+
}
|
|
3045
|
+
if (input.requires) {
|
|
3046
|
+
content = content.replace(
|
|
3047
|
+
/^requires:\s*.+$/m,
|
|
3048
|
+
`requires: ${formatRequires(input.requires)}`
|
|
3049
|
+
);
|
|
3050
|
+
}
|
|
3051
|
+
if (input.body) {
|
|
3052
|
+
const endOfFrontmatter = content.indexOf("---", 4);
|
|
3053
|
+
if (endOfFrontmatter !== -1) {
|
|
3054
|
+
const frontmatter = content.slice(0, endOfFrontmatter + 3);
|
|
3055
|
+
content = `${frontmatter}
|
|
3056
|
+
|
|
3057
|
+
${input.body}
|
|
3058
|
+
`;
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
if (input.appendHistory) {
|
|
3062
|
+
if (content.includes("## History")) {
|
|
3063
|
+
content = content.trimEnd() + `
|
|
3064
|
+
${input.appendHistory}
|
|
3065
|
+
`;
|
|
3066
|
+
} else {
|
|
3067
|
+
content = content.trimEnd() + `
|
|
3068
|
+
|
|
3069
|
+
## History
|
|
3070
|
+
|
|
3071
|
+
${input.appendHistory}
|
|
3072
|
+
`;
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
fs16.writeFileSync(filePath, content, "utf-8");
|
|
3076
|
+
return `Updated ${filePath}`;
|
|
3077
|
+
} catch (err) {
|
|
3078
|
+
return `Error updating ${filePath}: ${err.message}`;
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
case "deleteRoadmapItem": {
|
|
3082
|
+
const { slug } = input;
|
|
3083
|
+
const filePath = path10.join(ROADMAP_DIR, `${slug}.md`);
|
|
3084
|
+
try {
|
|
3085
|
+
if (!fs16.existsSync(filePath)) {
|
|
3086
|
+
return `Error: ${filePath} does not exist`;
|
|
3087
|
+
}
|
|
3088
|
+
fs16.unlinkSync(filePath);
|
|
3089
|
+
return `Deleted ${filePath}`;
|
|
3090
|
+
} catch (err) {
|
|
3091
|
+
return `Error deleting ${filePath}: ${err.message}`;
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
default:
|
|
3095
|
+
return `Error: unknown tool "${name}"`;
|
|
2824
3096
|
}
|
|
2825
3097
|
}
|
|
3098
|
+
|
|
3099
|
+
// src/subagents/productVision/prompt.ts
|
|
3100
|
+
import fs17 from "fs";
|
|
3101
|
+
import path11 from "path";
|
|
3102
|
+
var base3 = import.meta.dirname ?? path11.dirname(new URL(import.meta.url).pathname);
|
|
3103
|
+
var local2 = path11.join(base3, "prompt.md");
|
|
3104
|
+
var PROMPT_PATH2 = fs17.existsSync(local2) ? local2 : path11.join(base3, "subagents", "productVision", "prompt.md");
|
|
3105
|
+
var BASE_PROMPT2 = fs17.readFileSync(PROMPT_PATH2, "utf-8").trim();
|
|
3106
|
+
function getProductVisionPrompt() {
|
|
3107
|
+
const specContext = loadSpecContext();
|
|
3108
|
+
const roadmapContext = loadRoadmapContext();
|
|
3109
|
+
const parts = [BASE_PROMPT2, loadPlatformBrief()];
|
|
3110
|
+
if (specContext) {
|
|
3111
|
+
parts.push(specContext);
|
|
3112
|
+
}
|
|
3113
|
+
if (roadmapContext) {
|
|
3114
|
+
parts.push(roadmapContext);
|
|
3115
|
+
}
|
|
3116
|
+
return parts.join("\n\n");
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
// src/subagents/productVision/index.ts
|
|
2826
3120
|
var productVisionTool = {
|
|
2827
3121
|
definition: {
|
|
2828
3122
|
name: "productVision",
|
|
2829
|
-
description:
|
|
3123
|
+
description: "Owns the product roadmap. Reads spec and roadmap files automatically. Creates, updates, and deletes roadmap items in src/roadmap/. Describe the situation and what needs to happen.",
|
|
2830
3124
|
inputSchema: {
|
|
2831
3125
|
type: "object",
|
|
2832
3126
|
properties: {
|
|
2833
3127
|
task: {
|
|
2834
3128
|
type: "string",
|
|
2835
|
-
description: "
|
|
3129
|
+
description: "What to do with the roadmap. Include relevant context. The tool reads spec and roadmap files automatically."
|
|
2836
3130
|
}
|
|
2837
3131
|
},
|
|
2838
3132
|
required: ["task"]
|
|
@@ -2842,23 +3136,166 @@ var productVisionTool = {
|
|
|
2842
3136
|
if (!context) {
|
|
2843
3137
|
return "Error: product vision requires execution context";
|
|
2844
3138
|
}
|
|
2845
|
-
const
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
${specContext}` : BASE_PROMPT;
|
|
2849
|
-
return runSubAgent({
|
|
2850
|
-
system,
|
|
3139
|
+
const result = await runSubAgent({
|
|
3140
|
+
system: getProductVisionPrompt(),
|
|
2851
3141
|
task: input.task,
|
|
2852
3142
|
tools: VISION_TOOLS,
|
|
2853
3143
|
externalTools: /* @__PURE__ */ new Set(),
|
|
2854
3144
|
executeTool: executeVisionTool,
|
|
2855
3145
|
apiConfig: context.apiConfig,
|
|
2856
3146
|
model: context.model,
|
|
3147
|
+
subAgentId: "productVision",
|
|
3148
|
+
signal: context.signal,
|
|
3149
|
+
parentToolId: context.toolCallId,
|
|
3150
|
+
onEvent: context.onEvent,
|
|
3151
|
+
resolveExternalTool: context.resolveExternalTool
|
|
3152
|
+
});
|
|
3153
|
+
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
3154
|
+
return result.text;
|
|
3155
|
+
}
|
|
3156
|
+
};
|
|
3157
|
+
|
|
3158
|
+
// src/subagents/codeSanityCheck/index.ts
|
|
3159
|
+
import fs18 from "fs";
|
|
3160
|
+
import path12 from "path";
|
|
3161
|
+
|
|
3162
|
+
// src/subagents/codeSanityCheck/tools.ts
|
|
3163
|
+
var SANITY_CHECK_TOOLS = [
|
|
3164
|
+
{
|
|
3165
|
+
name: "readFile",
|
|
3166
|
+
description: "Read a file from the project.",
|
|
3167
|
+
inputSchema: {
|
|
3168
|
+
type: "object",
|
|
3169
|
+
properties: {
|
|
3170
|
+
path: {
|
|
3171
|
+
type: "string",
|
|
3172
|
+
description: "File path relative to project root."
|
|
3173
|
+
}
|
|
3174
|
+
},
|
|
3175
|
+
required: ["path"]
|
|
3176
|
+
}
|
|
3177
|
+
},
|
|
3178
|
+
{
|
|
3179
|
+
name: "grep",
|
|
3180
|
+
description: "Search file contents for a pattern.",
|
|
3181
|
+
inputSchema: {
|
|
3182
|
+
type: "object",
|
|
3183
|
+
properties: {
|
|
3184
|
+
pattern: { type: "string", description: "Search pattern (regex)." },
|
|
3185
|
+
path: {
|
|
3186
|
+
type: "string",
|
|
3187
|
+
description: "Directory or file to search in."
|
|
3188
|
+
}
|
|
3189
|
+
},
|
|
3190
|
+
required: ["pattern"]
|
|
3191
|
+
}
|
|
3192
|
+
},
|
|
3193
|
+
{
|
|
3194
|
+
name: "glob",
|
|
3195
|
+
description: "Find files by glob pattern.",
|
|
3196
|
+
inputSchema: {
|
|
3197
|
+
type: "object",
|
|
3198
|
+
properties: {
|
|
3199
|
+
pattern: {
|
|
3200
|
+
type: "string",
|
|
3201
|
+
description: 'Glob pattern (e.g., "src/**/*.ts").'
|
|
3202
|
+
}
|
|
3203
|
+
},
|
|
3204
|
+
required: ["pattern"]
|
|
3205
|
+
}
|
|
3206
|
+
},
|
|
3207
|
+
{
|
|
3208
|
+
name: "searchGoogle",
|
|
3209
|
+
description: "Search the web. Use to verify packages are current or find alternatives.",
|
|
3210
|
+
inputSchema: {
|
|
3211
|
+
type: "object",
|
|
3212
|
+
properties: {
|
|
3213
|
+
query: { type: "string", description: "Search query." }
|
|
3214
|
+
},
|
|
3215
|
+
required: ["query"]
|
|
3216
|
+
}
|
|
3217
|
+
},
|
|
3218
|
+
{
|
|
3219
|
+
name: "fetchUrl",
|
|
3220
|
+
description: "Fetch a web page as markdown. Use to read package docs, changelogs, npm pages.",
|
|
3221
|
+
inputSchema: {
|
|
3222
|
+
type: "object",
|
|
3223
|
+
properties: {
|
|
3224
|
+
url: { type: "string", description: "URL to fetch." }
|
|
3225
|
+
},
|
|
3226
|
+
required: ["url"]
|
|
3227
|
+
}
|
|
3228
|
+
},
|
|
3229
|
+
{
|
|
3230
|
+
name: "askMindStudioSdk",
|
|
3231
|
+
description: "Check if the MindStudio SDK has a managed action for something before writing custom code.",
|
|
3232
|
+
inputSchema: {
|
|
3233
|
+
type: "object",
|
|
3234
|
+
properties: {
|
|
3235
|
+
query: { type: "string", description: "What you want to check." }
|
|
3236
|
+
},
|
|
3237
|
+
required: ["query"]
|
|
3238
|
+
}
|
|
3239
|
+
},
|
|
3240
|
+
{
|
|
3241
|
+
name: "bash",
|
|
3242
|
+
description: "Run a shell command. Use for reading/search/etc operations only.",
|
|
3243
|
+
inputSchema: {
|
|
3244
|
+
type: "object",
|
|
3245
|
+
properties: {
|
|
3246
|
+
command: { type: "string", description: "The command to run." }
|
|
3247
|
+
},
|
|
3248
|
+
required: ["command"]
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
];
|
|
3252
|
+
|
|
3253
|
+
// src/subagents/codeSanityCheck/index.ts
|
|
3254
|
+
var base4 = import.meta.dirname ?? path12.dirname(new URL(import.meta.url).pathname);
|
|
3255
|
+
var local3 = path12.join(base4, "prompt.md");
|
|
3256
|
+
var PROMPT_PATH3 = fs18.existsSync(local3) ? local3 : path12.join(base4, "subagents", "codeSanityCheck", "prompt.md");
|
|
3257
|
+
var BASE_PROMPT3 = fs18.readFileSync(PROMPT_PATH3, "utf-8").trim();
|
|
3258
|
+
var codeSanityCheckTool = {
|
|
3259
|
+
definition: {
|
|
3260
|
+
name: "codeSanityCheck",
|
|
3261
|
+
description: 'Quick sanity check on an approach before building. Reviews architecture, package choices, and flags potential issues. Usually responds with "looks good." Occasionally catches something important. Readonly \u2014 can search the web and read code but cannot modify anything.',
|
|
3262
|
+
inputSchema: {
|
|
3263
|
+
type: "object",
|
|
3264
|
+
properties: {
|
|
3265
|
+
task: {
|
|
3266
|
+
type: "string",
|
|
3267
|
+
description: "What you're about to build and how. Include the plan, packages you intend to use, and any architectural decisions you've made."
|
|
3268
|
+
}
|
|
3269
|
+
},
|
|
3270
|
+
required: ["task"]
|
|
3271
|
+
}
|
|
3272
|
+
},
|
|
3273
|
+
async execute(input, context) {
|
|
3274
|
+
if (!context) {
|
|
3275
|
+
return "Error: code sanity check requires execution context";
|
|
3276
|
+
}
|
|
3277
|
+
const specContext = loadSpecContext();
|
|
3278
|
+
const parts = [BASE_PROMPT3, loadPlatformBrief()];
|
|
3279
|
+
if (specContext) {
|
|
3280
|
+
parts.push(specContext);
|
|
3281
|
+
}
|
|
3282
|
+
const system = parts.join("\n\n");
|
|
3283
|
+
const result = await runSubAgent({
|
|
3284
|
+
system,
|
|
3285
|
+
task: input.task,
|
|
3286
|
+
tools: SANITY_CHECK_TOOLS,
|
|
3287
|
+
externalTools: /* @__PURE__ */ new Set(),
|
|
3288
|
+
executeTool: (name, toolInput) => executeTool(name, toolInput, context),
|
|
3289
|
+
apiConfig: context.apiConfig,
|
|
3290
|
+
model: context.model,
|
|
3291
|
+
subAgentId: "codeSanityCheck",
|
|
2857
3292
|
signal: context.signal,
|
|
2858
3293
|
parentToolId: context.toolCallId,
|
|
2859
3294
|
onEvent: context.onEvent,
|
|
2860
3295
|
resolveExternalTool: context.resolveExternalTool
|
|
2861
3296
|
});
|
|
3297
|
+
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
3298
|
+
return result.text;
|
|
2862
3299
|
}
|
|
2863
3300
|
};
|
|
2864
3301
|
|
|
@@ -2896,7 +3333,8 @@ function getCommonTools() {
|
|
|
2896
3333
|
searchGoogleTool,
|
|
2897
3334
|
setProjectNameTool,
|
|
2898
3335
|
designExpertTool,
|
|
2899
|
-
productVisionTool
|
|
3336
|
+
productVisionTool,
|
|
3337
|
+
codeSanityCheckTool
|
|
2900
3338
|
];
|
|
2901
3339
|
}
|
|
2902
3340
|
function getPostOnboardingTools() {
|
|
@@ -2943,11 +3381,11 @@ function executeTool(name, input, context) {
|
|
|
2943
3381
|
}
|
|
2944
3382
|
|
|
2945
3383
|
// src/session.ts
|
|
2946
|
-
import
|
|
3384
|
+
import fs19 from "fs";
|
|
2947
3385
|
var SESSION_FILE = ".remy-session.json";
|
|
2948
3386
|
function loadSession(state) {
|
|
2949
3387
|
try {
|
|
2950
|
-
const raw =
|
|
3388
|
+
const raw = fs19.readFileSync(SESSION_FILE, "utf-8");
|
|
2951
3389
|
const data = JSON.parse(raw);
|
|
2952
3390
|
if (Array.isArray(data.messages) && data.messages.length > 0) {
|
|
2953
3391
|
state.messages = sanitizeMessages(data.messages);
|
|
@@ -2962,7 +3400,13 @@ function sanitizeMessages(messages) {
|
|
|
2962
3400
|
for (let i = 0; i < messages.length; i++) {
|
|
2963
3401
|
result.push(messages[i]);
|
|
2964
3402
|
const msg = messages[i];
|
|
2965
|
-
if (msg.role !== "assistant" || !msg.
|
|
3403
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
|
|
3404
|
+
continue;
|
|
3405
|
+
}
|
|
3406
|
+
const toolBlocks = msg.content.filter(
|
|
3407
|
+
(b) => b.type === "tool"
|
|
3408
|
+
);
|
|
3409
|
+
if (toolBlocks.length === 0) {
|
|
2966
3410
|
continue;
|
|
2967
3411
|
}
|
|
2968
3412
|
const resultIds = /* @__PURE__ */ new Set();
|
|
@@ -2974,7 +3418,7 @@ function sanitizeMessages(messages) {
|
|
|
2974
3418
|
break;
|
|
2975
3419
|
}
|
|
2976
3420
|
}
|
|
2977
|
-
for (const tc of
|
|
3421
|
+
for (const tc of toolBlocks) {
|
|
2978
3422
|
if (!resultIds.has(tc.id)) {
|
|
2979
3423
|
result.push({
|
|
2980
3424
|
role: "user",
|
|
@@ -2989,7 +3433,7 @@ function sanitizeMessages(messages) {
|
|
|
2989
3433
|
}
|
|
2990
3434
|
function saveSession(state) {
|
|
2991
3435
|
try {
|
|
2992
|
-
|
|
3436
|
+
fs19.writeFileSync(
|
|
2993
3437
|
SESSION_FILE,
|
|
2994
3438
|
JSON.stringify({ messages: state.messages }, null, 2),
|
|
2995
3439
|
"utf-8"
|
|
@@ -3000,7 +3444,7 @@ function saveSession(state) {
|
|
|
3000
3444
|
function clearSession(state) {
|
|
3001
3445
|
state.messages = [];
|
|
3002
3446
|
try {
|
|
3003
|
-
|
|
3447
|
+
fs19.unlinkSync(SESSION_FILE);
|
|
3004
3448
|
} catch {
|
|
3005
3449
|
}
|
|
3006
3450
|
}
|
|
@@ -3272,6 +3716,14 @@ function friendlyError(raw) {
|
|
|
3272
3716
|
}
|
|
3273
3717
|
|
|
3274
3718
|
// src/agent.ts
|
|
3719
|
+
function getTextContent(blocks) {
|
|
3720
|
+
return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
3721
|
+
}
|
|
3722
|
+
function getToolCalls(blocks) {
|
|
3723
|
+
return blocks.filter(
|
|
3724
|
+
(b) => b.type === "tool"
|
|
3725
|
+
);
|
|
3726
|
+
}
|
|
3275
3727
|
var EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
|
|
3276
3728
|
"promptUser",
|
|
3277
3729
|
"setProjectOnboardingState",
|
|
@@ -3283,7 +3735,6 @@ var EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
|
|
|
3283
3735
|
"runScenario",
|
|
3284
3736
|
"runMethod",
|
|
3285
3737
|
"browserCommand",
|
|
3286
|
-
"screenshot",
|
|
3287
3738
|
"setProjectName"
|
|
3288
3739
|
]);
|
|
3289
3740
|
function createAgentState() {
|
|
@@ -3327,6 +3778,12 @@ async function runTurn(params) {
|
|
|
3327
3778
|
});
|
|
3328
3779
|
}
|
|
3329
3780
|
state.messages.push(userMsg);
|
|
3781
|
+
const STATUS_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
|
|
3782
|
+
"setProjectOnboardingState",
|
|
3783
|
+
"setProjectName",
|
|
3784
|
+
"clearSyncStatus",
|
|
3785
|
+
"editsFinished"
|
|
3786
|
+
]);
|
|
3330
3787
|
let lastCompletedTools = "";
|
|
3331
3788
|
let lastCompletedResult = "";
|
|
3332
3789
|
while (true) {
|
|
@@ -3344,8 +3801,8 @@ async function runTurn(params) {
|
|
|
3344
3801
|
saveSession(state);
|
|
3345
3802
|
return;
|
|
3346
3803
|
}
|
|
3347
|
-
|
|
3348
|
-
|
|
3804
|
+
const contentBlocks = [];
|
|
3805
|
+
let thinkingStartedAt = 0;
|
|
3349
3806
|
const toolInputAccumulators = /* @__PURE__ */ new Map();
|
|
3350
3807
|
let stopReason = "end_turn";
|
|
3351
3808
|
async function handlePartialInput(acc, id, name, partial) {
|
|
@@ -3414,8 +3871,8 @@ async function runTurn(params) {
|
|
|
3414
3871
|
const statusWatcher = startStatusWatcher({
|
|
3415
3872
|
apiConfig,
|
|
3416
3873
|
getContext: () => ({
|
|
3417
|
-
assistantText:
|
|
3418
|
-
lastToolName:
|
|
3874
|
+
assistantText: getTextContent(contentBlocks).slice(-500),
|
|
3875
|
+
lastToolName: getToolCalls(contentBlocks).filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).at(-1)?.name || lastCompletedTools || void 0,
|
|
3419
3876
|
lastToolResult: lastCompletedResult || void 0
|
|
3420
3877
|
}),
|
|
3421
3878
|
onStatus: (label) => onEvent({ type: "status", message: label }),
|
|
@@ -3427,7 +3884,7 @@ async function runTurn(params) {
|
|
|
3427
3884
|
...apiConfig,
|
|
3428
3885
|
model,
|
|
3429
3886
|
system,
|
|
3430
|
-
messages: state.messages,
|
|
3887
|
+
messages: cleanMessagesForApi(state.messages),
|
|
3431
3888
|
tools,
|
|
3432
3889
|
signal
|
|
3433
3890
|
},
|
|
@@ -3444,13 +3901,36 @@ async function runTurn(params) {
|
|
|
3444
3901
|
break;
|
|
3445
3902
|
}
|
|
3446
3903
|
switch (event.type) {
|
|
3447
|
-
case "text":
|
|
3448
|
-
|
|
3904
|
+
case "text": {
|
|
3905
|
+
const lastBlock = contentBlocks.at(-1);
|
|
3906
|
+
if (lastBlock?.type === "text") {
|
|
3907
|
+
lastBlock.text += event.text;
|
|
3908
|
+
} else {
|
|
3909
|
+
contentBlocks.push({
|
|
3910
|
+
type: "text",
|
|
3911
|
+
text: event.text,
|
|
3912
|
+
startedAt: event.ts
|
|
3913
|
+
});
|
|
3914
|
+
}
|
|
3449
3915
|
onEvent({ type: "text", text: event.text });
|
|
3450
3916
|
break;
|
|
3917
|
+
}
|
|
3451
3918
|
case "thinking":
|
|
3919
|
+
if (!thinkingStartedAt) {
|
|
3920
|
+
thinkingStartedAt = event.ts;
|
|
3921
|
+
}
|
|
3452
3922
|
onEvent({ type: "thinking", text: event.text });
|
|
3453
3923
|
break;
|
|
3924
|
+
case "thinking_complete":
|
|
3925
|
+
contentBlocks.push({
|
|
3926
|
+
type: "thinking",
|
|
3927
|
+
thinking: event.thinking,
|
|
3928
|
+
signature: event.signature,
|
|
3929
|
+
startedAt: thinkingStartedAt,
|
|
3930
|
+
completedAt: event.ts
|
|
3931
|
+
});
|
|
3932
|
+
thinkingStartedAt = 0;
|
|
3933
|
+
break;
|
|
3454
3934
|
case "tool_input_delta": {
|
|
3455
3935
|
const acc = getOrCreateAccumulator2(event.id, event.name);
|
|
3456
3936
|
acc.json += event.delta;
|
|
@@ -3478,10 +3958,12 @@ async function runTurn(params) {
|
|
|
3478
3958
|
break;
|
|
3479
3959
|
}
|
|
3480
3960
|
case "tool_use": {
|
|
3481
|
-
|
|
3961
|
+
contentBlocks.push({
|
|
3962
|
+
type: "tool",
|
|
3482
3963
|
id: event.id,
|
|
3483
3964
|
name: event.name,
|
|
3484
|
-
input: event.input
|
|
3965
|
+
input: event.input,
|
|
3966
|
+
startedAt: event.ts
|
|
3485
3967
|
});
|
|
3486
3968
|
const acc = toolInputAccumulators.get(event.id);
|
|
3487
3969
|
const tool = getToolByName(event.name);
|
|
@@ -3520,11 +4002,15 @@ async function runTurn(params) {
|
|
|
3520
4002
|
statusWatcher.stop();
|
|
3521
4003
|
}
|
|
3522
4004
|
if (signal?.aborted) {
|
|
3523
|
-
if (
|
|
4005
|
+
if (contentBlocks.length > 0) {
|
|
4006
|
+
contentBlocks.push({
|
|
4007
|
+
type: "text",
|
|
4008
|
+
text: "\n\n(cancelled)",
|
|
4009
|
+
startedAt: Date.now()
|
|
4010
|
+
});
|
|
3524
4011
|
state.messages.push({
|
|
3525
4012
|
role: "assistant",
|
|
3526
|
-
content:
|
|
3527
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : void 0
|
|
4013
|
+
content: [...contentBlocks].sort((a, b) => a.startedAt - b.startedAt)
|
|
3528
4014
|
});
|
|
3529
4015
|
}
|
|
3530
4016
|
onEvent({ type: "turn_cancelled" });
|
|
@@ -3533,9 +4019,9 @@ async function runTurn(params) {
|
|
|
3533
4019
|
}
|
|
3534
4020
|
state.messages.push({
|
|
3535
4021
|
role: "assistant",
|
|
3536
|
-
content:
|
|
3537
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : void 0
|
|
4022
|
+
content: [...contentBlocks].sort((a, b) => a.startedAt - b.startedAt)
|
|
3538
4023
|
});
|
|
4024
|
+
const toolCalls = getToolCalls(contentBlocks);
|
|
3539
4025
|
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
3540
4026
|
saveSession(state);
|
|
3541
4027
|
onEvent({ type: "turn_done" });
|
|
@@ -3545,6 +4031,29 @@ async function runTurn(params) {
|
|
|
3545
4031
|
count: toolCalls.length,
|
|
3546
4032
|
tools: toolCalls.map((tc) => tc.name)
|
|
3547
4033
|
});
|
|
4034
|
+
let subAgentText = "";
|
|
4035
|
+
const origOnEvent = onEvent;
|
|
4036
|
+
const wrappedOnEvent = (e) => {
|
|
4037
|
+
if ("parentToolId" in e && e.parentToolId) {
|
|
4038
|
+
if (e.type === "text") {
|
|
4039
|
+
subAgentText = e.text;
|
|
4040
|
+
} else if (e.type === "tool_start") {
|
|
4041
|
+
subAgentText = `Using ${e.name}`;
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
origOnEvent(e);
|
|
4045
|
+
};
|
|
4046
|
+
const toolStatusWatcher = startStatusWatcher({
|
|
4047
|
+
apiConfig,
|
|
4048
|
+
getContext: () => ({
|
|
4049
|
+
assistantText: subAgentText || getTextContent(contentBlocks).slice(-500),
|
|
4050
|
+
lastToolName: toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ") || void 0,
|
|
4051
|
+
lastToolResult: lastCompletedResult || void 0
|
|
4052
|
+
}),
|
|
4053
|
+
onStatus: (label) => origOnEvent({ type: "status", message: label }),
|
|
4054
|
+
signal
|
|
4055
|
+
});
|
|
4056
|
+
const subAgentMessages = /* @__PURE__ */ new Map();
|
|
3548
4057
|
const results = await Promise.all(
|
|
3549
4058
|
toolCalls.map(async (tc) => {
|
|
3550
4059
|
if (signal?.aborted) {
|
|
@@ -3569,9 +4078,10 @@ async function runTurn(params) {
|
|
|
3569
4078
|
apiConfig,
|
|
3570
4079
|
model,
|
|
3571
4080
|
signal,
|
|
3572
|
-
onEvent,
|
|
4081
|
+
onEvent: wrappedOnEvent,
|
|
3573
4082
|
resolveExternalTool,
|
|
3574
|
-
toolCallId: tc.id
|
|
4083
|
+
toolCallId: tc.id,
|
|
4084
|
+
subAgentMessages
|
|
3575
4085
|
});
|
|
3576
4086
|
}
|
|
3577
4087
|
const isError = result.startsWith("Error");
|
|
@@ -3602,7 +4112,21 @@ async function runTurn(params) {
|
|
|
3602
4112
|
}
|
|
3603
4113
|
})
|
|
3604
4114
|
);
|
|
3605
|
-
|
|
4115
|
+
toolStatusWatcher.stop();
|
|
4116
|
+
for (const r of results) {
|
|
4117
|
+
const block = contentBlocks.find(
|
|
4118
|
+
(b) => b.type === "tool" && b.id === r.id
|
|
4119
|
+
);
|
|
4120
|
+
if (block?.type === "tool") {
|
|
4121
|
+
block.result = r.result;
|
|
4122
|
+
block.isError = r.isError;
|
|
4123
|
+
const msgs = subAgentMessages.get(r.id);
|
|
4124
|
+
if (msgs) {
|
|
4125
|
+
block.subAgentMessages = msgs;
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
lastCompletedTools = toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ");
|
|
3606
4130
|
lastCompletedResult = results.at(-1)?.result ?? "";
|
|
3607
4131
|
for (const r of results) {
|
|
3608
4132
|
state.messages.push({
|
|
@@ -3621,10 +4145,10 @@ async function runTurn(params) {
|
|
|
3621
4145
|
}
|
|
3622
4146
|
|
|
3623
4147
|
// src/headless.ts
|
|
3624
|
-
var BASE_DIR = import.meta.dirname ??
|
|
3625
|
-
var ACTIONS_DIR =
|
|
4148
|
+
var BASE_DIR = import.meta.dirname ?? path13.dirname(new URL(import.meta.url).pathname);
|
|
4149
|
+
var ACTIONS_DIR = path13.join(BASE_DIR, "actions");
|
|
3626
4150
|
function loadActionPrompt(name) {
|
|
3627
|
-
return
|
|
4151
|
+
return fs20.readFileSync(path13.join(ACTIONS_DIR, `${name}.md`), "utf-8").trim();
|
|
3628
4152
|
}
|
|
3629
4153
|
function emit(event, data) {
|
|
3630
4154
|
process.stdout.write(JSON.stringify({ event, ...data }) + "\n");
|
|
@@ -3712,19 +4236,27 @@ async function startHeadless(opts = {}) {
|
|
|
3712
4236
|
break;
|
|
3713
4237
|
}
|
|
3714
4238
|
}
|
|
3715
|
-
|
|
4239
|
+
const USER_FACING_TOOLS = /* @__PURE__ */ new Set([
|
|
4240
|
+
"promptUser",
|
|
4241
|
+
"confirmDestructiveAction",
|
|
4242
|
+
"presentPlan",
|
|
4243
|
+
"presentSyncPlan",
|
|
4244
|
+
"presentPublishPlan"
|
|
4245
|
+
]);
|
|
4246
|
+
function resolveExternalTool(id, name, _input) {
|
|
3716
4247
|
const early = earlyResults.get(id);
|
|
3717
4248
|
if (early !== void 0) {
|
|
3718
4249
|
earlyResults.delete(id);
|
|
3719
4250
|
return Promise.resolve(early);
|
|
3720
4251
|
}
|
|
4252
|
+
const shouldTimeout = !USER_FACING_TOOLS.has(name);
|
|
3721
4253
|
return new Promise((resolve) => {
|
|
3722
|
-
const timeout = setTimeout(() => {
|
|
4254
|
+
const timeout = shouldTimeout ? setTimeout(() => {
|
|
3723
4255
|
pendingTools.delete(id);
|
|
3724
4256
|
resolve(
|
|
3725
4257
|
"Error: Tool timed out \u2014 no response from the app environment after 5 minutes."
|
|
3726
4258
|
);
|
|
3727
|
-
}, EXTERNAL_TOOL_TIMEOUT_MS);
|
|
4259
|
+
}, EXTERNAL_TOOL_TIMEOUT_MS) : void 0;
|
|
3728
4260
|
pendingTools.set(id, {
|
|
3729
4261
|
resolve: (result) => {
|
|
3730
4262
|
clearTimeout(timeout);
|