@mindstudio-ai/remy 0.1.3 → 0.1.5

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/index.js CHANGED
@@ -698,8 +698,8 @@ var init_promptUser = __esm({
698
698
  },
699
699
  type: {
700
700
  type: "string",
701
- enum: ["select", "text", "file", "color"],
702
- 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).'
701
+ enum: ["select", "checklist", "text", "file", "color"],
702
+ 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)."
703
703
  },
704
704
  helpText: {
705
705
  type: "string",
@@ -730,15 +730,15 @@ var init_promptUser = __esm({
730
730
  }
731
731
  ]
732
732
  },
733
- description: "Options for select type. Each can be a string or { label, description }."
733
+ description: "Options for select and checklist types. Each can be a string or { label, description }."
734
734
  },
735
735
  multiple: {
736
736
  type: "boolean",
737
- description: "For select: allow picking multiple options (returns array). For file: allow multiple uploads (returns array of URLs). Defaults to false."
737
+ description: "For file type: allow multiple uploads (returns array of URLs). Defaults to false."
738
738
  },
739
739
  allowOther: {
740
740
  type: "boolean",
741
- description: 'For select type: adds an "Other" option with a free-form text input. Defaults to false.'
741
+ 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.'
742
742
  },
743
743
  format: {
744
744
  type: "string",
@@ -790,11 +790,11 @@ var init_promptUser = __esm({
790
790
  const questions = input.questions;
791
791
  const lines = questions.map((q) => {
792
792
  let line = `- ${q.question}`;
793
- if (q.type === "select") {
793
+ if (q.type === "select" || q.type === "checklist") {
794
794
  const opts = (q.options || []).map(
795
795
  (o) => typeof o === "string" ? o : o.label
796
796
  );
797
- line += q.multiple ? ` (pick one or more: ${opts.join(" / ")})` : ` (${opts.join(" / ")})`;
797
+ line += q.type === "checklist" ? ` (pick one or more: ${opts.join(" / ")})` : ` (${opts.join(" / ")})`;
798
798
  } else if (q.type === "file") {
799
799
  line += " (upload file)";
800
800
  } else if (q.type === "color") {
@@ -1052,14 +1052,26 @@ var init_writeFile = __esm({
1052
1052
  required: ["path", "content"]
1053
1053
  }
1054
1054
  },
1055
- streaming: {
1056
- transform: async (partial) => {
1057
- const oldContent = await fs7.readFile(partial.path, "utf-8").catch(() => "");
1058
- const lineCount = partial.content.split("\n").length;
1059
- return `Writing ${partial.path} (${lineCount} lines)
1055
+ streaming: /* @__PURE__ */ (() => {
1056
+ let lastLineCount = 0;
1057
+ let lastPath = "";
1058
+ return {
1059
+ transform: async (partial) => {
1060
+ if (partial.path !== lastPath) {
1061
+ lastLineCount = 0;
1062
+ lastPath = partial.path;
1063
+ }
1064
+ const lines = partial.content.split("\n");
1065
+ if (lines.length <= lastLineCount) {
1066
+ return null;
1067
+ }
1068
+ lastLineCount = lines.length;
1069
+ const oldContent = await fs7.readFile(partial.path, "utf-8").catch(() => "");
1070
+ return `Writing ${partial.path} (${lines.length} lines)
1060
1071
  ${unifiedDiff(partial.path, oldContent, partial.content)}`;
1061
- }
1062
- },
1072
+ }
1073
+ };
1074
+ })(),
1063
1075
  async execute(input) {
1064
1076
  try {
1065
1077
  await fs7.mkdir(path3.dirname(input.path), { recursive: true });
@@ -1700,6 +1712,359 @@ var init_askMindStudioSdk = __esm({
1700
1712
  }
1701
1713
  });
1702
1714
 
1715
+ // src/tools/code/runScenario.ts
1716
+ var runScenarioTool;
1717
+ var init_runScenario = __esm({
1718
+ "src/tools/code/runScenario.ts"() {
1719
+ "use strict";
1720
+ runScenarioTool = {
1721
+ definition: {
1722
+ name: "runScenario",
1723
+ 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.",
1724
+ inputSchema: {
1725
+ type: "object",
1726
+ properties: {
1727
+ scenarioId: {
1728
+ type: "string",
1729
+ description: "The scenario ID from mindstudio.json."
1730
+ }
1731
+ },
1732
+ required: ["scenarioId"]
1733
+ }
1734
+ },
1735
+ async execute() {
1736
+ return "ok";
1737
+ }
1738
+ };
1739
+ }
1740
+ });
1741
+
1742
+ // src/tools/code/runMethod.ts
1743
+ var runMethodTool;
1744
+ var init_runMethod = __esm({
1745
+ "src/tools/code/runMethod.ts"() {
1746
+ "use strict";
1747
+ runMethodTool = {
1748
+ definition: {
1749
+ name: "runMethod",
1750
+ 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.",
1751
+ inputSchema: {
1752
+ type: "object",
1753
+ properties: {
1754
+ method: {
1755
+ type: "string",
1756
+ description: 'The method export name (camelCase, e.g. "listHaikus").'
1757
+ },
1758
+ input: {
1759
+ type: "object",
1760
+ description: "The input payload to pass to the method. Omit for methods that take no input."
1761
+ }
1762
+ },
1763
+ required: ["method"]
1764
+ }
1765
+ },
1766
+ async execute() {
1767
+ return "ok";
1768
+ }
1769
+ };
1770
+ }
1771
+ });
1772
+
1773
+ // src/tools/code/screenshot.ts
1774
+ var screenshotTool;
1775
+ var init_screenshot = __esm({
1776
+ "src/tools/code/screenshot.ts"() {
1777
+ "use strict";
1778
+ screenshotTool = {
1779
+ definition: {
1780
+ name: "screenshot",
1781
+ 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.",
1782
+ inputSchema: {
1783
+ type: "object",
1784
+ properties: {}
1785
+ }
1786
+ },
1787
+ async execute() {
1788
+ return "ok";
1789
+ }
1790
+ };
1791
+ }
1792
+ });
1793
+
1794
+ // src/subagents/runner.ts
1795
+ async function runSubAgent(config) {
1796
+ const {
1797
+ system,
1798
+ task,
1799
+ tools,
1800
+ externalTools,
1801
+ executeTool: executeTool2,
1802
+ apiConfig,
1803
+ model,
1804
+ signal,
1805
+ parentToolId,
1806
+ onEvent,
1807
+ resolveExternalTool
1808
+ } = config;
1809
+ const emit2 = (e) => {
1810
+ onEvent({ ...e, parentToolId });
1811
+ };
1812
+ const messages = [{ role: "user", content: task }];
1813
+ while (true) {
1814
+ if (signal?.aborted) {
1815
+ return "Error: cancelled";
1816
+ }
1817
+ let assistantText = "";
1818
+ const toolCalls = [];
1819
+ let stopReason = "end_turn";
1820
+ try {
1821
+ for await (const event of streamChat({
1822
+ ...apiConfig,
1823
+ model,
1824
+ system,
1825
+ messages,
1826
+ tools,
1827
+ signal
1828
+ })) {
1829
+ if (signal?.aborted) {
1830
+ break;
1831
+ }
1832
+ switch (event.type) {
1833
+ case "text":
1834
+ assistantText += event.text;
1835
+ emit2({ type: "text", text: event.text });
1836
+ break;
1837
+ case "thinking":
1838
+ emit2({ type: "thinking", text: event.text });
1839
+ break;
1840
+ case "tool_use":
1841
+ toolCalls.push({
1842
+ id: event.id,
1843
+ name: event.name,
1844
+ input: event.input
1845
+ });
1846
+ emit2({
1847
+ type: "tool_start",
1848
+ id: event.id,
1849
+ name: event.name,
1850
+ input: event.input
1851
+ });
1852
+ break;
1853
+ case "done":
1854
+ stopReason = event.stopReason;
1855
+ break;
1856
+ case "error":
1857
+ return `Error: ${event.error}`;
1858
+ }
1859
+ }
1860
+ } catch (err) {
1861
+ if (!signal?.aborted) {
1862
+ throw err;
1863
+ }
1864
+ }
1865
+ if (signal?.aborted) {
1866
+ return "Error: cancelled";
1867
+ }
1868
+ messages.push({
1869
+ role: "assistant",
1870
+ content: assistantText,
1871
+ toolCalls: toolCalls.length > 0 ? toolCalls : void 0
1872
+ });
1873
+ if (stopReason !== "tool_use" || toolCalls.length === 0) {
1874
+ return assistantText;
1875
+ }
1876
+ log.info("Sub-agent executing tools", {
1877
+ parentToolId,
1878
+ count: toolCalls.length,
1879
+ tools: toolCalls.map((tc) => tc.name)
1880
+ });
1881
+ const results = await Promise.all(
1882
+ toolCalls.map(async (tc) => {
1883
+ if (signal?.aborted) {
1884
+ return { id: tc.id, result: "Error: cancelled", isError: true };
1885
+ }
1886
+ try {
1887
+ let result;
1888
+ if (externalTools.has(tc.name) && resolveExternalTool) {
1889
+ result = await resolveExternalTool(tc.id, tc.name, tc.input);
1890
+ } else {
1891
+ result = await executeTool2(tc.name, tc.input);
1892
+ }
1893
+ const isError = result.startsWith("Error");
1894
+ emit2({
1895
+ type: "tool_done",
1896
+ id: tc.id,
1897
+ name: tc.name,
1898
+ result,
1899
+ isError
1900
+ });
1901
+ return { id: tc.id, result, isError };
1902
+ } catch (err) {
1903
+ const errorMsg = `Error: ${err.message}`;
1904
+ emit2({
1905
+ type: "tool_done",
1906
+ id: tc.id,
1907
+ name: tc.name,
1908
+ result: errorMsg,
1909
+ isError: true
1910
+ });
1911
+ return { id: tc.id, result: errorMsg, isError: true };
1912
+ }
1913
+ })
1914
+ );
1915
+ for (const r of results) {
1916
+ messages.push({
1917
+ role: "user",
1918
+ content: r.result,
1919
+ toolCallId: r.id,
1920
+ isToolError: r.isError
1921
+ });
1922
+ }
1923
+ }
1924
+ }
1925
+ var init_runner = __esm({
1926
+ "src/subagents/runner.ts"() {
1927
+ "use strict";
1928
+ init_api();
1929
+ init_logger();
1930
+ }
1931
+ });
1932
+
1933
+ // src/subagents/browserAutomation/tools.ts
1934
+ var BROWSER_TOOLS, BROWSER_EXTERNAL_TOOLS;
1935
+ var init_tools = __esm({
1936
+ "src/subagents/browserAutomation/tools.ts"() {
1937
+ "use strict";
1938
+ BROWSER_TOOLS = [
1939
+ {
1940
+ name: "browserCommand",
1941
+ 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.",
1942
+ inputSchema: {
1943
+ type: "object",
1944
+ properties: {
1945
+ steps: {
1946
+ type: "array",
1947
+ items: {
1948
+ type: "object",
1949
+ properties: {
1950
+ command: {
1951
+ type: "string",
1952
+ enum: ["snapshot", "click", "type", "wait", "evaluate"],
1953
+ 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."
1954
+ },
1955
+ ref: {
1956
+ type: "string",
1957
+ description: "Element ref from the last snapshot (most reliable targeting)."
1958
+ },
1959
+ text: {
1960
+ type: "string",
1961
+ description: "For click/wait: match by accessible name or visible text. For type: the text to type."
1962
+ },
1963
+ role: {
1964
+ type: "string",
1965
+ description: "ARIA role to match (used with text for role+text targeting)."
1966
+ },
1967
+ label: {
1968
+ type: "string",
1969
+ description: "Find an input by its associated label text."
1970
+ },
1971
+ selector: {
1972
+ type: "string",
1973
+ description: "CSS selector fallback (last resort)."
1974
+ },
1975
+ clear: {
1976
+ type: "boolean",
1977
+ description: "For type: clear the field before typing."
1978
+ },
1979
+ timeout: {
1980
+ type: "number",
1981
+ description: "For wait: timeout in ms (default 5000)."
1982
+ },
1983
+ script: {
1984
+ type: "string",
1985
+ description: "For evaluate: JavaScript to run in the page."
1986
+ }
1987
+ },
1988
+ required: ["command"]
1989
+ }
1990
+ }
1991
+ },
1992
+ required: ["steps"]
1993
+ }
1994
+ },
1995
+ {
1996
+ name: "screenshot",
1997
+ description: "Capture a screenshot of the current page. Returns a CDN URL with dimensions.",
1998
+ inputSchema: {
1999
+ type: "object",
2000
+ properties: {}
2001
+ }
2002
+ }
2003
+ ];
2004
+ BROWSER_EXTERNAL_TOOLS = /* @__PURE__ */ new Set(["browserCommand", "screenshot"]);
2005
+ }
2006
+ });
2007
+
2008
+ // src/subagents/browserAutomation/prompt.ts
2009
+ import fs10 from "fs";
2010
+ import path4 from "path";
2011
+ var PROMPT_PATH, BROWSER_AUTOMATION_PROMPT;
2012
+ var init_prompt = __esm({
2013
+ "src/subagents/browserAutomation/prompt.ts"() {
2014
+ "use strict";
2015
+ PROMPT_PATH = path4.join(
2016
+ import.meta.dirname ?? path4.dirname(new URL(import.meta.url).pathname),
2017
+ "prompt.md"
2018
+ );
2019
+ BROWSER_AUTOMATION_PROMPT = fs10.readFileSync(PROMPT_PATH, "utf-8").trim();
2020
+ }
2021
+ });
2022
+
2023
+ // src/subagents/browserAutomation/index.ts
2024
+ var browserAutomationTool;
2025
+ var init_browserAutomation = __esm({
2026
+ "src/subagents/browserAutomation/index.ts"() {
2027
+ "use strict";
2028
+ init_runner();
2029
+ init_tools();
2030
+ init_prompt();
2031
+ browserAutomationTool = {
2032
+ definition: {
2033
+ name: "runAutomatedBrowserTest",
2034
+ 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.",
2035
+ inputSchema: {
2036
+ type: "object",
2037
+ properties: {
2038
+ task: {
2039
+ type: "string",
2040
+ description: "What to test, in natural language. Include how to navigate to the relevant page and what data/roles to expect."
2041
+ }
2042
+ },
2043
+ required: ["task"]
2044
+ }
2045
+ },
2046
+ async execute(input, context) {
2047
+ if (!context) {
2048
+ return "Error: browser automation requires execution context (only available in headless mode)";
2049
+ }
2050
+ return runSubAgent({
2051
+ system: BROWSER_AUTOMATION_PROMPT,
2052
+ task: input.task,
2053
+ tools: BROWSER_TOOLS,
2054
+ externalTools: BROWSER_EXTERNAL_TOOLS,
2055
+ executeTool: async () => "Error: no local tools in browser automation",
2056
+ apiConfig: context.apiConfig,
2057
+ model: context.model,
2058
+ signal: context.signal,
2059
+ parentToolId: context.toolCallId,
2060
+ onEvent: context.onEvent,
2061
+ resolveExternalTool: context.resolveExternalTool
2062
+ });
2063
+ }
2064
+ };
2065
+ }
2066
+ });
2067
+
1703
2068
  // src/tools/index.ts
1704
2069
  function getSpecTools() {
1705
2070
  return [readSpecTool, writeSpecTool, editSpecTool, listSpecFilesTool];
@@ -1714,7 +2079,11 @@ function getCodeTools() {
1714
2079
  globTool,
1715
2080
  listDirTool,
1716
2081
  editsFinishedTool,
1717
- askMindStudioSdkTool
2082
+ askMindStudioSdkTool,
2083
+ runScenarioTool,
2084
+ runMethodTool,
2085
+ screenshotTool,
2086
+ browserAutomationTool
1718
2087
  ];
1719
2088
  if (isLspConfigured()) {
1720
2089
  tools.push(lspDiagnosticsTool, restartProcessTool);
@@ -1763,14 +2132,14 @@ function getToolByName(name) {
1763
2132
  ];
1764
2133
  return allTools.find((t) => t.definition.name === name);
1765
2134
  }
1766
- function executeTool(name, input) {
2135
+ function executeTool(name, input, context) {
1767
2136
  const tool = getToolByName(name);
1768
2137
  if (!tool) {
1769
2138
  return Promise.resolve(`Error: Unknown tool "${name}"`);
1770
2139
  }
1771
- return tool.execute(input);
2140
+ return tool.execute(input, context);
1772
2141
  }
1773
- var init_tools = __esm({
2142
+ var init_tools2 = __esm({
1774
2143
  "src/tools/index.ts"() {
1775
2144
  "use strict";
1776
2145
  init_readSpec();
@@ -1796,14 +2165,18 @@ var init_tools = __esm({
1796
2165
  init_lspDiagnostics();
1797
2166
  init_restartProcess();
1798
2167
  init_askMindStudioSdk();
2168
+ init_runScenario();
2169
+ init_runMethod();
2170
+ init_screenshot();
2171
+ init_browserAutomation();
1799
2172
  }
1800
2173
  });
1801
2174
 
1802
2175
  // src/session.ts
1803
- import fs10 from "fs";
2176
+ import fs11 from "fs";
1804
2177
  function loadSession(state) {
1805
2178
  try {
1806
- const raw = fs10.readFileSync(SESSION_FILE, "utf-8");
2179
+ const raw = fs11.readFileSync(SESSION_FILE, "utf-8");
1807
2180
  const data = JSON.parse(raw);
1808
2181
  if (Array.isArray(data.messages) && data.messages.length > 0) {
1809
2182
  state.messages = sanitizeMessages(data.messages);
@@ -1845,7 +2218,7 @@ function sanitizeMessages(messages) {
1845
2218
  }
1846
2219
  function saveSession(state) {
1847
2220
  try {
1848
- fs10.writeFileSync(
2221
+ fs11.writeFileSync(
1849
2222
  SESSION_FILE,
1850
2223
  JSON.stringify({ messages: state.messages }, null, 2),
1851
2224
  "utf-8"
@@ -1856,7 +2229,7 @@ function saveSession(state) {
1856
2229
  function clearSession(state) {
1857
2230
  state.messages = [];
1858
2231
  try {
1859
- fs10.unlinkSync(SESSION_FILE);
2232
+ fs11.unlinkSync(SESSION_FILE);
1860
2233
  } catch {
1861
2234
  }
1862
2235
  }
@@ -2142,6 +2515,9 @@ async function runTurn(params) {
2142
2515
  }
2143
2516
  if (transform) {
2144
2517
  const result = await transform(partial);
2518
+ if (result === null) {
2519
+ return;
2520
+ }
2145
2521
  log.debug("Streaming content tool: emitting tool_input_delta", {
2146
2522
  id,
2147
2523
  name,
@@ -2213,7 +2589,7 @@ async function runTurn(params) {
2213
2589
  const tool = getToolByName(event.name);
2214
2590
  const wasStreamed = acc?.started ?? false;
2215
2591
  const isInputStreaming = !!tool?.streaming?.partialInput;
2216
- log.debug("Received tool_use", {
2592
+ log.info("Tool call received", {
2217
2593
  id: event.id,
2218
2594
  name: event.name,
2219
2595
  wasStreamed,
@@ -2283,16 +2659,23 @@ async function runTurn(params) {
2283
2659
  let result;
2284
2660
  if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
2285
2661
  saveSession(state);
2286
- log.debug("Waiting for external tool result", {
2662
+ log.info("Waiting for external tool result", {
2287
2663
  name: tc.name,
2288
2664
  id: tc.id
2289
2665
  });
2290
2666
  result = await resolveExternalTool(tc.id, tc.name, tc.input);
2291
2667
  } else {
2292
- result = await executeTool(tc.name, tc.input);
2668
+ result = await executeTool(tc.name, tc.input, {
2669
+ apiConfig,
2670
+ model,
2671
+ signal,
2672
+ onEvent,
2673
+ resolveExternalTool,
2674
+ toolCallId: tc.id
2675
+ });
2293
2676
  }
2294
2677
  const isError = result.startsWith("Error");
2295
- log.debug("Tool completed", {
2678
+ log.info("Tool completed", {
2296
2679
  name: tc.name,
2297
2680
  elapsed: `${Date.now() - toolStart}ms`,
2298
2681
  isError,
@@ -2339,7 +2722,7 @@ var init_agent = __esm({
2339
2722
  "src/agent.ts"() {
2340
2723
  "use strict";
2341
2724
  init_api();
2342
- init_tools();
2725
+ init_tools2();
2343
2726
  init_session();
2344
2727
  init_logger();
2345
2728
  init_parsePartialJson();
@@ -2350,18 +2733,22 @@ var init_agent = __esm({
2350
2733
  "presentSyncPlan",
2351
2734
  "presentPublishPlan",
2352
2735
  "presentPlan",
2353
- "confirmDestructiveAction"
2736
+ "confirmDestructiveAction",
2737
+ "runScenario",
2738
+ "runMethod",
2739
+ "browserCommand",
2740
+ "screenshot"
2354
2741
  ]);
2355
2742
  }
2356
2743
  });
2357
2744
 
2358
2745
  // src/prompt/static/projectContext.ts
2359
- import fs11 from "fs";
2360
- import path4 from "path";
2746
+ import fs12 from "fs";
2747
+ import path5 from "path";
2361
2748
  function loadProjectInstructions() {
2362
2749
  for (const file of AGENT_INSTRUCTION_FILES) {
2363
2750
  try {
2364
- const content = fs11.readFileSync(file, "utf-8").trim();
2751
+ const content = fs12.readFileSync(file, "utf-8").trim();
2365
2752
  if (content) {
2366
2753
  return `
2367
2754
  ## Project Instructions (${file})
@@ -2374,7 +2761,7 @@ ${content}`;
2374
2761
  }
2375
2762
  function loadProjectManifest() {
2376
2763
  try {
2377
- const manifest = fs11.readFileSync("mindstudio.json", "utf-8");
2764
+ const manifest = fs12.readFileSync("mindstudio.json", "utf-8");
2378
2765
  return `
2379
2766
  ## Project Manifest (mindstudio.json)
2380
2767
  \`\`\`json
@@ -2412,9 +2799,9 @@ ${entries.join("\n")}`;
2412
2799
  function walkMdFiles(dir) {
2413
2800
  const results = [];
2414
2801
  try {
2415
- const entries = fs11.readdirSync(dir, { withFileTypes: true });
2802
+ const entries = fs12.readdirSync(dir, { withFileTypes: true });
2416
2803
  for (const entry of entries) {
2417
- const full = path4.join(dir, entry.name);
2804
+ const full = path5.join(dir, entry.name);
2418
2805
  if (entry.isDirectory()) {
2419
2806
  results.push(...walkMdFiles(full));
2420
2807
  } else if (entry.name.endsWith(".md")) {
@@ -2427,7 +2814,7 @@ function walkMdFiles(dir) {
2427
2814
  }
2428
2815
  function parseFrontmatter(filePath) {
2429
2816
  try {
2430
- const content = fs11.readFileSync(filePath, "utf-8");
2817
+ const content = fs12.readFileSync(filePath, "utf-8");
2431
2818
  const match = content.match(/^---\n([\s\S]*?)\n---/);
2432
2819
  if (!match) {
2433
2820
  return { name: "", description: "" };
@@ -2442,7 +2829,7 @@ function parseFrontmatter(filePath) {
2442
2829
  }
2443
2830
  function loadProjectFileListing() {
2444
2831
  try {
2445
- const entries = fs11.readdirSync(".", { withFileTypes: true });
2832
+ const entries = fs12.readdirSync(".", { withFileTypes: true });
2446
2833
  const listing = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules").sort((a, b) => {
2447
2834
  if (a.isDirectory() && !b.isDirectory()) {
2448
2835
  return -1;
@@ -2485,12 +2872,12 @@ var init_projectContext = __esm({
2485
2872
  });
2486
2873
 
2487
2874
  // src/prompt/index.ts
2488
- import fs12 from "fs";
2489
- import path5 from "path";
2875
+ import fs13 from "fs";
2876
+ import path6 from "path";
2490
2877
  function requireFile(filePath) {
2491
- const full = path5.join(PROMPT_DIR, filePath);
2878
+ const full = path6.join(PROMPT_DIR, filePath);
2492
2879
  try {
2493
- return fs12.readFileSync(full, "utf-8").trim();
2880
+ return fs13.readFileSync(full, "utf-8").trim();
2494
2881
  } catch {
2495
2882
  throw new Error(`Required prompt file missing: ${full}`);
2496
2883
  }
@@ -2603,22 +2990,22 @@ ${viewContext?.activeFile ? `Active file: ${viewContext.activeFile}` : ""}
2603
2990
  return resolveIncludes(template);
2604
2991
  }
2605
2992
  var PROMPT_DIR;
2606
- var init_prompt = __esm({
2993
+ var init_prompt2 = __esm({
2607
2994
  "src/prompt/index.ts"() {
2608
2995
  "use strict";
2609
2996
  init_lsp();
2610
2997
  init_projectContext();
2611
- PROMPT_DIR = import.meta.dirname ?? path5.dirname(new URL(import.meta.url).pathname);
2998
+ PROMPT_DIR = import.meta.dirname ?? path6.dirname(new URL(import.meta.url).pathname);
2612
2999
  }
2613
3000
  });
2614
3001
 
2615
3002
  // src/config.ts
2616
- import fs13 from "fs";
2617
- import path6 from "path";
3003
+ import fs14 from "fs";
3004
+ import path7 from "path";
2618
3005
  import os from "os";
2619
3006
  function loadConfigFile() {
2620
3007
  try {
2621
- const raw = fs13.readFileSync(CONFIG_PATH, "utf-8");
3008
+ const raw = fs14.readFileSync(CONFIG_PATH, "utf-8");
2622
3009
  log.debug("Loaded config file", { path: CONFIG_PATH });
2623
3010
  return JSON.parse(raw);
2624
3011
  } catch (err) {
@@ -2654,7 +3041,7 @@ var init_config = __esm({
2654
3041
  "src/config.ts"() {
2655
3042
  "use strict";
2656
3043
  init_logger();
2657
- CONFIG_PATH = path6.join(
3044
+ CONFIG_PATH = path7.join(
2658
3045
  os.homedir(),
2659
3046
  ".mindstudio-local-tunnel",
2660
3047
  "config.json"
@@ -2669,10 +3056,10 @@ __export(headless_exports, {
2669
3056
  startHeadless: () => startHeadless
2670
3057
  });
2671
3058
  import { createInterface } from "readline";
2672
- import fs14 from "fs";
2673
- import path7 from "path";
3059
+ import fs15 from "fs";
3060
+ import path8 from "path";
2674
3061
  function loadActionPrompt(name) {
2675
- return fs14.readFileSync(path7.join(ACTIONS_DIR, `${name}.md`), "utf-8").trim();
3062
+ return fs15.readFileSync(path8.join(ACTIONS_DIR, `${name}.md`), "utf-8").trim();
2676
3063
  }
2677
3064
  function emit(event, data) {
2678
3065
  process.stdout.write(JSON.stringify({ event, ...data }) + "\n");
@@ -2705,20 +3092,32 @@ async function startHeadless(opts = {}) {
2705
3092
  function onEvent(e) {
2706
3093
  switch (e.type) {
2707
3094
  case "text":
2708
- emit("text", { text: e.text });
3095
+ emit("text", {
3096
+ text: e.text,
3097
+ ...e.parentToolId && { parentToolId: e.parentToolId }
3098
+ });
2709
3099
  break;
2710
3100
  case "thinking":
2711
- emit("thinking", { text: e.text });
3101
+ emit("thinking", {
3102
+ text: e.text,
3103
+ ...e.parentToolId && { parentToolId: e.parentToolId }
3104
+ });
2712
3105
  break;
2713
3106
  case "tool_input_delta":
2714
- emit("tool_input_delta", { id: e.id, name: e.name, result: e.result });
3107
+ emit("tool_input_delta", {
3108
+ id: e.id,
3109
+ name: e.name,
3110
+ result: e.result,
3111
+ ...e.parentToolId && { parentToolId: e.parentToolId }
3112
+ });
2715
3113
  break;
2716
3114
  case "tool_start":
2717
3115
  emit("tool_start", {
2718
3116
  id: e.id,
2719
3117
  name: e.name,
2720
3118
  input: e.input,
2721
- ...e.partial && { partial: true }
3119
+ ...e.partial && { partial: true },
3120
+ ...e.parentToolId && { parentToolId: e.parentToolId }
2722
3121
  });
2723
3122
  break;
2724
3123
  case "tool_done":
@@ -2726,7 +3125,8 @@ async function startHeadless(opts = {}) {
2726
3125
  id: e.id,
2727
3126
  name: e.name,
2728
3127
  result: e.result,
2729
- isError: e.isError
3128
+ isError: e.isError,
3129
+ ...e.parentToolId && { parentToolId: e.parentToolId }
2730
3130
  });
2731
3131
  break;
2732
3132
  case "turn_started":
@@ -2857,20 +3257,20 @@ var init_headless = __esm({
2857
3257
  "src/headless.ts"() {
2858
3258
  "use strict";
2859
3259
  init_config();
2860
- init_prompt();
3260
+ init_prompt2();
2861
3261
  init_lsp();
2862
3262
  init_agent();
2863
3263
  init_session();
2864
- BASE_DIR = import.meta.dirname ?? path7.dirname(new URL(import.meta.url).pathname);
2865
- ACTIONS_DIR = path7.join(BASE_DIR, "actions");
3264
+ BASE_DIR = import.meta.dirname ?? path8.dirname(new URL(import.meta.url).pathname);
3265
+ ACTIONS_DIR = path8.join(BASE_DIR, "actions");
2866
3266
  }
2867
3267
  });
2868
3268
 
2869
3269
  // src/index.tsx
2870
3270
  import { render } from "ink";
2871
3271
  import os2 from "os";
2872
- import fs15 from "fs";
2873
- import path8 from "path";
3272
+ import fs16 from "fs";
3273
+ import path9 from "path";
2874
3274
 
2875
3275
  // src/tui/App.tsx
2876
3276
  import { useState as useState2, useCallback, useRef } from "react";
@@ -3005,7 +3405,7 @@ function MessageList({ turns }) {
3005
3405
 
3006
3406
  // src/tui/App.tsx
3007
3407
  init_agent();
3008
- init_prompt();
3408
+ init_prompt2();
3009
3409
  init_session();
3010
3410
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
3011
3411
  function App({ apiConfig, model }) {
@@ -3187,8 +3587,8 @@ for (let i = 0; i < args.length; i++) {
3187
3587
  }
3188
3588
  function printDebugInfo(config) {
3189
3589
  const pkg = JSON.parse(
3190
- fs15.readFileSync(
3191
- path8.join(import.meta.dirname, "..", "package.json"),
3590
+ fs16.readFileSync(
3591
+ path9.join(import.meta.dirname, "..", "package.json"),
3192
3592
  "utf-8"
3193
3593
  )
3194
3594
  );