@mindstudio-ai/remy 0.1.313 → 0.1.315

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/headless.js CHANGED
@@ -434,6 +434,13 @@ var MODEL_SURFACES = {
434
434
  modelType: "text",
435
435
  userPickable: true
436
436
  },
437
+ research: {
438
+ default: "claude-5-sonnet",
439
+ label: "Research Agent",
440
+ description: "Researches using the web and reports back with citations.",
441
+ modelType: "text",
442
+ userPickable: true
443
+ },
437
444
  copyEditor: {
438
445
  default: "claude-5-sonnet",
439
446
  label: "Copy Agent",
@@ -519,6 +526,8 @@ var TEXT_MODELS = {
519
526
  // 500K window
520
527
  "glm-5.2": { forceCompactAt: 85e4 },
521
528
  "muse-spark-1.1": { forceCompactAt: 85e4 },
529
+ "muse-spark-1.2": { forceCompactAt: 85e4 },
530
+ "muse-spark-1.3": { forceCompactAt: 85e4 },
522
531
  "kimi-k2-7-code": { forceCompactAt: 2e5 },
523
532
  // 262K window
524
533
  "kimi-k3": { forceCompactAt: 85e4 },
@@ -1884,158 +1893,6 @@ var askMindStudioSdkTool = {
1884
1893
  }
1885
1894
  };
1886
1895
 
1887
- // src/usageLedger.ts
1888
- import fs12 from "fs";
1889
- var LEDGER_FILE = ".logs/usage.ndjson";
1890
- var fd = null;
1891
- function nanoToDollars(nano) {
1892
- return typeof nano === "number" ? nano / 1e9 : void 0;
1893
- }
1894
- function recordUsage(entry) {
1895
- try {
1896
- if (fd === null) {
1897
- fs12.mkdirSync(".logs", { recursive: true });
1898
- fd = fs12.openSync(LEDGER_FILE, "a");
1899
- }
1900
- fs12.writeSync(fd, JSON.stringify(entry) + "\n");
1901
- } catch {
1902
- }
1903
- }
1904
-
1905
- // src/subagents/common/runMindstudioCli.ts
1906
- function stripFlags(args) {
1907
- const out = [];
1908
- for (let i = 0; i < args.length; i++) {
1909
- const arg = args[i];
1910
- if (arg === "--no-meta") {
1911
- continue;
1912
- }
1913
- if (arg === "--output-key") {
1914
- i++;
1915
- continue;
1916
- }
1917
- out.push(arg);
1918
- }
1919
- return out;
1920
- }
1921
- async function runMindstudioCliResult(args, options) {
1922
- const cleanArgs = stripFlags(args);
1923
- const cliAction = args[0];
1924
- const agentName = options?.caller ?? "mindstudio-cli";
1925
- const start = Date.now();
1926
- const res = await runCli("mindstudio", cleanArgs, options);
1927
- if (!res.ok) {
1928
- return { ok: false, value: formatCliResult(res) };
1929
- }
1930
- const truncNote = res.truncated ? "\n\n[output truncated]" : "";
1931
- let envelope;
1932
- try {
1933
- envelope = JSON.parse(res.output);
1934
- } catch {
1935
- return { ok: true, value: res.output + truncNote };
1936
- }
1937
- if (envelope && typeof envelope === "object" && Array.isArray(envelope.results)) {
1938
- const durationMs = Date.now() - start;
1939
- for (const step of envelope.results) {
1940
- if (typeof step?.billingCost === "number") {
1941
- recordUsage({
1942
- ts: Date.now(),
1943
- agentName,
1944
- cliAction: `${cliAction}:${step.stepType ?? "step"}`,
1945
- cost: nanoToDollars(step.billingCost),
1946
- inputTokens: 0,
1947
- outputTokens: 0,
1948
- durationMs,
1949
- toolNames: []
1950
- });
1951
- }
1952
- }
1953
- return { ok: true, value: JSON.stringify(envelope.results) + truncNote };
1954
- }
1955
- if (typeof envelope?.$billingCost === "number") {
1956
- recordUsage({
1957
- ts: Date.now(),
1958
- agentName,
1959
- cliAction,
1960
- cost: nanoToDollars(envelope.$billingCost),
1961
- billingEvents: envelope.$billingEvents,
1962
- // CLI billing isn't expressed as input/output tokens for most actions
1963
- // (image gen is per-image, scrape per-page, etc). `numUnits` inside each
1964
- // billingEvent carries the per-event unit count.
1965
- inputTokens: 0,
1966
- outputTokens: 0,
1967
- durationMs: Date.now() - start,
1968
- toolNames: []
1969
- });
1970
- }
1971
- if (options?.outputKey) {
1972
- const v = envelope?.[options.outputKey];
1973
- if (v === void 0 || v === null) {
1974
- return { ok: false, value: JSON.stringify(stripDollarKeys(envelope)) };
1975
- }
1976
- const value = typeof v === "string" ? v : JSON.stringify(v);
1977
- return { ok: true, value: value + truncNote };
1978
- }
1979
- return {
1980
- ok: true,
1981
- value: JSON.stringify(stripDollarKeys(envelope)) + truncNote
1982
- };
1983
- }
1984
- async function runMindstudioCli(args, options) {
1985
- return (await runMindstudioCliResult(args, options)).value;
1986
- }
1987
- function stripDollarKeys(envelope) {
1988
- if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) {
1989
- return envelope;
1990
- }
1991
- const out = {};
1992
- for (const [k, v] of Object.entries(envelope)) {
1993
- if (!k.startsWith("$")) {
1994
- out[k] = v;
1995
- }
1996
- }
1997
- return out;
1998
- }
1999
-
2000
- // src/tools/common/searchGoogle.ts
2001
- var FETCH_TOP_N = 5;
2002
- var searchGoogleTool = {
2003
- definition: {
2004
- name: "searchGoogle",
2005
- description: "Search Google and return results. Use for research, finding documentation, looking up APIs, or any task where web search would help. The top results come back with their page content already included, so read those before reaching for scrapeWebUrl \u2014 you only need that for URLs this did not return, or for a result further down the list.",
2006
- inputSchema: {
2007
- type: "object",
2008
- properties: {
2009
- query: {
2010
- type: "string",
2011
- description: "The search query."
2012
- }
2013
- },
2014
- required: ["query"]
2015
- }
2016
- },
2017
- async execute(input, context) {
2018
- const query = input.query;
2019
- return runMindstudioCli(
2020
- [
2021
- "search-google",
2022
- "--query",
2023
- query,
2024
- "--export-type",
2025
- "json",
2026
- "--fetch-top-n",
2027
- String(FETCH_TOP_N)
2028
- ],
2029
- {
2030
- outputKey: "results",
2031
- maxBuffer: SEARCH_MAX_BUFFER,
2032
- onLog: context?.onLog,
2033
- caller: "parent"
2034
- }
2035
- );
2036
- }
2037
- };
2038
-
2039
1896
  // src/tools/common/setProjectMetadata.ts
