@mindstudio-ai/remy 0.1.4 → 0.1.6

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.
@@ -2,4 +2,6 @@ This is an automated action triggered by the user pressing "Build" in the editor
2
2
 
3
3
  The user has reviewed the spec and is ready to build. Build everything in one turn: methods, tables, interfaces, manifest updates, and scenarios, using the spec as the master plan.
4
4
 
5
- When code generation is complete, call `setProjectOnboardingState({ state: "onboardingFinished" })`.
5
+ When code generation is complete, verify your work: use `runScenario` to seed test data, then use `runMethod` to confirm a method works, then use `runAutomatedBrowserTest` to smoke-test the main UI flow. The dev database is a disposable snapshot, so don't worry about being destructive. Fix any errors before finishing.
6
+
7
+ When everything is working, call `setProjectOnboardingState({ state: "onboardingFinished" })`.
@@ -12,6 +12,17 @@ good on Dribbble, Behance, or Mobbin, it's not done.
12
12
  MindStudio apps are end-user products. The interface is the product. Users
13
13
  judge the entire app by how it looks and feels in the first 3 seconds.
14
14
 
15
+ ## Design System from the Spec
16
+
17
+ The spec file `src/interfaces/@brand/visual.md` may contain `typography` and
18
+ `colors` YAML blocks that define the app's fonts and color palette. When
19
+ these are present, always use them. Load fonts from the URLs in the `fonts`
20
+ section. Set up a lightweight theme layer early (CSS variables or a small
21
+ tokens file) so colors and type styles are defined once and referenced
22
+ everywhere. This makes the design easy to update later without hunting
23
+ through components. Keep it simple: a handful of CSS variables for colors
24
+ and a few reusable text style classes or utilities for typography.
25
+
15
26
  ## Be Distinctive
16
27
 
17
28
  AI-generated interfaces tend to converge on the same generic look: safe
package/dist/headless.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/headless.ts
2
2
  import { createInterface } from "readline";
3
- import fs14 from "fs";
4
- import path7 from "path";
3
+ import fs15 from "fs";
4
+ import path8 from "path";
5
5
 
6
6
  // src/config.ts
7
7
  import fs2 from "fs";
@@ -940,8 +940,8 @@ var promptUserTool = {
940
940
  },
941
941
  type: {
942
942
  type: "string",
943
- enum: ["select", "text", "file", "color"],
944
- description: 'select: pick from options (or options + free-form "other"). text: free-form input. file: file/image upload, returns CDN URL(s) that can be referenced directly or curled onto disk. color: color picker (returns hex).'
943
+ enum: ["select", "checklist", "text", "file", "color"],
944
+ description: "select: pick one from a list. checklist: pick one or more from a list. text: free-form input. file: file/image upload, returns CDN URL(s) that can be referenced directly or curled onto disk. color: color picker (returns hex)."
945
945
  },
946
946
  helpText: {
947
947
  type: "string",
@@ -972,15 +972,15 @@ var promptUserTool = {
972
972
  }
973
973
  ]
974
974
  },
975
- description: "Options for select type. Each can be a string or { label, description }."
975
+ description: "Options for select and checklist types. Each can be a string or { label, description }."
976
976
  },
977
977
  multiple: {
978
978
  type: "boolean",
979
- description: "For select: allow picking multiple options (returns array). For file: allow multiple uploads (returns array of URLs). Defaults to false."
979
+ description: "For file type: allow multiple uploads (returns array of URLs). Defaults to false."
980
980
  },
981
981
  allowOther: {
982
982
  type: "boolean",
983
- description: 'For select type: adds an "Other" option with a free-form text input. Defaults to false.'
983
+ description: 'For select and checklist types: adds an "Other" option that lets the user type a custom answer. Use this instead of adding a separate follow-up text field for custom input. Defaults to false.'
984
984
  },
