@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.
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,358 @@ 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 base, local, PROMPT_PATH, BROWSER_AUTOMATION_PROMPT;
2012
+ var init_prompt = __esm({
2013
+ "src/subagents/browserAutomation/prompt.ts"() {
2014
+ "use strict";
2015
+ base = import.meta.dirname ?? path4.dirname(new URL(import.meta.url).pathname);
2016
+ local = path4.join(base, "prompt.md");
2017
+ PROMPT_PATH = fs10.existsSync(local) ? local : path4.join(base, "subagents", "browserAutomation", "prompt.md");
2018
+ BROWSER_AUTOMATION_PROMPT = fs10.readFileSync(PROMPT_PATH, "utf-8").trim();
2019
+ }
2020
+ });
2021
+
2022
+ // src/subagents/browserAutomation/index.ts
2023
+ var browserAutomationTool;
2024
+ var init_browserAutomation = __esm({
2025
+ "src/subagents/browserAutomation/index.ts"() {
2026
+ "use strict";
2027
+ init_runner();
2028
+ init_tools();
2029
+ init_prompt();
2030
+ browserAutomationTool = {
2031
+ definition: {
2032
+ name: "runAutomatedBrowserTest",
2033
+ 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.",
2034
+ inputSchema: {
2035
+ type: "object",
2036
+ properties: {
2037
+ task: {
2038
+ type: "string",
2039
+ description: "What to test, in natural language. Include how to navigate to the relevant page and what data/roles to expect."
2040
+ }
2041
+ },
2042
+ required: ["task"]
2043
+ }
2044
+ },
2045
+ async execute(input, context) {
2046
+ if (!context) {
2047
+ return "Error: browser automation requires execution context (only available in headless mode)";
2048
+ }
2049
+ return runSubAgent({
2050
+ system: BROWSER_AUTOMATION_PROMPT,
2051
+ task: input.task,
2052
+ tools: BROWSER_TOOLS,
2053
+ externalTools: BROWSER_EXTERNAL_TOOLS,
2054
+ executeTool: async () => "Error: no local tools in browser automation",
2055
+ apiConfig: context.apiConfig,
2056
+ model: context.model,
2057
+ signal: context.signal,
2058
+ parentToolId: context.toolCallId,
2059
+ onEvent: context.onEvent,
2060
+ resolveExternalTool: context.resolveExternalTool
2061
+ });
2062
+ }
2063
+ };
2064
+ }
2065
+ });
2066
+
1703
2067
  // src/tools/index.ts
