@mindstudio-ai/remy 0.1.19 → 0.1.21

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.
Files changed (32) hide show
  1. package/dist/actions/buildFromInitialSpec.md +11 -3
  2. package/dist/compiled/design.md +2 -1
  3. package/dist/compiled/msfm.md +1 -0
  4. package/dist/compiled/sdk-actions.md +1 -3
  5. package/dist/headless.js +838 -306
  6. package/dist/index.js +952 -358
  7. package/dist/prompt/.notes.md +54 -0
  8. package/dist/prompt/actions/buildFromInitialSpec.md +11 -3
  9. package/dist/prompt/compiled/design.md +2 -1
  10. package/dist/prompt/compiled/msfm.md +1 -0
  11. package/dist/prompt/compiled/sdk-actions.md +1 -3
  12. package/dist/prompt/sources/frontend-design-notes.md +1 -0
  13. package/dist/prompt/static/authoring.md +4 -4
  14. package/dist/prompt/static/coding.md +5 -5
  15. package/dist/prompt/static/team.md +39 -0
  16. package/dist/static/authoring.md +4 -4
  17. package/dist/static/coding.md +5 -5
  18. package/dist/static/team.md +39 -0
  19. package/dist/subagents/browserAutomation/prompt.md +2 -0
  20. package/dist/subagents/codeSanityCheck/.notes.md +44 -0
  21. package/dist/subagents/codeSanityCheck/prompt.md +43 -0
  22. package/dist/subagents/designExpert/.notes.md +16 -4
  23. package/dist/subagents/designExpert/data/compile-inspiration.sh +2 -2
  24. package/dist/subagents/designExpert/prompts/frontend-design-notes.md +1 -0
  25. package/dist/subagents/designExpert/prompts/icons.md +18 -7
  26. package/dist/subagents/designExpert/prompts/identity.md +4 -4
  27. package/dist/subagents/designExpert/prompts/images.md +3 -2
  28. package/dist/subagents/designExpert/prompts/instructions.md +2 -2
  29. package/dist/subagents/designExpert/prompts/layout.md +4 -2
  30. package/dist/subagents/productVision/.notes.md +79 -0
  31. package/dist/subagents/productVision/prompt.md +29 -22
  32. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1012,23 +1012,46 @@ var init_confirmDestructiveAction = __esm({
1012
1012
  }
1013
1013
  });
1014
1014
 
1015
- // src/tools/common/askMindStudioSdk.ts
1015
+ // src/subagents/common/runCli.ts
1016
1016
  import { exec } from "child_process";
1017
+ function runCli(cmd, options) {
1018
+ return new Promise((resolve) => {
1019
+ exec(
1020
+ cmd,
1021
+ {
1022
+ timeout: options?.timeout ?? 6e4,
1023
+ maxBuffer: options?.maxBuffer ?? 1024 * 1024
1024
+ },
1025
+ (err, stdout, stderr) => {
1026
+ if (stdout.trim()) {
1027
+ resolve(stdout.trim());
1028
+ return;
1029
+ }
1030
+ if (err) {
1031
+ resolve(`Error: ${stderr.trim() || err.message}`);
1032
+ return;
1033
+ }
1034
+ resolve("(no response)");
1035
+ }
1036
+ );
1037
+ });
1038
+ }
1039
+ var init_runCli = __esm({
1040
+ "src/subagents/common/runCli.ts"() {
1041
+ "use strict";
1042
+ }
1043
+ });
1044
+
1045
+ // src/subagents/sdkConsultant/index.ts
1017
1046
  var askMindStudioSdkTool;
