@mindstudio-ai/remy 0.1.274 → 0.1.276
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 +332 -70
- package/dist/index.js +354 -79
- package/dist/prompt/static/team.md +1 -1
- package/dist/subagents/designExpert/prompts/images.md +11 -1
- package/dist/subagents/designExpert/prompts/instructions.md +1 -0
- package/dist/subagents/designExpert/tools/images/enhance-image-prompt.md +7 -3
- 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",
|
|
@@ -692,9 +692,9 @@ function parseFrontmatter(content) {
|
|
|
692
692
|
}
|
|
693
693
|
const fields = {};
|
|
694
694
|
for (const line of match[1].split("\n")) {
|
|
695
|
-
const
|
|
696
|
-
if (
|
|
697
|
-
fields[line.slice(0,
|
|
695
|
+
const sep2 = line.indexOf(":");
|
|
696
|
+
if (sep2 > 0) {
|
|
697
|
+
fields[line.slice(0, sep2).trim()] = line.slice(sep2 + 1).trim();
|
|
698
698
|
}
|
|
699
699
|
}
|
|
700
700
|
return fields;
|
|
@@ -1663,7 +1663,7 @@ function formatCliResult(r) {
|
|
|
1663
1663
|
return logBlock + body + truncNote;
|
|
1664
1664
|
}
|
|
1665
1665
|
function runCli(command, args, options) {
|
|
1666
|
-
return new Promise((
|
|
1666
|
+
return new Promise((resolve4) => {
|
|
1667
1667
|
const timeout = options?.timeout ?? 6e4;
|
|
1668
1668
|
const maxBuffer = options?.maxBuffer ?? 1024 * 1024;
|
|
1669
1669
|
let finalArgs = args;
|
|
@@ -1692,7 +1692,7 @@ function runCli(command, args, options) {
|
|
|
1692
1692
|
if (killTimer) {
|
|
1693
1693
|
clearTimeout(killTimer);
|
|
1694
1694
|
}
|
|
1695
|
-
|
|
1695
|
+
resolve4(result);
|
|
1696
1696
|
};
|
|
1697
1697
|
const child = spawn(command, finalArgs, {
|
|
1698
1698
|
stdio: [options?.stdin ? "pipe" : "ignore", "pipe", "pipe"]
|
|
@@ -2389,7 +2389,7 @@ var bashTool = {
|
|
|
2389
2389
|
async execute(input, context) {
|
|
2390
2390
|
const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES2;
|
|
2391
2391
|
const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
|
|
2392
|
-
return new Promise((
|
|
2392
|
+
return new Promise((resolve4) => {
|
|
2393
2393
|
const child = spawn2("sh", ["-c", input.command], {
|
|
2394
2394
|
// Pinned rather than inherited. `undefined` here means "wherever the
|
|
2395
2395
|
// process happens to be", which is the project root only by luck.
|
|
@@ -2414,9 +2414,9 @@ var bashTool = {
|
|
|
2414
2414
|
clearTimeout(timer);
|
|
2415
2415
|
if (!output) {
|
|
2416
2416
|
if (code && code !== 0) {
|
|
2417
|
-
|
|
2417
|
+
resolve4(`Error: process exited with code ${code}`);
|
|
2418
2418
|
} else {
|
|
2419
|
-
|
|
2419
|
+
resolve4("(no output)");
|
|
2420
2420
|
}
|
|
2421
2421
|
return;
|
|
2422
2422
|
}
|
|
@@ -2442,18 +2442,18 @@ var bashTool = {
|
|
|
2442
2442
|
`${(MAX_OUTPUT_BYTES / 1024).toFixed(0)}KB of ${(totalBytes / 1024).toFixed(0)}KB`
|
|
2443
2443
|
);
|
|
2444
2444
|
}
|
|
2445
|
-
|
|
2445
|
+
resolve4(
|
|
2446
2446
|
truncated + `
|
|
2447
2447
|
|
|
2448
2448
|
(truncated at ${reasons.join(" / ")} \u2014 narrow the command (grep, head/tail, smaller paths) instead of increasing limits)`
|
|
2449
2449
|
);
|
|
2450
2450
|
} else {
|
|
2451
|
-
|
|
2451
|
+
resolve4(output);
|
|
2452
2452
|
}
|
|
2453
2453
|
});
|
|
2454
2454
|
child.on("error", (err) => {
|
|
2455
2455
|
clearTimeout(timer);
|
|
2456
|
-
|
|
2456
|
+
resolve4(`Error: ${err.message}`);
|
|
2457
2457
|
});
|
|
2458
2458
|
});
|
|
2459
2459
|
}
|
|
@@ -2570,17 +2570,17 @@ var grepTool = {
|
|
|
2570
2570
|
}
|
|
2571
2571
|
const rgCmd = `rg ${rgFlags}${globFlag} '${escaped}' ${searchPath}`;
|
|
2572
2572
|
const grepCmd = `grep ${grepFlags} '${escaped}' ${searchPath} --include='*.ts' --include='*.tsx' --include='*.js' --include='*.json' --include='*.md'`;
|
|
2573
|
-
return new Promise((
|
|
2573
|
+
return new Promise((resolve4) => {
|
|
2574
2574
|
exec(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
|
|
2575
2575
|
if (stdout?.trim()) {
|
|
2576
|
-
|
|
2576
|
+
resolve4(formatResults(stdout, max, mode));
|
|
2577
2577
|
return;
|
|
2578
2578
|
}
|
|
2579
2579
|
exec(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
|
|
2580
2580
|
if (grepStdout?.trim()) {
|
|
2581
|
-
|
|
2581
|
+
resolve4(formatResults(grepStdout, max, mode));
|
|
2582
2582
|
} else {
|
|
2583
|
-
|
|
2583
|
+
resolve4("No matches found.");
|
|
2584
2584
|
}
|
|
2585
2585
|
});
|
|
2586
2586
|
});
|
|
@@ -2896,7 +2896,7 @@ var restartProcessTool = {
|
|
|
2896
2896
|
async execute(input) {
|
|
2897
2897
|
const data = await lspRequest("/restart-process", { name: input.name });
|
|
2898
2898
|
if (data.ok) {
|
|
2899
|
-
await new Promise((
|
|
2899
|
+
await new Promise((resolve4) => setTimeout(resolve4, 5e3));
|
|
2900
2900
|
return `Restarted ${input.name}.`;
|
|
2901
2901
|
}
|
|
2902
2902
|
return `Error: unexpected response: ${JSON.stringify(data)}`;
|
|
@@ -3123,6 +3123,26 @@ ${opts.styleMap}
|
|
|
3123
3123
|
${ANALYSIS_RESPONSE_FORMAT}`;
|
|
3124
3124
|
return p;
|
|
3125
3125
|
}
|
|
3126
|
+
async function renderHtmlViaSidecar(opts) {
|
|
3127
|
+
const result = await sidecarRequest(
|
|
3128
|
+
"/render-html",
|
|
3129
|
+
{
|
|
3130
|
+
html: opts.html,
|
|
3131
|
+
width: opts.width,
|
|
3132
|
+
height: opts.height,
|
|
3133
|
+
...opts.transparent ? { transparent: true } : {},
|
|
3134
|
+
...opts.scale != null ? { scale: opts.scale } : {}
|
|
3135
|
+
},
|
|
3136
|
+
{ timeout: VIEWPORT_CAPTURE_TIMEOUT_MS }
|
|
3137
|
+
);
|
|
3138
|
+
const url = result?.url;
|
|
3139
|
+
if (!url) {
|
|
3140
|
+
throw new Error(
|
|
3141
|
+
`No URL in sidecar render response. The browser may not be ready yet. Response: ${JSON.stringify(result)}`
|
|
3142
|
+
);
|
|
3143
|
+
}
|
|
3144
|
+
return { url, width: result.width, height: result.height };
|
|
3145
|
+
}
|
|
3126
3146
|
async function streamScreenshotAnalysis(opts) {
|
|
3127
3147
|
const { image, prompt, styleMap, onLog, model, apiConfig } = opts;
|
|
3128
3148
|
const url = await resolveImageRef(image, apiConfig);
|
|
@@ -3301,18 +3321,70 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
|
3301
3321
|
}
|
|
3302
3322
|
};
|
|
3303
3323
|
|
|
3304
|
-
// src/
|
|
3324
|
+
// src/historyLimits.ts
|
|
3305
3325
|
var MAX_TOOL_RESULT_BYTES = 256 * 1024;
|
|
3306
|
-
function capToolResult(result) {
|
|
3326
|
+
function capToolResult(result, maxBytes = MAX_TOOL_RESULT_BYTES) {
|
|
3307
3327
|
const total = Buffer.byteLength(result, "utf-8");
|
|
3308
|
-
if (total <=
|
|
3328
|
+
if (total <= maxBytes) {
|
|
3309
3329
|
return result;
|
|
3310
3330
|
}
|
|
3311
|
-
const head = Buffer.from(result, "utf-8").subarray(0,
|
|
3331
|
+
const head = Buffer.from(result, "utf-8").subarray(0, maxBytes).toString("utf-8");
|
|
3312
3332
|
return head + `
|
|
3313
3333
|
|
|
3314
|
-
(tool result truncated at ${(
|
|
3334
|
+
(tool result truncated at ${(maxBytes / 1024).toFixed(0)}KB of ${(total / 1024).toFixed(0)}KB \u2014 too large to keep in context. Narrow the call (select fewer fields, paginate, or query a subset) instead of fetching everything.)`;
|
|
3335
|
+
}
|
|
3336
|
+
var MAX_SUBAGENT_RESULT_BYTES = 32 * 1024;
|
|
3337
|
+
var MAX_SUBAGENT_TRANSCRIPT_BYTES = 512 * 1024;
|
|
3338
|
+
function capSubAgentTranscript(messages) {
|
|
3339
|
+
for (const msg of messages) {
|
|
3340
|
+
capMessageForHistory(msg, MAX_SUBAGENT_RESULT_BYTES);
|
|
3341
|
+
}
|
|
3342
|
+
const sizes = messages.map(
|
|
3343
|
+
(m) => Buffer.byteLength(JSON.stringify(m), "utf-8") + 1
|
|
3344
|
+
);
|
|
3345
|
+
let total = sizes.reduce((a, b) => a + b, 0);
|
|
3346
|
+
if (total <= MAX_SUBAGENT_TRANSCRIPT_BYTES) {
|
|
3347
|
+
return messages;
|
|
3348
|
+
}
|
|
3349
|
+
let start = 0;
|
|
3350
|
+
while (start < messages.length - 1 && total > MAX_SUBAGENT_TRANSCRIPT_BYTES) {
|
|
3351
|
+
total -= sizes[start];
|
|
3352
|
+
start++;
|
|
3353
|
+
}
|
|
3354
|
+
while (start < messages.length - 1 && messages[start].role === "user" && messages[start].toolCallId) {
|
|
3355
|
+
start++;
|
|
3356
|
+
}
|
|
3357
|
+
return messages.slice(start);
|
|
3315
3358
|
}
|
|
3359
|
+
function capMessageForHistory(msg, maxBytes = MAX_TOOL_RESULT_BYTES) {
|
|
3360
|
+
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
3361
|
+
for (const block of msg.content) {
|
|
3362
|
+
if (block.type !== "tool") {
|
|
3363
|
+
continue;
|
|
3364
|
+
}
|
|
3365
|
+
if (typeof block.result === "string") {
|
|
3366
|
+
block.result = capToolResult(block.result, maxBytes);
|
|
3367
|
+
}
|
|
3368
|
+
if (typeof block.backgroundResult === "string") {
|
|
3369
|
+
block.backgroundResult = capToolResult(
|
|
3370
|
+
block.backgroundResult,
|
|
3371
|
+
maxBytes
|
|
3372
|
+
);
|
|
3373
|
+
}
|
|
3374
|
+
if (Array.isArray(block.subAgentMessages)) {
|
|
3375
|
+
block.subAgentMessages = capSubAgentTranscript(block.subAgentMessages);
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
} else if (msg.role === "user" && msg.toolCallId && typeof msg.content === "string") {
|
|
3379
|
+
msg.content = capToolResult(msg.content, maxBytes);
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
var HISTORY_PAGE_MAX_BYTES = 2 * 1024 * 1024;
|
|
3383
|
+
var HISTORY_DEFAULT_LIMIT = 500;
|
|
3384
|
+
var HISTORY_MAX_LIMIT = 2e3;
|
|
3385
|
+
var ROTATE_THRESHOLD_BYTES = 32 * 1024 * 1024;
|
|
3386
|
+
var RETAIN_TAIL_BYTES = 16 * 1024 * 1024;
|
|
3387
|
+
var ARCHIVE_RETENTION_BYTES = 64 * 1024 * 1024;
|
|
3316
3388
|
|
|
3317
3389
|
// src/statusWatcher.ts
|
|
3318
3390
|
var INTERNAL_PAYLOAD_MARKERS = [
|
|
@@ -3944,7 +4016,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3944
4016
|
block.completedAt = Date.now();
|
|
3945
4017
|
const innerMsgs = subAgentMessages.get(r.id);
|
|
3946
4018
|
if (innerMsgs) {
|
|
3947
|
-
block.subAgentMessages = innerMsgs;
|
|
4019
|
+
block.subAgentMessages = capSubAgentTranscript(innerMsgs);
|
|
3948
4020
|
}
|
|
3949
4021
|
if (captureArtifacts?.includes(block.name) && !r.isError) {
|
|
3950
4022
|
try {
|
|
@@ -4210,10 +4282,10 @@ function parseFrontmatter2(filePath) {
|
|
|
4210
4282
|
}
|
|
4211
4283
|
const fm = {};
|
|
4212
4284
|
for (const line of match[1].split("\n")) {
|
|
4213
|
-
const
|
|
4214
|
-
if (
|
|
4215
|
-
const key = line.slice(0,
|
|
4216
|
-
const val = line.slice(
|
|
4285
|
+
const sep2 = line.indexOf(":");
|
|
4286
|
+
if (sep2 > 0) {
|
|
4287
|
+
const key = line.slice(0, sep2).trim();
|
|
4288
|
+
const val = line.slice(sep2 + 1).trim();
|
|
4217
4289
|
fm[key] = val;
|
|
4218
4290
|
}
|
|
4219
4291
|
}
|
|
@@ -4517,7 +4589,7 @@ var browserAutomationTool = {
|
|
|
4517
4589
|
// src/tools/code/screenshot.ts
|
|
4518
4590
|
var screenshotDefinition = {
|
|
4519
4591
|
name: "screenshot",
|
|
4520
|
-
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
|
|
4592
|
+
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.",
|
|
4521
4593
|
inputSchema: {
|
|
4522
4594
|
type: "object",
|
|
4523
4595
|
properties: {
|
|
@@ -4548,7 +4620,7 @@ var screenshotDefinition = {
|
|
|
4548
4620
|
format: {
|
|
4549
4621
|
type: "string",
|
|
4550
4622
|
enum: ["png", "jpeg"],
|
|
4551
|
-
description: "Output image format. Defaults to 'jpeg'. Use 'png' for crisp flat graphics
|
|
4623
|
+
description: "Output image format. Defaults to 'jpeg'. Use 'png' for crisp flat graphics where JPEG artifacts show on sharp type and edges."
|
|
4552
4624
|
},
|
|
4553
4625
|
instructions: {
|
|
4554
4626
|
type: "string",
|
|
@@ -4870,7 +4942,36 @@ ${brief}
|
|
|
4870
4942
|
}
|
|
4871
4943
|
|
|
4872
4944
|
// src/subagents/designExpert/tools/images/imageGenerator.ts
|
|
4873
|
-
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.';
|
|
4945
|
+
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.';
|
|
4946
|
+
var OPENAI_IMAGE_SIZES = [
|
|
4947
|
+
[768, 1024],
|
|
4948
|
+
[1024, 768],
|
|
4949
|
+
[1024, 1024],
|
|
4950
|
+
[1024, 1536],
|
|
4951
|
+
[1536, 1024],
|
|
4952
|
+
[1280, 2560],
|
|
4953
|
+
[2560, 1280],
|
|
4954
|
+
[1440, 2560],
|
|
4955
|
+
[2560, 1440],
|
|
4956
|
+
[1792, 2400],
|
|
4957
|
+
[2400, 1792],
|
|
4958
|
+
[2400, 2880],
|
|
4959
|
+
[2560, 2560]
|
|
4960
|
+
];
|
|
4961
|
+
function snapToOpenAiImageSize(width, height) {
|
|
4962
|
+
let best = OPENAI_IMAGE_SIZES[0];
|
|
4963
|
+
let bestScore = Infinity;
|
|
4964
|
+
for (const [w, h] of OPENAI_IMAGE_SIZES) {
|
|
4965
|
+
const aspectDiff = Math.abs(Math.log(w / h / (width / height)));
|
|
4966
|
+
const areaDiff = Math.abs(Math.log(w * h / (width * height)));
|
|
4967
|
+
const score = aspectDiff * 8 + areaDiff;
|
|
4968
|
+
if (score < bestScore) {
|
|
4969
|
+
bestScore = score;
|
|
4970
|
+
best = [w, h];
|
|
4971
|
+
}
|
|
4972
|
+
}
|
|
4973
|
+
return `${best[0]}x${best[1]}`;
|
|
4974
|
+
}
|
|
4874
4975
|
async function generateImageAssets(opts) {
|
|
4875
4976
|
const {
|
|
4876
4977
|
prompts,
|
|
@@ -4885,7 +4986,11 @@ async function generateImageAssets(opts) {
|
|
|
4885
4986
|
const sourceImages = opts.sourceImages?.length ? await resolveImageRefs(opts.sourceImages, apiConfig) : void 0;
|
|
4886
4987
|
const width = opts.width || 2048;
|
|
4887
4988
|
const height = opts.height || 2048;
|
|
4888
|
-
const config = {
|
|
4989
|
+
const config = {
|
|
4990
|
+
width,
|
|
4991
|
+
height,
|
|
4992
|
+
size: snapToOpenAiImageSize(width, height)
|
|
4993
|
+
};
|
|
4889
4994
|
if (sourceImages?.length) {
|
|
4890
4995
|
const [firstImage] = sourceImages;
|
|
4891
4996
|
config.images = sourceImages;
|
|
@@ -5137,11 +5242,143 @@ async function execute6(input, onLog, context) {
|
|
|
5137
5242
|
});
|
|
5138
5243
|
}
|
|
5139
5244
|
|
|
5245
|
+
// src/subagents/designExpert/tools/images/renderImage.ts
|
|
5246
|
+
var renderImage_exports = {};
|
|
5247
|
+
__export(renderImage_exports, {
|
|
5248
|
+
definition: () => definition7,
|
|
5249
|
+
execute: () => execute7
|
|
5250
|
+
});
|
|
5251
|
+
import { mkdir, unlink, writeFile } from "fs/promises";
|
|
5252
|
+
import { tmpdir } from "os";
|
|
5253
|
+
import { dirname, join, resolve as resolve2, sep } from "path";
|
|
5254
|
+
import { randomUUID } from "crypto";
|
|
5255
|
+
var MIN_DIMENSION = 16;
|
|
5256
|
+
var MAX_DIMENSION = 4096;
|
|
5257
|
+
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.';
|
|
5258
|
+
var definition7 = {
|
|
5259
|
+
name: "renderImage",
|
|
5260
|
+
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.",
|
|
5261
|
+
inputSchema: {
|
|
5262
|
+
type: "object",
|
|
5263
|
+
properties: {
|
|
5264
|
+
html: {
|
|
5265
|
+
type: "string",
|
|
5266
|
+
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."
|
|
5267
|
+
},
|
|
5268
|
+
width: {
|
|
5269
|
+
type: "number",
|
|
5270
|
+
description: "Viewport width in CSS pixels (e.g. 1200 for an OG card, 512 for an icon tile). Range: 16-4096."
|
|
5271
|
+
},
|
|
5272
|
+
height: {
|
|
5273
|
+
type: "number",
|
|
5274
|
+
description: "Viewport height in CSS pixels. Range: 16-4096."
|
|
5275
|
+
},
|
|
5276
|
+
scale: {
|
|
5277
|
+
type: "number",
|
|
5278
|
+
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)."
|
|
5279
|
+
},
|
|
5280
|
+
transparentBackground: {
|
|
5281
|
+
type: "boolean",
|
|
5282
|
+
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."
|
|
5283
|
+
},
|
|
5284
|
+
savePath: {
|
|
5285
|
+
type: "string",
|
|
5286
|
+
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)."
|
|
5287
|
+
}
|
|
5288
|
+
},
|
|
5289
|
+
required: ["html", "width", "height"]
|
|
5290
|
+
}
|
|
5291
|
+
};
|
|
5292
|
+
async function execute7(input, onLog, context) {
|
|
5293
|
+
const html = typeof input.html === "string" ? input.html : "";
|
|
5294
|
+
const width = Math.round(Number(input.width));
|
|
5295
|
+
const height = Math.round(Number(input.height));
|
|
5296
|
+
if (!html || !Number.isFinite(width) || !Number.isFinite(height) || width < MIN_DIMENSION || width > MAX_DIMENSION || height < MIN_DIMENSION || height > MAX_DIMENSION) {
|
|
5297
|
+
return `Error: renderImage requires an html string plus width/height between ${MIN_DIMENSION} and ${MAX_DIMENSION}.`;
|
|
5298
|
+
}
|
|
5299
|
+
const release = await acquireBrowserLock();
|
|
5300
|
+
let rendered;
|
|
5301
|
+
try {
|
|
5302
|
+
onLog?.("Rendering document in the sandbox browser...");
|
|
5303
|
+
rendered = await renderHtmlViaSidecar({
|
|
5304
|
+
html,
|
|
5305
|
+
width,
|
|
5306
|
+
height,
|
|
5307
|
+
transparent: input.transparentBackground === true,
|
|
5308
|
+
scale: typeof input.scale === "number" ? input.scale : void 0
|
|
5309
|
+
});
|
|
5310
|
+
} catch (err) {
|
|
5311
|
+
return `Error: render failed: ${err?.message ?? err}`;
|
|
5312
|
+
} finally {
|
|
5313
|
+
release();
|
|
5314
|
+
}
|
|
5315
|
+
let bytes;
|
|
5316
|
+
try {
|
|
5317
|
+
const res = await fetch(rendered.url);
|
|
5318
|
+
if (!res.ok) {
|
|
5319
|
+
throw new Error(`fetch returned ${res.status}`);
|
|
5320
|
+
}
|
|
5321
|
+
bytes = Buffer.from(await res.arrayBuffer());
|
|
5322
|
+
} catch (err) {
|
|
5323
|
+
return `Error: rendered but could not download the capture (${err?.message ?? err}). Temporary URL: ${rendered.url}`;
|
|
5324
|
+
}
|
|
5325
|
+
let savedPath;
|
|
5326
|
+
if (typeof input.savePath === "string" && input.savePath) {
|
|
5327
|
+
const absolute = resolve2(PROJECT_ROOT, input.savePath);
|
|
5328
|
+
if (!absolute.startsWith(PROJECT_ROOT + sep)) {
|
|
5329
|
+
return "Error: savePath must resolve inside the project.";
|
|
5330
|
+
}
|
|
5331
|
+
await mkdir(dirname(absolute), { recursive: true });
|
|
5332
|
+
await writeFile(absolute, bytes);
|
|
5333
|
+
savedPath = input.savePath;
|
|
5334
|
+
}
|
|
5335
|
+
onLog?.("Hosting the capture...");
|
|
5336
|
+
const tmpPath = join(tmpdir(), `render-${randomUUID()}.png`);
|
|
5337
|
+
let url = rendered.url;
|
|
5338
|
+
let temporary = true;
|
|
5339
|
+
try {
|
|
5340
|
+
await writeFile(tmpPath, bytes);
|
|
5341
|
+
const upload = await runMindstudioCliResult(["upload", tmpPath], {
|
|
5342
|
+
timeout: 6e4,
|
|
5343
|
+
onLog,
|
|
5344
|
+
caller: "designExpert"
|
|
5345
|
+
});
|
|
5346
|
+
const match = upload.ok ? upload.value.match(/https:\/\/\S+/g) : null;
|
|
5347
|
+
if (match?.length) {
|
|
5348
|
+
url = match[match.length - 1];
|
|
5349
|
+
temporary = false;
|
|
5350
|
+
}
|
|
5351
|
+
} finally {
|
|
5352
|
+
await unlink(tmpPath).catch(() => {
|
|
5353
|
+
});
|
|
5354
|
+
}
|
|
5355
|
+
const analysis = await analyzeImage({
|
|
5356
|
+
prompt: RENDER_ANALYZE_PROMPT,
|
|
5357
|
+
image: url,
|
|
5358
|
+
onLog,
|
|
5359
|
+
model: resolveModel("imageAnalysis", context?.models, context?.model)
|
|
5360
|
+
}).then((r) => r.analysis).catch((err) => `Could not review this image: ${err.message}`);
|
|
5361
|
+
return JSON.stringify({
|
|
5362
|
+
images: [
|
|
5363
|
+
{
|
|
5364
|
+
url,
|
|
5365
|
+
...temporary ? {
|
|
5366
|
+
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."
|
|
5367
|
+
} : {},
|
|
5368
|
+
...savedPath ? { savedPath } : {},
|
|
5369
|
+
analysis,
|
|
5370
|
+
width: rendered.width,
|
|
5371
|
+
height: rendered.height
|
|
5372
|
+
}
|
|
5373
|
+
]
|
|
5374
|
+
});
|
|
5375
|
+
}
|
|
5376
|
+
|
|
5140
5377
|
// src/subagents/designExpert/tools/polishCopy.ts
|
|
5141
5378
|
var polishCopy_exports = {};
|
|
5142
5379
|
__export(polishCopy_exports, {
|
|
5143
|
-
definition: () =>
|
|
5144
|
-
execute: () =>
|
|
5380
|
+
definition: () => definition8,
|
|
5381
|
+
execute: () => execute8
|
|
5145
5382
|
});
|
|
5146
5383
|
|
|
5147
5384
|
// src/subagents/copyEditor/tools.ts
|
|
@@ -5197,7 +5434,7 @@ var copyEditorTool = {
|
|
|
5197
5434
|
};
|
|
5198
5435
|
|
|
5199
5436
|
// src/subagents/designExpert/tools/polishCopy.ts
|
|
5200
|
-
var
|
|
5437
|
+
var definition8 = {
|
|
5201
5438
|
name: "polishCopy",
|
|
5202
5439
|
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).",
|
|
5203
5440
|
inputSchema: {
|
|
@@ -5211,7 +5448,7 @@ var definition7 = {
|
|
|
5211
5448
|
required: ["task"]
|
|
5212
5449
|
}
|
|
5213
5450
|
};
|
|
5214
|
-
async function
|
|
5451
|
+
async function execute8(input, _onLog, context) {
|
|
5215
5452
|
return copyEditorTool.execute(input, context);
|
|
5216
5453
|
}
|
|
5217
5454
|
|
|
@@ -5227,6 +5464,7 @@ var tools = {
|
|
|
5227
5464
|
screenshot: { definition: screenshotDefinition, execute: executeScreenshot },
|
|
5228
5465
|
generateImages: generateImages_exports,
|
|
5229
5466
|
editImages: editImages_exports,
|
|
5467
|
+
renderImage: renderImage_exports,
|
|
5230
5468
|
polishCopy: polishCopy_exports
|
|
5231
5469
|
};
|
|
5232
5470
|
var DESIGN_EXPERT_TOOLS = [
|
|
@@ -5646,13 +5884,13 @@ var PITCH_DECK_SHELL = readAsset(
|
|
|
5646
5884
|
"subagents/productVision",
|
|
5647
5885
|
"pitch-deck-shell.html"
|
|
5648
5886
|
);
|
|
5649
|
-
function
|
|
5887
|
+
function resolve3(filePath) {
|
|
5650
5888
|
return path10.join(ROADMAP_DIR, filePath);
|
|
5651
5889
|
}
|
|
5652
5890
|
async function executeVisionTool(name, input, context) {
|
|
5653
5891
|
switch (name) {
|
|
5654
5892
|
case "writeFile": {
|
|
5655
|
-
const filePath =
|
|
5893
|
+
const filePath = resolve3(input.path);
|
|
5656
5894
|
try {
|
|
5657
5895
|
fs19.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
5658
5896
|
let oldContent = null;
|
|
@@ -5670,7 +5908,7 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
|
|
|
5670
5908
|
}
|
|
5671
5909
|
}
|
|
5672
5910
|
case "deleteFile": {
|
|
5673
|
-
const filePath =
|
|
5911
|
+
const filePath = resolve3(input.path);
|
|
5674
5912
|
try {
|
|
5675
5913
|
if (!fs19.existsSync(filePath)) {
|
|
5676
5914
|
return `Error: ${filePath} does not exist`;
|
|
@@ -5687,7 +5925,7 @@ ${unifiedDiff(filePath, oldContent, "")}`;
|
|
|
5687
5925
|
if (!context) {
|
|
5688
5926
|
return "Error: writePitchDeck requires execution context for design expert delegation";
|
|
5689
5927
|
}
|
|
5690
|
-
const filePath =
|
|
5928
|
+
const filePath = resolve3("pitch.html");
|
|
5691
5929
|
try {
|
|
5692
5930
|
fs19.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
5693
5931
|
const exists = fs19.existsSync(filePath);
|
|
@@ -6677,14 +6915,9 @@ import path11 from "path";
|
|
|
6677
6915
|
var log10 = createLogger("session");
|
|
6678
6916
|
var SESSION_FILE = ".remy-session.json";
|
|
6679
6917
|
var ARCHIVE_DIR = ".logs/sessions";
|
|
6680
|
-
var ROTATE_THRESHOLD_BYTES = 32 * 1024 * 1024;
|
|
6681
|
-
var RETAIN_TAIL_BYTES = 16 * 1024 * 1024;
|
|
6682
|
-
var ARCHIVE_RETENTION_BYTES = 64 * 1024 * 1024;
|
|
6683
6918
|
var ARCHIVE_NAME_RE = /^(cleared|rotated)-.*\.json$/;
|
|
6684
6919
|
var archiveSortKey = (name) => name.replace(/^(cleared|rotated)-/, "");
|
|
6685
6920
|
var ARCHIVE_COUNT_RE = /\.c(\d+)\.json$/;
|
|
6686
|
-
var HISTORY_DEFAULT_LIMIT = 500;
|
|
6687
|
-
var HISTORY_MAX_LIMIT = 2e3;
|
|
6688
6921
|
var archiveCountCache = /* @__PURE__ */ new Map();
|
|
6689
6922
|
var archiveMsgCache = /* @__PURE__ */ new Map();
|
|
6690
6923
|
var ARCHIVE_MSG_CACHE_MAX = 3;
|
|
@@ -6714,22 +6947,11 @@ function loadSession(state) {
|
|
|
6714
6947
|
}
|
|
6715
6948
|
return false;
|
|
6716
6949
|
}
|
|
6717
|
-
function capOversizedResults(msg) {
|
|
6718
|
-
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
6719
|
-
for (const block of msg.content) {
|
|
6720
|
-
if (block.type === "tool" && typeof block.result === "string") {
|
|
6721
|
-
block.result = capToolResult(block.result);
|
|
6722
|
-
}
|
|
6723
|
-
}
|
|
6724
|
-
} else if (msg.role === "user" && msg.toolCallId && typeof msg.content === "string") {
|
|
6725
|
-
msg.content = capToolResult(msg.content);
|
|
6726
|
-
}
|
|
6727
|
-
}
|
|
6728
6950
|
function sanitizeMessages(messages) {
|
|
6729
6951
|
const result = [];
|
|
6730
6952
|
for (let i = 0; i < messages.length; i++) {
|
|
6731
6953
|
const msg = messages[i];
|
|
6732
|
-
|
|
6954
|
+
capMessageForHistory(msg);
|
|
6733
6955
|
result.push(msg);
|
|
6734
6956
|
if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
|
|
6735
6957
|
continue;
|
|
@@ -6841,6 +7063,9 @@ function parseArchive(name) {
|
|
|
6841
7063
|
const raw = fs21.readFileSync(path11.join(ARCHIVE_DIR, name), "utf-8");
|
|
6842
7064
|
const data = JSON.parse(raw);
|
|
6843
7065
|
const messages = Array.isArray(data?.messages) ? data.messages : [];
|
|
7066
|
+
for (const msg of messages) {
|
|
7067
|
+
capMessageForHistory(msg);
|
|
7068
|
+
}
|
|
6844
7069
|
archiveCountCache.set(name, messages.length);
|
|
6845
7070
|
archiveMsgCache.set(name, messages);
|
|
6846
7071
|
while (archiveMsgCache.size > ARCHIVE_MSG_CACHE_MAX) {
|
|
@@ -6951,6 +7176,29 @@ function getHistoryPage(state, opts) {
|
|
|
6951
7176
|
messages.push(state.messages[i]);
|
|
6952
7177
|
}
|
|
6953
7178
|
}
|
|
7179
|
+
if (messages.length > 1) {
|
|
7180
|
+
let bytes = 0;
|
|
7181
|
+
let cut = 0;
|
|
7182
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
7183
|
+
bytes += Buffer.byteLength(JSON.stringify(messages[i]), "utf-8") + 1;
|
|
7184
|
+
if (bytes > HISTORY_PAGE_MAX_BYTES && i < messages.length - 1) {
|
|
7185
|
+
cut = i + 1;
|
|
7186
|
+
break;
|
|
7187
|
+
}
|
|
7188
|
+
}
|
|
7189
|
+
if (cut > 0) {
|
|
7190
|
+
while (cut < messages.length - 1 && messages[cut]?.role === "user" && messages[cut]?.toolCallId) {
|
|
7191
|
+
cut++;
|
|
7192
|
+
}
|
|
7193
|
+
messages.splice(0, cut);
|
|
7194
|
+
startIndex += cut;
|
|
7195
|
+
log10.info("History page trimmed to byte budget", {
|
|
7196
|
+
dropped: cut,
|
|
7197
|
+
kept: messages.length,
|
|
7198
|
+
startIndex
|
|
7199
|
+
});
|
|
7200
|
+
}
|
|
7201
|
+
}
|
|
6954
7202
|
return { messages, startIndex, endIndex, totalMessageCount: total };
|
|
6955
7203
|
}
|
|
6956
7204
|
function rotate(state) {
|
|
@@ -7298,14 +7546,14 @@ function buildCorpus() {
|
|
|
7298
7546
|
});
|
|
7299
7547
|
}
|
|
7300
7548
|
}
|
|
7301
|
-
const
|
|
7549
|
+
const sep2 = "\n\n---\n\n";
|
|
7302
7550
|
const sections = [];
|
|
7303
7551
|
let usedChars = 0;
|
|
7304
7552
|
for (const { path: p, content } of files) {
|
|
7305
7553
|
const section = `## File: ${p}
|
|
7306
7554
|
|
|
7307
7555
|
${content}`;
|
|
7308
|
-
const added = section.length + (sections.length > 0 ?
|
|
7556
|
+
const added = section.length + (sections.length > 0 ? sep2.length : 0);
|
|
7309
7557
|
if (sections.length > 0 && usedChars + added > BRAND_CORPUS_CHAR_LIMIT) {
|
|
7310
7558
|
sections.push(
|
|
7311
7559
|
`(brand corpus truncated: included ${sections.length} of ${files.length} files, ~${(usedChars / 1024).toFixed(0)}KB; brand-relevant files were prioritized.)`
|
|
@@ -7315,7 +7563,7 @@ ${content}`;
|
|
|
7315
7563
|
sections.push(section);
|
|
7316
7564
|
usedChars += added;
|
|
7317
7565
|
}
|
|
7318
|
-
return sections.join(
|
|
7566
|
+
return sections.join(sep2);
|
|
7319
7567
|
}
|
|
7320
7568
|
function headSlice(content) {
|
|
7321
7569
|
if (content.length <= HEAD_SLICE_CHARS) {
|
|
@@ -7845,6 +8093,8 @@ async function runTurn(params) {
|
|
|
7845
8093
|
let lastCallInputTokens = 0;
|
|
7846
8094
|
let lastCallCacheCreation = 0;
|
|
7847
8095
|
let lastCallCacheRead = 0;
|
|
8096
|
+
let abnormalStopRecoveries = 0;
|
|
8097
|
+
const MAX_ABNORMAL_STOP_RECOVERIES = 2;
|
|
7848
8098
|
const statusWatcher = isFirstMessage ? { stop() {
|
|
7849
8099
|
}, pause() {
|
|
7850
8100
|
}, resume() {
|
|
@@ -8188,6 +8438,18 @@ async function runTurn(params) {
|
|
|
8188
8438
|
});
|
|
8189
8439
|
}
|
|
8190
8440
|
const toolCalls = getToolCalls(contentBlocks);
|
|
8441
|
+
if (toolCalls.length === 0 && (stopReason === "repetition" || stopReason === "max_tokens") && abnormalStopRecoveries < MAX_ABNORMAL_STOP_RECOVERIES && !signal?.aborted) {
|
|
8442
|
+
abnormalStopRecoveries++;
|
|
8443
|
+
log14.warn("Abnormal stop \u2014 nudging model to continue", {
|
|
8444
|
+
requestId,
|
|
8445
|
+
stopReason,
|
|
8446
|
+
attempt: abnormalStopRecoveries
|
|
8447
|
+
});
|
|
8448
|
+
const nudge = "Your previous response was cut off \u2014 it degenerated into repeated text or hit the output limit, and the repeated portion was removed. Reassess where you are in the task and continue from where you left off. Prefer a tool call over restating what you were about to do.";
|
|
8449
|
+
state.messages.push({ role: "user", content: nudge, hidden: true });
|
|
8450
|
+
onEvent({ type: "user_message", text: nudge, hidden: true });
|
|
8451
|
+
continue;
|
|
8452
|
+
}
|
|
8191
8453
|
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
8192
8454
|
statusWatcher.stop();
|
|
8193
8455
|
saveSession(state);
|
|
@@ -8354,7 +8616,7 @@ async function runTurn(params) {
|
|
|
8354
8616
|
block.completedAt = Date.now();
|
|
8355
8617
|
const msgs = subAgentMessages.get(r.id);
|
|
8356
8618
|
if (msgs) {
|
|
8357
|
-
block.subAgentMessages = msgs;
|
|
8619
|
+
block.subAgentMessages = capSubAgentTranscript(msgs);
|
|
8358
8620
|
}
|
|
8359
8621
|
}
|
|
8360
8622
|
}
|
|
@@ -8398,8 +8660,8 @@ async function runTurn(params) {
|
|
|
8398
8660
|
|
|
8399
8661
|
// src/headless/attachments.ts
|
|
8400
8662
|
import { mkdirSync, existsSync } from "fs";
|
|
8401
|
-
import { writeFile } from "fs/promises";
|
|
8402
|
-
import { basename as basename2, join, extname as extname2 } from "path";
|
|
8663
|
+
import { writeFile as writeFile2 } from "fs/promises";
|
|
8664
|
+
import { basename as basename2, join as join2, extname as extname2 } from "path";
|
|
8403
8665
|
var log15 = createLogger("headless:attachments");
|
|
8404
8666
|
var UPLOADS_DIR = "src/.user-uploads";
|
|
8405
8667
|
function filenameFromUrl(url) {
|
|
@@ -8412,7 +8674,7 @@ function filenameFromUrl(url) {
|
|
|
8412
8674
|
}
|
|
8413
8675
|
}
|
|
8414
8676
|
function resolveUniqueFilename(name, claimed) {
|
|
8415
|
-
const isFree = (candidate) => !claimed.has(candidate) && !existsSync(
|
|
8677
|
+
const isFree = (candidate) => !claimed.has(candidate) && !existsSync(join2(UPLOADS_DIR, candidate));
|
|
8416
8678
|
if (isFree(name)) {
|
|
8417
8679
|
return name;
|
|
8418
8680
|
}
|
|
@@ -8447,7 +8709,7 @@ async function persistAttachments(attachments) {
|
|
|
8447
8709
|
const results = await Promise.allSettled(
|
|
8448
8710
|
nonVoice.map(async (att, i) => {
|
|
8449
8711
|
const name = names[i];
|
|
8450
|
-
const localPath =
|
|
8712
|
+
const localPath = join2(UPLOADS_DIR, name);
|
|
8451
8713
|
const res = await fetch(att.url, {
|
|
8452
8714
|
signal: AbortSignal.timeout(3e4)
|
|
8453
8715
|
});
|
|
@@ -8455,7 +8717,7 @@ async function persistAttachments(attachments) {
|
|
|
8455
8717
|
throw new Error(`HTTP ${res.status} downloading ${att.url}`);
|
|
8456
8718
|
}
|
|
8457
8719
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
8458
|
-
await
|
|
8720
|
+
await writeFile2(localPath, buffer);
|
|
8459
8721
|
log15.info("Attachment saved", {
|
|
8460
8722
|
filename: name,
|
|
8461
8723
|
path: localPath,
|
|
@@ -8469,7 +8731,7 @@ async function persistAttachments(attachments) {
|
|
|
8469
8731
|
});
|
|
8470
8732
|
if (textRes.ok) {
|
|
8471
8733
|
extractedTextPath = `${localPath}.txt`;
|
|
8472
|
-
await
|
|
8734
|
+
await writeFile2(extractedTextPath, await textRes.text(), "utf-8");
|
|
8473
8735
|
log15.info("Extracted text saved", { path: extractedTextPath });
|
|
8474
8736
|
}
|
|
8475
8737
|
} catch {
|
|
@@ -9056,17 +9318,17 @@ var HeadlessSession = class {
|
|
|
9056
9318
|
return Promise.resolve(early);
|
|
9057
9319
|
}
|
|
9058
9320
|
const shouldTimeout = !USER_FACING_TOOLS.has(name);
|
|
9059
|
-
return new Promise((
|
|
9321
|
+
return new Promise((resolve4) => {
|
|
9060
9322
|
const timeout = shouldTimeout ? setTimeout(() => {
|
|
9061
9323
|
this.pendingTools.delete(id);
|
|
9062
|
-
|
|
9324
|
+
resolve4(
|
|
9063
9325
|
"Error: Tool timed out \u2014 no response from the app environment after 5 minutes."
|
|
9064
9326
|
);
|
|
9065
9327
|
}, EXTERNAL_TOOL_TIMEOUT_MS) : void 0;
|
|
9066
9328
|
this.pendingTools.set(id, {
|
|
9067
9329
|
resolve: (result) => {
|
|
9068
9330
|
clearTimeout(timeout);
|
|
9069
|
-
|
|
9331
|
+
resolve4(result);
|
|
9070
9332
|
},
|
|
9071
9333
|
timeout
|
|
9072
9334
|
});
|