@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/actions/buildFromInitialSpec.md +3 -1
- package/dist/compiled/design.md +11 -0
- package/dist/headless.js +385 -36
- package/dist/index.js +461 -62
- package/dist/static/authoring.md +1 -1
- package/dist/static/instructions.md +6 -2
- package/dist/subagents/browserAutomation/prompt.md +104 -0
- package/package.json +2 -2
|
@@ -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,
|
|
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" })`.
|
package/dist/compiled/design.md
CHANGED
|
@@ -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
|
|
4
|
-
import
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
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
|
|
2191
|
+
import fs14 from "fs";
|
|
1870
2192
|
var SESSION_FILE = ".remy-session.json";
|
|
1871
2193
|
function loadSession(state) {
|
|
1872
2194
|
try {
|
|
1873
|
-
const raw =
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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 ??
|
|
2403
|
-
var ACTIONS_DIR =
|
|
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
|
|
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", {
|
|
2774
|
+
emit("text", {
|
|
2775
|
+
text: e.text,
|
|
2776
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
2777
|
+
});
|
|
2439
2778
|
break;
|
|
2440
2779
|
case "thinking":
|
|
2441
|
-
emit("thinking", {
|
|
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", {
|
|
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":
|