1704
2068
  function getSpecTools() {
1705
2069
  return [readSpecTool, writeSpecTool, editSpecTool, listSpecFilesTool];
@@ -1714,7 +2078,11 @@ function getCodeTools() {
1714
2078
  globTool,
1715
2079
  listDirTool,
1716
2080
  editsFinishedTool,
1717
- askMindStudioSdkTool
2081
+ askMindStudioSdkTool,
2082
+ runScenarioTool,
2083
+ runMethodTool,
2084
+ screenshotTool,
2085
+ browserAutomationTool
1718
2086
  ];
1719
2087
  if (isLspConfigured()) {
1720
2088
  tools.push(lspDiagnosticsTool, restartProcessTool);
@@ -1763,14 +2131,14 @@ function getToolByName(name) {
1763
2131
  ];
1764
2132
  return allTools.find((t) => t.definition.name === name);
1765
2133
  }
1766
- function executeTool(name, input) {
2134
+ function executeTool(name, input, context) {
1767
2135
  const tool = getToolByName(name);
1768
2136
  if (!tool) {
1769
2137
  return Promise.resolve(`Error: Unknown tool "${name}"`);
1770
2138
  }
1771
- return tool.execute(input);
2139
+ return tool.execute(input, context);
1772
2140
  }
1773
- var init_tools = __esm({
2141
+ var init_tools2 = __esm({
1774
2142
  "src/tools/index.ts"() {
1775
2143
  "use strict";
1776
2144
  init_readSpec();
@@ -1796,14 +2164,18 @@ var init_tools = __esm({
1796
2164
  init_lspDiagnostics();
1797
2165
  init_restartProcess();
1798
2166
  init_askMindStudioSdk();
2167
+ init_runScenario();
2168
+ init_runMethod();
2169
+ init_screenshot();
2170
+ init_browserAutomation();
1799
2171
  }
1800
2172
  });
1801
2173
 
1802
2174
  // src/session.ts
1803
- import fs10 from "fs";
2175
+ import fs11 from "fs";
1804
2176
  function loadSession(state) {
1805
2177
  try {
1806
- const raw = fs10.readFileSync(SESSION_FILE, "utf-8");
2178
+ const raw = fs11.readFileSync(SESSION_FILE, "utf-8");
1807
2179
  const data = JSON.parse(raw);
1808
2180
  if (Array.isArray(data.messages) && data.messages.length > 0) {
1809
2181
  state.messages = sanitizeMessages(data.messages);
@@ -1845,7 +2217,7 @@ function sanitizeMessages(messages) {
1845
2217
  }
1846
2218
  function saveSession(state) {
1847
2219
  try {
1848
- fs10.writeFileSync(
2220
+ fs11.writeFileSync(
1849
2221
  SESSION_FILE,
1850
2222
  JSON.stringify({ messages: state.messages }, null, 2),
1851
2223
  "utf-8"
@@ -1856,7 +2228,7 @@ function saveSession(state) {
1856
2228
  function clearSession(state) {
1857
2229
  state.messages = [];
1858
2230
  try {
1859
- fs10.unlinkSync(SESSION_FILE);
2231
+ fs11.unlinkSync(SESSION_FILE);
1860
2232
  } catch {
1861
2233
  }
1862
2234
  }
@@ -2142,6 +2514,9 @@ async function runTurn(params) {
2142
2514
  }
2143
2515
  if (transform) {
2144
2516
  const result = await transform(partial);
2517
+ if (result === null) {
2518
+ return;
2519
+ }
2145
2520
  log.debug("Streaming content tool: emitting tool_input_delta", {
2146
2521
  id,
2147
2522
  name,
@@ -2213,7 +2588,7 @@ async function runTurn(params) {
2213
2588
  const tool = getToolByName(event.name);
2214
2589
  const wasStreamed = acc?.started ?? false;
2215
2590
  const isInputStreaming = !!tool?.streaming?.partialInput;
2216
- log.debug("Received tool_use", {
2591
+ log.info("Tool call received", {
2217
2592
  id: event.id,
2218
2593
  name: event.name,
2219
2594
  wasStreamed,
@@ -2283,16 +2658,23 @@ async function runTurn(params) {
2283
2658
  let result;
2284
2659
  if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
2285
2660
  saveSession(state);
2286
- log.debug("Waiting for external tool result", {
2661
+ log.info("Waiting for external tool result", {
2287
2662
  name: tc.name,
2288
2663
  id: tc.id
2289
2664
  });
2290
2665
  result = await resolveExternalTool(tc.id, tc.name, tc.input);
2291
2666
  } else {
2292
- result = await executeTool(tc.name, tc.input);
2667
+ result = await executeTool(tc.name, tc.input, {
2668
+ apiConfig,
2669
+ model,
2670
+ signal,
2671
+ onEvent,
2672
+ resolveExternalTool,
2673
+ toolCallId: tc.id
2674
+ });
2293
2675
  }
2294
2676
  const isError = result.startsWith("Error");
2295
- log.debug("Tool completed", {
2677
+ log.info("Tool completed", {
2296
2678
  name: tc.name,
2297
2679
  elapsed: `${Date.now() - toolStart}ms`,
2298
2680
  isError,
@@ -2339,7 +2721,7 @@ var init_agent = __esm({
2339
2721
  "src/agent.ts"() {
2340
2722
  "use strict";
2341
2723
  init_api();
2342
- init_tools();
2724
+ init_tools2();
2343
2725
  init_session();
2344
2726
  init_logger();
2345
2727
  init_parsePartialJson();
@@ -2350,18 +2732,22 @@ var init_agent = __esm({
2350
2732
  "presentSyncPlan",
2351
2733
  "presentPublishPlan",
2352
2734
  "presentPlan",
2353
- "confirmDestructiveAction"
2735
+ "confirmDestructiveAction",
2736
+ "runScenario",
2737
+ "runMethod",
2738
+ "browserCommand",
2739
+ "screenshot"
2354
2740
  ]);
2355
2741
  }
2356
2742
  });
2357
2743
 
2358
2744
  // src/prompt/static/projectContext.ts
2359
- import fs11 from "fs";
2360
- import path4 from "path";
2745
+ import fs12 from "fs";
2746
+ import path5 from "path";
2361
2747
  function loadProjectInstructions() {
2362
2748
  for (const file of AGENT_INSTRUCTION_FILES) {
2363
2749
  try {
2364
- const content = fs11.readFileSync(file, "utf-8").trim();
2750
+ const content = fs12.readFileSync(file, "utf-8").trim();
2365
2751
  if (content) {
2366
2752
  return `
2367
2753
  ## Project Instructions (${file})
@@ -2374,7 +2760,7 @@ ${content}`;
2374
2760
  }
2375
2761
  function loadProjectManifest() {
2376
2762
  try {
2377
- const manifest = fs11.readFileSync("mindstudio.json", "utf-8");
2763
+ const manifest = fs12.readFileSync("mindstudio.json", "utf-8");
2378
2764
  return `
2379
2765
  ## Project Manifest (mindstudio.json)
2380
2766
  \`\`\`json
@@ -2412,9 +2798,9 @@ ${entries.join("\n")}`;
2412
2798
  function walkMdFiles(dir) {
2413
2799
  const results = [];
2414
2800
  try {
2415
- const entries = fs11.readdirSync(dir, { withFileTypes: true });
2801
+ const entries = fs12.readdirSync(dir, { withFileTypes: true });
2416
2802
  for (const entry of entries) {
2417
- const full = path4.join(dir, entry.name);
2803
+ const full = path5.join(dir, entry.name);
2418
2804
  if (entry.isDirectory()) {
2419
2805
  results.push(...walkMdFiles(full));
2420
2806
  } else if (entry.name.endsWith(".md")) {
@@ -2427,7 +2813,7 @@ function walkMdFiles(dir) {
2427
2813
  }
2428
2814
  function parseFrontmatter(filePath) {
2429
2815
  try {
2430
- const content = fs11.readFileSync(filePath, "utf-8");
2816
+ const content = fs12.readFileSync(filePath, "utf-8");
2431
2817
  const match = content.match(/^---\n([\s\S]*?)\n---/);
2432
2818
  if (!match) {
2433
2819
  return { name: "", description: "" };
@@ -2442,7 +2828,7 @@ function parseFrontmatter(filePath) {
2442
2828
  }
2443
2829
  function loadProjectFileListing() {
2444
2830
  try {
2445
- const entries = fs11.readdirSync(".", { withFileTypes: true });
2831
+ const entries = fs12.readdirSync(".", { withFileTypes: true });
2446
2832
  const listing = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules").sort((a, b) => {
2447
2833
  if (a.isDirectory() && !b.isDirectory()) {
2448
2834
  return -1;
@@ -2485,12 +2871,12 @@ var init_projectContext = __esm({
2485
2871
  });
2486
2872
 
2487
2873
  // src/prompt/index.ts
2488
- import fs12 from "fs";
2489
- import path5 from "path";
2874
+ import fs13 from "fs";
2875
+ import path6 from "path";
2490
2876
  function requireFile(filePath) {
2491
- const full = path5.join(PROMPT_DIR, filePath);
2877
+ const full = path6.join(PROMPT_DIR, filePath);
2492
2878
  try {
2493
- return fs12.readFileSync(full, "utf-8").trim();
2879
+ return fs13.readFileSync(full, "utf-8").trim();
2494
2880
  } catch {
2495
2881
  throw new Error(`Required prompt file missing: ${full}`);
2496
2882
  }
@@ -2603,22 +2989,22 @@ ${viewContext?.activeFile ? `Active file: ${viewContext.activeFile}` : ""}
2603
2989
  return resolveIncludes(template);
2604
2990
  }
2605
2991
  var PROMPT_DIR;
2606
- var init_prompt = __esm({
2992
+ var init_prompt2 = __esm({
2607
2993
  "src/prompt/index.ts"() {
2608
2994
  "use strict";
2609
2995
  init_lsp();
2610
2996
  init_projectContext();
2611
- PROMPT_DIR = import.meta.dirname ?? path5.dirname(new URL(import.meta.url).pathname);
2997
+ PROMPT_DIR = import.meta.dirname ?? path6.dirname(new URL(import.meta.url).pathname);
2612
2998
  }
2613
2999
  });
2614
3000
 
2615
3001
  // src/config.ts
2616
- import fs13 from "fs";
2617
- import path6 from "path";
3002
+ import fs14 from "fs";
3003
+ import path7 from "path";
2618
3004
  import os from "os";
2619
3005
  function loadConfigFile() {
2620
3006
  try {
2621
- const raw = fs13.readFileSync(CONFIG_PATH, "utf-8");
3007
+ const raw = fs14.readFileSync(CONFIG_PATH, "utf-8");
2622
3008
  log.debug("Loaded config file", { path: CONFIG_PATH });
2623
3009
  return JSON.parse(raw);
2624
3010
  } catch (err) {
@@ -2654,7 +3040,7 @@ var init_config = __esm({
2654
3040
  "src/config.ts"() {
2655
3041
  "use strict";
2656
3042
  init_logger();
2657
- CONFIG_PATH = path6.join(
3043
+ CONFIG_PATH = path7.join(
2658
3044
  os.homedir(),
2659
3045
  ".mindstudio-local-tunnel",
2660
3046
  "config.json"
@@ -2669,10 +3055,10 @@ __export(headless_exports, {
2669
3055
  startHeadless: () => startHeadless
2670
3056
  });
2671
3057
  import { createInterface } from "readline";
2672
- import fs14 from "fs";
2673
- import path7 from "path";
3058
+ import fs15 from "fs";
3059
+ import path8 from "path";
2674
3060
  function loadActionPrompt(name) {
2675
- return fs14.readFileSync(path7.join(ACTIONS_DIR, `${name}.md`), "utf-8").trim();
3061
+ return fs15.readFileSync(path8.join(ACTIONS_DIR, `${name}.md`), "utf-8").trim();
2676
3062
  }
2677
3063
  function emit(event, data) {
2678
3064
  process.stdout.write(JSON.stringify({ event, ...data }) + "\n");
@@ -2705,20 +3091,32 @@ async function startHeadless(opts = {}) {
2705
3091
  function onEvent(e) {
2706
3092
  switch (e.type) {
2707
3093
  case "text":
2708
- emit("text", { text: e.text });
3094
+ emit("text", {
3095
+ text: e.text,
3096
+ ...e.parentToolId && { parentToolId: e.parentToolId }
3097
+ });
2709
3098
  break;
2710
3099
  case "thinking":
2711
- emit("thinking", { text: e.text });
3100
+ emit("thinking", {
3101
+ text: e.text,
3102
+ ...e.parentToolId && { parentToolId: e.parentToolId }
3103
+ });
2712
3104
  break;
2713
3105
  case "tool_input_delta":
2714
- emit("tool_input_delta", { id: e.id, name: e.name, result: e.result });
3106
+ emit("tool_input_delta", {
3107
+ id: e.id,
3108
+ name: e.name,
3109
+ result: e.result,
3110
+ ...e.parentToolId && { parentToolId: e.parentToolId }
3111
+ });
2715
3112
  break;
2716
3113
  case "tool_start":
2717
3114
  emit("tool_start", {
2718
3115
  id: e.id,
2719
3116
  name: e.name,
2720
3117
  input: e.input,
2721
- ...e.partial && { partial: true }
3118
+ ...e.partial && { partial: true },
3119
+ ...e.parentToolId && { parentToolId: e.parentToolId }
2722
3120
  });
2723
3121
  break;
2724
3122
  case "tool_done":
@@ -2726,7 +3124,8 @@ async function startHeadless(opts = {}) {
2726
3124
  id: e.id,
2727
3125
  name: e.name,
2728
3126
  result: e.result,
2729
- isError: e.isError
3127
+ isError: e.isError,
3128
+ ...e.parentToolId && { parentToolId: e.parentToolId }
2730
3129
  });
2731
3130
  break;
2732
3131
  case "turn_started":
@@ -2857,20 +3256,20 @@ var init_headless = __esm({
2857
3256
  "src/headless.ts"() {
2858
3257
  "use strict";
2859
3258
  init_config();
2860
- init_prompt();
3259
+ init_prompt2();
2861
3260
  init_lsp();
2862
3261
  init_agent();
2863
3262
  init_session();
2864
- BASE_DIR = import.meta.dirname ?? path7.dirname(new URL(import.meta.url).pathname);
2865
- ACTIONS_DIR = path7.join(BASE_DIR, "actions");
3263
+ BASE_DIR = import.meta.dirname ?? path8.dirname(new URL(import.meta.url).pathname);
3264
+ ACTIONS_DIR = path8.join(BASE_DIR, "actions");
2866
3265
  }
2867
3266
  });
2868
3267
 
2869
3268
  // src/index.tsx
2870
3269
  import { render } from "ink";
2871
3270
  import os2 from "os";
2872
- import fs15 from "fs";
2873
- import path8 from "path";
3271
+ import fs16 from "fs";
3272
+ import path9 from "path";
2874
3273
 
2875
3274
  // src/tui/App.tsx
2876
3275
  import { useState as useState2, useCallback, useRef } from "react";
@@ -3005,7 +3404,7 @@ function MessageList({ turns }) {
3005
3404
 
3006
3405
  // src/tui/App.tsx
3007
3406
  init_agent();
3008
- init_prompt();
3407
+ init_prompt2();
3009
3408
  init_session();
3010
3409
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
3011
3410
  function App({ apiConfig, model }) {
@@ -3187,8 +3586,8 @@ for (let i = 0; i < args.length; i++) {
3187
3586
  }
3188
3587
  function printDebugInfo(config) {
3189
3588
  const pkg = JSON.parse(
3190
- fs15.readFileSync(
3191
- path8.join(import.meta.dirname, "..", "package.json"),
3589
+ fs16.readFileSync(
3590
+ path9.join(import.meta.dirname, "..", "package.json"),
3192
3591
  "utf-8"
3193
3592
  )
3194
3593
  );