2040
1897
  var setProjectMetadataTool = {
2041
1898
  definition: {
@@ -2160,7 +2017,7 @@ This reference lives at ${skill.path}. Re-read it with readFile if you need it a
2160
2017
  };
2161
2018
 
2162
2019
  // src/tools/code/readFile.ts
2163
- import fs13 from "fs/promises";
2020
+ import fs12 from "fs/promises";
2164
2021
  var DEFAULT_WINDOW = 500;
2165
2022
  var MAX_BYTES = 64 * 1024;
2166
2023
  function isBinary(buffer) {
@@ -2201,7 +2058,7 @@ var readFileTool = {
2201
2058
  },
2202
2059
  async execute(input) {
2203
2060
  try {
2204
- const buffer = await fs13.readFile(input.path);
2061
+ const buffer = await fs12.readFile(input.path);
2205
2062
  if (isBinary(buffer)) {
2206
2063
  const size = buffer.length;
2207
2064
  const unit = size > 1024 * 1024 ? `${(size / (1024 * 1024)).toFixed(1)}MB` : `${(size / 1024).toFixed(1)}KB`;
@@ -2273,7 +2130,7 @@ var readFileTool = {
2273
2130
  };
2274
2131
 
2275
2132
  // src/tools/code/writeFile.ts
2276
- import fs14 from "fs/promises";
2133
+ import fs13 from "fs/promises";
2277
2134
  import path6 from "path";
2278
2135
  var writeFileTool = {
2279
2136
  definition: {
@@ -2310,7 +2167,7 @@ var writeFileTool = {
2310
2167
  lastNewlineCount = newlineCount;
2311
2168
  const lastNewline = partial.content.lastIndexOf("\n");
2312
2169
  const completeContent = partial.content.substring(0, lastNewline + 1);
2313
- const oldContent = await fs14.readFile(partial.path, "utf-8").catch(() => "");
2170
+ const oldContent = await fs13.readFile(partial.path, "utf-8").catch(() => "");
2314
2171
  return `Writing ${partial.path} (${newlineCount} lines)
2315
2172
  ${unifiedDiff(partial.path, oldContent, completeContent)}`;
2316
2173
  }
@@ -2319,13 +2176,13 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
2319
2176
  async execute(input) {
2320
2177
  const release = await acquireFileLock(input.path);
2321
2178
  try {
2322
- await fs14.mkdir(path6.dirname(input.path), { recursive: true });
2179
+ await fs13.mkdir(path6.dirname(input.path), { recursive: true });
2323
2180
  let oldContent = null;
2324
2181
  try {
2325
- oldContent = await fs14.readFile(input.path, "utf-8");
2182
+ oldContent = await fs13.readFile(input.path, "utf-8");
2326
2183
  } catch {
2327
2184
  }
2328
- await fs14.writeFile(input.path, input.content, "utf-8");
2185
+ await fs13.writeFile(input.path, input.content, "utf-8");
2329
2186
  const lineCount = input.content.split("\n").length;
2330
2187
  const label = oldContent !== null ? "Wrote" : "Created";
2331
2188
  return `${label} ${input.path} (${lineCount} lines)
@@ -2339,7 +2196,7 @@ ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
2339
2196
  };
2340
2197
 
2341
2198
  // src/tools/code/editFile/index.ts
2342
- import fs15 from "fs/promises";
2199
+ import fs14 from "fs/promises";
2343
2200
  var editFileTool = {
2344
2201
  definition: {
2345
2202
  name: "editFile",
@@ -2370,7 +2227,7 @@ var editFileTool = {
2370
2227
  async execute(input) {
2371
2228
  const release = await acquireFileLock(input.path);
2372
2229
  try {
2373
- const content = await fs15.readFile(input.path, "utf-8");
2230
+ const content = await fs14.readFile(input.path, "utf-8");
2374
2231
  const { old_string, new_string, replace_all } = input;
2375
2232
  const occurrences = findOccurrences(content, old_string);
2376
2233
  if (replace_all) {
@@ -2386,7 +2243,7 @@ var editFileTool = {
2386
2243
  new_string
2387
2244
  );
2388
2245
  }
2389
- await fs15.writeFile(input.path, updated, "utf-8");
2246
+ await fs14.writeFile(input.path, updated, "utf-8");
2390
2247
  return `Replaced ${occurrences.length} occurrence${occurrences.length > 1 ? "s" : ""} in ${input.path}
2391
2248
  ${unifiedDiff(input.path, content, updated)}`;
2392
2249
  }
@@ -2397,7 +2254,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2397
2254
  old_string.length,
2398
2255
  new_string
2399
2256
  );
2400
- await fs15.writeFile(input.path, updated, "utf-8");
2257
+ await fs14.writeFile(input.path, updated, "utf-8");
2401
2258
  return `Updated ${input.path}
2402
2259
  ${unifiedDiff(input.path, content, updated)}`;
2403
2260
  }
@@ -2413,7 +2270,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2413
2270
  flex.matchedText.length,
2414
2271
  new_string
2415
2272
  );
2416
- await fs15.writeFile(input.path, updated, "utf-8");
2273
+ await fs14.writeFile(input.path, updated, "utf-8");
2417
2274
  return `Updated ${input.path} (matched with flexible whitespace at line ${flex.line})
2418
2275
  ${unifiedDiff(input.path, content, updated)}`;
2419
2276
  }
@@ -2739,12 +2596,12 @@ var globTool = {
2739
2596
  };
2740
2597
 
2741
2598
  // src/tools/code/listDir.ts
2742
- import fs16 from "fs/promises";
2599
+ import fs15 from "fs/promises";
2743
2600
  import path8 from "path";
2744
2601
  var EXCLUDE = /* @__PURE__ */ new Set([".git", "node_modules"]);
2745
2602
  var MAX_CHILDREN = 15;
2746
2603
  async function readAndSort(dirPath) {
2747
- const entries = await fs16.readdir(dirPath, { withFileTypes: true });
2604
+ const entries = await fs15.readdir(dirPath, { withFileTypes: true });
2748
2605
  return entries.filter((e) => !EXCLUDE.has(e.name)).sort((a, b) => {
2749
2606
  if (a.isDirectory() && !b.isDirectory()) {
2750
2607
  return -1;
@@ -2785,7 +2642,7 @@ function formatSize(bytes) {
2785
2642
  }
2786
2643
  async function formatFile(dirPath, name, indent) {
2787
2644
  try {
2788
- const stat4 = await fs16.stat(path8.join(dirPath, name));
2645
+ const stat4 = await fs15.stat(path8.join(dirPath, name));
2789
2646
  return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat4.size)}`;
2790
2647
  } catch {
2791
2648
  return `${indent}${name}`;
@@ -3117,6 +2974,119 @@ var queryDatabaseTool = {
3117
2974
  }
3118
2975
  };
3119
2976
 
2977
+ // src/usageLedger.ts
2978
+ import fs16 from "fs";
2979
+ var LEDGER_FILE = ".logs/usage.ndjson";
2980
+ var fd = null;
2981
+ function nanoToDollars(nano) {
2982
+ return typeof nano === "number" ? nano / 1e9 : void 0;
2983
+ }
2984
+ function recordUsage(entry) {
2985
+ try {
2986
+ if (fd === null) {
2987
+ fs16.mkdirSync(".logs", { recursive: true });
2988
+ fd = fs16.openSync(LEDGER_FILE, "a");
2989
+ }
2990
+ fs16.writeSync(fd, JSON.stringify(entry) + "\n");
2991
+ } catch {
2992
+ }
2993
+ }
2994
+
2995
+ // src/subagents/common/runMindstudioCli.ts
2996
+ function stripFlags(args) {
2997
+ const out = [];
2998
+ for (let i = 0; i < args.length; i++) {
2999
+ const arg = args[i];
3000
+ if (arg === "--no-meta") {
3001
+ continue;
3002
+ }
3003
+ if (arg === "--output-key") {
3004
+ i++;
3005
+ continue;
3006
+ }
3007
+ out.push(arg);
3008
+ }
3009
+ return out;
3010
+ }
3011
+ async function runMindstudioCliResult(args, options) {
3012
+ const cleanArgs = stripFlags(args);
3013
+ const cliAction = args[0];
3014
+ const agentName = options?.caller ?? "mindstudio-cli";
3015
+ const start = Date.now();
3016
+ const res = await runCli("mindstudio", cleanArgs, options);
3017
+ if (!res.ok) {
3018
+ return { ok: false, value: formatCliResult(res) };
3019
+ }
3020
+ const truncNote = res.truncated ? "\n\n[output truncated]" : "";
3021
+ let envelope;
3022
+ try {
3023
+ envelope = JSON.parse(res.output);
3024
+ } catch {
3025
+ return { ok: true, value: res.output + truncNote };
3026
+ }
3027
+ if (envelope && typeof envelope === "object" && Array.isArray(envelope.results)) {
3028
+ const durationMs = Date.now() - start;
3029
+ for (const step of envelope.results) {
3030
+ if (typeof step?.billingCost === "number") {
3031
+ recordUsage({
3032
+ ts: Date.now(),
3033
+ agentName,
3034
+ cliAction: `${cliAction}:${step.stepType ?? "step"}`,
3035
+ cost: nanoToDollars(step.billingCost),
3036
+ inputTokens: 0,
3037
+ outputTokens: 0,
3038
+ durationMs,
3039
+ toolNames: []
3040
+ });
3041
+ }
3042
+ }
3043
+ return { ok: true, value: JSON.stringify(envelope.results) + truncNote };
3044
+ }
3045
+ if (typeof envelope?.$billingCost === "number") {
3046
+ recordUsage({
3047
+ ts: Date.now(),
3048
+ agentName,
3049
+ cliAction,
3050
+ cost: nanoToDollars(envelope.$billingCost),
3051
+ billingEvents: envelope.$billingEvents,
3052
+ // CLI billing isn't expressed as input/output tokens for most actions
3053
+ // (image gen is per-image, scrape per-page, etc). `numUnits` inside each
3054
+ // billingEvent carries the per-event unit count.
3055
+ inputTokens: 0,
3056
+ outputTokens: 0,
3057
+ durationMs: Date.now() - start,
3058
+ toolNames: []
3059
+ });
3060
+ }
3061
+ if (options?.outputKey) {
3062
+ const v = envelope?.[options.outputKey];
3063
+ if (v === void 0 || v === null) {
3064
+ return { ok: false, value: JSON.stringify(stripDollarKeys(envelope)) };
3065
+ }
3066
+ const value = typeof v === "string" ? v : JSON.stringify(v);
3067
+ return { ok: true, value: value + truncNote };
3068
+ }
3069
+ return {
3070
+ ok: true,
3071
+ value: JSON.stringify(stripDollarKeys(envelope)) + truncNote
3072
+ };
3073
+ }
3074
+ async function runMindstudioCli(args, options) {
3075
+ return (await runMindstudioCliResult(args, options)).value;
3076
+ }
3077
+ function stripDollarKeys(envelope) {
3078
+ if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) {
3079
+ return envelope;
3080
+ }
3081
+ const out = {};
3082
+ for (const [k, v] of Object.entries(envelope)) {
3083
+ if (!k.startsWith("$")) {
3084
+ out[k] = v;
3085
+ }
3086
+ }
3087
+ return out;
3088
+ }
3089
+
3120
3090
  // src/tools/_helpers/uploadImage.ts
3121
3091
  import { readFile, stat } from "fs/promises";
3122
3092
  import { basename, extname, resolve } from "path";
@@ -4595,7 +4565,7 @@ The first-party SDK (@mindstudio-ai/agent) provides access to 200+ AI models (Op
4595
4565
  ## What Remy apps are NOT good for
4596
4566
 
4597
4567
  - Native mobile apps (iOS/Android). Mobile-responsive web apps are fine.
4598
- - Fast-twitch multiplayer and live co-editing (shared cursors, 60fps sync) \u2014 client\u2192server is always a method invoke, so sub-100ms bidirectional interaction isn't a fit.
4568
+ - Real-time action games (server-authoritative simulation, guaranteed-order sync) \u2014 event delivery is at-most-once and ordering is the app's job, so there is no server tick to build one on.
4599
4569
  </platform_brief>`;
4600
4570
  }
4601
4571
 
@@ -4884,56 +4854,155 @@ var screenshotTool = {
4884
4854
  execute: (input, context) => executeScreenshot(input, context?.onLog, context)
4885
4855
  };
4886
4856
 
4887
- // src/subagents/designExpert/tools/searchGoogle.ts
4888
- var searchGoogle_exports = {};
4889
- __export(searchGoogle_exports, {
4890
- definition: () => definition,
4891
- execute: () => execute
4892
- });
4893
- var FETCH_TOP_N2 = 5;
4894
- var definition = {
4857
+ // src/subagents/research/tools.ts
4858
+ var searchGoogleDefinition = {
4895
4859
  name: "searchGoogle",
4896
- 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. Searching for and reading case studies is a great way to get information and context about a project\'s domain. Prioritize authoritative sources like Figma and other design leaders, avoid random blog spam. The top results come back with their page content already included, so read those directly \u2014 only use `scrapeWebUrl` for a result further down the list, or for a URL this did not return.',
4860
+ description: "Search Google. Returns ~25 results (title, description, URL) \u2014 fast (~5s) when `fetchTopN` is omitted. Set `fetchTopN` (3\u20135) to also get the top results' page content inline, capped at ~4,000 chars per page \u2014 one call delivers excerpts, but it is much slower (~30\u201360s). Default pattern: fire SERP-only searches in parallel, then scrape the specific pages worth reading in full.",
4897
4861
  inputSchema: {
4898
4862
  type: "object",
4899
4863
  properties: {
4900
4864
  query: {
4901
4865
  type: "string",
4902
4866
  description: "The search query."
4867
+ },
4868
+ fetchTopN: {
4869
+ type: "number",
4870
+ description: "Also fetch page content (capped ~4,000 chars each) for this many top results. Omit for a fast SERP-only search."
4903
4871
  }
4904
4872
  },
4905
4873
  required: ["query"]
4906
4874
  }
4907
4875
  };
4908
- async function execute(input, onLog) {
4876
+ var scrapeWebUrlDefinition = {
4877
+ name: "scrapeWebUrl",
4878
+ description: "Fetch a web page as markdown. Renders JavaScript, so client-rendered pages come back complete. Returns the full page \u2014 long docs can run tens of thousands of tokens, so fetch only pages you have chosen to read, and fire independent fetches in parallel. For code hosted on GitHub/npm, prefer cloning with bash over scraping the repo page.",
4879
+ inputSchema: {
4880
+ type: "object",
4881
+ properties: {
4882
+ url: {
4883
+ type: "string",
4884
+ description: "The URL to fetch."
4885
+ }
4886
+ },
4887
+ required: ["url"]
4888
+ }
4889
+ };
4890
+ var RESEARCH_TOOLS = [
4891
+ ...COMMON_READ_TOOLS,
4892
+ searchGoogleDefinition,
4893
+ scrapeWebUrlDefinition,
4894
+ bashTool.definition
4895
+ ];
4896
+ async function executeSearchGoogle(input, onLog, caller) {
4897
+ const fetchTopN = Math.max(0, Math.round(Number(input.fetchTopN) || 0));
4909
4898
  return runMindstudioCli(
4910
4899
  [
4911
4900
  "search-google",
4912
4901
  "--query",
4913
- input.query,
4902
+ String(input.query),
4914
4903
  "--export-type",
4915
4904
  "json",
4916
4905
  "--fetch-top-n",
4917
- String(FETCH_TOP_N2)
4906
+ String(fetchTopN)
4918
4907
  ],
4919
4908
  {
4920
4909
  outputKey: "results",
4910
+ maxBuffer: SEARCH_MAX_BUFFER,
4911
+ onLog,
4912
+ caller
4913
+ }
4914
+ );
4915
+ }
4916
+ async function executeScrapeWebUrl(input, onLog, caller) {
4917
+ return runMindstudioCli(
4918
+ [
4919
+ "scrape-url",
4920
+ "--url",
4921
+ String(input.url),
4922
+ "--page-options",
4923
+ JSON.stringify({ onlyMainContent: true })
4924
+ ],
4925
+ {
4926
+ maxBuffer: SCRAPE_MAX_BUFFER,
4921
4927
  onLog,
4922
- caller: "designExpert",
4923
- maxBuffer: SEARCH_MAX_BUFFER
4928
+ caller
4924
4929
  }
4925
4930
  );
4926
4931
  }
4927
4932
 
4933
+ // src/subagents/research/index.ts
4934
+ var BASE_PROMPT2 = readAsset("subagents/research", "prompt.md");
4935
+ async function runResearch(task, context) {
4936
+ const specIndex = loadSpecIndex();
4937
+ const parts = [BASE_PROMPT2, loadPlatformBrief()];
4938
+ parts.push("<!-- cache_breakpoint -->");
4939
+ if (specIndex) {
4940
+ parts.push(specIndex);
4941
+ }
4942
+ const system = parts.join("\n\n");
4943
+ const result = await runSubAgent({
4944
+ system,
4945
+ task,
4946
+ tools: RESEARCH_TOOLS,
4947
+ externalTools: /* @__PURE__ */ new Set(),
4948
+ executeTool: (name, toolInput, toolCallId, onLog, sams) => {
4949
+ const childCtx = toolCallId ? {
4950
+ ...deriveContext(context, toolCallId, onLog),
4951
+ subAgentMessages: sams
4952
+ } : context;
4953
+ if (name === "searchGoogle") {
4954
+ return executeSearchGoogle(toolInput, childCtx.onLog, "research");
4955
+ }
4956
+ if (name === "scrapeWebUrl") {
4957
+ return executeScrapeWebUrl(toolInput, childCtx.onLog, "research");
4958
+ }
4959
+ return executeTool(name, toolInput, childCtx);
4960
+ },
4961
+ apiConfig: context.apiConfig,
4962
+ model: resolveModel("research", context.models, context.model),
4963
+ subAgentId: "research",
4964
+ signal: context.signal,
4965
+ parentToolId: context.toolCallId,
4966
+ requestId: context.requestId,
4967
+ onEvent: context.onEvent,
4968
+ resolveExternalTool: context.resolveExternalTool,
4969
+ toolRegistry: context.toolRegistry
4970
+ });
4971
+ context.subAgentMessages?.set(context.toolCallId, result.messages);
4972
+ return result.text;
4973
+ }
4974
+ var researchTool = {
4975
+ definition: {
4976
+ name: "research",
4977
+ description: "Your researcher. Hand it a question and it searches the web, reads the pages and source code that matter, and returns a distilled, citation-backed report. Use it for both objective research like third-party APIs and services, as well as for subjective things like current trends, patterns, and approaches (from architecture to UI to even just high-level framings and ideas). Brief it neutrally: state the question and the relevant project context, not the answer you expect.",
4978
+ inputSchema: {
4979
+ type: "object",
4980
+ properties: {
4981
+ task: {
4982
+ type: "string",
4983
+ description: "What you need to find out, in natural language, plus any project context it cannot get from reading the spec. Phrase it as a question to answer, not a conclusion to confirm."
4984
+ }
4985
+ },
4986
+ required: ["task"]
4987
+ }
4988
+ },
4989
+ async execute(input, context) {
4990
+ if (!context) {
4991
+ return "Error: research requires execution context";
4992
+ }
4993
+ return runResearch(input.task, context);
4994
+ }
4995
+ };
4996
+
4928
4997
  // src/subagents/designExpert/tools/scrapeWebUrl.ts
4929
4998
  var scrapeWebUrl_exports = {};
4930
4999
  __export(scrapeWebUrl_exports, {
4931
- definition: () => definition2,
4932
- execute: () => execute2
5000
+ definition: () => definition,
5001
+ execute: () => execute
4933
5002
  });
4934
- var definition2 = {
5003
+ var definition = {
4935
5004
  name: "scrapeWebUrl",
4936
- 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.",
5005
+ description: "Fetch the content of a web page as markdown. Use for reading a specific URL \u2014 a site the user referenced, a brand to match, a page the researcher cited that you want in full.",
4937
5006
  inputSchema: {
4938
5007
  type: "object",
4939
5008
  properties: {
@@ -4945,7 +5014,7 @@ var definition2 = {
4945
5014
  required: ["url"]
4946
5015
  }
4947
5016
  };
4948
- async function execute2(input, onLog) {
5017
+ async function execute(input, onLog) {
4949
5018
  const pageOptions = { onlyMainContent: true };
4950
5019
  return runMindstudioCli(
4951
5020
  [
@@ -4962,8 +5031,8 @@ async function execute2(input, onLog) {
4962
5031
  // src/subagents/designExpert/tools/analyzeDesign.ts
4963
5032
  var analyzeDesign_exports = {};
4964
5033
  __export(analyzeDesign_exports, {
4965
- definition: () => definition4,
4966
- execute: () => execute4
5034
+ definition: () => definition3,
5035
+ execute: () => execute3
4967
5036
  });
4968
5037
  import { readFile as readFile2 } from "fs/promises";
4969
5038
  import { extname as extname2, join as join2 } from "path";
@@ -4972,8 +5041,8 @@ import { extname as extname2, join as join2 } from "path";
4972
5041
  var renderImage_exports = {};
4973
5042
  __export(renderImage_exports, {
4974
5043
  RENDER_ANALYZE_PROMPT: () => RENDER_ANALYZE_PROMPT,
4975
- definition: () => definition3,
4976
- execute: () => execute3
5044
+ definition: () => definition2,
5045
+ execute: () => execute2
4977
5046
  });
4978
5047
  import { mkdir, unlink, writeFile } from "fs/promises";
4979
5048
  import { tmpdir } from "os";
@@ -4982,7 +5051,7 @@ import { randomUUID } from "crypto";
4982
5051
  var MIN_DIMENSION = 16;
4983
5052
  var MAX_DIMENSION = 4096;
4984
5053
  var RENDER_ANALYZE_PROMPT = 'You are reviewing a browser-rendered graphic (composed from HTML/CSS by a designer) for fidelity. Report: whether the composition fills the full canvas or leaves unintended gaps at any edge, any clipped or overflowing text, whether custom webfonts appear to have loaded (distinctive letterforms vs generic fallback serif/sans), any misalignment or uneven spacing, any unintended scrollbars or default-styling artifacts, and \u2014 if the background is transparent \u2014 any fringing or stray opaque pixels at the edges. Then briefly describe the overall composition and how polished it looks. Be concise and practical. Respond only with your analysis as Markdown (starting with the title "Render Review") and absolutely no other text. Do not use emojis - use unicode if you need symbols.';
4985
- var definition3 = {
5054
+ var definition2 = {
4986
5055
  name: "renderImage",
4987
5056
  description: "Render a self-contained HTML document in a real browser and capture it as a hosted PNG at exact pixel dimensions, with a fidelity review included. Deterministic \u2014 exact hex colors, real loaded webfonts, precise geometry \u2014 unlike generateImages, which is an image model. Use for token-exact graphics: Open Graph share cards, wordmarks, flat/geometric icon tiles, badges, any composition where letterforms and spacing carry the design. Compose with HTML/CSS (link webfonts from CDNs \u2014 the renderer waits for them to load); inline existing SVG markup when needed, but never hand-write new SVG path data.",
4988
5057
  inputSchema: {
@@ -5016,7 +5085,7 @@ var definition3 = {
5016
5085
  required: ["html", "width", "height"]
5017
5086
  }
5018
5087
  };
5019
- async function execute3(input, onLog, context) {
5088
+ async function execute2(input, onLog, context) {
5020
5089
  const html = typeof input.html === "string" ? input.html : "";
5021
5090
  const width = Math.round(Number(input.width));
5022
5091
  const height = Math.round(Number(input.height));
@@ -5125,7 +5194,7 @@ Identify the specific design moves that make this page interesting and unique, d
5125
5194
 
5126
5195
  Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.
5127
5196
  `;
5128
- var definition4 = {
5197
+ var definition3 = {
5129
5198
  name: "analyzeDesign",
5130
5199
  description: "Analyze the visual design of a website, an image (URL or file on disk), or an HTML document on disk. Websites are screenshotted first; an HTML file is rendered in a real browser first and the render is analyzed, which is how you look at a wireframe you authored. Provides static image analysis only, will not capture animations or video. With no prompt, a website or image gets a full design reference analysis (mood, color, typography, layout, distinctiveness) and a rendered HTML document gets a fidelity review (clipping, overflow, webfont loading, alignment). Provide a custom prompt to ask a specific design question instead. Use a bulleted list to ask many questions at once.",
5131
5200
  inputSchema: {
@@ -5165,7 +5234,7 @@ function splitFrontmatter(raw) {
5165
5234
  html: raw.slice(match[0].length)
5166
5235
  };
5167
5236
  }
5168
- async function execute4(input, onLog, context) {
5237
+ async function execute3(input, onLog, context) {
5169
5238
  const url = String(input.url ?? "").trim();
5170
5239
  if (!url) {
5171
5240
  return "Error: url is required.";
@@ -5273,10 +5342,10 @@ ${basePrompt}` : basePrompt;
5273
5342
  // src/subagents/designExpert/tools/analyzeImage.ts
5274
5343
  var analyzeImage_exports = {};
5275
5344
  __export(analyzeImage_exports, {
5276
- definition: () => definition5,
5277
- execute: () => execute5
5345
+ definition: () => definition4,
5346
+ execute: () => execute4
5278
5347
  });
5279
- var definition5 = {
5348
+ var definition4 = {
5280
5349
  name: "analyzeImage",
5281
5350
  description: "Analyze an image using a vision model. Provides static image analysis only, will not capture animations or video. Returns an objective description of what is visible \u2014 shapes, colors, layout, text, artifacts. Use for factual inventory of image contents, not for subjective design judgment - the vision model providing the analysis has no sense of design. You are the design expert - use the analysis tool for factual inventory, then apply your own expertise for quality and suitability assessments. Optionally provide specific questions about what you're looking for. Use a bulleted list to ask many questions at once. If you are analyzing a screenshot of the app preview, you can reuse the same screenshot URL multiple times to ask multiple questions.",
5282
5351
  inputSchema: {
@@ -5297,7 +5366,7 @@ var definition5 = {
5297
5366
  required: ["imageUrl"]
5298
5367
  }
5299
5368
  };
5300
- async function execute5(input, onLog, context) {
5369
+ async function execute4(input, onLog, context) {
5301
5370
  const prompt = buildScreenshotAnalysisPrompt({
5302
5371
  prompt: input.prompt
5303
5372
  });
@@ -5314,8 +5383,8 @@ async function execute5(input, onLog, context) {
5314
5383
  // src/subagents/designExpert/tools/images/generateImages.ts
5315
5384
  var generateImages_exports = {};
5316
5385
  __export(generateImages_exports, {
5317
- definition: () => definition6,
5318
- execute: () => execute6
5386
+ definition: () => definition5,
5387
+ execute: () => execute5
5319
5388
  });
5320
5389
 
5321
5390
  // src/subagents/designExpert/tools/images/enhancePrompt.ts
@@ -5542,7 +5611,7 @@ async function generateImageAssets(opts) {
5542
5611
  }
5543
5612
 
5544
5613
  // src/subagents/designExpert/tools/images/generateImages.ts
5545
- var definition6 = {
5614
+ var definition5 = {
5546
5615
  name: "generateImages",
5547
5616
  description: "Generate images. 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.",
5548
5617
  inputSchema: {
@@ -5575,7 +5644,7 @@ var definition6 = {
5575
5644
  required: ["prompts"]
5576
5645
  }
5577
5646
  };
5578
- async function execute6(input, onLog, context) {
5647
+ async function execute5(input, onLog, context) {
5579
5648
  return generateImageAssets({
5580
5649
  prompts: input.prompts,
5581
5650
  width: input.width,
@@ -5606,10 +5675,10 @@ async function execute6(input, onLog, context) {
5606
5675
  // src/subagents/designExpert/tools/images/editImages.ts
5607
5676
  var editImages_exports = {};
5608
5677
  __export(editImages_exports, {
5609
- definition: () => definition7,
5610
- execute: () => execute7
5678
+ definition: () => definition6,
5679
+ execute: () => execute6
5611
5680
  });
5612
- var definition7 = {
5681
+ var definition6 = {
5613
5682
  name: "editImages",
5614
5683
  description: "Edit or transform existing images. 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 references into something new. Returns CDN URLs with analysis.",
5615
5684
  inputSchema: {
@@ -5645,7 +5714,7 @@ var definition7 = {
5645
5714
  required: ["prompts", "sourceImages"]
5646
5715
  }
5647
5716
  };
5648
- async function execute7(input, onLog, context) {
5717
+ async function execute6(input, onLog, context) {
5649
5718
  return generateImageAssets({
5650
5719
  prompts: input.prompts,
5651
5720
  sourceImages: input.sourceImages,
@@ -5676,15 +5745,15 @@ async function execute7(input, onLog, context) {
5676
5745
  // src/subagents/designExpert/tools/polishCopy.ts
5677
5746
  var polishCopy_exports = {};
5678
5747
  __export(polishCopy_exports, {
5679
- definition: () => definition8,
5680
- execute: () => execute8
5748
+ definition: () => definition7,
5749
+ execute: () => execute7
5681
5750
  });
5682
5751
 
5683
5752
  // src/subagents/copyEditor/tools.ts
5684
5753
  var COPY_EDITOR_TOOLS = [...COMMON_READ_TOOLS];
5685
5754
 
5686
5755
  // src/subagents/copyEditor/index.ts
5687
- var BASE_PROMPT2 = readAsset("subagents/copyEditor", "prompt.md");
5756
+ var BASE_PROMPT3 = readAsset("subagents/copyEditor", "prompt.md");
5688
5757
  var copyEditorTool = {
5689
5758
  definition: {
5690
5759
  name: "copyEditor",
@@ -5705,7 +5774,7 @@ var copyEditorTool = {
5705
5774
  return "Error: copy editor requires execution context";
5706
5775
  }
5707
5776
  const specIndex = loadSpecIndex();
5708
- const parts = [BASE_PROMPT2];
5777
+ const parts = [BASE_PROMPT3];
5709
5778
  parts.push("<!-- cache_breakpoint -->");
5710
5779
  if (specIndex) {
5711
5780
  parts.push(specIndex);
@@ -5739,7 +5808,7 @@ var copyEditorTool = {
5739
5808
  };
5740
5809
 
5741
5810
  // src/subagents/designExpert/tools/polishCopy.ts
5742
- var definition8 = {
5811
+ var definition7 = {
5743
5812
  name: "polishCopy",
5744
5813
  description: "Hand off any user-facing copy you've written \u2014 headlines, captions, labels, body text \u2014 and get back a sharper version: better built for its audience and free of the fingerprints that make writing read as AI. It elevates how the copy communicates without inventing facts or claims you didn't give it. Give it the text plus what it's for (where it appears, the audience).",
5745
5814
  inputSchema: {
@@ -5753,15 +5822,15 @@ var definition8 = {
5753
5822
  required: ["task"]
5754
5823
  }
5755
5824
  };
5756
- async function execute8(input, _onLog, context) {
5825
+ async function execute7(input, _onLog, context) {
5757
5826
  return copyEditorTool.execute(input, context);
5758
5827
  }
5759
5828
 
5760
5829
  // src/subagents/designExpert/tools/loadSkill.ts
5761
5830
  var loadSkill_exports = {};
5762
5831
  __export(loadSkill_exports, {
5763
- definition: () => definition9,
5764
- execute: () => execute9
5832
+ definition: () => definition8,
5833
+ execute: () => execute8
5765
5834
  });
5766
5835
 
5767
5836
  // src/subagents/designExpert/skills/_catalog.ts
@@ -5775,7 +5844,7 @@ var designSkillCatalog = buildSkillCatalog({
5775
5844
  });
5776
5845
 
5777
5846
  // src/subagents/designExpert/tools/loadSkill.ts
5778
- var definition9 = {
5847
+ var definition8 = {
5779
5848
  name: "loadSkill",
5780
5849
  description: "Load the full craft reference for a design surface that isn't in your prompt. The available skills and the trigger for each are listed in <available_skills>. Load one before designing in its area, not after \u2014 these are hard-won technique recipes, and the defaulted version of these surfaces is exactly what they exist to prevent. Calling this is cheap and expected \u2014 if you're unsure whether you need it, load it.",
5781
5850
  inputSchema: {
@@ -5793,7 +5862,7 @@ var definition9 = {
5793
5862
  required: ["skill"]
5794
5863
  }
5795
5864
  };
5796
- async function execute9(input) {
5865
+ async function execute8(input) {
5797
5866
  const id = String(input.skill ?? "");
5798
5867
  const skill = designSkillCatalog.get(id);
5799
5868
  if (!skill) {
@@ -5814,15 +5883,15 @@ This reference lives at ${skill.path}. Re-read it with readFile if you need it a
5814
5883
  var createWireframe_exports = {};
5815
5884
  __export(createWireframe_exports, {
5816
5885
  WIREFRAMES_DIR: () => WIREFRAMES_DIR,
5817
- definition: () => definition10,
5818
- execute: () => execute10
5886
+ definition: () => definition9,
5887
+ execute: () => execute9
5819
5888
  });
5820
5889
  import { mkdir as mkdir2, stat as stat2, writeFile as writeFile2 } from "fs/promises";
5821
5890
  import { join as join3 } from "path";
5822
5891
  var log9 = createLogger("createWireframe");
5823
5892
  var WIREFRAMES_DIR = "src/.wireframes";
5824
5893
  var UPLOAD_TIMEOUT_MS2 = 3e4;
5825
- var definition10 = {
5894
+ var definition9 = {
5826
5895
  name: "createWireframe",
5827
5896
  description: "Generate a wireframe from self-contained HTML+CSS you author and write it to disk as a design artifact. This is how a wireframe comes to exist \u2014 the way generateImages is how an image comes to exist \u2014 and the developer builds from the file it creates. The result also hands back the reference line that embeds the wireframe in your response and in specs; paste it wherever the wireframe belongs and it renders as a live preview. Calling again with the same slug revises the wireframe in place, so existing references stay current \u2014 a revision is a call to this tool, never a prose description of changes to an earlier wireframe.",
5828
5897
  inputSchema: {
@@ -5916,7 +5985,7 @@ async function uploadMirror(context, slug, content) {
5916
5985
  return { ok: false, note: err.message };
5917
5986
  }
5918
5987
  }
5919
- async function execute10(input, onLog, context) {
5988
+ async function execute9(input, onLog, context) {
5920
5989
  if (!context) {
5921
5990
  return "Error: createWireframe requires execution context";
5922
5991
  }
@@ -5964,8 +6033,29 @@ async function execute10(input, onLog, context) {
5964
6033
  }
5965
6034
 
5966
6035
  // src/subagents/designExpert/tools/index.ts
6036
+ var research = {
6037
+ definition: {
6038
+ name: "research",
6039
+ description: "Your researcher, for design questions your built-in catalogs and expertise do not settle: how leading products present a specific kind of data or interaction, current conventions or best practices for a pattern, general-purpose ideas and inspiration. It searches the web, reads the sources that matter, and returns a distilled report with citations and concrete specifics. Brief it neutrally: the question or topic, not the answer you expect.",
6040
+ inputSchema: {
6041
+ type: "object",
6042
+ properties: {
6043
+ task: {
6044
+ type: "string",
6045
+ description: "What you need to find out, in natural language, with enough product context to make the findings relevant."
6046
+ }
6047
+ },
6048
+ required: ["task"]
6049
+ }
6050
+ },
6051
+ execute: (input, _onLog, context) => {
6052
+ if (!context) {
6053
+ return Promise.resolve("Error: research requires execution context");
6054
+ }
6055
+ return runResearch(input.task, context);
6056
+ }
6057
+ };
5967
6058
  var tools = {
5968
- searchGoogle: searchGoogle_exports,
5969
6059
  scrapeWebUrl: scrapeWebUrl_exports,
5970
6060
  analyzeDesign: analyzeDesign_exports,
5971
6061
  analyzeImage: analyzeImage_exports,
@@ -5979,7 +6069,8 @@ var tools = {
5979
6069
  polishCopy: polishCopy_exports,
5980
6070
  loadSkill: loadSkill_exports,
5981
6071
  // Appended last: tool order is part of the subagent's prompt-cache prefix.
5982
- createWireframe: createWireframe_exports
6072
+ createWireframe: createWireframe_exports,
6073
+ research
5983
6074
  };
5984
6075
  var DESIGN_EXPERT_TOOLS = [
5985
6076
  ...COMMON_READ_TOOLS,
@@ -6537,11 +6628,11 @@ ${result.text}`;
6537
6628
  }
6538
6629
 
6539
6630
  // src/subagents/productVision/prompt.ts
6540
- var BASE_PROMPT3 = readAsset("subagents/productVision", "prompt.md");
6631
+ var BASE_PROMPT4 = readAsset("subagents/productVision", "prompt.md");
6541
6632
  function getProductVisionPrompt() {
6542
6633
  const specIndex = loadSpecIndex();
6543
6634
  const roadmapIndex = loadRoadmapIndex();
6544
- const parts = [BASE_PROMPT3, loadPlatformBrief()];
6635
+ const parts = [BASE_PROMPT4, loadPlatformBrief()];
6545
6636
  parts.push("<!-- cache_breakpoint -->");
6546
6637
  if (specIndex) {
6547
6638
  parts.push(specIndex);
@@ -6665,11 +6756,26 @@ var SANITY_CHECK_TOOLS = [
6665
6756
  },
6666
6757
  required: ["command"]
6667
6758
  }
6759
+ },
6760
+ // Appended last: tool order is part of the subagent's prompt-cache prefix.
6761
+ {
6762
+ name: "research",
6763
+ description: "Deep researcher. When the plan hinges on an unfamiliar third-party service, API, or claim you cannot settle with a quick search, hand it the question \u2014 it researches properly (multiple sources, official docs, reading real source code) and returns a distilled, citation-backed report. Quick package-liveness checks stay on searchGoogle; use this when being wrong would be expensive. Brief it neutrally: the question, not the answer you expect.",
6764
+ inputSchema: {
6765
+ type: "object",
6766
+ properties: {
6767
+ task: {
6768
+ type: "string",
6769
+ description: "What you need to find out, in natural language."
6770
+ }
6771
+ },
6772
+ required: ["task"]
6773
+ }
6668
6774
  }
6669
6775
  ];
6670
6776
 
6671
6777
  // src/subagents/codeSanityCheck/index.ts
6672
- var BASE_PROMPT4 = readAsset("subagents/codeSanityCheck", "prompt.md");
6778
+ var BASE_PROMPT5 = readAsset("subagents/codeSanityCheck", "prompt.md");
6673
6779
  var codeSanityCheckTool = {
6674
6780
  definition: {
6675
6781
  name: "codeSanityCheck",
@@ -6690,7 +6796,7 @@ var codeSanityCheckTool = {
6690
6796
  return "Error: code sanity check requires execution context";
6691
6797
  }
6692
6798
  const specIndex = loadSpecIndex();
6693
- const parts = [BASE_PROMPT4, loadPlatformBrief()];
6799
+ const parts = [BASE_PROMPT5, loadPlatformBrief()];
6694
6800
  parts.push("<!-- cache_breakpoint -->");
6695
6801
  if (specIndex) {
6696
6802
  parts.push(specIndex);
@@ -6706,6 +6812,16 @@ var codeSanityCheckTool = {
6706
6812
  ...deriveContext(context, toolCallId, onLog),
6707
6813
  subAgentMessages: sams
6708
6814
  } : context;
6815
+ if (name === "searchGoogle") {
6816
+ return executeSearchGoogle(
6817
+ { ...toolInput, fetchTopN: 5 },
6818
+ childCtx.onLog,
6819
+ "codeSanityCheck"
6820
+ );
6821
+ }
6822
+ if (name === "research") {
6823
+ return runResearch(String(toolInput.task ?? ""), childCtx);
6824
+ }
6709
6825
  return executeTool(name, toolInput, childCtx);
6710
6826
  },
6711
6827
  apiConfig: context.apiConfig,
@@ -6859,7 +6975,7 @@ function acquireSpecSyncLock() {
6859
6975
  }
6860
6976
 
6861
6977
  // src/subagents/specSync/index.ts
6862
- var BASE_PROMPT5 = readAsset("subagents/specSync", "prompt.md");
6978
+ var BASE_PROMPT6 = readAsset("subagents/specSync", "prompt.md");
6863
6979
  var MSFM_DOCS = `<mindstudio_flavored_markdown_spec_docs>
6864
6980
  ${readAsset(
6865
6981
  "prompt",
@@ -6898,7 +7014,7 @@ var specSyncTool = {
6898
7014
  }
6899
7015
  const specIndex = loadSpecIndex();
6900
7016
  const parts = [
6901
- BASE_PROMPT5,
7017
+ BASE_PROMPT6,
6902
7018
  loadPlatformBrief(),
6903
7019
  MSFM_DOCS,
6904
7020
  "<!-- cache_breakpoint -->"
@@ -7013,7 +7129,6 @@ var ALL_TOOLS = [
7013
7129
  confirmDestructiveActionTool,
7014
7130
  askMindStudioSdkTool,
7015
7131
  scrapeWebUrlTool,
7016
- searchGoogleTool,
7017
7132
  setProjectMetadataTool,
7018
7133
  designExpertTool,
7019
7134
  productVisionTool,
@@ -7052,7 +7167,8 @@ var ALL_TOOLS = [
7052
7167
  // Appended rather than grouped: position is part of the cache prefix, so a
7053
7168
  // new tool goes at the end to leave every existing session's prefix intact.
7054
7169
  loadSkillTool,
7055
- testJewelTool
7170
+ testJewelTool,
7171
+ researchTool
7056
7172
  ];
7057
7173
  var SUBAGENT_TOOL_NAMES = /* @__PURE__ */ new Set([
7058
7174
  "visualDesignExpert",
@@ -7061,7 +7177,8 @@ var SUBAGENT_TOOL_NAMES = /* @__PURE__ */ new Set([
7061
7177
  "copyEditor",
7062
7178
  "specSync",
7063
7179
  "runAutomatedBrowserTest",
7064
- "askMindStudioSdk"
7180
+ "askMindStudioSdk",
7181
+ "research"
7065
7182
  ]);
7066
7183
  function getToolDefinitions() {
7067
7184
  return ALL_TOOLS.map((t) => t.definition);