@mindstudio-ai/remy 0.1.308 → 0.1.309
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/headless.js +281 -188
- package/dist/index.js +300 -204
- package/dist/prompt/compiled/interfaces.md +2 -0
- package/dist/prompt/compiled/platform.md +1 -1
- package/dist/prompt/compiled/sdk-actions.md +4 -0
- package/dist/prompt/skills/agentInterfaces.md +7 -1
- package/dist/prompt/skills/realtime.md +80 -0
- package/dist/prompt/static/intake.md +1 -1
- package/dist/subagents/designExpert/skills/chatExperience.md +15 -6
- package/package.json +1 -1
package/dist/headless.js
CHANGED
|
@@ -3243,6 +3243,7 @@ async function renderHtmlViaSidecar(opts) {
|
|
|
3243
3243
|
html: opts.html,
|
|
3244
3244
|
width: opts.width,
|
|
3245
3245
|
height: opts.height,
|
|
3246
|
+
...opts.autoHeight ? { autoHeight: true } : {},
|
|
3246
3247
|
...opts.transparent ? { transparent: true } : {},
|
|
3247
3248
|
...opts.scale != null ? { scale: opts.scale } : {}
|
|
3248
3249
|
},
|
|
@@ -4566,7 +4567,7 @@ The first-party SDK (@mindstudio-ai/agent) provides access to 200+ AI models (Op
|
|
|
4566
4567
|
## What Remy apps are NOT good for
|
|
4567
4568
|
|
|
4568
4569
|
- Native mobile apps (iOS/Android). Mobile-responsive web apps are fine.
|
|
4569
|
-
-
|
|
4570
|
+
- Fast-twitch multiplayer and live co-editing (shared cursors, 60fps sync) \u2014 client\u2192server is always a method invoke, so sub-100ms bidirectional interaction isn't a fit.
|
|
4570
4571
|
</platform_brief>`;
|
|
4571
4572
|
}
|
|
4572
4573
|
|
|
@@ -4933,9 +4934,146 @@ async function execute2(input, onLog) {
|
|
|
4933
4934
|
// src/subagents/designExpert/tools/analyzeDesign.ts
|
|
4934
4935
|
var analyzeDesign_exports = {};
|
|
4935
4936
|
__export(analyzeDesign_exports, {
|
|
4937
|
+
definition: () => definition4,
|
|
4938
|
+
execute: () => execute4
|
|
4939
|
+
});
|
|
4940
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
4941
|
+
import { extname as extname2, join as join2 } from "path";
|
|
4942
|
+
|
|
4943
|
+
// src/subagents/designExpert/tools/images/renderImage.ts
|
|
4944
|
+
var renderImage_exports = {};
|
|
4945
|
+
__export(renderImage_exports, {
|
|
4946
|
+
RENDER_ANALYZE_PROMPT: () => RENDER_ANALYZE_PROMPT,
|
|
4936
4947
|
definition: () => definition3,
|
|
4937
4948
|
execute: () => execute3
|
|
4938
4949
|
});
|
|
4950
|
+
import { mkdir, unlink, writeFile } from "fs/promises";
|
|
4951
|
+
import { tmpdir } from "os";
|
|
4952
|
+
import { dirname, join, resolve as resolve2, sep } from "path";
|
|
4953
|
+
import { randomUUID } from "crypto";
|
|
4954
|
+
var MIN_DIMENSION = 16;
|
|
4955
|
+
var MAX_DIMENSION = 4096;
|
|
4956
|
+
var RENDER_ANALYZE_PROMPT = 'You are reviewing a browser-rendered graphic (composed from HTML/CSS by a designer) for fidelity. Report: whether the composition fills the full canvas or leaves unintended gaps at any edge, any clipped or overflowing text, whether custom webfonts appear to have loaded (distinctive letterforms vs generic fallback serif/sans), any misalignment or uneven spacing, any unintended scrollbars or default-styling artifacts, and \u2014 if the background is transparent \u2014 any fringing or stray opaque pixels at the edges. Then briefly describe the overall composition and how polished it looks. Be concise and practical. Respond only with your analysis as Markdown (starting with the title "Render Review") and absolutely no other text. Do not use emojis - use unicode if you need symbols.';
|
|
4957
|
+
var definition3 = {
|
|
4958
|
+
name: "renderImage",
|
|
4959
|
+
description: "Render a self-contained HTML document in a real browser and capture it as a hosted PNG at exact pixel dimensions, with a fidelity review included. Deterministic \u2014 exact hex colors, real loaded webfonts, precise geometry \u2014 unlike generateImages, which is an image model. Use for token-exact graphics: Open Graph share cards, wordmarks, flat/geometric icon tiles, badges, any composition where letterforms and spacing carry the design. Compose with HTML/CSS (link webfonts from CDNs \u2014 the renderer waits for them to load); inline existing SVG markup when needed, but never hand-write new SVG path data.",
|
|
4960
|
+
inputSchema: {
|
|
4961
|
+
type: "object",
|
|
4962
|
+
properties: {
|
|
4963
|
+
html: {
|
|
4964
|
+
type: "string",
|
|
4965
|
+
description: "A complete, self-contained HTML document sized to fill the viewport (style html/body to the full dimensions with margin 0). External webfonts and images from CDNs are fine \u2014 loading is awaited before capture."
|
|
4966
|
+
},
|
|
4967
|
+
width: {
|
|
4968
|
+
type: "number",
|
|
4969
|
+
description: "Viewport width in CSS pixels (e.g. 1200 for an OG card, 512 for an icon tile). Range: 16-4096."
|
|
4970
|
+
},
|
|
4971
|
+
height: {
|
|
4972
|
+
type: "number",
|
|
4973
|
+
description: "Viewport height in CSS pixels. Range: 16-4096."
|
|
4974
|
+
},
|
|
4975
|
+
scale: {
|
|
4976
|
+
type: "number",
|
|
4977
|
+
description: "Device scale factor, 1-3. Output pixels = css \xD7 scale. Use 2 for crisp icon masters (e.g. a 512\xD7512 document captured at 1024\xD71024)."
|
|
4978
|
+
},
|
|
4979
|
+
transparentBackground: {
|
|
4980
|
+
type: "boolean",
|
|
4981
|
+
description: "Capture with true alpha: leave the document background transparent (no background on html/body) and the PNG keeps it. No background-removal model involved."
|
|
4982
|
+
},
|
|
4983
|
+
savePath: {
|
|
4984
|
+
type: "string",
|
|
4985
|
+
description: "Optional project-relative path to also save the PNG into the app (e.g. 'dist/interfaces/web/public/og-image.png' so the deployed site self-hosts it)."
|
|
4986
|
+
}
|
|
4987
|
+
},
|
|
4988
|
+
required: ["html", "width", "height"]
|
|
4989
|
+
}
|
|
4990
|
+
};
|
|
4991
|
+
async function execute3(input, onLog, context) {
|
|
4992
|
+
const html = typeof input.html === "string" ? input.html : "";
|
|
4993
|
+
const width = Math.round(Number(input.width));
|
|
4994
|
+
const height = Math.round(Number(input.height));
|
|
4995
|
+
if (!html || !Number.isFinite(width) || !Number.isFinite(height) || width < MIN_DIMENSION || width > MAX_DIMENSION || height < MIN_DIMENSION || height > MAX_DIMENSION) {
|
|
4996
|
+
return `Error: renderImage requires an html string plus width/height between ${MIN_DIMENSION} and ${MAX_DIMENSION}.`;
|
|
4997
|
+
}
|
|
4998
|
+
const release = await acquireBrowserLock();
|
|
4999
|
+
let rendered;
|
|
5000
|
+
try {
|
|
5001
|
+
onLog?.("Rendering document in the sandbox browser...");
|
|
5002
|
+
rendered = await renderHtmlViaSidecar({
|
|
5003
|
+
html,
|
|
5004
|
+
width,
|
|
5005
|
+
height,
|
|
5006
|
+
transparent: input.transparentBackground === true,
|
|
5007
|
+
scale: typeof input.scale === "number" ? input.scale : void 0
|
|
5008
|
+
});
|
|
5009
|
+
} catch (err) {
|
|
5010
|
+
return `Error: render failed: ${err?.message ?? err}`;
|
|
5011
|
+
} finally {
|
|
5012
|
+
release();
|
|
5013
|
+
}
|
|
5014
|
+
let bytes;
|
|
5015
|
+
try {
|
|
5016
|
+
const res = await fetch(rendered.url);
|
|
5017
|
+
if (!res.ok) {
|
|
5018
|
+
throw new Error(`fetch returned ${res.status}`);
|
|
5019
|
+
}
|
|
5020
|
+
bytes = Buffer.from(await res.arrayBuffer());
|
|
5021
|
+
} catch (err) {
|
|
5022
|
+
return `Error: rendered but could not download the capture (${err?.message ?? err}). Temporary URL: ${rendered.url}`;
|
|
5023
|
+
}
|
|
5024
|
+
let savedPath;
|
|
5025
|
+
if (typeof input.savePath === "string" && input.savePath) {
|
|
5026
|
+
const absolute = resolve2(PROJECT_ROOT, input.savePath);
|
|
5027
|
+
if (!absolute.startsWith(PROJECT_ROOT + sep)) {
|
|
5028
|
+
return "Error: savePath must resolve inside the project.";
|
|
5029
|
+
}
|
|
5030
|
+
await mkdir(dirname(absolute), { recursive: true });
|
|
5031
|
+
await writeFile(absolute, bytes);
|
|
5032
|
+
savedPath = input.savePath;
|
|
5033
|
+
}
|
|
5034
|
+
onLog?.("Hosting the capture...");
|
|
5035
|
+
const tmpPath = join(tmpdir(), `render-${randomUUID()}.png`);
|
|
5036
|
+
let url = rendered.url;
|
|
5037
|
+
let temporary = true;
|
|
5038
|
+
try {
|
|
5039
|
+
await writeFile(tmpPath, bytes);
|
|
5040
|
+
const upload = await runMindstudioCliResult(["upload", tmpPath], {
|
|
5041
|
+
timeout: 6e4,
|
|
5042
|
+
onLog,
|
|
5043
|
+
caller: "designExpert"
|
|
5044
|
+
});
|
|
5045
|
+
const match = upload.ok ? upload.value.match(/https:\/\/\S+/g) : null;
|
|
5046
|
+
if (match?.length) {
|
|
5047
|
+
url = match[match.length - 1];
|
|
5048
|
+
temporary = false;
|
|
5049
|
+
}
|
|
5050
|
+
} finally {
|
|
5051
|
+
await unlink(tmpPath).catch(() => {
|
|
5052
|
+
});
|
|
5053
|
+
}
|
|
5054
|
+
const analysis = await analyzeImage({
|
|
5055
|
+
prompt: RENDER_ANALYZE_PROMPT,
|
|
5056
|
+
image: url,
|
|
5057
|
+
onLog,
|
|
5058
|
+
model: resolveModel("imageAnalysis", context?.models, context?.model)
|
|
5059
|
+
}).then((r) => r.analysis).catch((err) => `Could not review this image: ${err.message}`);
|
|
5060
|
+
return JSON.stringify({
|
|
5061
|
+
images: [
|
|
5062
|
+
{
|
|
5063
|
+
url,
|
|
5064
|
+
...temporary ? {
|
|
5065
|
+
note: "Durable hosting failed \u2014 this URL is dev-session scratch storage and may expire. Do not use it for app metadata; retry if a durable URL is needed."
|
|
5066
|
+
} : {},
|
|
5067
|
+
...savedPath ? { savedPath } : {},
|
|
5068
|
+
analysis,
|
|
5069
|
+
width: rendered.width,
|
|
5070
|
+
height: rendered.height
|
|
5071
|
+
}
|
|
5072
|
+
]
|
|
5073
|
+
});
|
|
5074
|
+
}
|
|
5075
|
+
|
|
5076
|
+
// src/subagents/designExpert/tools/analyzeDesign.ts
|
|
4939
5077
|
var DESIGN_REFERENCE_PROMPT = `
|
|
4940
5078
|
You are analyzing a screenshot of a real website or app for a designer's personal technique/inspiration reference notes.
|
|
4941
5079
|
|
|
@@ -4959,15 +5097,15 @@ Identify the specific design moves that make this page interesting and unique, d
|
|
|
4959
5097
|
|
|
4960
5098
|
Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.
|
|
4961
5099
|
`;
|
|
4962
|
-
var
|
|
5100
|
+
var definition4 = {
|
|
4963
5101
|
name: "analyzeDesign",
|
|
4964
|
-
description: "Analyze the visual design of a website, an image URL, or an
|
|
5102
|
+
description: "Analyze the visual design of a website, an image (URL or file on disk), or an HTML document on disk. Websites are screenshotted first; an HTML file is rendered in a real browser first and the render is analyzed, which is how you look at a wireframe you authored. Provides static image analysis only, will not capture animations or video. With no prompt, a website or image gets a full design reference analysis (mood, color, typography, layout, distinctiveness) and a rendered HTML document gets a fidelity review (clipping, overflow, webfont loading, alignment). Provide a custom prompt to ask a specific design question instead. Use a bulleted list to ask many questions at once.",
|
|
4965
5103
|
inputSchema: {
|
|
4966
5104
|
type: "object",
|
|
4967
5105
|
properties: {
|
|
4968
5106
|
url: {
|
|
4969
5107
|
type: "string",
|
|
4970
|
-
description: "What to analyze: a website URL (will be screenshotted), an image URL,
|
|
5108
|
+
description: "What to analyze: a website URL (will be screenshotted), an image URL, the path of an image file on disk (e.g. a reference the user uploaded, under src/.user-uploads/), or the path of an HTML file on disk (rendered in a browser, then analyzed). Local files are hosted automatically and their URL comes back in the result."
|
|
4971
5109
|
},
|
|
4972
5110
|
prompt: {
|
|
4973
5111
|
type: "string",
|
|
@@ -4977,53 +5115,140 @@ var definition3 = {
|
|
|
4977
5115
|
required: ["url"]
|
|
4978
5116
|
}
|
|
4979
5117
|
};
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5118
|
+
var IMAGE_EXT_RE = /\.(png|jpe?g|webp|gif|svg|avif)(\?|$)/i;
|
|
5119
|
+
var HTML_EXTS = /* @__PURE__ */ new Set([".html", ".htm"]);
|
|
5120
|
+
var HTML_RENDER_WIDTH = 1280;
|
|
5121
|
+
var HTML_RENDER_START_HEIGHT = 1400;
|
|
5122
|
+
function splitFrontmatter(raw) {
|
|
5123
|
+
const match = raw.match(/^---\n([\s\S]*?)\n---\n?/);
|
|
5124
|
+
if (!match) {
|
|
5125
|
+
return { html: raw };
|
|
5126
|
+
}
|
|
5127
|
+
const fields = {};
|
|
5128
|
+
for (const line of match[1].split("\n")) {
|
|
5129
|
+
const idx = line.indexOf(":");
|
|
5130
|
+
if (idx > 0) {
|
|
5131
|
+
fields[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
5132
|
+
}
|
|
5133
|
+
}
|
|
5134
|
+
return {
|
|
5135
|
+
name: fields.name,
|
|
5136
|
+
description: fields.description,
|
|
5137
|
+
html: raw.slice(match[0].length)
|
|
5138
|
+
};
|
|
5139
|
+
}
|
|
5140
|
+
async function execute4(input, onLog, context) {
|
|
5141
|
+
const url = String(input.url ?? "").trim();
|
|
5142
|
+
if (!url) {
|
|
5143
|
+
return "Error: url is required.";
|
|
5144
|
+
}
|
|
5145
|
+
const customPrompt = typeof input.prompt === "string" && input.prompt.trim() ? input.prompt : void 0;
|
|
5146
|
+
if (!isFetchableUrl(url)) {
|
|
5147
|
+
if (HTML_EXTS.has(extname2(url).toLowerCase())) {
|
|
5148
|
+
return analyzeLocalHtml(url, customPrompt, onLog, context);
|
|
5149
|
+
}
|
|
5150
|
+
return analyze(
|
|
5151
|
+
url,
|
|
5152
|
+
customPrompt ?? DESIGN_REFERENCE_PROMPT,
|
|
5153
|
+
onLog,
|
|
5154
|
+
context
|
|
5004
5155
|
);
|
|
5005
|
-
|
|
5006
|
-
|
|
5156
|
+
}
|
|
5157
|
+
if (IMAGE_EXT_RE.test(url)) {
|
|
5158
|
+
return analyze(
|
|
5159
|
+
url,
|
|
5160
|
+
customPrompt ?? DESIGN_REFERENCE_PROMPT,
|
|
5161
|
+
onLog,
|
|
5162
|
+
context
|
|
5163
|
+
);
|
|
5164
|
+
}
|
|
5165
|
+
const ss = await runMindstudioCliResult(
|
|
5166
|
+
[
|
|
5167
|
+
"screenshot-url",
|
|
5168
|
+
"--url",
|
|
5169
|
+
url,
|
|
5170
|
+
"--mode",
|
|
5171
|
+
"viewport",
|
|
5172
|
+
"--width",
|
|
5173
|
+
"1440",
|
|
5174
|
+
"--delay",
|
|
5175
|
+
"2000"
|
|
5176
|
+
],
|
|
5177
|
+
{
|
|
5178
|
+
outputKey: "screenshotUrl",
|
|
5179
|
+
timeout: 12e4,
|
|
5180
|
+
onLog,
|
|
5181
|
+
caller: "designExpert"
|
|
5007
5182
|
}
|
|
5008
|
-
|
|
5183
|
+
);
|
|
5184
|
+
if (!ss.ok) {
|
|
5185
|
+
return `Could not screenshot ${url}: ${ss.value}`;
|
|
5009
5186
|
}
|
|
5187
|
+
return analyze(
|
|
5188
|
+
ss.value,
|
|
5189
|
+
customPrompt ?? DESIGN_REFERENCE_PROMPT,
|
|
5190
|
+
onLog,
|
|
5191
|
+
context
|
|
5192
|
+
);
|
|
5193
|
+
}
|
|
5194
|
+
async function analyze(image, prompt, onLog, context, extra) {
|
|
5010
5195
|
const analyzed = await analyzeImage({
|
|
5011
|
-
prompt
|
|
5196
|
+
prompt,
|
|
5012
5197
|
image,
|
|
5013
5198
|
apiConfig: context?.apiConfig,
|
|
5014
5199
|
onLog,
|
|
5015
5200
|
model: resolveModel("imageAnalysis", context?.models, context?.model)
|
|
5016
5201
|
});
|
|
5017
|
-
return JSON.stringify({
|
|
5202
|
+
return JSON.stringify({
|
|
5203
|
+
url: analyzed.url,
|
|
5204
|
+
analysis: analyzed.analysis,
|
|
5205
|
+
...extra
|
|
5206
|
+
});
|
|
5207
|
+
}
|
|
5208
|
+
async function analyzeLocalHtml(path13, customPrompt, onLog, context) {
|
|
5209
|
+
let raw;
|
|
5210
|
+
try {
|
|
5211
|
+
raw = await readFile2(join2(PROJECT_ROOT, path13), "utf8");
|
|
5212
|
+
} catch {
|
|
5213
|
+
return `No file at "${path13}". Paths resolve from the project root; list the directory to check the name.`;
|
|
5214
|
+
}
|
|
5215
|
+
const { name, description, html } = splitFrontmatter(raw);
|
|
5216
|
+
if (!html.trim()) {
|
|
5217
|
+
return `"${path13}" has no HTML to render.`;
|
|
5218
|
+
}
|
|
5219
|
+
onLog?.(`Rendering ${path13} in the sandbox browser...`);
|
|
5220
|
+
const release = await acquireBrowserLock();
|
|
5221
|
+
let rendered;
|
|
5222
|
+
try {
|
|
5223
|
+
rendered = await renderHtmlViaSidecar({
|
|
5224
|
+
html,
|
|
5225
|
+
width: HTML_RENDER_WIDTH,
|
|
5226
|
+
height: HTML_RENDER_START_HEIGHT,
|
|
5227
|
+
autoHeight: true
|
|
5228
|
+
});
|
|
5229
|
+
} catch (err) {
|
|
5230
|
+
return `Could not render ${path13}: ${err?.message ?? err}`;
|
|
5231
|
+
} finally {
|
|
5232
|
+
release();
|
|
5233
|
+
}
|
|
5234
|
+
const basePrompt = customPrompt ?? RENDER_ANALYZE_PROMPT;
|
|
5235
|
+
const subject = [name, description].filter(Boolean).join(" \u2014 ");
|
|
5236
|
+
const prompt = subject ? `The document rendered below is described by its author as: ${subject}
|
|
5237
|
+
|
|
5238
|
+
${basePrompt}` : basePrompt;
|
|
5239
|
+
return analyze(rendered.url, prompt, onLog, context, {
|
|
5240
|
+
renderedWidth: rendered.width,
|
|
5241
|
+
renderedHeight: rendered.height
|
|
5242
|
+
});
|
|
5018
5243
|
}
|
|
5019
5244
|
|
|
5020
5245
|
// src/subagents/designExpert/tools/analyzeImage.ts
|
|
5021
5246
|
var analyzeImage_exports = {};
|
|
5022
5247
|
__export(analyzeImage_exports, {
|
|
5023
|
-
definition: () =>
|
|
5024
|
-
execute: () =>
|
|
5248
|
+
definition: () => definition5,
|
|
5249
|
+
execute: () => execute5
|
|
5025
5250
|
});
|
|
5026
|
-
var
|
|
5251
|
+
var definition5 = {
|
|
5027
5252
|
name: "analyzeImage",
|
|
5028
5253
|
description: "Analyze an image using a vision model. Provides static image analysis only, will not capture animations or video. Returns an objective description of what is visible \u2014 shapes, colors, layout, text, artifacts. Use for factual inventory of image contents, not for subjective design judgment - the vision model providing the analysis has no sense of design. You are the design expert - use the analysis tool for factual inventory, then apply your own expertise for quality and suitability assessments. Optionally provide specific questions about what you're looking for. Use a bulleted list to ask many questions at once. If you are analyzing a screenshot of the app preview, you can reuse the same screenshot URL multiple times to ask multiple questions.",
|
|
5029
5254
|
inputSchema: {
|
|
@@ -5044,7 +5269,7 @@ var definition4 = {
|
|
|
5044
5269
|
required: ["imageUrl"]
|
|
5045
5270
|
}
|
|
5046
5271
|
};
|
|
5047
|
-
async function
|
|
5272
|
+
async function execute5(input, onLog, context) {
|
|
5048
5273
|
const prompt = buildScreenshotAnalysisPrompt({
|
|
5049
5274
|
prompt: input.prompt
|
|
5050
5275
|
});
|
|
@@ -5061,8 +5286,8 @@ async function execute4(input, onLog, context) {
|
|
|
5061
5286
|
// src/subagents/designExpert/tools/images/generateImages.ts
|
|
5062
5287
|
var generateImages_exports = {};
|
|
5063
5288
|
__export(generateImages_exports, {
|
|
5064
|
-
definition: () =>
|
|
5065
|
-
execute: () =>
|
|
5289
|
+
definition: () => definition6,
|
|
5290
|
+
execute: () => execute6
|
|
5066
5291
|
});
|
|
5067
5292
|
|
|
5068
5293
|
// src/subagents/designExpert/tools/images/enhancePrompt.ts
|
|
@@ -5289,7 +5514,7 @@ async function generateImageAssets(opts) {
|
|
|
5289
5514
|
}
|
|
5290
5515
|
|
|
5291
5516
|
// src/subagents/designExpert/tools/images/generateImages.ts
|
|
5292
|
-
var
|
|
5517
|
+
var definition6 = {
|
|
5293
5518
|
name: "generateImages",
|
|
5294
5519
|
description: "Generate images. Returns CDN URLs with a quality analysis for each image. Produces high-quality results for everything from 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.",
|
|
5295
5520
|
inputSchema: {
|
|
@@ -5322,7 +5547,7 @@ var definition5 = {
|
|
|
5322
5547
|
required: ["prompts"]
|
|
5323
5548
|
}
|
|
5324
5549
|
};
|
|
5325
|
-
async function
|
|
5550
|
+
async function execute6(input, onLog, context) {
|
|
5326
5551
|
return generateImageAssets({
|
|
5327
5552
|
prompts: input.prompts,
|
|
5328
5553
|
width: input.width,
|
|
@@ -5353,10 +5578,10 @@ async function execute5(input, onLog, context) {
|
|
|
5353
5578
|
// src/subagents/designExpert/tools/images/editImages.ts
|
|
5354
5579
|
var editImages_exports = {};
|
|
5355
5580
|
__export(editImages_exports, {
|
|
5356
|
-
definition: () =>
|
|
5357
|
-
execute: () =>
|
|
5581
|
+
definition: () => definition7,
|
|
5582
|
+
execute: () => execute7
|
|
5358
5583
|
});
|
|
5359
|
-
var
|
|
5584
|
+
var definition7 = {
|
|
5360
5585
|
name: "editImages",
|
|
5361
5586
|
description: "Edit or transform existing images. Provide one or more source image URLs as reference and a prompt describing the desired edit. Use for compositing, style transfer, subject transformation, blending multiple references, or incorporating one or more references into something new. Returns CDN URLs with analysis.",
|
|
5362
5587
|
inputSchema: {
|
|
@@ -5392,7 +5617,7 @@ var definition6 = {
|
|
|
5392
5617
|
required: ["prompts", "sourceImages"]
|
|
5393
5618
|
}
|
|
5394
5619
|
};
|
|
5395
|
-
async function
|
|
5620
|
+
async function execute7(input, onLog, context) {
|
|
5396
5621
|
return generateImageAssets({
|
|
5397
5622
|
prompts: input.prompts,
|
|
5398
5623
|
sourceImages: input.sourceImages,
|
|
@@ -5420,138 +5645,6 @@ async function execute6(input, onLog, context) {
|
|
|
5420
5645
|
});
|
|
5421
5646
|
}
|
|
5422
5647
|
|
|
5423
|
-
// src/subagents/designExpert/tools/images/renderImage.ts
|
|
5424
|
-
var renderImage_exports = {};
|
|
5425
|
-
__export(renderImage_exports, {
|
|
5426
|
-
definition: () => definition7,
|
|
5427
|
-
execute: () => execute7
|
|
5428
|
-
});
|
|
5429
|
-
import { mkdir, unlink, writeFile } from "fs/promises";
|
|
5430
|
-
import { tmpdir } from "os";
|
|
5431
|
-
import { dirname, join, resolve as resolve2, sep } from "path";
|
|
5432
|
-
import { randomUUID } from "crypto";
|
|
5433
|
-
var MIN_DIMENSION = 16;
|
|
5434
|
-
var MAX_DIMENSION = 4096;
|
|
5435
|
-
var RENDER_ANALYZE_PROMPT = 'You are reviewing a browser-rendered graphic (composed from HTML/CSS by a designer) for fidelity. Report: whether the composition fills the full canvas or leaves unintended gaps at any edge, any clipped or overflowing text, whether custom webfonts appear to have loaded (distinctive letterforms vs generic fallback serif/sans), any misalignment or uneven spacing, any unintended scrollbars or default-styling artifacts, and \u2014 if the background is transparent \u2014 any fringing or stray opaque pixels at the edges. Then briefly describe the overall composition and how polished it looks. Be concise and practical. Respond only with your analysis as Markdown (starting with the title "Render Review") and absolutely no other text. Do not use emojis - use unicode if you need symbols.';
|
|
5436
|
-
var definition7 = {
|
|
5437
|
-
name: "renderImage",
|
|
5438
|
-
description: "Render a self-contained HTML document in a real browser and capture it as a hosted PNG at exact pixel dimensions, with a fidelity review included. Deterministic \u2014 exact hex colors, real loaded webfonts, precise geometry \u2014 unlike generateImages, which is an image model. Use for token-exact graphics: Open Graph share cards, wordmarks, flat/geometric icon tiles, badges, any composition where letterforms and spacing carry the design. Compose with HTML/CSS (link webfonts from CDNs \u2014 the renderer waits for them to load); inline existing SVG markup when needed, but never hand-write new SVG path data.",
|
|
5439
|
-
inputSchema: {
|
|
5440
|
-
type: "object",
|
|
5441
|
-
properties: {
|
|
5442
|
-
html: {
|
|
5443
|
-
type: "string",
|
|
5444
|
-
description: "A complete, self-contained HTML document sized to fill the viewport (style html/body to the full dimensions with margin 0). External webfonts and images from CDNs are fine \u2014 loading is awaited before capture."
|
|
5445
|
-
},
|
|
5446
|
-
width: {
|
|
5447
|
-
type: "number",
|
|
5448
|
-
description: "Viewport width in CSS pixels (e.g. 1200 for an OG card, 512 for an icon tile). Range: 16-4096."
|
|
5449
|
-
},
|
|
5450
|
-
height: {
|
|
5451
|
-
type: "number",
|
|
5452
|
-
description: "Viewport height in CSS pixels. Range: 16-4096."
|
|
5453
|
-
},
|
|
5454
|
-
scale: {
|
|
5455
|
-
type: "number",
|
|
5456
|
-
description: "Device scale factor, 1-3. Output pixels = css \xD7 scale. Use 2 for crisp icon masters (e.g. a 512\xD7512 document captured at 1024\xD71024)."
|
|
5457
|
-
},
|
|
5458
|
-
transparentBackground: {
|
|
5459
|
-
type: "boolean",
|
|
5460
|
-
description: "Capture with true alpha: leave the document background transparent (no background on html/body) and the PNG keeps it. No background-removal model involved."
|
|
5461
|
-
},
|
|
5462
|
-
savePath: {
|
|
5463
|
-
type: "string",
|
|
5464
|
-
description: "Optional project-relative path to also save the PNG into the app (e.g. 'dist/interfaces/web/public/og-image.png' so the deployed site self-hosts it)."
|
|
5465
|
-
}
|
|
5466
|
-
},
|
|
5467
|
-
required: ["html", "width", "height"]
|
|
5468
|
-
}
|
|
5469
|
-
};
|
|
5470
|
-
async function execute7(input, onLog, context) {
|
|
5471
|
-
const html = typeof input.html === "string" ? input.html : "";
|
|
5472
|
-
const width = Math.round(Number(input.width));
|
|
5473
|
-
const height = Math.round(Number(input.height));
|
|
5474
|
-
if (!html || !Number.isFinite(width) || !Number.isFinite(height) || width < MIN_DIMENSION || width > MAX_DIMENSION || height < MIN_DIMENSION || height > MAX_DIMENSION) {
|
|
5475
|
-
return `Error: renderImage requires an html string plus width/height between ${MIN_DIMENSION} and ${MAX_DIMENSION}.`;
|
|
5476
|
-
}
|
|
5477
|
-
const release = await acquireBrowserLock();
|
|
5478
|
-
let rendered;
|
|
5479
|
-
try {
|
|
5480
|
-
onLog?.("Rendering document in the sandbox browser...");
|
|
5481
|
-
rendered = await renderHtmlViaSidecar({
|
|
5482
|
-
html,
|
|
5483
|
-
width,
|
|
5484
|
-
height,
|
|
5485
|
-
transparent: input.transparentBackground === true,
|
|
5486
|
-
scale: typeof input.scale === "number" ? input.scale : void 0
|
|
5487
|
-
});
|
|
5488
|
-
} catch (err) {
|
|
5489
|
-
return `Error: render failed: ${err?.message ?? err}`;
|
|
5490
|
-
} finally {
|
|
5491
|
-
release();
|
|
5492
|
-
}
|
|
5493
|
-
let bytes;
|
|
5494
|
-
try {
|
|
5495
|
-
const res = await fetch(rendered.url);
|
|
5496
|
-
if (!res.ok) {
|
|
5497
|
-
throw new Error(`fetch returned ${res.status}`);
|
|
5498
|
-
}
|
|
5499
|
-
bytes = Buffer.from(await res.arrayBuffer());
|
|
5500
|
-
} catch (err) {
|
|
5501
|
-
return `Error: rendered but could not download the capture (${err?.message ?? err}). Temporary URL: ${rendered.url}`;
|
|
5502
|
-
}
|
|
5503
|
-
let savedPath;
|
|
5504
|
-
if (typeof input.savePath === "string" && input.savePath) {
|
|
5505
|
-
const absolute = resolve2(PROJECT_ROOT, input.savePath);
|
|
5506
|
-
if (!absolute.startsWith(PROJECT_ROOT + sep)) {
|
|
5507
|
-
return "Error: savePath must resolve inside the project.";
|
|
5508
|
-
}
|
|
5509
|
-
await mkdir(dirname(absolute), { recursive: true });
|
|
5510
|
-
await writeFile(absolute, bytes);
|
|
5511
|
-
savedPath = input.savePath;
|
|
5512
|
-
}
|
|
5513
|
-
onLog?.("Hosting the capture...");
|
|
5514
|
-
const tmpPath = join(tmpdir(), `render-${randomUUID()}.png`);
|
|
5515
|
-
let url = rendered.url;
|
|
5516
|
-
let temporary = true;
|
|
5517
|
-
try {
|
|
5518
|
-
await writeFile(tmpPath, bytes);
|
|
5519
|
-
const upload = await runMindstudioCliResult(["upload", tmpPath], {
|
|
5520
|
-
timeout: 6e4,
|
|
5521
|
-
onLog,
|
|
5522
|
-
caller: "designExpert"
|
|
5523
|
-
});
|
|
5524
|
-
const match = upload.ok ? upload.value.match(/https:\/\/\S+/g) : null;
|
|
5525
|
-
if (match?.length) {
|
|
5526
|
-
url = match[match.length - 1];
|
|
5527
|
-
temporary = false;
|
|
5528
|
-
}
|
|
5529
|
-
} finally {
|
|
5530
|
-
await unlink(tmpPath).catch(() => {
|
|
5531
|
-
});
|
|
5532
|
-
}
|
|
5533
|
-
const analysis = await analyzeImage({
|
|
5534
|
-
prompt: RENDER_ANALYZE_PROMPT,
|
|
5535
|
-
image: url,
|
|
5536
|
-
onLog,
|
|
5537
|
-
model: resolveModel("imageAnalysis", context?.models, context?.model)
|
|
5538
|
-
}).then((r) => r.analysis).catch((err) => `Could not review this image: ${err.message}`);
|
|
5539
|
-
return JSON.stringify({
|
|
5540
|
-
images: [
|
|
5541
|
-
{
|
|
5542
|
-
url,
|
|
5543
|
-
...temporary ? {
|
|
5544
|
-
note: "Durable hosting failed \u2014 this URL is dev-session scratch storage and may expire. Do not use it for app metadata; retry if a durable URL is needed."
|
|
5545
|
-
} : {},
|
|
5546
|
-
...savedPath ? { savedPath } : {},
|
|
5547
|
-
analysis,
|
|
5548
|
-
width: rendered.width,
|
|
5549
|
-
height: rendered.height
|
|
5550
|
-
}
|
|
5551
|
-
]
|
|
5552
|
-
});
|
|
5553
|
-
}
|
|
5554
|
-
|
|
5555
5648
|
// src/subagents/designExpert/tools/polishCopy.ts
|
|
5556
5649
|
var polishCopy_exports = {};
|
|
5557
5650
|
__export(polishCopy_exports, {
|
|
@@ -5697,7 +5790,7 @@ __export(createWireframe_exports, {
|
|
|
5697
5790
|
execute: () => execute10
|
|
5698
5791
|
});
|
|
5699
5792
|
import { mkdir as mkdir2, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
5700
|
-
import { join as
|
|
5793
|
+
import { join as join3 } from "path";
|
|
5701
5794
|
var log9 = createLogger("createWireframe");
|
|
5702
5795
|
var WIREFRAMES_DIR = "src/.wireframes";
|
|
5703
5796
|
var UPLOAD_TIMEOUT_MS2 = 3e4;
|
|
@@ -5816,12 +5909,12 @@ async function execute10(input, onLog, context) {
|
|
|
5816
5909
|
html.trim(),
|
|
5817
5910
|
""
|
|
5818
5911
|
].join("\n");
|
|
5819
|
-
await mkdir2(
|
|
5820
|
-
const existed = await stat2(
|
|
5912
|
+
await mkdir2(join3(PROJECT_ROOT, WIREFRAMES_DIR), { recursive: true });
|
|
5913
|
+
const existed = await stat2(join3(PROJECT_ROOT, relPath)).then(
|
|
5821
5914
|
() => true,
|
|
5822
5915
|
() => false
|
|
5823
5916
|
);
|
|
5824
|
-
await writeFile2(
|
|
5917
|
+
await writeFile2(join3(PROJECT_ROOT, relPath), content, "utf8");
|
|
5825
5918
|
onLog?.(`Wrote ${relPath}, mirroring for preview...`);
|
|
5826
5919
|
const mirror = await uploadMirror(context, slug, content);
|
|
5827
5920
|
if (!mirror.ok) {
|
|
@@ -6092,7 +6185,7 @@ ${RENDER_TASK_BLOCK}`;
|
|
|
6092
6185
|
|
|
6093
6186
|
// src/subagents/designExpert/validateWireframeRefs.ts
|
|
6094
6187
|
import { stat as stat3 } from "fs/promises";
|
|
6095
|
-
import { join as
|
|
6188
|
+
import { join as join4 } from "path";
|
|
6096
6189
|
var WIREFRAME_REF_RE = /src\/\.wireframes\/[a-z0-9][a-z0-9-]*\.html/g;
|
|
6097
6190
|
async function validateWireframeRefs(text) {
|
|
6098
6191
|
const refs = [...new Set(text.match(WIREFRAME_REF_RE) ?? [])];
|
|
@@ -6101,7 +6194,7 @@ async function validateWireframeRefs(text) {
|
|
|
6101
6194
|
}
|
|
6102
6195
|
const missing = [];
|
|
6103
6196
|
for (const ref of refs) {
|
|
6104
|
-
const exists = await stat3(
|
|
6197
|
+
const exists = await stat3(join4(PROJECT_ROOT, ref)).then(
|
|
6105
6198
|
(s) => s.isFile(),
|
|
6106
6199
|
() => false
|
|
6107
6200
|
);
|
|
@@ -9324,7 +9417,7 @@ async function runTurn(params) {
|
|
|
9324
9417
|
// src/headless/attachments.ts
|
|
9325
9418
|
import { mkdirSync, existsSync } from "fs";
|
|
9326
9419
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
9327
|
-
import { basename as basename2, join as
|
|
9420
|
+
import { basename as basename2, join as join5, extname as extname3 } from "path";
|
|
9328
9421
|
var log16 = createLogger("headless:attachments");
|
|
9329
9422
|
var UPLOADS_DIR = "src/.user-uploads";
|
|
9330
9423
|
function filenameFromUrl(url) {
|
|
@@ -9337,11 +9430,11 @@ function filenameFromUrl(url) {
|
|
|
9337
9430
|
}
|
|
9338
9431
|
}
|
|
9339
9432
|
function resolveUniqueFilename(name, claimed) {
|
|
9340
|
-
const isFree = (candidate) => !claimed.has(candidate) && !existsSync(
|
|
9433
|
+
const isFree = (candidate) => !claimed.has(candidate) && !existsSync(join5(UPLOADS_DIR, candidate));
|
|
9341
9434
|
if (isFree(name)) {
|
|
9342
9435
|
return name;
|
|
9343
9436
|
}
|
|
9344
|
-
const ext =
|
|
9437
|
+
const ext = extname3(name);
|
|
9345
9438
|
const base = name.slice(0, name.length - ext.length);
|
|
9346
9439
|
let counter = 1;
|
|
9347
9440
|
while (!isFree(`${base}-${counter}${ext}`)) {
|
|
@@ -9352,7 +9445,7 @@ function resolveUniqueFilename(name, claimed) {
|
|
|
9352
9445
|
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
|
|
9353
9446
|
function isImageAttachment(att) {
|
|
9354
9447
|
const name = att.filename || filenameFromUrl(att.url);
|
|
9355
|
-
return IMAGE_EXTENSIONS.has(
|
|
9448
|
+
return IMAGE_EXTENSIONS.has(extname3(name).toLowerCase());
|
|
9356
9449
|
}
|
|
9357
9450
|
async function persistAttachments(attachments) {
|
|
9358
9451
|
const nonVoice = attachments.filter((a) => !a.isVoice);
|
|
@@ -9372,7 +9465,7 @@ async function persistAttachments(attachments) {
|
|
|
9372
9465
|
const results = await Promise.allSettled(
|
|
9373
9466
|
nonVoice.map(async (att, i) => {
|
|
9374
9467
|
const name = names[i];
|
|
9375
|
-
const localPath =
|
|
9468
|
+
const localPath = join5(UPLOADS_DIR, name);
|
|
9376
9469
|
const res = await fetch(att.url, {
|
|
9377
9470
|
signal: AbortSignal.timeout(3e4)
|
|
9378
9471
|
});
|