@mindstudio-ai/remy 0.1.307 → 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 +468 -189
- package/dist/index.js +494 -205
- 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/instructions.md +1 -1
- 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
|
);
|
|
@@ -8444,6 +8537,155 @@ function friendlyError(raw) {
|
|
|
8444
8537
|
return `Something went wrong: ${raw}`;
|
|
8445
8538
|
}
|
|
8446
8539
|
|
|
8540
|
+
// src/suggestions.ts
|
|
8541
|
+
var SUGGEST_MARKER = "](suggest:";
|
|
8542
|
+
var SEPARATORS = "\\s\xB7|,\\-\u2013\u2014";
|
|
8543
|
+
var SEPARATORS_ONLY = new RegExp(`^[${SEPARATORS}]*$`);
|
|
8544
|
+
var TRAILING_SEPARATORS = new RegExp(`[${SEPARATORS}]+$`);
|
|
8545
|
+
var LIST_MARKER = /^\s*(?:[-*+]|\d+[.)])\s+/;
|
|
8546
|
+
var FENCE = /^ {0,3}(?:```|~~~)/;
|
|
8547
|
+
function findLinks(line) {
|
|
8548
|
+
const found = [];
|
|
8549
|
+
let i = 0;
|
|
8550
|
+
while (i < line.length) {
|
|
8551
|
+
const ch = line[i];
|
|
8552
|
+
if (ch === "\\") {
|
|
8553
|
+
i += 2;
|
|
8554
|
+
continue;
|
|
8555
|
+
}
|
|
8556
|
+
if (ch === "`") {
|
|
8557
|
+
let runEnd = i;
|
|
8558
|
+
while (runEnd < line.length && line[runEnd] === "`") {
|
|
8559
|
+
runEnd++;
|
|
8560
|
+
}
|
|
8561
|
+
const fence = line.slice(i, runEnd);
|
|
8562
|
+
const close = line.indexOf(fence, runEnd);
|
|
8563
|
+
if (close === -1) {
|
|
8564
|
+
break;
|
|
8565
|
+
}
|
|
8566
|
+
i = close + fence.length;
|
|
8567
|
+
continue;
|
|
8568
|
+
}
|
|
8569
|
+
if (ch !== "[") {
|
|
8570
|
+
i++;
|
|
8571
|
+
continue;
|
|
8572
|
+
}
|
|
8573
|
+
const labelEnd = line.indexOf("]", i + 1);
|
|
8574
|
+
if (labelEnd === -1) {
|
|
8575
|
+
break;
|
|
8576
|
+
}
|
|
8577
|
+
if (!line.startsWith(SUGGEST_MARKER, labelEnd)) {
|
|
8578
|
+
i = labelEnd + 1;
|
|
8579
|
+
continue;
|
|
8580
|
+
}
|
|
8581
|
+
let depth = 1;
|
|
8582
|
+
let p = labelEnd + SUGGEST_MARKER.length;
|
|
8583
|
+
while (p < line.length && depth > 0) {
|
|
8584
|
+
if (line[p] === "(") {
|
|
8585
|
+
depth++;
|
|
8586
|
+
} else if (line[p] === ")") {
|
|
8587
|
+
depth--;
|
|
8588
|
+
}
|
|
8589
|
+
p++;
|
|
8590
|
+
}
|
|
8591
|
+
if (depth > 0) {
|
|
8592
|
+
break;
|
|
8593
|
+
}
|
|
8594
|
+
found.push({
|
|
8595
|
+
start: i,
|
|
8596
|
+
end: p,
|
|
8597
|
+
label: line.slice(i + 1, labelEnd).trim(),
|
|
8598
|
+
message: line.slice(labelEnd + SUGGEST_MARKER.length, p - 1).trim(),
|
|
8599
|
+
standalone: false
|
|
8600
|
+
});
|
|
8601
|
+
i = p;
|
|
8602
|
+
}
|
|
8603
|
+
return found;
|
|
8604
|
+
}
|
|
8605
|
+
function classify(line, matches) {
|
|
8606
|
+
let tailClean = SEPARATORS_ONLY.test(
|
|
8607
|
+
line.slice(matches[matches.length - 1].end)
|
|
8608
|
+
);
|
|
8609
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
8610
|
+
matches[i].standalone = tailClean;
|
|
8611
|
+
if (!tailClean) {
|
|
8612
|
+
continue;
|
|
8613
|
+
}
|
|
8614
|
+
const gap = i > 0 ? line.slice(matches[i - 1].end, matches[i].start) : "";
|
|
8615
|
+
tailClean = SEPARATORS_ONLY.test(gap);
|
|
8616
|
+
}
|
|
8617
|
+
}
|
|
8618
|
+
function parseSuggestions(raw) {
|
|
8619
|
+
if (!raw.includes(SUGGEST_MARKER)) {
|
|
8620
|
+
return { text: raw, suggestions: [] };
|
|
8621
|
+
}
|
|
8622
|
+
const suggestions = [];
|
|
8623
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8624
|
+
const outLines = [];
|
|
8625
|
+
let inFence = false;
|
|
8626
|
+
let droppedLine = false;
|
|
8627
|
+
for (const line of raw.split("\n")) {
|
|
8628
|
+
if (FENCE.test(line)) {
|
|
8629
|
+
inFence = !inFence;
|
|
8630
|
+
outLines.push(line);
|
|
8631
|
+
continue;
|
|
8632
|
+
}
|
|
8633
|
+
if (inFence) {
|
|
8634
|
+
outLines.push(line);
|
|
8635
|
+
continue;
|
|
8636
|
+
}
|
|
8637
|
+
const matches = findLinks(line);
|
|
8638
|
+
if (matches.length === 0) {
|
|
8639
|
+
outLines.push(line);
|
|
8640
|
+
continue;
|
|
8641
|
+
}
|
|
8642
|
+
classify(line, matches);
|
|
8643
|
+
for (const m of matches) {
|
|
8644
|
+
const key = `${m.label}::${m.message}`;
|
|
8645
|
+
if (!seen.has(key)) {
|
|
8646
|
+
seen.add(key);
|
|
8647
|
+
suggestions.push({ label: m.label, message: m.message });
|
|
8648
|
+
}
|
|
8649
|
+
}
|
|
8650
|
+
let rebuilt = "";
|
|
8651
|
+
let cursor = 0;
|
|
8652
|
+
for (const m of matches) {
|
|
8653
|
+
rebuilt += line.slice(cursor, m.start);
|
|
8654
|
+
if (!m.standalone) {
|
|
8655
|
+
rebuilt += m.label;
|
|
8656
|
+
}
|
|
8657
|
+
cursor = m.end;
|
|
8658
|
+
}
|
|
8659
|
+
rebuilt += line.slice(cursor);
|
|
8660
|
+
if (matches.some((m) => m.standalone)) {
|
|
8661
|
+
if (SEPARATORS_ONLY.test(rebuilt.replace(LIST_MARKER, ""))) {
|
|
8662
|
+
droppedLine = true;
|
|
8663
|
+
continue;
|
|
8664
|
+
}
|
|
8665
|
+
rebuilt = rebuilt.replace(TRAILING_SEPARATORS, "");
|
|
8666
|
+
}
|
|
8667
|
+
outLines.push(rebuilt);
|
|
8668
|
+
}
|
|
8669
|
+
let text = outLines.join("\n").replace(/\s+$/, "");
|
|
8670
|
+
if (droppedLine) {
|
|
8671
|
+
text = text.replace(/\n{3,}/g, "\n\n");
|
|
8672
|
+
}
|
|
8673
|
+
return { text, suggestions };
|
|
8674
|
+
}
|
|
8675
|
+
function annotateSuggestions(blocks) {
|
|
8676
|
+
for (const block of blocks) {
|
|
8677
|
+
if (block.type !== "text" || !block.text.includes(SUGGEST_MARKER)) {
|
|
8678
|
+
continue;
|
|
8679
|
+
}
|
|
8680
|
+
const { text, suggestions } = parseSuggestions(block.text);
|
|
8681
|
+
if (suggestions.length === 0) {
|
|
8682
|
+
continue;
|
|
8683
|
+
}
|
|
8684
|
+
block.displayText = text;
|
|
8685
|
+
block.suggestions = suggestions;
|
|
8686
|
+
}
|
|
8687
|
+
}
|
|
8688
|
+
|
|
8447
8689
|
// src/agent.ts
|
|
8448
8690
|
var log15 = createLogger("agent");
|
|
8449
8691
|
var BRAND_TRIGGERING_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -8638,8 +8880,27 @@ async function runTurn(params) {
|
|
|
8638
8880
|
toolInputAccumulators.set(id, acc);
|
|
8639
8881
|
}
|
|
8640
8882
|
return acc;
|
|
8883
|
+
}, emitTextBlockSnapshot2 = function(closing) {
|
|
8884
|
+
let block;
|
|
8885
|
+
for (let i = contentBlocks.length - 1; i >= 0; i--) {
|
|
8886
|
+
const candidate = contentBlocks[i];
|
|
8887
|
+
if (candidate.type === "text") {
|
|
8888
|
+
block = candidate;
|
|
8889
|
+
break;
|
|
8890
|
+
}
|
|
8891
|
+
}
|
|
8892
|
+
if (!block || !block.text.includes(SUGGEST_MARKER)) {
|
|
8893
|
+
return;
|
|
8894
|
+
}
|
|
8895
|
+
const { text, suggestions } = parseSuggestions(block.text);
|
|
8896
|
+
const worthSending = closing ? suggestions.length > 0 : suggestions.length > emittedSuggestionCount;
|
|
8897
|
+
if (!worthSending) {
|
|
8898
|
+
return;
|
|
8899
|
+
}
|
|
8900
|
+
emittedSuggestionCount = suggestions.length;
|
|
8901
|
+
onEvent({ type: "text_block", text, suggestions });
|
|
8641
8902
|
};
|
|
8642
|
-
var getOrCreateAccumulator = getOrCreateAccumulator2;
|
|
8903
|
+
var getOrCreateAccumulator = getOrCreateAccumulator2, emitTextBlockSnapshot = emitTextBlockSnapshot2;
|
|
8643
8904
|
if (signal?.aborted) {
|
|
8644
8905
|
onEvent({ type: "turn_cancelled" });
|
|
8645
8906
|
saveSession(state);
|
|
@@ -8651,6 +8912,7 @@ async function runTurn(params) {
|
|
|
8651
8912
|
let thinkingCompleteCount = 0;
|
|
8652
8913
|
let lastThinkingRelatedStartedAt;
|
|
8653
8914
|
let textBlockOpen = false;
|
|
8915
|
+
let emittedSuggestionCount = 0;
|
|
8654
8916
|
const toolInputAccumulators = /* @__PURE__ */ new Map();
|
|
8655
8917
|
let stopReason = "end_turn";
|
|
8656
8918
|
let turnProviderMetadata;
|
|
@@ -8739,14 +9001,19 @@ async function runTurn(params) {
|
|
|
8739
9001
|
text: event.text,
|
|
8740
9002
|
startedAt: event.ts
|
|
8741
9003
|
});
|
|
9004
|
+
emittedSuggestionCount = 0;
|
|
8742
9005
|
}
|
|
8743
9006
|
textBlockOpen = true;
|
|
8744
9007
|
onEvent({ type: "text", text: event.text });
|
|
9008
|
+
emitTextBlockSnapshot2(false);
|
|
8745
9009
|
break;
|
|
8746
9010
|
}
|
|
8747
9011
|
case "thinking":
|
|
8748
9012
|
if (event.text === "") {
|
|
8749
9013
|
thinkingBlockStartTimes.push(event.ts);
|
|
9014
|
+
if (textBlockOpen) {
|
|
9015
|
+
emitTextBlockSnapshot2(true);
|
|
9016
|
+
}
|
|
8750
9017
|
textBlockOpen = false;
|
|
8751
9018
|
}
|
|
8752
9019
|
onEvent({ type: "thinking", text: event.text });
|
|
@@ -8872,6 +9139,11 @@ async function runTurn(params) {
|
|
|
8872
9139
|
throw err;
|
|
8873
9140
|
}
|
|
8874
9141
|
}
|
|
9142
|
+
if (textBlockOpen) {
|
|
9143
|
+
emitTextBlockSnapshot2(true);
|
|
9144
|
+
textBlockOpen = false;
|
|
9145
|
+
}
|
|
9146
|
+
annotateSuggestions(contentBlocks);
|
|
8875
9147
|
if (signal?.aborted) {
|
|
8876
9148
|
statusWatcher.stop();
|
|
8877
9149
|
if (contentBlocks.length > 0) {
|
|
@@ -9145,7 +9417,7 @@ async function runTurn(params) {
|
|
|
9145
9417
|
// src/headless/attachments.ts
|
|
9146
9418
|
import { mkdirSync, existsSync } from "fs";
|
|
9147
9419
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
9148
|
-
import { basename as basename2, join as
|
|
9420
|
+
import { basename as basename2, join as join5, extname as extname3 } from "path";
|
|
9149
9421
|
var log16 = createLogger("headless:attachments");
|
|
9150
9422
|
var UPLOADS_DIR = "src/.user-uploads";
|
|
9151
9423
|
function filenameFromUrl(url) {
|
|
@@ -9158,11 +9430,11 @@ function filenameFromUrl(url) {
|
|
|
9158
9430
|
}
|
|
9159
9431
|
}
|
|
9160
9432
|
function resolveUniqueFilename(name, claimed) {
|
|
9161
|
-
const isFree = (candidate) => !claimed.has(candidate) && !existsSync(
|
|
9433
|
+
const isFree = (candidate) => !claimed.has(candidate) && !existsSync(join5(UPLOADS_DIR, candidate));
|
|
9162
9434
|
if (isFree(name)) {
|
|
9163
9435
|
return name;
|
|
9164
9436
|
}
|
|
9165
|
-
const ext =
|
|
9437
|
+
const ext = extname3(name);
|
|
9166
9438
|
const base = name.slice(0, name.length - ext.length);
|
|
9167
9439
|
let counter = 1;
|
|
9168
9440
|
while (!isFree(`${base}-${counter}${ext}`)) {
|
|
@@ -9173,7 +9445,7 @@ function resolveUniqueFilename(name, claimed) {
|
|
|
9173
9445
|
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
|
|
9174
9446
|
function isImageAttachment(att) {
|
|
9175
9447
|
const name = att.filename || filenameFromUrl(att.url);
|
|
9176
|
-
return IMAGE_EXTENSIONS.has(
|
|
9448
|
+
return IMAGE_EXTENSIONS.has(extname3(name).toLowerCase());
|
|
9177
9449
|
}
|
|
9178
9450
|
async function persistAttachments(attachments) {
|
|
9179
9451
|
const nonVoice = attachments.filter((a) => !a.isVoice);
|
|
@@ -9193,7 +9465,7 @@ async function persistAttachments(attachments) {
|
|
|
9193
9465
|
const results = await Promise.allSettled(
|
|
9194
9466
|
nonVoice.map(async (att, i) => {
|
|
9195
9467
|
const name = names[i];
|
|
9196
|
-
const localPath =
|
|
9468
|
+
const localPath = join5(UPLOADS_DIR, name);
|
|
9197
9469
|
const res = await fetch(att.url, {
|
|
9198
9470
|
signal: AbortSignal.timeout(3e4)
|
|
9199
9471
|
});
|
|
@@ -10002,6 +10274,13 @@ var HeadlessSession = class {
|
|
|
10002
10274
|
rid
|
|
10003
10275
|
);
|
|
10004
10276
|
return;
|
|
10277
|
+
case "text_block":
|
|
10278
|
+
this.emit(
|
|
10279
|
+
"text_block",
|
|
10280
|
+
{ text: e.text, suggestions: e.suggestions },
|
|
10281
|
+
rid
|
|
10282
|
+
);
|
|
10283
|
+
return;
|
|
10005
10284
|
case "thinking":
|
|
10006
10285
|
this.emit(
|
|
10007
10286
|
"thinking",
|