@mindstudio-ai/remy 0.1.34 → 0.1.36

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 (55) hide show
  1. package/dist/headless.js +998 -586
  2. package/dist/index.js +1086 -584
  3. package/dist/prompt/compiled/media-cdn.md +1 -1
  4. package/dist/prompt/sources/llms.txt +1618 -0
  5. package/dist/prompt/static/instructions.md +1 -1
  6. package/dist/prompt/static/team.md +1 -1
  7. package/dist/subagents/.notes-background-agents.md +60 -48
  8. package/dist/subagents/browserAutomation/prompt.md +14 -11
  9. package/dist/subagents/designExpert/data/sources/dev/index.html +901 -0
  10. package/dist/subagents/designExpert/data/sources/dev/serve.mjs +244 -0
  11. package/dist/subagents/designExpert/data/sources/dev/specimens-fonts.html +126 -0
  12. package/dist/subagents/designExpert/data/sources/dev/specimens-pairings.html +114 -0
  13. package/dist/subagents/designExpert/data/{fonts.json → sources/fonts.json} +0 -97
  14. package/dist/subagents/designExpert/data/sources/inspiration.json +392 -0
  15. package/dist/subagents/designExpert/prompt.md +36 -12
  16. package/dist/subagents/designExpert/prompts/animation.md +14 -6
  17. package/dist/subagents/designExpert/prompts/color.md +25 -5
  18. package/dist/subagents/designExpert/prompts/{icons.md → components.md} +17 -5
  19. package/dist/subagents/designExpert/prompts/frontend-design-notes.md +17 -122
  20. package/dist/subagents/designExpert/prompts/identity.md +15 -61
  21. package/dist/subagents/designExpert/prompts/images.md +35 -10
  22. package/dist/subagents/designExpert/prompts/layout.md +14 -9
  23. package/dist/subagents/designExpert/prompts/typography.md +39 -0
  24. package/package.json +2 -2
  25. package/dist/actions/buildFromInitialSpec.md +0 -15
  26. package/dist/actions/publish.md +0 -12
  27. package/dist/actions/sync.md +0 -19
  28. package/dist/compiled/README.md +0 -100
  29. package/dist/compiled/auth.md +0 -77
  30. package/dist/compiled/design.md +0 -251
  31. package/dist/compiled/dev-and-deploy.md +0 -69
  32. package/dist/compiled/interfaces.md +0 -238
  33. package/dist/compiled/manifest.md +0 -107
  34. package/dist/compiled/media-cdn.md +0 -51
  35. package/dist/compiled/methods.md +0 -225
  36. package/dist/compiled/msfm.md +0 -222
  37. package/dist/compiled/platform.md +0 -105
  38. package/dist/compiled/scenarios.md +0 -103
  39. package/dist/compiled/sdk-actions.md +0 -146
  40. package/dist/compiled/tables.md +0 -263
  41. package/dist/static/authoring.md +0 -101
  42. package/dist/static/coding.md +0 -29
  43. package/dist/static/identity.md +0 -1
  44. package/dist/static/instructions.md +0 -31
  45. package/dist/static/intake.md +0 -44
  46. package/dist/static/lsp.md +0 -4
  47. package/dist/static/projectContext.ts +0 -160
  48. package/dist/static/team.md +0 -39
  49. package/dist/subagents/designExpert/data/inspiration.json +0 -392
  50. package/dist/subagents/designExpert/prompts/instructions.md +0 -18
  51. /package/dist/subagents/designExpert/data/{compile-font-descriptions.sh → sources/compile-font-descriptions.sh} +0 -0
  52. /package/dist/subagents/designExpert/data/{compile-inspiration.sh → sources/compile-inspiration.sh} +0 -0
  53. /package/dist/subagents/designExpert/data/{inspiration.raw.json → sources/inspiration.raw.json} +0 -0
  54. /package/dist/subagents/designExpert/{prompts/tool-prompts → data/sources/prompts}/design-analysis.md +0 -0
  55. /package/dist/subagents/designExpert/{prompts/tool-prompts → data/sources/prompts}/font-analysis.md +0 -0
package/dist/index.js CHANGED
@@ -1057,7 +1057,12 @@ function runCli(cmd, options) {
1057
1057
  const entry = JSON.parse(trimmed);
1058
1058
  if (entry.type === "log" && entry.value) {
1059
1059
  const prefix = entry.tag ? `[${entry.tag}]` : "[log]";
1060
- logs.push(`${prefix} ${entry.value}`);
1060
+ const formatted = `${prefix} ${entry.value}`;
1061
+ if (options?.onLog) {
1062
+ options.onLog(formatted);
1063
+ } else {
1064
+ logs.push(formatted);
1065
+ }
1061
1066
  }
1062
1067
  } catch {
1063
1068
  }
@@ -1069,7 +1074,7 @@ function runCli(cmd, options) {
1069
1074
  }, timeout);