985
985
  format: {
986
986
  type: "string",
@@ -1032,11 +1032,11 @@ var promptUserTool = {
1032
1032
  const questions = input.questions;
1033
1033
  const lines = questions.map((q) => {
1034
1034
  let line = `- ${q.question}`;
1035
- if (q.type === "select") {
1035
+ if (q.type === "select" || q.type === "checklist") {
1036
1036
  const opts = (q.options || []).map(
1037
1037
  (o) => typeof o === "string" ? o : o.label
1038
1038
  );
1039
- line += q.multiple ? ` (pick one or more: ${opts.join(" / ")})` : ` (${opts.join(" / ")})`;
1039
+ line += q.type === "checklist" ? ` (pick one or more: ${opts.join(" / ")})` : ` (${opts.join(" / ")})`;
1040
1040
  } else if (q.type === "file") {
1041
1041
  line += " (upload file)";
1042
1042
  } else if (q.type === "color") {
@@ -1251,14 +1251,26 @@ var writeFileTool = {
1251
1251
  required: ["path", "content"]
1252
1252
  }
1253
1253
  },
1254
- streaming: {
1255
- transform: async (partial) => {
1256
- const oldContent = await fs10.readFile(partial.path, "utf-8").catch(() => "");
1257
- const lineCount = partial.content.split("\n").length;
1258
- return `Writing ${partial.path} (${lineCount} lines)
1254
+ streaming: /* @__PURE__ */ (() => {
1255
+ let lastLineCount = 0;
1256
+ let lastPath = "";
1257
+ return {
1258
+ transform: async (partial) => {
1259
+ if (partial.path !== lastPath) {
1260
+ lastLineCount = 0;
1261
+ lastPath = partial.path;
1262
+ }
1263
+ const lines = partial.content.split("\n");
1264
+ if (lines.length <= lastLineCount) {
1265
+ return null;
1266
+ }
1267
+ lastLineCount = lines.length;
1268
+ const oldContent = await fs10.readFile(partial.path, "utf-8").catch(() => "");
1269
+ return `Writing ${partial.path} (${lines.length} lines)
1259
1270
  ${unifiedDiff(partial.path, oldContent, partial.content)}`;
1260
- }
1261
- },
1271
+ }
1272
+ };
1273
+ })(),
1262
1274
  async execute(input) {
1263
1275
  try {
1264
1276
  await fs10.mkdir(path6.dirname(input.path), { recursive: true });
@@ -1794,6 +1806,312 @@ var askMindStudioSdkTool = {
1794
1806
  }
1795
1807
  };
1796
1808
 
1809
+ // src/tools/code/runScenario.ts
1810
+ var runScenarioTool = {
1811
+ definition: {
1812
+ name: "runScenario",
1813
+ description: "Run a scenario to seed the dev database with test data. Truncates all tables first, then executes the seed function and impersonates the scenario roles. Blocks until complete. Scenario IDs are defined in mindstudio.json. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for details.",
1814
+ inputSchema: {
1815
+ type: "object",
1816
+ properties: {
1817
+ scenarioId: {
1818
+ type: "string",
1819
+ description: "The scenario ID from mindstudio.json."
1820
+ }
1821
+ },
1822
+ required: ["scenarioId"]
1823
+ }
1824
+ },
1825
+ async execute() {
1826
+ return "ok";
1827
+ }
1828
+ };
1829
+
1830
+ // src/tools/code/runMethod.ts
1831
+ var runMethodTool = {
1832
+ definition: {
1833
+ name: "runMethod",
1834
+ description: "Run a method in the dev environment and return the result. Use for testing methods after writing or modifying them. Returns output, captured console output, errors with stack traces, and duration. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for more details.",
1835
+ inputSchema: {
1836
+ type: "object",
1837
+ properties: {
1838
+ method: {
1839
+ type: "string",
1840
+ description: 'The method export name (camelCase, e.g. "listHaikus").'
1841
+ },
1842
+ input: {
1843
+ type: "object",
1844
+ description: "The input payload to pass to the method. Omit for methods that take no input."
1845
+ }
1846
+ },
1847
+ required: ["method"]
1848
+ }
1849
+ },
1850
+ async execute() {
1851
+ return "ok";
1852
+ }
1853
+ };
1854
+
1855
+ // src/tools/code/screenshot.ts
1856
+ var screenshotTool = {
1857
+ definition: {
1858
+ name: "screenshot",
1859
+ description: "Capture a screenshot of the app preview. Returns a CDN URL with dimensions. Useful for visually checking the current state after UI changes or when debugging layout issues.",
1860
+ inputSchema: {
1861
+ type: "object",
1862
+ properties: {}
1863
+ }
1864
+ },
1865
+ async execute() {
1866
+ return "ok";
1867
+ }
1868
+ };
1869
+
1870
+ // src/subagents/runner.ts
1871
+ async function runSubAgent(config) {
1872
+ const {
1873
+ system,
1874
+ task,
1875
+ tools,
1876
+ externalTools,
1877
+ executeTool: executeTool2,
1878
+ apiConfig,
1879
+ model,
1880
+ signal,
1881
+ parentToolId,
1882
+ onEvent,
1883
+ resolveExternalTool
1884
+ } = config;
1885
+ const emit2 = (e) => {
1886
+ onEvent({ ...e, parentToolId });
1887
+ };
1888
+ const messages = [{ role: "user", content: task }];
1889
+ while (true) {
1890
+ if (signal?.aborted) {
1891
+ return "Error: cancelled";
1892
+ }
1893
+ let assistantText = "";
1894
+ const toolCalls = [];
1895
+ let stopReason = "end_turn";
1896
+ try {
1897
+ for await (const event of streamChat({
1898
+ ...apiConfig,
1899
+ model,
1900
+ system,
1901
+ messages,
1902
+ tools,
1903
+ signal
1904
+ })) {
1905
+ if (signal?.aborted) {
1906
+ break;
1907
+ }
1908
+ switch (event.type) {
1909
+ case "text":
1910
+ assistantText += event.text;
1911
+ emit2({ type: "text", text: event.text });
1912
+ break;
1913
+ case "thinking":
1914
+ emit2({ type: "thinking", text: event.text });
1915
+ break;
1916
+ case "tool_use":
1917
+ toolCalls.push({
1918
+ id: event.id,
1919
+ name: event.name,
1920
+ input: event.input
1921
+ });
1922
+ emit2({
1923
+ type: "tool_start",
1924
+ id: event.id,
1925
+ name: event.name,
1926
+ input: event.input
1927
+ });
1928
+ break;
1929
+ case "done":
1930
+ stopReason = event.stopReason;
1931
+ break;
1932
+ case "error":
1933
+ return `Error: ${event.error}`;
1934
+ }
1935
+ }
1936
+ } catch (err) {
1937
+ if (!signal?.aborted) {
1938
+ throw err;
1939
+ }
1940
+ }
1941
+ if (signal?.aborted) {
1942
+ return "Error: cancelled";
1943
+ }
1944
+ messages.push({
1945
+ role: "assistant",
1946
+ content: assistantText,
1947
+ toolCalls: toolCalls.length > 0 ? toolCalls : void 0
1948
+ });
1949
+ if (stopReason !== "tool_use" || toolCalls.length === 0) {
1950
+ return assistantText;
1951
+ }
1952
+ log.info("Sub-agent executing tools", {
1953
+ parentToolId,
1954
+ count: toolCalls.length,
1955
+ tools: toolCalls.map((tc) => tc.name)
1956
+ });
1957
+ const results = await Promise.all(
1958
+ toolCalls.map(async (tc) => {
1959
+ if (signal?.aborted) {
1960
+ return { id: tc.id, result: "Error: cancelled", isError: true };
1961
+ }
1962
+ try {
1963
+ let result;
1964
+ if (externalTools.has(tc.name) && resolveExternalTool) {
1965
+ result = await resolveExternalTool(tc.id, tc.name, tc.input);
1966
+ } else {
1967
+ result = await executeTool2(tc.name, tc.input);
1968
+ }
1969
+ const isError = result.startsWith("Error");
1970
+ emit2({
1971
+ type: "tool_done",
1972
+ id: tc.id,
1973
+ name: tc.name,
1974
+ result,
1975
+ isError
1976
+ });
1977
+ return { id: tc.id, result, isError };
1978
+ } catch (err) {
1979
+ const errorMsg = `Error: ${err.message}`;
1980
+ emit2({
1981
+ type: "tool_done",
1982
+ id: tc.id,
1983
+ name: tc.name,
1984
+ result: errorMsg,
1985
+ isError: true
1986
+ });
1987
+ return { id: tc.id, result: errorMsg, isError: true };
1988
+ }
1989
+ })
1990
+ );
1991
+ for (const r of results) {
1992
+ messages.push({
1993
+ role: "user",
1994
+ content: r.result,
1995
+ toolCallId: r.id,
1996
+ isToolError: r.isError
1997
+ });
1998
+ }
1999
+ }
2000
+ }
2001
+
2002
+ // src/subagents/browserAutomation/tools.ts
2003
+ var BROWSER_TOOLS = [
2004
+ {
2005
+ name: "browserCommand",
2006
+ description: "Interact with the app's live preview by sending browser commands. Commands execute sequentially with an animated cursor. Always start with a snapshot to see the current state and get ref identifiers. The result includes a snapshot field with the final page state after all steps complete. On error, the failing step has an error field and execution stops. Timeout: 120s.",
2007
+ inputSchema: {
2008
+ type: "object",
2009
+ properties: {
2010
+ steps: {
2011
+ type: "array",
2012
+ items: {
2013
+ type: "object",
2014
+ properties: {
2015
+ command: {
2016
+ type: "string",
2017
+ enum: ["snapshot", "click", "type", "wait", "evaluate"],
2018
+ 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). wait: wait for an element to appear (polls 100ms, waits for network). evaluate: run JS in the page."
2019
+ },
2020
+ ref: {
2021
+ type: "string",
2022
+ description: "Element ref from the last snapshot (most reliable targeting)."
2023
+ },
2024
+ text: {
2025
+ type: "string",
2026
+ description: "For click/wait: match by accessible name or visible text. For type: the text to type."
2027
+ },
2028
+ role: {
2029
+ type: "string",
2030
+ description: "ARIA role to match (used with text for role+text targeting)."
2031
+ },
2032
+ label: {
2033
+ type: "string",
2034
+ description: "Find an input by its associated label text."
2035
+ },
2036
+ selector: {
2037
+ type: "string",
2038
+ description: "CSS selector fallback (last resort)."
2039
+ },
2040
+ clear: {
2041
+ type: "boolean",
2042
+ description: "For type: clear the field before typing."
2043
+ },
2044
+ timeout: {
2045
+ type: "number",
2046
+ description: "For wait: timeout in ms (default 5000)."
2047
+ },
2048
+ script: {
2049
+ type: "string",
2050
+ description: "For evaluate: JavaScript to run in the page."
2051
+ }
2052
+ },
2053
+ required: ["command"]
2054
+ }
2055
+ }
2056
+ },
2057
+ required: ["steps"]
2058
+ }
2059
+ },
2060
+ {
2061
+ name: "screenshot",
2062
+ description: "Capture a screenshot of the current page. Returns a CDN URL with dimensions.",
2063
+ inputSchema: {
2064
+ type: "object",
2065
+ properties: {}
2066
+ }
2067
+ }
2068
+ ];
2069
+ var BROWSER_EXTERNAL_TOOLS = /* @__PURE__ */ new Set(["browserCommand", "screenshot"]);
2070
+
2071
+ // src/subagents/browserAutomation/prompt.ts
2072
+ import fs13 from "fs";
2073
+ import path7 from "path";
2074
+ var base = import.meta.dirname ?? path7.dirname(new URL(import.meta.url).pathname);
2075
+ var local = path7.join(base, "prompt.md");
2076
+ var PROMPT_PATH = fs13.existsSync(local) ? local : path7.join(base, "subagents", "browserAutomation", "prompt.md");
2077
+ var BROWSER_AUTOMATION_PROMPT = fs13.readFileSync(PROMPT_PATH, "utf-8").trim();
2078
+
2079
+ // src/subagents/browserAutomation/index.ts
2080
+ var browserAutomationTool = {
2081
+ definition: {
2082
+ name: "runAutomatedBrowserTest",
2083
+ description: "Run an automated browser test against the live preview. The test agent always starts on the main page, so include navigation instructions if the test involves a sub-page. The browser uses the current user roles and dev database state, so run a scenario first if you need specific data or roles. Use after writing or modifying frontend code, to reproduce user-reported issues, or to test end-to-end flows.",
2084
+ inputSchema: {
2085
+ type: "object",
2086
+ properties: {
2087
+ task: {
2088
+ type: "string",
2089
+ description: "What to test, in natural language. Include how to navigate to the relevant page and what data/roles to expect."
2090
+ }
2091
+ },
2092
+ required: ["task"]
2093
+ }
2094
+ },
2095
+ async execute(input, context) {
2096
+ if (!context) {
2097
+ return "Error: browser automation requires execution context (only available in headless mode)";
2098
+ }
2099
+ return runSubAgent({
2100
+ system: BROWSER_AUTOMATION_PROMPT,
2101
+ task: input.task,
2102
+ tools: BROWSER_TOOLS,
2103
+ externalTools: BROWSER_EXTERNAL_TOOLS,
2104
+ executeTool: async () => "Error: no local tools in browser automation",
2105
+ apiConfig: context.apiConfig,
2106
+ model: context.model,
2107
+ signal: context.signal,
2108
+ parentToolId: context.toolCallId,
2109
+ onEvent: context.onEvent,
2110
+ resolveExternalTool: context.resolveExternalTool
2111
+ });
2112
+ }
2113
+ };
2114
+
1797
2115
  // src/tools/index.ts
1798
2116
  function getSpecTools() {
1799
2117
  return [readSpecTool, writeSpecTool, editSpecTool, listSpecFilesTool];
@@ -1808,7 +2126,11 @@ function getCodeTools() {
1808
2126
  globTool,
1809
2127
  listDirTool,
1810
2128
  editsFinishedTool,
1811
- askMindStudioSdkTool
2129
+ askMindStudioSdkTool,
2130
+ runScenarioTool,
2131
+ runMethodTool,
2132
+ screenshotTool,
2133
+ browserAutomationTool
1812
2134
  ];
1813
2135
  if (isLspConfigured()) {
1814
2136
  tools.push(lspDiagnosticsTool, restartProcessTool);
@@ -1857,20 +2179,20 @@ function getToolByName(name) {
1857
2179
  ];
1858
2180
  return allTools.find((t) => t.definition.name === name);
1859
2181
  }
1860
- function executeTool(name, input) {
2182
+ function executeTool(name, input, context) {
1861
2183
  const tool = getToolByName(name);
1862
2184
  if (!tool) {
1863
2185
  return Promise.resolve(`Error: Unknown tool "${name}"`);
1864
2186
  }
1865
- return tool.execute(input);
2187
+ return tool.execute(input, context);
1866
2188
  }
1867
2189
 
1868
2190
  // src/session.ts
1869
- import fs13 from "fs";
2191
+ import fs14 from "fs";
1870
2192
  var SESSION_FILE = ".remy-session.json";
1871
2193
  function loadSession(state) {
1872
2194
  try {
1873
- const raw = fs13.readFileSync(SESSION_FILE, "utf-8");
2195
+ const raw = fs14.readFileSync(SESSION_FILE, "utf-8");
1874
2196
  const data = JSON.parse(raw);
1875
2197
  if (Array.isArray(data.messages) && data.messages.length > 0) {
1876
2198
  state.messages = sanitizeMessages(data.messages);
@@ -1912,7 +2234,7 @@ function sanitizeMessages(messages) {
1912
2234
  }
1913
2235
  function saveSession(state) {
1914
2236
  try {
1915
- fs13.writeFileSync(
2237
+ fs14.writeFileSync(
1916
2238
  SESSION_FILE,
1917
2239
  JSON.stringify({ messages: state.messages }, null, 2),
1918
2240
  "utf-8"
@@ -1923,7 +2245,7 @@ function saveSession(state) {
1923
2245
  function clearSession(state) {
1924
2246
  state.messages = [];
1925
2247
  try {
1926
- fs13.unlinkSync(SESSION_FILE);
2248
+ fs14.unlinkSync(SESSION_FILE);
1927
2249
  } catch {
1928
2250
  }
1929
2251
  }
@@ -2099,7 +2421,11 @@ var EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
2099
2421
  "presentSyncPlan",
2100
2422
  "presentPublishPlan",
2101
2423
  "presentPlan",
2102
- "confirmDestructiveAction"
2424
+ "confirmDestructiveAction",
2425
+ "runScenario",
2426
+ "runMethod",
2427
+ "browserCommand",
2428
+ "screenshot"
2103
2429
  ]);
2104
2430
  function createAgentState() {
2105
2431
  return { messages: [] };
@@ -2205,6 +2531,9 @@ async function runTurn(params) {
2205
2531
  }
2206
2532
  if (transform) {
2207
2533
  const result = await transform(partial);
2534
+ if (result === null) {
2535
+ return;
2536
+ }
2208
2537
  log.debug("Streaming content tool: emitting tool_input_delta", {
2209
2538
  id,
2210
2539
  name,
@@ -2276,7 +2605,7 @@ async function runTurn(params) {
2276
2605
  const tool = getToolByName(event.name);
2277
2606
  const wasStreamed = acc?.started ?? false;
2278
2607
  const isInputStreaming = !!tool?.streaming?.partialInput;
2279
- log.debug("Received tool_use", {
2608
+ log.info("Tool call received", {
2280
2609
  id: event.id,
2281
2610
  name: event.name,
2282
2611
  wasStreamed,
@@ -2346,16 +2675,23 @@ async function runTurn(params) {
2346
2675
  let result;
2347
2676
  if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
2348
2677
  saveSession(state);
2349
- log.debug("Waiting for external tool result", {
2678
+ log.info("Waiting for external tool result", {
2350
2679
  name: tc.name,
2351
2680
  id: tc.id
2352
2681
  });
2353
2682
  result = await resolveExternalTool(tc.id, tc.name, tc.input);
2354
2683
  } else {
2355
- result = await executeTool(tc.name, tc.input);
2684
+ result = await executeTool(tc.name, tc.input, {
2685
+ apiConfig,
2686
+ model,
2687
+ signal,
2688
+ onEvent,
2689
+ resolveExternalTool,
2690
+ toolCallId: tc.id
2691
+ });
2356
2692
  }
2357
2693
  const isError = result.startsWith("Error");
2358
- log.debug("Tool completed", {
2694
+ log.info("Tool completed", {
2359
2695
  name: tc.name,
2360
2696
  elapsed: `${Date.now() - toolStart}ms`,
2361
2697
  isError,
@@ -2399,10 +2735,10 @@ async function runTurn(params) {
2399
2735
  }
2400
2736
 
2401
2737
  // src/headless.ts
2402
- var BASE_DIR = import.meta.dirname ?? path7.dirname(new URL(import.meta.url).pathname);
2403
- var ACTIONS_DIR = path7.join(BASE_DIR, "actions");
2738
+ var BASE_DIR = import.meta.dirname ?? path8.dirname(new URL(import.meta.url).pathname);
2739
+ var ACTIONS_DIR = path8.join(BASE_DIR, "actions");
2404
2740
  function loadActionPrompt(name) {
2405
- return fs14.readFileSync(path7.join(ACTIONS_DIR, `${name}.md`), "utf-8").trim();
2741
+ return fs15.readFileSync(path8.join(ACTIONS_DIR, `${name}.md`), "utf-8").trim();
2406
2742
  }
2407
2743
  function emit(event, data) {
2408
2744
  process.stdout.write(JSON.stringify({ event, ...data }) + "\n");
@@ -2435,20 +2771,32 @@ async function startHeadless(opts = {}) {
2435
2771
  function onEvent(e) {
2436
2772
  switch (e.type) {
2437
2773
  case "text":
2438
- emit("text", { text: e.text });
2774
+ emit("text", {
2775
+ text: e.text,
2776
+ ...e.parentToolId && { parentToolId: e.parentToolId }
2777
+ });
2439
2778
  break;
2440
2779
  case "thinking":
2441
- emit("thinking", { text: e.text });
2780
+ emit("thinking", {
2781
+ text: e.text,
2782
+ ...e.parentToolId && { parentToolId: e.parentToolId }
2783
+ });
2442
2784
  break;
2443
2785
  case "tool_input_delta":
2444
- emit("tool_input_delta", { id: e.id, name: e.name, result: e.result });
2786
+ emit("tool_input_delta", {
2787
+ id: e.id,
2788
+ name: e.name,
2789
+ result: e.result,
2790
+ ...e.parentToolId && { parentToolId: e.parentToolId }
2791
+ });
2445
2792
  break;
2446
2793
  case "tool_start":
2447
2794
  emit("tool_start", {
2448
2795
  id: e.id,
2449
2796
  name: e.name,
2450
2797
  input: e.input,
2451
- ...e.partial && { partial: true }
2798
+ ...e.partial && { partial: true },
2799
+ ...e.parentToolId && { parentToolId: e.parentToolId }
2452
2800
  });
2453
2801
  break;
2454
2802
  case "tool_done":
@@ -2456,7 +2804,8 @@ async function startHeadless(opts = {}) {
2456
2804
  id: e.id,
2457
2805
  name: e.name,
2458
2806
  result: e.result,
2459
- isError: e.isError
2807
+ isError: e.isError,
2808
+ ...e.parentToolId && { parentToolId: e.parentToolId }
2460
2809
  });
2461
2810
  break;
2462
2811
  case "turn_started":