@mindstudio-ai/remy 0.1.230 → 0.1.232

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 CHANGED
@@ -2882,6 +2882,19 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
2882
2882
  }
2883
2883
  };
2884
2884
 
2885
+ // src/toolResultCap.ts
2886
+ var MAX_TOOL_RESULT_BYTES = 256 * 1024;
2887
+ function capToolResult(result) {
2888
+ const total = Buffer.byteLength(result, "utf-8");
2889
+ if (total <= MAX_TOOL_RESULT_BYTES) {
2890
+ return result;
2891
+ }
2892
+ const head = Buffer.from(result, "utf-8").subarray(0, MAX_TOOL_RESULT_BYTES).toString("utf-8");
2893
+ return head + `
2894
+
2895
+ (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.)`;
2896
+ }
2897
+
2885
2898
  // src/statusWatcher.ts
2886
2899
  function startStatusWatcher(config) {
2887
2900
  const { apiConfig, getContext, onStatus, interval = 5e3, signal } = config;
@@ -3407,7 +3420,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
3407
3420
  subAgentMessages
3408
3421
  );
3409
3422
  }
3410
- safeSettle(result, result.startsWith("Error"));
3423
+ safeSettle(capToolResult(result), result.startsWith("Error"));
3411
3424
  } catch (err) {
3412
3425
  safeSettle(`Error: ${err.message}`, true);
3413
3426
  }
@@ -3595,9 +3608,10 @@ var BROWSER_TOOLS = [
3595
3608
  "evaluate",
3596
3609
  "styles",
3597
3610
  "screenshotFullPage",
3598
- "screenshotViewport"
3611
+ "screenshotViewport",
3612
+ "setViewport"
3599
3613
  ],
3600
- description: "snapshot: accessibility tree of the page (waits for network to settle). click: click an element (animated cursor, full event sequence). type: type text into input (one char at a time, works with React/Vue/Svelte). select: select a dropdown option by text. wait: wait for an element to appear (polls 100ms, waits for network). navigate: navigate to a URL within the app (waits for load, subsequent steps run on new page). evaluate: run JS in the page. styles: read computed CSS styles from elements (pass properties array with camelCase names, or omit for defaults). screenshotFullPage: full-page viewport-stitched screenshot (returns CDN url with dimensions). screenshotViewport: screenshot of just the visible viewport \u2014 pass `scrollToSelector` (or `scrollY`) on this step to scroll a section into view and capture it in one atomic step (no separate scroll needed)."
3614
+ description: 'snapshot: accessibility tree of the page (waits for network to settle). click: click an element (animated cursor, full event sequence). type: type text into input (one char at a time, works with React/Vue/Svelte). select: select a dropdown option by text. wait: wait for an element to appear (polls 100ms, waits for network). navigate: navigate to a URL within the app (waits for load, subsequent steps run on new page). evaluate: run JS in the page. styles: read computed CSS styles from elements (pass properties array with camelCase names, or omit for defaults). screenshotFullPage: full-page viewport-stitched screenshot (returns CDN url with dimensions). screenshotViewport: screenshot of just the visible viewport \u2014 pass `scrollToSelector` (or `scrollY`) on this step to scroll a section into view and capture it in one atomic step (no separate scroll needed). setViewport: switch the browser between desktop and mobile rendering (pass `mode`: "desktop" or "mobile"). Reloads the page so responsive layouts, media queries, and matchMedia re-evaluate \u2014 use it to QA mobile/responsive views.'
3601
3615
  },