1018
- var init_askMindStudioSdk = __esm({
1019
- "src/tools/common/askMindStudioSdk.ts"() {
1047
+ var init_sdkConsultant = __esm({
1048
+ "src/subagents/sdkConsultant/index.ts"() {
1020
1049
  "use strict";
1050
+ init_runCli();
1021
1051
  askMindStudioSdkTool = {
1022
1052
  definition: {
1023
1053
  name: "askMindStudioSdk",
1024
- description: `An expert consultant on building with the MindStudio SDK. Knows every action, model, connector, and configuration option. Use this as an architect, not just a docs lookup:
1025
-
1026
- - Describe what you're trying to build at the method level ("I need a method that takes user text, generates a summary with GPT, extracts entities, and returns structured JSON") and get back architectural guidance + working code.
1027
- - Ask about AI orchestration patterns: structured output, chaining model calls, batch processing, streaming, error handling.
1028
- - Ask about connectors and integrations: what's available, whether the user has configured it, how to use it.
1029
- - Always use this before writing SDK code. Model IDs, config options, and action signatures change frequently. Don't guess.
1030
-
1031
- Batch related questions into a single query. This runs its own LLM call so it has a few seconds of latency.`,
1054
+ description: "MindStudio SDK expert. Knows every action, model, connector, and configuration option. Returns architectural guidance and working code. Describe what you want to build, not just what API method you need. Batch related questions into a single query.",
1032
1055
  inputSchema: {
1033
1056
  type: "object",
1034
1057
  properties: {
@@ -1042,22 +1065,8 @@ Batch related questions into a single query. This runs its own LLM call so it ha
1042
1065
  },
1043
1066
  async execute(input) {
1044
1067
  const query = input.query;
1045
- return new Promise((resolve) => {
1046
- exec(
1047
- `mindstudio ask ${JSON.stringify(query)}`,
1048
- { timeout: 6e4, maxBuffer: 512 * 1024 },
1049
- (err, stdout, stderr) => {
1050
- if (stdout.trim()) {
1051
- resolve(stdout.trim());
1052
- return;
1053
- }
1054
- if (err) {
1055
- resolve(`Error: ${stderr.trim() || err.message}`);
1056
- return;
1057
- }
1058
- resolve("(no response)");
1059
- }
1060
- );
1068
+ return runCli(`mindstudio ask ${JSON.stringify(query)}`, {
1069
+ maxBuffer: 512 * 1024
1061
1070
  });
1062
1071
  }
1063
1072
  };
@@ -1065,11 +1074,11 @@ Batch related questions into a single query. This runs its own LLM call so it ha
1065
1074
  });
1066
1075
 
1067
1076
  // src/tools/common/fetchUrl.ts
1068
- import { exec as exec2 } from "child_process";
1069
1077
  var fetchUrlTool;
1070
1078
  var init_fetchUrl = __esm({
1071
1079
  "src/tools/common/fetchUrl.ts"() {
1072
1080
  "use strict";
1081
+ init_runCli();
1073
1082
  fetchUrlTool = {
1074
1083
  definition: {
1075
1084
  name: "scapeWebUrl",
@@ -1096,35 +1105,20 @@ var init_fetchUrl = __esm({
1096
1105
  if (screenshot) {
1097
1106
  pageOptions.screenshot = true;
1098
1107
  }
1099
- const cmd = `mindstudio scrape-url --url ${JSON.stringify(url)} --page-options ${JSON.stringify(JSON.stringify(pageOptions))} --no-meta`;
1100
- return new Promise((resolve) => {
1101
- exec2(
1102
- cmd,
1103
- { timeout: 6e4, maxBuffer: 1024 * 1024 },
1104
- (err, stdout, stderr) => {
1105
- if (stdout.trim()) {
1106
- resolve(stdout.trim());
1107
- return;
1108
- }
1109
- if (err) {
1110
- resolve(`Error: ${stderr.trim() || err.message}`);
1111
- return;
1112
- }
1113
- resolve("(no response)");
1114
- }
1115
- );
1116
- });
1108
+ return runCli(
1109
+ `mindstudio scrape-url --url ${JSON.stringify(url)} --page-options ${JSON.stringify(JSON.stringify(pageOptions))} --no-meta`
1110
+ );
1117
1111
  }
1118
1112
  };
1119
1113
  }
1120
1114
  });
1121
1115
 
1122
1116
  // src/tools/common/searchGoogle.ts
1123
- import { exec as exec3 } from "child_process";
1124
1117
  var searchGoogleTool;
1125
1118
  var init_searchGoogle = __esm({
1126
1119
  "src/tools/common/searchGoogle.ts"() {
1127
1120
  "use strict";
1121
+ init_runCli();
1128
1122
  searchGoogleTool = {
1129
1123
  definition: {
1130
1124
  name: "searchGoogle",
@@ -1142,24 +1136,10 @@ var init_searchGoogle = __esm({
1142
1136
  },
1143
1137
  async execute(input) {
1144
1138
  const query = input.query;
1145
- const cmd = `mindstudio search-google --query ${JSON.stringify(query)} --export-type json --output-key results --no-meta`;
1146
- return new Promise((resolve) => {
1147
- exec3(
1148
- cmd,
1149
- { timeout: 6e4, maxBuffer: 512 * 1024 },
1150
- (err, stdout, stderr) => {
1151
- if (stdout.trim()) {
1152
- resolve(stdout.trim());
1153
- return;
1154
- }
1155
- if (err) {
1156
- resolve(`Error: ${stderr.trim() || err.message}`);
1157
- return;
1158
- }
1159
- resolve("(no response)");
1160
- }
1161
- );
1162
- });
1139
+ return runCli(
1140
+ `mindstudio search-google --query ${JSON.stringify(query)} --export-type json --output-key results --no-meta`,
1141
+ { maxBuffer: 512 * 1024 }
1142
+ );
1163
1143
  }
1164
1144
  };
1165
1145
  }
@@ -1516,7 +1496,7 @@ ${unifiedDiff(input.path, content, updated)}`;
1516
1496
  });
1517
1497
 
1518
1498
  // src/tools/code/bash.ts
1519
- import { exec as exec4 } from "child_process";
1499
+ import { exec as exec2 } from "child_process";
1520
1500
  var DEFAULT_TIMEOUT_MS, DEFAULT_MAX_LINES3, bashTool;
1521
1501
  var init_bash = __esm({
1522
1502
  "src/tools/code/bash.ts"() {
@@ -1554,7 +1534,7 @@ var init_bash = __esm({
1554
1534
  const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES3;
1555
1535
  const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
1556
1536
  return new Promise((resolve) => {
1557
- exec4(
1537
+ exec2(
1558
1538
  input.command,
1559
1539
  {
1560
1540
  timeout: timeoutMs,
@@ -1596,7 +1576,7 @@ var init_bash = __esm({
1596
1576
  });
1597
1577
 
1598
1578
  // src/tools/code/grep.ts
1599
- import { exec as exec5 } from "child_process";
1579
+ import { exec as exec3 } from "child_process";
1600
1580
  function formatResults(stdout, max) {
1601
1581
  const lines = stdout.trim().split("\n");
1602
1582
  let result = lines.join("\n");
@@ -1647,12 +1627,12 @@ var init_grep = __esm({
1647
1627
  const rgCmd = `rg -n --no-heading --max-count=${max}${globFlag} '${escaped}' ${searchPath}`;
1648
1628
  const grepCmd = `grep -rn --max-count=${max} '${escaped}' ${searchPath} --include='*.ts' --include='*.tsx' --include='*.js' --include='*.json' --include='*.md'`;
1649
1629
  return new Promise((resolve) => {
1650
- exec5(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
1630
+ exec3(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
1651
1631
  if (stdout?.trim()) {
1652
1632
  resolve(formatResults(stdout, max));
1653
1633
  return;
1654
1634
  }
1655
- exec5(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
1635
+ exec3(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
1656
1636
  if (grepStdout?.trim()) {
1657
1637
  resolve(formatResults(grepStdout, max));
1658
1638
  } else {
@@ -1987,26 +1967,83 @@ var init_runMethod = __esm({
1987
1967
  });
1988
1968
 
1989
1969
  // src/tools/code/screenshot.ts
1990
- var screenshotTool;
1970
+ var DEFAULT_PROMPT, screenshotTool;
1991
1971
  var init_screenshot = __esm({
1992
1972
  "src/tools/code/screenshot.ts"() {
1993
1973
  "use strict";
1974
+ init_sidecar();
1975
+ init_runCli();
1976
+ DEFAULT_PROMPT = "Describe this app screenshot for a developer who cannot see it. What is visible on screen: the layout, content, interactive elements, any loading or error states. Be concise and factual.";
1994
1977
  screenshotTool = {
1995
1978
  definition: {
1996
1979
  name: "screenshot",
1997
- description: "Capture a screenshot of the app preview. Returns a CDN URL with dimensions. Useful for visually checking the current state after UI changes or when debugging layout issues.",
1980
+ description: "Capture a screenshot of the app preview and get a description of what's on screen. Optionally provide a specific question about what you're looking for.",
1998
1981
  inputSchema: {
1999
1982
  type: "object",
2000
- properties: {}
1983
+ properties: {
1984
+ prompt: {
1985
+ type: "string",
1986
+ description: "Optional question about the screenshot. If omitted, returns a general description of what's visible."
1987
+ }
1988
+ }
2001
1989
  }
2002
1990
  },
2003
- async execute() {
2004
- return "ok";
1991
+ async execute(input) {
1992
+ try {
1993
+ const { url } = await sidecarRequest(
1994
+ "/screenshot",
1995
+ {},
1996
+ { timeout: 3e4 }
1997
+ );
1998
+ const analysisPrompt = input.prompt || DEFAULT_PROMPT;
1999
+ const analysis = await runCli(
2000
+ `mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`
2001
+ );
2002
+ return `Screenshot: ${url}
2003
+
2004
+ ${analysis}`;
2005
+ } catch (err) {
2006
+ return `Error taking screenshot: ${err.message}`;
2007
+ }
2005
2008
  }
2006
2009
  };
2007
2010
  }
2008
2011
  });
2009
2012
 
2013
+ // src/subagents/common/cleanMessages.ts
2014
+ function cleanMessagesForApi(messages) {
2015
+ return messages.map((msg) => {
2016
+ if (!Array.isArray(msg.content)) {
2017
+ return msg;
2018
+ }
2019
+ const blocks = msg.content;
2020
+ const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
2021
+ const toolCalls = blocks.filter((b) => b.type === "tool").map((b) => ({ id: b.id, name: b.name, input: b.input }));
2022
+ const thinking = blocks.filter(
2023
+ (b) => b.type === "thinking"
2024
+ ).map((b) => ({ thinking: b.thinking, signature: b.signature }));
2025
+ const cleaned = {
2026
+ role: msg.role,
2027
+ content: text
2028
+ };
2029
+ if (toolCalls.length > 0) {
2030
+ cleaned.toolCalls = toolCalls;
2031
+ }
2032
+ if (thinking.length > 0) {
2033
+ cleaned.thinking = thinking;
2034
+ }
2035
+ if (msg.hidden) {
2036
+ cleaned.hidden = true;
2037
+ }
2038
+ return cleaned;
2039
+ });
2040
+ }
2041
+ var init_cleanMessages = __esm({
2042
+ "src/subagents/common/cleanMessages.ts"() {
2043
+ "use strict";
2044
+ }
2045
+ });
2046
+
2010
2047
  // src/subagents/runner.ts
2011
2048
  async function runSubAgent(config) {
2012
2049
  const {
@@ -2017,6 +2054,7 @@ async function runSubAgent(config) {
2017
2054
  executeTool: executeTool2,
2018
2055
  apiConfig,
2019
2056
  model,
2057
+ subAgentId,
2020
2058
  signal,
2021
2059
  parentToolId,
2022
2060
  onEvent,
@@ -2028,17 +2066,18 @@ async function runSubAgent(config) {
2028
2066
  const messages = [{ role: "user", content: task }];
2029
2067
  while (true) {
2030
2068
  if (signal?.aborted) {
2031
- return "Error: cancelled";
2069
+ return { text: "Error: cancelled", messages };
2032
2070
  }
2033
- let assistantText = "";
2034
- const toolCalls = [];
2071
+ const contentBlocks = [];
2072
+ let thinkingStartedAt = 0;
2035
2073
  let stopReason = "end_turn";
2036
2074
  try {
2037
2075
  for await (const event of streamChatWithRetry({
2038
2076
  ...apiConfig,
2039
2077
  model,
2078
+ subAgentId,
2040
2079
  system,
2041
- messages,
2080
+ messages: cleanMessagesForApi(messages),
2042
2081
  tools,
2043
2082
  signal
2044
2083
  })) {
@@ -2046,18 +2085,43 @@ async function runSubAgent(config) {
2046
2085
  break;
2047
2086
  }
2048
2087
  switch (event.type) {
2049
- case "text":
2050
- assistantText += event.text;
2088
+ case "text": {
2089
+ const lastBlock = contentBlocks.at(-1);
2090
+ if (lastBlock?.type === "text") {
2091
+ lastBlock.text += event.text;
2092
+ } else {
2093
+ contentBlocks.push({
2094
+ type: "text",
2095
+ text: event.text,
2096
+ startedAt: event.ts
2097
+ });
2098
+ }
2051
2099
  emit2({ type: "text", text: event.text });
2052
2100
  break;
2101
+ }
2053
2102
  case "thinking":
2103
+ if (!thinkingStartedAt) {
2104
+ thinkingStartedAt = event.ts;
2105
+ }
2054
2106
  emit2({ type: "thinking", text: event.text });
2055
2107
  break;
2108
+ case "thinking_complete":
2109
+ contentBlocks.push({
2110
+ type: "thinking",
2111
+ thinking: event.thinking,
2112
+ signature: event.signature,
2113
+ startedAt: thinkingStartedAt,
2114
+ completedAt: event.ts
2115
+ });
2116
+ thinkingStartedAt = 0;
2117
+ break;
2056
2118
  case "tool_use":
2057
- toolCalls.push({
2119
+ contentBlocks.push({
2120
+ type: "tool",
2058
2121
  id: event.id,
2059
2122
  name: event.name,
2060
- input: event.input
2123
+ input: event.input,
2124
+ startedAt: Date.now()
2061
2125
  });
2062
2126
  emit2({
2063
2127
  type: "tool_start",
@@ -2070,7 +2134,7 @@ async function runSubAgent(config) {
2070
2134
  stopReason = event.stopReason;
2071
2135
  break;
2072
2136
  case "error":
2073
- return `Error: ${event.error}`;
2137
+ return { text: `Error: ${event.error}`, messages };
2074
2138
  }
2075
2139
  }
2076
2140
  } catch (err) {
@@ -2079,15 +2143,18 @@ async function runSubAgent(config) {
2079
2143
  }
2080
2144
  }
2081
2145
  if (signal?.aborted) {
2082
- return "Error: cancelled";
2146
+ return { text: "Error: cancelled", messages };
2083
2147
  }
2084
2148
  messages.push({
2085
2149
  role: "assistant",
2086
- content: assistantText,
2087
- toolCalls: toolCalls.length > 0 ? toolCalls : void 0
2150
+ content: contentBlocks
2088
2151
  });
2152
+ const toolCalls = contentBlocks.filter(
2153
+ (b) => b.type === "tool"
2154
+ );
2089
2155
  if (stopReason !== "tool_use" || toolCalls.length === 0) {
2090
- return assistantText;
2156
+ const text = contentBlocks.filter((b) => b.type === "text").map((b) => b.text).join("");
2157
+ return { text, messages };
2091
2158
  }
2092
2159
  log.info("Sub-agent executing tools", {
2093
2160
  parentToolId,
@@ -2143,6 +2210,7 @@ var init_runner = __esm({
2143
2210
  "use strict";
2144
2211
  init_api();
2145
2212
  init_logger();
2213
+ init_cleanMessages();
2146
2214
  }
2147
2215
  });
2148
2216
 
@@ -2232,14 +2300,26 @@ var init_tools = __esm({
2232
2300
  // src/subagents/browserAutomation/prompt.ts
2233
2301
  import fs10 from "fs";
2234
2302
  import path4 from "path";
2235
- var base, local, PROMPT_PATH, BROWSER_AUTOMATION_PROMPT;
2303
+ function getBrowserAutomationPrompt() {
2304
+ try {
2305
+ const appSpec = fs10.readFileSync("src/app.md", "utf-8").trim();
2306
+ return `${BASE_PROMPT}
2307
+
2308
+ <app_context>
2309
+ ${appSpec}
2310
+ </app_context>`;
2311
+ } catch {
2312
+ return BASE_PROMPT;
2313
+ }
2314
+ }
2315
+ var base, local, PROMPT_PATH, BASE_PROMPT;
2236
2316
  var init_prompt = __esm({
2237
2317
  "src/subagents/browserAutomation/prompt.ts"() {
2238
2318
  "use strict";
2239
2319
  base = import.meta.dirname ?? path4.dirname(new URL(import.meta.url).pathname);
2240
2320
  local = path4.join(base, "prompt.md");
2241
2321
  PROMPT_PATH = fs10.existsSync(local) ? local : path4.join(base, "subagents", "browserAutomation", "prompt.md");
2242
- BROWSER_AUTOMATION_PROMPT = fs10.readFileSync(PROMPT_PATH, "utf-8").trim();
2322
+ BASE_PROMPT = fs10.readFileSync(PROMPT_PATH, "utf-8").trim();
2243
2323
  }
2244
2324
  });
2245
2325
 
@@ -2283,8 +2363,8 @@ var init_browserAutomation = __esm({
2283
2363
  } catch {
2284
2364
  return "Error: could not check browser status. The dev environment may not be running.";
2285
2365
  }
2286
- return runSubAgent({
2287
- system: BROWSER_AUTOMATION_PROMPT,
2366
+ const result = await runSubAgent({
2367
+ system: getBrowserAutomationPrompt(),
2288
2368
  task: input.task,
2289
2369
  tools: BROWSER_TOOLS,
2290
2370
  externalTools: BROWSER_EXTERNAL_TOOLS,
@@ -2301,39 +2381,40 @@ var init_browserAutomation = __esm({
2301
2381
  },
2302
2382
  apiConfig: context.apiConfig,
2303
2383
  model: context.model,
2384
+ subAgentId: "browserAutomation",
2304
2385
  signal: context.signal,
2305
2386
  parentToolId: context.toolCallId,
2306
2387
  onEvent: context.onEvent,
2307
2388
  resolveExternalTool: context.resolveExternalTool
2308
2389
  });
2390
+ context.subAgentMessages?.set(context.toolCallId, result.messages);
2391
+ return result.text;
2309
2392
  }
2310
2393
  };
2311
2394
  }
2312
2395
  });
2313
2396
 
2314
2397
  // src/subagents/designExpert/tools.ts
2315
- import { exec as exec6 } from "child_process";
2316
- function runCli(cmd) {
2317
- return new Promise((resolve) => {
2318
- exec6(
2319
- cmd,
2320
- { timeout: 6e4, maxBuffer: 1024 * 1024 },
2321
- (err, stdout, stderr) => {
2322
- if (stdout.trim()) {
2323
- resolve(stdout.trim());
2324
- return;
2325
- }
2326
- if (err) {
2327
- resolve(`Error: ${stderr.trim() || err.message}`);
2328
- return;
2329
- }
2330
- resolve("(no response)");
2331
- }
2332
- );
2333
- });
2334
- }
2335
- async function executeDesignTool(name, input) {
2398
+ async function executeDesignExpertTool(name, input) {
2336
2399
  switch (name) {
2400
+ case "screenshot": {
2401
+ try {
2402
+ const { url } = await sidecarRequest(
2403
+ "/screenshot",
2404
+ {},
2405
+ { timeout: 3e4 }
2406
+ );
2407
+ const analysisPrompt = input.prompt || "Describe this app screenshot for a visual designer reviewing the current state. What is visible: layout, typography, colors, spacing, imagery. Note anything that looks broken or off. Be concise.";
2408
+ const analysis = await runCli(
2409
+ `mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`
2410
+ );
2411
+ return `Screenshot: ${url}
2412
+
2413
+ ${analysis}`;
2414
+ } catch (err) {
2415
+ return `Error taking screenshot: ${err.message}`;
2416
+ }
2417
+ }
2337
2418
  case "searchGoogle":
2338
2419
  return runCli(
2339
2420
  `mindstudio search-google --query ${JSON.stringify(input.query)} --export-type json --output-key results --no-meta`
@@ -2347,26 +2428,24 @@ async function executeDesignTool(name, input) {
2347
2428
  `mindstudio scrape-url --url ${JSON.stringify(input.url)} --page-options ${JSON.stringify(JSON.stringify(pageOptions))} --no-meta`
2348
2429
  );
2349
2430
  }
2350
- case "analyzeImage":
2351
- return runCli(
2352
- `mindstudio analyze-image --prompt ${JSON.stringify(input.prompt)} --image-url ${JSON.stringify(input.imageUrl)} --no-meta`
2353
- );
2354
- case "analyzeDesignReference":
2355
- return runCli(
2356
- `mindstudio analyze-image --prompt ${JSON.stringify(DESIGN_REFERENCE_PROMPT)} --image-url ${JSON.stringify(input.imageUrl)} --no-meta`
2357
- );
2358
- case "screenshotAndAnalyze": {
2359
- const ssUrl = await runCli(
2360
- `mindstudio screenshot-url --url ${JSON.stringify(input.url)} --mode viewport --width 1440 --delay 2000 --output-key screenshotUrl --no-meta`
2361
- );
2362
- if (ssUrl.startsWith("Error")) {
2363
- return `Could not screenshot ${input.url}: ${ssUrl}`;
2364
- }
2431
+ case "analyzeReferenceImageOrUrl": {
2432
+ const url = input.url;
2365
2433
  const analysisPrompt = input.prompt || DESIGN_REFERENCE_PROMPT;
2434
+ const isImageUrl = /\.(png|jpe?g|webp|gif|svg|avif)(\?|$)/i.test(url);
2435
+ let imageUrl = url;
2436
+ if (!isImageUrl) {
2437
+ const ssUrl = await runCli(
2438
+ `mindstudio screenshot-url --url ${JSON.stringify(url)} --mode viewport --width 1440 --delay 2000 --output-key screenshotUrl --no-meta`
2439
+ );
2440
+ if (ssUrl.startsWith("Error")) {
2441
+ return `Could not screenshot ${url}: ${ssUrl}`;
2442
+ }
2443
+ imageUrl = ssUrl;
2444
+ }
2366
2445
  const analysis = await runCli(
2367
- `mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(ssUrl)} --no-meta`
2446
+ `mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(imageUrl)} --output-key analysis --no-meta`
2368
2447
  );
2369
- return `Screenshot: ${ssUrl}
2448
+ return isImageUrl ? analysis : `Screenshot: ${imageUrl}
2370
2449
 
2371
2450
  ${analysis}`;
2372
2451
  }
@@ -2380,6 +2459,8 @@ ${analysis}`;
2380
2459
  const prompts = input.prompts;
2381
2460
  const width = input.width || 2048;
2382
2461
  const height = input.height || 2048;
2462
+ const 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, whether there are any issues (unwanted text, artifacts, distortions), and how it could be used in a layout (hero background, feature section, card texture, etc). Be concise and practical.";
2463
+ let imageUrls;
2383
2464
  if (prompts.length === 1) {
2384
2465
  const step = JSON.stringify({
2385
2466
  prompt: prompts[0],
@@ -2388,30 +2469,58 @@ ${analysis}`;
2388
2469
  config: { width, height }
2389
2470
  }
2390
2471
  });
2391
- return runCli(
2472
+ const url = await runCli(
2392
2473
  `mindstudio generate-image '${step}' --output-key imageUrl --no-meta`
2393
2474
  );
2394
- }
2395
- const steps = prompts.map((prompt) => ({
2396
- stepType: "generateImage",
2397
- step: {
2398
- prompt,
2399
- imageModelOverride: {
2400
- model: "seedream-4.5",
2401
- config: { width, height }
2475
+ imageUrls = [url];
2476
+ } else {
2477
+ const steps = prompts.map((prompt) => ({
2478
+ stepType: "generateImage",
2479
+ step: {
2480
+ prompt,
2481
+ imageModelOverride: {
2482
+ model: "seedream-4.5",
2483
+ config: { width, height }
2484
+ }
2402
2485
  }
2486
+ }));
2487
+ const batchResult = await runCli(
2488
+ `mindstudio batch '${JSON.stringify(steps)}' --no-meta`
2489
+ );
2490
+ try {
2491
+ const parsed = JSON.parse(batchResult);
2492
+ imageUrls = parsed.results.map(
2493
+ (r) => r.output?.imageUrl ?? `Error: ${r.error}`
2494
+ );
2495
+ } catch {
2496
+ return batchResult;
2403
2497
  }
2404
- }));
2405
- return runCli(`mindstudio batch '${JSON.stringify(steps)}' --no-meta`);
2498
+ }
2499
+ const analyses = await Promise.all(
2500
+ imageUrls.map(async (url, i) => {
2501
+ if (url.startsWith("Error")) {
2502
+ return `Image ${i + 1}: ${url}`;
2503
+ }
2504
+ const analysis = await runCli(
2505
+ `mindstudio analyze-image --prompt ${JSON.stringify(ANALYZE_PROMPT)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`
2506
+ );
2507
+ return `**Image ${i + 1}:** ${url}
2508
+ Prompt: ${prompts[i]}
2509
+ Analysis: ${analysis}`;
2510
+ })
2511
+ );
2512
+ return analyses.join("\n\n");
2406
2513
  }
2407
2514
  default:
2408
2515
  return `Error: unknown tool "${name}"`;
2409
2516
  }
2410
2517
  }
2411
- var DESIGN_REFERENCE_PROMPT, DESIGN_RESEARCH_TOOLS;
2518
+ var DESIGN_REFERENCE_PROMPT, DESIGN_EXPERT_TOOLS;
2412
2519
  var init_tools2 = __esm({
2413
2520
  "src/subagents/designExpert/tools.ts"() {
2414
2521
  "use strict";
2522
+ init_runCli();
2523
+ init_sidecar();
2415
2524
  DESIGN_REFERENCE_PROMPT = `Analyze this website/app screenshot as a design reference. Assess:
2416
2525
  1) Mood/aesthetic
2417
2526
  2) Color palette with approximate hex values and palette strategy
@@ -2419,7 +2528,7 @@ var init_tools2 = __esm({
2419
2528
  4) Layout composition (symmetric/asymmetric, grid structure, whitespace usage, content density)
2420
2529
  5) What makes it distinctive and interesting vs generic AI-generated interfaces
2421
2530
  Be specific and concise.`;
2422
- DESIGN_RESEARCH_TOOLS = [
2531
+ DESIGN_EXPERT_TOOLS = [
2423
2532
  {
2424
2533
  name: "searchGoogle",
2425
2534
  description: "Search Google for web results. Use for finding design inspiration, font recommendations, UI patterns, real products in a domain, and reference material.",
@@ -2453,53 +2562,29 @@ Be specific and concise.`;
2453
2562
  }
2454
2563
  },
2455
2564
  {
2456
- name: "analyzeImage",
2457
- description: 'Analyze an image using a vision model with a custom prompt. Use when you have a specific question about an image (e.g., "what colors dominate this image?", "describe the typography choices").',
2565
+ name: "analyzeReferenceImageOrUrl",
2566
+ description: "Analyze any visual \u2014 pass an image URL or a website URL. Websites are automatically screenshotted first. If no prompt is provided, performs a full design reference analysis (mood, color, typography, layout, distinctiveness). Provide a custom prompt to ask a specific question instead.",
2458
2567
  inputSchema: {
2459
2568
  type: "object",
2460
2569
  properties: {
2461
- prompt: {
2570
+ url: {
2462
2571
  type: "string",
2463
- description: "What to analyze or extract from the image."
2572
+ description: "URL to analyze. Can be an image URL or a website URL (will be screenshotted)."
2464
2573
  },
2465
- imageUrl: {
2466
- type: "string",
2467
- description: "URL of the image to analyze."
2468
- }
2469
- },
2470
- required: ["prompt", "imageUrl"]
2471
- }
2472
- },
2473
- {
2474
- name: "analyzeDesignReference",
2475
- description: "Analyze a screenshot or design image for design inspiration. Returns a structured analysis: mood/aesthetic, color palette with hex values, typography style, layout composition, and what makes it distinctive. Use this instead of analyzeImage when studying a design reference.",
2476
- inputSchema: {
2477
- type: "object",
2478
- properties: {
2479
- imageUrl: {
2574
+ prompt: {
2480
2575
  type: "string",
2481
- description: "URL of the screenshot or design image to analyze."
2576
+ description: "Optional custom analysis prompt. If omitted, performs the standard design reference analysis."
2482
2577
  }
2483
2578
  },
2484
- required: ["imageUrl"]
2579
+ required: ["url"]
2485
2580
  }
2486
2581
  },
2487
2582
  {
2488
- name: "screenshotAndAnalyze",
2489
- description: "Screenshot a live URL and analyze it in one step. If no prompt is provided, performs a full design reference analysis (mood, color, typography, layout, distinctiveness). Provide a custom prompt to ask a specific question about the visual design instead.",
2583
+ name: "screenshot",
2584
+ description: "Capture a screenshot of the app preview. Returns a CDN URL. Use to review the current state of the UI being built.",
2490
2585
  inputSchema: {
2491
2586
  type: "object",
2492
- properties: {
2493
- url: {
2494
- type: "string",
2495
- description: "The URL to screenshot."
2496
- },
2497
- prompt: {
2498
- type: "string",
2499
- description: "Optional custom analysis prompt. If omitted, performs the standard design reference analysis."
2500
- }
2501
- },
2502
- required: ["url"]
2587
+ properties: {}
2503
2588
  }
2504
2589
  },
2505
2590
  {
@@ -2518,7 +2603,7 @@ Be specific and concise.`;
2518
2603
  },
2519
2604
  {
2520
2605
  name: "generateImages",
2521
- description: "Generate images using AI (Seedream). Returns CDN URLs. Produces high-quality results for both photorealistic images and abstract/creative visuals. Pass multiple prompts to generate in parallel.",
2606
+ description: "Generate images using AI (Seedream). Returns CDN URLs with a quality analysis for each image. Produces high-quality results for both photorealistic images and abstract/creative visuals. Pass multiple prompts to generate in parallel. No need to analyze images separately after generating \u2014 the analysis is included.",
2522
2607
  inputSchema: {
2523
2608
  type: "object",
2524
2609
  properties: {
@@ -2545,19 +2630,120 @@ Be specific and concise.`;
2545
2630
  }
2546
2631
  });
2547
2632
 
2548
- // src/subagents/designExpert/prompt.ts
2633
+ // src/subagents/common/context.ts
2549
2634
  import fs11 from "fs";
2550
2635
  import path5 from "path";
2636
+ function walkMdFiles(dir, skip) {
2637
+ const files = [];
2638
+ try {
2639
+ for (const entry of fs11.readdirSync(dir, { withFileTypes: true })) {
2640
+ const full = path5.join(dir, entry.name);
2641
+ if (entry.isDirectory()) {
2642
+ if (!skip?.has(entry.name)) {
2643
+ files.push(...walkMdFiles(full, skip));
2644
+ }
2645
+ } else if (entry.name.endsWith(".md")) {
2646
+ files.push(full);
2647
+ }
2648
+ }
2649
+ } catch {
2650
+ }
2651
+ return files;
2652
+ }
2653
+ function loadFilesAsXml(dir, tag, skip) {
2654
+ const files = walkMdFiles(dir, skip);
2655
+ if (files.length === 0) {
2656
+ return "";
2657
+ }
2658
+ const sections = files.map((f) => {
2659
+ try {
2660
+ const content = fs11.readFileSync(f, "utf-8").trim();
2661
+ return `<file path="${f}">
2662
+ ${content}
2663
+ </file>`;
2664
+ } catch {
2665
+ return "";
2666
+ }
2667
+ }).filter(Boolean);
2668
+ return `<${tag}>
2669
+ ${sections.join("\n\n")}
2670
+ </${tag}>`;
2671
+ }
2672
+ function loadSpecContext() {
2673
+ return loadFilesAsXml("src", "spec_files", /* @__PURE__ */ new Set(["roadmap"]));
2674
+ }
2675
+ function loadRoadmapContext() {
2676
+ return loadFilesAsXml("src/roadmap", "current_roadmap");
2677
+ }
2678
+ function loadPlatformBrief() {
2679
+ return `<platform_brief>
2680
+ ## What is a MindStudio app?
2681
+
2682
+ A MindStudio app is a managed TypeScript project with three layers: a spec (natural language in src/), a backend contract (methods, tables, roles in dist/), and one or more interfaces (web, API, bots, cron, etc.). The spec is the source of truth; code is derived from it.
2683
+
2684
+ ## What people build
2685
+
2686
+ - Business tools \u2014 dashboards, admin panels, approval workflows, data entry apps, internal tools with role-based access
2687
+ - AI-powered apps \u2014 chatbots, content generators, document processors, image/video tools, AI agents that take actions
2688
+ - Automations with no UI \u2014 cron jobs, webhook handlers, email processors, data sync pipelines
2689
+ - Bots \u2014 Discord slash-command bots, Telegram bots, MCP tool servers for AI assistants
2690
+ - Creative/interactive projects \u2014 games, interactive visualizations, generative art, portfolio sites
2691
+ - API services \u2014 backend logic exposed as REST endpoints
2692
+ - Simple static sites \u2014 no backend needed, just a web interface with a build step
2693
+
2694
+ An app can be any combination of these.
2695
+
2696
+ ## Interfaces
2697
+
2698
+ Each interface type invokes the same backend methods. Methods don't know which interface called them.
2699
+
2700
+ - Web \u2014 any TypeScript project with a build command. Framework-agnostic (React, Vue, Svelte, vanilla, anything). The frontend SDK provides typed RPC to backend methods.
2701
+ - API \u2014 auto-generated REST endpoints for every method
2702
+ - Cron \u2014 scheduled jobs on a configurable interval
2703
+ - Webhook \u2014 HTTP endpoints that trigger methods
2704
+ - Discord \u2014 slash-command bots
2705
+ - Telegram \u2014 message-handling bots
2706
+ - Email \u2014 inbound email processing
2707
+ - MCP \u2014 tool servers for AI assistants
2708
+
2709
+ ## Backend
2710
+
2711
+ TypeScript running in a sandboxed environment. Any npm package can be installed. Key capabilities:
2712
+
2713
+ - Managed SQLite database with typed schemas and automatic migrations. Define a TypeScript interface, push, and the platform handles diffing and migrating.
2714
+ - Built-in role-based auth. Define roles in the manifest, gate methods with auth.requireRole(). Platform handles sessions, tokens, user resolution.
2715
+ - Sandboxed execution with npm packages pre-installed.
2716
+ - Git-native deployment. Push to default branch to deploy.
2717
+
2718
+ ## MindStudio SDK
2719
+
2720
+ The first-party SDK (@mindstudio-ai/agent) provides access to 200+ AI models (OpenAI, Anthropic, Google, Meta, Mistral, and more) and 1000+ integrations (email, SMS, Slack, HubSpot, Google Workspace, web scraping, image/video generation, media processing, and much more) with zero configuration \u2014 credentials are handled automatically in the execution environment. No API keys needed.
2721
+
2722
+ ## What MindStudio apps are NOT good for
2723
+
2724
+ - Native mobile apps (iOS/Android). Mobile-responsive web apps are fine.
2725
+ - Real-time multiplayer with persistent connections (no WebSocket support). Turn-based or async patterns work.
2726
+ </platform_brief>`;
2727
+ }
2728
+ var init_context = __esm({
2729
+ "src/subagents/common/context.ts"() {
2730
+ "use strict";
2731
+ }
2732
+ });
2733
+
2734
+ // src/subagents/designExpert/prompt.ts
2735
+ import fs12 from "fs";
2736
+ import path6 from "path";
2551
2737
  function resolvePath(filename) {
2552
- const local3 = path5.join(base2, filename);
2553
- return fs11.existsSync(local3) ? local3 : path5.join(base2, "subagents", "designExpert", filename);
2738
+ const local4 = path6.join(base2, filename);
2739
+ return fs12.existsSync(local4) ? local4 : path6.join(base2, "subagents", "designExpert", filename);
2554
2740
  }
2555
2741
  function readFile(filename) {
2556
- return fs11.readFileSync(resolvePath(filename), "utf-8").trim();
2742
+ return fs12.readFileSync(resolvePath(filename), "utf-8").trim();
2557
2743
  }
2558
2744
  function readJson(filename, fallback) {
2559
2745
  try {
2560
- return JSON.parse(fs11.readFileSync(resolvePath(filename), "utf-8"));
2746
+ return JSON.parse(fs12.readFileSync(resolvePath(filename), "utf-8"));
2561
2747
  } catch {
2562
2748
  return fallback;
2563
2749
  }
@@ -2573,7 +2759,7 @@ function sample(arr, n) {
2573
2759
  }
2574
2760
  return copy.slice(0, n);
2575
2761
  }
2576
- function getDesignResearchPrompt() {
2762
+ function getDesignExpertPrompt() {
2577
2763
  const fonts = sample(fontData.fonts, 30);
2578
2764
  const pairings = sample(fontData.pairings, 20);
2579
2765
  const images = sample(inspirationImages, 15);
@@ -2612,16 +2798,24 @@ This is what the bar looks like. These are real sites that made it onto curated
2612
2798
 
2613
2799
  ${imageList}
2614
2800
  </inspiration_images>` : "";
2615
- return PROMPT_TEMPLATE.replace("{{fonts_to_consider}}", fontsSection).replace(
2616
- "{{inspiration_images}}",
2617
- inspirationSection
2618
- );
2801
+ const specContext = loadSpecContext();
2802
+ let prompt = PROMPT_TEMPLATE.replace(
2803
+ "{{fonts_to_consider}}",
2804
+ fontsSection
2805
+ ).replace("{{inspiration_images}}", inspirationSection);
2806
+ if (specContext) {
2807
+ prompt += `
2808
+
2809
+ ${specContext}`;
2810
+ }
2811
+ return prompt;
2619
2812
  }
2620
2813
  var base2, RUNTIME_PLACEHOLDERS, PROMPT_TEMPLATE, fontData, inspirationImages;
2621
2814
  var init_prompt2 = __esm({
2622
2815
  "src/subagents/designExpert/prompt.ts"() {
2623
2816
  "use strict";
2624
- base2 = import.meta.dirname ?? path5.dirname(new URL(import.meta.url).pathname);
2817
+ init_context();
2818
+ base2 = import.meta.dirname ?? path6.dirname(new URL(import.meta.url).pathname);
2625
2819
  RUNTIME_PLACEHOLDERS = /* @__PURE__ */ new Set([
2626
2820
  "fonts_to_consider",
2627
2821
  "inspiration_images"
@@ -2650,23 +2844,7 @@ var init_designExpert = __esm({
2650
2844
  init_tools2();
2651
2845
  init_prompt2();
2652
2846
  DESCRIPTION = `
2653
- A dedicated visual design expert. You have a lot on your plate as a coding agent, and design is a specialized skill \u2014 delegate visual design here rather than making those decisions yourself. This agent has curated font catalogs, color theory knowledge, access to design inspiration galleries, ability to create beautiful photos and images, and strong opinions about what looks good. It can answer from expertise alone or research the web when needed.
2654
-
2655
- The visual design expert can be used for all things visual design, from quick questions to comprehensive plans:
2656
- - Font selection and pairings ("suggest fonts for a <x> app")
2657
- - Color palettes from a mood, domain, or seed color ("earthy tones for a <x> brand")
2658
- - Gradient, animation, and visual effect recommendations
2659
- - Layout and composition ideas that go beyond generic AI defaults
2660
- - Analyzing a reference site or screenshot for design insights (it can take screenshots and do research on its own)
2661
- - Beautiful layout images or photos
2662
- - Icon recommendations or AI image editing
2663
- - Proposing full visual design and layout directions during intake
2664
-
2665
- **How to write the task:**
2666
- Include context about the app \u2014 what it does, who uses it, what mood or feeling the interface should convey. If the user has any specific requirements, be sure to include them. The agent can not see your conversation with the user, so you need to include all details. More context produces better results. For quick questions ("three font pairings for a <x> app"), brief is fine. You can ask for multiple topics, multiple options, etc.
2667
-
2668
- **What it returns:**
2669
- Concrete resources: hex values, font names with CSS URLs, image URLs, layout descriptions. Use the results directly in brand spec files or as guidance when building the interface.
2847
+ 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.
2670
2848
  `.trim();
2671
2849
  designExpertTool = {
2672
2850
  definition: {
@@ -2685,105 +2863,38 @@ Concrete resources: hex values, font names with CSS URLs, image URLs, layout des
2685
2863
  },
2686
2864
  async execute(input, context) {
2687
2865
  if (!context) {
2688
- return "Error: design research requires execution context";
2866
+ return "Error: visual design expert requires execution context";
2689
2867
  }
2690
- return runSubAgent({
2691
- system: getDesignResearchPrompt(),
2868
+ const result = await runSubAgent({
2869
+ system: getDesignExpertPrompt(),
2692
2870
  task: input.task,
2693
- tools: DESIGN_RESEARCH_TOOLS,
2871
+ tools: DESIGN_EXPERT_TOOLS,
2694
2872
  externalTools: /* @__PURE__ */ new Set(),
2695
- executeTool: executeDesignTool,
2873
+ executeTool: executeDesignExpertTool,
2696
2874
  apiConfig: context.apiConfig,
2697
2875
  model: context.model,
2876
+ subAgentId: "visualDesignExpert",
2698
2877
  signal: context.signal,
2699
2878
  parentToolId: context.toolCallId,
2700
2879
  onEvent: context.onEvent,
2701
2880
  resolveExternalTool: context.resolveExternalTool
2702
2881
  });
2882
+ context.subAgentMessages?.set(context.toolCallId, result.messages);
2883
+ return result.text;
2703
2884
  }
2704
2885
  };
2705
2886
  }
2706
2887
  });
2707
2888
 
2708
- // src/subagents/productVision/index.ts
2709
- import fs12 from "fs";
2710
- import path6 from "path";
2711
- function loadSpecContext() {
2712
- const specDir = "src";
2713
- const files = [];
2714
- function walk(dir) {
2715
- try {
2716
- for (const entry of fs12.readdirSync(dir, { withFileTypes: true })) {
2717
- const full = path6.join(dir, entry.name);
2718
- if (entry.isDirectory()) {
2719
- if (entry.name !== "roadmap") {
2720
- walk(full);
2721
- }
2722
- } else if (entry.name.endsWith(".md")) {
2723
- files.push(full);
2724
- }
2725
- }
2726
- } catch {
2727
- }
2728
- }
2729
- walk(specDir);
2730
- if (files.length === 0) {
2731
- return "";
2732
- }
2733
- const sections = files.map((f) => {
2734
- try {
2735
- const content = fs12.readFileSync(f, "utf-8").trim();
2736
- return `<file path="${f}">
2737
- ${content}
2738
- </file>`;
2739
- } catch {
2740
- return "";
2741
- }
2742
- }).filter(Boolean);
2743
- return `<spec_files>
2744
- ${sections.join("\n\n")}
2745
- </spec_files>`;
2746
- }
2747
- async function executeVisionTool(name, input) {
2748
- if (name !== "writeRoadmapItem") {
2749
- return `Error: unknown tool "${name}"`;
2750
- }
2751
- const { slug, name: itemName, description, effort, requires, body } = input;
2752
- const dir = "src/roadmap";
2753
- const filePath = path6.join(dir, `${slug}.md`);
2754
- try {
2755
- fs12.mkdirSync(dir, { recursive: true });
2756
- const requiresYaml = requires.length === 0 ? "[]" : `[${requires.map((r) => `"${r}"`).join(", ")}]`;
2757
- const content = `---
2758
- name: ${itemName}
2759
- type: roadmap
2760
- status: ${slug === "mvp" ? "in-progress" : "not-started"}
2761
- description: ${description}
2762
- effort: ${effort}
2763
- requires: ${requiresYaml}
2764
- ---
2765
-
2766
- ${body}
2767
- `;
2768
- fs12.writeFileSync(filePath, content, "utf-8");
2769
- return `Wrote ${filePath}`;
2770
- } catch (err) {
2771
- return `Error writing ${filePath}: ${err.message}`;
2772
- }
2773
- }
2774
- var base3, local2, PROMPT_PATH2, BASE_PROMPT, VISION_TOOLS, productVisionTool;
2775
- var init_productVision = __esm({
2776
- "src/subagents/productVision/index.ts"() {
2889
+ // src/subagents/productVision/tools.ts
2890
+ var VISION_TOOLS;
2891
+ var init_tools3 = __esm({
2892
+ "src/subagents/productVision/tools.ts"() {
2777
2893
  "use strict";
2778
- init_runner();
2779
- base3 = import.meta.dirname ?? path6.dirname(new URL(import.meta.url).pathname);
2780
- local2 = path6.join(base3, "prompt.md");
2781
- PROMPT_PATH2 = fs12.existsSync(local2) ? local2 : path6.join(base3, "subagents", "productVision", "prompt.md");
2782
- BASE_PROMPT = fs12.readFileSync(PROMPT_PATH2, "utf-8").trim();
2783
2894
  VISION_TOOLS = [
2784
2895
  {
2785
2896
  name: "writeRoadmapItem",
2786
- description: "Write a roadmap item to src/roadmap/. Call this once for each idea.",
2897
+ description: "Create a new roadmap item in src/roadmap/.",
2787
2898
  inputSchema: {
2788
2899
  type: "object",
2789
2900
  properties: {
@@ -2810,23 +2921,250 @@ var init_productVision = __esm({
2810
2921
  },
2811
2922
  body: {
2812
2923
  type: "string",
2813
- description: "Full MSFM body: prose description for the user, followed by ~~~annotation~~~ with technical implementation notes for the building agent."
2924
+ description: "Full MSFM body: prose description for the user, followed by ~~~annotation~~~ with technical implementation notes."
2814
2925
  }
2815
2926
  },
2816
2927
  required: ["slug", "name", "description", "effort", "requires", "body"]
2817
2928
  }
2929
+ },
2930
+ {
2931
+ name: "updateRoadmapItem",
2932
+ description: "Update an existing roadmap item. Only include the fields you want to change.",
2933
+ inputSchema: {
2934
+ type: "object",
2935
+ properties: {
2936
+ slug: {
2937
+ type: "string",
2938
+ description: "The slug of the item to update (filename without .md)."
2939
+ },
2940
+ status: {
2941
+ type: "string",
2942
+ enum: ["done", "in-progress", "not-started"],
2943
+ description: "New status."
2944
+ },
2945
+ name: {
2946
+ type: "string",
2947
+ description: "Updated feature name."
2948
+ },
2949
+ description: {
2950
+ type: "string",
2951
+ description: "Updated summary."
2952
+ },
2953
+ effort: {
2954
+ type: "string",
2955
+ enum: ["quick", "small", "medium", "large"],
2956
+ description: "Updated effort level."
2957
+ },
2958
+ requires: {
2959
+ type: "array",
2960
+ items: { type: "string" },
2961
+ description: "Updated prerequisites."
2962
+ },
2963
+ body: {
2964
+ type: "string",
2965
+ description: "Full replacement body (overwrites existing body)."
2966
+ },
2967
+ appendHistory: {
2968
+ type: "string",
2969
+ description: 'A history entry to append. Format: "- **2026-03-22** \u2014 Description of what was done."'
2970
+ }
2971
+ },
2972
+ required: ["slug"]
2973
+ }
2974
+ },
2975
+ {
2976
+ name: "deleteRoadmapItem",
2977
+ description: "Remove a roadmap item. Use when an idea is no longer relevant or has been absorbed into another item.",
2978
+ inputSchema: {
2979
+ type: "object",
2980
+ properties: {
2981
+ slug: {
2982
+ type: "string",
2983
+ description: "The slug of the item to delete (filename without .md)."
2984
+ }
2985
+ },
2986
+ required: ["slug"]
2987
+ }
2818
2988
  }
2819
2989
  ];
2990
+ }
2991
+ });
2992
+
2993
+ // src/subagents/productVision/executor.ts
2994
+ import fs13 from "fs";
2995
+ import path7 from "path";
2996
+ function formatRequires(requires) {
2997
+ return requires.length === 0 ? "[]" : `[${requires.map((r) => `"${r}"`).join(", ")}]`;
2998
+ }
2999
+ async function executeVisionTool(name, input) {
3000
+ switch (name) {
3001
+ case "writeRoadmapItem": {
3002
+ const {
3003
+ slug,
3004
+ name: itemName,
3005
+ description,
3006
+ effort,
3007
+ requires,
3008
+ body
3009
+ } = input;
3010
+ const filePath = path7.join(ROADMAP_DIR, `${slug}.md`);
3011
+ try {
3012
+ fs13.mkdirSync(ROADMAP_DIR, { recursive: true });
3013
+ const content = `---
3014
+ name: ${itemName}
3015
+ type: roadmap
3016
+ status: ${slug === "mvp" ? "in-progress" : "not-started"}
3017
+ description: ${description}
3018
+ effort: ${effort}
3019
+ requires: ${formatRequires(requires)}
3020
+ ---
3021
+
3022
+ ${body}
3023
+ `;
3024
+ fs13.writeFileSync(filePath, content, "utf-8");
3025
+ return `Wrote ${filePath}`;
3026
+ } catch (err) {
3027
+ return `Error writing ${filePath}: ${err.message}`;
3028
+ }
3029
+ }
3030
+ case "updateRoadmapItem": {
3031
+ const { slug } = input;
3032
+ const filePath = path7.join(ROADMAP_DIR, `${slug}.md`);
3033
+ try {
3034
+ if (!fs13.existsSync(filePath)) {
3035
+ return `Error: ${filePath} does not exist`;
3036
+ }
3037
+ let content = fs13.readFileSync(filePath, "utf-8");
3038
+ if (input.status) {
3039
+ content = content.replace(
3040
+ /^status:\s*.+$/m,
3041
+ `status: ${input.status}`
3042
+ );
3043
+ }
3044
+ if (input.name) {
3045
+ content = content.replace(/^name:\s*.+$/m, `name: ${input.name}`);
3046
+ }
3047
+ if (input.description) {
3048
+ content = content.replace(
3049
+ /^description:\s*.+$/m,
3050
+ `description: ${input.description}`
3051
+ );
3052
+ }
3053
+ if (input.effort) {
3054
+ content = content.replace(
3055
+ /^effort:\s*.+$/m,
3056
+ `effort: ${input.effort}`
3057
+ );
3058
+ }
3059
+ if (input.requires) {
3060
+ content = content.replace(
3061
+ /^requires:\s*.+$/m,
3062
+ `requires: ${formatRequires(input.requires)}`
3063
+ );
3064
+ }
3065
+ if (input.body) {
3066
+ const endOfFrontmatter = content.indexOf("---", 4);
3067
+ if (endOfFrontmatter !== -1) {
3068
+ const frontmatter = content.slice(0, endOfFrontmatter + 3);
3069
+ content = `${frontmatter}
3070
+
3071
+ ${input.body}
3072
+ `;
3073
+ }
3074
+ }
3075
+ if (input.appendHistory) {
3076
+ if (content.includes("## History")) {
3077
+ content = content.trimEnd() + `
3078
+ ${input.appendHistory}
3079
+ `;
3080
+ } else {
3081
+ content = content.trimEnd() + `
3082
+
3083
+ ## History
3084
+
3085
+ ${input.appendHistory}
3086
+ `;
3087
+ }
3088
+ }
3089
+ fs13.writeFileSync(filePath, content, "utf-8");
3090
+ return `Updated ${filePath}`;
3091
+ } catch (err) {
3092
+ return `Error updating ${filePath}: ${err.message}`;
3093
+ }
3094
+ }
3095
+ case "deleteRoadmapItem": {
3096
+ const { slug } = input;
3097
+ const filePath = path7.join(ROADMAP_DIR, `${slug}.md`);
3098
+ try {
3099
+ if (!fs13.existsSync(filePath)) {
3100
+ return `Error: ${filePath} does not exist`;
3101
+ }
3102
+ fs13.unlinkSync(filePath);
3103
+ return `Deleted ${filePath}`;
3104
+ } catch (err) {
3105
+ return `Error deleting ${filePath}: ${err.message}`;
3106
+ }
3107
+ }
3108
+ default:
3109
+ return `Error: unknown tool "${name}"`;
3110
+ }
3111
+ }
3112
+ var ROADMAP_DIR;
3113
+ var init_executor = __esm({
3114
+ "src/subagents/productVision/executor.ts"() {
3115
+ "use strict";
3116
+ init_context();
3117
+ ROADMAP_DIR = "src/roadmap";
3118
+ }
3119
+ });
3120
+
3121
+ // src/subagents/productVision/prompt.ts
3122
+ import fs14 from "fs";
3123
+ import path8 from "path";
3124
+ function getProductVisionPrompt() {
3125
+ const specContext = loadSpecContext();
3126
+ const roadmapContext = loadRoadmapContext();
3127
+ const parts = [BASE_PROMPT2, loadPlatformBrief()];
3128
+ if (specContext) {
3129
+ parts.push(specContext);
3130
+ }
3131
+ if (roadmapContext) {
3132
+ parts.push(roadmapContext);
3133
+ }
3134
+ return parts.join("\n\n");
3135
+ }
3136
+ var base3, local2, PROMPT_PATH2, BASE_PROMPT2;
3137
+ var init_prompt3 = __esm({
3138
+ "src/subagents/productVision/prompt.ts"() {
3139
+ "use strict";
3140
+ init_executor();
3141
+ init_context();
3142
+ base3 = import.meta.dirname ?? path8.dirname(new URL(import.meta.url).pathname);
3143
+ local2 = path8.join(base3, "prompt.md");
3144
+ PROMPT_PATH2 = fs14.existsSync(local2) ? local2 : path8.join(base3, "subagents", "productVision", "prompt.md");
3145
+ BASE_PROMPT2 = fs14.readFileSync(PROMPT_PATH2, "utf-8").trim();
3146
+ }
3147
+ });
3148
+
3149
+ // src/subagents/productVision/index.ts
3150
+ var productVisionTool;
3151
+ var init_productVision = __esm({
3152
+ "src/subagents/productVision/index.ts"() {
3153
+ "use strict";
3154
+ init_runner();
3155
+ init_tools3();
3156
+ init_executor();
3157
+ init_prompt3();
2820
3158
  productVisionTool = {
2821
3159
  definition: {
2822
3160
  name: "productVision",
2823
- description: `A product visionary that imagines where the project could go next. It automatically reads all spec files from src/ for context. Pass a brief description of the app and who it's for. It generates 10-15 ambitious, creative roadmap ideas and writes them directly to src/roadmap/. Use this at the end of spec authoring to populate the roadmap.`,
3161
+ description: "Owns the product roadmap. Reads spec and roadmap files automatically. Creates, updates, and deletes roadmap items in src/roadmap/. Describe the situation and what needs to happen.",
2824
3162
  inputSchema: {
2825
3163
  type: "object",
2826
3164
  properties: {
2827
3165
  task: {
2828
3166
  type: "string",
2829
- description: "Brief description of the app and who it's for. The tool reads the full spec files automatically \u2014 no need to repeat their contents."
3167
+ description: "What to do with the roadmap. Include relevant context. The tool reads spec and roadmap files automatically."
2830
3168
  }
2831
3169
  },
2832
3170
  required: ["task"]
@@ -2836,23 +3174,180 @@ var init_productVision = __esm({
2836
3174
  if (!context) {
2837
3175
  return "Error: product vision requires execution context";
2838
3176
  }
2839
- const specContext = loadSpecContext();
2840
- const system = specContext ? `${BASE_PROMPT}
2841
-
2842
- ${specContext}` : BASE_PROMPT;
2843
- return runSubAgent({
2844
- system,
3177
+ const result = await runSubAgent({
3178
+ system: getProductVisionPrompt(),
2845
3179
  task: input.task,
2846
3180
  tools: VISION_TOOLS,
2847
3181
  externalTools: /* @__PURE__ */ new Set(),
2848
3182
  executeTool: executeVisionTool,
2849
3183
  apiConfig: context.apiConfig,
2850
3184
  model: context.model,
3185
+ subAgentId: "productVision",
3186
+ signal: context.signal,
3187
+ parentToolId: context.toolCallId,
3188
+ onEvent: context.onEvent,
3189
+ resolveExternalTool: context.resolveExternalTool
3190
+ });
3191
+ context.subAgentMessages?.set(context.toolCallId, result.messages);
3192
+ return result.text;
3193
+ }
3194
+ };
3195
+ }
3196
+ });
3197
+
3198
+ // src/subagents/codeSanityCheck/tools.ts
3199
+ var SANITY_CHECK_TOOLS;
3200
+ var init_tools4 = __esm({
3201
+ "src/subagents/codeSanityCheck/tools.ts"() {
3202
+ "use strict";
3203
+ SANITY_CHECK_TOOLS = [
3204
+ {
3205
+ name: "readFile",
3206
+ description: "Read a file from the project.",
3207
+ inputSchema: {
3208
+ type: "object",
3209
+ properties: {
3210
+ path: {
3211
+ type: "string",
3212
+ description: "File path relative to project root."
3213
+ }
3214
+ },
3215
+ required: ["path"]
3216
+ }
3217
+ },
3218
+ {
3219
+ name: "grep",
3220
+ description: "Search file contents for a pattern.",
3221
+ inputSchema: {
3222
+ type: "object",
3223
+ properties: {
3224
+ pattern: { type: "string", description: "Search pattern (regex)." },
3225
+ path: {
3226
+ type: "string",
3227
+ description: "Directory or file to search in."
3228
+ }
3229
+ },
3230
+ required: ["pattern"]
3231
+ }
3232
+ },
3233
+ {
3234
+ name: "glob",
3235
+ description: "Find files by glob pattern.",
3236
+ inputSchema: {
3237
+ type: "object",
3238
+ properties: {
3239
+ pattern: {
3240
+ type: "string",
3241
+ description: 'Glob pattern (e.g., "src/**/*.ts").'
3242
+ }
3243
+ },
3244
+ required: ["pattern"]
3245
+ }
3246
+ },
3247
+ {
3248
+ name: "searchGoogle",
3249
+ description: "Search the web. Use to verify packages are current or find alternatives.",
3250
+ inputSchema: {
3251
+ type: "object",
3252
+ properties: {
3253
+ query: { type: "string", description: "Search query." }
3254
+ },
3255
+ required: ["query"]
3256
+ }
3257
+ },
3258
+ {
3259
+ name: "fetchUrl",
3260
+ description: "Fetch a web page as markdown. Use to read package docs, changelogs, npm pages.",
3261
+ inputSchema: {
3262
+ type: "object",
3263
+ properties: {
3264
+ url: { type: "string", description: "URL to fetch." }
3265
+ },
3266
+ required: ["url"]
3267
+ }
3268
+ },
3269
+ {
3270
+ name: "askMindStudioSdk",
3271
+ description: "Check if the MindStudio SDK has a managed action for something before writing custom code.",
3272
+ inputSchema: {
3273
+ type: "object",
3274
+ properties: {
3275
+ query: { type: "string", description: "What you want to check." }
3276
+ },
3277
+ required: ["query"]
3278
+ }
3279
+ },
3280
+ {
3281
+ name: "bash",
3282
+ description: "Run a shell command. Use for reading/search/etc operations only.",
3283
+ inputSchema: {
3284
+ type: "object",
3285
+ properties: {
3286
+ command: { type: "string", description: "The command to run." }
3287
+ },
3288
+ required: ["command"]
3289
+ }
3290
+ }
3291
+ ];
3292
+ }
3293
+ });
3294
+
3295
+ // src/subagents/codeSanityCheck/index.ts
3296
+ import fs15 from "fs";
3297
+ import path9 from "path";
3298
+ var base4, local3, PROMPT_PATH3, BASE_PROMPT3, codeSanityCheckTool;
3299
+ var init_codeSanityCheck = __esm({
3300
+ "src/subagents/codeSanityCheck/index.ts"() {
3301
+ "use strict";
3302
+ init_runner();
3303
+ init_context();
3304
+ init_tools5();
3305
+ init_tools4();
3306
+ base4 = import.meta.dirname ?? path9.dirname(new URL(import.meta.url).pathname);
3307
+ local3 = path9.join(base4, "prompt.md");
3308
+ PROMPT_PATH3 = fs15.existsSync(local3) ? local3 : path9.join(base4, "subagents", "codeSanityCheck", "prompt.md");
3309
+ BASE_PROMPT3 = fs15.readFileSync(PROMPT_PATH3, "utf-8").trim();
3310
+ codeSanityCheckTool = {
3311
+ definition: {
3312
+ name: "codeSanityCheck",
3313
+ description: 'Quick sanity check on an approach before building. Reviews architecture, package choices, and flags potential issues. Usually responds with "looks good." Occasionally catches something important. Readonly \u2014 can search the web and read code but cannot modify anything.',
3314
+ inputSchema: {
3315
+ type: "object",
3316
+ properties: {
3317
+ task: {
3318
+ type: "string",
3319
+ description: "What you're about to build and how. Include the plan, packages you intend to use, and any architectural decisions you've made."
3320
+ }
3321
+ },
3322
+ required: ["task"]
3323
+ }
3324
+ },
3325
+ async execute(input, context) {
3326
+ if (!context) {
3327
+ return "Error: code sanity check requires execution context";
3328
+ }
3329
+ const specContext = loadSpecContext();
3330
+ const parts = [BASE_PROMPT3, loadPlatformBrief()];
3331
+ if (specContext) {
3332
+ parts.push(specContext);
3333
+ }
3334
+ const system = parts.join("\n\n");
3335
+ const result = await runSubAgent({
3336
+ system,
3337
+ task: input.task,
3338
+ tools: SANITY_CHECK_TOOLS,
3339
+ externalTools: /* @__PURE__ */ new Set(),
3340
+ executeTool: (name, toolInput) => executeTool(name, toolInput, context),
3341
+ apiConfig: context.apiConfig,
3342
+ model: context.model,
3343
+ subAgentId: "codeSanityCheck",
2851
3344
  signal: context.signal,
2852
3345
  parentToolId: context.toolCallId,
2853
3346
  onEvent: context.onEvent,
2854
3347
  resolveExternalTool: context.resolveExternalTool
2855
3348
  });
3349
+ context.subAgentMessages?.set(context.toolCallId, result.messages);
3350
+ return result.text;
2856
3351
  }
2857
3352
  };
2858
3353
  }
@@ -2892,7 +3387,8 @@ function getCommonTools() {
2892
3387
  searchGoogleTool,
2893
3388
  setProjectNameTool,
2894
3389
  designExpertTool,
2895
- productVisionTool
3390
+ productVisionTool,
3391
+ codeSanityCheckTool
2896
3392
  ];
2897
3393
  }
2898
3394
  function getPostOnboardingTools() {
@@ -2937,7 +3433,7 @@ function executeTool(name, input, context) {
2937
3433
  }
2938
3434
  return tool.execute(input, context);
2939
3435
  }
2940
- var init_tools3 = __esm({
3436
+ var init_tools5 = __esm({
2941
3437
  "src/tools/index.ts"() {
2942
3438
  "use strict";
2943
3439
  init_readSpec();
@@ -2951,7 +3447,7 @@ var init_tools3 = __esm({
2951
3447
  init_setProjectOnboardingState();
2952
3448
  init_promptUser();
2953
3449
  init_confirmDestructiveAction();
2954
- init_askMindStudioSdk();
3450
+ init_sdkConsultant();
2955
3451
  init_fetchUrl();
2956
3452
  init_searchGoogle();
2957
3453
  init_setProjectName();
@@ -2972,14 +3468,15 @@ var init_tools3 = __esm({
2972
3468
  init_browserAutomation();
2973
3469
  init_designExpert();
2974
3470
  init_productVision();
3471
+ init_codeSanityCheck();
2975
3472
  }
2976
3473
  });
2977
3474
 
2978
3475
  // src/session.ts
2979
- import fs13 from "fs";
3476
+ import fs16 from "fs";
2980
3477
  function loadSession(state) {
2981
3478
  try {
2982
- const raw = fs13.readFileSync(SESSION_FILE, "utf-8");
3479
+ const raw = fs16.readFileSync(SESSION_FILE, "utf-8");
2983
3480
  const data = JSON.parse(raw);
2984
3481
  if (Array.isArray(data.messages) && data.messages.length > 0) {
2985
3482
  state.messages = sanitizeMessages(data.messages);
@@ -2994,7 +3491,13 @@ function sanitizeMessages(messages) {
2994
3491
  for (let i = 0; i < messages.length; i++) {
2995
3492
  result.push(messages[i]);
2996
3493
  const msg = messages[i];
2997
- if (msg.role !== "assistant" || !msg.toolCalls?.length) {
3494
+ if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
3495
+ continue;
3496
+ }
3497
+ const toolBlocks = msg.content.filter(
3498
+ (b) => b.type === "tool"
3499
+ );
3500
+ if (toolBlocks.length === 0) {
2998
3501
  continue;
2999
3502
  }
3000
3503
  const resultIds = /* @__PURE__ */ new Set();
@@ -3006,7 +3509,7 @@ function sanitizeMessages(messages) {
3006
3509
  break;
3007
3510
  }
3008
3511
  }
3009
- for (const tc of msg.toolCalls) {
3512
+ for (const tc of toolBlocks) {
3010
3513
  if (!resultIds.has(tc.id)) {
3011
3514
  result.push({
3012
3515
  role: "user",
@@ -3021,7 +3524,7 @@ function sanitizeMessages(messages) {
3021
3524
  }
3022
3525
  function saveSession(state) {
3023
3526
  try {
3024
- fs13.writeFileSync(
3527
+ fs16.writeFileSync(
3025
3528
  SESSION_FILE,
3026
3529
  JSON.stringify({ messages: state.messages }, null, 2),
3027
3530
  "utf-8"
@@ -3032,7 +3535,7 @@ function saveSession(state) {
3032
3535
  function clearSession(state) {
3033
3536
  state.messages = [];
3034
3537
  try {
3035
- fs13.unlinkSync(SESSION_FILE);
3538
+ fs16.unlinkSync(SESSION_FILE);
3036
3539
  } catch {
3037
3540
  }
3038
3541
  }
@@ -3329,6 +3832,14 @@ var init_errors = __esm({
3329
3832
  });
3330
3833
 
3331
3834
  // src/agent.ts
3835
+ function getTextContent(blocks) {
3836
+ return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
3837
+ }
3838
+ function getToolCalls(blocks) {
3839
+ return blocks.filter(
3840
+ (b) => b.type === "tool"
3841
+ );
3842
+ }
3332
3843
  function createAgentState() {
3333
3844
  return { messages: [] };
3334
3845
  }
@@ -3370,6 +3881,12 @@ async function runTurn(params) {
3370
3881
  });
3371
3882
  }
3372
3883
  state.messages.push(userMsg);
3884
+ const STATUS_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
3885
+ "setProjectOnboardingState",
3886
+ "setProjectName",
3887
+ "clearSyncStatus",
3888
+ "editsFinished"
3889
+ ]);
3373
3890
  let lastCompletedTools = "";
3374
3891
  let lastCompletedResult = "";
3375
3892
  while (true) {
@@ -3387,8 +3904,8 @@ async function runTurn(params) {
3387
3904
  saveSession(state);
3388
3905
  return;
3389
3906
  }
3390
- let assistantText = "";
3391
- const toolCalls = [];
3907
+ const contentBlocks = [];
3908
+ let thinkingStartedAt = 0;
3392
3909
  const toolInputAccumulators = /* @__PURE__ */ new Map();
3393
3910
  let stopReason = "end_turn";
3394
3911
  async function handlePartialInput(acc, id, name, partial) {
@@ -3457,8 +3974,8 @@ async function runTurn(params) {
3457
3974
  const statusWatcher = startStatusWatcher({
3458
3975
  apiConfig,
3459
3976
  getContext: () => ({
3460
- assistantText: assistantText.slice(-500),
3461
- lastToolName: toolCalls.at(-1)?.name || lastCompletedTools || void 0,
3977
+ assistantText: getTextContent(contentBlocks).slice(-500),
3978
+ lastToolName: getToolCalls(contentBlocks).filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).at(-1)?.name || lastCompletedTools || void 0,
3462
3979
  lastToolResult: lastCompletedResult || void 0
3463
3980
  }),
3464
3981
  onStatus: (label) => onEvent({ type: "status", message: label }),
@@ -3470,7 +3987,7 @@ async function runTurn(params) {
3470
3987
  ...apiConfig,
3471
3988
  model,
3472
3989
  system,
3473
- messages: state.messages,
3990
+ messages: cleanMessagesForApi(state.messages),
3474
3991
  tools,
3475
3992
  signal
3476
3993
  },
@@ -3487,13 +4004,36 @@ async function runTurn(params) {
3487
4004
  break;
3488
4005
  }
3489
4006
  switch (event.type) {
3490
- case "text":
3491
- assistantText += event.text;
4007
+ case "text": {
4008
+ const lastBlock = contentBlocks.at(-1);
4009
+ if (lastBlock?.type === "text") {
4010
+ lastBlock.text += event.text;
4011
+ } else {
4012
+ contentBlocks.push({
4013
+ type: "text",
4014
+ text: event.text,
4015
+ startedAt: event.ts
4016
+ });
4017
+ }
3492
4018
  onEvent({ type: "text", text: event.text });
3493
4019
  break;
4020
+ }
3494
4021
  case "thinking":
4022
+ if (!thinkingStartedAt) {
4023
+ thinkingStartedAt = event.ts;
4024
+ }
3495
4025
  onEvent({ type: "thinking", text: event.text });
3496
4026
  break;
4027
+ case "thinking_complete":
4028
+ contentBlocks.push({
4029
+ type: "thinking",
4030
+ thinking: event.thinking,
4031
+ signature: event.signature,
4032
+ startedAt: thinkingStartedAt,
4033
+ completedAt: event.ts
4034
+ });
4035
+ thinkingStartedAt = 0;
4036
+ break;
3497
4037
  case "tool_input_delta": {
3498
4038
  const acc = getOrCreateAccumulator2(event.id, event.name);
3499
4039
  acc.json += event.delta;
@@ -3521,10 +4061,12 @@ async function runTurn(params) {
3521
4061
  break;
3522
4062
  }
3523
4063
  case "tool_use": {
3524
- toolCalls.push({
4064
+ contentBlocks.push({
4065
+ type: "tool",
3525
4066
  id: event.id,
3526
4067
  name: event.name,
3527
- input: event.input
4068
+ input: event.input,
4069
+ startedAt: event.ts
3528
4070
  });
3529
4071
  const acc = toolInputAccumulators.get(event.id);
3530
4072
  const tool = getToolByName(event.name);
@@ -3563,11 +4105,15 @@ async function runTurn(params) {
3563
4105
  statusWatcher.stop();
3564
4106
  }
3565
4107
  if (signal?.aborted) {
3566
- if (assistantText) {
4108
+ if (contentBlocks.length > 0) {
4109
+ contentBlocks.push({
4110
+ type: "text",
4111
+ text: "\n\n(cancelled)",
4112
+ startedAt: Date.now()
4113
+ });
3567
4114
  state.messages.push({
3568
4115
  role: "assistant",
3569
- content: assistantText + "\n\n(cancelled)",
3570
- toolCalls: toolCalls.length > 0 ? toolCalls : void 0
4116
+ content: [...contentBlocks].sort((a, b) => a.startedAt - b.startedAt)
3571
4117
  });
3572
4118
  }
3573
4119
  onEvent({ type: "turn_cancelled" });
@@ -3576,9 +4122,9 @@ async function runTurn(params) {
3576
4122
  }
3577
4123
  state.messages.push({
3578
4124
  role: "assistant",
3579
- content: assistantText,
3580
- toolCalls: toolCalls.length > 0 ? toolCalls : void 0
4125
+ content: [...contentBlocks].sort((a, b) => a.startedAt - b.startedAt)
3581
4126
  });
4127
+ const toolCalls = getToolCalls(contentBlocks);
3582
4128
  if (stopReason !== "tool_use" || toolCalls.length === 0) {
3583
4129
  saveSession(state);
3584
4130
  onEvent({ type: "turn_done" });
@@ -3588,6 +4134,29 @@ async function runTurn(params) {
3588
4134
  count: toolCalls.length,
3589
4135
  tools: toolCalls.map((tc) => tc.name)
3590
4136
  });
4137
+ let subAgentText = "";
4138
+ const origOnEvent = onEvent;
4139
+ const wrappedOnEvent = (e) => {
4140
+ if ("parentToolId" in e && e.parentToolId) {
4141
+ if (e.type === "text") {
4142
+ subAgentText = e.text;
4143
+ } else if (e.type === "tool_start") {
4144
+ subAgentText = `Using ${e.name}`;
4145
+ }
4146
+ }
4147
+ origOnEvent(e);
4148
+ };
4149
+ const toolStatusWatcher = startStatusWatcher({
4150
+ apiConfig,
4151
+ getContext: () => ({
4152
+ assistantText: subAgentText || getTextContent(contentBlocks).slice(-500),
4153
+ lastToolName: toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ") || void 0,
4154
+ lastToolResult: lastCompletedResult || void 0
4155
+ }),
4156
+ onStatus: (label) => origOnEvent({ type: "status", message: label }),
4157
+ signal
4158
+ });
4159
+ const subAgentMessages = /* @__PURE__ */ new Map();
3591
4160
  const results = await Promise.all(
3592
4161
  toolCalls.map(async (tc) => {
3593
4162
  if (signal?.aborted) {
@@ -3612,9 +4181,10 @@ async function runTurn(params) {
3612
4181
  apiConfig,
3613
4182
  model,
3614
4183
  signal,
3615
- onEvent,
4184
+ onEvent: wrappedOnEvent,
3616
4185
  resolveExternalTool,
3617
- toolCallId: tc.id
4186
+ toolCallId: tc.id,
4187
+ subAgentMessages
3618
4188
  });
3619
4189
  }
3620
4190
  const isError = result.startsWith("Error");
@@ -3645,7 +4215,21 @@ async function runTurn(params) {
3645
4215
  }
3646
4216
  })
3647
4217
  );
3648
- lastCompletedTools = toolCalls.map((tc) => tc.name).join(", ");
4218
+ toolStatusWatcher.stop();
4219
+ for (const r of results) {
4220
+ const block = contentBlocks.find(
4221
+ (b) => b.type === "tool" && b.id === r.id
4222
+ );
4223
+ if (block?.type === "tool") {
4224
+ block.result = r.result;
4225
+ block.isError = r.isError;
4226
+ const msgs = subAgentMessages.get(r.id);
4227
+ if (msgs) {
4228
+ block.subAgentMessages = msgs;
4229
+ }
4230
+ }
4231
+ }
4232
+ lastCompletedTools = toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ");
3649
4233
  lastCompletedResult = results.at(-1)?.result ?? "";
3650
4234
  for (const r of results) {
3651
4235
  state.messages.push({
@@ -3667,12 +4251,13 @@ var init_agent = __esm({
3667
4251
  "src/agent.ts"() {
3668
4252
  "use strict";
3669
4253
  init_api();
3670
- init_tools3();
4254
+ init_tools5();
3671
4255
  init_session();
3672
4256
  init_logger();
3673
4257
  init_parsePartialJson();
3674
4258
  init_statusWatcher();
3675
4259
  init_errors();
4260
+ init_cleanMessages();
3676
4261
  EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
3677
4262
  "promptUser",
3678
4263
  "setProjectOnboardingState",
@@ -3684,19 +4269,18 @@ var init_agent = __esm({
3684
4269
  "runScenario",
3685
4270
  "runMethod",
3686
4271
  "browserCommand",
3687
- "screenshot",
3688
4272
  "setProjectName"
3689
4273
  ]);
3690
4274
  }
3691
4275
  });
3692
4276
 
3693
4277
  // src/prompt/static/projectContext.ts
3694
- import fs14 from "fs";
3695
- import path7 from "path";
4278
+ import fs17 from "fs";
4279
+ import path10 from "path";
3696
4280
  function loadProjectInstructions() {
3697
4281
  for (const file of AGENT_INSTRUCTION_FILES) {
3698
4282
  try {
3699
- const content = fs14.readFileSync(file, "utf-8").trim();
4283
+ const content = fs17.readFileSync(file, "utf-8").trim();
3700
4284
  if (content) {
3701
4285
  return `
3702
4286
  ## Project Instructions (${file})
@@ -3709,7 +4293,7 @@ ${content}`;
3709
4293
  }
3710
4294
  function loadProjectManifest() {
3711
4295
  try {
3712
- const manifest = fs14.readFileSync("mindstudio.json", "utf-8");
4296
+ const manifest = fs17.readFileSync("mindstudio.json", "utf-8");
3713
4297
  return `
3714
4298
  ## Project Manifest (mindstudio.json)
3715
4299
  \`\`\`json
@@ -3721,7 +4305,7 @@ ${manifest}
3721
4305
  }
3722
4306
  function loadSpecFileMetadata() {
3723
4307
  try {
3724
- const files = walkMdFiles("src");
4308
+ const files = walkMdFiles2("src");
3725
4309
  if (files.length === 0) {
3726
4310
  return "";
3727
4311
  }
@@ -3747,14 +4331,14 @@ ${entries.join("\n")}`;
3747
4331
  return "";
3748
4332
  }
3749
4333
  }
3750
- function walkMdFiles(dir) {
4334
+ function walkMdFiles2(dir) {
3751
4335
  const results = [];
3752
4336
  try {
3753
- const entries = fs14.readdirSync(dir, { withFileTypes: true });
4337
+ const entries = fs17.readdirSync(dir, { withFileTypes: true });
3754
4338
  for (const entry of entries) {
3755
- const full = path7.join(dir, entry.name);
4339
+ const full = path10.join(dir, entry.name);
3756
4340
  if (entry.isDirectory()) {
3757
- results.push(...walkMdFiles(full));
4341
+ results.push(...walkMdFiles2(full));
3758
4342
  } else if (entry.name.endsWith(".md")) {
3759
4343
  results.push(full);
3760
4344
  }
@@ -3765,7 +4349,7 @@ function walkMdFiles(dir) {
3765
4349
  }
3766
4350
  function parseFrontmatter(filePath) {
3767
4351
  try {
3768
- const content = fs14.readFileSync(filePath, "utf-8");
4352
+ const content = fs17.readFileSync(filePath, "utf-8");
3769
4353
  const match = content.match(/^---\n([\s\S]*?)\n---/);
3770
4354
  if (!match) {
3771
4355
  return { name: "", description: "", type: "" };
@@ -3781,7 +4365,7 @@ function parseFrontmatter(filePath) {
3781
4365
  }
3782
4366
  function loadProjectFileListing() {
3783
4367
  try {
3784
- const entries = fs14.readdirSync(".", { withFileTypes: true });
4368
+ const entries = fs17.readdirSync(".", { withFileTypes: true });
3785
4369
  const listing = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules").sort((a, b) => {
3786
4370
  if (a.isDirectory() && !b.isDirectory()) {
3787
4371
  return -1;
@@ -3824,12 +4408,12 @@ var init_projectContext = __esm({
3824
4408
  });
3825
4409
 
3826
4410
  // src/prompt/index.ts
3827
- import fs15 from "fs";
3828
- import path8 from "path";
4411
+ import fs18 from "fs";
4412
+ import path11 from "path";
3829
4413
  function requireFile(filePath) {
3830
- const full = path8.join(PROMPT_DIR, filePath);
4414
+ const full = path11.join(PROMPT_DIR, filePath);
3831
4415
  try {
3832
- return fs15.readFileSync(full, "utf-8").trim();
4416
+ return fs18.readFileSync(full, "utf-8").trim();
3833
4417
  } catch {
3834
4418
  throw new Error(`Required prompt file missing: ${full}`);
3835
4419
  }
@@ -3919,6 +4503,8 @@ ${projectContext}
3919
4503
  {{static/authoring.md}}
3920
4504
  </spec_authoring_instructions>
3921
4505
 
4506
+ {{static/team.md}}
4507
+
3922
4508
  <code_authoring_instructions>
3923
4509
  {{static/coding.md}}
3924
4510
  ${isLspConfigured() ? `<typescript_lsp>
@@ -3949,22 +4535,22 @@ ${viewContext?.activeFile ? `Active file: ${viewContext.activeFile}` : ""}
3949
4535
  return resolveIncludes(template);
3950
4536
  }
3951
4537
  var PROMPT_DIR;
3952
- var init_prompt3 = __esm({
4538
+ var init_prompt4 = __esm({
3953
4539
  "src/prompt/index.ts"() {
3954
4540
  "use strict";
3955
4541
  init_lsp();
3956
4542
  init_projectContext();
3957
- PROMPT_DIR = import.meta.dirname ?? path8.dirname(new URL(import.meta.url).pathname);
4543
+ PROMPT_DIR = import.meta.dirname ?? path11.dirname(new URL(import.meta.url).pathname);
3958
4544
  }
3959
4545
  });
3960
4546
 
3961
4547
  // src/config.ts
3962
- import fs16 from "fs";
3963
- import path9 from "path";
4548
+ import fs19 from "fs";
4549
+ import path12 from "path";
3964
4550
  import os from "os";
3965
4551
  function loadConfigFile() {
3966
4552
  try {
3967
- const raw = fs16.readFileSync(CONFIG_PATH, "utf-8");
4553
+ const raw = fs19.readFileSync(CONFIG_PATH, "utf-8");
3968
4554
  log.debug("Loaded config file", { path: CONFIG_PATH });
3969
4555
  return JSON.parse(raw);
3970
4556
  } catch (err) {
@@ -4000,7 +4586,7 @@ var init_config = __esm({
4000
4586
  "src/config.ts"() {
4001
4587
  "use strict";
4002
4588
  init_logger();
4003
- CONFIG_PATH = path9.join(
4589
+ CONFIG_PATH = path12.join(
4004
4590
  os.homedir(),
4005
4591
  ".mindstudio-local-tunnel",
4006
4592
  "config.json"
@@ -4015,10 +4601,10 @@ __export(headless_exports, {
4015
4601
  startHeadless: () => startHeadless
4016
4602
  });
4017
4603
  import { createInterface } from "readline";
4018
- import fs17 from "fs";
4019
- import path10 from "path";
4604
+ import fs20 from "fs";
4605
+ import path13 from "path";
4020
4606
  function loadActionPrompt(name) {
4021
- return fs17.readFileSync(path10.join(ACTIONS_DIR, `${name}.md`), "utf-8").trim();
4607
+ return fs20.readFileSync(path13.join(ACTIONS_DIR, `${name}.md`), "utf-8").trim();
4022
4608
  }
4023
4609
  function emit(event, data) {
4024
4610
  process.stdout.write(JSON.stringify({ event, ...data }) + "\n");
@@ -4106,19 +4692,27 @@ async function startHeadless(opts = {}) {
4106
4692
  break;
4107
4693
  }
4108
4694
  }
4109
- function resolveExternalTool(id, _name, _input) {
4695
+ const USER_FACING_TOOLS = /* @__PURE__ */ new Set([
4696
+ "promptUser",
4697
+ "confirmDestructiveAction",
4698
+ "presentPlan",
4699
+ "presentSyncPlan",
4700
+ "presentPublishPlan"
4701
+ ]);
4702
+ function resolveExternalTool(id, name, _input) {
4110
4703
  const early = earlyResults.get(id);
4111
4704
  if (early !== void 0) {
4112
4705
  earlyResults.delete(id);
4113
4706
  return Promise.resolve(early);
4114
4707
  }
4708
+ const shouldTimeout = !USER_FACING_TOOLS.has(name);
4115
4709
  return new Promise((resolve) => {
4116
- const timeout = setTimeout(() => {
4710
+ const timeout = shouldTimeout ? setTimeout(() => {
4117
4711
  pendingTools.delete(id);
4118
4712
  resolve(
4119
4713
  "Error: Tool timed out \u2014 no response from the app environment after 5 minutes."
4120
4714
  );
4121
- }, EXTERNAL_TOOL_TIMEOUT_MS);
4715
+ }, EXTERNAL_TOOL_TIMEOUT_MS) : void 0;
4122
4716
  pendingTools.set(id, {
4123
4717
  resolve: (result) => {
4124
4718
  clearTimeout(timeout);
@@ -4233,20 +4827,20 @@ var init_headless = __esm({
4233
4827
  "src/headless.ts"() {
4234
4828
  "use strict";
4235
4829
  init_config();
4236
- init_prompt3();
4830
+ init_prompt4();
4237
4831
  init_lsp();
4238
4832
  init_agent();
4239
4833
  init_session();
4240
- BASE_DIR = import.meta.dirname ?? path10.dirname(new URL(import.meta.url).pathname);
4241
- ACTIONS_DIR = path10.join(BASE_DIR, "actions");
4834
+ BASE_DIR = import.meta.dirname ?? path13.dirname(new URL(import.meta.url).pathname);
4835
+ ACTIONS_DIR = path13.join(BASE_DIR, "actions");
4242
4836
  }
4243
4837
  });
4244
4838
 
4245
4839
  // src/index.tsx
4246
4840
  import { render } from "ink";
4247
4841
  import os2 from "os";
4248
- import fs18 from "fs";
4249
- import path11 from "path";
4842
+ import fs21 from "fs";
4843
+ import path14 from "path";
4250
4844
 
4251
4845
  // src/tui/App.tsx
4252
4846
  import { useState as useState2, useCallback, useRef } from "react";
@@ -4381,7 +4975,7 @@ function MessageList({ turns }) {
4381
4975
 
4382
4976
  // src/tui/App.tsx
4383
4977
  init_agent();
4384
- init_prompt3();
4978
+ init_prompt4();
4385
4979
  init_session();
4386
4980
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
4387
4981
  function App({ apiConfig, model }) {
@@ -4563,8 +5157,8 @@ for (let i = 0; i < args.length; i++) {
4563
5157
  }
4564
5158
  function printDebugInfo(config) {
4565
5159
  const pkg = JSON.parse(
4566
- fs18.readFileSync(
4567
- path11.join(import.meta.dirname, "..", "package.json"),
5160
+ fs21.readFileSync(
5161
+ path14.join(import.meta.dirname, "..", "package.json"),
4568
5162
  "utf-8"
4569
5163
  )
4570
5164
  );