@mindstudio-ai/remy 0.1.4 → 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/actions/buildFromInitialSpec.md +3 -1
- package/dist/compiled/design.md +11 -0
- package/dist/headless.js +386 -36
- package/dist/index.js +462 -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,313 @@ 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 PROMPT_PATH = path7.join(
|
|
2075
|
+
import.meta.dirname ?? path7.dirname(new URL(import.meta.url).pathname),
|
|
2076
|
+
"prompt.md"
|
|
2077
|
+
);
|
|
2078
|
+
var BROWSER_AUTOMATION_PROMPT = fs13.readFileSync(PROMPT_PATH, "utf-8").trim();
|
|
2079
|
+
|
|
2080
|
+
// src/subagents/browserAutomation/index.ts
|
|
2081
|
+
var browserAutomationTool = {
|
|
2082
|
+
definition: {
|
|
2083
|
+
name: "runAutomatedBrowserTest",
|
|
2084
|
+
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.",
|
|
2085
|
+
inputSchema: {
|
|
2086
|
+
type: "object",
|
|
2087
|
+
properties: {
|
|
2088
|
+
task: {
|
|
2089
|
+
type: "string",
|
|
2090
|
+
description: "What to test, in natural language. Include how to navigate to the relevant page and what data/roles to expect."
|
|
2091
|
+
}
|
|
2092
|
+
},
|
|
2093
|
+
required: ["task"]
|
|
2094
|
+
}
|
|
2095
|
+
},
|
|
2096
|
+
async execute(input, context) {
|
|
2097
|
+
if (!context) {
|
|
2098
|
+
return "Error: browser automation requires execution context (only available in headless mode)";
|
|
2099
|
+
}
|
|
2100
|
+
return runSubAgent({
|
|
2101
|
+
system: BROWSER_AUTOMATION_PROMPT,
|
|
2102
|
+
task: input.task,
|
|
2103
|
+
tools: BROWSER_TOOLS,
|
|
2104
|
+
externalTools: BROWSER_EXTERNAL_TOOLS,
|
|
2105
|
+
executeTool: async () => "Error: no local tools in browser automation",
|
|
2106
|
+
apiConfig: context.apiConfig,
|
|
2107
|
+
model: context.model,
|
|
2108
|
+
signal: context.signal,
|
|
2109
|
+
parentToolId: context.toolCallId,
|
|
2110
|
+
onEvent: context.onEvent,
|
|
2111
|
+
resolveExternalTool: context.resolveExternalTool
|
|
2112
|
+
});
|
|
2113
|
+
}
|
|
2114
|
+
};
|
|
2115
|
+
|
|
1797
2116
|
// src/tools/index.ts
|
|
1798
2117
|
function getSpecTools() {
|
|
1799
2118
|
return [readSpecTool, writeSpecTool, editSpecTool, listSpecFilesTool];
|
|
@@ -1808,7 +2127,11 @@ function getCodeTools() {
|
|
|
1808
2127
|
globTool,
|
|
1809
2128
|
listDirTool,
|
|
1810
2129
|
editsFinishedTool,
|
|
1811
|
-
askMindStudioSdkTool
|
|
2130
|
+
askMindStudioSdkTool,
|
|
2131
|
+
runScenarioTool,
|
|
2132
|
+
runMethodTool,
|
|
2133
|
+
screenshotTool,
|
|
2134
|
+
browserAutomationTool
|
|
1812
2135
|
];
|
|
1813
2136
|
if (isLspConfigured()) {
|
|
1814
2137
|
tools.push(lspDiagnosticsTool, restartProcessTool);
|
|
@@ -1857,20 +2180,20 @@ function getToolByName(name) {
|
|
|
1857
2180
|
];
|
|
1858
2181
|
return allTools.find((t) => t.definition.name === name);
|
|
1859
2182
|
}
|
|
1860
|
-
function executeTool(name, input) {
|
|
2183
|
+
function executeTool(name, input, context) {
|
|
1861
2184
|
const tool = getToolByName(name);
|
|
1862
2185
|
if (!tool) {
|
|
1863
2186
|
return Promise.resolve(`Error: Unknown tool "${name}"`);
|
|
1864
2187
|
}
|
|
1865
|
-
return tool.execute(input);
|
|
2188
|
+
return tool.execute(input, context);
|
|
1866
2189
|
}
|
|
1867
2190
|
|
|
1868
2191
|
// src/session.ts
|
|
1869
|
-
import
|
|
2192
|
+
import fs14 from "fs";
|
|
1870
2193
|
var SESSION_FILE = ".remy-session.json";
|
|
1871
2194
|
function loadSession(state) {
|
|
1872
2195
|
try {
|
|
1873
|
-
const raw =
|
|
2196
|
+
const raw = fs14.readFileSync(SESSION_FILE, "utf-8");
|
|
1874
2197
|
const data = JSON.parse(raw);
|
|
1875
2198
|
if (Array.isArray(data.messages) && data.messages.length > 0) {
|
|
1876
2199
|
state.messages = sanitizeMessages(data.messages);
|
|
@@ -1912,7 +2235,7 @@ function sanitizeMessages(messages) {
|
|
|
1912
2235
|
}
|
|
1913
2236
|
function saveSession(state) {
|
|
1914
2237
|
try {
|
|
1915
|
-
|
|
2238
|
+
fs14.writeFileSync(
|
|
1916
2239
|
SESSION_FILE,
|
|
1917
2240
|
JSON.stringify({ messages: state.messages }, null, 2),
|
|
1918
2241
|
"utf-8"
|
|
@@ -1923,7 +2246,7 @@ function saveSession(state) {
|
|
|
1923
2246
|
function clearSession(state) {
|
|
1924
2247
|
state.messages = [];
|
|
1925
2248
|
try {
|
|
1926
|
-
|
|
2249
|
+
fs14.unlinkSync(SESSION_FILE);
|
|
1927
2250
|
} catch {
|
|
1928
2251
|
}
|
|
1929
2252
|
}
|
|
@@ -2099,7 +2422,11 @@ var EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
|
|
|
2099
2422
|
"presentSyncPlan",
|
|
2100
2423
|
"presentPublishPlan",
|
|
2101
2424
|
"presentPlan",
|
|
2102
|
-
"confirmDestructiveAction"
|
|
2425
|
+
"confirmDestructiveAction",
|
|
2426
|
+
"runScenario",
|
|
2427
|
+
"runMethod",
|
|
2428
|
+
"browserCommand",
|
|
2429
|
+
"screenshot"
|
|
2103
2430
|
]);
|
|
2104
2431
|
function createAgentState() {
|
|
2105
2432
|
return { messages: [] };
|
|
@@ -2205,6 +2532,9 @@ async function runTurn(params) {
|
|
|
2205
2532
|
}
|
|
2206
2533
|
if (transform) {
|
|
2207
2534
|
const result = await transform(partial);
|
|
2535
|
+
if (result === null) {
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2208
2538
|
log.debug("Streaming content tool: emitting tool_input_delta", {
|
|
2209
2539
|
id,
|
|
2210
2540
|
name,
|
|
@@ -2276,7 +2606,7 @@ async function runTurn(params) {
|
|
|
2276
2606
|
const tool = getToolByName(event.name);
|
|
2277
2607
|
const wasStreamed = acc?.started ?? false;
|
|
2278
2608
|
const isInputStreaming = !!tool?.streaming?.partialInput;
|
|
2279
|
-
log.
|
|
2609
|
+
log.info("Tool call received", {
|
|
2280
2610
|
id: event.id,
|
|
2281
2611
|
name: event.name,
|
|
2282
2612
|
wasStreamed,
|
|
@@ -2346,16 +2676,23 @@ async function runTurn(params) {
|
|
|
2346
2676
|
let result;
|
|
2347
2677
|
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
2348
2678
|
saveSession(state);
|
|
2349
|
-
log.
|
|
2679
|
+
log.info("Waiting for external tool result", {
|
|
2350
2680
|
name: tc.name,
|
|
2351
2681
|
id: tc.id
|
|
2352
2682
|
});
|
|
2353
2683
|
result = await resolveExternalTool(tc.id, tc.name, tc.input);
|
|
2354
2684
|
} else {
|
|
2355
|
-
result = await executeTool(tc.name, tc.input
|
|
2685
|
+
result = await executeTool(tc.name, tc.input, {
|
|
2686
|
+
apiConfig,
|
|
2687
|
+
model,
|
|
2688
|
+
signal,
|
|
2689
|
+
onEvent,
|
|
2690
|
+
resolveExternalTool,
|
|
2691
|
+
toolCallId: tc.id
|
|
2692
|
+
});
|
|
2356
2693
|
}
|
|
2357
2694
|
const isError = result.startsWith("Error");
|
|
2358
|
-
log.
|
|
2695
|
+
log.info("Tool completed", {
|
|
2359
2696
|
name: tc.name,
|
|
2360
2697
|
elapsed: `${Date.now() - toolStart}ms`,
|
|
2361
2698
|
isError,
|
|
@@ -2399,10 +2736,10 @@ async function runTurn(params) {
|
|
|
2399
2736
|
}
|
|
2400
2737
|
|
|
2401
2738
|
// src/headless.ts
|
|
2402
|
-
var BASE_DIR = import.meta.dirname ??
|
|
2403
|
-
var ACTIONS_DIR =
|
|
2739
|
+
var BASE_DIR = import.meta.dirname ?? path8.dirname(new URL(import.meta.url).pathname);
|
|
2740
|
+
var ACTIONS_DIR = path8.join(BASE_DIR, "actions");
|
|
2404
2741
|
function loadActionPrompt(name) {
|
|
2405
|
-
return
|
|
2742
|
+
return fs15.readFileSync(path8.join(ACTIONS_DIR, `${name}.md`), "utf-8").trim();
|
|
2406
2743
|
}
|
|
2407
2744
|
function emit(event, data) {
|
|
2408
2745
|
process.stdout.write(JSON.stringify({ event, ...data }) + "\n");
|
|
@@ -2435,20 +2772,32 @@ async function startHeadless(opts = {}) {
|
|
|
2435
2772
|
function onEvent(e) {
|
|
2436
2773
|
switch (e.type) {
|
|
2437
2774
|
case "text":
|
|
2438
|
-
emit("text", {
|
|
2775
|
+
emit("text", {
|
|
2776
|
+
text: e.text,
|
|
2777
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
2778
|
+
});
|
|
2439
2779
|
break;
|
|
2440
2780
|
case "thinking":
|
|
2441
|
-
emit("thinking", {
|
|
2781
|
+
emit("thinking", {
|
|
2782
|
+
text: e.text,
|
|
2783
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
2784
|
+
});
|
|
2442
2785
|
break;
|
|
2443
2786
|
case "tool_input_delta":
|
|
2444
|
-
emit("tool_input_delta", {
|
|
2787
|
+
emit("tool_input_delta", {
|
|
2788
|
+
id: e.id,
|
|
2789
|
+
name: e.name,
|
|
2790
|
+
result: e.result,
|
|
2791
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
2792
|
+
});
|
|
2445
2793
|
break;
|
|
2446
2794
|
case "tool_start":
|
|
2447
2795
|
emit("tool_start", {
|
|
2448
2796
|
id: e.id,
|
|
2449
2797
|
name: e.name,
|
|
2450
2798
|
input: e.input,
|
|
2451
|
-
...e.partial && { partial: true }
|
|
2799
|
+
...e.partial && { partial: true },
|
|
2800
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
2452
2801
|
});
|
|
2453
2802
|
break;
|
|
2454
2803
|
case "tool_done":
|
|
@@ -2456,7 +2805,8 @@ async function startHeadless(opts = {}) {
|
|
|
2456
2805
|
id: e.id,
|
|
2457
2806
|
name: e.name,
|
|
2458
2807
|
result: e.result,
|
|
2459
|
-
isError: e.isError
|
|
2808
|
+
isError: e.isError,
|
|
2809
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
2460
2810
|
});
|
|
2461
2811
|
break;
|
|
2462
2812
|
case "turn_started":
|