@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/index.js CHANGED
@@ -270,7 +270,7 @@ function isRetryableError(error) {
270
270
  /Unable to download/i.test(error);
271
271
  }
272
272
  function sleep(ms) {
273
- return new Promise((resolve3) => setTimeout(resolve3, ms));
273
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
274
274
  }
275
275
  async function* streamChatWithRetry(params, options) {
276
276
  for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
@@ -1203,7 +1203,7 @@ function formatCliResult(r) {
1203
1203
  return logBlock + body + truncNote;
1204
1204
  }
1205
1205
  function runCli(command, args2, options) {
1206
- return new Promise((resolve3) => {
1206
+ return new Promise((resolve4) => {
1207
1207
  const timeout = options?.timeout ?? 6e4;
1208
1208
  const maxBuffer = options?.maxBuffer ?? 1024 * 1024;
1209
1209
  let finalArgs = args2;
@@ -1232,7 +1232,7 @@ function runCli(command, args2, options) {
1232
1232
  if (killTimer) {
1233
1233
  clearTimeout(killTimer);
1234
1234
  }
1235
- resolve3(result);
1235
+ resolve4(result);
1236
1236
  };
1237
1237
  const child = spawn(command, finalArgs, {
1238
1238
  stdio: [options?.stdin ? "pipe" : "ignore", "pipe", "pipe"]
@@ -2173,7 +2173,7 @@ var init_surfaces = __esm({
2173
2173
  userPickable: true
2174
2174
  },
2175
2175
  imageGeneration: {
2176
- default: "seedream-4.5",
2176
+ default: "gpt-image-2",
2177
2177
  label: "Image Generation",
2178
2178
  description: "Creates images for your product \u2014 icons, illustrations, photos, and any other visual assets.",
2179
2179
  modelType: "image_generation",
@@ -2409,22 +2409,74 @@ var init_cleanMessages = __esm({
2409
2409
  }
2410
2410
  });
2411
2411
 
2412
- // src/toolResultCap.ts
2413
- function capToolResult(result) {
2412
+ // src/historyLimits.ts
2413
+ function capToolResult(result, maxBytes = MAX_TOOL_RESULT_BYTES) {
2414
2414
  const total = Buffer.byteLength(result, "utf-8");
2415
- if (total <= MAX_TOOL_RESULT_BYTES) {
2415
+ if (total <= maxBytes) {
2416
2416
  return result;
2417
2417
  }
2418
- const head = Buffer.from(result, "utf-8").subarray(0, MAX_TOOL_RESULT_BYTES).toString("utf-8");
2418
+ const head = Buffer.from(result, "utf-8").subarray(0, maxBytes).toString("utf-8");
2419
2419
  return head + `
2420
2420
 
2421
- (tool result truncated at ${(MAX_TOOL_RESULT_BYTES / 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.)`;
2421
+ (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.)`;
2422
2422
  }
2423
- var MAX_TOOL_RESULT_BYTES;
2424
- var init_toolResultCap = __esm({
2425
- "src/toolResultCap.ts"() {
2423
+ function capSubAgentTranscript(messages) {
2424
+ for (const msg of messages) {
2425
+ capMessageForHistory(msg, MAX_SUBAGENT_RESULT_BYTES);
2426
+ }
2427
+ const sizes = messages.map(
2428
+ (m) => Buffer.byteLength(JSON.stringify(m), "utf-8") + 1
2429
+ );
2430
+ let total = sizes.reduce((a, b) => a + b, 0);
2431
+ if (total <= MAX_SUBAGENT_TRANSCRIPT_BYTES) {
2432
+ return messages;
2433
+ }
2434
+ let start = 0;
2435
+ while (start < messages.length - 1 && total > MAX_SUBAGENT_TRANSCRIPT_BYTES) {
2436
+ total -= sizes[start];
2437
+ start++;
2438
+ }
2439
+ while (start < messages.length - 1 && messages[start].role === "user" && messages[start].toolCallId) {
2440
+ start++;
2441
+ }
2442
+ return messages.slice(start);
2443
+ }
2444
+ function capMessageForHistory(msg, maxBytes = MAX_TOOL_RESULT_BYTES) {
2445
+ if (msg.role === "assistant" && Array.isArray(msg.content)) {
2446
+ for (const block of msg.content) {
2447
+ if (block.type !== "tool") {
2448
+ continue;
2449
+ }
2450
+ if (typeof block.result === "string") {
2451
+ block.result = capToolResult(block.result, maxBytes);
2452
+ }
2453
+ if (typeof block.backgroundResult === "string") {
2454
+ block.backgroundResult = capToolResult(
2455
+ block.backgroundResult,
2456
+ maxBytes
2457
+ );
2458
+ }
2459
+ if (Array.isArray(block.subAgentMessages)) {
2460
+ block.subAgentMessages = capSubAgentTranscript(block.subAgentMessages);
2461
+ }
2462
+ }
2463
+ } else if (msg.role === "user" && msg.toolCallId && typeof msg.content === "string") {
2464
+ msg.content = capToolResult(msg.content, maxBytes);
2465
+ }
2466
+ }
2467
+ var MAX_TOOL_RESULT_BYTES, MAX_SUBAGENT_RESULT_BYTES, MAX_SUBAGENT_TRANSCRIPT_BYTES, HISTORY_PAGE_MAX_BYTES, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT, ROTATE_THRESHOLD_BYTES, RETAIN_TAIL_BYTES, ARCHIVE_RETENTION_BYTES;
2468
+ var init_historyLimits = __esm({
2469
+ "src/historyLimits.ts"() {
2426
2470
  "use strict";
2427
2471
  MAX_TOOL_RESULT_BYTES = 256 * 1024;
2472
+ MAX_SUBAGENT_RESULT_BYTES = 32 * 1024;
2473
+ MAX_SUBAGENT_TRANSCRIPT_BYTES = 512 * 1024;
2474
+ HISTORY_PAGE_MAX_BYTES = 2 * 1024 * 1024;
2475
+ HISTORY_DEFAULT_LIMIT = 500;
2476
+ HISTORY_MAX_LIMIT = 2e3;
2477
+ ROTATE_THRESHOLD_BYTES = 32 * 1024 * 1024;
2478
+ RETAIN_TAIL_BYTES = 16 * 1024 * 1024;
2479
+ ARCHIVE_RETENTION_BYTES = 64 * 1024 * 1024;
2428
2480
  }
2429
2481
  });
2430
2482
 
@@ -2457,22 +2509,11 @@ function loadSession(state) {
2457
2509
  }
2458
2510
  return false;
2459
2511
  }
2460
- function capOversizedResults(msg) {
2461
- if (msg.role === "assistant" && Array.isArray(msg.content)) {
2462
- for (const block of msg.content) {
2463
- if (block.type === "tool" && typeof block.result === "string") {
2464
- block.result = capToolResult(block.result);
2465
- }
2466
- }
2467
- } else if (msg.role === "user" && msg.toolCallId && typeof msg.content === "string") {
2468
- msg.content = capToolResult(msg.content);
2469
- }
2470
- }
2471
2512
  function sanitizeMessages(messages) {
2472
2513
  const result = [];
2473
2514
  for (let i = 0; i < messages.length; i++) {
2474
2515
  const msg = messages[i];
2475
- capOversizedResults(msg);
2516
+ capMessageForHistory(msg);
2476
2517
  result.push(msg);
2477
2518
  if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
2478
2519
  continue;
@@ -2584,6 +2625,9 @@ function parseArchive(name) {
2584
2625
  const raw = fs10.readFileSync(path4.join(ARCHIVE_DIR, name), "utf-8");
2585
2626
  const data = JSON.parse(raw);
2586
2627
  const messages = Array.isArray(data?.messages) ? data.messages : [];
2628
+ for (const msg of messages) {
2629
+ capMessageForHistory(msg);
2630
+ }
2587
2631
  archiveCountCache.set(name, messages.length);
2588
2632
  archiveMsgCache.set(name, messages);
2589
2633
  while (archiveMsgCache.size > ARCHIVE_MSG_CACHE_MAX) {
@@ -2694,6 +2738,29 @@ function getHistoryPage(state, opts) {
2694
2738
  messages.push(state.messages[i]);
2695
2739
  }
2696
2740
  }
2741
+ if (messages.length > 1) {
2742
+ let bytes = 0;
2743
+ let cut = 0;
2744
+ for (let i = messages.length - 1; i >= 0; i--) {
2745
+ bytes += Buffer.byteLength(JSON.stringify(messages[i]), "utf-8") + 1;
2746
+ if (bytes > HISTORY_PAGE_MAX_BYTES && i < messages.length - 1) {
2747
+ cut = i + 1;
2748
+ break;
2749
+ }
2750
+ }
2751
+ if (cut > 0) {
2752
+ while (cut < messages.length - 1 && messages[cut]?.role === "user" && messages[cut]?.toolCallId) {
2753
+ cut++;
2754
+ }
2755
+ messages.splice(0, cut);
2756
+ startIndex += cut;
2757
+ log3.info("History page trimmed to byte budget", {
2758
+ dropped: cut,
2759
+ kept: messages.length,
2760
+ startIndex
2761
+ });
2762
+ }
2763
+ }
2697
2764
  return { messages, startIndex, endIndex, totalMessageCount: total };
2698
2765
  }
2699
2766
  function rotate(state) {
@@ -2755,7 +2822,7 @@ function clearSession(state) {
2755
2822
  });
2756
2823
  }
2757
2824
  }
2758
- var log3, SESSION_FILE, ARCHIVE_DIR, ROTATE_THRESHOLD_BYTES, RETAIN_TAIL_BYTES, ARCHIVE_RETENTION_BYTES, ARCHIVE_NAME_RE, archiveSortKey, ARCHIVE_COUNT_RE, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT, archiveCountCache, archiveMsgCache, ARCHIVE_MSG_CACHE_MAX;
2825
+ var log3, SESSION_FILE, ARCHIVE_DIR, ARCHIVE_NAME_RE, archiveSortKey, ARCHIVE_COUNT_RE, archiveCountCache, archiveMsgCache, ARCHIVE_MSG_CACHE_MAX;
2759
2826
  var init_session = __esm({
2760
2827
  "src/session.ts"() {
2761
2828
  "use strict";
@@ -2763,18 +2830,13 @@ var init_session = __esm({
2763
2830
  init_logger();
2764
2831
  init_compaction();
2765
2832
  init_cleanMessages();
2766
- init_toolResultCap();
2833
+ init_historyLimits();
2767
2834
  log3 = createLogger("session");
2768
2835
  SESSION_FILE = ".remy-session.json";
2769
2836
  ARCHIVE_DIR = ".logs/sessions";
2770
- ROTATE_THRESHOLD_BYTES = 32 * 1024 * 1024;
2771
- RETAIN_TAIL_BYTES = 16 * 1024 * 1024;
2772
- ARCHIVE_RETENTION_BYTES = 64 * 1024 * 1024;
2773
2837
  ARCHIVE_NAME_RE = /^(cleared|rotated)-.*\.json$/;
2774
2838
  archiveSortKey = (name) => name.replace(/^(cleared|rotated)-/, "");
2775
2839
  ARCHIVE_COUNT_RE = /\.c(\d+)\.json$/;
2776
- HISTORY_DEFAULT_LIMIT = 500;
2777
- HISTORY_MAX_LIMIT = 2e3;
2778
2840
  archiveCountCache = /* @__PURE__ */ new Map();
2779
2841
  archiveMsgCache = /* @__PURE__ */ new Map();
2780
2842
  ARCHIVE_MSG_CACHE_MAX = 3;
@@ -2963,9 +3025,9 @@ function parseFrontmatter(content) {
2963
3025
  }
2964
3026
  const fields = {};
2965
3027
  for (const line of match[1].split("\n")) {
2966
- const sep = line.indexOf(":");
2967
- if (sep > 0) {
2968
- fields[line.slice(0, sep).trim()] = line.slice(sep + 1).trim();
3028
+ const sep2 = line.indexOf(":");
3029
+ if (sep2 > 0) {
3030
+ fields[line.slice(0, sep2).trim()] = line.slice(sep2 + 1).trim();
2969
3031
  }
2970
3032
  }
2971
3033
  return fields;
@@ -3420,7 +3482,7 @@ var init_bash = __esm({
3420
3482
  async execute(input, context) {
3421
3483
  const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES2;
3422
3484
  const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
3423
- return new Promise((resolve3) => {
3485
+ return new Promise((resolve4) => {
3424
3486
  const child = spawn2("sh", ["-c", input.command], {
3425
3487
  // Pinned rather than inherited. `undefined` here means "wherever the
3426
3488
  // process happens to be", which is the project root only by luck.
@@ -3445,9 +3507,9 @@ var init_bash = __esm({
3445
3507
  clearTimeout(timer);
3446
3508
  if (!output) {
3447
3509
  if (code && code !== 0) {
3448
- resolve3(`Error: process exited with code ${code}`);
3510
+ resolve4(`Error: process exited with code ${code}`);
3449
3511
  } else {
3450
- resolve3("(no output)");
3512
+ resolve4("(no output)");
3451
3513
  }
3452
3514
  return;
3453
3515
  }
@@ -3473,18 +3535,18 @@ var init_bash = __esm({
3473
3535
  `${(MAX_OUTPUT_BYTES / 1024).toFixed(0)}KB of ${(totalBytes / 1024).toFixed(0)}KB`
3474
3536
  );
3475
3537
  }
3476
- resolve3(
3538
+ resolve4(
3477
3539
  truncated + `
3478
3540
 
3479
3541
  (truncated at ${reasons.join(" / ")} \u2014 narrow the command (grep, head/tail, smaller paths) instead of increasing limits)`
3480
3542
  );
3481
3543
  } else {
3482
- resolve3(output);
3544
+ resolve4(output);
3483
3545
  }
3484
3546
  });
3485
3547
  child.on("error", (err) => {
3486
3548
  clearTimeout(timer);
3487
- resolve3(`Error: ${err.message}`);
3549
+ resolve4(`Error: ${err.message}`);
3488
3550
  });
3489
3551
  });
3490
3552
  }
@@ -3607,17 +3669,17 @@ var init_grep = __esm({
3607
3669
  }
3608
3670
  const rgCmd = `rg ${rgFlags}${globFlag} '${escaped}' ${searchPath}`;
3609
3671
  const grepCmd = `grep ${grepFlags} '${escaped}' ${searchPath} --include='*.ts' --include='*.tsx' --include='*.js' --include='*.json' --include='*.md'`;
3610
- return new Promise((resolve3) => {
3672
+ return new Promise((resolve4) => {
3611
3673
  exec(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
3612
3674
  if (stdout?.trim()) {
3613
- resolve3(formatResults(stdout, max, mode));
3675
+ resolve4(formatResults(stdout, max, mode));
3614
3676
  return;
3615
3677
  }
3616
3678
  exec(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
3617
3679
  if (grepStdout?.trim()) {
3618
- resolve3(formatResults(grepStdout, max, mode));
3680
+ resolve4(formatResults(grepStdout, max, mode));
3619
3681
  } else {
3620
- resolve3("No matches found.");
3682
+ resolve4("No matches found.");
3621
3683
  }
3622
3684
  });
3623
3685
  });
@@ -3979,7 +4041,7 @@ var init_restartProcess = __esm({
3979
4041
  async execute(input) {
3980
4042
  const data = await lspRequest("/restart-process", { name: input.name });
3981
4043
  if (data.ok) {
3982
- await new Promise((resolve3) => setTimeout(resolve3, 5e3));
4044
+ await new Promise((resolve4) => setTimeout(resolve4, 5e3));
3983
4045
  return `Restarted ${input.name}.`;
3984
4046
  }
3985
4047
  return `Error: unexpected response: ${JSON.stringify(data)}`;
@@ -4237,6 +4299,26 @@ ${opts.styleMap}
4237
4299
  ${ANALYSIS_RESPONSE_FORMAT}`;
4238
4300
  return p;
4239
4301
  }
4302
+ async function renderHtmlViaSidecar(opts) {
4303
+ const result = await sidecarRequest(
4304
+ "/render-html",
4305
+ {
4306
+ html: opts.html,
4307
+ width: opts.width,
4308
+ height: opts.height,
4309
+ ...opts.transparent ? { transparent: true } : {},
4310
+ ...opts.scale != null ? { scale: opts.scale } : {}
4311
+ },
4312
+ { timeout: VIEWPORT_CAPTURE_TIMEOUT_MS }
4313
+ );
4314
+ const url = result?.url;
4315
+ if (!url) {
4316
+ throw new Error(
4317
+ `No URL in sidecar render response. The browser may not be ready yet. Response: ${JSON.stringify(result)}`
4318
+ );
4319
+ }
4320
+ return { url, width: result.width, height: result.height };
4321
+ }
4240
4322
  async function streamScreenshotAnalysis(opts) {
4241
4323
  const { image, prompt, styleMap, onLog, model, apiConfig } = opts;
4242
4324
  const url = await resolveImageRef(image, apiConfig);
@@ -4920,7 +5002,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
4920
5002
  block.completedAt = Date.now();
4921
5003
  const innerMsgs = subAgentMessages.get(r.id);
4922
5004
  if (innerMsgs) {
4923
- block.subAgentMessages = innerMsgs;
5005
+ block.subAgentMessages = capSubAgentTranscript(innerMsgs);
4924
5006
  }
4925
5007
  if (captureArtifacts?.includes(block.name) && !r.isError) {
4926
5008
  try {
@@ -5008,7 +5090,7 @@ var init_runner = __esm({
5008
5090
  init_logger();
5009
5091
  init_usageLedger();
5010
5092
  init_toolRegistry();
5011
- init_toolResultCap();
5093
+ init_historyLimits();
5012
5094
  init_statusWatcher();
5013
5095
  init_cleanMessages();
5014
5096
  log8 = createLogger("sub-agent");
@@ -5218,10 +5300,10 @@ function parseFrontmatter2(filePath) {
5218
5300
  }
5219
5301
  const fm = {};
5220
5302
  for (const line of match[1].split("\n")) {
5221
- const sep = line.indexOf(":");
5222
- if (sep > 0) {
5223
- const key = line.slice(0, sep).trim();
5224
- const val = line.slice(sep + 1).trim();
5303
+ const sep2 = line.indexOf(":");
5304
+ if (sep2 > 0) {
5305
+ const key = line.slice(0, sep2).trim();
5306
+ const val = line.slice(sep2 + 1).trim();
5225
5307
  fm[key] = val;
5226
5308
  }
5227
5309
  }
@@ -5615,7 +5697,7 @@ var init_screenshot2 = __esm({
5615
5697
  init_surfaces();
5616
5698
  screenshotDefinition = {
5617
5699
  name: "screenshot",
5618
- 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 render a fixed-size image such as an Open Graph share card, set `width` and `height` (e.g. 1200 \xD7 630) and `format: 'png'`: the tool navigates to `path`, clips to exactly those pixel dimensions, and returns the image URL.",
5700
+ 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.",
5619
5701
  inputSchema: {
5620
5702
  type: "object",
5621
5703
  properties: {
@@ -5646,7 +5728,7 @@ var init_screenshot2 = __esm({
5646
5728
  format: {
5647
5729
  type: "string",
5648
5730
  enum: ["png", "jpeg"],
5649
- description: "Output image format. Defaults to 'jpeg'. Use 'png' for crisp flat graphics like share cards, where JPEG artifacts show on sharp type and edges."
5731
+ description: "Output image format. Defaults to 'jpeg'. Use 'png' for crisp flat graphics where JPEG artifacts show on sharp type and edges."
5650
5732
  },
5651
5733
  instructions: {
5652
5734
  type: "string",
@@ -5954,6 +6036,20 @@ var init_enhancePrompt = __esm({
5954
6036
  });
5955
6037
 
5956
6038
  // src/subagents/designExpert/tools/images/imageGenerator.ts
6039
+ function snapToOpenAiImageSize(width, height) {
6040
+ let best = OPENAI_IMAGE_SIZES[0];
6041
+ let bestScore = Infinity;
6042
+ for (const [w, h] of OPENAI_IMAGE_SIZES) {
6043
+ const aspectDiff = Math.abs(Math.log(w / h / (width / height)));
6044
+ const areaDiff = Math.abs(Math.log(w * h / (width * height)));
6045
+ const score = aspectDiff * 8 + areaDiff;
6046
+ if (score < bestScore) {
6047
+ bestScore = score;
6048
+ best = [w, h];
6049
+ }
6050
+ }
6051
+ return `${best[0]}x${best[1]}`;
6052
+ }
5957
6053
  async function generateImageAssets(opts) {
5958
6054
  const {
5959
6055
  prompts,
@@ -5968,7 +6064,11 @@ async function generateImageAssets(opts) {
5968
6064
  const sourceImages = opts.sourceImages?.length ? await resolveImageRefs(opts.sourceImages, apiConfig) : void 0;
5969
6065
  const width = opts.width || 2048;
5970
6066
  const height = opts.height || 2048;
5971
- const config = { width, height };
6067
+ const config = {
6068
+ width,
6069
+ height,
6070
+ size: snapToOpenAiImageSize(width, height)
6071
+ };
5972
6072
  if (sourceImages?.length) {
5973
6073
  const [firstImage] = sourceImages;
5974
6074
  config.images = sourceImages;
@@ -6087,7 +6187,7 @@ async function generateImageAssets(opts) {
6087
6187
  );
6088
6188
  return JSON.stringify({ images });
6089
6189
  }
6090
- var ANALYZE_PROMPT;
6190
+ var ANALYZE_PROMPT, OPENAI_IMAGE_SIZES;
6091
6191
  var init_imageGenerator = __esm({
6092
6192
  "src/subagents/designExpert/tools/images/imageGenerator.ts"() {
6093
6193
  "use strict";
@@ -6095,7 +6195,22 @@ var init_imageGenerator = __esm({
6095
6195
  init_analyzeImage();
6096
6196
  init_uploadImage();
6097
6197
  init_enhancePrompt();
6098
- 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.';
6198
+ 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.';
6199
+ OPENAI_IMAGE_SIZES = [
6200
+ [768, 1024],
6201
+ [1024, 768],
6202
+ [1024, 1024],
6203
+ [1024, 1536],
6204
+ [1536, 1024],
6205
+ [1280, 2560],
6206
+ [2560, 1280],
6207
+ [1440, 2560],
6208
+ [2560, 1440],
6209
+ [1792, 2400],
6210
+ [2400, 1792],
6211
+ [2400, 2880],
6212
+ [2560, 2560]
6213
+ ];
6099
6214
  }
6100
6215
  });
6101
6216
 
@@ -6252,6 +6367,150 @@ var init_editImages = __esm({
6252
6367
  }
6253
6368
  });
6254
6369
 
6370
+ // src/subagents/designExpert/tools/images/renderImage.ts
6371
+ var renderImage_exports = {};
6372
+ __export(renderImage_exports, {
6373
+ definition: () => definition7,
6374
+ execute: () => execute7
6375
+ });
6376
+ import { mkdir, unlink, writeFile } from "fs/promises";
6377
+ import { tmpdir } from "os";
6378
+ import { dirname, join, resolve as resolve2, sep } from "path";
6379
+ import { randomUUID } from "crypto";
6380
+ async function execute7(input, onLog, context) {
6381
+ const html = typeof input.html === "string" ? input.html : "";
6382
+ const width = Math.round(Number(input.width));
6383
+ const height = Math.round(Number(input.height));
6384
+ if (!html || !Number.isFinite(width) || !Number.isFinite(height) || width < MIN_DIMENSION || width > MAX_DIMENSION || height < MIN_DIMENSION || height > MAX_DIMENSION) {
6385
+ return `Error: renderImage requires an html string plus width/height between ${MIN_DIMENSION} and ${MAX_DIMENSION}.`;
6386
+ }
6387
+ const release = await acquireBrowserLock();
6388
+ let rendered;
6389
+ try {
6390
+ onLog?.("Rendering document in the sandbox browser...");
6391
+ rendered = await renderHtmlViaSidecar({
6392
+ html,
6393
+ width,
6394
+ height,
6395
+ transparent: input.transparentBackground === true,
6396
+ scale: typeof input.scale === "number" ? input.scale : void 0
6397
+ });
6398
+ } catch (err) {
6399
+ return `Error: render failed: ${err?.message ?? err}`;
6400
+ } finally {
6401
+ release();
6402
+ }
6403
+ let bytes;
6404
+ try {
6405
+ const res = await fetch(rendered.url);
6406
+ if (!res.ok) {
6407
+ throw new Error(`fetch returned ${res.status}`);
6408
+ }
6409
+ bytes = Buffer.from(await res.arrayBuffer());
6410
+ } catch (err) {
6411
+ return `Error: rendered but could not download the capture (${err?.message ?? err}). Temporary URL: ${rendered.url}`;
6412
+ }
6413
+ let savedPath;
6414
+ if (typeof input.savePath === "string" && input.savePath) {
6415
+ const absolute = resolve2(PROJECT_ROOT, input.savePath);
6416
+ if (!absolute.startsWith(PROJECT_ROOT + sep)) {
6417
+ return "Error: savePath must resolve inside the project.";
6418
+ }
6419
+ await mkdir(dirname(absolute), { recursive: true });
6420
+ await writeFile(absolute, bytes);
6421
+ savedPath = input.savePath;
6422
+ }
6423
+ onLog?.("Hosting the capture...");
6424
+ const tmpPath = join(tmpdir(), `render-${randomUUID()}.png`);
6425
+ let url = rendered.url;
6426
+ let temporary = true;
6427
+ try {
6428
+ await writeFile(tmpPath, bytes);
6429
+ const upload = await runMindstudioCliResult(["upload", tmpPath], {
6430
+ timeout: 6e4,
6431
+ onLog,
6432
+ caller: "designExpert"
6433
+ });
6434
+ const match = upload.ok ? upload.value.match(/https:\/\/\S+/g) : null;
6435
+ if (match?.length) {
6436
+ url = match[match.length - 1];
6437
+ temporary = false;
6438
+ }
6439
+ } finally {
6440
+ await unlink(tmpPath).catch(() => {
6441
+ });
6442
+ }
6443
+ const analysis = await analyzeImage({
6444
+ prompt: RENDER_ANALYZE_PROMPT,
6445
+ image: url,
6446
+ onLog,
6447
+ model: resolveModel("imageAnalysis", context?.models, context?.model)
6448
+ }).then((r) => r.analysis).catch((err) => `Could not review this image: ${err.message}`);
6449
+ return JSON.stringify({
6450
+ images: [
6451
+ {
6452
+ url,
6453
+ ...temporary ? {
6454
+ 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."
6455
+ } : {},
6456
+ ...savedPath ? { savedPath } : {},
6457
+ analysis,
6458
+ width: rendered.width,
6459
+ height: rendered.height
6460
+ }
6461
+ ]
6462
+ });
6463
+ }
6464
+ var MIN_DIMENSION, MAX_DIMENSION, RENDER_ANALYZE_PROMPT, definition7;
6465
+ var init_renderImage = __esm({
6466
+ "src/subagents/designExpert/tools/images/renderImage.ts"() {
6467
+ "use strict";
6468
+ init_screenshot();
6469
+ init_browserLock();
6470
+ init_runMindstudioCli();
6471
+ init_analyzeImage();
6472
+ init_surfaces();
6473
+ init_projectRoot();
6474
+ MIN_DIMENSION = 16;
6475
+ MAX_DIMENSION = 4096;
6476
+ 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.';
6477
+ definition7 = {
6478
+ name: "renderImage",
6479
+ 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.",
6480
+ inputSchema: {
6481
+ type: "object",
6482
+ properties: {
6483
+ html: {
6484
+ type: "string",
6485
+ 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."
6486
+ },
6487
+ width: {
6488
+ type: "number",
6489
+ description: "Viewport width in CSS pixels (e.g. 1200 for an OG card, 512 for an icon tile). Range: 16-4096."
6490
+ },
6491
+ height: {
6492
+ type: "number",
6493
+ description: "Viewport height in CSS pixels. Range: 16-4096."
6494
+ },
6495
+ scale: {
6496
+ type: "number",
6497
+ 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)."
6498
+ },
6499
+ transparentBackground: {
6500
+ type: "boolean",
6501
+ 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."
6502
+ },
6503
+ savePath: {
6504
+ type: "string",
6505
+ 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)."
6506
+ }
6507
+ },
6508
+ required: ["html", "width", "height"]
6509
+ }
6510
+ };
6511
+ }
6512
+ });
6513
+
6255
6514
  // src/subagents/copyEditor/tools.ts
6256
6515
  var COPY_EDITOR_TOOLS;
6257
6516
  var init_tools3 = __esm({
@@ -6326,18 +6585,18 @@ var init_copyEditor = __esm({
6326
6585
  // src/subagents/designExpert/tools/polishCopy.ts
6327
6586
  var polishCopy_exports = {};
6328
6587
  __export(polishCopy_exports, {
6329
- definition: () => definition7,
6330
- execute: () => execute7
6588
+ definition: () => definition8,
6589
+ execute: () => execute8
6331
6590
  });
6332
- async function execute7(input, _onLog, context) {
6591
+ async function execute8(input, _onLog, context) {
6333
6592
  return copyEditorTool.execute(input, context);
6334
6593
  }
6335
- var definition7;
6594
+ var definition8;
6336
6595
  var init_polishCopy = __esm({
6337
6596
  "src/subagents/designExpert/tools/polishCopy.ts"() {
6338
6597
  "use strict";
6339
6598
  init_copyEditor();
6340
- definition7 = {
6599
+ definition8 = {
6341
6600
  name: "polishCopy",
6342
6601
  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).",
6343
6602
  inputSchema: {
@@ -6375,6 +6634,7 @@ var init_tools4 = __esm({
6375
6634
  init_analyzeImage2();
6376
6635
  init_generateImages();
6377
6636
  init_editImages();
6637
+ init_renderImage();
6378
6638
  init_polishCopy();
6379
6639
  init_screenshot2();
6380
6640
  tools = {
@@ -6388,6 +6648,7 @@ var init_tools4 = __esm({
6388
6648
  screenshot: { definition: screenshotDefinition, execute: executeScreenshot },
6389
6649
  generateImages: generateImages_exports,
6390
6650
  editImages: editImages_exports,
6651
+ renderImage: renderImage_exports,
6391
6652
  polishCopy: polishCopy_exports
6392
6653
  };
6393
6654
  DESIGN_EXPERT_TOOLS = [
@@ -6930,13 +7191,13 @@ var init_tools5 = __esm({
6930
7191
  // src/subagents/productVision/executor.ts
6931
7192
  import fs18 from "fs";
6932
7193
  import path10 from "path";
6933
- function resolve2(filePath) {
7194
+ function resolve3(filePath) {
6934
7195
  return path10.join(ROADMAP_DIR, filePath);
6935
7196
  }
6936
7197
  async function executeVisionTool(name, input, context) {
6937
7198
  switch (name) {
6938
7199
  case "writeFile": {
6939
- const filePath = resolve2(input.path);
7200
+ const filePath = resolve3(input.path);
6940
7201
  try {
6941
7202
  fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
6942
7203
  let oldContent = null;
@@ -6954,7 +7215,7 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
6954
7215
  }
6955
7216
  }
6956
7217
  case "deleteFile": {
6957
- const filePath = resolve2(input.path);
7218
+ const filePath = resolve3(input.path);
6958
7219
  try {
6959
7220
  if (!fs18.existsSync(filePath)) {
6960
7221
  return `Error: ${filePath} does not exist`;
@@ -6971,7 +7232,7 @@ ${unifiedDiff(filePath, oldContent, "")}`;
6971
7232
  if (!context) {
6972
7233
  return "Error: writePitchDeck requires execution context for design expert delegation";
6973
7234
  }
6974
- const filePath = resolve2("pitch.html");
7235
+ const filePath = resolve3("pitch.html");
6975
7236
  try {
6976
7237
  fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
6977
7238
  const exists = fs18.existsSync(filePath);
@@ -8142,14 +8403,14 @@ function buildCorpus() {
8142
8403
  });
8143
8404
  }
8144
8405
  }
8145
- const sep = "\n\n---\n\n";
8406
+ const sep2 = "\n\n---\n\n";
8146
8407
  const sections = [];
8147
8408
  let usedChars = 0;
8148
8409
  for (const { path: p, content } of files) {
8149
8410
  const section = `## File: ${p}
8150
8411
 
8151
8412
  ${content}`;
8152
- const added = section.length + (sections.length > 0 ? sep.length : 0);
8413
+ const added = section.length + (sections.length > 0 ? sep2.length : 0);
8153
8414
  if (sections.length > 0 && usedChars + added > BRAND_CORPUS_CHAR_LIMIT) {
8154
8415
  sections.push(
8155
8416
  `(brand corpus truncated: included ${sections.length} of ${files.length} files, ~${(usedChars / 1024).toFixed(0)}KB; brand-relevant files were prioritized.)`
@@ -8159,7 +8420,7 @@ ${content}`;
8159
8420
  sections.push(section);
8160
8421
  usedChars += added;
8161
8422
  }
8162
- return sections.join(sep);
8423
+ return sections.join(sep2);
8163
8424
  }
8164
8425
  function headSlice(content) {
8165
8426
  if (content.length <= HEAD_SLICE_CHARS) {
@@ -8439,6 +8700,8 @@ async function runTurn(params) {
8439
8700
  let lastCallInputTokens = 0;
8440
8701
  let lastCallCacheCreation = 0;
8441
8702
  let lastCallCacheRead = 0;
8703
+ let abnormalStopRecoveries = 0;
8704
+ const MAX_ABNORMAL_STOP_RECOVERIES = 2;
8442
8705
  const statusWatcher = isFirstMessage ? { stop() {
8443
8706
  }, pause() {
8444
8707
  }, resume() {
@@ -8782,6 +9045,18 @@ async function runTurn(params) {
8782
9045
  });
8783
9046
  }
8784
9047
  const toolCalls = getToolCalls(contentBlocks);
9048
+ if (toolCalls.length === 0 && (stopReason === "repetition" || stopReason === "max_tokens") && abnormalStopRecoveries < MAX_ABNORMAL_STOP_RECOVERIES && !signal?.aborted) {
9049
+ abnormalStopRecoveries++;
9050
+ log13.warn("Abnormal stop \u2014 nudging model to continue", {
9051
+ requestId,
9052
+ stopReason,
9053
+ attempt: abnormalStopRecoveries
9054
+ });
9055
+ 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.";
9056
+ state.messages.push({ role: "user", content: nudge, hidden: true });
9057
+ onEvent({ type: "user_message", text: nudge, hidden: true });
9058
+ continue;
9059
+ }
8785
9060
  if (stopReason !== "tool_use" || toolCalls.length === 0) {
8786
9061
  statusWatcher.stop();
8787
9062
  saveSession(state);
@@ -8948,7 +9223,7 @@ async function runTurn(params) {
8948
9223
  block.completedAt = Date.now();
8949
9224
  const msgs = subAgentMessages.get(r.id);
8950
9225
  if (msgs) {
8951
- block.subAgentMessages = msgs;
9226
+ block.subAgentMessages = capSubAgentTranscript(msgs);
8952
9227
  }
8953
9228
  }
8954
9229
  }
@@ -9007,7 +9282,7 @@ var init_agent = __esm({
9007
9282
  init_trigger2();
9008
9283
  init_surfaces();
9009
9284
  init_toolRegistry();
9010
- init_toolResultCap();
9285
+ init_historyLimits();
9011
9286
  log13 = createLogger("agent");
9012
9287
  BRAND_TRIGGERING_TOOLS = /* @__PURE__ */ new Set([
9013
9288
  "writeSpec",
@@ -9276,8 +9551,8 @@ var init_config = __esm({
9276
9551
 
9277
9552
  // src/headless/attachments.ts
9278
9553
  import { mkdirSync, existsSync } from "fs";
9279
- import { writeFile } from "fs/promises";
9280
- import { basename as basename2, join, extname as extname2 } from "path";
9554
+ import { writeFile as writeFile2 } from "fs/promises";
9555
+ import { basename as basename2, join as join2, extname as extname2 } from "path";
9281
9556
  function filenameFromUrl(url) {
9282
9557
  try {
9283
9558
  const pathname = new URL(url).pathname;
@@ -9288,7 +9563,7 @@ function filenameFromUrl(url) {
9288
9563
  }
9289
9564
  }
9290
9565
  function resolveUniqueFilename(name, claimed) {
9291
- const isFree = (candidate) => !claimed.has(candidate) && !existsSync(join(UPLOADS_DIR, candidate));
9566
+ const isFree = (candidate) => !claimed.has(candidate) && !existsSync(join2(UPLOADS_DIR, candidate));
9292
9567
  if (isFree(name)) {
9293
9568
  return name;
9294
9569
  }
@@ -9322,7 +9597,7 @@ async function persistAttachments(attachments) {
9322
9597
  const results = await Promise.allSettled(
9323
9598
  nonVoice.map(async (att, i) => {
9324
9599
  const name = names[i];
9325
- const localPath = join(UPLOADS_DIR, name);
9600
+ const localPath = join2(UPLOADS_DIR, name);
9326
9601
  const res = await fetch(att.url, {
9327
9602
  signal: AbortSignal.timeout(3e4)
9328
9603
  });
@@ -9330,7 +9605,7 @@ async function persistAttachments(attachments) {
9330
9605
  throw new Error(`HTTP ${res.status} downloading ${att.url}`);
9331
9606
  }
9332
9607
  const buffer = Buffer.from(await res.arrayBuffer());
9333
- await writeFile(localPath, buffer);
9608
+ await writeFile2(localPath, buffer);
9334
9609
  log15.info("Attachment saved", {
9335
9610
  filename: name,
9336
9611
  path: localPath,
@@ -9344,7 +9619,7 @@ async function persistAttachments(attachments) {
9344
9619
  });
9345
9620
  if (textRes.ok) {
9346
9621
  extractedTextPath = `${localPath}.txt`;
9347
- await writeFile(extractedTextPath, await textRes.text(), "utf-8");
9622
+ await writeFile2(extractedTextPath, await textRes.text(), "utf-8");
9348
9623
  log15.info("Extracted text saved", { path: extractedTextPath });
9349
9624
  }
9350
9625
  } catch {
@@ -9988,17 +10263,17 @@ var init_headless = __esm({
9988
10263
  return Promise.resolve(early);
9989
10264
  }
9990
10265
  const shouldTimeout = !USER_FACING_TOOLS.has(name);
9991
- return new Promise((resolve3) => {
10266
+ return new Promise((resolve4) => {
9992
10267
  const timeout = shouldTimeout ? setTimeout(() => {
9993
10268
  this.pendingTools.delete(id);
9994
- resolve3(
10269
+ resolve4(
9995
10270
  "Error: Tool timed out \u2014 no response from the app environment after 5 minutes."
9996
10271
  );
9997
10272
  }, EXTERNAL_TOOL_TIMEOUT_MS) : void 0;
9998
10273
  this.pendingTools.set(id, {
9999
10274
  resolve: (result) => {
10000
10275
  clearTimeout(timeout);
10001
- resolve3(result);
10276
+ resolve4(result);
10002
10277
  },
10003
10278
  timeout
10004
10279
  });