@mindstudio-ai/remy 0.1.275 → 0.1.277
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 +344 -79
- package/dist/index.js +382 -83
- package/dist/prompt/skills/agentInterfaces.md +13 -39
- package/dist/prompt/skills/auth.md +1 -7
- package/dist/prompt/skills/dataSources.md +20 -66
- package/dist/prompt/skills/files.md +17 -47
- package/dist/prompt/skills/inboundEmail.md +11 -32
- package/dist/prompt/skills/mcpInterfaces.md +21 -63
- package/dist/prompt/skills/restApi.md +7 -21
- package/dist/prompt/skills/scenarios.md +3 -7
- package/dist/prompt/skills/scheduledJobs.md +3 -8
- package/dist/prompt/skills/voiceInterfaces.md +117 -366
- package/dist/prompt/skills/webhooks.md +10 -31
- package/dist/prompt/static/authoring.md +1 -2
- package/dist/prompt/static/team.md +1 -1
- package/dist/subagents/browserAutomation/prompt.md +6 -22
- package/dist/subagents/codeSanityCheck/prompt.md +1 -2
- package/dist/subagents/designExpert/prompt.md +0 -3
- package/dist/subagents/designExpert/prompts/instructions.md +1 -0
- package/dist/subagents/designExpert/prompts/ui-patterns.md +7 -21
- package/dist/subagents/designExpert/skills/authExperience.md +60 -0
- package/dist/subagents/designExpert/skills/chatExperience.md +59 -0
- package/dist/subagents/designExpert/skills/dataViz.md +82 -0
- package/dist/subagents/designExpert/{prompts → skills}/images.md +17 -1
- package/dist/subagents/designExpert/skills/voiceExperience.md +103 -0
- package/dist/subagents/designExpert/tools/images/enhance-image-prompt.md +7 -3
- package/dist/subagents/productVision/prompt.md +2 -0
- package/package.json +1 -1
package/dist/headless.js
CHANGED
|
@@ -289,7 +289,7 @@ function isRetryableError(error) {
|
|
|
289
289
|
/Unable to download/i.test(error);
|
|
290
290
|
}
|
|
291
291
|
function sleep(ms) {
|
|
292
|
-
return new Promise((
|
|
292
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
293
293
|
}
|
|
294
294
|
async function* streamChatWithRetry(params, options) {
|
|
295
295
|
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
@@ -433,7 +433,7 @@ var MODEL_SURFACES = {
|
|
|
433
433
|
userPickable: true
|
|
434
434
|
},
|
|
435
435
|
imageGeneration: {
|
|
436
|
-
default: "
|
|
436
|
+
default: "gpt-image-2",
|
|
437
437
|
label: "Image Generation",
|
|
438
438
|
description: "Creates images for your product \u2014 icons, illustrations, photos, and any other visual assets.",
|
|
439
439
|
modelType: "image_generation",
|
|
@@ -681,10 +681,9 @@ The user has approved your implementation plan in .remy-plan.md. You may referen
|
|
|
681
681
|
}
|
|
682
682
|
}
|
|
683
683
|
|
|
684
|
-
// src/
|
|
684
|
+
// src/skillCatalog.ts
|
|
685
685
|
import fs5 from "fs";
|
|
686
686
|
import path3 from "path";
|
|
687
|
-
var SKILLS_DIR = assetPath("prompt", "skills");
|
|
688
687
|
function parseFrontmatter(content) {
|
|
689
688
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
690
689
|
if (!match) {
|
|
@@ -692,23 +691,23 @@ function parseFrontmatter(content) {
|
|
|
692
691
|
}
|
|
693
692
|
const fields = {};
|
|
694
693
|
for (const line of match[1].split("\n")) {
|
|
695
|
-
const
|
|
696
|
-
if (
|
|
697
|
-
fields[line.slice(0,
|
|
694
|
+
const sep2 = line.indexOf(":");
|
|
695
|
+
if (sep2 > 0) {
|
|
696
|
+
fields[line.slice(0, sep2).trim()] = line.slice(sep2 + 1).trim();
|
|
698
697
|
}
|
|
699
698
|
}
|
|
700
699
|
return fields;
|
|
701
700
|
}
|
|
702
|
-
function
|
|
701
|
+
function buildSkillCatalog(opts) {
|
|
703
702
|
let files;
|
|
704
703
|
try {
|
|
705
|
-
files = fs5.readdirSync(
|
|
704
|
+
files = fs5.readdirSync(opts.dir).filter((f) => f.endsWith(".md"));
|
|
706
705
|
} catch {
|
|
707
|
-
|
|
706
|
+
files = [];
|
|
708
707
|
}
|
|
709
708
|
const skills = [];
|
|
710
709
|
for (const file of files.sort()) {
|
|
711
|
-
const full = path3.join(
|
|
710
|
+
const full = path3.join(opts.dir, file);
|
|
712
711
|
const id = file.replace(/\.md$/, "");
|
|
713
712
|
const fields = parseFrontmatter(fs5.readFileSync(full, "utf-8"));
|
|
714
713
|
if (!fields.name || !fields.what || !fields.when) {
|
|
@@ -722,38 +721,58 @@ function loadCatalog() {
|
|
|
722
721
|
path: full
|
|
723
722
|
});
|
|
724
723
|
}
|
|
725
|
-
return
|
|
724
|
+
return {
|
|
725
|
+
skills,
|
|
726
|
+
ids: skills.map((s) => s.id),
|
|
727
|
+
get(id) {
|
|
728
|
+
return skills.find((s) => s.id === id);
|
|
729
|
+
},
|
|
730
|
+
readBody(skill) {
|
|
731
|
+
return fs5.readFileSync(skill.path, "utf-8").replace(/^---[\s\S]*?---\s*/, "").trim();
|
|
732
|
+
},
|
|
733
|
+
renderCatalogBlock() {
|
|
734
|
+
if (skills.length === 0) {
|
|
735
|
+
return "";
|
|
736
|
+
}
|
|
737
|
+
const entries = skills.map(
|
|
738
|
+
(s) => [
|
|
739
|
+
`### ${s.name} (\`${s.id}\`)`,
|
|
740
|
+
s.what,
|
|
741
|
+
"",
|
|
742
|
+
`When to load: ${s.when}`,
|
|
743
|
+
`Reference: ${s.path}`
|
|
744
|
+
].join("\n")
|
|
745
|
+
);
|
|
746
|
+
return `<${opts.tag}>
|
|
747
|
+
${opts.intro}
|
|
748
|
+
|
|
749
|
+
${entries.join("\n\n")}
|
|
750
|
+
</${opts.tag}>`;
|
|
751
|
+
}
|
|
752
|
+
};
|
|
726
753
|
}
|
|
727
|
-
|
|
728
|
-
|
|
754
|
+
|
|
755
|
+
// src/prompt/skills/_catalog.ts
|
|
756
|
+
var INTRO = `Platform capabilities most apps don't use, so their references are kept out of this prompt rather than competing for your attention on every task \u2014 not because they're marginal.
|
|
757
|
+
|
|
758
|
+
Read what follows as part of what the platform can do, not as a lookup table. Recognising that one of these fits a feature is your job, and proposing one is fair game \u2014 several of them are the difference between an app that works and an app worth showing off. When a trigger fires, load the reference with loadSkill before writing the code rather than after. Loading is cheap and expected; guessing at one of these APIs is not.
|
|
759
|
+
|
|
760
|
+
A loaded reference drops out of the conversation once it ages out. Re-read it at the path listed with readFile whenever you need it again.`;
|
|
761
|
+
var catalog = buildSkillCatalog({
|
|
762
|
+
dir: assetPath("prompt", "skills"),
|
|
763
|
+
tag: "available_skills",
|
|
764
|
+
intro: INTRO
|
|
765
|
+
});
|
|
766
|
+
var SKILLS = catalog.skills;
|
|
767
|
+
var SKILL_IDS = catalog.ids;
|
|
729
768
|
function getSkill(id) {
|
|
730
|
-
return
|
|
769
|
+
return catalog.get(id);
|
|
731
770
|
}
|
|
732
771
|
function readSkillBody(skill) {
|
|
733
|
-
return
|
|
772
|
+
return catalog.readBody(skill);
|
|
734
773
|
}
|
|
735
774
|
function loadSkillsCatalog() {
|
|
736
|
-
|
|
737
|
-
return "";
|
|
738
|
-
}
|
|
739
|
-
const entries = SKILLS.map(
|
|
740
|
-
(s) => [
|
|
741
|
-
`### ${s.name} (\`${s.id}\`)`,
|
|
742
|
-
s.what,
|
|
743
|
-
"",
|
|
744
|
-
`When to load: ${s.when}`,
|
|
745
|
-
`Reference: ${s.path}`
|
|
746
|
-
].join("\n")
|
|
747
|
-
);
|
|
748
|
-
return `<available_skills>
|
|
749
|
-
Platform capabilities most apps don't use, so their references are kept out of this prompt rather than competing for your attention on every task \u2014 not because they're marginal.
|
|
750
|
-
|
|
751
|
-
Read what follows as part of what the platform can do, not as a lookup table. Recognising that one of these fits a feature is your job, and proposing one is fair game \u2014 several of them are the difference between an app that works and an app worth showing off. When a trigger fires, load the reference with loadSkill before writing the code rather than after. Loading is cheap and expected; guessing at one of these APIs is not.
|
|
752
|
-
|
|
753
|
-
A loaded reference drops out of the conversation once it ages out. Re-read it at the path listed with readFile whenever you need it again.
|
|
754
|
-
|
|
755
|
-
${entries.join("\n\n")}
|
|
756
|
-
</available_skills>`;
|
|
775
|
+
return catalog.renderCatalogBlock();
|
|
757
776
|
}
|
|
758
777
|
|
|
759
778
|
// src/prompt/index.ts
|
|
@@ -1663,7 +1682,7 @@ function formatCliResult(r) {
|
|
|
1663
1682
|
return logBlock + body + truncNote;
|
|
1664
1683
|
}
|
|
1665
1684
|
function runCli(command, args, options) {
|
|
1666
|
-
return new Promise((
|
|
1685
|
+
return new Promise((resolve4) => {
|
|
1667
1686
|
const timeout = options?.timeout ?? 6e4;
|
|
1668
1687
|
const maxBuffer = options?.maxBuffer ?? 1024 * 1024;
|
|
1669
1688
|
let finalArgs = args;
|
|
@@ -1692,7 +1711,7 @@ function runCli(command, args, options) {
|
|
|
1692
1711
|
if (killTimer) {
|
|
1693
1712
|
clearTimeout(killTimer);
|
|
1694
1713
|
}
|
|
1695
|
-
|
|
1714
|
+
resolve4(result);
|
|
1696
1715
|
};
|
|
1697
1716
|
const child = spawn(command, finalArgs, {
|
|
1698
1717
|
stdio: [options?.stdin ? "pipe" : "ignore", "pipe", "pipe"]
|
|
@@ -2389,7 +2408,7 @@ var bashTool = {
|
|
|
2389
2408
|
async execute(input, context) {
|
|
2390
2409
|
const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES2;
|
|
2391
2410
|
const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
|
|
2392
|
-
return new Promise((
|
|
2411
|
+
return new Promise((resolve4) => {
|
|
2393
2412
|
const child = spawn2("sh", ["-c", input.command], {
|
|
2394
2413
|
// Pinned rather than inherited. `undefined` here means "wherever the
|
|
2395
2414
|
// process happens to be", which is the project root only by luck.
|
|
@@ -2414,9 +2433,9 @@ var bashTool = {
|
|
|
2414
2433
|
clearTimeout(timer);
|
|
2415
2434
|
if (!output) {
|
|
2416
2435
|
if (code && code !== 0) {
|
|
2417
|
-
|
|
2436
|
+
resolve4(`Error: process exited with code ${code}`);
|
|
2418
2437
|
} else {
|
|
2419
|
-
|
|
2438
|
+
resolve4("(no output)");
|
|
2420
2439
|
}
|
|
2421
2440
|
return;
|
|
2422
2441
|
}
|
|
@@ -2442,18 +2461,18 @@ var bashTool = {
|
|
|
2442
2461
|
`${(MAX_OUTPUT_BYTES / 1024).toFixed(0)}KB of ${(totalBytes / 1024).toFixed(0)}KB`
|
|
2443
2462
|
);
|
|
2444
2463
|
}
|
|
2445
|
-
|
|
2464
|
+
resolve4(
|
|
2446
2465
|
truncated + `
|
|
2447
2466
|
|
|
2448
2467
|
(truncated at ${reasons.join(" / ")} \u2014 narrow the command (grep, head/tail, smaller paths) instead of increasing limits)`
|
|
2449
2468
|
);
|
|
2450
2469
|
} else {
|
|
2451
|
-
|
|
2470
|
+
resolve4(output);
|
|
2452
2471
|
}
|
|
2453
2472
|
});
|
|
2454
2473
|
child.on("error", (err) => {
|
|
2455
2474
|
clearTimeout(timer);
|
|
2456
|
-
|
|
2475
|
+
resolve4(`Error: ${err.message}`);
|
|
2457
2476
|
});
|
|
2458
2477
|
});
|
|
2459
2478
|
}
|
|
@@ -2570,17 +2589,17 @@ var grepTool = {
|
|
|
2570
2589
|
}
|
|
2571
2590
|
const rgCmd = `rg ${rgFlags}${globFlag} '${escaped}' ${searchPath}`;
|
|
2572
2591
|
const grepCmd = `grep ${grepFlags} '${escaped}' ${searchPath} --include='*.ts' --include='*.tsx' --include='*.js' --include='*.json' --include='*.md'`;
|
|
2573
|
-
return new Promise((
|
|
2592
|
+
return new Promise((resolve4) => {
|
|
2574
2593
|
exec(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
|
|
2575
2594
|
if (stdout?.trim()) {
|
|
2576
|
-
|
|
2595
|
+
resolve4(formatResults(stdout, max, mode));
|
|
2577
2596
|
return;
|
|
2578
2597
|
}
|
|
2579
2598
|
exec(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
|
|
2580
2599
|
if (grepStdout?.trim()) {
|
|
2581
|
-
|
|
2600
|
+
resolve4(formatResults(grepStdout, max, mode));
|
|
2582
2601
|
} else {
|
|
2583
|
-
|
|
2602
|
+
resolve4("No matches found.");
|
|
2584
2603
|
}
|
|
2585
2604
|
});
|
|
2586
2605
|
});
|
|
@@ -2896,7 +2915,7 @@ var restartProcessTool = {
|
|
|
2896
2915
|
async execute(input) {
|
|
2897
2916
|
const data = await lspRequest("/restart-process", { name: input.name });
|
|
2898
2917
|
if (data.ok) {
|
|
2899
|
-
await new Promise((
|
|
2918
|
+
await new Promise((resolve4) => setTimeout(resolve4, 5e3));
|
|
2900
2919
|
return `Restarted ${input.name}.`;
|
|
2901
2920
|
}
|
|
2902
2921
|
return `Error: unexpected response: ${JSON.stringify(data)}`;
|
|
@@ -3123,6 +3142,26 @@ ${opts.styleMap}
|
|
|
3123
3142
|
${ANALYSIS_RESPONSE_FORMAT}`;
|
|
3124
3143
|
return p;
|
|
3125
3144
|
}
|
|
3145
|
+
async function renderHtmlViaSidecar(opts) {
|
|
3146
|
+
const result = await sidecarRequest(
|
|
3147
|
+
"/render-html",
|
|
3148
|
+
{
|
|
3149
|
+
html: opts.html,
|
|
3150
|
+
width: opts.width,
|
|
3151
|
+
height: opts.height,
|
|
3152
|
+
...opts.transparent ? { transparent: true } : {},
|
|
3153
|
+
...opts.scale != null ? { scale: opts.scale } : {}
|
|
3154
|
+
},
|
|
3155
|
+
{ timeout: VIEWPORT_CAPTURE_TIMEOUT_MS }
|
|
3156
|
+
);
|
|
3157
|
+
const url = result?.url;
|
|
3158
|
+
if (!url) {
|
|
3159
|
+
throw new Error(
|
|
3160
|
+
`No URL in sidecar render response. The browser may not be ready yet. Response: ${JSON.stringify(result)}`
|
|
3161
|
+
);
|
|
3162
|
+
}
|
|
3163
|
+
return { url, width: result.width, height: result.height };
|
|
3164
|
+
}
|
|
3126
3165
|
async function streamScreenshotAnalysis(opts) {
|
|
3127
3166
|
const { image, prompt, styleMap, onLog, model, apiConfig } = opts;
|
|
3128
3167
|
const url = await resolveImageRef(image, apiConfig);
|
|
@@ -4262,10 +4301,10 @@ function parseFrontmatter2(filePath) {
|
|
|
4262
4301
|
}
|
|
4263
4302
|
const fm = {};
|
|
4264
4303
|
for (const line of match[1].split("\n")) {
|
|
4265
|
-
const
|
|
4266
|
-
if (
|
|
4267
|
-
const key = line.slice(0,
|
|
4268
|
-
const val = line.slice(
|
|
4304
|
+
const sep2 = line.indexOf(":");
|
|
4305
|
+
if (sep2 > 0) {
|
|
4306
|
+
const key = line.slice(0, sep2).trim();
|
|
4307
|
+
const val = line.slice(sep2 + 1).trim();
|
|
4269
4308
|
fm[key] = val;
|
|
4270
4309
|
}
|
|
4271
4310
|
}
|
|
@@ -4569,7 +4608,7 @@ var browserAutomationTool = {
|
|
|
4569
4608
|
// src/tools/code/screenshot.ts
|
|
4570
4609
|
var screenshotDefinition = {
|
|
4571
4610
|
name: "screenshot",
|
|
4572
|
-
description: "Capture a screenshot of the app preview and get a description of what's on screen. Choose `fullPage`: `false` captures just the visible viewport (fast \u2014 for a specific section the page is scrolled to), `true` captures the entire page top-to-bottom (slower \u2014 for overall composition or content past the fold). Captures the settled page state \u2014 it cannot catch animations, transitions, or transient state. The analysis is not precise about every detail \u2014 for example it cannot reliably identify specific fonts by name, only describe what the letterforms look like. Optionally provide specific questions about what you're looking for. Use a bulleted list to ask many questions at once. To ask additional questions about a screenshot you have already captured, pass its URL as `imageUrl` to skip recapture; `imageUrl` also accepts the disk path of an image file (a user upload, a saved asset) to analyze that instead of the preview. If the screenshot requires interaction first (logging in, clicking a tab, dismissing a modal, scrolling to a section), use the instructions param to describe the steps. To
|
|
4611
|
+
description: "Capture a screenshot of the app preview and get a description of what's on screen. Choose `fullPage`: `false` captures just the visible viewport (fast \u2014 for a specific section the page is scrolled to), `true` captures the entire page top-to-bottom (slower \u2014 for overall composition or content past the fold). Captures the settled page state \u2014 it cannot catch animations, transitions, or transient state. The analysis is not precise about every detail \u2014 for example it cannot reliably identify specific fonts by name, only describe what the letterforms look like. Optionally provide specific questions about what you're looking for. Use a bulleted list to ask many questions at once. To ask additional questions about a screenshot you have already captured, pass its URL as `imageUrl` to skip recapture; `imageUrl` also accepts the disk path of an image file (a user upload, a saved asset) to analyze that instead of the preview. If the screenshot requires interaction first (logging in, clicking a tab, dismissing a modal, scrolling to a section), use the instructions param to describe the steps. To capture at exact pixel dimensions, set `width` and `height`: the tool clips to exactly that viewport and returns the image URL.",
|
|
4573
4612
|
inputSchema: {
|
|
4574
4613
|
type: "object",
|
|
4575
4614
|
properties: {
|
|
@@ -4600,7 +4639,7 @@ var screenshotDefinition = {
|
|
|
4600
4639
|
format: {
|
|
4601
4640
|
type: "string",
|
|
4602
4641
|
enum: ["png", "jpeg"],
|
|
4603
|
-
description: "Output image format. Defaults to 'jpeg'. Use 'png' for crisp flat graphics
|
|
4642
|
+
description: "Output image format. Defaults to 'jpeg'. Use 'png' for crisp flat graphics where JPEG artifacts show on sharp type and edges."
|
|
4604
4643
|
},
|
|
4605
4644
|
instructions: {
|
|
4606
4645
|
type: "string",
|
|
@@ -4922,7 +4961,36 @@ ${brief}
|
|
|
4922
4961
|
}
|
|
4923
4962
|
|
|
4924
4963
|
// src/subagents/designExpert/tools/images/imageGenerator.ts
|
|
4925
|
-
var 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, any text present in the image, whether there are any issues (artifacts, distortions), and how it could be used in a layout for an app or website. Be concise and practical. Respond only with your analysis as Markdown (starting with the title "Asset Review") and absolutely no other text. Do not use emojis - use unicode if you need symbols.';
|
|
4964
|
+
var 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, any text present in the image, whether there are any issues (artifacts, distortions), whether the artwork extends fully to all four edges of the canvas or sits inset (call out any baked-in border, frame, rounded-corner mask, drop-shadow margin, or mockup presentation such as an icon rendered on a background or device), and how it could be used in a layout for an app or website. Be concise and practical. Respond only with your analysis as Markdown (starting with the title "Asset Review") and absolutely no other text. Do not use emojis - use unicode if you need symbols.';
|
|
4965
|
+
var OPENAI_IMAGE_SIZES = [
|
|
4966
|
+
[768, 1024],
|
|
4967
|
+
[1024, 768],
|
|
4968
|
+
[1024, 1024],
|
|
4969
|
+
[1024, 1536],
|
|
4970
|
+
[1536, 1024],
|
|
4971
|
+
[1280, 2560],
|
|
4972
|
+
[2560, 1280],
|
|
4973
|
+
[1440, 2560],
|
|
4974
|
+
[2560, 1440],
|
|
4975
|
+
[1792, 2400],
|
|
4976
|
+
[2400, 1792],
|
|
4977
|
+
[2400, 2880],
|
|
4978
|
+
[2560, 2560]
|
|
4979
|
+
];
|
|
4980
|
+
function snapToOpenAiImageSize(width, height) {
|
|
4981
|
+
let best = OPENAI_IMAGE_SIZES[0];
|
|
4982
|
+
let bestScore = Infinity;
|
|
4983
|
+
for (const [w, h] of OPENAI_IMAGE_SIZES) {
|
|
4984
|
+
const aspectDiff = Math.abs(Math.log(w / h / (width / height)));
|
|
4985
|
+
const areaDiff = Math.abs(Math.log(w * h / (width * height)));
|
|
4986
|
+
const score = aspectDiff * 8 + areaDiff;
|
|
4987
|
+
if (score < bestScore) {
|
|
4988
|
+
bestScore = score;
|
|
4989
|
+
best = [w, h];
|
|
4990
|
+
}
|
|
4991
|
+
}
|
|
4992
|
+
return `${best[0]}x${best[1]}`;
|
|
4993
|
+
}
|
|
4926
4994
|
async function generateImageAssets(opts) {
|
|
4927
4995
|
const {
|
|
4928
4996
|
prompts,
|
|
@@ -4937,7 +5005,11 @@ async function generateImageAssets(opts) {
|
|
|
4937
5005
|
const sourceImages = opts.sourceImages?.length ? await resolveImageRefs(opts.sourceImages, apiConfig) : void 0;
|
|
4938
5006
|
const width = opts.width || 2048;
|
|
4939
5007
|
const height = opts.height || 2048;
|
|
4940
|
-
const config = {
|
|
5008
|
+
const config = {
|
|
5009
|
+
width,
|
|
5010
|
+
height,
|
|
5011
|
+
size: snapToOpenAiImageSize(width, height)
|
|
5012
|
+
};
|
|
4941
5013
|
if (sourceImages?.length) {
|
|
4942
5014
|
const [firstImage] = sourceImages;
|
|
4943
5015
|
config.images = sourceImages;
|
|
@@ -5189,11 +5261,143 @@ async function execute6(input, onLog, context) {
|
|
|
5189
5261
|
});
|
|
5190
5262
|
}
|
|
5191
5263
|
|
|
5264
|
+
// src/subagents/designExpert/tools/images/renderImage.ts
|
|
5265
|
+
var renderImage_exports = {};
|
|
5266
|
+
__export(renderImage_exports, {
|
|
5267
|
+
definition: () => definition7,
|
|
5268
|
+
execute: () => execute7
|
|
5269
|
+
});
|
|
5270
|
+
import { mkdir, unlink, writeFile } from "fs/promises";
|
|
5271
|
+
import { tmpdir } from "os";
|
|
5272
|
+
import { dirname, join, resolve as resolve2, sep } from "path";
|
|
5273
|
+
import { randomUUID } from "crypto";
|
|
5274
|
+
var MIN_DIMENSION = 16;
|
|
5275
|
+
var MAX_DIMENSION = 4096;
|
|
5276
|
+
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.';
|
|
5277
|
+
var definition7 = {
|
|
5278
|
+
name: "renderImage",
|
|
5279
|
+
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.",
|
|
5280
|
+
inputSchema: {
|
|
5281
|
+
type: "object",
|
|
5282
|
+
properties: {
|
|
5283
|
+
html: {
|
|
5284
|
+
type: "string",
|
|
5285
|
+
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."
|
|
5286
|
+
},
|
|
5287
|
+
width: {
|
|
5288
|
+
type: "number",
|
|
5289
|
+
description: "Viewport width in CSS pixels (e.g. 1200 for an OG card, 512 for an icon tile). Range: 16-4096."
|
|
5290
|
+
},
|
|
5291
|
+
height: {
|
|
5292
|
+
type: "number",
|
|
5293
|
+
description: "Viewport height in CSS pixels. Range: 16-4096."
|
|
5294
|
+
},
|
|
5295
|
+
scale: {
|
|
5296
|
+
type: "number",
|
|
5297
|
+
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)."
|
|
5298
|
+
},
|
|
5299
|
+
transparentBackground: {
|
|
5300
|
+
type: "boolean",
|
|
5301
|
+
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."
|
|
5302
|
+
},
|
|
5303
|
+
savePath: {
|
|
5304
|
+
type: "string",
|
|
5305
|
+
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)."
|
|
5306
|
+
}
|
|
5307
|
+
},
|
|
5308
|
+
required: ["html", "width", "height"]
|
|
5309
|
+
}
|
|
5310
|
+
};
|
|
5311
|
+
async function execute7(input, onLog, context) {
|
|
5312
|
+
const html = typeof input.html === "string" ? input.html : "";
|
|
5313
|
+
const width = Math.round(Number(input.width));
|
|
5314
|
+
const height = Math.round(Number(input.height));
|
|
5315
|
+
if (!html || !Number.isFinite(width) || !Number.isFinite(height) || width < MIN_DIMENSION || width > MAX_DIMENSION || height < MIN_DIMENSION || height > MAX_DIMENSION) {
|
|
5316
|
+
return `Error: renderImage requires an html string plus width/height between ${MIN_DIMENSION} and ${MAX_DIMENSION}.`;
|
|
5317
|
+
}
|
|
5318
|
+
const release = await acquireBrowserLock();
|
|
5319
|
+
let rendered;
|
|
5320
|
+
try {
|
|
5321
|
+
onLog?.("Rendering document in the sandbox browser...");
|
|
5322
|
+
rendered = await renderHtmlViaSidecar({
|
|
5323
|
+
html,
|
|
5324
|
+
width,
|
|
5325
|
+
height,
|
|
5326
|
+
transparent: input.transparentBackground === true,
|
|
5327
|
+
scale: typeof input.scale === "number" ? input.scale : void 0
|
|
5328
|
+
});
|
|
5329
|
+
} catch (err) {
|
|
5330
|
+
return `Error: render failed: ${err?.message ?? err}`;
|
|
5331
|
+
} finally {
|
|
5332
|
+
release();
|
|
5333
|
+
}
|
|
5334
|
+
let bytes;
|
|
5335
|
+
try {
|
|
5336
|
+
const res = await fetch(rendered.url);
|
|
5337
|
+
if (!res.ok) {
|
|
5338
|
+
throw new Error(`fetch returned ${res.status}`);
|
|
5339
|
+
}
|
|
5340
|
+
bytes = Buffer.from(await res.arrayBuffer());
|
|
5341
|
+
} catch (err) {
|
|
5342
|
+
return `Error: rendered but could not download the capture (${err?.message ?? err}). Temporary URL: ${rendered.url}`;
|
|
5343
|
+
}
|
|
5344
|
+
let savedPath;
|
|
5345
|
+
if (typeof input.savePath === "string" && input.savePath) {
|
|
5346
|
+
const absolute = resolve2(PROJECT_ROOT, input.savePath);
|
|
5347
|
+
if (!absolute.startsWith(PROJECT_ROOT + sep)) {
|
|
5348
|
+
return "Error: savePath must resolve inside the project.";
|
|
5349
|
+
}
|
|
5350
|
+
await mkdir(dirname(absolute), { recursive: true });
|
|
5351
|
+
await writeFile(absolute, bytes);
|
|
5352
|
+
savedPath = input.savePath;
|
|
5353
|
+
}
|
|
5354
|
+
onLog?.("Hosting the capture...");
|
|
5355
|
+
const tmpPath = join(tmpdir(), `render-${randomUUID()}.png`);
|
|
5356
|
+
let url = rendered.url;
|
|
5357
|
+
let temporary = true;
|
|
5358
|
+
try {
|
|
5359
|
+
await writeFile(tmpPath, bytes);
|
|
5360
|
+
const upload = await runMindstudioCliResult(["upload", tmpPath], {
|
|
5361
|
+
timeout: 6e4,
|
|
5362
|
+
onLog,
|
|
5363
|
+
caller: "designExpert"
|
|
5364
|
+
});
|
|
5365
|
+
const match = upload.ok ? upload.value.match(/https:\/\/\S+/g) : null;
|
|
5366
|
+
if (match?.length) {
|
|
5367
|
+
url = match[match.length - 1];
|
|
5368
|
+
temporary = false;
|
|
5369
|
+
}
|
|
5370
|
+
} finally {
|
|
5371
|
+
await unlink(tmpPath).catch(() => {
|
|
5372
|
+
});
|
|
5373
|
+
}
|
|
5374
|
+
const analysis = await analyzeImage({
|
|
5375
|
+
prompt: RENDER_ANALYZE_PROMPT,
|
|
5376
|
+
image: url,
|
|
5377
|
+
onLog,
|
|
5378
|
+
model: resolveModel("imageAnalysis", context?.models, context?.model)
|
|
5379
|
+
}).then((r) => r.analysis).catch((err) => `Could not review this image: ${err.message}`);
|
|
5380
|
+
return JSON.stringify({
|
|
5381
|
+
images: [
|
|
5382
|
+
{
|
|
5383
|
+
url,
|
|
5384
|
+
...temporary ? {
|
|
5385
|
+
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."
|
|
5386
|
+
} : {},
|
|
5387
|
+
...savedPath ? { savedPath } : {},
|
|
5388
|
+
analysis,
|
|
5389
|
+
width: rendered.width,
|
|
5390
|
+
height: rendered.height
|
|
5391
|
+
}
|
|
5392
|
+
]
|
|
5393
|
+
});
|
|
5394
|
+
}
|
|
5395
|
+
|
|
5192
5396
|
// src/subagents/designExpert/tools/polishCopy.ts
|
|
5193
5397
|
var polishCopy_exports = {};
|
|
5194
5398
|
__export(polishCopy_exports, {
|
|
5195
|
-
definition: () =>
|
|
5196
|
-
execute: () =>
|
|
5399
|
+
definition: () => definition8,
|
|
5400
|
+
execute: () => execute8
|
|
5197
5401
|
});
|
|
5198
5402
|
|
|
5199
5403
|
// src/subagents/copyEditor/tools.ts
|
|
@@ -5249,7 +5453,7 @@ var copyEditorTool = {
|
|
|
5249
5453
|
};
|
|
5250
5454
|
|
|
5251
5455
|
// src/subagents/designExpert/tools/polishCopy.ts
|
|
5252
|
-
var
|
|
5456
|
+
var definition8 = {
|
|
5253
5457
|
name: "polishCopy",
|
|
5254
5458
|
description: "Hand off any user-facing copy you've written \u2014 headlines, captions, labels, body text \u2014 and get back a sharper version: better built for its audience and free of the fingerprints that make writing read as AI. It elevates how the copy communicates without inventing facts or claims you didn't give it. Give it the text plus what it's for (where it appears, the audience).",
|
|
5255
5459
|
inputSchema: {
|
|
@@ -5263,10 +5467,63 @@ var definition7 = {
|
|
|
5263
5467
|
required: ["task"]
|
|
5264
5468
|
}
|
|
5265
5469
|
};
|
|
5266
|
-
async function
|
|
5470
|
+
async function execute8(input, _onLog, context) {
|
|
5267
5471
|
return copyEditorTool.execute(input, context);
|
|
5268
5472
|
}
|
|
5269
5473
|
|
|
5474
|
+
// src/subagents/designExpert/tools/loadSkill.ts
|
|
5475
|
+
var loadSkill_exports = {};
|
|
5476
|
+
__export(loadSkill_exports, {
|
|
5477
|
+
definition: () => definition9,
|
|
5478
|
+
execute: () => execute9
|
|
5479
|
+
});
|
|
5480
|
+
|
|
5481
|
+
// src/subagents/designExpert/skills/_catalog.ts
|
|
5482
|
+
var INTRO2 = `Deep craft references for specific surfaces, kept out of this prompt so they can go far beyond what a resident section could \u2014 full technique recipes, worked examples, quality bars. Recognising that one of these fits the brief is part of your job as much as the design work itself: when a trigger fires, load the reference with loadSkill before designing in that area, not after. The craft in these docs is the difference between a defaulted artifact and a designed one.
|
|
5483
|
+
|
|
5484
|
+
A loaded reference drops out of the conversation once it ages out. Re-read it at the path listed with readFile whenever you need it again.`;
|
|
5485
|
+
var designSkillCatalog = buildSkillCatalog({
|
|
5486
|
+
dir: assetPath("subagents/designExpert", "skills"),
|
|
5487
|
+
tag: "available_skills",
|
|
5488
|
+
intro: INTRO2
|
|
5489
|
+
});
|
|
5490
|
+
|
|
5491
|
+
// src/subagents/designExpert/tools/loadSkill.ts
|
|
5492
|
+
var definition9 = {
|
|
5493
|
+
name: "loadSkill",
|
|
5494
|
+
description: "Load the full craft reference for a design surface that isn't in your prompt. The available skills and the trigger for each are listed in <available_skills>. Load one before designing in its area, not after \u2014 these are hard-won technique recipes, and the defaulted version of these surfaces is exactly what they exist to prevent. Calling this is cheap and expected \u2014 if you're unsure whether you need it, load it.",
|
|
5495
|
+
inputSchema: {
|
|
5496
|
+
type: "object",
|
|
5497
|
+
properties: {
|
|
5498
|
+
skill: {
|
|
5499
|
+
type: "string",
|
|
5500
|
+
// Omitted when the catalog is empty: an empty enum is a schema no
|
|
5501
|
+
// provider accepts, and failing one tool call beats failing every
|
|
5502
|
+
// request if the docs ever go missing from a build.
|
|
5503
|
+
...designSkillCatalog.ids.length > 0 ? { enum: designSkillCatalog.ids } : {},
|
|
5504
|
+
description: "The skill id, as listed in <available_skills>."
|
|
5505
|
+
}
|
|
5506
|
+
},
|
|
5507
|
+
required: ["skill"]
|
|
5508
|
+
}
|
|
5509
|
+
};
|
|
5510
|
+
async function execute9(input) {
|
|
5511
|
+
const id = String(input.skill ?? "");
|
|
5512
|
+
const skill = designSkillCatalog.get(id);
|
|
5513
|
+
if (!skill) {
|
|
5514
|
+
return `Error: unknown skill "${id}". Available: ${designSkillCatalog.ids.join(", ") || "(none)"}`;
|
|
5515
|
+
}
|
|
5516
|
+
try {
|
|
5517
|
+
const body = designSkillCatalog.readBody(skill);
|
|
5518
|
+
return `${body}
|
|
5519
|
+
|
|
5520
|
+
---
|
|
5521
|
+
This reference lives at ${skill.path}. Re-read it with readFile if you need it again later \u2014 it won't stay in the conversation.`;
|
|
5522
|
+
} catch (err) {
|
|
5523
|
+
return `Error loading skill "${id}": ${err.message}`;
|
|
5524
|
+
}
|
|
5525
|
+
}
|
|
5526
|
+
|
|
5270
5527
|
// src/subagents/designExpert/tools/index.ts
|
|
5271
5528
|
var tools = {
|
|
5272
5529
|
searchGoogle: searchGoogle_exports,
|
|
@@ -5279,7 +5536,9 @@ var tools = {
|
|
|
5279
5536
|
screenshot: { definition: screenshotDefinition, execute: executeScreenshot },
|
|
5280
5537
|
generateImages: generateImages_exports,
|
|
5281
5538
|
editImages: editImages_exports,
|
|
5282
|
-
|
|
5539
|
+
renderImage: renderImage_exports,
|
|
5540
|
+
polishCopy: polishCopy_exports,
|
|
5541
|
+
loadSkill: loadSkill_exports
|
|
5283
5542
|
};
|
|
5284
5543
|
var DESIGN_EXPERT_TOOLS = [
|
|
5285
5544
|
...COMMON_READ_TOOLS,
|
|
@@ -5475,6 +5734,12 @@ function getDesignExpertPrompt(onboardingState, opts) {
|
|
|
5475
5734
|
"{{ui_case_studies}}",
|
|
5476
5735
|
getUiInspirationSample(indices.uiInspiration)
|
|
5477
5736
|
);
|
|
5737
|
+
const skillsBlock = designSkillCatalog.renderCatalogBlock();
|
|
5738
|
+
if (skillsBlock) {
|
|
5739
|
+
prompt += `
|
|
5740
|
+
|
|
5741
|
+
${skillsBlock}`;
|
|
5742
|
+
}
|
|
5478
5743
|
prompt += "\n\n<!-- cache_breakpoint -->";
|
|
5479
5744
|
if (specContext) {
|
|
5480
5745
|
prompt += `
|
|
@@ -5698,13 +5963,13 @@ var PITCH_DECK_SHELL = readAsset(
|
|
|
5698
5963
|
"subagents/productVision",
|
|
5699
5964
|
"pitch-deck-shell.html"
|
|
5700
5965
|
);
|
|
5701
|
-
function
|
|
5966
|
+
function resolve3(filePath) {
|
|
5702
5967
|
return path10.join(ROADMAP_DIR, filePath);
|
|
5703
5968
|
}
|
|
5704
5969
|
async function executeVisionTool(name, input, context) {
|
|
5705
5970
|
switch (name) {
|
|
5706
5971
|
case "writeFile": {
|
|
5707
|
-
const filePath =
|
|
5972
|
+
const filePath = resolve3(input.path);
|
|
5708
5973
|
try {
|
|
5709
5974
|
fs19.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
5710
5975
|
let oldContent = null;
|
|
@@ -5722,7 +5987,7 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
|
|
|
5722
5987
|
}
|
|
5723
5988
|
}
|
|
5724
5989
|
case "deleteFile": {
|
|
5725
|
-
const filePath =
|
|
5990
|
+
const filePath = resolve3(input.path);
|
|
5726
5991
|
try {
|
|
5727
5992
|
if (!fs19.existsSync(filePath)) {
|
|
5728
5993
|
return `Error: ${filePath} does not exist`;
|
|
@@ -5739,7 +6004,7 @@ ${unifiedDiff(filePath, oldContent, "")}`;
|
|
|
5739
6004
|
if (!context) {
|
|
5740
6005
|
return "Error: writePitchDeck requires execution context for design expert delegation";
|
|
5741
6006
|
}
|
|
5742
|
-
const filePath =
|
|
6007
|
+
const filePath = resolve3("pitch.html");
|
|
5743
6008
|
try {
|
|
5744
6009
|
fs19.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
5745
6010
|
const exists = fs19.existsSync(filePath);
|
|
@@ -7360,14 +7625,14 @@ function buildCorpus() {
|
|
|
7360
7625
|
});
|
|
7361
7626
|
}
|
|
7362
7627
|
}
|
|
7363
|
-
const
|
|
7628
|
+
const sep2 = "\n\n---\n\n";
|
|
7364
7629
|
const sections = [];
|
|
7365
7630
|
let usedChars = 0;
|
|
7366
7631
|
for (const { path: p, content } of files) {
|
|
7367
7632
|
const section = `## File: ${p}
|
|
7368
7633
|
|
|
7369
7634
|
${content}`;
|
|
7370
|
-
const added = section.length + (sections.length > 0 ?
|
|
7635
|
+
const added = section.length + (sections.length > 0 ? sep2.length : 0);
|
|
7371
7636
|
if (sections.length > 0 && usedChars + added > BRAND_CORPUS_CHAR_LIMIT) {
|
|
7372
7637
|
sections.push(
|
|
7373
7638
|
`(brand corpus truncated: included ${sections.length} of ${files.length} files, ~${(usedChars / 1024).toFixed(0)}KB; brand-relevant files were prioritized.)`
|
|
@@ -7377,7 +7642,7 @@ ${content}`;
|
|
|
7377
7642
|
sections.push(section);
|
|
7378
7643
|
usedChars += added;
|
|
7379
7644
|
}
|
|
7380
|
-
return sections.join(
|
|
7645
|
+
return sections.join(sep2);
|
|
7381
7646
|
}
|
|
7382
7647
|
function headSlice(content) {
|
|
7383
7648
|
if (content.length <= HEAD_SLICE_CHARS) {
|
|
@@ -8474,8 +8739,8 @@ async function runTurn(params) {
|
|
|
8474
8739
|
|
|
8475
8740
|
// src/headless/attachments.ts
|
|
8476
8741
|
import { mkdirSync, existsSync } from "fs";
|
|
8477
|
-
import { writeFile } from "fs/promises";
|
|
8478
|
-
import { basename as basename2, join, extname as extname2 } from "path";
|
|
8742
|
+
import { writeFile as writeFile2 } from "fs/promises";
|
|
8743
|
+
import { basename as basename2, join as join2, extname as extname2 } from "path";
|
|
8479
8744
|
var log15 = createLogger("headless:attachments");
|
|
8480
8745
|
var UPLOADS_DIR = "src/.user-uploads";
|
|
8481
8746
|
function filenameFromUrl(url) {
|
|
@@ -8488,7 +8753,7 @@ function filenameFromUrl(url) {
|
|
|
8488
8753
|
}
|
|
8489
8754
|
}
|
|
8490
8755
|
function resolveUniqueFilename(name, claimed) {
|
|
8491
|
-
const isFree = (candidate) => !claimed.has(candidate) && !existsSync(
|
|
8756
|
+
const isFree = (candidate) => !claimed.has(candidate) && !existsSync(join2(UPLOADS_DIR, candidate));
|
|
8492
8757
|
if (isFree(name)) {
|
|
8493
8758
|
return name;
|
|
8494
8759
|
}
|
|
@@ -8523,7 +8788,7 @@ async function persistAttachments(attachments) {
|
|
|
8523
8788
|
const results = await Promise.allSettled(
|
|
8524
8789
|
nonVoice.map(async (att, i) => {
|
|
8525
8790
|
const name = names[i];
|
|
8526
|
-
const localPath =
|
|
8791
|
+
const localPath = join2(UPLOADS_DIR, name);
|
|
8527
8792
|
const res = await fetch(att.url, {
|
|
8528
8793
|
signal: AbortSignal.timeout(3e4)
|
|
8529
8794
|
});
|
|
@@ -8531,7 +8796,7 @@ async function persistAttachments(attachments) {
|
|
|
8531
8796
|
throw new Error(`HTTP ${res.status} downloading ${att.url}`);
|
|
8532
8797
|
}
|
|
8533
8798
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
8534
|
-
await
|
|
8799
|
+
await writeFile2(localPath, buffer);
|
|
8535
8800
|
log15.info("Attachment saved", {
|
|
8536
8801
|
filename: name,
|
|
8537
8802
|
path: localPath,
|
|
@@ -8545,7 +8810,7 @@ async function persistAttachments(attachments) {
|
|
|
8545
8810
|
});
|
|
8546
8811
|
if (textRes.ok) {
|
|
8547
8812
|
extractedTextPath = `${localPath}.txt`;
|
|
8548
|
-
await
|
|
8813
|
+
await writeFile2(extractedTextPath, await textRes.text(), "utf-8");
|
|
8549
8814
|
log15.info("Extracted text saved", { path: extractedTextPath });
|
|
8550
8815
|
}
|
|
8551
8816
|
} catch {
|
|
@@ -9132,17 +9397,17 @@ var HeadlessSession = class {
|
|
|
9132
9397
|
return Promise.resolve(early);
|
|
9133
9398
|
}
|
|
9134
9399
|
const shouldTimeout = !USER_FACING_TOOLS.has(name);
|
|
9135
|
-
return new Promise((
|
|
9400
|
+
return new Promise((resolve4) => {
|
|
9136
9401
|
const timeout = shouldTimeout ? setTimeout(() => {
|
|
9137
9402
|
this.pendingTools.delete(id);
|
|
9138
|
-
|
|
9403
|
+
resolve4(
|
|
9139
9404
|
"Error: Tool timed out \u2014 no response from the app environment after 5 minutes."
|
|
9140
9405
|
);
|
|
9141
9406
|
}, EXTERNAL_TOOL_TIMEOUT_MS) : void 0;
|
|
9142
9407
|
this.pendingTools.set(id, {
|
|
9143
9408
|
resolve: (result) => {
|
|
9144
9409
|
clearTimeout(timeout);
|
|
9145
|
-
|
|
9410
|
+
resolve4(result);
|
|
9146
9411
|
},
|
|
9147
9412
|
timeout
|
|
9148
9413
|
});
|