3602
3616
  ref: {
3603
3617
  type: "string",
@@ -3651,6 +3665,11 @@ var BROWSER_TOOLS = [
3651
3665
  scrollY: {
3652
3666
  type: "number",
3653
3667
  description: "For screenshotViewport: absolute Y offset to scroll to before the shot, when no selector is available."
3668
+ },
3669
+ mode: {
3670
+ type: "string",
3671
+ enum: ["desktop", "mobile"],
3672
+ description: 'For setViewport: the rendering mode to switch to. "mobile" emulates a phone (narrow width, touch, device pixel ratio); "desktop" is the standard wide viewport.'
3654
3673
  }
3655
3674
  },
3656
3675
  required: ["command"]
@@ -3700,6 +3719,14 @@ var log7 = createLogger("browser-automation");
3700
3719
  async function runBrowserAutomation(task, context, opts) {
3701
3720
  const release = await acquireBrowserLock();
3702
3721
  try {
3722
+ try {
3723
+ await sidecarRequest(
3724
+ "/set-viewport",
3725
+ { mode: "default" },
3726
+ { timeout: 2e4 }
3727
+ );
3728
+ } catch {
3729
+ }
3703
3730
  let lastBrowserCommandViewport;
3704
3731
  const result = await runSubAgent({
3705
3732
  system: getBrowserAutomationPrompt(),
@@ -5122,6 +5149,50 @@ ${summaryText}
5122
5149
  var DESCRIPTION = `
5123
5150
  Visual design expert. Describe the situation and what you need \u2014 the agent decides what to deliver. It reads the spec files automatically. Include relevant user requirements and context it can't get from the spec, but do not list specific deliverables or tell it how to do its job. Do not suggest implementation details or ideas - only relay what is needed.
5124
5151
  `.trim();
5152
+ var RENDER_WRITE_TOOL_NAMES = /* @__PURE__ */ new Set(["writeFile", "editFile"]);
5153
+ var DESIGN_EXPERT_RENDER_TOOLS = [
5154
+ ...DESIGN_EXPERT_TOOLS,
5155
+ writeFileTool.definition,
5156
+ editFileTool.definition
5157
+ ];
5158
+ async function runDesignExpert(opts, context) {
5159
+ const history = context.conversationMessages ? getSubAgentHistory(context.conversationMessages, "visualDesignExpert") : [];
5160
+ return runSubAgent({
5161
+ system: getDesignExpertPrompt(context.onboardingState),
5162
+ task: opts.task,
5163
+ history: history.length > 0 ? history : void 0,
5164
+ tools: opts.enableWrite ? DESIGN_EXPERT_RENDER_TOOLS : DESIGN_EXPERT_TOOLS,
5165
+ externalTools: /* @__PURE__ */ new Set(),
5166
+ executeTool: (name, input, toolCallId, onLog, sams) => {
5167
+ const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
5168
+ if (COMMON_READ_TOOL_NAMES.has(name)) {
5169
+ return executeTool(name, input, childCtx);
5170
+ }
5171
+ if (opts.enableWrite && RENDER_WRITE_TOOL_NAMES.has(name)) {
5172
+ return executeTool(name, input, childCtx);
5173
+ }
5174
+ return executeDesignExpertTool(name, input, childCtx, toolCallId, onLog);
5175
+ },
5176
+ apiConfig: context.apiConfig,
5177
+ model: resolveModel("visualDesignExpert", context.models, context.model),
5178
+ subAgentId: "visualDesignExpert",
5179
+ signal: context.signal,
5180
+ parentToolId: context.toolCallId,
5181
+ requestId: context.requestId,
5182
+ onEvent: context.onEvent,
5183
+ resolveExternalTool: context.resolveExternalTool,
5184
+ toolRegistry: context.toolRegistry,
5185
+ background: opts.background,
5186
+ onBackgroundComplete: opts.background ? (bgResult) => {
5187
+ context.onBackgroundComplete?.(
5188
+ context.toolCallId,
5189
+ opts.reportingName ?? "visualDesignExpert",
5190
+ bgResult.text,
5191
+ bgResult.messages
5192
+ );
5193
+ } : void 0
5194
+ });
5195
+ }
5125
5196
  var designExpertTool = {
5126
5197
  clearable: false,
5127
5198
  definition: {
@@ -5146,49 +5217,20 @@ var designExpertTool = {
5146
5217
  if (!context) {
5147
5218
  return "Error: visual design expert requires execution context";
5148
5219
  }
5149
- const history = context.conversationMessages ? getSubAgentHistory(context.conversationMessages, "visualDesignExpert") : [];
5150
- const result = await runSubAgent({
5151
- system: getDesignExpertPrompt(context.onboardingState),
5152
- task: input.task,
5153
- history: history.length > 0 ? history : void 0,
5154
- tools: DESIGN_EXPERT_TOOLS,
5155
- externalTools: /* @__PURE__ */ new Set(),
5156
- executeTool: (name, input2, toolCallId, onLog, sams) => {
5157
- const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
5158
- if (COMMON_READ_TOOL_NAMES.has(name)) {
5159
- return executeTool(name, input2, childCtx);
5160
- }
5161
- return executeDesignExpertTool(
5162
- name,
5163
- input2,
5164
- childCtx,
5165
- toolCallId,
5166
- onLog
5167
- );
5220
+ const result = await runDesignExpert(
5221
+ {
5222
+ task: input.task,
5223
+ background: input.background
5168
5224
  },
5169
- apiConfig: context.apiConfig,
5170
- model: resolveModel("visualDesignExpert", context.models, context.model),
5171
- subAgentId: "visualDesignExpert",
5172
- signal: context.signal,
5173
- parentToolId: context.toolCallId,
5174
- requestId: context.requestId,
5175
- onEvent: context.onEvent,
5176
- resolveExternalTool: context.resolveExternalTool,
5177
- toolRegistry: context.toolRegistry,
5178
- background: input.background,
5179
- onBackgroundComplete: input.background ? (bgResult) => {
5180
- context.onBackgroundComplete?.(
5181
- context.toolCallId,
5182
- "visualDesignExpert",
5183
- bgResult.text,
5184
- bgResult.messages
5185
- );
5186
- } : void 0
5187
- });
5225
+ context
5226
+ );
5188
5227
  context.subAgentMessages?.set(context.toolCallId, result.messages);
5189
5228
  return result.text;
5190
5229
  }
5191
5230
  };
5231
+ async function runDesignExpertRender(opts, context) {
5232
+ return runDesignExpert({ ...opts, enableWrite: true }, context);
5233
+ }
5192
5234
 
5193
5235
  // src/subagents/productVision/tools.ts
5194
5236
  var VISION_TOOLS = [
@@ -5293,16 +5335,25 @@ ${unifiedDiff(filePath, oldContent, "")}`;
5293
5335
  const filePath = resolve("pitch.html");
5294
5336
  try {
5295
5337
  fs19.mkdirSync(ROADMAP_DIR, { recursive: true });
5296
- const existing = fs19.existsSync(filePath) ? fs19.readFileSync(filePath, "utf-8").trim() : "";
5297
- const currentDeck = existing || PITCH_DECK_SHELL;
5338
+ const exists = fs19.existsSync(filePath);
5339
+ const before = exists ? fs19.statSync(filePath).mtimeMs : null;
5340
+ const delivery = exists ? `### Your deliverable
5341
+ The pitch deck already exists at \`${filePath}\`. Read it, then update it for the new <pitch_content>, keeping the presentation scaffolding intact \u2014 change only what needs to change.
5342
+
5343
+ Reply with a one-line summary of what you changed \u2014 not the HTML.` : `### Your deliverable
5344
+ Write the complete pitch deck to \`${filePath}\`. It does not exist yet \u2014 start from this scaffold, keeping its progress bar, chevron navigation, keyboard navigation, and transition mechanics intact:
5345
+
5346
+ <pitch_deck_shell>
5347
+ ${PITCH_DECK_SHELL}
5348
+ </pitch_deck_shell>
5349
+
5350
+ Reply with a one-line summary of what you wrote \u2014 not the HTML.`;
5298
5351
  const task = `
5299
5352
  <pitch_content>${input.task}</pitch_content>
5300
5353
 
5301
- <current_deck>${currentDeck}</current_deck>
5302
-
5303
5354
  We are building the pitch deck for the app. Using the provided <pitch_content>, as well as the app's spec data, think about what would make a compelling, interactive, self-contained horizontally-scrolling HTML slide deck for this product. Keep it simple, clean, powerful. Giant text, large logo, big, bold stats and claims. Edit the content as necessary to create the most impactful, bold, and beautiful slides. This should not feel like an essay, and it should not feel like a landing page \u2014 it should feel like a modern interactive presentation that leaves the user wowed by the product and excited about its future.
5304
5355
 
5305
- Use <current_deck> as your starting point and replace or update the content as needed, maintaining the bones of the presentation scaffolding. Always keep the progress bar, chevron navigation, and keyboard navigation - they are part of the scaffold.
5356
+ Maintain the bones of the presentation scaffolding. Always keep the progress bar, chevron navigation, and keyboard navigation - they are part of the scaffold.
5306
5357
 
5307
5358
  ### Rules
5308
5359
  - The deck must be a single HTML file \u2014 it will be rendered in an iframe.
@@ -5312,14 +5363,18 @@ Use <current_deck> as your starting point and replace or update the content as n
5312
5363
  - Code must be clean, bug free, and easy to parse. Use GSAP for animations. Pay close attention to layout and alignment to make sure everything is perfect.
5313
5364
  - Keep the progress bar and edge chevrons from the shell \u2014 they are part of the navigation UX.
5314
5365
 
5315
- Respond only with the complete HTML file and absolutely no other text. Your response will be written directly to an html file.`;
5316
- const result = await designExpertTool.execute({ task }, context);
5317
- const htmlMatch = result.match(
5318
- /```(?:html|wireframe)\n([\s\S]*?)```/
5319
- );
5320
- const html = htmlMatch ? htmlMatch[1].trim() : result;
5321
- fs19.writeFileSync(filePath, html, "utf-8");
5322
- return `Pitch deck written successfully.`;
5366
+ ${delivery}`;
5367
+ const result = await runDesignExpertRender({ task }, context);
5368
+ context.subAgentMessages?.set(context.toolCallId, result.messages);
5369
+ if (!fs19.existsSync(filePath)) {
5370
+ return `Error: the design expert did not write ${filePath}. Its reply was:
5371
+ ${result.text}`;
5372
+ }
5373
+ if (before !== null && fs19.statSync(filePath).mtimeMs === before) {
5374
+ return `Error: the pitch deck at ${filePath} was not modified. The design expert's reply was:
5375
+ ${result.text}`;
5376
+ }
5377
+ return `Pitch deck written to ${filePath}. ${result.text}`;
5323
5378
  } catch (err) {
5324
5379
  return `Error generating pitch deck: ${err.message}`;
5325
5380
  }
@@ -5553,7 +5608,7 @@ import fs20 from "fs";
5553
5608
  var OVERVIEW_FILE = "src/overview.html";
5554
5609
  var DESIGN_BRIEF = `We are building the Build Overview for this app \u2014 the home page of its Spec tab. It is a calm, dense, one-page reference of everything the app actually contains, including the parts the user can't see. It renders flush inside the Spec tab's content panel (the IDE supplies the surrounding nav).
5555
5610
 
5556
- Take the plain-language copy in <overview_copy> and lay it out and skin it into a single, beautiful, self-contained HTML document in the app's own brand. If <current_overview> is non-empty, use it as your starting point and preserve its established skin, updating only what the copy changed.
5611
+ Take the plain-language copy in <overview_copy> and lay it out and skin it into a single, beautiful, self-contained HTML document in the app's own brand.
5557
5612
 
5558
5613
  ### The single hard rule
5559
5614
  The copy in <overview_copy> is final \u2014 it was authored and edited before it reached you. Treat it as locked content to typeset, not a draft to improve. Reproduce the words exactly: do not rewrite, rephrase, shorten, expand, reorder, or "polish" them, and do not run them through any copy tool. This is the opposite of your usual role \u2014 here you own layout, typography, and visual design only, and the words (every number, name, label, claim, and sentence) are fixed. A single changed word or wrong number breaks this document's purpose.
@@ -5568,9 +5623,7 @@ The copy in <overview_copy> is final \u2014 it was authored and edited before it
5568
5623
 
5569
5624
  ### Constraints
5570
5625
  - A single self-contained HTML file. Fonts may load from a CDN; everything else (CSS, the logo SVG) is inline.
5571
- - Responsive: fills the embedded panel width and collapses gracefully at narrow widths.
5572
-
5573
- Respond only with the complete HTML file and absolutely no other text. Your response will be written directly to src/overview.html.`;
5626
+ - Responsive: fills the embedded panel width and collapses gracefully at narrow widths.`;
5574
5627
  var OVERVIEW_SHELL = `<!DOCTYPE html>
5575
5628
  <html lang="en">
5576
5629
  <head>
@@ -5591,6 +5644,22 @@ var OVERVIEW_SHELL = `<!DOCTYPE html>
5591
5644
  app's spec. -->
5592
5645
  </body>
5593
5646
  </html>`;
5647
+ function initialDelivery() {
5648
+ return `### Your deliverable
5649
+ Write the complete Build Overview to \`${OVERVIEW_FILE}\`. The file does not exist yet \u2014 start from this scaffold, which carries the technical hygiene for the iframed render context (keep the viewport meta and the reset). Layout, sections, type, and styling are yours to compose.
5650
+
5651
+ <overview_shell>
5652
+ ${OVERVIEW_SHELL}
5653
+ </overview_shell>
5654
+
5655
+ Reply with a one-line summary of what you wrote \u2014 not the HTML.`;
5656
+ }
5657
+ function refreshDelivery() {
5658
+ return `### Your deliverable
5659
+ The Build Overview already exists at \`${OVERVIEW_FILE}\`. Read it, then update it to reflect <overview_copy>, preserving its established skin \u2014 change only what the copy changed.
5660
+
5661
+ Reply with a one-line summary of what you changed \u2014 not the HTML.`;
5662
+ }
5594
5663
  var buildOverviewTool = {
5595
5664
  clearable: false,
5596
5665
  definition: {
@@ -5615,21 +5684,29 @@ var buildOverviewTool = {
5615
5684
  if (!content) {
5616
5685
  return "Error: writeBuildOverview requires non-empty `content` (the overview copy).";
5617
5686
  }
5618
- try {
5619
- const existing = fs20.existsSync(OVERVIEW_FILE) ? fs20.readFileSync(OVERVIEW_FILE, "utf-8").trim() : "";
5620
- const currentOverview = existing || OVERVIEW_SHELL;
5621
- const task = `<overview_copy>${content}</overview_copy>
5687
+ const exists = fs20.existsSync(OVERVIEW_FILE);
5688
+ const task = `<overview_copy>${content}</overview_copy>
5622
5689
 
5623
- <current_overview>${currentOverview}</current_overview>
5690
+ ${DESIGN_BRIEF}
5624
5691
 
5625
- ${DESIGN_BRIEF}`;
5626
- const result = await designExpertTool.execute({ task }, context);
5627
- const htmlMatch = result.match(
5628
- /```(?:html|wireframe)\n([\s\S]*?)```/
5629
- );
5630
- const html = htmlMatch ? htmlMatch[1].trim() : result;
5631
- fs20.writeFileSync(OVERVIEW_FILE, html, "utf-8");
5632
- return "Build overview written successfully to src/overview.html.";
5692
+ ${exists ? refreshDelivery() : initialDelivery()}`;
5693
+ try {
5694
+ if (exists) {
5695
+ input.background = true;
5696
+ const result2 = await runDesignExpertRender(
5697
+ { task, background: true, reportingName: "writeBuildOverview" },
5698
+ context
5699
+ );
5700
+ context.subAgentMessages?.set(context.toolCallId, result2.messages);
5701
+ return result2.text;
5702
+ }
5703
+ const result = await runDesignExpertRender({ task }, context);
5704
+ context.subAgentMessages?.set(context.toolCallId, result.messages);
5705
+ if (!fs20.existsSync(OVERVIEW_FILE)) {
5706
+ return `Error: the design expert did not write ${OVERVIEW_FILE}. Its reply was:
5707
+ ${result.text}`;
5708
+ }
5709
+ return `Build overview written to ${OVERVIEW_FILE}. ${result.text}`;
5633
5710
  } catch (err) {
5634
5711
  return `Error generating build overview: ${err.message}`;
5635
5712
  }
@@ -6351,21 +6428,6 @@ function triggerBrandExtraction(apiConfig, model) {
6351
6428
  // src/session.ts
6352
6429
  import fs22 from "fs";
6353
6430
  import path11 from "path";
6354
-
6355
- // src/toolResultCap.ts
6356
- var MAX_TOOL_RESULT_BYTES = 256 * 1024;
6357
- function capToolResult(result) {
6358
- const total = Buffer.byteLength(result, "utf-8");
6359
- if (total <= MAX_TOOL_RESULT_BYTES) {
6360
- return result;
6361
- }
6362
- const head = Buffer.from(result, "utf-8").subarray(0, MAX_TOOL_RESULT_BYTES).toString("utf-8");
6363
- return head + `
6364
-
6365
- (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.)`;
6366
- }
6367
-
6368
- // src/session.ts
6369
6431
  var log12 = createLogger("session");
6370
6432
  var SESSION_FILE = ".remy-session.json";
6371
6433
  var ARCHIVE_DIR = ".logs/sessions";
package/dist/index.js CHANGED
@@ -3513,6 +3513,25 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
3513
3513
  }
3514
3514
  });
3515
3515
 
3516
+ // src/toolResultCap.ts
3517
+ function capToolResult(result) {
3518
+ const total = Buffer.byteLength(result, "utf-8");
3519
+ if (total <= MAX_TOOL_RESULT_BYTES) {
3520
+ return result;
3521
+ }
3522
+ const head = Buffer.from(result, "utf-8").subarray(0, MAX_TOOL_RESULT_BYTES).toString("utf-8");
3523
+ return head + `
3524
+
3525
+ (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.)`;
3526
+ }
3527
+ var MAX_TOOL_RESULT_BYTES;
3528
+ var init_toolResultCap = __esm({
3529
+ "src/toolResultCap.ts"() {
3530
+ "use strict";
3531
+ MAX_TOOL_RESULT_BYTES = 256 * 1024;
3532
+ }
3533
+ });
3534
+
3516
3535
  // src/statusWatcher.ts
3517
3536
  function startStatusWatcher(config) {
3518
3537
  const { apiConfig, getContext, onStatus, interval = 5e3, signal } = config;
@@ -4053,7 +4072,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
4053
4072
  subAgentMessages
4054
4073
  );
4055
4074
  }
4056
- safeSettle(result, result.startsWith("Error"));
4075
+ safeSettle(capToolResult(result), result.startsWith("Error"));
4057
4076
  } catch (err) {
4058
4077
  safeSettle(`Error: ${err.message}`, true);
4059
4078
  }
@@ -4189,6 +4208,7 @@ var init_runner = __esm({
4189
4208
  init_logger();
4190
4209
  init_usageLedger();
4191
4210
  init_toolRegistry();
4211
+ init_toolResultCap();
4192
4212
  init_statusWatcher();
4193
4213
  init_cleanMessages();
4194
4214
  log7 = createLogger("sub-agent");
@@ -4258,9 +4278,10 @@ var init_tools = __esm({
4258
4278
  "evaluate",
4259
4279
  "styles",
4260
4280
  "screenshotFullPage",
4261
- "screenshotViewport"
4281
+ "screenshotViewport",
4282
+ "setViewport"
4262
4283
  ],
4263
- description: "snapshot: accessibility tree of the page (waits for network to settle). click: click an element (animated cursor, full event sequence). type: type text into input (one char at a time, works with React/Vue/Svelte). select: select a dropdown option by text. wait: wait for an element to appear (polls 100ms, waits for network). navigate: navigate to a URL within the app (waits for load, subsequent steps run on new page). evaluate: run JS in the page. styles: read computed CSS styles from elements (pass properties array with camelCase names, or omit for defaults). screenshotFullPage: full-page viewport-stitched screenshot (returns CDN url with dimensions). screenshotViewport: screenshot of just the visible viewport \u2014 pass `scrollToSelector` (or `scrollY`) on this step to scroll a section into view and capture it in one atomic step (no separate scroll needed)."
4284
+ description: 'snapshot: accessibility tree of the page (waits for network to settle). click: click an element (animated cursor, full event sequence). type: type text into input (one char at a time, works with React/Vue/Svelte). select: select a dropdown option by text. wait: wait for an element to appear (polls 100ms, waits for network). navigate: navigate to a URL within the app (waits for load, subsequent steps run on new page). evaluate: run JS in the page. styles: read computed CSS styles from elements (pass properties array with camelCase names, or omit for defaults). screenshotFullPage: full-page viewport-stitched screenshot (returns CDN url with dimensions). screenshotViewport: screenshot of just the visible viewport \u2014 pass `scrollToSelector` (or `scrollY`) on this step to scroll a section into view and capture it in one atomic step (no separate scroll needed). setViewport: switch the browser between desktop and mobile rendering (pass `mode`: "desktop" or "mobile"). Reloads the page so responsive layouts, media queries, and matchMedia re-evaluate \u2014 use it to QA mobile/responsive views.'
4264
4285
  },
4265
4286
  ref: {
4266
4287
  type: "string",
@@ -4314,6 +4335,11 @@ var init_tools = __esm({
4314
4335
  scrollY: {
4315
4336
  type: "number",
4316
4337
  description: "For screenshotViewport: absolute Y offset to scroll to before the shot, when no selector is available."
4338
+ },
4339
+ mode: {
4340
+ type: "string",
4341
+ enum: ["desktop", "mobile"],
4342
+ description: 'For setViewport: the rendering mode to switch to. "mobile" emulates a phone (narrow width, touch, device pixel ratio); "desktop" is the standard wide viewport.'
4317
4343
  }
4318
4344
  },
4319
4345
  required: ["command"]
@@ -4371,6 +4397,14 @@ var init_prompt2 = __esm({
4371
4397
  async function runBrowserAutomation(task, context, opts) {
4372
4398
  const release = await acquireBrowserLock();
4373
4399
  try {
4400
+ try {
4401
+ await sidecarRequest(
4402
+ "/set-viewport",
4403
+ { mode: "default" },
4404
+ { timeout: 2e4 }
4405
+ );
4406
+ } catch {
4407
+ }
4374
4408
  let lastBrowserCommandViewport;
4375
4409
  const result = await runSubAgent({
4376
4410
  system: getBrowserAutomationPrompt(),
@@ -5988,7 +6022,48 @@ var init_history = __esm({
5988
6022
  });
5989
6023
 
5990
6024
  // src/subagents/designExpert/index.ts
5991
- var DESCRIPTION, designExpertTool;
6025
+ async function runDesignExpert(opts, context) {
6026
+ const history = context.conversationMessages ? getSubAgentHistory(context.conversationMessages, "visualDesignExpert") : [];
6027
+ return runSubAgent({
6028
+ system: getDesignExpertPrompt(context.onboardingState),
6029
+ task: opts.task,
6030
+ history: history.length > 0 ? history : void 0,
6031
+ tools: opts.enableWrite ? DESIGN_EXPERT_RENDER_TOOLS : DESIGN_EXPERT_TOOLS,
6032
+ externalTools: /* @__PURE__ */ new Set(),
6033
+ executeTool: (name, input, toolCallId, onLog, sams) => {
6034
+ const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
6035
+ if (COMMON_READ_TOOL_NAMES.has(name)) {
6036
+ return executeTool(name, input, childCtx);
6037
+ }
6038
+ if (opts.enableWrite && RENDER_WRITE_TOOL_NAMES.has(name)) {
6039
+ return executeTool(name, input, childCtx);
6040
+ }
6041
+ return executeDesignExpertTool(name, input, childCtx, toolCallId, onLog);
6042
+ },
6043
+ apiConfig: context.apiConfig,
6044
+ model: resolveModel("visualDesignExpert", context.models, context.model),
6045
+ subAgentId: "visualDesignExpert",
6046
+ signal: context.signal,
6047
+ parentToolId: context.toolCallId,
6048
+ requestId: context.requestId,
6049
+ onEvent: context.onEvent,
6050
+ resolveExternalTool: context.resolveExternalTool,
6051
+ toolRegistry: context.toolRegistry,
6052
+ background: opts.background,
6053
+ onBackgroundComplete: opts.background ? (bgResult) => {
6054
+ context.onBackgroundComplete?.(
6055
+ context.toolCallId,
6056
+ opts.reportingName ?? "visualDesignExpert",
6057
+ bgResult.text,
6058
+ bgResult.messages
6059
+ );
6060
+ } : void 0
6061
+ });
6062
+ }
6063
+ async function runDesignExpertRender(opts, context) {
6064
+ return runDesignExpert({ ...opts, enableWrite: true }, context);
6065
+ }
6066
+ var DESCRIPTION, RENDER_WRITE_TOOL_NAMES, DESIGN_EXPERT_RENDER_TOOLS, designExpertTool;
5992
6067
  var init_designExpert = __esm({
5993
6068
  "src/subagents/designExpert/index.ts"() {
5994
6069
  "use strict";
@@ -5999,9 +6074,17 @@ var init_designExpert = __esm({
5999
6074
  init_prompt3();
6000
6075
  init_history();
6001
6076
  init_surfaces();
6077
+ init_writeFile();
6078
+ init_editFile();
6002
6079
  DESCRIPTION = `
6003
6080
  Visual design expert. Describe the situation and what you need \u2014 the agent decides what to deliver. It reads the spec files automatically. Include relevant user requirements and context it can't get from the spec, but do not list specific deliverables or tell it how to do its job. Do not suggest implementation details or ideas - only relay what is needed.
6004
6081
  `.trim();
6082
+ RENDER_WRITE_TOOL_NAMES = /* @__PURE__ */ new Set(["writeFile", "editFile"]);
6083
+ DESIGN_EXPERT_RENDER_TOOLS = [
6084
+ ...DESIGN_EXPERT_TOOLS,
6085
+ writeFileTool.definition,
6086
+ editFileTool.definition
6087
+ ];
6005
6088
  designExpertTool = {
6006
6089
  clearable: false,
6007
6090
  definition: {
@@ -6026,45 +6109,13 @@ Visual design expert. Describe the situation and what you need \u2014 the agent
6026
6109
  if (!context) {
6027
6110
  return "Error: visual design expert requires execution context";
6028
6111
  }
6029
- const history = context.conversationMessages ? getSubAgentHistory(context.conversationMessages, "visualDesignExpert") : [];
6030
- const result = await runSubAgent({
6031
- system: getDesignExpertPrompt(context.onboardingState),
6032
- task: input.task,
6033
- history: history.length > 0 ? history : void 0,
6034
- tools: DESIGN_EXPERT_TOOLS,
6035
- externalTools: /* @__PURE__ */ new Set(),
6036
- executeTool: (name, input2, toolCallId, onLog, sams) => {
6037
- const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
6038
- if (COMMON_READ_TOOL_NAMES.has(name)) {
6039
- return executeTool(name, input2, childCtx);
6040
- }
6041
- return executeDesignExpertTool(
6042
- name,
6043
- input2,
6044
- childCtx,
6045
- toolCallId,
6046
- onLog
6047
- );
6112
+ const result = await runDesignExpert(
6113
+ {
6114
+ task: input.task,
6115
+ background: input.background
6048
6116
  },
6049
- apiConfig: context.apiConfig,
6050
- model: resolveModel("visualDesignExpert", context.models, context.model),
6051
- subAgentId: "visualDesignExpert",
6052
- signal: context.signal,
6053
- parentToolId: context.toolCallId,
6054
- requestId: context.requestId,
6055
- onEvent: context.onEvent,
6056
- resolveExternalTool: context.resolveExternalTool,
6057
- toolRegistry: context.toolRegistry,
6058
- background: input.background,
6059
- onBackgroundComplete: input.background ? (bgResult) => {
6060
- context.onBackgroundComplete?.(
6061
- context.toolCallId,
6062
- "visualDesignExpert",
6063
- bgResult.text,
6064
- bgResult.messages
6065
- );
6066
- } : void 0
6067
- });
6117
+ context
6118
+ );
6068
6119
  context.subAgentMessages?.set(context.toolCallId, result.messages);
6069
6120
  return result.text;
6070
6121
  }
@@ -6177,16 +6228,25 @@ ${unifiedDiff(filePath, oldContent, "")}`;
6177
6228
  const filePath = resolve("pitch.html");
6178
6229
  try {
6179
6230
  fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
6180
- const existing = fs18.existsSync(filePath) ? fs18.readFileSync(filePath, "utf-8").trim() : "";
6181
- const currentDeck = existing || PITCH_DECK_SHELL;
6231
+ const exists = fs18.existsSync(filePath);
6232
+ const before = exists ? fs18.statSync(filePath).mtimeMs : null;
6233
+ const delivery = exists ? `### Your deliverable
6234
+ The pitch deck already exists at \`${filePath}\`. Read it, then update it for the new <pitch_content>, keeping the presentation scaffolding intact \u2014 change only what needs to change.
6235
+
6236
+ Reply with a one-line summary of what you changed \u2014 not the HTML.` : `### Your deliverable
6237
+ Write the complete pitch deck to \`${filePath}\`. It does not exist yet \u2014 start from this scaffold, keeping its progress bar, chevron navigation, keyboard navigation, and transition mechanics intact:
6238
+
6239
+ <pitch_deck_shell>
6240
+ ${PITCH_DECK_SHELL}
6241
+ </pitch_deck_shell>
6242
+
6243
+ Reply with a one-line summary of what you wrote \u2014 not the HTML.`;
6182
6244
  const task = `
6183
6245
  <pitch_content>${input.task}</pitch_content>
6184
6246
 
6185
- <current_deck>${currentDeck}</current_deck>
6186
-
6187
6247
  We are building the pitch deck for the app. Using the provided <pitch_content>, as well as the app's spec data, think about what would make a compelling, interactive, self-contained horizontally-scrolling HTML slide deck for this product. Keep it simple, clean, powerful. Giant text, large logo, big, bold stats and claims. Edit the content as necessary to create the most impactful, bold, and beautiful slides. This should not feel like an essay, and it should not feel like a landing page \u2014 it should feel like a modern interactive presentation that leaves the user wowed by the product and excited about its future.
6188
6248
 
6189
- Use <current_deck> as your starting point and replace or update the content as needed, maintaining the bones of the presentation scaffolding. Always keep the progress bar, chevron navigation, and keyboard navigation - they are part of the scaffold.
6249
+ Maintain the bones of the presentation scaffolding. Always keep the progress bar, chevron navigation, and keyboard navigation - they are part of the scaffold.
6190
6250
 
6191
6251
  ### Rules
6192
6252
  - The deck must be a single HTML file \u2014 it will be rendered in an iframe.
@@ -6196,14 +6256,18 @@ Use <current_deck> as your starting point and replace or update the content as n
6196
6256
  - Code must be clean, bug free, and easy to parse. Use GSAP for animations. Pay close attention to layout and alignment to make sure everything is perfect.
6197
6257
  - Keep the progress bar and edge chevrons from the shell \u2014 they are part of the navigation UX.
6198
6258
 
6199
- Respond only with the complete HTML file and absolutely no other text. Your response will be written directly to an html file.`;
6200
- const result = await designExpertTool.execute({ task }, context);
6201
- const htmlMatch = result.match(
6202
- /```(?:html|wireframe)\n([\s\S]*?)```/
6203
- );
6204
- const html = htmlMatch ? htmlMatch[1].trim() : result;
6205
- fs18.writeFileSync(filePath, html, "utf-8");
6206
- return `Pitch deck written successfully.`;
6259
+ ${delivery}`;
6260
+ const result = await runDesignExpertRender({ task }, context);
6261
+ context.subAgentMessages?.set(context.toolCallId, result.messages);
6262
+ if (!fs18.existsSync(filePath)) {
6263
+ return `Error: the design expert did not write ${filePath}. Its reply was:
6264
+ ${result.text}`;
6265
+ }
6266
+ if (before !== null && fs18.statSync(filePath).mtimeMs === before) {
6267
+ return `Error: the pitch deck at ${filePath} was not modified. The design expert's reply was:
6268
+ ${result.text}`;
6269
+ }
6270
+ return `Pitch deck written to ${filePath}. ${result.text}`;
6207
6271
  } catch (err) {
6208
6272
  return `Error generating pitch deck: ${err.message}`;
6209
6273
  }
@@ -6498,6 +6562,22 @@ var init_scrapeWebUrl2 = __esm({
6498
6562
 
6499
6563
  // src/tools/spec/writeBuildOverview.ts
6500
6564
  import fs19 from "fs";
6565
+ function initialDelivery() {
6566
+ return `### Your deliverable
6567
+ Write the complete Build Overview to \`${OVERVIEW_FILE}\`. The file does not exist yet \u2014 start from this scaffold, which carries the technical hygiene for the iframed render context (keep the viewport meta and the reset). Layout, sections, type, and styling are yours to compose.
6568
+
6569
+ <overview_shell>
6570
+ ${OVERVIEW_SHELL}
6571
+ </overview_shell>
6572
+
6573
+ Reply with a one-line summary of what you wrote \u2014 not the HTML.`;
6574
+ }
6575
+ function refreshDelivery() {
6576
+ return `### Your deliverable
6577
+ The Build Overview already exists at \`${OVERVIEW_FILE}\`. Read it, then update it to reflect <overview_copy>, preserving its established skin \u2014 change only what the copy changed.
6578
+
6579
+ Reply with a one-line summary of what you changed \u2014 not the HTML.`;
6580
+ }
6501
6581
  var OVERVIEW_FILE, DESIGN_BRIEF, OVERVIEW_SHELL, buildOverviewTool;
6502
6582
  var init_writeBuildOverview = __esm({
6503
6583
  "src/tools/spec/writeBuildOverview.ts"() {
@@ -6506,7 +6586,7 @@ var init_writeBuildOverview = __esm({
6506
6586
  OVERVIEW_FILE = "src/overview.html";
6507
6587
  DESIGN_BRIEF = `We are building the Build Overview for this app \u2014 the home page of its Spec tab. It is a calm, dense, one-page reference of everything the app actually contains, including the parts the user can't see. It renders flush inside the Spec tab's content panel (the IDE supplies the surrounding nav).
6508
6588
 
6509
- Take the plain-language copy in <overview_copy> and lay it out and skin it into a single, beautiful, self-contained HTML document in the app's own brand. If <current_overview> is non-empty, use it as your starting point and preserve its established skin, updating only what the copy changed.
6589
+ Take the plain-language copy in <overview_copy> and lay it out and skin it into a single, beautiful, self-contained HTML document in the app's own brand.
6510
6590
 
6511
6591
  ### The single hard rule
6512
6592
  The copy in <overview_copy> is final \u2014 it was authored and edited before it reached you. Treat it as locked content to typeset, not a draft to improve. Reproduce the words exactly: do not rewrite, rephrase, shorten, expand, reorder, or "polish" them, and do not run them through any copy tool. This is the opposite of your usual role \u2014 here you own layout, typography, and visual design only, and the words (every number, name, label, claim, and sentence) are fixed. A single changed word or wrong number breaks this document's purpose.
@@ -6521,9 +6601,7 @@ The copy in <overview_copy> is final \u2014 it was authored and edited before it
6521
6601
 
6522
6602
  ### Constraints
6523
6603
  - A single self-contained HTML file. Fonts may load from a CDN; everything else (CSS, the logo SVG) is inline.
6524
- - Responsive: fills the embedded panel width and collapses gracefully at narrow widths.
6525
-
6526
- Respond only with the complete HTML file and absolutely no other text. Your response will be written directly to src/overview.html.`;
6604
+ - Responsive: fills the embedded panel width and collapses gracefully at narrow widths.`;
6527
6605
  OVERVIEW_SHELL = `<!DOCTYPE html>
6528
6606
  <html lang="en">
6529
6607
  <head>
@@ -6568,21 +6646,29 @@ Respond only with the complete HTML file and absolutely no other text. Your resp
6568
6646
  if (!content) {
6569
6647
  return "Error: writeBuildOverview requires non-empty `content` (the overview copy).";
6570
6648
  }
6571
- try {
6572
- const existing = fs19.existsSync(OVERVIEW_FILE) ? fs19.readFileSync(OVERVIEW_FILE, "utf-8").trim() : "";
6573
- const currentOverview = existing || OVERVIEW_SHELL;
6574
- const task = `<overview_copy>${content}</overview_copy>
6649
+ const exists = fs19.existsSync(OVERVIEW_FILE);
6650
+ const task = `<overview_copy>${content}</overview_copy>
6575
6651
 
6576
- <current_overview>${currentOverview}</current_overview>
6652
+ ${DESIGN_BRIEF}
6577
6653
 
6578
- ${DESIGN_BRIEF}`;
6579
- const result = await designExpertTool.execute({ task }, context);
6580
- const htmlMatch = result.match(
6581
- /```(?:html|wireframe)\n([\s\S]*?)```/
6582
- );
6583
- const html = htmlMatch ? htmlMatch[1].trim() : result;
6584
- fs19.writeFileSync(OVERVIEW_FILE, html, "utf-8");
6585
- return "Build overview written successfully to src/overview.html.";
6654
+ ${exists ? refreshDelivery() : initialDelivery()}`;
6655
+ try {
6656
+ if (exists) {
6657
+ input.background = true;
6658
+ const result2 = await runDesignExpertRender(
6659
+ { task, background: true, reportingName: "writeBuildOverview" },
6660
+ context
6661
+ );
6662
+ context.subAgentMessages?.set(context.toolCallId, result2.messages);
6663
+ return result2.text;
6664
+ }
6665
+ const result = await runDesignExpertRender({ task }, context);
6666
+ context.subAgentMessages?.set(context.toolCallId, result.messages);
6667
+ if (!fs19.existsSync(OVERVIEW_FILE)) {
6668
+ return `Error: the design expert did not write ${OVERVIEW_FILE}. Its reply was:
6669
+ ${result.text}`;
6670
+ }
6671
+ return `Build overview written to ${OVERVIEW_FILE}. ${result.text}`;
6586
6672
  } catch (err) {
6587
6673
  return `Error generating build overview: ${err.message}`;
6588
6674
  }
@@ -6703,25 +6789,6 @@ var init_tools7 = __esm({
6703
6789
  }
6704
6790
  });
6705
6791
 
6706
- // src/toolResultCap.ts
6707
- function capToolResult(result) {
6708
- const total = Buffer.byteLength(result, "utf-8");
6709
- if (total <= MAX_TOOL_RESULT_BYTES) {
6710
- return result;
6711
- }
6712
- const head = Buffer.from(result, "utf-8").subarray(0, MAX_TOOL_RESULT_BYTES).toString("utf-8");
6713
- return head + `
6714
-
6715
- (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.)`;
6716
- }
6717
- var MAX_TOOL_RESULT_BYTES;
6718
- var init_toolResultCap = __esm({
6719
- "src/toolResultCap.ts"() {
6720
- "use strict";
6721
- MAX_TOOL_RESULT_BYTES = 256 * 1024;
6722
- }
6723
- });
6724
-
6725
6792
  // src/session.ts
6726
6793
  import fs20 from "fs";
6727
6794
  import path9 from "path";
@@ -44,6 +44,7 @@ Note: the snapshot concatenates inline text and strips whitespace. If you need t
44
44
  - `evaluate`: Run arbitrary JavaScript in the page and return the result.
45
45
  - `styles`: Read computed CSS styles from page elements. Pass a `properties` array with camelCase CSS property names (e.g., `["backgroundColor", "borderRadius", "fontSize"]`). Omit `properties` for a default set covering colors, typography, spacing, borders, shadows, dimensions, and layout. Uses the same targeting as click/type (ref, text, role, label, selector). Omit the target to get styles for all elements from the last snapshot.
46
46
  - `screenshotViewport`: Take a screenshot of the visible viewport. Returns CDN url with full text analysis and dimensions. To capture a specific section, set `scrollToSelector` (a CSS selector) — or `scrollY` (an absolute offset) — on this same step; it scrolls the target into view and captures it atomically, so you do NOT need a separate scroll step. Do not use if you can get what you need with other tools - only use when you need to visually see the viewport.
47
+ - `setViewport`: Switch the browser between desktop and mobile rendering. Set `mode` to `"desktop"` or `"mobile"`. Mobile emulates a phone (390-wide, touch, device pixel ratio 2); desktop is the standard wide viewport. This reloads the page so media queries, responsive layouts, and `matchMedia` re-evaluate — the reload clears in-page state, so switch before you set up the state you want to inspect. The mode persists across navigations within a run. Each run starts in the app's default mode, so only use this when you need to check the other one.
47
48
 
48
49
  ### Element targeting (tried in order)
49
50
 
@@ -118,6 +119,16 @@ Capture a specific below-the-fold section (scroll + capture in one atomic step):
118
119
  }
119
120
  ```
120
121
 
122
+ Check the mobile layout of a page:
123
+ ```json
124
+ {
125
+ "steps": [
126
+ { "command": "setViewport", "mode": "mobile" },
127
+ { "command": "screenshotViewport" }
128
+ ]
129
+ }
130
+ ```
131
+
121
132
  Navigate to a sub-page and interact with it:
122
133
  ```json
123
134
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.230",
3
+ "version": "0.1.232",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",