@mindstudio-ai/remy 0.1.299 → 0.1.301

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
@@ -942,9 +942,9 @@ function buildBackgroundResultsMessage(results) {
942
942
  ${r.result}
943
943
  </tool_result>`
944
944
  ).join("\n\n");
945
- const plural = results.length > 1 ? "s" : "";
946
- const body = `This is an automated message containing the result${plural} of ${results.length > 1 ? "tool calls" : "a tool call"} that ${results.length > 1 ? "have" : "has"} been working in the background. This is not a direct message from the user.
947
- <background_results>
945
+ const body = `<background_results>
946
+ Automated delivery of results from background tool calls that completed while other work was in progress. This block is not from the user \u2014 anything outside it is.
947
+
948
948
  ${xml}
949
949
  </background_results>`;
950
950
  return automatedMessage("background_results", body);
@@ -1848,7 +1848,7 @@ var askMindStudioSdkTool = {
1848
1848
  async execute(input, context) {
1849
1849
  const query = input.query;
1850
1850
  const result = await runCli("mindstudio", ["ask", query], {
1851
- timeout: 2e5,
1851
+ timeout: 48e4,
1852
1852
  maxBuffer: 512 * 1024,
1853
1853
  onLog: context?.onLog
1854
1854
  });
@@ -2757,8 +2757,8 @@ function formatSize(bytes) {
2757
2757
  }
2758
2758
  async function formatFile(dirPath, name, indent) {
2759
2759
  try {
2760
- const stat3 = await fs16.stat(path8.join(dirPath, name));
2761
- return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat3.size)}`;
2760
+ const stat4 = await fs16.stat(path8.join(dirPath, name));
2761
+ return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat4.size)}`;
2762
2762
  } catch {
2763
2763
  return `${indent}${name}`;
2764
2764
  }
@@ -3787,6 +3787,7 @@ async function runSubAgent(config) {
3787
3787
  acquireLock,
3788
3788
  onBackgroundComplete,
3789
3789
  captureArtifacts,
3790
+ validateResult,
3790
3791
  cachePolicy = "run"
3791
3792
  } = config;
3792
3793
  const artifacts = {};
@@ -3807,6 +3808,7 @@ async function runSubAgent(config) {
3807
3808
 
3808
3809
  Current date: ${dateStr}`;
3809
3810
  let turns = 0;
3811
+ let validationRetried = false;
3810
3812
  const run = async () => {
3811
3813
  const historyLen = (history ?? []).length;
3812
3814
  const subAgentMessages = /* @__PURE__ */ new Map();
@@ -4010,8 +4012,41 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
4010
4012
  (b) => b.type === "tool"
4011
4013
  );
4012
4014
  if (stopReason !== "tool_use" || toolCalls.length === 0) {
4015
+ let text = getPartialText(contentBlocks);
4016
+ if (validateResult) {
4017
+ let objection = null;
4018
+ try {
4019
+ objection = await validateResult(text, thisInvocation());
4020
+ } catch (err) {
4021
+ log7.warn("Result validator failed, accepting response", {
4022
+ requestId,
4023
+ parentToolId,
4024
+ agentName,
4025
+ error: err.message
4026
+ });
4027
+ }
4028
+ if (objection && !validationRetried && !signal?.aborted) {
4029
+ validationRetried = true;
4030
+ log7.info("Result rejected by validator, retrying once", {
4031
+ requestId,
4032
+ parentToolId,
4033
+ agentName,
4034
+ objection: objection.slice(0, 200)
4035
+ });
4036
+ emit({
4037
+ type: "status",
4038
+ message: "Response failed validation, revising"
4039
+ });
4040
+ messages.push({ role: "user", content: objection });
4041
+ continue;
4042
+ }
4043
+ if (objection) {
4044
+ text = `${text}
4045
+
4046
+ [Response validator: ${objection}]`;
4047
+ }
4048
+ }
4013
4049
  statusWatcher.stop();
4014
- const text = getPartialText(contentBlocks);
4015
4050
  const hasArtifacts = Object.keys(artifacts).length > 0;
4016
4051
  return {
4017
4052
  text,
@@ -4570,7 +4605,7 @@ async function runBrowserAutomation(task, context, opts) {
4570
4605
  task,
4571
4606
  tools: BROWSER_TOOLS,
4572
4607
  externalTools: BROWSER_EXTERNAL_TOOLS,
4573
- executeTool: async (name, _input, _toolCallId, onLog) => {
4608
+ executeTool: async (name, _input, toolCallId, onLog) => {
4574
4609
  if (name === "setupBrowser") {
4575
4610
  try {
4576
4611
  const result2 = await sidecarRequest(
@@ -4587,7 +4622,11 @@ async function runBrowserAutomation(task, context, opts) {
4587
4622
  }
4588
4623
  }
4589
4624
  if (COMMON_READ_TOOL_NAMES.has(name) || name === readSpecTool.definition.name) {
4590
- return executeTool(name, _input, context);
4625
+ return executeTool(
4626
+ name,
4627
+ _input,
4628
+ toolCallId ? deriveContext(context, toolCallId, onLog) : context
4629
+ );
4591
4630
  }
4592
4631
  return `Error: unknown local tool "${name}"`;
4593
4632
  },
@@ -5556,7 +5595,13 @@ var copyEditorTool = {
5556
5595
  task: input.task,
5557
5596
  tools: COPY_EDITOR_TOOLS,
5558
5597
  externalTools: /* @__PURE__ */ new Set(),
5559
- executeTool: (name, toolInput) => executeTool(name, toolInput, context),
5598
+ executeTool: (name, toolInput, toolCallId, onLog, sams) => {
5599
+ const childCtx = toolCallId ? {
5600
+ ...deriveContext(context, toolCallId, onLog),
5601
+ subAgentMessages: sams
5602
+ } : context;
5603
+ return executeTool(name, toolInput, childCtx);
5604
+ },
5560
5605
  apiConfig: context.apiConfig,
5561
5606
  model: resolveModel("copyEditor", context.models, context.model),
5562
5607
  subAgentId: "copyEditor",
@@ -5824,7 +5869,7 @@ async function executeDesignExpertTool(name, input, context, toolCallId, onLog)
5824
5869
  if (!tool) {
5825
5870
  return `Error: unknown tool "${name}"`;
5826
5871
  }
5827
- const childContext = context && toolCallId ? deriveContext(context, toolCallId) : context;
5872
+ const childContext = context && toolCallId ? deriveContext(context, toolCallId, onLog) : context;
5828
5873
  return tool.execute(input, onLog, childContext);
5829
5874
  }
5830
5875
 
@@ -6045,6 +6090,38 @@ ${RENDER_TASK_BLOCK}`;
6045
6090
  return prompt;
6046
6091
  }
6047
6092
 
6093
+ // src/subagents/designExpert/validateWireframeRefs.ts
6094
+ import { stat as stat3 } from "fs/promises";
6095
+ import { join as join3 } from "path";
6096
+ var WIREFRAME_REF_RE = /src\/\.wireframes\/[a-z0-9][a-z0-9-]*\.html/g;
6097
+ async function validateWireframeRefs(text) {
6098
+ const refs = [...new Set(text.match(WIREFRAME_REF_RE) ?? [])];
6099
+ if (refs.length === 0) {
6100
+ return null;
6101
+ }
6102
+ const missing = [];
6103
+ for (const ref of refs) {
6104
+ const exists = await stat3(join3(PROJECT_ROOT, ref)).then(
6105
+ (s) => s.isFile(),
6106
+ () => false
6107
+ );
6108
+ if (!exists) {
6109
+ missing.push(ref);
6110
+ }
6111
+ }
6112
+ if (missing.length === 0) {
6113
+ return null;
6114
+ }
6115
+ return [
6116
+ `Your response references wireframe files that do not exist on disk:`,
6117
+ ...missing.map((p) => `- ${p}`),
6118
+ ``,
6119
+ `A wireframe reference is only valid as a receipt handed back by a createWireframe result \u2014 that tool is the only way a wireframe comes to exist. A reference composed in prose points at nothing and renders as a dead preview.`,
6120
+ ``,
6121
+ `For each missing path, either author that wireframe now with createWireframe (use the matching slug so the path is identical) or remove the reference. Then send your complete final response again from the top \u2014 it fully replaces your previous response, so include everything, not just the fixes.`
6122
+ ].join("\n");
6123
+ }
6124
+
6048
6125
  // src/subagents/common/history.ts
6049
6126
  function getSubAgentHistory(messages, subAgentName) {
6050
6127
  let checkpointIdx = -1;
@@ -6112,7 +6189,10 @@ async function runDesignExpert(opts, context) {
6112
6189
  tools: opts.render ? DESIGN_EXPERT_RENDER_TOOLS : DESIGN_EXPERT_TOOLS,
6113
6190
  externalTools: /* @__PURE__ */ new Set(),
6114
6191
  executeTool: (name, input, toolCallId, onLog, sams) => {
6115
- const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
6192
+ const childCtx = toolCallId ? {
6193
+ ...deriveContext(context, toolCallId, onLog),
6194
+ subAgentMessages: sams
6195
+ } : { ...context, subAgentMessages: sams };
6116
6196
  if (COMMON_READ_TOOL_NAMES.has(name)) {
6117
6197
  return executeTool(name, input, childCtx);
6118
6198
  }
@@ -6130,6 +6210,10 @@ async function runDesignExpert(opts, context) {
6130
6210
  onEvent: context.onEvent,
6131
6211
  resolveExternalTool: context.resolveExternalTool,
6132
6212
  toolRegistry: context.toolRegistry,
6213
+ // Receipt guard: every wireframe reference in the final response must
6214
+ // resolve to a file on disk (see validateWireframeRefs.ts). Covers the
6215
+ // advisor tool, render mode, and both render callers.
6216
+ validateResult: (text) => validateWireframeRefs(text),
6133
6217
  background: opts.background,
6134
6218
  onBackgroundComplete: opts.background ? (bgResult) => {
6135
6219
  context.onBackgroundComplete?.(
@@ -6380,8 +6464,11 @@ var productVisionTool = {
6380
6464
  cachePolicy: "conversation",
6381
6465
  tools: VISION_TOOLS,
6382
6466
  externalTools: /* @__PURE__ */ new Set(),
6383
- executeTool: (name, input2, toolCallId, _onLog, sams) => {
6384
- const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
6467
+ executeTool: (name, input2, toolCallId, onLog, sams) => {
6468
+ const childCtx = toolCallId ? {
6469
+ ...deriveContext(context, toolCallId, onLog),
6470
+ subAgentMessages: sams
6471
+ } : { ...context, subAgentMessages: sams };
6385
6472
  if (COMMON_READ_TOOL_NAMES.has(name)) {
6386
6473
  return executeTool(name, input2, childCtx);
6387
6474
  }
@@ -6493,7 +6580,13 @@ var codeSanityCheckTool = {
6493
6580
  task: input.task,
6494
6581
  tools: SANITY_CHECK_TOOLS,
6495
6582
  externalTools: /* @__PURE__ */ new Set(),
6496
- executeTool: (name, toolInput) => executeTool(name, toolInput, context),
6583
+ executeTool: (name, toolInput, toolCallId, onLog, sams) => {
6584
+ const childCtx = toolCallId ? {
6585
+ ...deriveContext(context, toolCallId, onLog),
6586
+ subAgentMessages: sams
6587
+ } : context;
6588
+ return executeTool(name, toolInput, childCtx);
6589
+ },
6497
6590
  apiConfig: context.apiConfig,
6498
6591
  model: resolveModel("codeSanityCheck", context.models, context.model),
6499
6592
  subAgentId: "codeSanityCheck",
@@ -6703,16 +6796,19 @@ After reconciling the spec, refresh the Build Overview: author the complete upda
6703
6796
  task,
6704
6797
  tools: tools2,
6705
6798
  externalTools: /* @__PURE__ */ new Set(),
6706
- executeTool: (name, toolInput, toolCallId, _onLog, sams) => {
6799
+ executeTool: (name, toolInput, toolCallId, onLog, sams) => {
6800
+ const childCtx = toolCallId ? {
6801
+ ...deriveContext(context, toolCallId, onLog),
6802
+ subAgentMessages: sams
6803
+ } : { ...context, subAgentMessages: sams };
6707
6804
  if (name === "writeBuildOverview") {
6708
- const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
6709
6805
  return renderBuildOverview(
6710
6806
  String(toolInput.content ?? "").trim(),
6711
6807
  childCtx,
6712
6808
  { background: false }
6713
6809
  );
6714
6810
  }
6715
- return executeTool(name, toolInput, context);
6811
+ return executeTool(name, toolInput, childCtx);
6716
6812
  },
6717
6813
  apiConfig: context.apiConfig,
6718
6814
  model: resolveModel("specSync", context.models, context.model),
@@ -6786,8 +6882,8 @@ var scrapeWebUrlTool = {
6786
6882
  };
6787
6883
 
6788
6884
  // src/tools/index.ts
6789
- function deriveContext(parent, toolCallId) {
6790
- return { ...parent, toolCallId };
6885
+ function deriveContext(parent, toolCallId, onLog) {
6886
+ return { ...parent, toolCallId, onLog };
6791
6887
  }
6792
6888
  var ALL_TOOLS = [
6793
6889
  // Common
@@ -9058,7 +9154,7 @@ async function runTurn(params) {
9058
9154
  // src/headless/attachments.ts
9059
9155
  import { mkdirSync, existsSync } from "fs";
9060
9156
  import { writeFile as writeFile3 } from "fs/promises";
9061
- import { basename as basename2, join as join3, extname as extname2 } from "path";
9157
+ import { basename as basename2, join as join4, extname as extname2 } from "path";
9062
9158
  var log16 = createLogger("headless:attachments");
9063
9159
  var UPLOADS_DIR = "src/.user-uploads";
9064
9160
  function filenameFromUrl(url) {
@@ -9071,7 +9167,7 @@ function filenameFromUrl(url) {
9071
9167
  }
9072
9168
  }
9073
9169
  function resolveUniqueFilename(name, claimed) {
9074
- const isFree = (candidate) => !claimed.has(candidate) && !existsSync(join3(UPLOADS_DIR, candidate));
9170
+ const isFree = (candidate) => !claimed.has(candidate) && !existsSync(join4(UPLOADS_DIR, candidate));
9075
9171
  if (isFree(name)) {
9076
9172
  return name;
9077
9173
  }
@@ -9106,7 +9202,7 @@ async function persistAttachments(attachments) {
9106
9202
  const results = await Promise.allSettled(
9107
9203
  nonVoice.map(async (att, i) => {
9108
9204
  const name = names[i];
9109
- const localPath = join3(UPLOADS_DIR, name);
9205
+ const localPath = join4(UPLOADS_DIR, name);
9110
9206
  const res = await fetch(att.url, {
9111
9207
  signal: AbortSignal.timeout(3e4)
9112
9208
  });
package/dist/index.js CHANGED
@@ -1379,7 +1379,7 @@ var init_sdkConsultant = __esm({
1379
1379
  async execute(input, context) {
1380
1380
  const query = input.query;
1381
1381
  const result = await runCli("mindstudio", ["ask", query], {
1382
- timeout: 2e5,
1382
+ timeout: 48e4,
1383
1383
  maxBuffer: 512 * 1024,
1384
1384
  onLog: context?.onLog
1385
1385
  });
@@ -1660,9 +1660,9 @@ function buildBackgroundResultsMessage(results) {
1660
1660
  ${r.result}
1661
1661
  </tool_result>`
1662
1662
  ).join("\n\n");
1663
- const plural = results.length > 1 ? "s" : "";
1664
- const body = `This is an automated message containing the result${plural} of ${results.length > 1 ? "tool calls" : "a tool call"} that ${results.length > 1 ? "have" : "has"} been working in the background. This is not a direct message from the user.
1665
- <background_results>
1663
+ const body = `<background_results>
1664
+ Automated delivery of results from background tool calls that completed while other work was in progress. This block is not from the user \u2014 anything outside it is.
1665
+
1666
1666
  ${xml}
1667
1667
  </background_results>`;
1668
1668
  return automatedMessage("background_results", body);
@@ -3905,8 +3905,8 @@ function formatSize(bytes) {
3905
3905
  }
3906
3906
  async function formatFile(dirPath, name, indent) {
3907
3907
  try {
3908
- const stat3 = await fs15.stat(path8.join(dirPath, name));
3909
- return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat3.size)}`;
3908
+ const stat4 = await fs15.stat(path8.join(dirPath, name));
3909
+ return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat4.size)}`;
3910
3910
  } catch {
3911
3911
  return `${indent}${name}`;
3912
3912
  }
@@ -4822,6 +4822,7 @@ async function runSubAgent(config) {
4822
4822
  acquireLock,
4823
4823
  onBackgroundComplete,
4824
4824
  captureArtifacts,
4825
+ validateResult,
4825
4826
  cachePolicy = "run"
4826
4827
  } = config;
4827
4828
  const artifacts = {};
@@ -4842,6 +4843,7 @@ async function runSubAgent(config) {
4842
4843
 
4843
4844
  Current date: ${dateStr}`;
4844
4845
  let turns = 0;
4846
+ let validationRetried = false;
4845
4847
  const run = async () => {
4846
4848
  const historyLen = (history ?? []).length;
4847
4849
  const subAgentMessages = /* @__PURE__ */ new Map();
@@ -5045,8 +5047,41 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
5045
5047
  (b) => b.type === "tool"
5046
5048
  );
5047
5049
  if (stopReason !== "tool_use" || toolCalls.length === 0) {
5050
+ let text = getPartialText(contentBlocks);
5051
+ if (validateResult) {
5052
+ let objection = null;
5053
+ try {
5054
+ objection = await validateResult(text, thisInvocation());
5055
+ } catch (err) {
5056
+ log8.warn("Result validator failed, accepting response", {
5057
+ requestId,
5058
+ parentToolId,
5059
+ agentName,
5060
+ error: err.message
5061
+ });
5062
+ }
5063
+ if (objection && !validationRetried && !signal?.aborted) {
5064
+ validationRetried = true;
5065
+ log8.info("Result rejected by validator, retrying once", {
5066
+ requestId,
5067
+ parentToolId,
5068
+ agentName,
5069
+ objection: objection.slice(0, 200)
5070
+ });
5071
+ emit({
5072
+ type: "status",
5073
+ message: "Response failed validation, revising"
5074
+ });
5075
+ messages.push({ role: "user", content: objection });
5076
+ continue;
5077
+ }
5078
+ if (objection) {
5079
+ text = `${text}
5080
+
5081
+ [Response validator: ${objection}]`;
5082
+ }
5083
+ }
5048
5084
  statusWatcher.stop();
5049
- const text = getPartialText(contentBlocks);
5050
5085
  const hasArtifacts = Object.keys(artifacts).length > 0;
5051
5086
  return {
5052
5087
  text,
@@ -5648,7 +5683,7 @@ async function runBrowserAutomation(task, context, opts) {
5648
5683
  task,
5649
5684
  tools: BROWSER_TOOLS,
5650
5685
  externalTools: BROWSER_EXTERNAL_TOOLS,
5651
- executeTool: async (name, _input, _toolCallId, onLog) => {
5686
+ executeTool: async (name, _input, toolCallId, onLog) => {
5652
5687
  if (name === "setupBrowser") {
5653
5688
  try {
5654
5689
  const result2 = await sidecarRequest(
@@ -5665,7 +5700,11 @@ async function runBrowserAutomation(task, context, opts) {
5665
5700
  }
5666
5701
  }
5667
5702
  if (COMMON_READ_TOOL_NAMES.has(name) || name === readSpecTool.definition.name) {
5668
- return executeTool(name, _input, context);
5703
+ return executeTool(
5704
+ name,
5705
+ _input,
5706
+ toolCallId ? deriveContext(context, toolCallId, onLog) : context
5707
+ );
5669
5708
  }
5670
5709
  return `Error: unknown local tool "${name}"`;
5671
5710
  },
@@ -6752,7 +6791,13 @@ var init_copyEditor = __esm({
6752
6791
  task: input.task,
6753
6792
  tools: COPY_EDITOR_TOOLS,
6754
6793
  externalTools: /* @__PURE__ */ new Set(),
6755
- executeTool: (name, toolInput) => executeTool(name, toolInput, context),
6794
+ executeTool: (name, toolInput, toolCallId, onLog, sams) => {
6795
+ const childCtx = toolCallId ? {
6796
+ ...deriveContext(context, toolCallId, onLog),
6797
+ subAgentMessages: sams
6798
+ } : context;
6799
+ return executeTool(name, toolInput, childCtx);
6800
+ },
6756
6801
  apiConfig: context.apiConfig,
6757
6802
  model: resolveModel("copyEditor", context.models, context.model),
6758
6803
  subAgentId: "copyEditor",
@@ -7034,7 +7079,7 @@ async function executeDesignExpertTool(name, input, context, toolCallId, onLog)
7034
7079
  if (!tool) {
7035
7080
  return `Error: unknown tool "${name}"`;
7036
7081
  }
7037
- const childContext = context && toolCallId ? deriveContext(context, toolCallId) : context;
7082
+ const childContext = context && toolCallId ? deriveContext(context, toolCallId, onLog) : context;
7038
7083
  return tool.execute(input, onLog, childContext);
7039
7084
  }
7040
7085
  var tools, DESIGN_EXPERT_TOOLS;
@@ -7403,6 +7448,45 @@ The guidance about specifying layouts in prose, writing implementation notes, an
7403
7448
  }
7404
7449
  });
7405
7450
 
7451
+ // src/subagents/designExpert/validateWireframeRefs.ts
7452
+ import { stat as stat3 } from "fs/promises";
7453
+ import { join as join3 } from "path";
7454
+ async function validateWireframeRefs(text) {
7455
+ const refs = [...new Set(text.match(WIREFRAME_REF_RE) ?? [])];
7456
+ if (refs.length === 0) {
7457
+ return null;
7458
+ }
7459
+ const missing = [];
7460
+ for (const ref of refs) {
7461
+ const exists = await stat3(join3(PROJECT_ROOT, ref)).then(
7462
+ (s) => s.isFile(),
7463
+ () => false
7464
+ );
7465
+ if (!exists) {
7466
+ missing.push(ref);
7467
+ }
7468
+ }
7469
+ if (missing.length === 0) {
7470
+ return null;
7471
+ }
7472
+ return [
7473
+ `Your response references wireframe files that do not exist on disk:`,
7474
+ ...missing.map((p) => `- ${p}`),
7475
+ ``,
7476
+ `A wireframe reference is only valid as a receipt handed back by a createWireframe result \u2014 that tool is the only way a wireframe comes to exist. A reference composed in prose points at nothing and renders as a dead preview.`,
7477
+ ``,
7478
+ `For each missing path, either author that wireframe now with createWireframe (use the matching slug so the path is identical) or remove the reference. Then send your complete final response again from the top \u2014 it fully replaces your previous response, so include everything, not just the fixes.`
7479
+ ].join("\n");
7480
+ }
7481
+ var WIREFRAME_REF_RE;
7482
+ var init_validateWireframeRefs = __esm({
7483
+ "src/subagents/designExpert/validateWireframeRefs.ts"() {
7484
+ "use strict";
7485
+ init_projectRoot();
7486
+ WIREFRAME_REF_RE = /src\/\.wireframes\/[a-z0-9][a-z0-9-]*\.html/g;
7487
+ }
7488
+ });
7489
+
7406
7490
  // src/subagents/common/history.ts
7407
7491
  function getSubAgentHistory(messages, subAgentName) {
7408
7492
  let checkpointIdx = -1;
@@ -7466,7 +7550,10 @@ async function runDesignExpert(opts, context) {
7466
7550
  tools: opts.render ? DESIGN_EXPERT_RENDER_TOOLS : DESIGN_EXPERT_TOOLS,
7467
7551
  externalTools: /* @__PURE__ */ new Set(),
7468
7552
  executeTool: (name, input, toolCallId, onLog, sams) => {
7469
- const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
7553
+ const childCtx = toolCallId ? {
7554
+ ...deriveContext(context, toolCallId, onLog),
7555
+ subAgentMessages: sams
7556
+ } : { ...context, subAgentMessages: sams };
7470
7557
  if (COMMON_READ_TOOL_NAMES.has(name)) {
7471
7558
  return executeTool(name, input, childCtx);
7472
7559
  }
@@ -7484,6 +7571,10 @@ async function runDesignExpert(opts, context) {
7484
7571
  onEvent: context.onEvent,
7485
7572
  resolveExternalTool: context.resolveExternalTool,
7486
7573
  toolRegistry: context.toolRegistry,
7574
+ // Receipt guard: every wireframe reference in the final response must
7575
+ // resolve to a file on disk (see validateWireframeRefs.ts). Covers the
7576
+ // advisor tool, render mode, and both render callers.
7577
+ validateResult: (text) => validateWireframeRefs(text),
7487
7578
  background: opts.background,
7488
7579
  onBackgroundComplete: opts.background ? (bgResult) => {
7489
7580
  context.onBackgroundComplete?.(
@@ -7507,6 +7598,7 @@ var init_designExpert = __esm({
7507
7598
  init_tools4();
7508
7599
  init_tools();
7509
7600
  init_prompt2();
7601
+ init_validateWireframeRefs();
7510
7602
  init_history();
7511
7603
  init_surfaces();
7512
7604
  init_writeFile();
@@ -7794,8 +7886,11 @@ var init_productVision = __esm({
7794
7886
  cachePolicy: "conversation",
7795
7887
  tools: VISION_TOOLS,
7796
7888
  externalTools: /* @__PURE__ */ new Set(),
7797
- executeTool: (name, input2, toolCallId, _onLog, sams) => {
7798
- const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
7889
+ executeTool: (name, input2, toolCallId, onLog, sams) => {
7890
+ const childCtx = toolCallId ? {
7891
+ ...deriveContext(context, toolCallId, onLog),
7892
+ subAgentMessages: sams
7893
+ } : { ...context, subAgentMessages: sams };
7799
7894
  if (COMMON_READ_TOOL_NAMES.has(name)) {
7800
7895
  return executeTool(name, input2, childCtx);
7801
7896
  }
@@ -7926,7 +8021,13 @@ var init_codeSanityCheck = __esm({
7926
8021
  task: input.task,
7927
8022
  tools: SANITY_CHECK_TOOLS,
7928
8023
  externalTools: /* @__PURE__ */ new Set(),
7929
- executeTool: (name, toolInput) => executeTool(name, toolInput, context),
8024
+ executeTool: (name, toolInput, toolCallId, onLog, sams) => {
8025
+ const childCtx = toolCallId ? {
8026
+ ...deriveContext(context, toolCallId, onLog),
8027
+ subAgentMessages: sams
8028
+ } : context;
8029
+ return executeTool(name, toolInput, childCtx);
8030
+ },
7930
8031
  apiConfig: context.apiConfig,
7931
8032
  model: resolveModel("codeSanityCheck", context.models, context.model),
7932
8033
  subAgentId: "codeSanityCheck",
@@ -8173,16 +8274,19 @@ After reconciling the spec, refresh the Build Overview: author the complete upda
8173
8274
  task,
8174
8275
  tools: tools2,
8175
8276
  externalTools: /* @__PURE__ */ new Set(),
8176
- executeTool: (name, toolInput, toolCallId, _onLog, sams) => {
8277
+ executeTool: (name, toolInput, toolCallId, onLog, sams) => {
8278
+ const childCtx = toolCallId ? {
8279
+ ...deriveContext(context, toolCallId, onLog),
8280
+ subAgentMessages: sams
8281
+ } : { ...context, subAgentMessages: sams };
8177
8282
  if (name === "writeBuildOverview") {
8178
- const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
8179
8283
  return renderBuildOverview(
8180
8284
  String(toolInput.content ?? "").trim(),
8181
8285
  childCtx,
8182
8286
  { background: false }
8183
8287
  );
8184
8288
  }
8185
- return executeTool(name, toolInput, context);
8289
+ return executeTool(name, toolInput, childCtx);
8186
8290
  },
8187
8291
  apiConfig: context.apiConfig,
8188
8292
  model: resolveModel("specSync", context.models, context.model),
@@ -8266,8 +8370,8 @@ var init_scrapeWebUrl2 = __esm({
8266
8370
  });
8267
8371
 
8268
8372
  // src/tools/index.ts
8269
- function deriveContext(parent, toolCallId) {
8270
- return { ...parent, toolCallId };
8373
+ function deriveContext(parent, toolCallId, onLog) {
8374
+ return { ...parent, toolCallId, onLog };
8271
8375
  }
8272
8376
  function getToolDefinitions() {
8273
8377
  return ALL_TOOLS.map((t) => t.definition);
@@ -9986,7 +10090,7 @@ var init_config = __esm({
9986
10090
  // src/headless/attachments.ts
9987
10091
  import { mkdirSync, existsSync } from "fs";
9988
10092
  import { writeFile as writeFile3 } from "fs/promises";
9989
- import { basename as basename2, join as join3, extname as extname2 } from "path";
10093
+ import { basename as basename2, join as join4, extname as extname2 } from "path";
9990
10094
  function filenameFromUrl(url) {
9991
10095
  try {
9992
10096
  const pathname = new URL(url).pathname;
@@ -9997,7 +10101,7 @@ function filenameFromUrl(url) {
9997
10101
  }
9998
10102
  }
9999
10103
  function resolveUniqueFilename(name, claimed) {
10000
- const isFree = (candidate) => !claimed.has(candidate) && !existsSync(join3(UPLOADS_DIR, candidate));
10104
+ const isFree = (candidate) => !claimed.has(candidate) && !existsSync(join4(UPLOADS_DIR, candidate));
10001
10105
  if (isFree(name)) {
10002
10106
  return name;
10003
10107
  }
@@ -10031,7 +10135,7 @@ async function persistAttachments(attachments) {
10031
10135
  const results = await Promise.allSettled(
10032
10136
  nonVoice.map(async (att, i) => {
10033
10137
  const name = names[i];
10034
- const localPath = join3(UPLOADS_DIR, name);
10138
+ const localPath = join4(UPLOADS_DIR, name);
10035
10139
  const res = await fetch(att.url, {
10036
10140
  signal: AbortSignal.timeout(3e4)
10037
10141
  });
@@ -38,7 +38,8 @@ These are things we already know about and have decided to accept:
38
38
  - Preferences:
39
39
  - use [wouter](https://github.com/molefrog/wouter) for React routing instead of reaching for react-router
40
40
  - uploading user files should always happen via `platform.uploadFile()` from `@mindstudio-ai/interface` — not custom S3 code, not FormData to a method endpoint
41
- - for static prerendering of Vite + React sites, roll your own with a post-build `renderToString` script — do not use `vite-prerender-plugin` (it bundles the prerender script as a client chunk, adding ~800KB to the user-facing bundle with no way to prevent it)
41
+ - for build-time prerendering of purely static sites (marketing pages with no dynamic content — distinct from the platform's crawler prerendering, below), roll your own with a post-build `renderToString` script — do not use `vite-prerender-plugin` (it bundles the prerender script as a client chunk, adding ~800KB to the user-facing bundle with no way to prevent it)
42
+ - **Prerendering for crawlers/unfurlers is a platform feature — don't design around it.** For SEO / link-unfurl / AI-crawler visibility on a non-static SPA, the platform already handles it: routes opt in via `web.json` (`{ "web": { "prerender": { "paths": ["/blog/*"] } } }`), the SPA signals readiness by setting `data-prerender-ready` on the html element, deploys invalidate the snapshot cache automatically, and content that changes outside deploys is invalidated at runtime with `await prerender.invalidate([...])` from `@mindstudio-ai/agent` (called from the mutating method). It serves cached headless snapshots of the live SPA to bots — it is NOT build-time rendering. Do not recommend post-build render scripts, rebuild-on-content-change, or third-party prerender services for this. The developer's main context carries the full interfaces reference with exact semantics — tell them to consult it rather than improvising.
42
43
 
43
44
  ### Common pitfalls (always flag these)
44
45
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.299",
3
+ "version": "0.1.301",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",