@mindstudio-ai/remy 0.1.313 → 0.1.314

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",
@@ -1884,158 +1891,6 @@ var askMindStudioSdkTool = {
1884
1891
  }
1885
1892
  };
1886
1893
 
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
1894
  // src/tools/common/setProjectMetadata.ts
2040
1895
  var setProjectMetadataTool = {
2041
1896
  definition: {
@@ -2160,7 +2015,7 @@ This reference lives at ${skill.path}. Re-read it with readFile if you need it a
2160
2015
  };
2161
2016
 
2162
2017
  // src/tools/code/readFile.ts
2163
- import fs13 from "fs/promises";
2018
+ import fs12 from "fs/promises";
2164
2019
  var DEFAULT_WINDOW = 500;
2165
2020
  var MAX_BYTES = 64 * 1024;
2166
2021
  function isBinary(buffer) {
@@ -2201,7 +2056,7 @@ var readFileTool = {
2201
2056
  },
2202
2057
  async execute(input) {
2203
2058
  try {
2204
- const buffer = await fs13.readFile(input.path);
2059
+ const buffer = await fs12.readFile(input.path);
2205
2060
  if (isBinary(buffer)) {
2206
2061
  const size = buffer.length;
2207
2062
  const unit = size > 1024 * 1024 ? `${(size / (1024 * 1024)).toFixed(1)}MB` : `${(size / 1024).toFixed(1)}KB`;
@@ -2273,7 +2128,7 @@ var readFileTool = {
2273
2128
  };
2274
2129
 
2275
2130
  // src/tools/code/writeFile.ts
2276
- import fs14 from "fs/promises";
2131
+ import fs13 from "fs/promises";
2277
2132
  import path6 from "path";
2278
2133
  var writeFileTool = {
2279
2134
  definition: {
@@ -2310,7 +2165,7 @@ var writeFileTool = {
2310
2165
  lastNewlineCount = newlineCount;
2311
2166
  const lastNewline = partial.content.lastIndexOf("\n");
2312
2167
  const completeContent = partial.content.substring(0, lastNewline + 1);
2313
- const oldContent = await fs14.readFile(partial.path, "utf-8").catch(() => "");
2168
+ const oldContent = await fs13.readFile(partial.path, "utf-8").catch(() => "");
2314
2169
  return `Writing ${partial.path} (${newlineCount} lines)
2315
2170
  ${unifiedDiff(partial.path, oldContent, completeContent)}`;
2316
2171
  }
@@ -2319,13 +2174,13 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
2319
2174
  async execute(input) {
2320
2175
  const release = await acquireFileLock(input.path);
2321
2176
  try {
2322
- await fs14.mkdir(path6.dirname(input.path), { recursive: true });
2177
+ await fs13.mkdir(path6.dirname(input.path), { recursive: true });
2323
2178
  let oldContent = null;
2324
2179
  try {
2325
- oldContent = await fs14.readFile(input.path, "utf-8");
2180
+ oldContent = await fs13.readFile(input.path, "utf-8");
2326
2181
  } catch {
2327
2182
  }
2328
- await fs14.writeFile(input.path, input.content, "utf-8");
2183
+ await fs13.writeFile(input.path, input.content, "utf-8");
2329
2184
  const lineCount = input.content.split("\n").length;
2330
2185
  const label = oldContent !== null ? "Wrote" : "Created";
2331
2186
  return `${label} ${input.path} (${lineCount} lines)
@@ -2339,7 +2194,7 @@ ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
2339
2194
  };
2340
2195
 
2341
2196
  // src/tools/code/editFile/index.ts
2342
- import fs15 from "fs/promises";
2197
+ import fs14 from "fs/promises";
2343
2198
  var editFileTool = {
2344
2199
  definition: {
2345
2200
  name: "editFile",
@@ -2370,7 +2225,7 @@ var editFileTool = {
2370
2225
  async execute(input) {
2371
2226
  const release = await acquireFileLock(input.path);
2372
2227
  try {
2373
- const content = await fs15.readFile(input.path, "utf-8");
2228
+ const content = await fs14.readFile(input.path, "utf-8");
2374
2229
  const { old_string, new_string, replace_all } = input;
2375
2230
  const occurrences = findOccurrences(content, old_string);
2376
2231
  if (replace_all) {
@@ -2386,7 +2241,7 @@ var editFileTool = {
2386
2241
  new_string
2387
2242
  );
2388
2243
  }
2389
- await fs15.writeFile(input.path, updated, "utf-8");
2244
+ await fs14.writeFile(input.path, updated, "utf-8");
2390
2245
  return `Replaced ${occurrences.length} occurrence${occurrences.length > 1 ? "s" : ""} in ${input.path}
2391
2246
  ${unifiedDiff(input.path, content, updated)}`;
2392
2247
  }
@@ -2397,7 +2252,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2397
2252
  old_string.length,
2398
2253
  new_string
2399
2254
  );
2400
- await fs15.writeFile(input.path, updated, "utf-8");
2255
+ await fs14.writeFile(input.path, updated, "utf-8");
2401
2256
  return `Updated ${input.path}
2402
2257
  ${unifiedDiff(input.path, content, updated)}`;
2403
2258
  }
@@ -2413,7 +2268,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2413
2268
  flex.matchedText.length,
2414
2269
  new_string
2415
2270
  );
2416
- await fs15.writeFile(input.path, updated, "utf-8");
2271
+ await fs14.writeFile(input.path, updated, "utf-8");
2417
2272
  return `Updated ${input.path} (matched with flexible whitespace at line ${flex.line})
2418
2273
  ${unifiedDiff(input.path, content, updated)}`;
2419
2274
  }
@@ -2739,12 +2594,12 @@ var globTool = {
2739
2594
  };
2740
2595
 
2741
2596
  // src/tools/code/listDir.ts
2742
- import fs16 from "fs/promises";
2597
+ import fs15 from "fs/promises";
2743
2598
  import path8 from "path";
2744
2599
  var EXCLUDE = /* @__PURE__ */ new Set([".git", "node_modules"]);
2745
2600
  var MAX_CHILDREN = 15;
2746
2601
  async function readAndSort(dirPath) {
2747
- const entries = await fs16.readdir(dirPath, { withFileTypes: true });
2602
+ const entries = await fs15.readdir(dirPath, { withFileTypes: true });
2748
2603
  return entries.filter((e) => !EXCLUDE.has(e.name)).sort((a, b) => {
2749
2604
  if (a.isDirectory() && !b.isDirectory()) {
2750
2605
  return -1;
@@ -2785,7 +2640,7 @@ function formatSize(bytes) {
2785
2640
  }
2786
2641
  async function formatFile(dirPath, name, indent) {
2787
2642
  try {
2788
- const stat4 = await fs16.stat(path8.join(dirPath, name));
2643
+ const stat4 = await fs15.stat(path8.join(dirPath, name));
2789
2644
  return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat4.size)}`;
2790
2645
  } catch {
2791
2646
  return `${indent}${name}`;
@@ -3117,6 +2972,119 @@ var queryDatabaseTool = {
3117
2972
  }
3118
2973
  };
3119
2974
 
2975
+ // src/usageLedger.ts
2976
+ import fs16 from "fs";
2977
+ var LEDGER_FILE = ".logs/usage.ndjson";
2978
+ var fd = null;
2979
+ function nanoToDollars(nano) {
2980
+ return typeof nano === "number" ? nano / 1e9 : void 0;
2981
+ }
2982
+ function recordUsage(entry) {
2983
+ try {
2984
+ if (fd === null) {
2985
+ fs16.mkdirSync(".logs", { recursive: true });
2986
+ fd = fs16.openSync(LEDGER_FILE, "a");
2987
+ }
2988
+ fs16.writeSync(fd, JSON.stringify(entry) + "\n");
2989
+ } catch {
2990
+ }
2991
+ }
2992
+
2993
+ // src/subagents/common/runMindstudioCli.ts
2994
+ function stripFlags(args) {
2995
+ const out = [];
2996
+ for (let i = 0; i < args.length; i++) {
2997
+ const arg = args[i];
2998
+ if (arg === "--no-meta") {
2999
+ continue;
3000
+ }
3001
+ if (arg === "--output-key") {
3002
+ i++;
3003
+ continue;
3004
+ }
3005
+ out.push(arg);
3006
+ }
3007
+ return out;
3008
+ }
3009
+ async function runMindstudioCliResult(args, options) {
3010
+ const cleanArgs = stripFlags(args);
3011
+ const cliAction = args[0];
3012
+ const agentName = options?.caller ?? "mindstudio-cli";
3013
+ const start = Date.now();
3014
+ const res = await runCli("mindstudio", cleanArgs, options);
3015
+ if (!res.ok) {
3016
+ return { ok: false, value: formatCliResult(res) };
3017
+ }
3018
+ const truncNote = res.truncated ? "\n\n[output truncated]" : "";
3019
+ let envelope;
3020
+ try {
3021
+ envelope = JSON.parse(res.output);
3022
+ } catch {
3023
+ return { ok: true, value: res.output + truncNote };
3024
+ }
3025
+ if (envelope && typeof envelope === "object" && Array.isArray(envelope.results)) {
3026
+ const durationMs = Date.now() - start;
3027
+ for (const step of envelope.results) {
3028
+ if (typeof step?.billingCost === "number") {
3029
+ recordUsage({
3030
+ ts: Date.now(),
3031
+ agentName,
3032
+ cliAction: `${cliAction}:${step.stepType ?? "step"}`,
3033
+ cost: nanoToDollars(step.billingCost),
3034
+ inputTokens: 0,
3035
+ outputTokens: 0,
3036
+ durationMs,
3037
+ toolNames: []
3038
+ });
3039
+ }
3040
+ }
3041
+ return { ok: true, value: JSON.stringify(envelope.results) + truncNote };
3042
+ }
3043
+ if (typeof envelope?.$billingCost === "number") {
3044
+ recordUsage({
3045
+ ts: Date.now(),
3046
+ agentName,
3047
+ cliAction,
3048
+ cost: nanoToDollars(envelope.$billingCost),
3049
+ billingEvents: envelope.$billingEvents,
3050
+ // CLI billing isn't expressed as input/output tokens for most actions
3051
+ // (image gen is per-image, scrape per-page, etc). `numUnits` inside each
3052
+ // billingEvent carries the per-event unit count.
3053
+ inputTokens: 0,
3054
+ outputTokens: 0,
3055
+ durationMs: Date.now() - start,
3056
+ toolNames: []
3057
+ });
3058
+ }
3059
+ if (options?.outputKey) {
3060
+ const v = envelope?.[options.outputKey];
3061
+ if (v === void 0 || v === null) {
3062
+ return { ok: false, value: JSON.stringify(stripDollarKeys(envelope)) };
3063
+ }
3064
+ const value = typeof v === "string" ? v : JSON.stringify(v);
3065
+ return { ok: true, value: value + truncNote };
3066
+ }
3067
+ return {
3068
+ ok: true,
3069
+ value: JSON.stringify(stripDollarKeys(envelope)) + truncNote
3070
+ };
3071
+ }
3072
+ async function runMindstudioCli(args, options) {
3073
+ return (await runMindstudioCliResult(args, options)).value;
3074
+ }
3075
+ function stripDollarKeys(envelope) {
3076
+ if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) {
3077
+ return envelope;
3078
+ }
3079
+ const out = {};
3080
+ for (const [k, v] of Object.entries(envelope)) {
3081
+ if (!k.startsWith("$")) {
3082
+ out[k] = v;
3083
+ }
3084
+ }
3085
+ return out;
3086
+ }
3087
+
3120
3088
  // src/tools/_helpers/uploadImage.ts
3121
3089
  import { readFile, stat } from "fs/promises";
3122
3090
  import { basename, extname, resolve } from "path";
@@ -4595,7 +4563,7 @@ The first-party SDK (@mindstudio-ai/agent) provides access to 200+ AI models (Op
4595
4563
  ## What Remy apps are NOT good for
4596
4564
 
4597
4565
  - 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.
4566
+ - 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
4567
  </platform_brief>`;
4600
4568
  }
4601
4569
 
@@ -4884,56 +4852,155 @@ var screenshotTool = {
4884
4852
  execute: (input, context) => executeScreenshot(input, context?.onLog, context)
4885
4853
  };
4886
4854
 
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 = {
4855
+ // src/subagents/research/tools.ts
4856
+ var searchGoogleDefinition = {
4895
4857
  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.',
4858
+ 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
4859
  inputSchema: {
4898
4860
  type: "object",
4899
4861
  properties: {
4900
4862
  query: {
4901
4863
  type: "string",
4902
4864
  description: "The search query."
4865
+ },
4866
+ fetchTopN: {
4867
+ type: "number",
4868
+ description: "Also fetch page content (capped ~4,000 chars each) for this many top results. Omit for a fast SERP-only search."
4903
4869
  }
4904
4870
  },
4905
4871
  required: ["query"]
4906
4872
  }
4907
4873
  };
4908
- async function execute(input, onLog) {
4874
+ var scrapeWebUrlDefinition = {
4875
+ name: "scrapeWebUrl",
4876
+ 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.",
4877
+ inputSchema: {
4878
+ type: "object",
4879
+ properties: {
4880
+ url: {
4881
+ type: "string",
4882
+ description: "The URL to fetch."
4883
+ }
4884
+ },
4885
+ required: ["url"]
4886
+ }
4887
+ };
4888
+ var RESEARCH_TOOLS = [
4889
+ ...COMMON_READ_TOOLS,
4890
+ searchGoogleDefinition,
4891
+ scrapeWebUrlDefinition,
4892
+ bashTool.definition
4893
+ ];
4894
+ async function executeSearchGoogle(input, onLog, caller) {
4895
+ const fetchTopN = Math.max(0, Math.round(Number(input.fetchTopN) || 0));
4909
4896
  return runMindstudioCli(
4910
4897
  [
4911
4898
  "search-google",
4912
4899
  "--query",
4913
- input.query,
4900
+ String(input.query),
4914
4901
  "--export-type",
4915
4902
  "json",
4916
4903
  "--fetch-top-n",
4917
- String(FETCH_TOP_N2)
4904
+ String(fetchTopN)
4918
4905
  ],
4919
4906
  {
4920
4907
  outputKey: "results",
4908
+ maxBuffer: SEARCH_MAX_BUFFER,
4909
+ onLog,
4910
+ caller
4911
+ }
4912
+ );
4913
+ }
4914
+ async function executeScrapeWebUrl(input, onLog, caller) {
4915
+ return runMindstudioCli(
4916
+ [
4917
+ "scrape-url",
4918
+ "--url",
4919
+ String(input.url),
4920
+ "--page-options",
4921
+ JSON.stringify({ onlyMainContent: true })
4922
+ ],
4923
+ {
4924
+ maxBuffer: SCRAPE_MAX_BUFFER,
4921
4925
  onLog,
4922
- caller: "designExpert",
4923
- maxBuffer: SEARCH_MAX_BUFFER
4926
+ caller
4924
4927
  }
4925
4928
  );
4926
4929
  }
4927
4930
 
4931
+ // src/subagents/research/index.ts
4932
+ var BASE_PROMPT2 = readAsset("subagents/research", "prompt.md");
4933
+ async function runResearch(task, context) {
4934
+ const specIndex = loadSpecIndex();
4935
+ const parts = [BASE_PROMPT2, loadPlatformBrief()];
4936
+ parts.push("<!-- cache_breakpoint -->");
4937
+ if (specIndex) {
4938
+ parts.push(specIndex);
4939
+ }
4940
+ const system = parts.join("\n\n");
4941
+ const result = await runSubAgent({
4942
+ system,
4943
+ task,
4944
+ tools: RESEARCH_TOOLS,
4945
+ externalTools: /* @__PURE__ */ new Set(),
4946
+ executeTool: (name, toolInput, toolCallId, onLog, sams) => {
4947
+ const childCtx = toolCallId ? {
4948
+ ...deriveContext(context, toolCallId, onLog),
4949
+ subAgentMessages: sams
4950
+ } : context;
4951
+ if (name === "searchGoogle") {
4952
+ return executeSearchGoogle(toolInput, childCtx.onLog, "research");
4953
+ }
4954
+ if (name === "scrapeWebUrl") {
4955
+ return executeScrapeWebUrl(toolInput, childCtx.onLog, "research");
4956
+ }
4957
+ return executeTool(name, toolInput, childCtx);
4958
+ },
4959
+ apiConfig: context.apiConfig,
4960
+ model: resolveModel("research", context.models, context.model),
4961
+ subAgentId: "research",
4962
+ signal: context.signal,
4963
+ parentToolId: context.toolCallId,
4964
+ requestId: context.requestId,
4965
+ onEvent: context.onEvent,
4966
+ resolveExternalTool: context.resolveExternalTool,
4967
+ toolRegistry: context.toolRegistry
4968
+ });
4969
+ context.subAgentMessages?.set(context.toolCallId, result.messages);
4970
+ return result.text;
4971
+ }
4972
+ var researchTool = {
4973
+ definition: {
4974
+ name: "research",
4975
+ 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.",
4976
+ inputSchema: {
4977
+ type: "object",
4978
+ properties: {
4979
+ task: {
4980
+ type: "string",
4981
+ 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."
4982
+ }
4983
+ },
4984
+ required: ["task"]
4985
+ }
4986
+ },
4987
+ async execute(input, context) {
4988
+ if (!context) {
4989
+ return "Error: research requires execution context";
4990
+ }
4991
+ return runResearch(input.task, context);
4992
+ }
4993
+ };
4994
+
4928
4995
  // src/subagents/designExpert/tools/scrapeWebUrl.ts
4929
4996
  var scrapeWebUrl_exports = {};
4930
4997
  __export(scrapeWebUrl_exports, {
4931
- definition: () => definition2,
4932
- execute: () => execute2
4998
+ definition: () => definition,
4999
+ execute: () => execute
4933
5000
  });
4934
- var definition2 = {
5001
+ var definition = {
4935
5002
  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.",
5003
+ 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
5004
  inputSchema: {
4938
5005
  type: "object",
4939
5006
  properties: {
@@ -4945,7 +5012,7 @@ var definition2 = {
4945
5012
  required: ["url"]
4946
5013
  }
4947
5014
  };
4948
- async function execute2(input, onLog) {
5015
+ async function execute(input, onLog) {
4949
5016
  const pageOptions = { onlyMainContent: true };
4950
5017
  return runMindstudioCli(
4951
5018
  [
@@ -4962,8 +5029,8 @@ async function execute2(input, onLog) {
4962
5029
  // src/subagents/designExpert/tools/analyzeDesign.ts
4963
5030
  var analyzeDesign_exports = {};
4964
5031
  __export(analyzeDesign_exports, {
4965
- definition: () => definition4,
4966
- execute: () => execute4
5032
+ definition: () => definition3,
5033
+ execute: () => execute3
4967
5034
  });
4968
5035
  import { readFile as readFile2 } from "fs/promises";
4969
5036
  import { extname as extname2, join as join2 } from "path";
@@ -4972,8 +5039,8 @@ import { extname as extname2, join as join2 } from "path";
4972
5039
  var renderImage_exports = {};
4973
5040
  __export(renderImage_exports, {
4974
5041
  RENDER_ANALYZE_PROMPT: () => RENDER_ANALYZE_PROMPT,
4975
- definition: () => definition3,
4976
- execute: () => execute3
5042
+ definition: () => definition2,
5043
+ execute: () => execute2
4977
5044
  });
4978
5045
  import { mkdir, unlink, writeFile } from "fs/promises";
4979
5046
  import { tmpdir } from "os";
@@ -4982,7 +5049,7 @@ import { randomUUID } from "crypto";
4982
5049
  var MIN_DIMENSION = 16;
4983
5050
  var MAX_DIMENSION = 4096;
4984
5051
  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 = {
5052
+ var definition2 = {
4986
5053
  name: "renderImage",
4987
5054
  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
5055
  inputSchema: {
@@ -5016,7 +5083,7 @@ var definition3 = {
5016
5083
  required: ["html", "width", "height"]
5017
5084
  }
5018
5085
  };
5019
- async function execute3(input, onLog, context) {
5086
+ async function execute2(input, onLog, context) {
5020
5087
  const html = typeof input.html === "string" ? input.html : "";
5021
5088
  const width = Math.round(Number(input.width));
5022
5089
  const height = Math.round(Number(input.height));
@@ -5125,7 +5192,7 @@ Identify the specific design moves that make this page interesting and unique, d
5125
5192
 
5126
5193
  Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.
5127
5194
  `;
5128
- var definition4 = {
5195
+ var definition3 = {
5129
5196
  name: "analyzeDesign",
5130
5197
  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
5198
  inputSchema: {
@@ -5165,7 +5232,7 @@ function splitFrontmatter(raw) {
5165
5232
  html: raw.slice(match[0].length)
5166
5233
  };
5167
5234
  }
5168
- async function execute4(input, onLog, context) {
5235
+ async function execute3(input, onLog, context) {
5169
5236
  const url = String(input.url ?? "").trim();
5170
5237
  if (!url) {
5171
5238
  return "Error: url is required.";
@@ -5273,10 +5340,10 @@ ${basePrompt}` : basePrompt;
5273
5340
  // src/subagents/designExpert/tools/analyzeImage.ts
5274
5341
  var analyzeImage_exports = {};
5275
5342
  __export(analyzeImage_exports, {
5276
- definition: () => definition5,
5277
- execute: () => execute5
5343
+ definition: () => definition4,
5344
+ execute: () => execute4
5278
5345
  });
5279
- var definition5 = {
5346
+ var definition4 = {
5280
5347
  name: "analyzeImage",
5281
5348
  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
5349
  inputSchema: {
@@ -5297,7 +5364,7 @@ var definition5 = {
5297
5364
  required: ["imageUrl"]
5298
5365
  }
5299
5366
  };
5300
- async function execute5(input, onLog, context) {
5367
+ async function execute4(input, onLog, context) {
5301
5368
  const prompt = buildScreenshotAnalysisPrompt({
5302
5369
  prompt: input.prompt
5303
5370
  });
@@ -5314,8 +5381,8 @@ async function execute5(input, onLog, context) {
5314
5381
  // src/subagents/designExpert/tools/images/generateImages.ts
5315
5382
  var generateImages_exports = {};
5316
5383
  __export(generateImages_exports, {
5317
- definition: () => definition6,
5318
- execute: () => execute6
5384
+ definition: () => definition5,
5385
+ execute: () => execute5
5319
5386
  });
5320
5387
 
5321
5388
  // src/subagents/designExpert/tools/images/enhancePrompt.ts
@@ -5542,7 +5609,7 @@ async function generateImageAssets(opts) {
5542
5609
  }
5543
5610
 
5544
5611
  // src/subagents/designExpert/tools/images/generateImages.ts
5545
- var definition6 = {
5612
+ var definition5 = {
5546
5613
  name: "generateImages",
5547
5614
  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
5615
  inputSchema: {
@@ -5575,7 +5642,7 @@ var definition6 = {
5575
5642
  required: ["prompts"]
5576
5643
  }
5577
5644
  };
5578
- async function execute6(input, onLog, context) {
5645
+ async function execute5(input, onLog, context) {
5579
5646
  return generateImageAssets({
5580
5647
  prompts: input.prompts,
5581
5648
  width: input.width,
@@ -5606,10 +5673,10 @@ async function execute6(input, onLog, context) {
5606
5673
  // src/subagents/designExpert/tools/images/editImages.ts
5607
5674
  var editImages_exports = {};
5608
5675
  __export(editImages_exports, {
5609
- definition: () => definition7,
5610
- execute: () => execute7
5676
+ definition: () => definition6,
5677
+ execute: () => execute6
5611
5678
  });
5612
- var definition7 = {
5679
+ var definition6 = {
5613
5680
  name: "editImages",
5614
5681
  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
5682
  inputSchema: {
@@ -5645,7 +5712,7 @@ var definition7 = {
5645
5712
  required: ["prompts", "sourceImages"]
5646
5713
  }
5647
5714
  };
5648
- async function execute7(input, onLog, context) {
5715
+ async function execute6(input, onLog, context) {
5649
5716
  return generateImageAssets({
5650
5717
  prompts: input.prompts,
5651
5718
  sourceImages: input.sourceImages,
@@ -5676,15 +5743,15 @@ async function execute7(input, onLog, context) {
5676
5743
  // src/subagents/designExpert/tools/polishCopy.ts
5677
5744
  var polishCopy_exports = {};
5678
5745
  __export(polishCopy_exports, {
5679
- definition: () => definition8,
5680
- execute: () => execute8
5746
+ definition: () => definition7,
5747
+ execute: () => execute7
5681
5748
  });
5682
5749
 
5683
5750
  // src/subagents/copyEditor/tools.ts
5684
5751
  var COPY_EDITOR_TOOLS = [...COMMON_READ_TOOLS];
5685
5752
 
5686
5753
  // src/subagents/copyEditor/index.ts
5687
- var BASE_PROMPT2 = readAsset("subagents/copyEditor", "prompt.md");
5754
+ var BASE_PROMPT3 = readAsset("subagents/copyEditor", "prompt.md");
5688
5755
  var copyEditorTool = {
5689
5756
  definition: {
5690
5757
  name: "copyEditor",
@@ -5705,7 +5772,7 @@ var copyEditorTool = {
5705
5772
  return "Error: copy editor requires execution context";
5706
5773
  }
5707
5774
  const specIndex = loadSpecIndex();
5708
- const parts = [BASE_PROMPT2];
5775
+ const parts = [BASE_PROMPT3];
5709
5776
  parts.push("<!-- cache_breakpoint -->");
5710
5777
  if (specIndex) {
5711
5778
  parts.push(specIndex);
@@ -5739,7 +5806,7 @@ var copyEditorTool = {
5739
5806
  };
5740
5807
 
5741
5808
  // src/subagents/designExpert/tools/polishCopy.ts
5742
- var definition8 = {
5809
+ var definition7 = {
5743
5810
  name: "polishCopy",
5744
5811
  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
5812
  inputSchema: {
@@ -5753,15 +5820,15 @@ var definition8 = {
5753
5820
  required: ["task"]
5754
5821
  }
5755
5822
  };
5756
- async function execute8(input, _onLog, context) {
5823
+ async function execute7(input, _onLog, context) {
5757
5824
  return copyEditorTool.execute(input, context);
5758
5825
  }
5759
5826
 
5760
5827
  // src/subagents/designExpert/tools/loadSkill.ts
5761
5828
  var loadSkill_exports = {};
5762
5829
  __export(loadSkill_exports, {
5763
- definition: () => definition9,
5764
- execute: () => execute9
5830
+ definition: () => definition8,
5831
+ execute: () => execute8
5765
5832
  });
5766
5833
 
5767
5834
  // src/subagents/designExpert/skills/_catalog.ts
@@ -5775,7 +5842,7 @@ var designSkillCatalog = buildSkillCatalog({
5775
5842
  });
5776
5843
 
5777
5844
  // src/subagents/designExpert/tools/loadSkill.ts
5778
- var definition9 = {
5845
+ var definition8 = {
5779
5846
  name: "loadSkill",
5780
5847
  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
5848
  inputSchema: {
@@ -5793,7 +5860,7 @@ var definition9 = {
5793
5860
  required: ["skill"]
5794
5861
  }
5795
5862
  };
5796
- async function execute9(input) {
5863
+ async function execute8(input) {
5797
5864
  const id = String(input.skill ?? "");
5798
5865
  const skill = designSkillCatalog.get(id);
5799
5866
  if (!skill) {
@@ -5814,15 +5881,15 @@ This reference lives at ${skill.path}. Re-read it with readFile if you need it a
5814
5881
  var createWireframe_exports = {};
5815
5882
  __export(createWireframe_exports, {
5816
5883
  WIREFRAMES_DIR: () => WIREFRAMES_DIR,
5817
- definition: () => definition10,
5818
- execute: () => execute10
5884
+ definition: () => definition9,
5885
+ execute: () => execute9
5819
5886
  });
5820
5887
  import { mkdir as mkdir2, stat as stat2, writeFile as writeFile2 } from "fs/promises";
5821
5888
  import { join as join3 } from "path";
5822
5889
  var log9 = createLogger("createWireframe");
5823
5890
  var WIREFRAMES_DIR = "src/.wireframes";
5824
5891
  var UPLOAD_TIMEOUT_MS2 = 3e4;
5825
- var definition10 = {
5892
+ var definition9 = {
5826
5893
  name: "createWireframe",
5827
5894
  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
5895
  inputSchema: {
@@ -5916,7 +5983,7 @@ async function uploadMirror(context, slug, content) {
5916
5983
  return { ok: false, note: err.message };
5917
5984
  }
5918
5985
  }
5919
- async function execute10(input, onLog, context) {
5986
+ async function execute9(input, onLog, context) {
5920
5987
  if (!context) {
5921
5988
  return "Error: createWireframe requires execution context";
5922
5989
  }
@@ -5964,8 +6031,29 @@ async function execute10(input, onLog, context) {
5964
6031
  }
5965
6032
 
5966
6033
  // src/subagents/designExpert/tools/index.ts
6034
+ var research = {
6035
+ definition: {
6036
+ name: "research",
6037
+ 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.",
6038
+ inputSchema: {
6039
+ type: "object",
6040
+ properties: {
6041
+ task: {
6042
+ type: "string",
6043
+ description: "What you need to find out, in natural language, with enough product context to make the findings relevant."
6044
+ }
6045
+ },
6046
+ required: ["task"]
6047
+ }
6048
+ },
6049
+ execute: (input, _onLog, context) => {
6050
+ if (!context) {
6051
+ return Promise.resolve("Error: research requires execution context");
6052
+ }
6053
+ return runResearch(input.task, context);
6054
+ }
6055
+ };
5967
6056
  var tools = {
5968
- searchGoogle: searchGoogle_exports,
5969
6057
  scrapeWebUrl: scrapeWebUrl_exports,
5970
6058
  analyzeDesign: analyzeDesign_exports,
5971
6059
  analyzeImage: analyzeImage_exports,
@@ -5979,7 +6067,8 @@ var tools = {
5979
6067
  polishCopy: polishCopy_exports,
5980
6068
  loadSkill: loadSkill_exports,
5981
6069
  // Appended last: tool order is part of the subagent's prompt-cache prefix.
5982
- createWireframe: createWireframe_exports
6070
+ createWireframe: createWireframe_exports,
6071
+ research
5983
6072
  };
5984
6073
  var DESIGN_EXPERT_TOOLS = [
5985
6074
  ...COMMON_READ_TOOLS,
@@ -6537,11 +6626,11 @@ ${result.text}`;
6537
6626
  }
6538
6627
 
6539
6628
  // src/subagents/productVision/prompt.ts
6540
- var BASE_PROMPT3 = readAsset("subagents/productVision", "prompt.md");
6629
+ var BASE_PROMPT4 = readAsset("subagents/productVision", "prompt.md");
6541
6630
  function getProductVisionPrompt() {
6542
6631
  const specIndex = loadSpecIndex();
6543
6632
  const roadmapIndex = loadRoadmapIndex();
6544
- const parts = [BASE_PROMPT3, loadPlatformBrief()];
6633
+ const parts = [BASE_PROMPT4, loadPlatformBrief()];
6545
6634
  parts.push("<!-- cache_breakpoint -->");
6546
6635
  if (specIndex) {
6547
6636
  parts.push(specIndex);
@@ -6665,11 +6754,26 @@ var SANITY_CHECK_TOOLS = [
6665
6754
  },
6666
6755
  required: ["command"]
6667
6756
  }
6757
+ },
6758
+ // Appended last: tool order is part of the subagent's prompt-cache prefix.
6759
+ {
6760
+ name: "research",
6761
+ 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.",
6762
+ inputSchema: {
6763
+ type: "object",
6764
+ properties: {
6765
+ task: {
6766
+ type: "string",
6767
+ description: "What you need to find out, in natural language."
6768
+ }
6769
+ },
6770
+ required: ["task"]
6771
+ }
6668
6772
  }
6669
6773
  ];
6670
6774
 
6671
6775
  // src/subagents/codeSanityCheck/index.ts
6672
- var BASE_PROMPT4 = readAsset("subagents/codeSanityCheck", "prompt.md");
6776
+ var BASE_PROMPT5 = readAsset("subagents/codeSanityCheck", "prompt.md");
6673
6777
  var codeSanityCheckTool = {
6674
6778
  definition: {
6675
6779
  name: "codeSanityCheck",
@@ -6690,7 +6794,7 @@ var codeSanityCheckTool = {
6690
6794
  return "Error: code sanity check requires execution context";
6691
6795
  }
6692
6796
  const specIndex = loadSpecIndex();
6693
- const parts = [BASE_PROMPT4, loadPlatformBrief()];
6797
+ const parts = [BASE_PROMPT5, loadPlatformBrief()];
6694
6798
  parts.push("<!-- cache_breakpoint -->");
6695
6799
  if (specIndex) {
6696
6800
  parts.push(specIndex);
@@ -6706,6 +6810,16 @@ var codeSanityCheckTool = {
6706
6810
  ...deriveContext(context, toolCallId, onLog),
6707
6811
  subAgentMessages: sams
6708
6812
  } : context;
6813
+ if (name === "searchGoogle") {
6814
+ return executeSearchGoogle(
6815
+ { ...toolInput, fetchTopN: 5 },
6816
+ childCtx.onLog,
6817
+ "codeSanityCheck"
6818
+ );
6819
+ }
6820
+ if (name === "research") {
6821
+ return runResearch(String(toolInput.task ?? ""), childCtx);
6822
+ }
6709
6823
  return executeTool(name, toolInput, childCtx);
6710
6824
  },
6711
6825
  apiConfig: context.apiConfig,
@@ -6859,7 +6973,7 @@ function acquireSpecSyncLock() {
6859
6973
  }
6860
6974
 
6861
6975
  // src/subagents/specSync/index.ts
6862
- var BASE_PROMPT5 = readAsset("subagents/specSync", "prompt.md");
6976
+ var BASE_PROMPT6 = readAsset("subagents/specSync", "prompt.md");
6863
6977
  var MSFM_DOCS = `<mindstudio_flavored_markdown_spec_docs>
6864
6978
  ${readAsset(
6865
6979
  "prompt",
@@ -6898,7 +7012,7 @@ var specSyncTool = {
6898
7012
  }
6899
7013
  const specIndex = loadSpecIndex();
6900
7014
  const parts = [
6901
- BASE_PROMPT5,
7015
+ BASE_PROMPT6,
6902
7016
  loadPlatformBrief(),
6903
7017
  MSFM_DOCS,
6904
7018
  "<!-- cache_breakpoint -->"
@@ -7013,7 +7127,6 @@ var ALL_TOOLS = [
7013
7127
  confirmDestructiveActionTool,
7014
7128
  askMindStudioSdkTool,
7015
7129
  scrapeWebUrlTool,
7016
- searchGoogleTool,
7017
7130
  setProjectMetadataTool,
7018
7131
  designExpertTool,
7019
7132
  productVisionTool,
@@ -7052,7 +7165,8 @@ var ALL_TOOLS = [
7052
7165
  // Appended rather than grouped: position is part of the cache prefix, so a
7053
7166
  // new tool goes at the end to leave every existing session's prefix intact.
7054
7167
  loadSkillTool,
7055
- testJewelTool
7168
+ testJewelTool,
7169
+ researchTool
7056
7170
  ];
7057
7171
  var SUBAGENT_TOOL_NAMES = /* @__PURE__ */ new Set([
7058
7172
  "visualDesignExpert",
@@ -7061,7 +7175,8 @@ var SUBAGENT_TOOL_NAMES = /* @__PURE__ */ new Set([
7061
7175
  "copyEditor",
7062
7176
  "specSync",
7063
7177
  "runAutomatedBrowserTest",
7064
- "askMindStudioSdk"
7178
+ "askMindStudioSdk",
7179
+ "research"
7065
7180
  ]);
7066
7181
  function getToolDefinitions() {
7067
7182
  return ALL_TOOLS.map((t) => t.definition);