1070
1075
  child.on("close", (code) => {
1071
1076
  clearTimeout(timer);
1072
- const logBlock = logs.length > 0 ? logs.join("\n") + "\n\n" : "";
1077
+ const logBlock = !options?.onLog && logs.length > 0 ? logs.join("\n") + "\n\n" : "";
1073
1078
  const out = stdout.trim();
1074
1079
  if (out) {
1075
1080
  resolve(logBlock + out);
@@ -1111,11 +1116,12 @@ var init_sdkConsultant = __esm({
1111
1116
  required: ["query"]
1112
1117
  }
1113
1118
  },
1114
- async execute(input) {
1119
+ async execute(input, context) {
1115
1120
  const query = input.query;
1116
1121
  return runCli(`mindstudio ask ${JSON.stringify(query)}`, {
1117
1122
  timeout: 2e5,
1118
- maxBuffer: 512 * 1024
1123
+ maxBuffer: 512 * 1024,
1124
+ onLog: context?.onLog
1119
1125
  });
1120
1126
  }
1121
1127
  };
@@ -1147,7 +1153,7 @@ var init_fetchUrl = __esm({
1147
1153
  required: ["url"]
1148
1154
  }
1149
1155
  },
1150
- async execute(input) {
1156
+ async execute(input, context) {
1151
1157
  const url = input.url;
1152
1158
  const screenshot = input.screenshot;
1153
1159
  const pageOptions = { onlyMainContent: true };
@@ -1155,7 +1161,8 @@ var init_fetchUrl = __esm({
1155
1161
  pageOptions.screenshot = true;
1156
1162
  }
1157
1163
  return runCli(
1158
- `mindstudio scrape-url --url ${JSON.stringify(url)} --page-options ${JSON.stringify(JSON.stringify(pageOptions))} --no-meta`
1164
+ `mindstudio scrape-url --url ${JSON.stringify(url)} --page-options ${JSON.stringify(JSON.stringify(pageOptions))} --no-meta`,
1165
+ { onLog: context?.onLog }
1159
1166
  );
1160
1167
  }
1161
1168
  };
@@ -1183,11 +1190,11 @@ var init_searchGoogle = __esm({
1183
1190
  required: ["query"]
1184
1191
  }
1185
1192
  },
1186
- async execute(input) {
1193
+ async execute(input, context) {
1187
1194
  const query = input.query;
1188
1195
  return runCli(
1189
1196
  `mindstudio search-google --query ${JSON.stringify(query)} --export-type json --output-key results --no-meta`,
1190
- { maxBuffer: 512 * 1024 }
1197
+ { maxBuffer: 512 * 1024, onLog: context?.onLog }
1191
1198
  );
1192
1199
  }
1193
1200
  };
@@ -1224,9 +1231,9 @@ var init_setProjectName = __esm({
1224
1231
  // src/tools/code/readFile.ts
1225
1232
  import fs6 from "fs/promises";
1226
1233
  function isBinary(buffer) {
1227
- const sample2 = buffer.subarray(0, 8192);
1228
- for (let i = 0; i < sample2.length; i++) {
1229
- if (sample2[i] === 0) {
1234
+ const sample3 = buffer.subarray(0, 8192);
1235
+ for (let i = 0; i < sample3.length; i++) {
1236
+ if (sample3[i] === 0) {
1230
1237
  return true;
1231
1238
  }
1232
1239
  }
@@ -2018,18 +2025,16 @@ var init_runMethod = __esm({
2018
2025
  // src/tools/_helpers/screenshot.ts
2019
2026
  async function captureAndAnalyzeScreenshot(promptOrOptions) {
2020
2027
  let prompt;
2021
- let fullPage = false;
2028
+ let onLog;
2022
2029
  if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
2023
2030
  prompt = promptOrOptions.prompt;
2024
- fullPage = promptOrOptions.fullPage ?? false;
2031
+ onLog = promptOrOptions.onLog;
2025
2032
  } else {
2026
2033
  prompt = promptOrOptions;
2027
2034
  }
2028
- const ssResult = await sidecarRequest(
2029
- "/screenshot",
2030
- { fullPage },
2031
- { timeout: 12e4 }
2032
- );
2035
+ const ssResult = await sidecarRequest("/screenshot-full-page", void 0, {
2036
+ timeout: 12e4
2037
+ });
2033
2038
  log.debug("Screenshot response", { ssResult });
2034
2039
  const url = ssResult?.url || ssResult?.screenshotUrl;
2035
2040
  if (!url) {
@@ -2043,7 +2048,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
2043
2048
  const analysisPrompt = prompt || SCREENSHOT_ANALYSIS_PROMPT;
2044
2049
  const analysis = await runCli(
2045
2050
  `mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`,
2046
- { timeout: 2e5 }
2051
+ { timeout: 2e5, onLog }
2047
2052
  );
2048
2053
  return JSON.stringify({ url, analysis });
2049
2054
  }
@@ -2054,7 +2059,7 @@ var init_screenshot = __esm({
2054
2059
  init_sidecar();
2055
2060
  init_runCli();
2056
2061
  init_logger();
2057
- SCREENSHOT_ANALYSIS_PROMPT = "Describe everything visible on screen from top to bottom \u2014 every element, its position, its size relative to the viewport, its colors, its content. Be thorough and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components).";
2062
+ SCREENSHOT_ANALYSIS_PROMPT = "Describe everything visible on screen from top to bottom \u2014 every element, its position, its size relative to the viewport, its colors, its content. Be comprehensive, thorough, and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components). Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.";
2058
2063
  }
2059
2064
  });
2060
2065
 
@@ -2067,26 +2072,22 @@ var init_screenshot2 = __esm({
2067
2072
  screenshotTool = {
2068
2073
  definition: {
2069
2074
  name: "screenshot",
2070
- 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. By default captures the viewport (what the user sees). Set fullPage to capture the entire scrollable page.",
2075
+ description: "Capture a full-height 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..",
2071
2076
  inputSchema: {
2072
2077
  type: "object",
2073
2078
  properties: {
2074
2079
  prompt: {
2075
2080
  type: "string",
2076
2081
  description: "Optional question about the screenshot. If omitted, returns a general description of what's visible."
2077
- },
2078
- fullPage: {
2079
- type: "boolean",
2080
- description: "Capture the full scrollable page instead of just the viewport. Use when you need to see below-the-fold content."
2081
2082
  }
2082
2083
  }
2083
2084
  }
2084
2085
  },
2085
- async execute(input) {
2086
+ async execute(input, context) {
2086
2087
  try {
2087
2088
  return await captureAndAnalyzeScreenshot({
2088
2089
  prompt: input.prompt,
2089
- fullPage: input.fullPage
2090
+ onLog: context?.onLog
2090
2091
  });
2091
2092
  } catch (err) {
2092
2093
  return `Error taking screenshot: ${err.message}`;
@@ -2096,6 +2097,91 @@ var init_screenshot2 = __esm({
2096
2097
  }
2097
2098
  });
2098
2099
 
2100
+ // src/statusWatcher.ts
2101
+ function startStatusWatcher(config) {
2102
+ const { apiConfig, getContext, onStatus, interval = 3e3, signal } = config;
2103
+ let lastLabel = "";
2104
+ let inflight = false;
2105
+ let stopped = false;
2106
+ const url = `${apiConfig.baseUrl}/_internal/v2/agent/remy/generate-status`;
2107
+ async function tick() {
2108
+ if (stopped || signal?.aborted || inflight) {
2109
+ return;
2110
+ }
2111
+ inflight = true;
2112
+ try {
2113
+ const ctx = getContext();
2114
+ if (!ctx.assistantText && !ctx.lastToolName) {
2115
+ log.debug("Status watcher: no context, skipping");
2116
+ return;
2117
+ }
2118
+ log.debug("Status watcher: requesting label", {
2119
+ textLength: ctx.assistantText.length,
2120
+ lastToolName: ctx.lastToolName
2121
+ });
2122
+ const res = await fetch(url, {
2123
+ method: "POST",
2124
+ headers: {
2125
+ "Content-Type": "application/json",
2126
+ Authorization: `Bearer ${apiConfig.apiKey}`
2127
+ },
2128
+ body: JSON.stringify({
2129
+ assistantText: ctx.assistantText.slice(-500),
2130
+ lastToolName: ctx.lastToolName,
2131
+ lastToolResult: ctx.lastToolResult?.slice(-200),
2132
+ onboardingState: ctx.onboardingState,
2133
+ userMessage: ctx.userMessage?.slice(-200)
2134
+ }),
2135
+ signal
2136
+ });
2137
+ if (!res.ok) {
2138
+ log.debug("Status watcher: endpoint returned non-ok", {
2139
+ status: res.status
2140
+ });
2141
+ return;
2142
+ }
2143
+ const data = await res.json();
2144
+ if (!data.label) {
2145
+ log.debug("Status watcher: no label in response");
2146
+ return;
2147
+ }
2148
+ if (data.label === lastLabel) {
2149
+ log.debug("Status watcher: duplicate label, skipping", {
2150
+ label: data.label
2151
+ });
2152
+ return;
2153
+ }
2154
+ lastLabel = data.label;
2155
+ if (stopped) {
2156
+ return;
2157
+ }
2158
+ log.debug("Status watcher: emitting", { label: data.label });
2159
+ onStatus(data.label);
2160
+ } catch (err) {
2161
+ log.debug("Status watcher: error", { error: err?.message ?? "unknown" });
2162
+ } finally {
2163
+ inflight = false;
2164
+ }
2165
+ }
2166
+ const timer = setInterval(tick, interval);
2167
+ tick().catch(() => {
2168
+ });
2169
+ log.debug("Status watcher started", { interval });
2170
+ return {
2171
+ stop() {
2172
+ stopped = true;
2173
+ clearInterval(timer);
2174
+ log.debug("Status watcher stopped");
2175
+ }
2176
+ };
2177
+ }
2178
+ var init_statusWatcher = __esm({
2179
+ "src/statusWatcher.ts"() {
2180
+ "use strict";
2181
+ init_logger();
2182
+ }
2183
+ });
2184
+
2099
2185
  // src/subagents/common/cleanMessages.ts
2100
2186
  function cleanMessagesForApi(messages) {
2101
2187
  return messages.map((msg) => {
@@ -2135,7 +2221,7 @@ async function runSubAgent(config) {
2135
2221
  const {
2136
2222
  system,
2137
2223
  task,
2138
- tools,
2224
+ tools: tools2,
2139
2225
  externalTools,
2140
2226
  executeTool: executeTool2,
2141
2227
  apiConfig,
@@ -2144,19 +2230,47 @@ async function runSubAgent(config) {
2144
2230
  signal,
2145
2231
  parentToolId,
2146
2232
  onEvent,
2147
- resolveExternalTool
2233
+ resolveExternalTool,
2234
+ toolRegistry
2148
2235
  } = config;
2149
2236
  const emit2 = (e) => {
2150
2237
  onEvent({ ...e, parentToolId });
2151
2238
  };
2152
2239
  const messages = [{ role: "user", content: task }];
2240
+ function getPartialText(blocks) {
2241
+ return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
2242
+ }
2243
+ function abortResult(blocks) {
2244
+ if (signal?.reason === "graceful") {
2245
+ const partial = getPartialText(blocks);
2246
+ return {
2247
+ text: partial ? `[INTERRUPTED]
2248
+
2249
+ ${partial}` : "[INTERRUPTED] Sub-agent was interrupted before producing output.",
2250
+ messages
2251
+ };
2252
+ }
2253
+ return { text: "Error: cancelled", messages };
2254
+ }
2255
+ let lastToolResult = "";
2153
2256
  while (true) {
2154
2257
  if (signal?.aborted) {
2155
- return { text: "Error: cancelled", messages };
2258
+ return abortResult([]);
2156
2259
  }
2157
2260
  const contentBlocks = [];
2158
2261
  let thinkingStartedAt = 0;
2159
2262
  let stopReason = "end_turn";
2263
+ let currentToolNames = "";
2264
+ const statusWatcher = startStatusWatcher({
2265
+ apiConfig,
2266
+ getContext: () => ({
2267
+ assistantText: getPartialText(contentBlocks),
2268
+ lastToolName: currentToolNames || void 0,
2269
+ lastToolResult: lastToolResult || void 0
2270
+ }),
2271
+ onStatus: (label) => emit2({ type: "status", message: label }),
2272
+ signal
2273
+ });
2160
2274
  const fullSystem = `${system}
2161
2275
 
2162
2276
  Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC")}`;
@@ -2167,7 +2281,7 @@ Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " "
2167
2281
  subAgentId,
2168
2282
  system: fullSystem,
2169
2283
  messages: cleanMessagesForApi(messages),
2170
- tools,
2284
+ tools: tools2,
2171
2285
  signal
2172
2286
  })) {
2173
2287
  if (signal?.aborted) {
@@ -2232,7 +2346,8 @@ Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " "
2232
2346
  }
2233
2347
  }
2234
2348
  if (signal?.aborted) {
2235
- return { text: "Error: cancelled", messages };
2349
+ statusWatcher.stop();
2350
+ return abortResult(contentBlocks);
2236
2351
  }
2237
2352
  messages.push({
2238
2353
  role: "assistant",
@@ -2242,6 +2357,7 @@ Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " "
2242
2357
  (b) => b.type === "tool"
2243
2358
  );
2244
2359
  if (stopReason !== "tool_use" || toolCalls.length === 0) {
2360
+ statusWatcher.stop();
2245
2361
  const text = contentBlocks.filter((b) => b.type === "text").map((b) => b.text).join("");
2246
2362
  return { text, messages };
2247
2363
  }
@@ -2250,40 +2366,82 @@ Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " "
2250
2366
  count: toolCalls.length,
2251
2367
  tools: toolCalls.map((tc) => tc.name)
2252
2368
  });
2369
+ currentToolNames = toolCalls.map((tc) => tc.name).join(", ");
2253
2370
  const results = await Promise.all(
2254
2371
  toolCalls.map(async (tc) => {
2255
2372
  if (signal?.aborted) {
2256
2373
  return { id: tc.id, result: "Error: cancelled", isError: true };
2257
2374
  }
2258
- try {
2259
- let result;
2260
- if (externalTools.has(tc.name) && resolveExternalTool) {
2261
- result = await resolveExternalTool(tc.id, tc.name, tc.input);
2262
- } else {
2263
- result = await executeTool2(tc.name, tc.input, tc.id);
2375
+ let settle;
2376
+ const resultPromise = new Promise((res) => {
2377
+ settle = (result, isError) => res({ id: tc.id, result, isError });
2378
+ });
2379
+ let toolAbort = new AbortController();
2380
+ const cascadeAbort = () => toolAbort.abort();
2381
+ signal?.addEventListener("abort", cascadeAbort, { once: true });
2382
+ let settled = false;
2383
+ const safeSettle = (result, isError) => {
2384
+ if (settled) {
2385
+ return;
2264
2386
  }
2265
- const isError = result.startsWith("Error");
2266
- emit2({
2267
- type: "tool_done",
2268
- id: tc.id,
2269
- name: tc.name,
2270
- result,
2271
- isError
2272
- });
2273
- return { id: tc.id, result, isError };
2274
- } catch (err) {
2275
- const errorMsg = `Error: ${err.message}`;
2276
- emit2({
2277
- type: "tool_done",
2278
- id: tc.id,
2279
- name: tc.name,
2280
- result: errorMsg,
2281
- isError: true
2282
- });
2283
- return { id: tc.id, result: errorMsg, isError: true };
2284
- }
2387
+ settled = true;
2388
+ signal?.removeEventListener("abort", cascadeAbort);
2389
+ settle(result, isError);
2390
+ };
2391
+ const run = async (input) => {
2392
+ try {
2393
+ let result;
2394
+ if (externalTools.has(tc.name) && resolveExternalTool) {
2395
+ result = await resolveExternalTool(tc.id, tc.name, input);
2396
+ } else {
2397
+ const onLog = (line) => emit2({
2398
+ type: "tool_input_delta",
2399
+ id: tc.id,
2400
+ name: tc.name,
2401
+ result: line
2402
+ });
2403
+ result = await executeTool2(tc.name, input, tc.id, onLog);
2404
+ }
2405
+ safeSettle(result, result.startsWith("Error"));
2406
+ } catch (err) {
2407
+ safeSettle(`Error: ${err.message}`, true);
2408
+ }
2409
+ };
2410
+ const entry = {
2411
+ id: tc.id,
2412
+ name: tc.name,
2413
+ input: tc.input,
2414
+ parentToolId,
2415
+ abortController: toolAbort,
2416
+ startedAt: Date.now(),
2417
+ settle: safeSettle,
2418
+ rerun: (newInput) => {
2419
+ settled = false;
2420
+ toolAbort = new AbortController();
2421
+ signal?.addEventListener("abort", () => toolAbort.abort(), {
2422
+ once: true
2423
+ });
2424
+ entry.abortController = toolAbort;
2425
+ entry.input = newInput;
2426
+ run(newInput);
2427
+ }
2428
+ };
2429
+ toolRegistry?.register(entry);
2430
+ run(tc.input);
2431
+ const r = await resultPromise;
2432
+ toolRegistry?.unregister(tc.id);
2433
+ emit2({
2434
+ type: "tool_done",
2435
+ id: tc.id,
2436
+ name: tc.name,
2437
+ result: r.result,
2438
+ isError: r.isError
2439
+ });
2440
+ return r;
2285
2441
  })
2286
2442
  );
2443
+ statusWatcher.stop();
2444
+ lastToolResult = results.at(-1)?.result ?? "";
2287
2445
  for (const r of results) {
2288
2446
  const block = contentBlocks.find(
2289
2447
  (b) => b.type === "tool" && b.id === r.id
@@ -2291,6 +2449,7 @@ Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " "
2291
2449
  if (block?.type === "tool") {
2292
2450
  block.result = r.result;
2293
2451
  block.isError = r.isError;
2452
+ block.completedAt = Date.now();
2294
2453
  }
2295
2454
  messages.push({
2296
2455
  role: "user",
@@ -2306,6 +2465,7 @@ var init_runner = __esm({
2306
2465
  "use strict";
2307
2466
  init_api();
2308
2467
  init_logger();
2468
+ init_statusWatcher();
2309
2469
  init_cleanMessages();
2310
2470
  }
2311
2471
  });
@@ -2340,7 +2500,7 @@ var init_tools = __esm({
2340
2500
  "styles",
2341
2501
  "screenshot"
2342
2502
  ],
2343
- description: "snapshot: accessibility tree of the page (waits for network to settle). click: click an element (animated cursor, full event sequence). type: type text into input (one char at a time, works with React/Vue/Svelte). select: select a dropdown option by text. wait: wait for an element to appear (polls 100ms, waits for network). navigate: navigate to a URL within the app (waits for load, subsequent steps run on new page). evaluate: run JS in the page. styles: read computed CSS styles from elements (pass properties array with camelCase names, or omit for defaults). screenshot: full-page viewport-stitched screenshot (returns base64 JPEG with dimensions)."
2503
+ description: "snapshot: accessibility tree of the page (waits for network to settle). click: click an element (animated cursor, full event sequence). type: type text into input (one char at a time, works with React/Vue/Svelte). select: select a dropdown option by text. wait: wait for an element to appear (polls 100ms, waits for network). navigate: navigate to a URL within the app (waits for load, subsequent steps run on new page). evaluate: run JS in the page. styles: read computed CSS styles from elements (pass properties array with camelCase names, or omit for defaults). screenshot: full-page viewport-stitched screenshot (returns CDN url with dimensions)."
2344
2504
  },
2345
2505
  ref: {
2346
2506
  type: "string",
@@ -2396,8 +2556,8 @@ var init_tools = __esm({
2396
2556
  }
2397
2557
  },
2398
2558
  {
2399
- name: "screenshot",
2400
- description: "Capture a screenshot of the current page. Returns a CDN URL with dimensions.",
2559
+ name: "screenshotFullPage",
2560
+ description: "Capture a full-height screenshot of the current page. Returns a CDN URL with full text analysis and description.",
2401
2561
  inputSchema: {
2402
2562
  type: "object",
2403
2563
  properties: {}
@@ -2416,12 +2576,54 @@ var init_tools = __esm({
2416
2576
  }
2417
2577
  });
2418
2578
 
2419
- // src/subagents/browserAutomation/prompt.ts
2579
+ // src/assets.ts
2420
2580
  import fs10 from "fs";
2421
2581
  import path4 from "path";
2582
+ function findRoot(start) {
2583
+ let dir = start;
2584
+ while (dir !== path4.dirname(dir)) {
2585
+ if (fs10.existsSync(path4.join(dir, "package.json"))) {
2586
+ return dir;
2587
+ }
2588
+ dir = path4.dirname(dir);
2589
+ }
2590
+ return start;
2591
+ }
2592
+ function assetPath(...segments) {
2593
+ return path4.join(ASSETS_BASE, ...segments);
2594
+ }
2595
+ function readAsset(...segments) {
2596
+ const full = assetPath(...segments);
2597
+ try {
2598
+ return fs10.readFileSync(full, "utf-8").trim();
2599
+ } catch {
2600
+ throw new Error(`Required asset missing: ${full}`);
2601
+ }
2602
+ }
2603
+ function readJsonAsset(fallback, ...segments) {
2604
+ const full = assetPath(...segments);
2605
+ try {
2606
+ return JSON.parse(fs10.readFileSync(full, "utf-8"));
2607
+ } catch {
2608
+ return fallback;
2609
+ }
2610
+ }
2611
+ var ROOT, ASSETS_BASE;
2612
+ var init_assets = __esm({
2613
+ "src/assets.ts"() {
2614
+ "use strict";
2615
+ ROOT = findRoot(
2616
+ import.meta.dirname ?? path4.dirname(new URL(import.meta.url).pathname)
2617
+ );
2618
+ ASSETS_BASE = fs10.existsSync(path4.join(ROOT, "dist", "prompt")) ? path4.join(ROOT, "dist") : path4.join(ROOT, "src");
2619
+ }
2620
+ });
2621
+
2622
+ // src/subagents/browserAutomation/prompt.ts
2623
+ import fs11 from "fs";
2422
2624
  function getBrowserAutomationPrompt() {
2423
2625
  try {
2424
- const appSpec = fs10.readFileSync("src/app.md", "utf-8").trim();
2626
+ const appSpec = fs11.readFileSync("src/app.md", "utf-8").trim();
2425
2627
  return `${BASE_PROMPT}
2426
2628
 
2427
2629
  <app_context>
@@ -2431,14 +2633,12 @@ ${appSpec}
2431
2633
  return BASE_PROMPT;
2432
2634
  }
2433
2635
  }
2434
- var base, local, PROMPT_PATH, BASE_PROMPT;
2636
+ var BASE_PROMPT;
2435
2637
  var init_prompt = __esm({
2436
2638
  "src/subagents/browserAutomation/prompt.ts"() {
2437
2639
  "use strict";
2438
- base = import.meta.dirname ?? path4.dirname(new URL(import.meta.url).pathname);
2439
- local = path4.join(base, "prompt.md");
2440
- PROMPT_PATH = fs10.existsSync(local) ? local : path4.join(base, "subagents", "browserAutomation", "prompt.md");
2441
- BASE_PROMPT = fs10.readFileSync(PROMPT_PATH, "utf-8").trim();
2640
+ init_assets();
2641
+ BASE_PROMPT = readAsset("subagents/browserAutomation", "prompt.md");
2442
2642
  }
2443
2643
  });
2444
2644
 
@@ -2457,7 +2657,7 @@ var init_browserAutomation = __esm({
2457
2657
  browserAutomationTool = {
2458
2658
  definition: {
2459
2659
  name: "runAutomatedBrowserTest",
2460
- description: "Run an automated browser test against the live preview. Describe what to test \u2014 the agent figures out how. Use after writing or modifying frontend code, to reproduce user-reported issues, or to test end-to-end flows.",
2660
+ description: "Run an automated browser test against the live preview. Describe what to test \u2014 the agent figures out how. Use after meaningful changes frontend code, to reproduce user-reported issues, or to test end-to-end flows.",
2461
2661
  inputSchema: {
2462
2662
  type: "object",
2463
2663
  properties: {
@@ -2490,10 +2690,10 @@ var init_browserAutomation = __esm({
2490
2690
  task: input.task,
2491
2691
  tools: BROWSER_TOOLS,
2492
2692
  externalTools: BROWSER_EXTERNAL_TOOLS,
2493
- executeTool: async (name) => {
2494
- if (name === "screenshot") {
2693
+ executeTool: async (name, _input, _toolCallId, onLog) => {
2694
+ if (name === "screenshotFullPage") {
2495
2695
  try {
2496
- return await captureAndAnalyzeScreenshot();
2696
+ return await captureAndAnalyzeScreenshot({ onLog });
2497
2697
  } catch (err) {
2498
2698
  return `Error taking screenshot: ${err.message}`;
2499
2699
  }
@@ -2523,7 +2723,7 @@ var init_browserAutomation = __esm({
2523
2723
  try {
2524
2724
  const parsed = JSON.parse(result2);
2525
2725
  const screenshotSteps = (parsed.steps || []).filter(
2526
- (s) => s.command === "screenshot" && s.result?.url
2726
+ (s) => s.command === "screenshotViewport" && s.result?.url
2527
2727
  );
2528
2728
  if (screenshotSteps.length > 0) {
2529
2729
  const batchInput = screenshotSteps.map((s) => ({
@@ -2541,7 +2741,7 @@ var init_browserAutomation = __esm({
2541
2741
  const analyses = JSON.parse(batchResult);
2542
2742
  let ai = 0;
2543
2743
  for (const step of parsed.steps) {
2544
- if (step.command === "screenshot" && step.result?.url && ai < analyses.length) {
2744
+ if (step.command === "screenshotViewport" && step.result?.url && ai < analyses.length) {
2545
2745
  step.result.analysis = analyses[ai]?.output?.analysis || analyses[ai]?.output || "";
2546
2746
  ai++;
2547
2747
  }
@@ -2557,7 +2757,8 @@ var init_browserAutomation = __esm({
2557
2757
  }
2558
2758
  }
2559
2759
  return result2;
2560
- }
2760
+ },
2761
+ toolRegistry: context.toolRegistry
2561
2762
  });
2562
2763
  context.subAgentMessages?.set(context.toolCallId, result.messages);
2563
2764
  return result.text;
@@ -2566,262 +2767,471 @@ var init_browserAutomation = __esm({
2566
2767
  }
2567
2768
  });
2568
2769
 
2569
- // src/subagents/designExpert/tools.ts
2570
- import fs11 from "fs";
2571
- import path5 from "path";
2572
- function resolvePath(filename) {
2573
- const local4 = path5.join(base2, filename);
2574
- return fs11.existsSync(local4) ? local4 : path5.join(base2, "subagents", "designExpert", filename);
2770
+ // src/subagents/designExpert/tools/searchGoogle.ts
2771
+ var searchGoogle_exports = {};
2772
+ __export(searchGoogle_exports, {
2773
+ definition: () => definition,
2774
+ execute: () => execute
2775
+ });
2776
+ async function execute(input, onLog) {
2777
+ return runCli(
2778
+ `mindstudio search-google --query ${JSON.stringify(input.query)} --export-type json --output-key results --no-meta`,
2779
+ { onLog }
2780
+ );
2575
2781
  }
2576
- async function executeDesignExpertTool(name, input, context, toolCallId) {
2577
- switch (name) {
2578
- case "screenshot": {
2579
- try {
2580
- return await captureAndAnalyzeScreenshot({
2581
- prompt: input.prompt,
2582
- fullPage: input.fullPage
2583
- });
2584
- } catch (err) {
2585
- return `Error taking screenshot: ${err.message}`;
2782
+ var definition;
2783
+ var init_searchGoogle2 = __esm({
2784
+ "src/subagents/designExpert/tools/searchGoogle.ts"() {
2785
+ "use strict";
2786
+ init_runCli();
2787
+ definition = {
2788
+ name: "searchGoogle",
2789
+ description: 'Search Google for web results. Reserch modern design trends in industries or verticals, "best [domain] apps 2026", ui patterns, or find something specific if the the user has an explicit reference. Prioritize authoritative sources like Figma and other design leaders, avoid random blog spam. Pick one or more URLs from the results and then use `fetchUrl` to get their text content.',
2790
+ inputSchema: {
2791
+ type: "object",
2792
+ properties: {
2793
+ query: {
2794
+ type: "string",
2795
+ description: "The search query."
2796
+ }
2797
+ },
2798
+ required: ["query"]
2586
2799
  }
2587
- }
2588
- case "searchGoogle":
2589
- return runCli(
2590
- `mindstudio search-google --query ${JSON.stringify(input.query)} --export-type json --output-key results --no-meta`
2591
- );
2592
- case "fetchUrl": {
2593
- const pageOptions = { onlyMainContent: true };
2594
- if (input.screenshot) {
2595
- pageOptions.screenshot = true;
2800
+ };
2801
+ }
2802
+ });
2803
+
2804
+ // src/subagents/designExpert/tools/fetchUrl.ts
2805
+ var fetchUrl_exports = {};
2806
+ __export(fetchUrl_exports, {
2807
+ definition: () => definition2,
2808
+ execute: () => execute2
2809
+ });
2810
+ async function execute2(input, onLog) {
2811
+ const pageOptions = { onlyMainContent: true };
2812
+ if (input.screenshot) {
2813
+ pageOptions.screenshot = true;
2814
+ }
2815
+ return runCli(
2816
+ `mindstudio scrape-url --url ${JSON.stringify(input.url)} --page-options ${JSON.stringify(JSON.stringify(pageOptions))} --no-meta`,
2817
+ { onLog }
2818
+ );
2819
+ }
2820
+ var definition2;
2821
+ var init_fetchUrl2 = __esm({
2822
+ "src/subagents/designExpert/tools/fetchUrl.ts"() {
2823
+ "use strict";
2824
+ init_runCli();
2825
+ definition2 = {
2826
+ name: "fetchUrl",
2827
+ description: "Fetch the content of a web page as markdown. Use when reading sites from search results or specific things the user wants to incorporate.",
2828
+ inputSchema: {
2829
+ type: "object",
2830
+ properties: {
2831
+ url: {
2832
+ type: "string",
2833
+ description: "The URL to fetch."
2834
+ }
2835
+ },
2836
+ required: ["url"]
2596
2837
  }
2597
- return runCli(
2598
- `mindstudio scrape-url --url ${JSON.stringify(input.url)} --page-options ${JSON.stringify(JSON.stringify(pageOptions))} --no-meta`
2599
- );
2838
+ };
2839
+ }
2840
+ });
2841
+
2842
+ // src/subagents/designExpert/tools/analyzeDesign.ts
2843
+ var analyzeDesign_exports = {};
2844
+ __export(analyzeDesign_exports, {
2845
+ definition: () => definition3,
2846
+ execute: () => execute3
2847
+ });
2848
+ async function execute3(input, onLog) {
2849
+ const url = input.url;
2850
+ const analysisPrompt = input.prompt || DESIGN_REFERENCE_PROMPT;
2851
+ const isImageUrl = /\.(png|jpe?g|webp|gif|svg|avif)(\?|$)/i.test(url);
2852
+ let imageUrl = url;
2853
+ if (!isImageUrl) {
2854
+ const ssUrl = await runCli(
2855
+ `mindstudio screenshot-url --url ${JSON.stringify(url)} --mode viewport --width 1440 --delay 2000 --output-key screenshotUrl --no-meta`,
2856
+ { timeout: 12e4, onLog }
2857
+ );
2858
+ if (ssUrl.startsWith("Error")) {
2859
+ return `Could not screenshot ${url}: ${ssUrl}`;
2600
2860
  }
2601
- case "analyzeReferenceImageOrUrl": {
2602
- const url = input.url;
2603
- const analysisPrompt = input.prompt || DESIGN_REFERENCE_PROMPT;
2604
- const isImageUrl = /\.(png|jpe?g|webp|gif|svg|avif)(\?|$)/i.test(url);
2605
- let imageUrl = url;
2606
- if (!isImageUrl) {
2607
- const ssUrl = await runCli(
2608
- `mindstudio screenshot-url --url ${JSON.stringify(url)} --mode viewport --width 1440 --delay 2000 --output-key screenshotUrl --no-meta`,
2609
- { timeout: 12e4 }
2610
- );
2611
- if (ssUrl.startsWith("Error")) {
2612
- return `Could not screenshot ${url}: ${ssUrl}`;
2613
- }
2614
- imageUrl = ssUrl;
2615
- }
2616
- const analysis = await runCli(
2617
- `mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(imageUrl)} --output-key analysis --no-meta`,
2618
- { timeout: 2e5 }
2619
- );
2620
- return isImageUrl ? analysis : `Screenshot: ${imageUrl}
2861
+ imageUrl = ssUrl;
2862
+ }
2863
+ const analysis = await runCli(
2864
+ `mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(imageUrl)} --output-key analysis --no-meta`,
2865
+ { timeout: 2e5, onLog }
2866
+ );
2867
+ return isImageUrl ? analysis : `Screenshot: ${imageUrl}
2621
2868
 
2622
2869
  ${analysis}`;
2623
- }
2624
- case "generateImages": {
2625
- const prompts = input.prompts;
2626
- const width = input.width || 2048;
2627
- const height = input.height || 2048;
2628
- 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.";
2629
- let imageUrls;
2630
- if (prompts.length === 1) {
2631
- const step = JSON.stringify({
2632
- prompt: prompts[0],
2633
- imageModelOverride: {
2634
- model: "seedream-4.5",
2635
- config: { width, height }
2870
+ }
2871
+ var DESIGN_REFERENCE_PROMPT, definition3;
2872
+ var init_analyzeDesign = __esm({
2873
+ "src/subagents/designExpert/tools/analyzeDesign.ts"() {
2874
+ "use strict";
2875
+ init_runCli();
2876
+ DESIGN_REFERENCE_PROMPT = `
2877
+ You are analyzing a screenshot of a real website or app for a designer's personal technique/inspiration reference notes.
2878
+
2879
+ Analyze the image and think about what makes the site or app special and unique. What is it doing that is unique, different, original, and creative? What makes it special? What isn't working? What doesn't look or feel good?
2880
+
2881
+ Then, provide the following analysis:
2882
+
2883
+ ## Context
2884
+ What is this page, and what does it look like? Very briefly note the industry/vertical and purpose, then describe the composition with enough context to frame the analysis that follows \u2014 what's on the page, where things are positioned, what does the viewport look and feel like. Give enough detail that someone who can't see the image could understand the spatial references in the techniques section. Do not mention specific brand names. Keep it concise.
2885
+
2886
+ ## Colors
2887
+ List the palette as hex values with short labels. Just the swatches \u2014 no "strategy" paragraph.
2888
+
2889
+ ## Typography
2890
+ Brief description of the types used on the page. If you can identify the actual typeface name, provide it, otherwise provide a concrete description (e.g., "ultra-condensed grotesque, ~900 weight, tracked tight at maybe -0.03em, all-caps"). Include size relationships if notable (e.g., "hero text is viewport-width, body is 14px").
2891
+
2892
+ ## Techniques
2893
+ Identify the specific design moves that make this page interesting and unique, described in terms of how a designer with a technical background would write them down as notes in their notebook for inspiration. Focus only on the non-obvious, hard-to-think-of techniques \u2014 the things that make this page gallery-worthy. Skip basics like "high contrast CTA" or "generous whitespace" that any competent designer already knows.
2894
+
2895
+ Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.
2896
+ `;
2897
+ definition3 = {
2898
+ name: "analyzeDesign",
2899
+ description: "Analyze the visual design of a website or image 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 design question instead.",
2900
+ inputSchema: {
2901
+ type: "object",
2902
+ properties: {
2903
+ url: {
2904
+ type: "string",
2905
+ description: "URL to analyze. Can be an image URL or a website URL (will be screenshotted)."
2906
+ },
2907
+ prompt: {
2908
+ type: "string",
2909
+ description: "Optional custom analysis prompt. If omitted, performs the standard design reference analysis."
2636
2910
  }
2637
- });
2638
- const url = await runCli(
2639
- `mindstudio generate-image '${step}' --output-key imageUrl --no-meta`,
2640
- { jsonLogs: true, timeout: 2e5 }
2641
- );
2642
- imageUrls = [url];
2643
- } else {
2644
- const steps = prompts.map((prompt) => ({
2645
- stepType: "generateImage",
2646
- step: {
2647
- prompt,
2648
- imageModelOverride: {
2649
- model: "seedream-4.5",
2650
- config: { width, height }
2651
- }
2911
+ },
2912
+ required: ["url"]
2913
+ }
2914
+ };
2915
+ }
2916
+ });
2917
+
2918
+ // src/subagents/designExpert/tools/analyzeImage.ts
2919
+ var analyzeImage_exports = {};
2920
+ __export(analyzeImage_exports, {
2921
+ definition: () => definition4,
2922
+ execute: () => execute4
2923
+ });
2924
+ async function execute4(input, onLog) {
2925
+ const imageUrl = input.imageUrl;
2926
+ const prompt = input.prompt || DEFAULT_PROMPT;
2927
+ const analysis = await runCli(
2928
+ `mindstudio analyze-image --prompt ${JSON.stringify(prompt)} --image-url ${JSON.stringify(imageUrl)} --output-key analysis --no-meta`,
2929
+ { timeout: 2e5, onLog }
2930
+ );
2931
+ return JSON.stringify({ url: imageUrl, analysis });
2932
+ }
2933
+ var DEFAULT_PROMPT, definition4;
2934
+ var init_analyzeImage = __esm({
2935
+ "src/subagents/designExpert/tools/analyzeImage.ts"() {
2936
+ "use strict";
2937
+ init_runCli();
2938
+ DEFAULT_PROMPT = "Describe everything visible in this image \u2014 every element, its position, its size relative to the frame, its colors, its content. Be comprhensive, thorough and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components). Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.";
2939
+ definition4 = {
2940
+ name: "analyzeImage",
2941
+ description: "Analyze an image by URL. Returns a detailed description of everything visible. Provide a custom prompt to ask a specific question instead of the default full description.",
2942
+ inputSchema: {
2943
+ type: "object",
2944
+ properties: {
2945
+ imageUrl: {
2946
+ type: "string",
2947
+ description: "The image URL to analyze."
2948
+ },
2949
+ prompt: {
2950
+ type: "string",
2951
+ description: "Optional custom analysis prompt. If omitted, describes everything visible in the image."
2652
2952
  }
2653
- }));
2654
- const batchResult = await runCli(
2655
- `mindstudio batch '${JSON.stringify(steps)}' --no-meta`,
2656
- { jsonLogs: true, timeout: 2e5 }
2657
- );
2658
- try {
2659
- const parsed = JSON.parse(batchResult);
2660
- imageUrls = parsed.results.map(
2661
- (r) => r.output?.imageUrl ?? `Error: ${r.error}`
2662
- );
2663
- } catch {
2664
- return batchResult;
2665
- }
2953
+ },
2954
+ required: ["imageUrl"]
2666
2955
  }
2667
- const images = await Promise.all(
2668
- imageUrls.map(async (url, i) => {
2669
- if (url.startsWith("Error")) {
2670
- return { prompt: prompts[i], error: url };
2956
+ };
2957
+ }
2958
+ });
2959
+
2960
+ // src/subagents/designExpert/tools/screenshot.ts
2961
+ var screenshot_exports = {};
2962
+ __export(screenshot_exports, {
2963
+ definition: () => definition5,
2964
+ execute: () => execute5
2965
+ });
2966
+ async function execute5(input, onLog) {
2967
+ try {
2968
+ return await captureAndAnalyzeScreenshot({
2969
+ prompt: input.prompt,
2970
+ onLog
2971
+ });
2972
+ } catch (err) {
2973
+ return `Error taking screenshot: ${err.message}`;
2974
+ }
2975
+ }
2976
+ var definition5;
2977
+ var init_screenshot3 = __esm({
2978
+ "src/subagents/designExpert/tools/screenshot.ts"() {
2979
+ "use strict";
2980
+ init_screenshot();
2981
+ definition5 = {
2982
+ name: "screenshot",
2983
+ description: "Capture a full-height screenshot of the current app preview. Returns a CDN URL along with visual analysis. Use to review the current state of the UI being built. Remember, the screenshot analysis is not overly precise - for example, it cannot reliably identify specific fonts by name \u2014 it can only describe what letterforms look like.",
2984
+ inputSchema: {
2985
+ type: "object",
2986
+ properties: {
2987
+ prompt: {
2988
+ type: "string",
2989
+ description: "Optional specific question about the screenshot."
2671
2990
  }
2672
- const analysis = await runCli(
2673
- `mindstudio analyze-image --prompt ${JSON.stringify(ANALYZE_PROMPT)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`,
2674
- { timeout: 2e5 }
2675
- );
2676
- return { url, prompt: prompts[i], analysis, width, height };
2677
- })
2678
- );
2679
- return `%%JSON%%${JSON.stringify({ images })}`;
2680
- }
2681
- case "runBrowserTest": {
2682
- if (!context) {
2683
- return "Error: browser testing requires execution context (only available in headless mode)";
2991
+ }
2684
2992
  }
2685
- return browserAutomationTool.execute(
2686
- { task: input.task },
2687
- {
2688
- ...context,
2689
- toolCallId: toolCallId || context.toolCallId
2993
+ };
2994
+ }
2995
+ });
2996
+
2997
+ // src/subagents/designExpert/tools/_seedream.ts
2998
+ async function seedreamGenerate(opts) {
2999
+ const { prompts, sourceImages, transparentBackground, onLog } = opts;
3000
+ const width = opts.width || 2048;
3001
+ const height = opts.height || 2048;
3002
+ const config = { width, height };
3003
+ if (sourceImages?.length) {
3004
+ config.images = sourceImages;
3005
+ }
3006
+ let imageUrls;
3007
+ if (prompts.length === 1) {
3008
+ const step = JSON.stringify({
3009
+ prompt: prompts[0],
3010
+ imageModelOverride: {
3011
+ model: "seedream-4.5",
3012
+ config
3013
+ }
3014
+ });
3015
+ const url = await runCli(
3016
+ `mindstudio generate-image ${JSON.stringify(step)} --output-key imageUrl --no-meta`,
3017
+ { jsonLogs: true, timeout: 2e5, onLog }
3018
+ );
3019
+ imageUrls = [url];
3020
+ } else {
3021
+ const steps = prompts.map((prompt) => ({
3022
+ stepType: "generateImage",
3023
+ step: {
3024
+ prompt,
3025
+ imageModelOverride: {
3026
+ model: "seedream-4.5",
3027
+ config
2690
3028
  }
3029
+ }
3030
+ }));
3031
+ const batchResult = await runCli(
3032
+ `mindstudio batch ${JSON.stringify(JSON.stringify(steps))} --no-meta`,
3033
+ { jsonLogs: true, timeout: 2e5, onLog }
3034
+ );
3035
+ try {
3036
+ const parsed = JSON.parse(batchResult);
3037
+ imageUrls = parsed.map(
3038
+ (r) => r.output?.imageUrl ?? `Error: ${r.error}`
2691
3039
  );
3040
+ } catch {
3041
+ return batchResult;
2692
3042
  }
2693
- default:
2694
- return `Error: unknown tool "${name}"`;
2695
3043
  }
3044
+ if (transparentBackground) {
3045
+ imageUrls = await Promise.all(
3046
+ imageUrls.map(async (url) => {
3047
+ if (url.startsWith("Error")) {
3048
+ return url;
3049
+ }
3050
+ const result = await runCli(
3051
+ `mindstudio remove-background-from-image --image-url ${JSON.stringify(url)} --output-key imageUrl --no-meta`,
3052
+ { timeout: 2e5, onLog }
3053
+ );
3054
+ return result.startsWith("Error") ? url : result;
3055
+ })
3056
+ );
3057
+ }
3058
+ const images = await Promise.all(
3059
+ imageUrls.map(async (url, i) => {
3060
+ if (url.startsWith("Error")) {
3061
+ return { prompt: prompts[i], error: url };
3062
+ }
3063
+ const analysis = await runCli(
3064
+ `mindstudio analyze-image --prompt ${JSON.stringify(ANALYZE_PROMPT)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`,
3065
+ { timeout: 2e5, onLog }
3066
+ );
3067
+ return { url, prompt: prompts[i], analysis, width, height };
3068
+ })
3069
+ );
3070
+ return JSON.stringify({ images });
2696
3071
  }
2697
- var base2, DESIGN_REFERENCE_PROMPT, DESIGN_EXPERT_TOOLS;
2698
- var init_tools2 = __esm({
2699
- "src/subagents/designExpert/tools.ts"() {
3072
+ var ANALYZE_PROMPT;
3073
+ var init_seedream = __esm({
3074
+ "src/subagents/designExpert/tools/_seedream.ts"() {
2700
3075
  "use strict";
2701
3076
  init_runCli();
2702
- init_screenshot();
2703
- init_browserAutomation();
2704
- base2 = import.meta.dirname ?? path5.dirname(new URL(import.meta.url).pathname);
2705
- DESIGN_REFERENCE_PROMPT = fs11.readFileSync(resolvePath("prompts/tool-prompts/design-analysis.md"), "utf-8").trim();
2706
- DESIGN_EXPERT_TOOLS = [
2707
- {
2708
- name: "searchGoogle",
2709
- description: 'Search Google for web results. Reserch modern design trends in industries or verticals, "best [domain] apps 2026", ui patterns, etc. Prioritize authoritative sources like Figma and other design leaders, avoid random blog spam. Pick one or more URLs from the results and then use `fetchUrl` to get their text content.',
2710
- inputSchema: {
2711
- type: "object",
2712
- properties: {
2713
- query: {
2714
- type: "string",
2715
- description: "The search query."
2716
- }
2717
- },
2718
- required: ["query"]
2719
- }
2720
- },
2721
- {
2722
- name: "fetchUrl",
2723
- description: "Fetch the content of a web page as markdown. Optionally capture a screenshot to see the visual design. Use when reading sites from search results or specific things the user wants to incorporate.",
2724
- inputSchema: {
2725
- type: "object",
2726
- properties: {
2727
- url: {
2728
- type: "string",
2729
- description: "The URL to fetch."
3077
+ 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. Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.";
3078
+ }
3079
+ });
3080
+
3081
+ // src/subagents/designExpert/tools/generateImages.ts
3082
+ var generateImages_exports = {};
3083
+ __export(generateImages_exports, {
3084
+ definition: () => definition6,
3085
+ execute: () => execute6
3086
+ });
3087
+ async function execute6(input, onLog) {
3088
+ return seedreamGenerate({
3089
+ prompts: input.prompts,
3090
+ width: input.width,
3091
+ height: input.height,
3092
+ transparentBackground: input.transparentBackground,
3093
+ onLog
3094
+ });
3095
+ }
3096
+ var definition6;
3097
+ var init_generateImages = __esm({
3098
+ "src/subagents/designExpert/tools/generateImages.ts"() {
3099
+ "use strict";
3100
+ init_seedream();
3101
+ definition6 = {
3102
+ name: "generateImages",
3103
+ description: "Generate images using AI. Returns CDN URLs with a quality analysis for each image. Produces high-quality results for everything from 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.",
3104
+ inputSchema: {
3105
+ type: "object",
3106
+ properties: {
3107
+ prompts: {
3108
+ type: "array",
3109
+ items: {
3110
+ type: "string"
2730
3111
  },
2731
- screenshot: {
2732
- type: "boolean",
2733
- description: "Capture a screenshot of the page. Use when you need to see the visual design, not just the text."
2734
- }
3112
+ description: "One or more image generation prompts. Be detailed: describe style, mood, composition, colors. Multiple prompts run in parallel."
2735
3113
  },
2736
- required: ["url"]
2737
- }
2738
- },
2739
- {
2740
- name: "analyzeReferenceImageOrUrl",
2741
- 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.",
2742
- inputSchema: {
2743
- type: "object",
2744
- properties: {
2745
- url: {
2746
- type: "string",
2747
- description: "URL to analyze. Can be an image URL or a website URL (will be screenshotted)."
2748
- },
2749
- prompt: {
2750
- type: "string",
2751
- description: "Optional custom analysis prompt. If omitted, performs the standard design reference analysis."
2752
- }
3114
+ width: {
3115
+ type: "number",
3116
+ description: "Image width in pixels. Default 2048. Range: 2048-4096."
2753
3117
  },
2754
- required: ["url"]
2755
- }
2756
- },
2757
- {
2758
- name: "screenshot",
2759
- description: "Capture a screenshot of the app preview. Returns a CDN URL with visual analysis. Use to review the current state of the UI being built. By default captures the viewport. Set fullPage to capture the entire scrollable page.",
2760
- inputSchema: {
2761
- type: "object",
2762
- properties: {
2763
- prompt: {
2764
- type: "string",
2765
- description: "Optional specific question about the screenshot."
2766
- },
2767
- fullPage: {
2768
- type: "boolean",
2769
- description: "Capture the full scrollable page instead of just the viewport. Use when you need to see below-the-fold content."
2770
- }
2771
- }
2772
- }
2773
- },
2774
- {
2775
- name: "runBrowserTest",
2776
- description: "Run an automated browser test against the live app preview. Use to verify implementation details via getComputedStyle: font-family names, exact colors, spacing, borders, shadows, font sizes, transforms. Only use this to evaluate computed CSS properties that can't be deduced from sceenshots.",
2777
- inputSchema: {
2778
- type: "object",
2779
- properties: {
2780
- task: {
2781
- type: "string",
2782
- description: 'What to verify, in natural language. Focus on measurable properties: "Check the hero cards have border-radius: 24px and box-shadow" or "Verify the background color of the CTA section is #C4FF0D".'
2783
- }
3118
+ height: {
3119
+ type: "number",
3120
+ description: "Image height in pixels. Default 2048. Range: 2048-4096."
2784
3121
  },
2785
- required: ["task"]
2786
- }
2787
- },
2788
- {
2789
- name: "generateImages",
2790
- 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.",
2791
- inputSchema: {
2792
- type: "object",
2793
- properties: {
2794
- prompts: {
2795
- type: "array",
2796
- items: {
2797
- type: "string"
2798
- },
2799
- description: "One or more image generation prompts. Be detailed: describe style, mood, composition, colors. Multiple prompts run in parallel."
3122
+ transparentBackground: {
3123
+ type: "boolean",
3124
+ description: "Remove the background from generated images, producing transparent PNGs. Useful for icons, logos, product shots, and assets that need to be composited onto other backgrounds."
3125
+ }
3126
+ },
3127
+ required: ["prompts"]
3128
+ }
3129
+ };
3130
+ }
3131
+ });
3132
+
3133
+ // src/subagents/designExpert/tools/editImages.ts
3134
+ var editImages_exports = {};
3135
+ __export(editImages_exports, {
3136
+ definition: () => definition7,
3137
+ execute: () => execute7
3138
+ });
3139
+ async function execute7(input, onLog) {
3140
+ return seedreamGenerate({
3141
+ prompts: input.prompts,
3142
+ sourceImages: input.sourceImages,
3143
+ width: input.width,
3144
+ height: input.height,
3145
+ transparentBackground: input.transparentBackground,
3146
+ onLog
3147
+ });
3148
+ }
3149
+ var definition7;
3150
+ var init_editImages = __esm({
3151
+ "src/subagents/designExpert/tools/editImages.ts"() {
3152
+ "use strict";
3153
+ init_seedream();
3154
+ definition7 = {
3155
+ name: "editImages",
3156
+ description: "Edit or transform existing images using AI. Provide one or more source image URLs as reference and a prompt describing the desired edit. Use for compositing, style transfer, subject transformation, blending multiple references, or incorporating one or more ferences into something new. Returns CDN URLs with analysis.",
3157
+ inputSchema: {
3158
+ type: "object",
3159
+ properties: {
3160
+ prompts: {
3161
+ type: "array",
3162
+ items: {
3163
+ type: "string"
2800
3164
  },
2801
- width: {
2802
- type: "number",
2803
- description: "Image width in pixels. Default 2048. Range: 2048-4096."
3165
+ description: "One or more edit prompts describing how to transform the source images. Multiple prompts run in parallel, each using the same source images."
3166
+ },
3167
+ sourceImages: {
3168
+ type: "array",
3169
+ items: {
3170
+ type: "string"
2804
3171
  },
2805
- height: {
2806
- type: "number",
2807
- description: "Image height in pixels. Default 2048. Range: 2048-4096."
2808
- }
3172
+ description: "One or more source/reference image URLs. These are used as the basis for the edit \u2014 the AI will use them as reference for style, subject, or composition."
2809
3173
  },
2810
- required: ["prompts"]
2811
- }
3174
+ width: {
3175
+ type: "number",
3176
+ description: "Output width in pixels. Default 2048. Range: 2048-4096."
3177
+ },
3178
+ height: {
3179
+ type: "number",
3180
+ description: "Output height in pixels. Default 2048. Range: 2048-4096."
3181
+ },
3182
+ transparentBackground: {
3183
+ type: "boolean",
3184
+ description: "Remove the background from output images, producing transparent PNGs."
3185
+ }
3186
+ },
3187
+ required: ["prompts", "sourceImages"]
2812
3188
  }
2813
- ];
3189
+ };
3190
+ }
3191
+ });
3192
+
3193
+ // src/subagents/designExpert/tools/index.ts
3194
+ async function executeDesignExpertTool(name, input, context, toolCallId, onLog) {
3195
+ const tool = tools[name];
3196
+ if (!tool) {
3197
+ return `Error: unknown tool "${name}"`;
3198
+ }
3199
+ return tool.execute(input, onLog);
3200
+ }
3201
+ var tools, DESIGN_EXPERT_TOOLS;
3202
+ var init_tools2 = __esm({
3203
+ "src/subagents/designExpert/tools/index.ts"() {
3204
+ "use strict";
3205
+ init_searchGoogle2();
3206
+ init_fetchUrl2();
3207
+ init_analyzeDesign();
3208
+ init_analyzeImage();
3209
+ init_screenshot3();
3210
+ init_generateImages();
3211
+ init_editImages();
3212
+ tools = {
3213
+ searchGoogle: searchGoogle_exports,
3214
+ fetchUrl: fetchUrl_exports,
3215
+ analyzeDesign: analyzeDesign_exports,
3216
+ analyzeImage: analyzeImage_exports,
3217
+ screenshot: screenshot_exports,
3218
+ generateImages: generateImages_exports,
3219
+ editImages: editImages_exports
3220
+ };
3221
+ DESIGN_EXPERT_TOOLS = Object.values(tools).map(
3222
+ (t) => t.definition
3223
+ );
2814
3224
  }
2815
3225
  });
2816
3226
 
2817
3227
  // src/subagents/common/context.ts
2818
3228
  import fs12 from "fs";
2819
- import path6 from "path";
3229
+ import path5 from "path";
2820
3230
  function walkMdFiles(dir, skip) {
2821
3231
  const files = [];
2822
3232
  try {
2823
3233
  for (const entry of fs12.readdirSync(dir, { withFileTypes: true })) {
2824
- const full = path6.join(dir, entry.name);
3234
+ const full = path5.join(dir, entry.name);
2825
3235
  if (entry.isDirectory()) {
2826
3236
  if (!skip?.has(entry.name)) {
2827
3237
  files.push(...walkMdFiles(full, skip));
@@ -2915,23 +3325,7 @@ var init_context = __esm({
2915
3325
  }
2916
3326
  });
2917
3327
 
2918
- // src/subagents/designExpert/prompt.ts
2919
- import fs13 from "fs";
2920
- import path7 from "path";
2921
- function resolvePath2(filename) {
2922
- const local4 = path7.join(base3, filename);
2923
- return fs13.existsSync(local4) ? local4 : path7.join(base3, "subagents", "designExpert", filename);
2924
- }
2925
- function readFile(filename) {
2926
- return fs13.readFileSync(resolvePath2(filename), "utf-8").trim();
2927
- }
2928
- function readJson(filename, fallback) {
2929
- try {
2930
- return JSON.parse(fs13.readFileSync(resolvePath2(filename), "utf-8"));
2931
- } catch {
2932
- return fallback;
2933
- }
2934
- }
3328
+ // src/subagents/designExpert/data/getFontLibrarySample.ts
2935
3329
  function sample(arr, n) {
2936
3330
  if (arr.length <= n) {
2937
3331
  return [...arr];
@@ -2943,10 +3337,12 @@ function sample(arr, n) {
2943
3337
  }
2944
3338
  return copy.slice(0, n);
2945
3339
  }
2946
- function getDesignExpertPrompt() {
2947
- const fonts = sample(fontData.fonts, 30);
2948
- const pairings = sample(fontData.pairings, 20);
2949
- const images = sample(inspirationImages, 15);
3340
+ function getFontLibrarySample() {
3341
+ const fonts = sample(fontData.fonts, 60);
3342
+ const pairings = sample(fontData.pairings, 30);
3343
+ if (!fonts.length) {
3344
+ return "";
3345
+ }
2950
3346
  const fontList = fonts.map((f) => {
2951
3347
  let cssInfo = "";
2952
3348
  if (f.source === "fontshare") {
@@ -2962,60 +3358,102 @@ function getDesignExpertPrompt() {
2962
3358
  const pairingList = pairings.map(
2963
3359
  (p) => `- **${p.heading.font}** (${p.heading.weight}) heading + **${p.body.font}** (${p.body.weight}) body`
2964
3360
  ).join("\n");
2965
- const fontsSection = fonts.length ? `<fonts_to_consider>
2966
- ## Fonts to consider
3361
+ return `
3362
+ ## Font Library
2967
3363
 
2968
- A random sample from Fontshare, Open Foundry, and Google Fonts. Use these as starting points for font selection.
2969
- CSS URL pattern: ${fontData.cssUrlPattern}
3364
+ A random sample from a curated font library. Use these as starting points for font selection.
3365
+
3366
+ ### Fonts
2970
3367
 
2971
3368
  ${fontList}
2972
3369
 
2973
- ### Suggested pairings
3370
+ ### Pairings
3371
+
3372
+ ${pairingList}`.trim();
3373
+ }
3374
+ var fontData;
3375
+ var init_getFontLibrarySample = __esm({
3376
+ "src/subagents/designExpert/data/getFontLibrarySample.ts"() {
3377
+ "use strict";
3378
+ init_assets();
3379
+ fontData = readJsonAsset(
3380
+ { cssUrlPattern: "", fonts: [], pairings: [] },
3381
+ "subagents/designExpert/data/sources/fonts.json"
3382
+ );
3383
+ }
3384
+ });
2974
3385
 
2975
- ${pairingList}
2976
- </fonts_to_consider>` : "";
2977
- const imageList = images.map((img) => `- ${img.analysis}`).join("\n\n");
2978
- const inspirationSection = images.length ? `<design_inspiration>
2979
- ## Design inspiration
3386
+ // src/subagents/designExpert/data/getDesignReferencesSample.ts
3387
+ function sample2(arr, n) {
3388
+ if (arr.length <= n) {
3389
+ return [...arr];
3390
+ }
3391
+ const copy = [...arr];
3392
+ for (let i = copy.length - 1; i > 0; i--) {
3393
+ const j = Math.floor(Math.random() * (i + 1));
3394
+ [copy[i], copy[j]] = [copy[j], copy[i]];
3395
+ }
3396
+ return copy.slice(0, n);
3397
+ }
3398
+ function getDesignReferencesSample() {
3399
+ const images = sample2(inspirationImages, 30);
3400
+ if (!images.length) {
3401
+ return "";
3402
+ }
3403
+ const imageList = images.map((img, i) => `### Reference ${i + 1}
3404
+ ${img.analysis}`).join("\n\n");
3405
+ return `
3406
+ ## Design References
2980
3407
 
2981
3408
  This is what the bar looks like. These are real sites that made it onto curated design galleries because they did something bold, intentional, and memorable. Use them as inspiration and let the takeaways guide your work. Your designs should feel like they belong in this company.
2982
3409
 
2983
- ${imageList}
2984
- </design_inspiration>` : "";
3410
+ ${imageList}`.trim();
3411
+ }
3412
+ var inspirationImages;
3413
+ var init_getDesignReferencesSample = __esm({
3414
+ "src/subagents/designExpert/data/getDesignReferencesSample.ts"() {
3415
+ "use strict";
3416
+ init_assets();
3417
+ inspirationImages = readJsonAsset(
3418
+ { images: [] },
3419
+ "subagents/designExpert/data/sources/inspiration.json"
3420
+ ).images;
3421
+ }
3422
+ });
3423
+
3424
+ // src/subagents/designExpert/prompt.ts
3425
+ import fs13 from "fs";
3426
+ function getDesignExpertPrompt() {
2985
3427
  const specContext = loadSpecContext();
2986
3428
  let prompt = PROMPT_TEMPLATE.replace(
2987
- "{{fonts_to_consider}}",
2988
- fontsSection
2989
- ).replace("{{inspiration_images}}", inspirationSection);
3429
+ "{{font_library}}",
3430
+ getFontLibrarySample()
3431
+ ).replace("{{design_references}}", getDesignReferencesSample());
2990
3432
  if (specContext) {
2991
3433
  prompt += `
2992
3434
 
2993
3435
  ${specContext}`;
3436
+ }
3437
+ try {
3438
+ fs13.writeFileSync(`.design-prompt.md`, prompt);
3439
+ } catch {
2994
3440
  }
2995
3441
  return prompt;
2996
3442
  }
2997
- var base3, RUNTIME_PLACEHOLDERS, PROMPT_TEMPLATE, fontData, inspirationImages;
3443
+ var SUBAGENT, RUNTIME_PLACEHOLDERS, PROMPT_TEMPLATE;
2998
3444
  var init_prompt2 = __esm({
2999
3445
  "src/subagents/designExpert/prompt.ts"() {
3000
3446
  "use strict";
3447
+ init_assets();
3001
3448
  init_context();
3002
- base3 = import.meta.dirname ?? path7.dirname(new URL(import.meta.url).pathname);
3003
- RUNTIME_PLACEHOLDERS = /* @__PURE__ */ new Set([
3004
- "fonts_to_consider",
3005
- "inspiration_images"
3006
- ]);
3007
- PROMPT_TEMPLATE = readFile("prompt.md").replace(/\{\{([^}]+)\}\}/g, (match, key) => {
3449
+ init_getFontLibrarySample();
3450
+ init_getDesignReferencesSample();
3451
+ SUBAGENT = "subagents/designExpert";
3452
+ RUNTIME_PLACEHOLDERS = /* @__PURE__ */ new Set(["font_library", "design_references"]);
3453
+ PROMPT_TEMPLATE = readAsset(SUBAGENT, "prompt.md").replace(/\{\{([^}]+)\}\}/g, (match, key) => {
3008
3454
  const k = key.trim();
3009
- return RUNTIME_PLACEHOLDERS.has(k) ? match : readFile(k);
3455
+ return RUNTIME_PLACEHOLDERS.has(k) ? match : readAsset(SUBAGENT, k);
3010
3456
  }).replace(/\n{3,}/g, "\n\n");
3011
- fontData = readJson("data/fonts.json", {
3012
- cssUrlPattern: "",
3013
- fonts: [],
3014
- pairings: []
3015
- });
3016
- inspirationImages = readJson("data/inspiration.json", {
3017
- images: []
3018
- }).images;
3019
3457
  }
3020
3458
  });
3021
3459
 
@@ -3039,7 +3477,7 @@ Visual design expert. Describe the situation and what you need \u2014 the agent
3039
3477
  properties: {
3040
3478
  task: {
3041
3479
  type: "string",
3042
- description: "What you need, in natural language. Include context about the app when relevant."
3480
+ description: "What you need, in natural language. Include context about the project when relevant."
3043
3481
  }
3044
3482
  },
3045
3483
  required: ["task"]
@@ -3054,14 +3492,15 @@ Visual design expert. Describe the situation and what you need \u2014 the agent
3054
3492
  task: input.task,
3055
3493
  tools: DESIGN_EXPERT_TOOLS,
3056
3494
  externalTools: /* @__PURE__ */ new Set(),
3057
- executeTool: (name, input2, toolCallId) => executeDesignExpertTool(name, input2, context, toolCallId),
3495
+ executeTool: (name, input2, toolCallId, onLog) => executeDesignExpertTool(name, input2, context, toolCallId, onLog),
3058
3496
  apiConfig: context.apiConfig,
3059
3497
  model: context.model,
3060
3498
  subAgentId: "visualDesignExpert",
3061
3499
  signal: context.signal,
3062
3500
  parentToolId: context.toolCallId,
3063
3501
  onEvent: context.onEvent,
3064
- resolveExternalTool: context.resolveExternalTool
3502
+ resolveExternalTool: context.resolveExternalTool,
3503
+ toolRegistry: context.toolRegistry
3065
3504
  });
3066
3505
  context.subAgentMessages?.set(context.toolCallId, result.messages);
3067
3506
  return result.text;
@@ -3176,7 +3615,7 @@ var init_tools3 = __esm({
3176
3615
 
3177
3616
  // src/subagents/productVision/executor.ts
3178
3617
  import fs14 from "fs";
3179
- import path8 from "path";
3618
+ import path6 from "path";
3180
3619
  function formatRequires(requires) {
3181
3620
  return requires.length === 0 ? "[]" : `[${requires.map((r) => `"${r}"`).join(", ")}]`;
3182
3621
  }
@@ -3191,7 +3630,7 @@ async function executeVisionTool(name, input) {
3191
3630
  requires,
3192
3631
  body
3193
3632
  } = input;
3194
- const filePath = path8.join(ROADMAP_DIR, `${slug}.md`);
3633
+ const filePath = path6.join(ROADMAP_DIR, `${slug}.md`);
3195
3634
  try {
3196
3635
  fs14.mkdirSync(ROADMAP_DIR, { recursive: true });
3197
3636
  const oldContent = fs14.existsSync(filePath) ? fs14.readFileSync(filePath, "utf-8") : "";
@@ -3217,7 +3656,7 @@ ${unifiedDiff(filePath, oldContent, content)}`;
3217
3656
  }
3218
3657
  case "updateRoadmapItem": {
3219
3658
  const { slug } = input;
3220
- const filePath = path8.join(ROADMAP_DIR, `${slug}.md`);
3659
+ const filePath = path6.join(ROADMAP_DIR, `${slug}.md`);
3221
3660
  try {
3222
3661
  if (!fs14.existsSync(filePath)) {
3223
3662
  return `Error: ${filePath} does not exist`;
@@ -3285,7 +3724,7 @@ ${unifiedDiff(filePath, oldContent, content)}`;
3285
3724
  }
3286
3725
  case "deleteRoadmapItem": {
3287
3726
  const { slug } = input;
3288
- const filePath = path8.join(ROADMAP_DIR, `${slug}.md`);
3727
+ const filePath = path6.join(ROADMAP_DIR, `${slug}.md`);
3289
3728
  try {
3290
3729
  if (!fs14.existsSync(filePath)) {
3291
3730
  return `Error: ${filePath} does not exist`;
@@ -3313,8 +3752,6 @@ var init_executor = __esm({
3313
3752
  });
3314
3753
 
3315
3754
  // src/subagents/productVision/prompt.ts
3316
- import fs15 from "fs";
3317
- import path9 from "path";
3318
3755
  function getProductVisionPrompt() {
3319
3756
  const specContext = loadSpecContext();
3320
3757
  const roadmapContext = loadRoadmapContext();
@@ -3327,16 +3764,14 @@ function getProductVisionPrompt() {
3327
3764
  }
3328
3765
  return parts.join("\n\n");
3329
3766
  }
3330
- var base4, local2, PROMPT_PATH2, BASE_PROMPT2;
3767
+ var BASE_PROMPT2;
3331
3768
  var init_prompt3 = __esm({
3332
3769
  "src/subagents/productVision/prompt.ts"() {
3333
3770
  "use strict";
3771
+ init_assets();
3334
3772
  init_executor();
3335
3773
  init_context();
3336
- base4 = import.meta.dirname ?? path9.dirname(new URL(import.meta.url).pathname);
3337
- local2 = path9.join(base4, "prompt.md");
3338
- PROMPT_PATH2 = fs15.existsSync(local2) ? local2 : path9.join(base4, "subagents", "productVision", "prompt.md");
3339
- BASE_PROMPT2 = fs15.readFileSync(PROMPT_PATH2, "utf-8").trim();
3774
+ BASE_PROMPT2 = readAsset("subagents/productVision", "prompt.md");
3340
3775
  }
3341
3776
  });
3342
3777
 
@@ -3380,7 +3815,8 @@ var init_productVision = __esm({
3380
3815
  signal: context.signal,
3381
3816
  parentToolId: context.toolCallId,
3382
3817
  onEvent: context.onEvent,
3383
- resolveExternalTool: context.resolveExternalTool
3818
+ resolveExternalTool: context.resolveExternalTool,
3819
+ toolRegistry: context.toolRegistry
3384
3820
  });
3385
3821
  context.subAgentMessages?.set(context.toolCallId, result.messages);
3386
3822
  return result.text;
@@ -3487,20 +3923,16 @@ var init_tools4 = __esm({
3487
3923
  });
3488
3924
 
3489
3925
  // src/subagents/codeSanityCheck/index.ts
3490
- import fs16 from "fs";
3491
- import path10 from "path";
3492
- var base5, local3, PROMPT_PATH3, BASE_PROMPT3, codeSanityCheckTool;
3926
+ var BASE_PROMPT3, codeSanityCheckTool;
3493
3927
  var init_codeSanityCheck = __esm({
3494
3928
  "src/subagents/codeSanityCheck/index.ts"() {
3495
3929
  "use strict";
3930
+ init_assets();
3496
3931
  init_runner();
3497
3932
  init_context();
3498
3933
  init_tools5();
3499
3934
  init_tools4();
3500
- base5 = import.meta.dirname ?? path10.dirname(new URL(import.meta.url).pathname);
3501
- local3 = path10.join(base5, "prompt.md");
3502
- PROMPT_PATH3 = fs16.existsSync(local3) ? local3 : path10.join(base5, "subagents", "codeSanityCheck", "prompt.md");
3503
- BASE_PROMPT3 = fs16.readFileSync(PROMPT_PATH3, "utf-8").trim();
3935
+ BASE_PROMPT3 = readAsset("subagents/codeSanityCheck", "prompt.md");
3504
3936
  codeSanityCheckTool = {
3505
3937
  definition: {
3506
3938
  name: "codeSanityCheck",
@@ -3538,7 +3970,8 @@ var init_codeSanityCheck = __esm({
3538
3970
  signal: context.signal,
3539
3971
  parentToolId: context.toolCallId,
3540
3972
  onEvent: context.onEvent,
3541
- resolveExternalTool: context.resolveExternalTool
3973
+ resolveExternalTool: context.resolveExternalTool,
3974
+ toolRegistry: context.toolRegistry
3542
3975
  });
3543
3976
  context.subAgentMessages?.set(context.toolCallId, result.messages);
3544
3977
  return result.text;
@@ -3552,7 +3985,7 @@ function getSpecTools() {
3552
3985
  return [readSpecTool, writeSpecTool, editSpecTool, listSpecFilesTool];
3553
3986
  }
3554
3987
  function getCodeTools() {
3555
- const tools = [
3988
+ const tools2 = [
3556
3989
  readFileTool,
3557
3990
  writeFileTool,
3558
3991
  editFileTool,
@@ -3567,9 +4000,9 @@ function getCodeTools() {
3567
4000
  browserAutomationTool
3568
4001
  ];
3569
4002
  if (isLspConfigured()) {
3570
- tools.push(lspDiagnosticsTool, restartProcessTool);
4003
+ tools2.push(lspDiagnosticsTool, restartProcessTool);
3571
4004
  }
3572
- return tools;
4005
+ return tools2;
3573
4006
  }
3574
4007
  function getCommonTools() {
3575
4008
  return [
@@ -3667,10 +4100,10 @@ var init_tools5 = __esm({
3667
4100
  });
3668
4101
 
3669
4102
  // src/session.ts
3670
- import fs17 from "fs";
4103
+ import fs15 from "fs";
3671
4104
  function loadSession(state) {
3672
4105
  try {
3673
- const raw = fs17.readFileSync(SESSION_FILE, "utf-8");
4106
+ const raw = fs15.readFileSync(SESSION_FILE, "utf-8");
3674
4107
  const data = JSON.parse(raw);
3675
4108
  if (Array.isArray(data.messages) && data.messages.length > 0) {
3676
4109
  state.messages = sanitizeMessages(data.messages);
@@ -3718,7 +4151,7 @@ function sanitizeMessages(messages) {
3718
4151
  }
3719
4152
  function saveSession(state) {
3720
4153
  try {
3721
- fs17.writeFileSync(
4154
+ fs15.writeFileSync(
3722
4155
  SESSION_FILE,
3723
4156
  JSON.stringify({ messages: state.messages }, null, 2),
3724
4157
  "utf-8"
@@ -3729,7 +4162,7 @@ function saveSession(state) {
3729
4162
  function clearSession(state) {
3730
4163
  state.messages = [];
3731
4164
  try {
3732
- fs17.unlinkSync(SESSION_FILE);
4165
+ fs15.unlinkSync(SESSION_FILE);
3733
4166
  } catch {
3734
4167
  }
3735
4168
  }
@@ -3910,91 +4343,6 @@ var init_parsePartialJson = __esm({
3910
4343
  }
3911
4344
  });
3912
4345
 
3913
- // src/statusWatcher.ts
3914
- function startStatusWatcher(config) {
3915
- const { apiConfig, getContext, onStatus, interval = 3e3, signal } = config;
3916
- let lastLabel = "";
3917
- let inflight = false;
3918
- let stopped = false;
3919
- const url = `${apiConfig.baseUrl}/_internal/v2/agent/remy/generate-status`;
3920
- async function tick() {
3921
- if (stopped || signal?.aborted || inflight) {
3922
- return;
3923
- }
3924
- inflight = true;
3925
- try {
3926
- const ctx = getContext();
3927
- if (!ctx.assistantText && !ctx.lastToolName) {
3928
- log.debug("Status watcher: no context, skipping");
3929
- return;
3930
- }
3931
- log.debug("Status watcher: requesting label", {
3932
- textLength: ctx.assistantText.length,
3933
- lastToolName: ctx.lastToolName
3934
- });
3935
- const res = await fetch(url, {
3936
- method: "POST",
3937
- headers: {
3938
- "Content-Type": "application/json",
3939
- Authorization: `Bearer ${apiConfig.apiKey}`
3940
- },
3941
- body: JSON.stringify({
3942
- assistantText: ctx.assistantText.slice(-500),
3943
- lastToolName: ctx.lastToolName,
3944
- lastToolResult: ctx.lastToolResult?.slice(-200),
3945
- onboardingState: ctx.onboardingState,
3946
- userMessage: ctx.userMessage?.slice(-200)
3947
- }),
3948
- signal
3949
- });
3950
- if (!res.ok) {
3951
- log.debug("Status watcher: endpoint returned non-ok", {
3952
- status: res.status
3953
- });
3954
- return;
3955
- }
3956
- const data = await res.json();
3957
- if (!data.label) {
3958
- log.debug("Status watcher: no label in response");
3959
- return;
3960
- }
3961
- if (data.label === lastLabel) {
3962
- log.debug("Status watcher: duplicate label, skipping", {
3963
- label: data.label
3964
- });
3965
- return;
3966
- }
3967
- lastLabel = data.label;
3968
- if (stopped) {
3969
- return;
3970
- }
3971
- log.debug("Status watcher: emitting", { label: data.label });
3972
- onStatus(data.label);
3973
- } catch (err) {
3974
- log.debug("Status watcher: error", { error: err?.message ?? "unknown" });
3975
- } finally {
3976
- inflight = false;
3977
- }
3978
- }
3979
- const timer = setInterval(tick, interval);
3980
- tick().catch(() => {
3981
- });
3982
- log.debug("Status watcher started", { interval });
3983
- return {
3984
- stop() {
3985
- stopped = true;
3986
- clearInterval(timer);
3987
- log.debug("Status watcher stopped");
3988
- }
3989
- };
3990
- }
3991
- var init_statusWatcher = __esm({
3992
- "src/statusWatcher.ts"() {
3993
- "use strict";
3994
- init_logger();
3995
- }
3996
- });
3997
-
3998
4346
  // src/errors.ts
3999
4347
  function friendlyError(raw) {
4000
4348
  for (const [pattern, message] of patterns) {
@@ -4051,13 +4399,14 @@ async function runTurn(params) {
4051
4399
  signal,
4052
4400
  onEvent,
4053
4401
  resolveExternalTool,
4054
- hidden
4402
+ hidden,
4403
+ toolRegistry
4055
4404
  } = params;
4056
- const tools = getToolDefinitions(onboardingState);
4405
+ const tools2 = getToolDefinitions(onboardingState);
4057
4406
  log.info("Turn started", {
4058
4407
  messageLength: userMessage.length,
4059
- toolCount: tools.length,
4060
- tools: tools.map((t) => t.name),
4408
+ toolCount: tools2.length,
4409
+ tools: tools2.map((t) => t.name),
4061
4410
  ...attachments && attachments.length > 0 && {
4062
4411
  attachmentCount: attachments.length,
4063
4412
  attachmentUrls: attachments.map((a) => a.url)
@@ -4104,6 +4453,20 @@ async function runTurn(params) {
4104
4453
  let thinkingStartedAt = 0;
4105
4454
  const toolInputAccumulators = /* @__PURE__ */ new Map();
4106
4455
  let stopReason = "end_turn";
4456
+ let subAgentText = "";
4457
+ let currentToolNames = "";
4458
+ const statusWatcher = startStatusWatcher({
4459
+ apiConfig,
4460
+ getContext: () => ({
4461
+ assistantText: subAgentText || getTextContent(contentBlocks).slice(-500),
4462
+ lastToolName: currentToolNames || getToolCalls(contentBlocks).filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).at(-1)?.name || lastCompletedTools || void 0,
4463
+ lastToolResult: lastCompletedResult || void 0,
4464
+ onboardingState,
4465
+ userMessage
4466
+ }),
4467
+ onStatus: (label) => onEvent({ type: "status", message: label }),
4468
+ signal
4469
+ });
4107
4470
  async function handlePartialInput(acc, id, name, partial) {
4108
4471
  const tool = getToolByName(name);
4109
4472
  if (!tool?.streaming) {
@@ -4167,18 +4530,6 @@ async function runTurn(params) {
4167
4530
  onEvent({ type: "tool_input_delta", id, name, result: content });
4168
4531
  }
4169
4532
  }
4170
- const statusWatcher = startStatusWatcher({
4171
- apiConfig,
4172
- getContext: () => ({
4173
- assistantText: getTextContent(contentBlocks).slice(-500),
4174
- lastToolName: getToolCalls(contentBlocks).filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).at(-1)?.name || lastCompletedTools || void 0,
4175
- lastToolResult: lastCompletedResult || void 0,
4176
- onboardingState,
4177
- userMessage
4178
- }),
4179
- onStatus: (label) => onEvent({ type: "status", message: label }),
4180
- signal
4181
- });
4182
4533
  try {
4183
4534
  for await (const event of streamChatWithRetry(
4184
4535
  {
@@ -4186,7 +4537,7 @@ async function runTurn(params) {
4186
4537
  model,
4187
4538
  system,
4188
4539
  messages: cleanMessagesForApi(state.messages),
4189
- tools,
4540
+ tools: tools2,
4190
4541
  signal
4191
4542
  },
4192
4543
  {
@@ -4299,10 +4650,9 @@ async function runTurn(params) {
4299
4650
  } else {
4300
4651
  throw err;
4301
4652
  }
4302
- } finally {
4303
- statusWatcher.stop();
4304
4653
  }
4305
4654
  if (signal?.aborted) {
4655
+ statusWatcher.stop();
4306
4656
  if (contentBlocks.length > 0) {
4307
4657
  contentBlocks.push({
4308
4658
  type: "text",
@@ -4324,6 +4674,7 @@ async function runTurn(params) {
4324
4674
  });
4325
4675
  const toolCalls = getToolCalls(contentBlocks);
4326
4676
  if (stopReason !== "tool_use" || toolCalls.length === 0) {
4677
+ statusWatcher.stop();
4327
4678
  saveSession(state);
4328
4679
  onEvent({ type: "turn_done" });
4329
4680
  return;
@@ -4332,8 +4683,7 @@ async function runTurn(params) {
4332
4683
  count: toolCalls.length,
4333
4684
  tools: toolCalls.map((tc) => tc.name)
4334
4685
  });
4335
- let subAgentText = "";
4336
- const origOnEvent = onEvent;
4686
+ currentToolNames = toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ");
4337
4687
  const wrappedOnEvent = (e) => {
4338
4688
  if ("parentToolId" in e && e.parentToolId) {
4339
4689
  if (e.type === "text") {
@@ -4342,80 +4692,103 @@ async function runTurn(params) {
4342
4692
  subAgentText = `Using ${e.name}`;
4343
4693
  }
4344
4694
  }
4345
- origOnEvent(e);
4695
+ onEvent(e);
4346
4696
  };
4347
- const toolStatusWatcher = startStatusWatcher({
4348
- apiConfig,
4349
- getContext: () => ({
4350
- assistantText: subAgentText || getTextContent(contentBlocks).slice(-500),
4351
- lastToolName: toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ") || void 0,
4352
- lastToolResult: lastCompletedResult || void 0,
4353
- onboardingState,
4354
- userMessage
4355
- }),
4356
- onStatus: (label) => origOnEvent({ type: "status", message: label }),
4357
- signal
4358
- });
4359
4697
  const subAgentMessages = /* @__PURE__ */ new Map();
4360
4698
  const results = await Promise.all(
4361
4699
  toolCalls.map(async (tc) => {
4362
4700
  if (signal?.aborted) {
4363
- return {
4364
- id: tc.id,
4365
- result: "Error: cancelled",
4366
- isError: true
4367
- };
4701
+ return { id: tc.id, result: "Error: cancelled", isError: true };
4368
4702
  }
4369
4703
  const toolStart = Date.now();
4370
- try {
4371
- let result;
4372
- if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
4373
- saveSession(state);
4374
- log.info("Waiting for external tool result", {
4375
- name: tc.name,
4376
- id: tc.id
4377
- });
4378
- result = await resolveExternalTool(tc.id, tc.name, tc.input);
4379
- } else {
4380
- result = await executeTool(tc.name, tc.input, {
4381
- apiConfig,
4382
- model,
4383
- signal,
4384
- onEvent: wrappedOnEvent,
4385
- resolveExternalTool,
4386
- toolCallId: tc.id,
4387
- subAgentMessages
4704
+ let settle;
4705
+ const resultPromise = new Promise((res) => {
4706
+ settle = (result, isError) => res({ id: tc.id, result, isError });
4707
+ });
4708
+ let toolAbort = new AbortController();
4709
+ const cascadeAbort = () => toolAbort.abort();
4710
+ signal?.addEventListener("abort", cascadeAbort, { once: true });
4711
+ let settled = false;
4712
+ const safeSettle = (result, isError) => {
4713
+ if (settled) {
4714
+ return;
4715
+ }
4716
+ settled = true;
4717
+ signal?.removeEventListener("abort", cascadeAbort);
4718
+ settle(result, isError);
4719
+ };
4720
+ const run = async (input) => {
4721
+ try {
4722
+ let result;
4723
+ if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
4724
+ saveSession(state);
4725
+ log.info("Waiting for external tool result", {
4726
+ name: tc.name,
4727
+ id: tc.id
4728
+ });
4729
+ result = await resolveExternalTool(tc.id, tc.name, input);
4730
+ } else {
4731
+ result = await executeTool(tc.name, input, {
4732
+ apiConfig,
4733
+ model,
4734
+ signal: toolAbort.signal,
4735
+ onEvent: wrappedOnEvent,
4736
+ resolveExternalTool,
4737
+ toolCallId: tc.id,
4738
+ subAgentMessages,
4739
+ toolRegistry,
4740
+ onLog: (line) => wrappedOnEvent({
4741
+ type: "tool_input_delta",
4742
+ id: tc.id,
4743
+ name: tc.name,
4744
+ result: line
4745
+ })
4746
+ });
4747
+ }
4748
+ safeSettle(result, result.startsWith("Error"));
4749
+ } catch (err) {
4750
+ safeSettle(`Error: ${err.message}`, true);
4751
+ }
4752
+ };
4753
+ const entry = {
4754
+ id: tc.id,
4755
+ name: tc.name,
4756
+ input: tc.input,
4757
+ abortController: toolAbort,
4758
+ startedAt: toolStart,
4759
+ settle: safeSettle,
4760
+ rerun: (newInput) => {
4761
+ settled = false;
4762
+ toolAbort = new AbortController();
4763
+ signal?.addEventListener("abort", () => toolAbort.abort(), {
4764
+ once: true
4388
4765
  });
4766
+ entry.abortController = toolAbort;
4767
+ entry.input = newInput;
4768
+ run(newInput);
4389
4769
  }
4390
- const isError = result.startsWith("Error");
4391
- log.info("Tool completed", {
4392
- name: tc.name,
4393
- elapsed: `${Date.now() - toolStart}ms`,
4394
- isError,
4395
- resultLength: result.length
4396
- });
4397
- onEvent({
4398
- type: "tool_done",
4399
- id: tc.id,
4400
- name: tc.name,
4401
- result,
4402
- isError
4403
- });
4404
- return { id: tc.id, result, isError };
4405
- } catch (err) {
4406
- const errorMsg = `Error: ${err.message}`;
4407
- onEvent({
4408
- type: "tool_done",
4409
- id: tc.id,
4410
- name: tc.name,
4411
- result: errorMsg,
4412
- isError: true
4413
- });
4414
- return { id: tc.id, result: errorMsg, isError: true };
4415
- }
4770
+ };
4771
+ toolRegistry?.register(entry);
4772
+ run(tc.input);
4773
+ const r = await resultPromise;
4774
+ toolRegistry?.unregister(tc.id);
4775
+ log.info("Tool completed", {
4776
+ name: tc.name,
4777
+ elapsed: `${Date.now() - toolStart}ms`,
4778
+ isError: r.isError,
4779
+ resultLength: r.result.length
4780
+ });
4781
+ onEvent({
4782
+ type: "tool_done",
4783
+ id: tc.id,
4784
+ name: tc.name,
4785
+ result: r.result,
4786
+ isError: r.isError
4787
+ });
4788
+ return r;
4416
4789
  })
4417
4790
  );
4418
- toolStatusWatcher.stop();
4791
+ statusWatcher.stop();
4419
4792
  for (const r of results) {
4420
4793
  const block = contentBlocks.find(
4421
4794
  (b) => b.type === "tool" && b.id === r.id
@@ -4423,6 +4796,7 @@ async function runTurn(params) {
4423
4796
  if (block?.type === "tool") {
4424
4797
  block.result = r.result;
4425
4798
  block.isError = r.isError;
4799
+ block.completedAt = Date.now();
4426
4800
  const msgs = subAgentMessages.get(r.id);
4427
4801
  if (msgs) {
4428
4802
  block.subAgentMessages = msgs;
@@ -4475,12 +4849,12 @@ var init_agent = __esm({
4475
4849
  });
4476
4850
 
4477
4851
  // src/prompt/static/projectContext.ts
4478
- import fs18 from "fs";
4479
- import path11 from "path";
4852
+ import fs16 from "fs";
4853
+ import path7 from "path";
4480
4854
  function loadProjectInstructions() {
4481
4855
  for (const file of AGENT_INSTRUCTION_FILES) {
4482
4856
  try {
4483
- const content = fs18.readFileSync(file, "utf-8").trim();
4857
+ const content = fs16.readFileSync(file, "utf-8").trim();
4484
4858
  if (content) {
4485
4859
  return `
4486
4860
  ## Project Instructions (${file})
@@ -4493,7 +4867,7 @@ ${content}`;
4493
4867
  }
4494
4868
  function loadProjectManifest() {
4495
4869
  try {
4496
- const manifest = fs18.readFileSync("mindstudio.json", "utf-8");
4870
+ const manifest = fs16.readFileSync("mindstudio.json", "utf-8");
4497
4871
  return `
4498
4872
  ## Project Manifest (mindstudio.json)
4499
4873
  \`\`\`json
@@ -4534,9 +4908,9 @@ ${entries.join("\n")}`;
4534
4908
  function walkMdFiles2(dir) {
4535
4909
  const results = [];
4536
4910
  try {
4537
- const entries = fs18.readdirSync(dir, { withFileTypes: true });
4911
+ const entries = fs16.readdirSync(dir, { withFileTypes: true });
4538
4912
  for (const entry of entries) {
4539
- const full = path11.join(dir, entry.name);
4913
+ const full = path7.join(dir, entry.name);
4540
4914
  if (entry.isDirectory()) {
4541
4915
  results.push(...walkMdFiles2(full));
4542
4916
  } else if (entry.name.endsWith(".md")) {
@@ -4549,7 +4923,7 @@ function walkMdFiles2(dir) {
4549
4923
  }
4550
4924
  function parseFrontmatter(filePath) {
4551
4925
  try {
4552
- const content = fs18.readFileSync(filePath, "utf-8");
4926
+ const content = fs16.readFileSync(filePath, "utf-8");
4553
4927
  const match = content.match(/^---\n([\s\S]*?)\n---/);
4554
4928
  if (!match) {
4555
4929
  return { name: "", description: "", type: "" };
@@ -4565,7 +4939,7 @@ function parseFrontmatter(filePath) {
4565
4939
  }
4566
4940
  function loadProjectFileListing() {
4567
4941
  try {
4568
- const entries = fs18.readdirSync(".", { withFileTypes: true });
4942
+ const entries = fs16.readdirSync(".", { withFileTypes: true });
4569
4943
  const listing = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules").sort((a, b) => {
4570
4944
  if (a.isDirectory() && !b.isDirectory()) {
4571
4945
  return -1;
@@ -4608,20 +4982,10 @@ var init_projectContext = __esm({
4608
4982
  });
4609
4983
 
4610
4984
  // src/prompt/index.ts
4611
- import fs19 from "fs";
4612
- import path12 from "path";
4613
- function requireFile(filePath) {
4614
- const full = path12.join(PROMPT_DIR, filePath);
4615
- try {
4616
- return fs19.readFileSync(full, "utf-8").trim();
4617
- } catch {
4618
- throw new Error(`Required prompt file missing: ${full}`);
4619
- }
4620
- }
4621
4985
  function resolveIncludes(template) {
4622
4986
  const result = template.replace(
4623
4987
  /\{\{([^}]+)\}\}/g,
4624
- (_, filePath) => requireFile(filePath.trim())
4988
+ (_, filePath) => readAsset("prompt", filePath.trim())
4625
4989
  );
4626
4990
  return result.replace(/\n{3,}/g, "\n\n").trim();
4627
4991
  }
@@ -4731,23 +5095,22 @@ ${viewContext?.activeFile ? `Active file: ${viewContext.activeFile}` : ""}
4731
5095
  `;
4732
5096
  return resolveIncludes(template);
4733
5097
  }
4734
- var PROMPT_DIR;
4735
5098
  var init_prompt4 = __esm({
4736
5099
  "src/prompt/index.ts"() {
4737
5100
  "use strict";
5101
+ init_assets();
4738
5102
  init_lsp();
4739
5103
  init_projectContext();
4740
- PROMPT_DIR = import.meta.dirname ?? path12.dirname(new URL(import.meta.url).pathname);
4741
5104
  }
4742
5105
  });
4743
5106
 
4744
5107
  // src/config.ts
4745
- import fs20 from "fs";
4746
- import path13 from "path";
5108
+ import fs17 from "fs";
5109
+ import path8 from "path";
4747
5110
  import os from "os";
4748
5111
  function loadConfigFile() {
4749
5112
  try {
4750
- const raw = fs20.readFileSync(CONFIG_PATH, "utf-8");
5113
+ const raw = fs17.readFileSync(CONFIG_PATH, "utf-8");
4751
5114
  log.debug("Loaded config file", { path: CONFIG_PATH });
4752
5115
  return JSON.parse(raw);
4753
5116
  } catch (err) {
@@ -4783,7 +5146,7 @@ var init_config = __esm({
4783
5146
  "src/config.ts"() {
4784
5147
  "use strict";
4785
5148
  init_logger();
4786
- CONFIG_PATH = path13.join(
5149
+ CONFIG_PATH = path8.join(
4787
5150
  os.homedir(),
4788
5151
  ".mindstudio-local-tunnel",
4789
5152
  "config.json"
@@ -4792,16 +5155,92 @@ var init_config = __esm({
4792
5155
  }
4793
5156
  });
4794
5157
 
5158
+ // src/toolRegistry.ts
5159
+ var ToolRegistry;
5160
+ var init_toolRegistry = __esm({
5161
+ "src/toolRegistry.ts"() {
5162
+ "use strict";
5163
+ ToolRegistry = class {
5164
+ entries = /* @__PURE__ */ new Map();
5165
+ onEvent;
5166
+ register(entry) {
5167
+ this.entries.set(entry.id, entry);
5168
+ }
5169
+ unregister(id) {
5170
+ this.entries.delete(id);
5171
+ }
5172
+ get(id) {
5173
+ return this.entries.get(id);
5174
+ }
5175
+ /**
5176
+ * Stop a running tool.
5177
+ *
5178
+ * - graceful: abort and settle with [INTERRUPTED] + partial result
5179
+ * - hard: abort and settle with a generic error
5180
+ *
5181
+ * Returns true if the tool was found and stopped.
5182
+ */
5183
+ stop(id, mode) {
5184
+ const entry = this.entries.get(id);
5185
+ if (!entry) {
5186
+ return false;
5187
+ }
5188
+ entry.abortController.abort(mode);
5189
+ if (mode === "graceful") {
5190
+ const partial = entry.getPartialResult?.() ?? "";
5191
+ const result = partial ? `[INTERRUPTED]
5192
+
5193
+ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
5194
+ entry.settle(result, false);
5195
+ } else {
5196
+ entry.settle("Error: tool was cancelled", true);
5197
+ }
5198
+ this.onEvent?.({
5199
+ type: "tool_stopped",
5200
+ id: entry.id,
5201
+ name: entry.name,
5202
+ mode,
5203
+ ...entry.parentToolId && { parentToolId: entry.parentToolId }
5204
+ });
5205
+ this.entries.delete(id);
5206
+ return true;
5207
+ }
5208
+ /**
5209
+ * Restart a running tool with the same or patched input.
5210
+ * The original controllable promise stays pending and settles
5211
+ * when the new execution finishes.
5212
+ *
5213
+ * Returns true if the tool was found and restarted.
5214
+ */
5215
+ restart(id, patchedInput) {
5216
+ const entry = this.entries.get(id);
5217
+ if (!entry) {
5218
+ return false;
5219
+ }
5220
+ entry.abortController.abort("restart");
5221
+ const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
5222
+ this.onEvent?.({
5223
+ type: "tool_restarted",
5224
+ id: entry.id,
5225
+ name: entry.name,
5226
+ input: newInput,
5227
+ ...entry.parentToolId && { parentToolId: entry.parentToolId }
5228
+ });
5229
+ entry.rerun(newInput);
5230
+ return true;
5231
+ }
5232
+ };
5233
+ }
5234
+ });
5235
+
4795
5236
  // src/headless.ts
4796
5237
  var headless_exports = {};
4797
5238
  __export(headless_exports, {
4798
5239
  startHeadless: () => startHeadless
4799
5240
  });
4800
5241
  import { createInterface } from "readline";
4801
- import fs21 from "fs";
4802
- import path14 from "path";
4803
5242
  function loadActionPrompt(name) {
4804
- return fs21.readFileSync(path14.join(ACTIONS_DIR, `${name}.md`), "utf-8").trim();
5243
+ return readAsset("prompt", "actions", `${name}.md`);
4805
5244
  }
4806
5245
  function emit(event, data, requestId) {
4807
5246
  const payload = { event, ...data };
@@ -4865,6 +5304,7 @@ async function startHeadless(opts = {}) {
4865
5304
  const EXTERNAL_TOOL_TIMEOUT_MS = 3e5;
4866
5305
  const pendingTools = /* @__PURE__ */ new Map();
4867
5306
  const earlyResults = /* @__PURE__ */ new Map();
5307
+ const toolRegistry = new ToolRegistry();
4868
5308
  const USER_FACING_TOOLS = /* @__PURE__ */ new Set([
4869
5309
  "promptUser",
4870
5310
  "confirmDestructiveAction",
@@ -4969,14 +5409,46 @@ async function startHeadless(opts = {}) {
4969
5409
  rid
4970
5410
  );
4971
5411
  return;
5412
+ case "tool_stopped":
5413
+ emit(
5414
+ "tool_stopped",
5415
+ {
5416
+ id: e.id,
5417
+ name: e.name,
5418
+ mode: e.mode,
5419
+ ...e.parentToolId && { parentToolId: e.parentToolId }
5420
+ },
5421
+ rid
5422
+ );
5423
+ return;
5424
+ case "tool_restarted":
5425
+ emit(
5426
+ "tool_restarted",
5427
+ {
5428
+ id: e.id,
5429
+ name: e.name,
5430
+ input: e.input,
5431
+ ...e.parentToolId && { parentToolId: e.parentToolId }
5432
+ },
5433
+ rid
5434
+ );
5435
+ return;
4972
5436
  case "status":
4973
- emit("status", { message: e.message }, rid);
5437
+ emit(
5438
+ "status",
5439
+ {
5440
+ message: e.message,
5441
+ ...e.parentToolId && { parentToolId: e.parentToolId }
5442
+ },
5443
+ rid
5444
+ );
4974
5445
  return;
4975
5446
  case "error":
4976
5447
  emit("error", { error: e.error }, rid);
4977
5448
  return;
4978
5449
  }
4979
5450
  }
5451
+ toolRegistry.onEvent = onEvent;
4980
5452
  async function handleMessage(parsed, requestId) {
4981
5453
  if (running) {
4982
5454
  emit(
@@ -5028,7 +5500,8 @@ async function startHeadless(opts = {}) {
5028
5500
  signal: currentAbort.signal,
5029
5501
  onEvent,
5030
5502
  resolveExternalTool,
5031
- hidden: isCommand
5503
+ hidden: isCommand,
5504
+ toolRegistry
5032
5505
  });
5033
5506
  if (!completedEmitted) {
5034
5507
  emit(
@@ -5082,6 +5555,36 @@ async function startHeadless(opts = {}) {
5082
5555
  emit("completed", { success: true }, requestId);
5083
5556
  return;
5084
5557
  }
5558
+ if (action === "stop_tool") {
5559
+ const id = parsed.id;
5560
+ const mode = parsed.mode ?? "hard";
5561
+ const found = toolRegistry.stop(id, mode);
5562
+ if (found) {
5563
+ emit("completed", { success: true }, requestId);
5564
+ } else {
5565
+ emit(
5566
+ "completed",
5567
+ { success: false, error: "Tool not found" },
5568
+ requestId
5569
+ );
5570
+ }
5571
+ return;
5572
+ }
5573
+ if (action === "restart_tool") {
5574
+ const id = parsed.id;
5575
+ const patchedInput = parsed.input;
5576
+ const found = toolRegistry.restart(id, patchedInput);
5577
+ if (found) {
5578
+ emit("completed", { success: true }, requestId);
5579
+ } else {
5580
+ emit(
5581
+ "completed",
5582
+ { success: false, error: "Tool not found" },
5583
+ requestId
5584
+ );
5585
+ }
5586
+ return;
5587
+ }
5085
5588
  if (action === "message") {
5086
5589
  await handleMessage(parsed, requestId);
5087
5590
  return;
@@ -5107,25 +5610,24 @@ async function startHeadless(opts = {}) {
5107
5610
  process.on("SIGINT", shutdown);
5108
5611
  emit("ready");
5109
5612
  }
5110
- var BASE_DIR, ACTIONS_DIR;
5111
5613
  var init_headless = __esm({
5112
5614
  "src/headless.ts"() {
5113
5615
  "use strict";
5616
+ init_assets();
5114
5617
  init_config();
5115
5618
  init_prompt4();
5116
5619
  init_lsp();
5117
5620
  init_agent();
5118
5621
  init_session();
5119
- BASE_DIR = import.meta.dirname ?? path14.dirname(new URL(import.meta.url).pathname);
5120
- ACTIONS_DIR = path14.join(BASE_DIR, "actions");
5622
+ init_toolRegistry();
5121
5623
  }
5122
5624
  });
5123
5625
 
5124
5626
  // src/index.tsx
5125
5627
  import { render } from "ink";
5126
5628
  import os2 from "os";
5127
- import fs22 from "fs";
5128
- import path15 from "path";
5629
+ import fs18 from "fs";
5630
+ import path9 from "path";
5129
5631
 
5130
5632
  // src/tui/App.tsx
5131
5633
  import { useState as useState2, useCallback, useRef } from "react";
@@ -5442,8 +5944,8 @@ for (let i = 0; i < args.length; i++) {
5442
5944
  }
5443
5945
  function printDebugInfo(config) {
5444
5946
  const pkg = JSON.parse(
5445
- fs22.readFileSync(
5446
- path15.join(import.meta.dirname, "..", "package.json"),
5947
+ fs18.readFileSync(
5948
+ path9.join(import.meta.dirname, "..", "package.json"),
5447
5949
  "utf-8"
5448
5950
  )
5449
5951
  );