@mindstudio-ai/remy 0.1.34 → 0.1.36
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/headless.js +998 -586
- package/dist/index.js +1086 -584
- package/dist/prompt/compiled/media-cdn.md +1 -1
- package/dist/prompt/sources/llms.txt +1618 -0
- package/dist/prompt/static/instructions.md +1 -1
- package/dist/prompt/static/team.md +1 -1
- package/dist/subagents/.notes-background-agents.md +60 -48
- package/dist/subagents/browserAutomation/prompt.md +14 -11
- package/dist/subagents/designExpert/data/sources/dev/index.html +901 -0
- package/dist/subagents/designExpert/data/sources/dev/serve.mjs +244 -0
- package/dist/subagents/designExpert/data/sources/dev/specimens-fonts.html +126 -0
- package/dist/subagents/designExpert/data/sources/dev/specimens-pairings.html +114 -0
- package/dist/subagents/designExpert/data/{fonts.json → sources/fonts.json} +0 -97
- package/dist/subagents/designExpert/data/sources/inspiration.json +392 -0
- package/dist/subagents/designExpert/prompt.md +36 -12
- package/dist/subagents/designExpert/prompts/animation.md +14 -6
- package/dist/subagents/designExpert/prompts/color.md +25 -5
- package/dist/subagents/designExpert/prompts/{icons.md → components.md} +17 -5
- package/dist/subagents/designExpert/prompts/frontend-design-notes.md +17 -122
- package/dist/subagents/designExpert/prompts/identity.md +15 -61
- package/dist/subagents/designExpert/prompts/images.md +35 -10
- package/dist/subagents/designExpert/prompts/layout.md +14 -9
- package/dist/subagents/designExpert/prompts/typography.md +39 -0
- package/package.json +2 -2
- package/dist/actions/buildFromInitialSpec.md +0 -15
- package/dist/actions/publish.md +0 -12
- package/dist/actions/sync.md +0 -19
- package/dist/compiled/README.md +0 -100
- package/dist/compiled/auth.md +0 -77
- package/dist/compiled/design.md +0 -251
- package/dist/compiled/dev-and-deploy.md +0 -69
- package/dist/compiled/interfaces.md +0 -238
- package/dist/compiled/manifest.md +0 -107
- package/dist/compiled/media-cdn.md +0 -51
- package/dist/compiled/methods.md +0 -225
- package/dist/compiled/msfm.md +0 -222
- package/dist/compiled/platform.md +0 -105
- package/dist/compiled/scenarios.md +0 -103
- package/dist/compiled/sdk-actions.md +0 -146
- package/dist/compiled/tables.md +0 -263
- package/dist/static/authoring.md +0 -101
- package/dist/static/coding.md +0 -29
- package/dist/static/identity.md +0 -1
- package/dist/static/instructions.md +0 -31
- package/dist/static/intake.md +0 -44
- package/dist/static/lsp.md +0 -4
- package/dist/static/projectContext.ts +0 -160
- package/dist/static/team.md +0 -39
- package/dist/subagents/designExpert/data/inspiration.json +0 -392
- package/dist/subagents/designExpert/prompts/instructions.md +0 -18
- /package/dist/subagents/designExpert/data/{compile-font-descriptions.sh → sources/compile-font-descriptions.sh} +0 -0
- /package/dist/subagents/designExpert/data/{compile-inspiration.sh → sources/compile-inspiration.sh} +0 -0
- /package/dist/subagents/designExpert/data/{inspiration.raw.json → sources/inspiration.raw.json} +0 -0
- /package/dist/subagents/designExpert/{prompts/tool-prompts → data/sources/prompts}/design-analysis.md +0 -0
- /package/dist/subagents/designExpert/{prompts/tool-prompts → data/sources/prompts}/font-analysis.md +0 -0
package/dist/index.js
CHANGED
|
@@ -1057,7 +1057,12 @@ function runCli(cmd, options) {
|
|
|
1057
1057
|
const entry = JSON.parse(trimmed);
|
|
1058
1058
|
if (entry.type === "log" && entry.value) {
|
|
1059
1059
|
const prefix = entry.tag ? `[${entry.tag}]` : "[log]";
|
|
1060
|
-
|
|
1060
|
+
const formatted = `${prefix} ${entry.value}`;
|
|
1061
|
+
if (options?.onLog) {
|
|
1062
|
+
options.onLog(formatted);
|
|
1063
|
+
} else {
|
|
1064
|
+
logs.push(formatted);
|
|
1065
|
+
}
|
|
1061
1066
|
}
|
|
1062
1067
|
} catch {
|
|
1063
1068
|
}
|
|
@@ -1069,7 +1074,7 @@ function runCli(cmd, options) {
|
|
|
1069
1074
|
}, timeout);
|
|
1070
1075
|
child.on("close", (code) => {
|
|
1071
1076
|
clearTimeout(timer);
|
|
1072
|
-
const logBlock = logs.length > 0 ? logs.join("\n") + "\n\n" : "";
|
|
1077
|
+
const logBlock = !options?.onLog && logs.length > 0 ? logs.join("\n") + "\n\n" : "";
|
|
1073
1078
|
const out = stdout.trim();
|
|
1074
1079
|
if (out) {
|
|
1075
1080
|
resolve(logBlock + out);
|
|
@@ -1111,11 +1116,12 @@ var init_sdkConsultant = __esm({
|
|
|
1111
1116
|
required: ["query"]
|
|
1112
1117
|
}
|
|
1113
1118
|
},
|
|
1114
|
-
async execute(input) {
|
|
1119
|
+
async execute(input, context) {
|
|
1115
1120
|
const query = input.query;
|
|
1116
1121
|
return runCli(`mindstudio ask ${JSON.stringify(query)}`, {
|
|
1117
1122
|
timeout: 2e5,
|
|
1118
|
-
maxBuffer: 512 * 1024
|
|
1123
|
+
maxBuffer: 512 * 1024,
|
|
1124
|
+
onLog: context?.onLog
|
|
1119
1125
|
});
|
|
1120
1126
|
}
|
|
1121
1127
|
};
|
|
@@ -1147,7 +1153,7 @@ var init_fetchUrl = __esm({
|
|
|
1147
1153
|
required: ["url"]
|
|
1148
1154
|
}
|
|
1149
1155
|
},
|
|
1150
|
-
async execute(input) {
|
|
1156
|
+
async execute(input, context) {
|
|
1151
1157
|
const url = input.url;
|
|
1152
1158
|
const screenshot = input.screenshot;
|
|
1153
1159
|
const pageOptions = { onlyMainContent: true };
|
|
@@ -1155,7 +1161,8 @@ var init_fetchUrl = __esm({
|
|
|
1155
1161
|
pageOptions.screenshot = true;
|
|
1156
1162
|
}
|
|
1157
1163
|
return runCli(
|
|
1158
|
-
`mindstudio scrape-url --url ${JSON.stringify(url)} --page-options ${JSON.stringify(JSON.stringify(pageOptions))} --no-meta
|
|
1164
|
+
`mindstudio scrape-url --url ${JSON.stringify(url)} --page-options ${JSON.stringify(JSON.stringify(pageOptions))} --no-meta`,
|
|
1165
|
+
{ onLog: context?.onLog }
|
|
1159
1166
|
);
|
|
1160
1167
|
}
|
|
1161
1168
|
};
|
|
@@ -1183,11 +1190,11 @@ var init_searchGoogle = __esm({
|
|
|
1183
1190
|
required: ["query"]
|
|
1184
1191
|
}
|
|
1185
1192
|
},
|
|
1186
|
-
async execute(input) {
|
|
1193
|
+
async execute(input, context) {
|
|
1187
1194
|
const query = input.query;
|
|
1188
1195
|
return runCli(
|
|
1189
1196
|
`mindstudio search-google --query ${JSON.stringify(query)} --export-type json --output-key results --no-meta`,
|
|
1190
|
-
{ maxBuffer: 512 * 1024 }
|
|
1197
|
+
{ maxBuffer: 512 * 1024, onLog: context?.onLog }
|
|
1191
1198
|
);
|
|
1192
1199
|
}
|
|
1193
1200
|
};
|
|
@@ -1224,9 +1231,9 @@ var init_setProjectName = __esm({
|
|
|
1224
1231
|
// src/tools/code/readFile.ts
|
|
1225
1232
|
import fs6 from "fs/promises";
|
|
1226
1233
|
function isBinary(buffer) {
|
|
1227
|
-
const
|
|
1228
|
-
for (let i = 0; i <
|
|
1229
|
-
if (
|
|
1234
|
+
const sample3 = buffer.subarray(0, 8192);
|
|
1235
|
+
for (let i = 0; i < sample3.length; i++) {
|
|
1236
|
+
if (sample3[i] === 0) {
|
|
1230
1237
|
return true;
|
|
1231
1238
|
}
|
|
1232
1239
|
}
|
|
@@ -2018,18 +2025,16 @@ var init_runMethod = __esm({
|
|
|
2018
2025
|
// src/tools/_helpers/screenshot.ts
|
|
2019
2026
|
async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
2020
2027
|
let prompt;
|
|
2021
|
-
let
|
|
2028
|
+
let onLog;
|
|
2022
2029
|
if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
|
|
2023
2030
|
prompt = promptOrOptions.prompt;
|
|
2024
|
-
|
|
2031
|
+
onLog = promptOrOptions.onLog;
|
|
2025
2032
|
} else {
|
|
2026
2033
|
prompt = promptOrOptions;
|
|
2027
2034
|
}
|
|
2028
|
-
const ssResult = await sidecarRequest(
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
{ timeout: 12e4 }
|
|
2032
|
-
);
|
|
2035
|
+
const ssResult = await sidecarRequest("/screenshot-full-page", void 0, {
|
|
2036
|
+
timeout: 12e4
|
|
2037
|
+
});
|
|
2033
2038
|
log.debug("Screenshot response", { ssResult });
|
|
2034
2039
|
const url = ssResult?.url || ssResult?.screenshotUrl;
|
|
2035
2040
|
if (!url) {
|
|
@@ -2043,7 +2048,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
2043
2048
|
const analysisPrompt = prompt || SCREENSHOT_ANALYSIS_PROMPT;
|
|
2044
2049
|
const analysis = await runCli(
|
|
2045
2050
|
`mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`,
|
|
2046
|
-
{ timeout: 2e5 }
|
|
2051
|
+
{ timeout: 2e5, onLog }
|
|
2047
2052
|
);
|
|
2048
2053
|
return JSON.stringify({ url, analysis });
|
|
2049
2054
|
}
|
|
@@ -2054,7 +2059,7 @@ var init_screenshot = __esm({
|
|
|
2054
2059
|
init_sidecar();
|
|
2055
2060
|
init_runCli();
|
|
2056
2061
|
init_logger();
|
|
2057
|
-
SCREENSHOT_ANALYSIS_PROMPT = "Describe everything visible on screen from top to bottom \u2014 every element, its position, its size relative to the viewport, its colors, its content. Be thorough and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components).";
|
|
2062
|
+
SCREENSHOT_ANALYSIS_PROMPT = "Describe everything visible on screen from top to bottom \u2014 every element, its position, its size relative to the viewport, its colors, its content. Be comprehensive, thorough, and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components). Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.";
|
|
2058
2063
|
}
|
|
2059
2064
|
});
|
|
2060
2065
|
|
|
@@ -2067,26 +2072,22 @@ var init_screenshot2 = __esm({
|
|
|
2067
2072
|
screenshotTool = {
|
|
2068
2073
|
definition: {
|
|
2069
2074
|
name: "screenshot",
|
|
2070
|
-
description: "Capture a screenshot of the app preview and get a description of what's on screen. Optionally provide a specific question about what you're looking for
|
|
2075
|
+
description: "Capture a full-height screenshot of the app preview and get a description of what's on screen. Optionally provide a specific question about what you're looking for..",
|
|
2071
2076
|
inputSchema: {
|
|
2072
2077
|
type: "object",
|
|
2073
2078
|
properties: {
|
|
2074
2079
|
prompt: {
|
|
2075
2080
|
type: "string",
|
|
2076
2081
|
description: "Optional question about the screenshot. If omitted, returns a general description of what's visible."
|
|
2077
|
-
},
|
|
2078
|
-
fullPage: {
|
|
2079
|
-
type: "boolean",
|
|
2080
|
-
description: "Capture the full scrollable page instead of just the viewport. Use when you need to see below-the-fold content."
|
|
2081
2082
|
}
|
|
2082
2083
|
}
|
|
2083
2084
|
}
|
|
2084
2085
|
},
|
|
2085
|
-
async execute(input) {
|
|
2086
|
+
async execute(input, context) {
|
|
2086
2087
|
try {
|
|
2087
2088
|
return await captureAndAnalyzeScreenshot({
|
|
2088
2089
|
prompt: input.prompt,
|
|
2089
|
-
|
|
2090
|
+
onLog: context?.onLog
|
|
2090
2091
|
});
|
|
2091
2092
|
} catch (err) {
|
|
2092
2093
|
return `Error taking screenshot: ${err.message}`;
|
|
@@ -2096,6 +2097,91 @@ var init_screenshot2 = __esm({
|
|
|
2096
2097
|
}
|
|
2097
2098
|
});
|
|
2098
2099
|
|
|
2100
|
+
// src/statusWatcher.ts
|
|
2101
|
+
function startStatusWatcher(config) {
|
|
2102
|
+
const { apiConfig, getContext, onStatus, interval = 3e3, signal } = config;
|
|
2103
|
+
let lastLabel = "";
|
|
2104
|
+
let inflight = false;
|
|
2105
|
+
let stopped = false;
|
|
2106
|
+
const url = `${apiConfig.baseUrl}/_internal/v2/agent/remy/generate-status`;
|
|
2107
|
+
async function tick() {
|
|
2108
|
+
if (stopped || signal?.aborted || inflight) {
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
inflight = true;
|
|
2112
|
+
try {
|
|
2113
|
+
const ctx = getContext();
|
|
2114
|
+
if (!ctx.assistantText && !ctx.lastToolName) {
|
|
2115
|
+
log.debug("Status watcher: no context, skipping");
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
log.debug("Status watcher: requesting label", {
|
|
2119
|
+
textLength: ctx.assistantText.length,
|
|
2120
|
+
lastToolName: ctx.lastToolName
|
|
2121
|
+
});
|
|
2122
|
+
const res = await fetch(url, {
|
|
2123
|
+
method: "POST",
|
|
2124
|
+
headers: {
|
|
2125
|
+
"Content-Type": "application/json",
|
|
2126
|
+
Authorization: `Bearer ${apiConfig.apiKey}`
|
|
2127
|
+
},
|
|
2128
|
+
body: JSON.stringify({
|
|
2129
|
+
assistantText: ctx.assistantText.slice(-500),
|
|
2130
|
+
lastToolName: ctx.lastToolName,
|
|
2131
|
+
lastToolResult: ctx.lastToolResult?.slice(-200),
|
|
2132
|
+
onboardingState: ctx.onboardingState,
|
|
2133
|
+
userMessage: ctx.userMessage?.slice(-200)
|
|
2134
|
+
}),
|
|
2135
|
+
signal
|
|
2136
|
+
});
|
|
2137
|
+
if (!res.ok) {
|
|
2138
|
+
log.debug("Status watcher: endpoint returned non-ok", {
|
|
2139
|
+
status: res.status
|
|
2140
|
+
});
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
2143
|
+
const data = await res.json();
|
|
2144
|
+
if (!data.label) {
|
|
2145
|
+
log.debug("Status watcher: no label in response");
|
|
2146
|
+
return;
|
|
2147
|
+
}
|
|
2148
|
+
if (data.label === lastLabel) {
|
|
2149
|
+
log.debug("Status watcher: duplicate label, skipping", {
|
|
2150
|
+
label: data.label
|
|
2151
|
+
});
|
|
2152
|
+
return;
|
|
2153
|
+
}
|
|
2154
|
+
lastLabel = data.label;
|
|
2155
|
+
if (stopped) {
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
log.debug("Status watcher: emitting", { label: data.label });
|
|
2159
|
+
onStatus(data.label);
|
|
2160
|
+
} catch (err) {
|
|
2161
|
+
log.debug("Status watcher: error", { error: err?.message ?? "unknown" });
|
|
2162
|
+
} finally {
|
|
2163
|
+
inflight = false;
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
const timer = setInterval(tick, interval);
|
|
2167
|
+
tick().catch(() => {
|
|
2168
|
+
});
|
|
2169
|
+
log.debug("Status watcher started", { interval });
|
|
2170
|
+
return {
|
|
2171
|
+
stop() {
|
|
2172
|
+
stopped = true;
|
|
2173
|
+
clearInterval(timer);
|
|
2174
|
+
log.debug("Status watcher stopped");
|
|
2175
|
+
}
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
var init_statusWatcher = __esm({
|
|
2179
|
+
"src/statusWatcher.ts"() {
|
|
2180
|
+
"use strict";
|
|
2181
|
+
init_logger();
|
|
2182
|
+
}
|
|
2183
|
+
});
|
|
2184
|
+
|
|
2099
2185
|
// src/subagents/common/cleanMessages.ts
|
|
2100
2186
|
function cleanMessagesForApi(messages) {
|
|
2101
2187
|
return messages.map((msg) => {
|
|
@@ -2135,7 +2221,7 @@ async function runSubAgent(config) {
|
|
|
2135
2221
|
const {
|
|
2136
2222
|
system,
|
|
2137
2223
|
task,
|
|
2138
|
-
tools,
|
|
2224
|
+
tools: tools2,
|
|
2139
2225
|
externalTools,
|
|
2140
2226
|
executeTool: executeTool2,
|
|
2141
2227
|
apiConfig,
|
|
@@ -2144,19 +2230,47 @@ async function runSubAgent(config) {
|
|
|
2144
2230
|
signal,
|
|
2145
2231
|
parentToolId,
|
|
2146
2232
|
onEvent,
|
|
2147
|
-
resolveExternalTool
|
|
2233
|
+
resolveExternalTool,
|
|
2234
|
+
toolRegistry
|
|
2148
2235
|
} = config;
|
|
2149
2236
|
const emit2 = (e) => {
|
|
2150
2237
|
onEvent({ ...e, parentToolId });
|
|
2151
2238
|
};
|
|
2152
2239
|
const messages = [{ role: "user", content: task }];
|
|
2240
|
+
function getPartialText(blocks) {
|
|
2241
|
+
return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
2242
|
+
}
|
|
2243
|
+
function abortResult(blocks) {
|
|
2244
|
+
if (signal?.reason === "graceful") {
|
|
2245
|
+
const partial = getPartialText(blocks);
|
|
2246
|
+
return {
|
|
2247
|
+
text: partial ? `[INTERRUPTED]
|
|
2248
|
+
|
|
2249
|
+
${partial}` : "[INTERRUPTED] Sub-agent was interrupted before producing output.",
|
|
2250
|
+
messages
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
2253
|
+
return { text: "Error: cancelled", messages };
|
|
2254
|
+
}
|
|
2255
|
+
let lastToolResult = "";
|
|
2153
2256
|
while (true) {
|
|
2154
2257
|
if (signal?.aborted) {
|
|
2155
|
-
return
|
|
2258
|
+
return abortResult([]);
|
|
2156
2259
|
}
|
|
2157
2260
|
const contentBlocks = [];
|
|
2158
2261
|
let thinkingStartedAt = 0;
|
|
2159
2262
|
let stopReason = "end_turn";
|
|
2263
|
+
let currentToolNames = "";
|
|
2264
|
+
const statusWatcher = startStatusWatcher({
|
|
2265
|
+
apiConfig,
|
|
2266
|
+
getContext: () => ({
|
|
2267
|
+
assistantText: getPartialText(contentBlocks),
|
|
2268
|
+
lastToolName: currentToolNames || void 0,
|
|
2269
|
+
lastToolResult: lastToolResult || void 0
|
|
2270
|
+
}),
|
|
2271
|
+
onStatus: (label) => emit2({ type: "status", message: label }),
|
|
2272
|
+
signal
|
|
2273
|
+
});
|
|
2160
2274
|
const fullSystem = `${system}
|
|
2161
2275
|
|
|
2162
2276
|
Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC")}`;
|
|
@@ -2167,7 +2281,7 @@ Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " "
|
|
|
2167
2281
|
subAgentId,
|
|
2168
2282
|
system: fullSystem,
|
|
2169
2283
|
messages: cleanMessagesForApi(messages),
|
|
2170
|
-
tools,
|
|
2284
|
+
tools: tools2,
|
|
2171
2285
|
signal
|
|
2172
2286
|
})) {
|
|
2173
2287
|
if (signal?.aborted) {
|
|
@@ -2232,7 +2346,8 @@ Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " "
|
|
|
2232
2346
|
}
|
|
2233
2347
|
}
|
|
2234
2348
|
if (signal?.aborted) {
|
|
2235
|
-
|
|
2349
|
+
statusWatcher.stop();
|
|
2350
|
+
return abortResult(contentBlocks);
|
|
2236
2351
|
}
|
|
2237
2352
|
messages.push({
|
|
2238
2353
|
role: "assistant",
|
|
@@ -2242,6 +2357,7 @@ Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " "
|
|
|
2242
2357
|
(b) => b.type === "tool"
|
|
2243
2358
|
);
|
|
2244
2359
|
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
2360
|
+
statusWatcher.stop();
|
|
2245
2361
|
const text = contentBlocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
2246
2362
|
return { text, messages };
|
|
2247
2363
|
}
|
|
@@ -2250,40 +2366,82 @@ Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " "
|
|
|
2250
2366
|
count: toolCalls.length,
|
|
2251
2367
|
tools: toolCalls.map((tc) => tc.name)
|
|
2252
2368
|
});
|
|
2369
|
+
currentToolNames = toolCalls.map((tc) => tc.name).join(", ");
|
|
2253
2370
|
const results = await Promise.all(
|
|
2254
2371
|
toolCalls.map(async (tc) => {
|
|
2255
2372
|
if (signal?.aborted) {
|
|
2256
2373
|
return { id: tc.id, result: "Error: cancelled", isError: true };
|
|
2257
2374
|
}
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2375
|
+
let settle;
|
|
2376
|
+
const resultPromise = new Promise((res) => {
|
|
2377
|
+
settle = (result, isError) => res({ id: tc.id, result, isError });
|
|
2378
|
+
});
|
|
2379
|
+
let toolAbort = new AbortController();
|
|
2380
|
+
const cascadeAbort = () => toolAbort.abort();
|
|
2381
|
+
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
2382
|
+
let settled = false;
|
|
2383
|
+
const safeSettle = (result, isError) => {
|
|
2384
|
+
if (settled) {
|
|
2385
|
+
return;
|
|
2264
2386
|
}
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2387
|
+
settled = true;
|
|
2388
|
+
signal?.removeEventListener("abort", cascadeAbort);
|
|
2389
|
+
settle(result, isError);
|
|
2390
|
+
};
|
|
2391
|
+
const run = async (input) => {
|
|
2392
|
+
try {
|
|
2393
|
+
let result;
|
|
2394
|
+
if (externalTools.has(tc.name) && resolveExternalTool) {
|
|
2395
|
+
result = await resolveExternalTool(tc.id, tc.name, input);
|
|
2396
|
+
} else {
|
|
2397
|
+
const onLog = (line) => emit2({
|
|
2398
|
+
type: "tool_input_delta",
|
|
2399
|
+
id: tc.id,
|
|
2400
|
+
name: tc.name,
|
|
2401
|
+
result: line
|
|
2402
|
+
});
|
|
2403
|
+
result = await executeTool2(tc.name, input, tc.id, onLog);
|
|
2404
|
+
}
|
|
2405
|
+
safeSettle(result, result.startsWith("Error"));
|
|
2406
|
+
} catch (err) {
|
|
2407
|
+
safeSettle(`Error: ${err.message}`, true);
|
|
2408
|
+
}
|
|
2409
|
+
};
|
|
2410
|
+
const entry = {
|
|
2411
|
+
id: tc.id,
|
|
2412
|
+
name: tc.name,
|
|
2413
|
+
input: tc.input,
|
|
2414
|
+
parentToolId,
|
|
2415
|
+
abortController: toolAbort,
|
|
2416
|
+
startedAt: Date.now(),
|
|
2417
|
+
settle: safeSettle,
|
|
2418
|
+
rerun: (newInput) => {
|
|
2419
|
+
settled = false;
|
|
2420
|
+
toolAbort = new AbortController();
|
|
2421
|
+
signal?.addEventListener("abort", () => toolAbort.abort(), {
|
|
2422
|
+
once: true
|
|
2423
|
+
});
|
|
2424
|
+
entry.abortController = toolAbort;
|
|
2425
|
+
entry.input = newInput;
|
|
2426
|
+
run(newInput);
|
|
2427
|
+
}
|
|
2428
|
+
};
|
|
2429
|
+
toolRegistry?.register(entry);
|
|
2430
|
+
run(tc.input);
|
|
2431
|
+
const r = await resultPromise;
|
|
2432
|
+
toolRegistry?.unregister(tc.id);
|
|
2433
|
+
emit2({
|
|
2434
|
+
type: "tool_done",
|
|
2435
|
+
id: tc.id,
|
|
2436
|
+
name: tc.name,
|
|
2437
|
+
result: r.result,
|
|
2438
|
+
isError: r.isError
|
|
2439
|
+
});
|
|
2440
|
+
return r;
|
|
2285
2441
|
})
|
|
2286
2442
|
);
|
|
2443
|
+
statusWatcher.stop();
|
|
2444
|
+
lastToolResult = results.at(-1)?.result ?? "";
|
|
2287
2445
|
for (const r of results) {
|
|
2288
2446
|
const block = contentBlocks.find(
|
|
2289
2447
|
(b) => b.type === "tool" && b.id === r.id
|
|
@@ -2291,6 +2449,7 @@ Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " "
|
|
|
2291
2449
|
if (block?.type === "tool") {
|
|
2292
2450
|
block.result = r.result;
|
|
2293
2451
|
block.isError = r.isError;
|
|
2452
|
+
block.completedAt = Date.now();
|
|
2294
2453
|
}
|
|
2295
2454
|
messages.push({
|
|
2296
2455
|
role: "user",
|
|
@@ -2306,6 +2465,7 @@ var init_runner = __esm({
|
|
|
2306
2465
|
"use strict";
|
|
2307
2466
|
init_api();
|
|
2308
2467
|
init_logger();
|
|
2468
|
+
init_statusWatcher();
|
|
2309
2469
|
init_cleanMessages();
|
|
2310
2470
|
}
|
|
2311
2471
|
});
|
|
@@ -2340,7 +2500,7 @@ var init_tools = __esm({
|
|
|
2340
2500
|
"styles",
|
|
2341
2501
|
"screenshot"
|
|
2342
2502
|
],
|
|
2343
|
-
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). select: select a dropdown option by text. wait: wait for an element to appear (polls 100ms, waits for network). navigate: navigate to a URL within the app (waits for load, subsequent steps run on new page). evaluate: run JS in the page. styles: read computed CSS styles from elements (pass properties array with camelCase names, or omit for defaults). screenshot: full-page viewport-stitched screenshot (returns
|
|
2503
|
+
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). select: select a dropdown option by text. wait: wait for an element to appear (polls 100ms, waits for network). navigate: navigate to a URL within the app (waits for load, subsequent steps run on new page). evaluate: run JS in the page. styles: read computed CSS styles from elements (pass properties array with camelCase names, or omit for defaults). screenshot: full-page viewport-stitched screenshot (returns CDN url with dimensions)."
|
|
2344
2504
|
},
|
|
2345
2505
|
ref: {
|
|
2346
2506
|
type: "string",
|
|
@@ -2396,8 +2556,8 @@ var init_tools = __esm({
|
|
|
2396
2556
|
}
|
|
2397
2557
|
},
|
|
2398
2558
|
{
|
|
2399
|
-
name: "
|
|
2400
|
-
description: "Capture a screenshot of the current page. Returns a CDN URL with
|
|
2559
|
+
name: "screenshotFullPage",
|
|
2560
|
+
description: "Capture a full-height screenshot of the current page. Returns a CDN URL with full text analysis and description.",
|
|
2401
2561
|
inputSchema: {
|
|
2402
2562
|
type: "object",
|
|
2403
2563
|
properties: {}
|
|
@@ -2416,12 +2576,54 @@ var init_tools = __esm({
|
|
|
2416
2576
|
}
|
|
2417
2577
|
});
|
|
2418
2578
|
|
|
2419
|
-
// src/
|
|
2579
|
+
// src/assets.ts
|
|
2420
2580
|
import fs10 from "fs";
|
|
2421
2581
|
import path4 from "path";
|
|
2582
|
+
function findRoot(start) {
|
|
2583
|
+
let dir = start;
|
|
2584
|
+
while (dir !== path4.dirname(dir)) {
|
|
2585
|
+
if (fs10.existsSync(path4.join(dir, "package.json"))) {
|
|
2586
|
+
return dir;
|
|
2587
|
+
}
|
|
2588
|
+
dir = path4.dirname(dir);
|
|
2589
|
+
}
|
|
2590
|
+
return start;
|
|
2591
|
+
}
|
|
2592
|
+
function assetPath(...segments) {
|
|
2593
|
+
return path4.join(ASSETS_BASE, ...segments);
|
|
2594
|
+
}
|
|
2595
|
+
function readAsset(...segments) {
|
|
2596
|
+
const full = assetPath(...segments);
|
|
2597
|
+
try {
|
|
2598
|
+
return fs10.readFileSync(full, "utf-8").trim();
|
|
2599
|
+
} catch {
|
|
2600
|
+
throw new Error(`Required asset missing: ${full}`);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
function readJsonAsset(fallback, ...segments) {
|
|
2604
|
+
const full = assetPath(...segments);
|
|
2605
|
+
try {
|
|
2606
|
+
return JSON.parse(fs10.readFileSync(full, "utf-8"));
|
|
2607
|
+
} catch {
|
|
2608
|
+
return fallback;
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
var ROOT, ASSETS_BASE;
|
|
2612
|
+
var init_assets = __esm({
|
|
2613
|
+
"src/assets.ts"() {
|
|
2614
|
+
"use strict";
|
|
2615
|
+
ROOT = findRoot(
|
|
2616
|
+
import.meta.dirname ?? path4.dirname(new URL(import.meta.url).pathname)
|
|
2617
|
+
);
|
|
2618
|
+
ASSETS_BASE = fs10.existsSync(path4.join(ROOT, "dist", "prompt")) ? path4.join(ROOT, "dist") : path4.join(ROOT, "src");
|
|
2619
|
+
}
|
|
2620
|
+
});
|
|
2621
|
+
|
|
2622
|
+
// src/subagents/browserAutomation/prompt.ts
|
|
2623
|
+
import fs11 from "fs";
|
|
2422
2624
|
function getBrowserAutomationPrompt() {
|
|
2423
2625
|
try {
|
|
2424
|
-
const appSpec =
|
|
2626
|
+
const appSpec = fs11.readFileSync("src/app.md", "utf-8").trim();
|
|
2425
2627
|
return `${BASE_PROMPT}
|
|
2426
2628
|
|
|
2427
2629
|
<app_context>
|
|
@@ -2431,14 +2633,12 @@ ${appSpec}
|
|
|
2431
2633
|
return BASE_PROMPT;
|
|
2432
2634
|
}
|
|
2433
2635
|
}
|
|
2434
|
-
var
|
|
2636
|
+
var BASE_PROMPT;
|
|
2435
2637
|
var init_prompt = __esm({
|
|
2436
2638
|
"src/subagents/browserAutomation/prompt.ts"() {
|
|
2437
2639
|
"use strict";
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
PROMPT_PATH = fs10.existsSync(local) ? local : path4.join(base, "subagents", "browserAutomation", "prompt.md");
|
|
2441
|
-
BASE_PROMPT = fs10.readFileSync(PROMPT_PATH, "utf-8").trim();
|
|
2640
|
+
init_assets();
|
|
2641
|
+
BASE_PROMPT = readAsset("subagents/browserAutomation", "prompt.md");
|
|
2442
2642
|
}
|
|
2443
2643
|
});
|
|
2444
2644
|
|
|
@@ -2457,7 +2657,7 @@ var init_browserAutomation = __esm({
|
|
|
2457
2657
|
browserAutomationTool = {
|
|
2458
2658
|
definition: {
|
|
2459
2659
|
name: "runAutomatedBrowserTest",
|
|
2460
|
-
description: "Run an automated browser test against the live preview. Describe what to test \u2014 the agent figures out how. Use after
|
|
2660
|
+
description: "Run an automated browser test against the live preview. Describe what to test \u2014 the agent figures out how. Use after meaningful changes frontend code, to reproduce user-reported issues, or to test end-to-end flows.",
|
|
2461
2661
|
inputSchema: {
|
|
2462
2662
|
type: "object",
|
|
2463
2663
|
properties: {
|
|
@@ -2490,10 +2690,10 @@ var init_browserAutomation = __esm({
|
|
|
2490
2690
|
task: input.task,
|
|
2491
2691
|
tools: BROWSER_TOOLS,
|
|
2492
2692
|
externalTools: BROWSER_EXTERNAL_TOOLS,
|
|
2493
|
-
executeTool: async (name) => {
|
|
2494
|
-
if (name === "
|
|
2693
|
+
executeTool: async (name, _input, _toolCallId, onLog) => {
|
|
2694
|
+
if (name === "screenshotFullPage") {
|
|
2495
2695
|
try {
|
|
2496
|
-
return await captureAndAnalyzeScreenshot();
|
|
2696
|
+
return await captureAndAnalyzeScreenshot({ onLog });
|
|
2497
2697
|
} catch (err) {
|
|
2498
2698
|
return `Error taking screenshot: ${err.message}`;
|
|
2499
2699
|
}
|
|
@@ -2523,7 +2723,7 @@ var init_browserAutomation = __esm({
|
|
|
2523
2723
|
try {
|
|
2524
2724
|
const parsed = JSON.parse(result2);
|
|
2525
2725
|
const screenshotSteps = (parsed.steps || []).filter(
|
|
2526
|
-
(s) => s.command === "
|
|
2726
|
+
(s) => s.command === "screenshotViewport" && s.result?.url
|
|
2527
2727
|
);
|
|
2528
2728
|
if (screenshotSteps.length > 0) {
|
|
2529
2729
|
const batchInput = screenshotSteps.map((s) => ({
|
|
@@ -2541,7 +2741,7 @@ var init_browserAutomation = __esm({
|
|
|
2541
2741
|
const analyses = JSON.parse(batchResult);
|
|
2542
2742
|
let ai = 0;
|
|
2543
2743
|
for (const step of parsed.steps) {
|
|
2544
|
-
if (step.command === "
|
|
2744
|
+
if (step.command === "screenshotViewport" && step.result?.url && ai < analyses.length) {
|
|
2545
2745
|
step.result.analysis = analyses[ai]?.output?.analysis || analyses[ai]?.output || "";
|
|
2546
2746
|
ai++;
|
|
2547
2747
|
}
|
|
@@ -2557,7 +2757,8 @@ var init_browserAutomation = __esm({
|
|
|
2557
2757
|
}
|
|
2558
2758
|
}
|
|
2559
2759
|
return result2;
|
|
2560
|
-
}
|
|
2760
|
+
},
|
|
2761
|
+
toolRegistry: context.toolRegistry
|
|
2561
2762
|
});
|
|
2562
2763
|
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
2563
2764
|
return result.text;
|
|
@@ -2566,262 +2767,471 @@ var init_browserAutomation = __esm({
|
|
|
2566
2767
|
}
|
|
2567
2768
|
});
|
|
2568
2769
|
|
|
2569
|
-
// src/subagents/designExpert/tools.ts
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2770
|
+
// src/subagents/designExpert/tools/searchGoogle.ts
|
|
2771
|
+
var searchGoogle_exports = {};
|
|
2772
|
+
__export(searchGoogle_exports, {
|
|
2773
|
+
definition: () => definition,
|
|
2774
|
+
execute: () => execute
|
|
2775
|
+
});
|
|
2776
|
+
async function execute(input, onLog) {
|
|
2777
|
+
return runCli(
|
|
2778
|
+
`mindstudio search-google --query ${JSON.stringify(input.query)} --export-type json --output-key results --no-meta`,
|
|
2779
|
+
{ onLog }
|
|
2780
|
+
);
|
|
2575
2781
|
}
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2782
|
+
var definition;
|
|
2783
|
+
var init_searchGoogle2 = __esm({
|
|
2784
|
+
"src/subagents/designExpert/tools/searchGoogle.ts"() {
|
|
2785
|
+
"use strict";
|
|
2786
|
+
init_runCli();
|
|
2787
|
+
definition = {
|
|
2788
|
+
name: "searchGoogle",
|
|
2789
|
+
description: 'Search Google for web results. Reserch modern design trends in industries or verticals, "best [domain] apps 2026", ui patterns, or find something specific if the the user has an explicit reference. Prioritize authoritative sources like Figma and other design leaders, avoid random blog spam. Pick one or more URLs from the results and then use `fetchUrl` to get their text content.',
|
|
2790
|
+
inputSchema: {
|
|
2791
|
+
type: "object",
|
|
2792
|
+
properties: {
|
|
2793
|
+
query: {
|
|
2794
|
+
type: "string",
|
|
2795
|
+
description: "The search query."
|
|
2796
|
+
}
|
|
2797
|
+
},
|
|
2798
|
+
required: ["query"]
|
|
2586
2799
|
}
|
|
2587
|
-
}
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2800
|
+
};
|
|
2801
|
+
}
|
|
2802
|
+
});
|
|
2803
|
+
|
|
2804
|
+
// src/subagents/designExpert/tools/fetchUrl.ts
|
|
2805
|
+
var fetchUrl_exports = {};
|
|
2806
|
+
__export(fetchUrl_exports, {
|
|
2807
|
+
definition: () => definition2,
|
|
2808
|
+
execute: () => execute2
|
|
2809
|
+
});
|
|
2810
|
+
async function execute2(input, onLog) {
|
|
2811
|
+
const pageOptions = { onlyMainContent: true };
|
|
2812
|
+
if (input.screenshot) {
|
|
2813
|
+
pageOptions.screenshot = true;
|
|
2814
|
+
}
|
|
2815
|
+
return runCli(
|
|
2816
|
+
`mindstudio scrape-url --url ${JSON.stringify(input.url)} --page-options ${JSON.stringify(JSON.stringify(pageOptions))} --no-meta`,
|
|
2817
|
+
{ onLog }
|
|
2818
|
+
);
|
|
2819
|
+
}
|
|
2820
|
+
var definition2;
|
|
2821
|
+
var init_fetchUrl2 = __esm({
|
|
2822
|
+
"src/subagents/designExpert/tools/fetchUrl.ts"() {
|
|
2823
|
+
"use strict";
|
|
2824
|
+
init_runCli();
|
|
2825
|
+
definition2 = {
|
|
2826
|
+
name: "fetchUrl",
|
|
2827
|
+
description: "Fetch the content of a web page as markdown. Use when reading sites from search results or specific things the user wants to incorporate.",
|
|
2828
|
+
inputSchema: {
|
|
2829
|
+
type: "object",
|
|
2830
|
+
properties: {
|
|
2831
|
+
url: {
|
|
2832
|
+
type: "string",
|
|
2833
|
+
description: "The URL to fetch."
|
|
2834
|
+
}
|
|
2835
|
+
},
|
|
2836
|
+
required: ["url"]
|
|
2596
2837
|
}
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2838
|
+
};
|
|
2839
|
+
}
|
|
2840
|
+
});
|
|
2841
|
+
|
|
2842
|
+
// src/subagents/designExpert/tools/analyzeDesign.ts
|
|
2843
|
+
var analyzeDesign_exports = {};
|
|
2844
|
+
__export(analyzeDesign_exports, {
|
|
2845
|
+
definition: () => definition3,
|
|
2846
|
+
execute: () => execute3
|
|
2847
|
+
});
|
|
2848
|
+
async function execute3(input, onLog) {
|
|
2849
|
+
const url = input.url;
|
|
2850
|
+
const analysisPrompt = input.prompt || DESIGN_REFERENCE_PROMPT;
|
|
2851
|
+
const isImageUrl = /\.(png|jpe?g|webp|gif|svg|avif)(\?|$)/i.test(url);
|
|
2852
|
+
let imageUrl = url;
|
|
2853
|
+
if (!isImageUrl) {
|
|
2854
|
+
const ssUrl = await runCli(
|
|
2855
|
+
`mindstudio screenshot-url --url ${JSON.stringify(url)} --mode viewport --width 1440 --delay 2000 --output-key screenshotUrl --no-meta`,
|
|
2856
|
+
{ timeout: 12e4, onLog }
|
|
2857
|
+
);
|
|
2858
|
+
if (ssUrl.startsWith("Error")) {
|
|
2859
|
+
return `Could not screenshot ${url}: ${ssUrl}`;
|
|
2600
2860
|
}
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
`mindstudio screenshot-url --url ${JSON.stringify(url)} --mode viewport --width 1440 --delay 2000 --output-key screenshotUrl --no-meta`,
|
|
2609
|
-
{ timeout: 12e4 }
|
|
2610
|
-
);
|
|
2611
|
-
if (ssUrl.startsWith("Error")) {
|
|
2612
|
-
return `Could not screenshot ${url}: ${ssUrl}`;
|
|
2613
|
-
}
|
|
2614
|
-
imageUrl = ssUrl;
|
|
2615
|
-
}
|
|
2616
|
-
const analysis = await runCli(
|
|
2617
|
-
`mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(imageUrl)} --output-key analysis --no-meta`,
|
|
2618
|
-
{ timeout: 2e5 }
|
|
2619
|
-
);
|
|
2620
|
-
return isImageUrl ? analysis : `Screenshot: ${imageUrl}
|
|
2861
|
+
imageUrl = ssUrl;
|
|
2862
|
+
}
|
|
2863
|
+
const analysis = await runCli(
|
|
2864
|
+
`mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(imageUrl)} --output-key analysis --no-meta`,
|
|
2865
|
+
{ timeout: 2e5, onLog }
|
|
2866
|
+
);
|
|
2867
|
+
return isImageUrl ? analysis : `Screenshot: ${imageUrl}
|
|
2621
2868
|
|
|
2622
2869
|
${analysis}`;
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2870
|
+
}
|
|
2871
|
+
var DESIGN_REFERENCE_PROMPT, definition3;
|
|
2872
|
+
var init_analyzeDesign = __esm({
|
|
2873
|
+
"src/subagents/designExpert/tools/analyzeDesign.ts"() {
|
|
2874
|
+
"use strict";
|
|
2875
|
+
init_runCli();
|
|
2876
|
+
DESIGN_REFERENCE_PROMPT = `
|
|
2877
|
+
You are analyzing a screenshot of a real website or app for a designer's personal technique/inspiration reference notes.
|
|
2878
|
+
|
|
2879
|
+
Analyze the image and think about what makes the site or app special and unique. What is it doing that is unique, different, original, and creative? What makes it special? What isn't working? What doesn't look or feel good?
|
|
2880
|
+
|
|
2881
|
+
Then, provide the following analysis:
|
|
2882
|
+
|
|
2883
|
+
## Context
|
|
2884
|
+
What is this page, and what does it look like? Very briefly note the industry/vertical and purpose, then describe the composition with enough context to frame the analysis that follows \u2014 what's on the page, where things are positioned, what does the viewport look and feel like. Give enough detail that someone who can't see the image could understand the spatial references in the techniques section. Do not mention specific brand names. Keep it concise.
|
|
2885
|
+
|
|
2886
|
+
## Colors
|
|
2887
|
+
List the palette as hex values with short labels. Just the swatches \u2014 no "strategy" paragraph.
|
|
2888
|
+
|
|
2889
|
+
## Typography
|
|
2890
|
+
Brief description of the types used on the page. If you can identify the actual typeface name, provide it, otherwise provide a concrete description (e.g., "ultra-condensed grotesque, ~900 weight, tracked tight at maybe -0.03em, all-caps"). Include size relationships if notable (e.g., "hero text is viewport-width, body is 14px").
|
|
2891
|
+
|
|
2892
|
+
## Techniques
|
|
2893
|
+
Identify the specific design moves that make this page interesting and unique, described in terms of how a designer with a technical background would write them down as notes in their notebook for inspiration. Focus only on the non-obvious, hard-to-think-of techniques \u2014 the things that make this page gallery-worthy. Skip basics like "high contrast CTA" or "generous whitespace" that any competent designer already knows.
|
|
2894
|
+
|
|
2895
|
+
Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.
|
|
2896
|
+
`;
|
|
2897
|
+
definition3 = {
|
|
2898
|
+
name: "analyzeDesign",
|
|
2899
|
+
description: "Analyze the visual design of a website or image URL. Websites are automatically screenshotted first. If no prompt is provided, performs a full design reference analysis (mood, color, typography, layout, distinctiveness). Provide a custom prompt to ask a specific design question instead.",
|
|
2900
|
+
inputSchema: {
|
|
2901
|
+
type: "object",
|
|
2902
|
+
properties: {
|
|
2903
|
+
url: {
|
|
2904
|
+
type: "string",
|
|
2905
|
+
description: "URL to analyze. Can be an image URL or a website URL (will be screenshotted)."
|
|
2906
|
+
},
|
|
2907
|
+
prompt: {
|
|
2908
|
+
type: "string",
|
|
2909
|
+
description: "Optional custom analysis prompt. If omitted, performs the standard design reference analysis."
|
|
2636
2910
|
}
|
|
2637
|
-
}
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2911
|
+
},
|
|
2912
|
+
required: ["url"]
|
|
2913
|
+
}
|
|
2914
|
+
};
|
|
2915
|
+
}
|
|
2916
|
+
});
|
|
2917
|
+
|
|
2918
|
+
// src/subagents/designExpert/tools/analyzeImage.ts
|
|
2919
|
+
var analyzeImage_exports = {};
|
|
2920
|
+
__export(analyzeImage_exports, {
|
|
2921
|
+
definition: () => definition4,
|
|
2922
|
+
execute: () => execute4
|
|
2923
|
+
});
|
|
2924
|
+
async function execute4(input, onLog) {
|
|
2925
|
+
const imageUrl = input.imageUrl;
|
|
2926
|
+
const prompt = input.prompt || DEFAULT_PROMPT;
|
|
2927
|
+
const analysis = await runCli(
|
|
2928
|
+
`mindstudio analyze-image --prompt ${JSON.stringify(prompt)} --image-url ${JSON.stringify(imageUrl)} --output-key analysis --no-meta`,
|
|
2929
|
+
{ timeout: 2e5, onLog }
|
|
2930
|
+
);
|
|
2931
|
+
return JSON.stringify({ url: imageUrl, analysis });
|
|
2932
|
+
}
|
|
2933
|
+
var DEFAULT_PROMPT, definition4;
|
|
2934
|
+
var init_analyzeImage = __esm({
|
|
2935
|
+
"src/subagents/designExpert/tools/analyzeImage.ts"() {
|
|
2936
|
+
"use strict";
|
|
2937
|
+
init_runCli();
|
|
2938
|
+
DEFAULT_PROMPT = "Describe everything visible in this image \u2014 every element, its position, its size relative to the frame, its colors, its content. Be comprhensive, thorough and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components). Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.";
|
|
2939
|
+
definition4 = {
|
|
2940
|
+
name: "analyzeImage",
|
|
2941
|
+
description: "Analyze an image by URL. Returns a detailed description of everything visible. Provide a custom prompt to ask a specific question instead of the default full description.",
|
|
2942
|
+
inputSchema: {
|
|
2943
|
+
type: "object",
|
|
2944
|
+
properties: {
|
|
2945
|
+
imageUrl: {
|
|
2946
|
+
type: "string",
|
|
2947
|
+
description: "The image URL to analyze."
|
|
2948
|
+
},
|
|
2949
|
+
prompt: {
|
|
2950
|
+
type: "string",
|
|
2951
|
+
description: "Optional custom analysis prompt. If omitted, describes everything visible in the image."
|
|
2652
2952
|
}
|
|
2653
|
-
}
|
|
2654
|
-
|
|
2655
|
-
`mindstudio batch '${JSON.stringify(steps)}' --no-meta`,
|
|
2656
|
-
{ jsonLogs: true, timeout: 2e5 }
|
|
2657
|
-
);
|
|
2658
|
-
try {
|
|
2659
|
-
const parsed = JSON.parse(batchResult);
|
|
2660
|
-
imageUrls = parsed.results.map(
|
|
2661
|
-
(r) => r.output?.imageUrl ?? `Error: ${r.error}`
|
|
2662
|
-
);
|
|
2663
|
-
} catch {
|
|
2664
|
-
return batchResult;
|
|
2665
|
-
}
|
|
2953
|
+
},
|
|
2954
|
+
required: ["imageUrl"]
|
|
2666
2955
|
}
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2956
|
+
};
|
|
2957
|
+
}
|
|
2958
|
+
});
|
|
2959
|
+
|
|
2960
|
+
// src/subagents/designExpert/tools/screenshot.ts
|
|
2961
|
+
var screenshot_exports = {};
|
|
2962
|
+
__export(screenshot_exports, {
|
|
2963
|
+
definition: () => definition5,
|
|
2964
|
+
execute: () => execute5
|
|
2965
|
+
});
|
|
2966
|
+
async function execute5(input, onLog) {
|
|
2967
|
+
try {
|
|
2968
|
+
return await captureAndAnalyzeScreenshot({
|
|
2969
|
+
prompt: input.prompt,
|
|
2970
|
+
onLog
|
|
2971
|
+
});
|
|
2972
|
+
} catch (err) {
|
|
2973
|
+
return `Error taking screenshot: ${err.message}`;
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
var definition5;
|
|
2977
|
+
var init_screenshot3 = __esm({
|
|
2978
|
+
"src/subagents/designExpert/tools/screenshot.ts"() {
|
|
2979
|
+
"use strict";
|
|
2980
|
+
init_screenshot();
|
|
2981
|
+
definition5 = {
|
|
2982
|
+
name: "screenshot",
|
|
2983
|
+
description: "Capture a full-height screenshot of the current app preview. Returns a CDN URL along with visual analysis. Use to review the current state of the UI being built. Remember, the screenshot analysis is not overly precise - for example, it cannot reliably identify specific fonts by name \u2014 it can only describe what letterforms look like.",
|
|
2984
|
+
inputSchema: {
|
|
2985
|
+
type: "object",
|
|
2986
|
+
properties: {
|
|
2987
|
+
prompt: {
|
|
2988
|
+
type: "string",
|
|
2989
|
+
description: "Optional specific question about the screenshot."
|
|
2671
2990
|
}
|
|
2672
|
-
|
|
2673
|
-
`mindstudio analyze-image --prompt ${JSON.stringify(ANALYZE_PROMPT)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`,
|
|
2674
|
-
{ timeout: 2e5 }
|
|
2675
|
-
);
|
|
2676
|
-
return { url, prompt: prompts[i], analysis, width, height };
|
|
2677
|
-
})
|
|
2678
|
-
);
|
|
2679
|
-
return `%%JSON%%${JSON.stringify({ images })}`;
|
|
2680
|
-
}
|
|
2681
|
-
case "runBrowserTest": {
|
|
2682
|
-
if (!context) {
|
|
2683
|
-
return "Error: browser testing requires execution context (only available in headless mode)";
|
|
2991
|
+
}
|
|
2684
2992
|
}
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
});
|
|
2996
|
+
|
|
2997
|
+
// src/subagents/designExpert/tools/_seedream.ts
|
|
2998
|
+
async function seedreamGenerate(opts) {
|
|
2999
|
+
const { prompts, sourceImages, transparentBackground, onLog } = opts;
|
|
3000
|
+
const width = opts.width || 2048;
|
|
3001
|
+
const height = opts.height || 2048;
|
|
3002
|
+
const config = { width, height };
|
|
3003
|
+
if (sourceImages?.length) {
|
|
3004
|
+
config.images = sourceImages;
|
|
3005
|
+
}
|
|
3006
|
+
let imageUrls;
|
|
3007
|
+
if (prompts.length === 1) {
|
|
3008
|
+
const step = JSON.stringify({
|
|
3009
|
+
prompt: prompts[0],
|
|
3010
|
+
imageModelOverride: {
|
|
3011
|
+
model: "seedream-4.5",
|
|
3012
|
+
config
|
|
3013
|
+
}
|
|
3014
|
+
});
|
|
3015
|
+
const url = await runCli(
|
|
3016
|
+
`mindstudio generate-image ${JSON.stringify(step)} --output-key imageUrl --no-meta`,
|
|
3017
|
+
{ jsonLogs: true, timeout: 2e5, onLog }
|
|
3018
|
+
);
|
|
3019
|
+
imageUrls = [url];
|
|
3020
|
+
} else {
|
|
3021
|
+
const steps = prompts.map((prompt) => ({
|
|
3022
|
+
stepType: "generateImage",
|
|
3023
|
+
step: {
|
|
3024
|
+
prompt,
|
|
3025
|
+
imageModelOverride: {
|
|
3026
|
+
model: "seedream-4.5",
|
|
3027
|
+
config
|
|
2690
3028
|
}
|
|
3029
|
+
}
|
|
3030
|
+
}));
|
|
3031
|
+
const batchResult = await runCli(
|
|
3032
|
+
`mindstudio batch ${JSON.stringify(JSON.stringify(steps))} --no-meta`,
|
|
3033
|
+
{ jsonLogs: true, timeout: 2e5, onLog }
|
|
3034
|
+
);
|
|
3035
|
+
try {
|
|
3036
|
+
const parsed = JSON.parse(batchResult);
|
|
3037
|
+
imageUrls = parsed.map(
|
|
3038
|
+
(r) => r.output?.imageUrl ?? `Error: ${r.error}`
|
|
2691
3039
|
);
|
|
3040
|
+
} catch {
|
|
3041
|
+
return batchResult;
|
|
2692
3042
|
}
|
|
2693
|
-
default:
|
|
2694
|
-
return `Error: unknown tool "${name}"`;
|
|
2695
3043
|
}
|
|
3044
|
+
if (transparentBackground) {
|
|
3045
|
+
imageUrls = await Promise.all(
|
|
3046
|
+
imageUrls.map(async (url) => {
|
|
3047
|
+
if (url.startsWith("Error")) {
|
|
3048
|
+
return url;
|
|
3049
|
+
}
|
|
3050
|
+
const result = await runCli(
|
|
3051
|
+
`mindstudio remove-background-from-image --image-url ${JSON.stringify(url)} --output-key imageUrl --no-meta`,
|
|
3052
|
+
{ timeout: 2e5, onLog }
|
|
3053
|
+
);
|
|
3054
|
+
return result.startsWith("Error") ? url : result;
|
|
3055
|
+
})
|
|
3056
|
+
);
|
|
3057
|
+
}
|
|
3058
|
+
const images = await Promise.all(
|
|
3059
|
+
imageUrls.map(async (url, i) => {
|
|
3060
|
+
if (url.startsWith("Error")) {
|
|
3061
|
+
return { prompt: prompts[i], error: url };
|
|
3062
|
+
}
|
|
3063
|
+
const analysis = await runCli(
|
|
3064
|
+
`mindstudio analyze-image --prompt ${JSON.stringify(ANALYZE_PROMPT)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`,
|
|
3065
|
+
{ timeout: 2e5, onLog }
|
|
3066
|
+
);
|
|
3067
|
+
return { url, prompt: prompts[i], analysis, width, height };
|
|
3068
|
+
})
|
|
3069
|
+
);
|
|
3070
|
+
return JSON.stringify({ images });
|
|
2696
3071
|
}
|
|
2697
|
-
var
|
|
2698
|
-
var
|
|
2699
|
-
"src/subagents/designExpert/tools.ts"() {
|
|
3072
|
+
var ANALYZE_PROMPT;
|
|
3073
|
+
var init_seedream = __esm({
|
|
3074
|
+
"src/subagents/designExpert/tools/_seedream.ts"() {
|
|
2700
3075
|
"use strict";
|
|
2701
3076
|
init_runCli();
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
3077
|
+
ANALYZE_PROMPT = "You are reviewing this image for a visual designer sourcing assets for a project. Describe: what the image depicts, the mood and color palette, how the lighting and composition work, whether there are any issues (unwanted text, artifacts, distortions), and how it could be used in a layout (hero background, feature section, card texture, etc). Be concise and practical. Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.";
|
|
3078
|
+
}
|
|
3079
|
+
});
|
|
3080
|
+
|
|
3081
|
+
// src/subagents/designExpert/tools/generateImages.ts
|
|
3082
|
+
var generateImages_exports = {};
|
|
3083
|
+
__export(generateImages_exports, {
|
|
3084
|
+
definition: () => definition6,
|
|
3085
|
+
execute: () => execute6
|
|
3086
|
+
});
|
|
3087
|
+
async function execute6(input, onLog) {
|
|
3088
|
+
return seedreamGenerate({
|
|
3089
|
+
prompts: input.prompts,
|
|
3090
|
+
width: input.width,
|
|
3091
|
+
height: input.height,
|
|
3092
|
+
transparentBackground: input.transparentBackground,
|
|
3093
|
+
onLog
|
|
3094
|
+
});
|
|
3095
|
+
}
|
|
3096
|
+
var definition6;
|
|
3097
|
+
var init_generateImages = __esm({
|
|
3098
|
+
"src/subagents/designExpert/tools/generateImages.ts"() {
|
|
3099
|
+
"use strict";
|
|
3100
|
+
init_seedream();
|
|
3101
|
+
definition6 = {
|
|
3102
|
+
name: "generateImages",
|
|
3103
|
+
description: "Generate images using AI. Returns CDN URLs with a quality analysis for each image. Produces high-quality results for everything from photorealistic images and abstract/creative visuals. Pass multiple prompts to generate in parallel. No need to analyze images separately after generating \u2014 the analysis is included.",
|
|
3104
|
+
inputSchema: {
|
|
3105
|
+
type: "object",
|
|
3106
|
+
properties: {
|
|
3107
|
+
prompts: {
|
|
3108
|
+
type: "array",
|
|
3109
|
+
items: {
|
|
3110
|
+
type: "string"
|
|
2730
3111
|
},
|
|
2731
|
-
|
|
2732
|
-
type: "boolean",
|
|
2733
|
-
description: "Capture a screenshot of the page. Use when you need to see the visual design, not just the text."
|
|
2734
|
-
}
|
|
3112
|
+
description: "One or more image generation prompts. Be detailed: describe style, mood, composition, colors. Multiple prompts run in parallel."
|
|
2735
3113
|
},
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
{
|
|
2740
|
-
name: "analyzeReferenceImageOrUrl",
|
|
2741
|
-
description: "Analyze any visual \u2014 pass an image URL or a website URL. Websites are automatically screenshotted first. If no prompt is provided, performs a full design reference analysis (mood, color, typography, layout, distinctiveness). Provide a custom prompt to ask a specific question instead.",
|
|
2742
|
-
inputSchema: {
|
|
2743
|
-
type: "object",
|
|
2744
|
-
properties: {
|
|
2745
|
-
url: {
|
|
2746
|
-
type: "string",
|
|
2747
|
-
description: "URL to analyze. Can be an image URL or a website URL (will be screenshotted)."
|
|
2748
|
-
},
|
|
2749
|
-
prompt: {
|
|
2750
|
-
type: "string",
|
|
2751
|
-
description: "Optional custom analysis prompt. If omitted, performs the standard design reference analysis."
|
|
2752
|
-
}
|
|
3114
|
+
width: {
|
|
3115
|
+
type: "number",
|
|
3116
|
+
description: "Image width in pixels. Default 2048. Range: 2048-4096."
|
|
2753
3117
|
},
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
{
|
|
2758
|
-
name: "screenshot",
|
|
2759
|
-
description: "Capture a screenshot of the app preview. Returns a CDN URL with visual analysis. Use to review the current state of the UI being built. By default captures the viewport. Set fullPage to capture the entire scrollable page.",
|
|
2760
|
-
inputSchema: {
|
|
2761
|
-
type: "object",
|
|
2762
|
-
properties: {
|
|
2763
|
-
prompt: {
|
|
2764
|
-
type: "string",
|
|
2765
|
-
description: "Optional specific question about the screenshot."
|
|
2766
|
-
},
|
|
2767
|
-
fullPage: {
|
|
2768
|
-
type: "boolean",
|
|
2769
|
-
description: "Capture the full scrollable page instead of just the viewport. Use when you need to see below-the-fold content."
|
|
2770
|
-
}
|
|
2771
|
-
}
|
|
2772
|
-
}
|
|
2773
|
-
},
|
|
2774
|
-
{
|
|
2775
|
-
name: "runBrowserTest",
|
|
2776
|
-
description: "Run an automated browser test against the live app preview. Use to verify implementation details via getComputedStyle: font-family names, exact colors, spacing, borders, shadows, font sizes, transforms. Only use this to evaluate computed CSS properties that can't be deduced from sceenshots.",
|
|
2777
|
-
inputSchema: {
|
|
2778
|
-
type: "object",
|
|
2779
|
-
properties: {
|
|
2780
|
-
task: {
|
|
2781
|
-
type: "string",
|
|
2782
|
-
description: 'What to verify, in natural language. Focus on measurable properties: "Check the hero cards have border-radius: 24px and box-shadow" or "Verify the background color of the CTA section is #C4FF0D".'
|
|
2783
|
-
}
|
|
3118
|
+
height: {
|
|
3119
|
+
type: "number",
|
|
3120
|
+
description: "Image height in pixels. Default 2048. Range: 2048-4096."
|
|
2784
3121
|
},
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
3122
|
+
transparentBackground: {
|
|
3123
|
+
type: "boolean",
|
|
3124
|
+
description: "Remove the background from generated images, producing transparent PNGs. Useful for icons, logos, product shots, and assets that need to be composited onto other backgrounds."
|
|
3125
|
+
}
|
|
3126
|
+
},
|
|
3127
|
+
required: ["prompts"]
|
|
3128
|
+
}
|
|
3129
|
+
};
|
|
3130
|
+
}
|
|
3131
|
+
});
|
|
3132
|
+
|
|
3133
|
+
// src/subagents/designExpert/tools/editImages.ts
|
|
3134
|
+
var editImages_exports = {};
|
|
3135
|
+
__export(editImages_exports, {
|
|
3136
|
+
definition: () => definition7,
|
|
3137
|
+
execute: () => execute7
|
|
3138
|
+
});
|
|
3139
|
+
async function execute7(input, onLog) {
|
|
3140
|
+
return seedreamGenerate({
|
|
3141
|
+
prompts: input.prompts,
|
|
3142
|
+
sourceImages: input.sourceImages,
|
|
3143
|
+
width: input.width,
|
|
3144
|
+
height: input.height,
|
|
3145
|
+
transparentBackground: input.transparentBackground,
|
|
3146
|
+
onLog
|
|
3147
|
+
});
|
|
3148
|
+
}
|
|
3149
|
+
var definition7;
|
|
3150
|
+
var init_editImages = __esm({
|
|
3151
|
+
"src/subagents/designExpert/tools/editImages.ts"() {
|
|
3152
|
+
"use strict";
|
|
3153
|
+
init_seedream();
|
|
3154
|
+
definition7 = {
|
|
3155
|
+
name: "editImages",
|
|
3156
|
+
description: "Edit or transform existing images using AI. Provide one or more source image URLs as reference and a prompt describing the desired edit. Use for compositing, style transfer, subject transformation, blending multiple references, or incorporating one or more ferences into something new. Returns CDN URLs with analysis.",
|
|
3157
|
+
inputSchema: {
|
|
3158
|
+
type: "object",
|
|
3159
|
+
properties: {
|
|
3160
|
+
prompts: {
|
|
3161
|
+
type: "array",
|
|
3162
|
+
items: {
|
|
3163
|
+
type: "string"
|
|
2800
3164
|
},
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
3165
|
+
description: "One or more edit prompts describing how to transform the source images. Multiple prompts run in parallel, each using the same source images."
|
|
3166
|
+
},
|
|
3167
|
+
sourceImages: {
|
|
3168
|
+
type: "array",
|
|
3169
|
+
items: {
|
|
3170
|
+
type: "string"
|
|
2804
3171
|
},
|
|
2805
|
-
|
|
2806
|
-
type: "number",
|
|
2807
|
-
description: "Image height in pixels. Default 2048. Range: 2048-4096."
|
|
2808
|
-
}
|
|
3172
|
+
description: "One or more source/reference image URLs. These are used as the basis for the edit \u2014 the AI will use them as reference for style, subject, or composition."
|
|
2809
3173
|
},
|
|
2810
|
-
|
|
2811
|
-
|
|
3174
|
+
width: {
|
|
3175
|
+
type: "number",
|
|
3176
|
+
description: "Output width in pixels. Default 2048. Range: 2048-4096."
|
|
3177
|
+
},
|
|
3178
|
+
height: {
|
|
3179
|
+
type: "number",
|
|
3180
|
+
description: "Output height in pixels. Default 2048. Range: 2048-4096."
|
|
3181
|
+
},
|
|
3182
|
+
transparentBackground: {
|
|
3183
|
+
type: "boolean",
|
|
3184
|
+
description: "Remove the background from output images, producing transparent PNGs."
|
|
3185
|
+
}
|
|
3186
|
+
},
|
|
3187
|
+
required: ["prompts", "sourceImages"]
|
|
2812
3188
|
}
|
|
2813
|
-
|
|
3189
|
+
};
|
|
3190
|
+
}
|
|
3191
|
+
});
|
|
3192
|
+
|
|
3193
|
+
// src/subagents/designExpert/tools/index.ts
|
|
3194
|
+
async function executeDesignExpertTool(name, input, context, toolCallId, onLog) {
|
|
3195
|
+
const tool = tools[name];
|
|
3196
|
+
if (!tool) {
|
|
3197
|
+
return `Error: unknown tool "${name}"`;
|
|
3198
|
+
}
|
|
3199
|
+
return tool.execute(input, onLog);
|
|
3200
|
+
}
|
|
3201
|
+
var tools, DESIGN_EXPERT_TOOLS;
|
|
3202
|
+
var init_tools2 = __esm({
|
|
3203
|
+
"src/subagents/designExpert/tools/index.ts"() {
|
|
3204
|
+
"use strict";
|
|
3205
|
+
init_searchGoogle2();
|
|
3206
|
+
init_fetchUrl2();
|
|
3207
|
+
init_analyzeDesign();
|
|
3208
|
+
init_analyzeImage();
|
|
3209
|
+
init_screenshot3();
|
|
3210
|
+
init_generateImages();
|
|
3211
|
+
init_editImages();
|
|
3212
|
+
tools = {
|
|
3213
|
+
searchGoogle: searchGoogle_exports,
|
|
3214
|
+
fetchUrl: fetchUrl_exports,
|
|
3215
|
+
analyzeDesign: analyzeDesign_exports,
|
|
3216
|
+
analyzeImage: analyzeImage_exports,
|
|
3217
|
+
screenshot: screenshot_exports,
|
|
3218
|
+
generateImages: generateImages_exports,
|
|
3219
|
+
editImages: editImages_exports
|
|
3220
|
+
};
|
|
3221
|
+
DESIGN_EXPERT_TOOLS = Object.values(tools).map(
|
|
3222
|
+
(t) => t.definition
|
|
3223
|
+
);
|
|
2814
3224
|
}
|
|
2815
3225
|
});
|
|
2816
3226
|
|
|
2817
3227
|
// src/subagents/common/context.ts
|
|
2818
3228
|
import fs12 from "fs";
|
|
2819
|
-
import
|
|
3229
|
+
import path5 from "path";
|
|
2820
3230
|
function walkMdFiles(dir, skip) {
|
|
2821
3231
|
const files = [];
|
|
2822
3232
|
try {
|
|
2823
3233
|
for (const entry of fs12.readdirSync(dir, { withFileTypes: true })) {
|
|
2824
|
-
const full =
|
|
3234
|
+
const full = path5.join(dir, entry.name);
|
|
2825
3235
|
if (entry.isDirectory()) {
|
|
2826
3236
|
if (!skip?.has(entry.name)) {
|
|
2827
3237
|
files.push(...walkMdFiles(full, skip));
|
|
@@ -2915,23 +3325,7 @@ var init_context = __esm({
|
|
|
2915
3325
|
}
|
|
2916
3326
|
});
|
|
2917
3327
|
|
|
2918
|
-
// src/subagents/designExpert/
|
|
2919
|
-
import fs13 from "fs";
|
|
2920
|
-
import path7 from "path";
|
|
2921
|
-
function resolvePath2(filename) {
|
|
2922
|
-
const local4 = path7.join(base3, filename);
|
|
2923
|
-
return fs13.existsSync(local4) ? local4 : path7.join(base3, "subagents", "designExpert", filename);
|
|
2924
|
-
}
|
|
2925
|
-
function readFile(filename) {
|
|
2926
|
-
return fs13.readFileSync(resolvePath2(filename), "utf-8").trim();
|
|
2927
|
-
}
|
|
2928
|
-
function readJson(filename, fallback) {
|
|
2929
|
-
try {
|
|
2930
|
-
return JSON.parse(fs13.readFileSync(resolvePath2(filename), "utf-8"));
|
|
2931
|
-
} catch {
|
|
2932
|
-
return fallback;
|
|
2933
|
-
}
|
|
2934
|
-
}
|
|
3328
|
+
// src/subagents/designExpert/data/getFontLibrarySample.ts
|
|
2935
3329
|
function sample(arr, n) {
|
|
2936
3330
|
if (arr.length <= n) {
|
|
2937
3331
|
return [...arr];
|
|
@@ -2943,10 +3337,12 @@ function sample(arr, n) {
|
|
|
2943
3337
|
}
|
|
2944
3338
|
return copy.slice(0, n);
|
|
2945
3339
|
}
|
|
2946
|
-
function
|
|
2947
|
-
const fonts = sample(fontData.fonts,
|
|
2948
|
-
const pairings = sample(fontData.pairings,
|
|
2949
|
-
|
|
3340
|
+
function getFontLibrarySample() {
|
|
3341
|
+
const fonts = sample(fontData.fonts, 60);
|
|
3342
|
+
const pairings = sample(fontData.pairings, 30);
|
|
3343
|
+
if (!fonts.length) {
|
|
3344
|
+
return "";
|
|
3345
|
+
}
|
|
2950
3346
|
const fontList = fonts.map((f) => {
|
|
2951
3347
|
let cssInfo = "";
|
|
2952
3348
|
if (f.source === "fontshare") {
|
|
@@ -2962,60 +3358,102 @@ function getDesignExpertPrompt() {
|
|
|
2962
3358
|
const pairingList = pairings.map(
|
|
2963
3359
|
(p) => `- **${p.heading.font}** (${p.heading.weight}) heading + **${p.body.font}** (${p.body.weight}) body`
|
|
2964
3360
|
).join("\n");
|
|
2965
|
-
|
|
2966
|
-
##
|
|
3361
|
+
return `
|
|
3362
|
+
## Font Library
|
|
2967
3363
|
|
|
2968
|
-
A random sample from
|
|
2969
|
-
|
|
3364
|
+
A random sample from a curated font library. Use these as starting points for font selection.
|
|
3365
|
+
|
|
3366
|
+
### Fonts
|
|
2970
3367
|
|
|
2971
3368
|
${fontList}
|
|
2972
3369
|
|
|
2973
|
-
###
|
|
3370
|
+
### Pairings
|
|
3371
|
+
|
|
3372
|
+
${pairingList}`.trim();
|
|
3373
|
+
}
|
|
3374
|
+
var fontData;
|
|
3375
|
+
var init_getFontLibrarySample = __esm({
|
|
3376
|
+
"src/subagents/designExpert/data/getFontLibrarySample.ts"() {
|
|
3377
|
+
"use strict";
|
|
3378
|
+
init_assets();
|
|
3379
|
+
fontData = readJsonAsset(
|
|
3380
|
+
{ cssUrlPattern: "", fonts: [], pairings: [] },
|
|
3381
|
+
"subagents/designExpert/data/sources/fonts.json"
|
|
3382
|
+
);
|
|
3383
|
+
}
|
|
3384
|
+
});
|
|
2974
3385
|
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
3386
|
+
// src/subagents/designExpert/data/getDesignReferencesSample.ts
|
|
3387
|
+
function sample2(arr, n) {
|
|
3388
|
+
if (arr.length <= n) {
|
|
3389
|
+
return [...arr];
|
|
3390
|
+
}
|
|
3391
|
+
const copy = [...arr];
|
|
3392
|
+
for (let i = copy.length - 1; i > 0; i--) {
|
|
3393
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
3394
|
+
[copy[i], copy[j]] = [copy[j], copy[i]];
|
|
3395
|
+
}
|
|
3396
|
+
return copy.slice(0, n);
|
|
3397
|
+
}
|
|
3398
|
+
function getDesignReferencesSample() {
|
|
3399
|
+
const images = sample2(inspirationImages, 30);
|
|
3400
|
+
if (!images.length) {
|
|
3401
|
+
return "";
|
|
3402
|
+
}
|
|
3403
|
+
const imageList = images.map((img, i) => `### Reference ${i + 1}
|
|
3404
|
+
${img.analysis}`).join("\n\n");
|
|
3405
|
+
return `
|
|
3406
|
+
## Design References
|
|
2980
3407
|
|
|
2981
3408
|
This is what the bar looks like. These are real sites that made it onto curated design galleries because they did something bold, intentional, and memorable. Use them as inspiration and let the takeaways guide your work. Your designs should feel like they belong in this company.
|
|
2982
3409
|
|
|
2983
|
-
${imageList}
|
|
2984
|
-
|
|
3410
|
+
${imageList}`.trim();
|
|
3411
|
+
}
|
|
3412
|
+
var inspirationImages;
|
|
3413
|
+
var init_getDesignReferencesSample = __esm({
|
|
3414
|
+
"src/subagents/designExpert/data/getDesignReferencesSample.ts"() {
|
|
3415
|
+
"use strict";
|
|
3416
|
+
init_assets();
|
|
3417
|
+
inspirationImages = readJsonAsset(
|
|
3418
|
+
{ images: [] },
|
|
3419
|
+
"subagents/designExpert/data/sources/inspiration.json"
|
|
3420
|
+
).images;
|
|
3421
|
+
}
|
|
3422
|
+
});
|
|
3423
|
+
|
|
3424
|
+
// src/subagents/designExpert/prompt.ts
|
|
3425
|
+
import fs13 from "fs";
|
|
3426
|
+
function getDesignExpertPrompt() {
|
|
2985
3427
|
const specContext = loadSpecContext();
|
|
2986
3428
|
let prompt = PROMPT_TEMPLATE.replace(
|
|
2987
|
-
"{{
|
|
2988
|
-
|
|
2989
|
-
).replace("{{
|
|
3429
|
+
"{{font_library}}",
|
|
3430
|
+
getFontLibrarySample()
|
|
3431
|
+
).replace("{{design_references}}", getDesignReferencesSample());
|
|
2990
3432
|
if (specContext) {
|
|
2991
3433
|
prompt += `
|
|
2992
3434
|
|
|
2993
3435
|
${specContext}`;
|
|
3436
|
+
}
|
|
3437
|
+
try {
|
|
3438
|
+
fs13.writeFileSync(`.design-prompt.md`, prompt);
|
|
3439
|
+
} catch {
|
|
2994
3440
|
}
|
|
2995
3441
|
return prompt;
|
|
2996
3442
|
}
|
|
2997
|
-
var
|
|
3443
|
+
var SUBAGENT, RUNTIME_PLACEHOLDERS, PROMPT_TEMPLATE;
|
|
2998
3444
|
var init_prompt2 = __esm({
|
|
2999
3445
|
"src/subagents/designExpert/prompt.ts"() {
|
|
3000
3446
|
"use strict";
|
|
3447
|
+
init_assets();
|
|
3001
3448
|
init_context();
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
])
|
|
3007
|
-
PROMPT_TEMPLATE = readFile("prompt.md").replace(/\{\{([^}]+)\}\}/g, (match, key) => {
|
|
3449
|
+
init_getFontLibrarySample();
|
|
3450
|
+
init_getDesignReferencesSample();
|
|
3451
|
+
SUBAGENT = "subagents/designExpert";
|
|
3452
|
+
RUNTIME_PLACEHOLDERS = /* @__PURE__ */ new Set(["font_library", "design_references"]);
|
|
3453
|
+
PROMPT_TEMPLATE = readAsset(SUBAGENT, "prompt.md").replace(/\{\{([^}]+)\}\}/g, (match, key) => {
|
|
3008
3454
|
const k = key.trim();
|
|
3009
|
-
return RUNTIME_PLACEHOLDERS.has(k) ? match :
|
|
3455
|
+
return RUNTIME_PLACEHOLDERS.has(k) ? match : readAsset(SUBAGENT, k);
|
|
3010
3456
|
}).replace(/\n{3,}/g, "\n\n");
|
|
3011
|
-
fontData = readJson("data/fonts.json", {
|
|
3012
|
-
cssUrlPattern: "",
|
|
3013
|
-
fonts: [],
|
|
3014
|
-
pairings: []
|
|
3015
|
-
});
|
|
3016
|
-
inspirationImages = readJson("data/inspiration.json", {
|
|
3017
|
-
images: []
|
|
3018
|
-
}).images;
|
|
3019
3457
|
}
|
|
3020
3458
|
});
|
|
3021
3459
|
|
|
@@ -3039,7 +3477,7 @@ Visual design expert. Describe the situation and what you need \u2014 the agent
|
|
|
3039
3477
|
properties: {
|
|
3040
3478
|
task: {
|
|
3041
3479
|
type: "string",
|
|
3042
|
-
description: "What you need, in natural language. Include context about the
|
|
3480
|
+
description: "What you need, in natural language. Include context about the project when relevant."
|
|
3043
3481
|
}
|
|
3044
3482
|
},
|
|
3045
3483
|
required: ["task"]
|
|
@@ -3054,14 +3492,15 @@ Visual design expert. Describe the situation and what you need \u2014 the agent
|
|
|
3054
3492
|
task: input.task,
|
|
3055
3493
|
tools: DESIGN_EXPERT_TOOLS,
|
|
3056
3494
|
externalTools: /* @__PURE__ */ new Set(),
|
|
3057
|
-
executeTool: (name, input2, toolCallId) => executeDesignExpertTool(name, input2, context, toolCallId),
|
|
3495
|
+
executeTool: (name, input2, toolCallId, onLog) => executeDesignExpertTool(name, input2, context, toolCallId, onLog),
|
|
3058
3496
|
apiConfig: context.apiConfig,
|
|
3059
3497
|
model: context.model,
|
|
3060
3498
|
subAgentId: "visualDesignExpert",
|
|
3061
3499
|
signal: context.signal,
|
|
3062
3500
|
parentToolId: context.toolCallId,
|
|
3063
3501
|
onEvent: context.onEvent,
|
|
3064
|
-
resolveExternalTool: context.resolveExternalTool
|
|
3502
|
+
resolveExternalTool: context.resolveExternalTool,
|
|
3503
|
+
toolRegistry: context.toolRegistry
|
|
3065
3504
|
});
|
|
3066
3505
|
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
3067
3506
|
return result.text;
|
|
@@ -3176,7 +3615,7 @@ var init_tools3 = __esm({
|
|
|
3176
3615
|
|
|
3177
3616
|
// src/subagents/productVision/executor.ts
|
|
3178
3617
|
import fs14 from "fs";
|
|
3179
|
-
import
|
|
3618
|
+
import path6 from "path";
|
|
3180
3619
|
function formatRequires(requires) {
|
|
3181
3620
|
return requires.length === 0 ? "[]" : `[${requires.map((r) => `"${r}"`).join(", ")}]`;
|
|
3182
3621
|
}
|
|
@@ -3191,7 +3630,7 @@ async function executeVisionTool(name, input) {
|
|
|
3191
3630
|
requires,
|
|
3192
3631
|
body
|
|
3193
3632
|
} = input;
|
|
3194
|
-
const filePath =
|
|
3633
|
+
const filePath = path6.join(ROADMAP_DIR, `${slug}.md`);
|
|
3195
3634
|
try {
|
|
3196
3635
|
fs14.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
3197
3636
|
const oldContent = fs14.existsSync(filePath) ? fs14.readFileSync(filePath, "utf-8") : "";
|
|
@@ -3217,7 +3656,7 @@ ${unifiedDiff(filePath, oldContent, content)}`;
|
|
|
3217
3656
|
}
|
|
3218
3657
|
case "updateRoadmapItem": {
|
|
3219
3658
|
const { slug } = input;
|
|
3220
|
-
const filePath =
|
|
3659
|
+
const filePath = path6.join(ROADMAP_DIR, `${slug}.md`);
|
|
3221
3660
|
try {
|
|
3222
3661
|
if (!fs14.existsSync(filePath)) {
|
|
3223
3662
|
return `Error: ${filePath} does not exist`;
|
|
@@ -3285,7 +3724,7 @@ ${unifiedDiff(filePath, oldContent, content)}`;
|
|
|
3285
3724
|
}
|
|
3286
3725
|
case "deleteRoadmapItem": {
|
|
3287
3726
|
const { slug } = input;
|
|
3288
|
-
const filePath =
|
|
3727
|
+
const filePath = path6.join(ROADMAP_DIR, `${slug}.md`);
|
|
3289
3728
|
try {
|
|
3290
3729
|
if (!fs14.existsSync(filePath)) {
|
|
3291
3730
|
return `Error: ${filePath} does not exist`;
|
|
@@ -3313,8 +3752,6 @@ var init_executor = __esm({
|
|
|
3313
3752
|
});
|
|
3314
3753
|
|
|
3315
3754
|
// src/subagents/productVision/prompt.ts
|
|
3316
|
-
import fs15 from "fs";
|
|
3317
|
-
import path9 from "path";
|
|
3318
3755
|
function getProductVisionPrompt() {
|
|
3319
3756
|
const specContext = loadSpecContext();
|
|
3320
3757
|
const roadmapContext = loadRoadmapContext();
|
|
@@ -3327,16 +3764,14 @@ function getProductVisionPrompt() {
|
|
|
3327
3764
|
}
|
|
3328
3765
|
return parts.join("\n\n");
|
|
3329
3766
|
}
|
|
3330
|
-
var
|
|
3767
|
+
var BASE_PROMPT2;
|
|
3331
3768
|
var init_prompt3 = __esm({
|
|
3332
3769
|
"src/subagents/productVision/prompt.ts"() {
|
|
3333
3770
|
"use strict";
|
|
3771
|
+
init_assets();
|
|
3334
3772
|
init_executor();
|
|
3335
3773
|
init_context();
|
|
3336
|
-
|
|
3337
|
-
local2 = path9.join(base4, "prompt.md");
|
|
3338
|
-
PROMPT_PATH2 = fs15.existsSync(local2) ? local2 : path9.join(base4, "subagents", "productVision", "prompt.md");
|
|
3339
|
-
BASE_PROMPT2 = fs15.readFileSync(PROMPT_PATH2, "utf-8").trim();
|
|
3774
|
+
BASE_PROMPT2 = readAsset("subagents/productVision", "prompt.md");
|
|
3340
3775
|
}
|
|
3341
3776
|
});
|
|
3342
3777
|
|
|
@@ -3380,7 +3815,8 @@ var init_productVision = __esm({
|
|
|
3380
3815
|
signal: context.signal,
|
|
3381
3816
|
parentToolId: context.toolCallId,
|
|
3382
3817
|
onEvent: context.onEvent,
|
|
3383
|
-
resolveExternalTool: context.resolveExternalTool
|
|
3818
|
+
resolveExternalTool: context.resolveExternalTool,
|
|
3819
|
+
toolRegistry: context.toolRegistry
|
|
3384
3820
|
});
|
|
3385
3821
|
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
3386
3822
|
return result.text;
|
|
@@ -3487,20 +3923,16 @@ var init_tools4 = __esm({
|
|
|
3487
3923
|
});
|
|
3488
3924
|
|
|
3489
3925
|
// src/subagents/codeSanityCheck/index.ts
|
|
3490
|
-
|
|
3491
|
-
import path10 from "path";
|
|
3492
|
-
var base5, local3, PROMPT_PATH3, BASE_PROMPT3, codeSanityCheckTool;
|
|
3926
|
+
var BASE_PROMPT3, codeSanityCheckTool;
|
|
3493
3927
|
var init_codeSanityCheck = __esm({
|
|
3494
3928
|
"src/subagents/codeSanityCheck/index.ts"() {
|
|
3495
3929
|
"use strict";
|
|
3930
|
+
init_assets();
|
|
3496
3931
|
init_runner();
|
|
3497
3932
|
init_context();
|
|
3498
3933
|
init_tools5();
|
|
3499
3934
|
init_tools4();
|
|
3500
|
-
|
|
3501
|
-
local3 = path10.join(base5, "prompt.md");
|
|
3502
|
-
PROMPT_PATH3 = fs16.existsSync(local3) ? local3 : path10.join(base5, "subagents", "codeSanityCheck", "prompt.md");
|
|
3503
|
-
BASE_PROMPT3 = fs16.readFileSync(PROMPT_PATH3, "utf-8").trim();
|
|
3935
|
+
BASE_PROMPT3 = readAsset("subagents/codeSanityCheck", "prompt.md");
|
|
3504
3936
|
codeSanityCheckTool = {
|
|
3505
3937
|
definition: {
|
|
3506
3938
|
name: "codeSanityCheck",
|
|
@@ -3538,7 +3970,8 @@ var init_codeSanityCheck = __esm({
|
|
|
3538
3970
|
signal: context.signal,
|
|
3539
3971
|
parentToolId: context.toolCallId,
|
|
3540
3972
|
onEvent: context.onEvent,
|
|
3541
|
-
resolveExternalTool: context.resolveExternalTool
|
|
3973
|
+
resolveExternalTool: context.resolveExternalTool,
|
|
3974
|
+
toolRegistry: context.toolRegistry
|
|
3542
3975
|
});
|
|
3543
3976
|
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
3544
3977
|
return result.text;
|
|
@@ -3552,7 +3985,7 @@ function getSpecTools() {
|
|
|
3552
3985
|
return [readSpecTool, writeSpecTool, editSpecTool, listSpecFilesTool];
|
|
3553
3986
|
}
|
|
3554
3987
|
function getCodeTools() {
|
|
3555
|
-
const
|
|
3988
|
+
const tools2 = [
|
|
3556
3989
|
readFileTool,
|
|
3557
3990
|
writeFileTool,
|
|
3558
3991
|
editFileTool,
|
|
@@ -3567,9 +4000,9 @@ function getCodeTools() {
|
|
|
3567
4000
|
browserAutomationTool
|
|
3568
4001
|
];
|
|
3569
4002
|
if (isLspConfigured()) {
|
|
3570
|
-
|
|
4003
|
+
tools2.push(lspDiagnosticsTool, restartProcessTool);
|
|
3571
4004
|
}
|
|
3572
|
-
return
|
|
4005
|
+
return tools2;
|
|
3573
4006
|
}
|
|
3574
4007
|
function getCommonTools() {
|
|
3575
4008
|
return [
|
|
@@ -3667,10 +4100,10 @@ var init_tools5 = __esm({
|
|
|
3667
4100
|
});
|
|
3668
4101
|
|
|
3669
4102
|
// src/session.ts
|
|
3670
|
-
import
|
|
4103
|
+
import fs15 from "fs";
|
|
3671
4104
|
function loadSession(state) {
|
|
3672
4105
|
try {
|
|
3673
|
-
const raw =
|
|
4106
|
+
const raw = fs15.readFileSync(SESSION_FILE, "utf-8");
|
|
3674
4107
|
const data = JSON.parse(raw);
|
|
3675
4108
|
if (Array.isArray(data.messages) && data.messages.length > 0) {
|
|
3676
4109
|
state.messages = sanitizeMessages(data.messages);
|
|
@@ -3718,7 +4151,7 @@ function sanitizeMessages(messages) {
|
|
|
3718
4151
|
}
|
|
3719
4152
|
function saveSession(state) {
|
|
3720
4153
|
try {
|
|
3721
|
-
|
|
4154
|
+
fs15.writeFileSync(
|
|
3722
4155
|
SESSION_FILE,
|
|
3723
4156
|
JSON.stringify({ messages: state.messages }, null, 2),
|
|
3724
4157
|
"utf-8"
|
|
@@ -3729,7 +4162,7 @@ function saveSession(state) {
|
|
|
3729
4162
|
function clearSession(state) {
|
|
3730
4163
|
state.messages = [];
|
|
3731
4164
|
try {
|
|
3732
|
-
|
|
4165
|
+
fs15.unlinkSync(SESSION_FILE);
|
|
3733
4166
|
} catch {
|
|
3734
4167
|
}
|
|
3735
4168
|
}
|
|
@@ -3910,91 +4343,6 @@ var init_parsePartialJson = __esm({
|
|
|
3910
4343
|
}
|
|
3911
4344
|
});
|
|
3912
4345
|
|
|
3913
|
-
// src/statusWatcher.ts
|
|
3914
|
-
function startStatusWatcher(config) {
|
|
3915
|
-
const { apiConfig, getContext, onStatus, interval = 3e3, signal } = config;
|
|
3916
|
-
let lastLabel = "";
|
|
3917
|
-
let inflight = false;
|
|
3918
|
-
let stopped = false;
|
|
3919
|
-
const url = `${apiConfig.baseUrl}/_internal/v2/agent/remy/generate-status`;
|
|
3920
|
-
async function tick() {
|
|
3921
|
-
if (stopped || signal?.aborted || inflight) {
|
|
3922
|
-
return;
|
|
3923
|
-
}
|
|
3924
|
-
inflight = true;
|
|
3925
|
-
try {
|
|
3926
|
-
const ctx = getContext();
|
|
3927
|
-
if (!ctx.assistantText && !ctx.lastToolName) {
|
|
3928
|
-
log.debug("Status watcher: no context, skipping");
|
|
3929
|
-
return;
|
|
3930
|
-
}
|
|
3931
|
-
log.debug("Status watcher: requesting label", {
|
|
3932
|
-
textLength: ctx.assistantText.length,
|
|
3933
|
-
lastToolName: ctx.lastToolName
|
|
3934
|
-
});
|
|
3935
|
-
const res = await fetch(url, {
|
|
3936
|
-
method: "POST",
|
|
3937
|
-
headers: {
|
|
3938
|
-
"Content-Type": "application/json",
|
|
3939
|
-
Authorization: `Bearer ${apiConfig.apiKey}`
|
|
3940
|
-
},
|
|
3941
|
-
body: JSON.stringify({
|
|
3942
|
-
assistantText: ctx.assistantText.slice(-500),
|
|
3943
|
-
lastToolName: ctx.lastToolName,
|
|
3944
|
-
lastToolResult: ctx.lastToolResult?.slice(-200),
|
|
3945
|
-
onboardingState: ctx.onboardingState,
|
|
3946
|
-
userMessage: ctx.userMessage?.slice(-200)
|
|
3947
|
-
}),
|
|
3948
|
-
signal
|
|
3949
|
-
});
|
|
3950
|
-
if (!res.ok) {
|
|
3951
|
-
log.debug("Status watcher: endpoint returned non-ok", {
|
|
3952
|
-
status: res.status
|
|
3953
|
-
});
|
|
3954
|
-
return;
|
|
3955
|
-
}
|
|
3956
|
-
const data = await res.json();
|
|
3957
|
-
if (!data.label) {
|
|
3958
|
-
log.debug("Status watcher: no label in response");
|
|
3959
|
-
return;
|
|
3960
|
-
}
|
|
3961
|
-
if (data.label === lastLabel) {
|
|
3962
|
-
log.debug("Status watcher: duplicate label, skipping", {
|
|
3963
|
-
label: data.label
|
|
3964
|
-
});
|
|
3965
|
-
return;
|
|
3966
|
-
}
|
|
3967
|
-
lastLabel = data.label;
|
|
3968
|
-
if (stopped) {
|
|
3969
|
-
return;
|
|
3970
|
-
}
|
|
3971
|
-
log.debug("Status watcher: emitting", { label: data.label });
|
|
3972
|
-
onStatus(data.label);
|
|
3973
|
-
} catch (err) {
|
|
3974
|
-
log.debug("Status watcher: error", { error: err?.message ?? "unknown" });
|
|
3975
|
-
} finally {
|
|
3976
|
-
inflight = false;
|
|
3977
|
-
}
|
|
3978
|
-
}
|
|
3979
|
-
const timer = setInterval(tick, interval);
|
|
3980
|
-
tick().catch(() => {
|
|
3981
|
-
});
|
|
3982
|
-
log.debug("Status watcher started", { interval });
|
|
3983
|
-
return {
|
|
3984
|
-
stop() {
|
|
3985
|
-
stopped = true;
|
|
3986
|
-
clearInterval(timer);
|
|
3987
|
-
log.debug("Status watcher stopped");
|
|
3988
|
-
}
|
|
3989
|
-
};
|
|
3990
|
-
}
|
|
3991
|
-
var init_statusWatcher = __esm({
|
|
3992
|
-
"src/statusWatcher.ts"() {
|
|
3993
|
-
"use strict";
|
|
3994
|
-
init_logger();
|
|
3995
|
-
}
|
|
3996
|
-
});
|
|
3997
|
-
|
|
3998
4346
|
// src/errors.ts
|
|
3999
4347
|
function friendlyError(raw) {
|
|
4000
4348
|
for (const [pattern, message] of patterns) {
|
|
@@ -4051,13 +4399,14 @@ async function runTurn(params) {
|
|
|
4051
4399
|
signal,
|
|
4052
4400
|
onEvent,
|
|
4053
4401
|
resolveExternalTool,
|
|
4054
|
-
hidden
|
|
4402
|
+
hidden,
|
|
4403
|
+
toolRegistry
|
|
4055
4404
|
} = params;
|
|
4056
|
-
const
|
|
4405
|
+
const tools2 = getToolDefinitions(onboardingState);
|
|
4057
4406
|
log.info("Turn started", {
|
|
4058
4407
|
messageLength: userMessage.length,
|
|
4059
|
-
toolCount:
|
|
4060
|
-
tools:
|
|
4408
|
+
toolCount: tools2.length,
|
|
4409
|
+
tools: tools2.map((t) => t.name),
|
|
4061
4410
|
...attachments && attachments.length > 0 && {
|
|
4062
4411
|
attachmentCount: attachments.length,
|
|
4063
4412
|
attachmentUrls: attachments.map((a) => a.url)
|
|
@@ -4104,6 +4453,20 @@ async function runTurn(params) {
|
|
|
4104
4453
|
let thinkingStartedAt = 0;
|
|
4105
4454
|
const toolInputAccumulators = /* @__PURE__ */ new Map();
|
|
4106
4455
|
let stopReason = "end_turn";
|
|
4456
|
+
let subAgentText = "";
|
|
4457
|
+
let currentToolNames = "";
|
|
4458
|
+
const statusWatcher = startStatusWatcher({
|
|
4459
|
+
apiConfig,
|
|
4460
|
+
getContext: () => ({
|
|
4461
|
+
assistantText: subAgentText || getTextContent(contentBlocks).slice(-500),
|
|
4462
|
+
lastToolName: currentToolNames || getToolCalls(contentBlocks).filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).at(-1)?.name || lastCompletedTools || void 0,
|
|
4463
|
+
lastToolResult: lastCompletedResult || void 0,
|
|
4464
|
+
onboardingState,
|
|
4465
|
+
userMessage
|
|
4466
|
+
}),
|
|
4467
|
+
onStatus: (label) => onEvent({ type: "status", message: label }),
|
|
4468
|
+
signal
|
|
4469
|
+
});
|
|
4107
4470
|
async function handlePartialInput(acc, id, name, partial) {
|
|
4108
4471
|
const tool = getToolByName(name);
|
|
4109
4472
|
if (!tool?.streaming) {
|
|
@@ -4167,18 +4530,6 @@ async function runTurn(params) {
|
|
|
4167
4530
|
onEvent({ type: "tool_input_delta", id, name, result: content });
|
|
4168
4531
|
}
|
|
4169
4532
|
}
|
|
4170
|
-
const statusWatcher = startStatusWatcher({
|
|
4171
|
-
apiConfig,
|
|
4172
|
-
getContext: () => ({
|
|
4173
|
-
assistantText: getTextContent(contentBlocks).slice(-500),
|
|
4174
|
-
lastToolName: getToolCalls(contentBlocks).filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).at(-1)?.name || lastCompletedTools || void 0,
|
|
4175
|
-
lastToolResult: lastCompletedResult || void 0,
|
|
4176
|
-
onboardingState,
|
|
4177
|
-
userMessage
|
|
4178
|
-
}),
|
|
4179
|
-
onStatus: (label) => onEvent({ type: "status", message: label }),
|
|
4180
|
-
signal
|
|
4181
|
-
});
|
|
4182
4533
|
try {
|
|
4183
4534
|
for await (const event of streamChatWithRetry(
|
|
4184
4535
|
{
|
|
@@ -4186,7 +4537,7 @@ async function runTurn(params) {
|
|
|
4186
4537
|
model,
|
|
4187
4538
|
system,
|
|
4188
4539
|
messages: cleanMessagesForApi(state.messages),
|
|
4189
|
-
tools,
|
|
4540
|
+
tools: tools2,
|
|
4190
4541
|
signal
|
|
4191
4542
|
},
|
|
4192
4543
|
{
|
|
@@ -4299,10 +4650,9 @@ async function runTurn(params) {
|
|
|
4299
4650
|
} else {
|
|
4300
4651
|
throw err;
|
|
4301
4652
|
}
|
|
4302
|
-
} finally {
|
|
4303
|
-
statusWatcher.stop();
|
|
4304
4653
|
}
|
|
4305
4654
|
if (signal?.aborted) {
|
|
4655
|
+
statusWatcher.stop();
|
|
4306
4656
|
if (contentBlocks.length > 0) {
|
|
4307
4657
|
contentBlocks.push({
|
|
4308
4658
|
type: "text",
|
|
@@ -4324,6 +4674,7 @@ async function runTurn(params) {
|
|
|
4324
4674
|
});
|
|
4325
4675
|
const toolCalls = getToolCalls(contentBlocks);
|
|
4326
4676
|
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
4677
|
+
statusWatcher.stop();
|
|
4327
4678
|
saveSession(state);
|
|
4328
4679
|
onEvent({ type: "turn_done" });
|
|
4329
4680
|
return;
|
|
@@ -4332,8 +4683,7 @@ async function runTurn(params) {
|
|
|
4332
4683
|
count: toolCalls.length,
|
|
4333
4684
|
tools: toolCalls.map((tc) => tc.name)
|
|
4334
4685
|
});
|
|
4335
|
-
|
|
4336
|
-
const origOnEvent = onEvent;
|
|
4686
|
+
currentToolNames = toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ");
|
|
4337
4687
|
const wrappedOnEvent = (e) => {
|
|
4338
4688
|
if ("parentToolId" in e && e.parentToolId) {
|
|
4339
4689
|
if (e.type === "text") {
|
|
@@ -4342,80 +4692,103 @@ async function runTurn(params) {
|
|
|
4342
4692
|
subAgentText = `Using ${e.name}`;
|
|
4343
4693
|
}
|
|
4344
4694
|
}
|
|
4345
|
-
|
|
4695
|
+
onEvent(e);
|
|
4346
4696
|
};
|
|
4347
|
-
const toolStatusWatcher = startStatusWatcher({
|
|
4348
|
-
apiConfig,
|
|
4349
|
-
getContext: () => ({
|
|
4350
|
-
assistantText: subAgentText || getTextContent(contentBlocks).slice(-500),
|
|
4351
|
-
lastToolName: toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ") || void 0,
|
|
4352
|
-
lastToolResult: lastCompletedResult || void 0,
|
|
4353
|
-
onboardingState,
|
|
4354
|
-
userMessage
|
|
4355
|
-
}),
|
|
4356
|
-
onStatus: (label) => origOnEvent({ type: "status", message: label }),
|
|
4357
|
-
signal
|
|
4358
|
-
});
|
|
4359
4697
|
const subAgentMessages = /* @__PURE__ */ new Map();
|
|
4360
4698
|
const results = await Promise.all(
|
|
4361
4699
|
toolCalls.map(async (tc) => {
|
|
4362
4700
|
if (signal?.aborted) {
|
|
4363
|
-
return {
|
|
4364
|
-
id: tc.id,
|
|
4365
|
-
result: "Error: cancelled",
|
|
4366
|
-
isError: true
|
|
4367
|
-
};
|
|
4701
|
+
return { id: tc.id, result: "Error: cancelled", isError: true };
|
|
4368
4702
|
}
|
|
4369
4703
|
const toolStart = Date.now();
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4704
|
+
let settle;
|
|
4705
|
+
const resultPromise = new Promise((res) => {
|
|
4706
|
+
settle = (result, isError) => res({ id: tc.id, result, isError });
|
|
4707
|
+
});
|
|
4708
|
+
let toolAbort = new AbortController();
|
|
4709
|
+
const cascadeAbort = () => toolAbort.abort();
|
|
4710
|
+
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
4711
|
+
let settled = false;
|
|
4712
|
+
const safeSettle = (result, isError) => {
|
|
4713
|
+
if (settled) {
|
|
4714
|
+
return;
|
|
4715
|
+
}
|
|
4716
|
+
settled = true;
|
|
4717
|
+
signal?.removeEventListener("abort", cascadeAbort);
|
|
4718
|
+
settle(result, isError);
|
|
4719
|
+
};
|
|
4720
|
+
const run = async (input) => {
|
|
4721
|
+
try {
|
|
4722
|
+
let result;
|
|
4723
|
+
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
4724
|
+
saveSession(state);
|
|
4725
|
+
log.info("Waiting for external tool result", {
|
|
4726
|
+
name: tc.name,
|
|
4727
|
+
id: tc.id
|
|
4728
|
+
});
|
|
4729
|
+
result = await resolveExternalTool(tc.id, tc.name, input);
|
|
4730
|
+
} else {
|
|
4731
|
+
result = await executeTool(tc.name, input, {
|
|
4732
|
+
apiConfig,
|
|
4733
|
+
model,
|
|
4734
|
+
signal: toolAbort.signal,
|
|
4735
|
+
onEvent: wrappedOnEvent,
|
|
4736
|
+
resolveExternalTool,
|
|
4737
|
+
toolCallId: tc.id,
|
|
4738
|
+
subAgentMessages,
|
|
4739
|
+
toolRegistry,
|
|
4740
|
+
onLog: (line) => wrappedOnEvent({
|
|
4741
|
+
type: "tool_input_delta",
|
|
4742
|
+
id: tc.id,
|
|
4743
|
+
name: tc.name,
|
|
4744
|
+
result: line
|
|
4745
|
+
})
|
|
4746
|
+
});
|
|
4747
|
+
}
|
|
4748
|
+
safeSettle(result, result.startsWith("Error"));
|
|
4749
|
+
} catch (err) {
|
|
4750
|
+
safeSettle(`Error: ${err.message}`, true);
|
|
4751
|
+
}
|
|
4752
|
+
};
|
|
4753
|
+
const entry = {
|
|
4754
|
+
id: tc.id,
|
|
4755
|
+
name: tc.name,
|
|
4756
|
+
input: tc.input,
|
|
4757
|
+
abortController: toolAbort,
|
|
4758
|
+
startedAt: toolStart,
|
|
4759
|
+
settle: safeSettle,
|
|
4760
|
+
rerun: (newInput) => {
|
|
4761
|
+
settled = false;
|
|
4762
|
+
toolAbort = new AbortController();
|
|
4763
|
+
signal?.addEventListener("abort", () => toolAbort.abort(), {
|
|
4764
|
+
once: true
|
|
4388
4765
|
});
|
|
4766
|
+
entry.abortController = toolAbort;
|
|
4767
|
+
entry.input = newInput;
|
|
4768
|
+
run(newInput);
|
|
4389
4769
|
}
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
id: tc.id,
|
|
4410
|
-
name: tc.name,
|
|
4411
|
-
result: errorMsg,
|
|
4412
|
-
isError: true
|
|
4413
|
-
});
|
|
4414
|
-
return { id: tc.id, result: errorMsg, isError: true };
|
|
4415
|
-
}
|
|
4770
|
+
};
|
|
4771
|
+
toolRegistry?.register(entry);
|
|
4772
|
+
run(tc.input);
|
|
4773
|
+
const r = await resultPromise;
|
|
4774
|
+
toolRegistry?.unregister(tc.id);
|
|
4775
|
+
log.info("Tool completed", {
|
|
4776
|
+
name: tc.name,
|
|
4777
|
+
elapsed: `${Date.now() - toolStart}ms`,
|
|
4778
|
+
isError: r.isError,
|
|
4779
|
+
resultLength: r.result.length
|
|
4780
|
+
});
|
|
4781
|
+
onEvent({
|
|
4782
|
+
type: "tool_done",
|
|
4783
|
+
id: tc.id,
|
|
4784
|
+
name: tc.name,
|
|
4785
|
+
result: r.result,
|
|
4786
|
+
isError: r.isError
|
|
4787
|
+
});
|
|
4788
|
+
return r;
|
|
4416
4789
|
})
|
|
4417
4790
|
);
|
|
4418
|
-
|
|
4791
|
+
statusWatcher.stop();
|
|
4419
4792
|
for (const r of results) {
|
|
4420
4793
|
const block = contentBlocks.find(
|
|
4421
4794
|
(b) => b.type === "tool" && b.id === r.id
|
|
@@ -4423,6 +4796,7 @@ async function runTurn(params) {
|
|
|
4423
4796
|
if (block?.type === "tool") {
|
|
4424
4797
|
block.result = r.result;
|
|
4425
4798
|
block.isError = r.isError;
|
|
4799
|
+
block.completedAt = Date.now();
|
|
4426
4800
|
const msgs = subAgentMessages.get(r.id);
|
|
4427
4801
|
if (msgs) {
|
|
4428
4802
|
block.subAgentMessages = msgs;
|
|
@@ -4475,12 +4849,12 @@ var init_agent = __esm({
|
|
|
4475
4849
|
});
|
|
4476
4850
|
|
|
4477
4851
|
// src/prompt/static/projectContext.ts
|
|
4478
|
-
import
|
|
4479
|
-
import
|
|
4852
|
+
import fs16 from "fs";
|
|
4853
|
+
import path7 from "path";
|
|
4480
4854
|
function loadProjectInstructions() {
|
|
4481
4855
|
for (const file of AGENT_INSTRUCTION_FILES) {
|
|
4482
4856
|
try {
|
|
4483
|
-
const content =
|
|
4857
|
+
const content = fs16.readFileSync(file, "utf-8").trim();
|
|
4484
4858
|
if (content) {
|
|
4485
4859
|
return `
|
|
4486
4860
|
## Project Instructions (${file})
|
|
@@ -4493,7 +4867,7 @@ ${content}`;
|
|
|
4493
4867
|
}
|
|
4494
4868
|
function loadProjectManifest() {
|
|
4495
4869
|
try {
|
|
4496
|
-
const manifest =
|
|
4870
|
+
const manifest = fs16.readFileSync("mindstudio.json", "utf-8");
|
|
4497
4871
|
return `
|
|
4498
4872
|
## Project Manifest (mindstudio.json)
|
|
4499
4873
|
\`\`\`json
|
|
@@ -4534,9 +4908,9 @@ ${entries.join("\n")}`;
|
|
|
4534
4908
|
function walkMdFiles2(dir) {
|
|
4535
4909
|
const results = [];
|
|
4536
4910
|
try {
|
|
4537
|
-
const entries =
|
|
4911
|
+
const entries = fs16.readdirSync(dir, { withFileTypes: true });
|
|
4538
4912
|
for (const entry of entries) {
|
|
4539
|
-
const full =
|
|
4913
|
+
const full = path7.join(dir, entry.name);
|
|
4540
4914
|
if (entry.isDirectory()) {
|
|
4541
4915
|
results.push(...walkMdFiles2(full));
|
|
4542
4916
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -4549,7 +4923,7 @@ function walkMdFiles2(dir) {
|
|
|
4549
4923
|
}
|
|
4550
4924
|
function parseFrontmatter(filePath) {
|
|
4551
4925
|
try {
|
|
4552
|
-
const content =
|
|
4926
|
+
const content = fs16.readFileSync(filePath, "utf-8");
|
|
4553
4927
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
4554
4928
|
if (!match) {
|
|
4555
4929
|
return { name: "", description: "", type: "" };
|
|
@@ -4565,7 +4939,7 @@ function parseFrontmatter(filePath) {
|
|
|
4565
4939
|
}
|
|
4566
4940
|
function loadProjectFileListing() {
|
|
4567
4941
|
try {
|
|
4568
|
-
const entries =
|
|
4942
|
+
const entries = fs16.readdirSync(".", { withFileTypes: true });
|
|
4569
4943
|
const listing = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules").sort((a, b) => {
|
|
4570
4944
|
if (a.isDirectory() && !b.isDirectory()) {
|
|
4571
4945
|
return -1;
|
|
@@ -4608,20 +4982,10 @@ var init_projectContext = __esm({
|
|
|
4608
4982
|
});
|
|
4609
4983
|
|
|
4610
4984
|
// src/prompt/index.ts
|
|
4611
|
-
import fs19 from "fs";
|
|
4612
|
-
import path12 from "path";
|
|
4613
|
-
function requireFile(filePath) {
|
|
4614
|
-
const full = path12.join(PROMPT_DIR, filePath);
|
|
4615
|
-
try {
|
|
4616
|
-
return fs19.readFileSync(full, "utf-8").trim();
|
|
4617
|
-
} catch {
|
|
4618
|
-
throw new Error(`Required prompt file missing: ${full}`);
|
|
4619
|
-
}
|
|
4620
|
-
}
|
|
4621
4985
|
function resolveIncludes(template) {
|
|
4622
4986
|
const result = template.replace(
|
|
4623
4987
|
/\{\{([^}]+)\}\}/g,
|
|
4624
|
-
(_, filePath) =>
|
|
4988
|
+
(_, filePath) => readAsset("prompt", filePath.trim())
|
|
4625
4989
|
);
|
|
4626
4990
|
return result.replace(/\n{3,}/g, "\n\n").trim();
|
|
4627
4991
|
}
|
|
@@ -4731,23 +5095,22 @@ ${viewContext?.activeFile ? `Active file: ${viewContext.activeFile}` : ""}
|
|
|
4731
5095
|
`;
|
|
4732
5096
|
return resolveIncludes(template);
|
|
4733
5097
|
}
|
|
4734
|
-
var PROMPT_DIR;
|
|
4735
5098
|
var init_prompt4 = __esm({
|
|
4736
5099
|
"src/prompt/index.ts"() {
|
|
4737
5100
|
"use strict";
|
|
5101
|
+
init_assets();
|
|
4738
5102
|
init_lsp();
|
|
4739
5103
|
init_projectContext();
|
|
4740
|
-
PROMPT_DIR = import.meta.dirname ?? path12.dirname(new URL(import.meta.url).pathname);
|
|
4741
5104
|
}
|
|
4742
5105
|
});
|
|
4743
5106
|
|
|
4744
5107
|
// src/config.ts
|
|
4745
|
-
import
|
|
4746
|
-
import
|
|
5108
|
+
import fs17 from "fs";
|
|
5109
|
+
import path8 from "path";
|
|
4747
5110
|
import os from "os";
|
|
4748
5111
|
function loadConfigFile() {
|
|
4749
5112
|
try {
|
|
4750
|
-
const raw =
|
|
5113
|
+
const raw = fs17.readFileSync(CONFIG_PATH, "utf-8");
|
|
4751
5114
|
log.debug("Loaded config file", { path: CONFIG_PATH });
|
|
4752
5115
|
return JSON.parse(raw);
|
|
4753
5116
|
} catch (err) {
|
|
@@ -4783,7 +5146,7 @@ var init_config = __esm({
|
|
|
4783
5146
|
"src/config.ts"() {
|
|
4784
5147
|
"use strict";
|
|
4785
5148
|
init_logger();
|
|
4786
|
-
CONFIG_PATH =
|
|
5149
|
+
CONFIG_PATH = path8.join(
|
|
4787
5150
|
os.homedir(),
|
|
4788
5151
|
".mindstudio-local-tunnel",
|
|
4789
5152
|
"config.json"
|
|
@@ -4792,16 +5155,92 @@ var init_config = __esm({
|
|
|
4792
5155
|
}
|
|
4793
5156
|
});
|
|
4794
5157
|
|
|
5158
|
+
// src/toolRegistry.ts
|
|
5159
|
+
var ToolRegistry;
|
|
5160
|
+
var init_toolRegistry = __esm({
|
|
5161
|
+
"src/toolRegistry.ts"() {
|
|
5162
|
+
"use strict";
|
|
5163
|
+
ToolRegistry = class {
|
|
5164
|
+
entries = /* @__PURE__ */ new Map();
|
|
5165
|
+
onEvent;
|
|
5166
|
+
register(entry) {
|
|
5167
|
+
this.entries.set(entry.id, entry);
|
|
5168
|
+
}
|
|
5169
|
+
unregister(id) {
|
|
5170
|
+
this.entries.delete(id);
|
|
5171
|
+
}
|
|
5172
|
+
get(id) {
|
|
5173
|
+
return this.entries.get(id);
|
|
5174
|
+
}
|
|
5175
|
+
/**
|
|
5176
|
+
* Stop a running tool.
|
|
5177
|
+
*
|
|
5178
|
+
* - graceful: abort and settle with [INTERRUPTED] + partial result
|
|
5179
|
+
* - hard: abort and settle with a generic error
|
|
5180
|
+
*
|
|
5181
|
+
* Returns true if the tool was found and stopped.
|
|
5182
|
+
*/
|
|
5183
|
+
stop(id, mode) {
|
|
5184
|
+
const entry = this.entries.get(id);
|
|
5185
|
+
if (!entry) {
|
|
5186
|
+
return false;
|
|
5187
|
+
}
|
|
5188
|
+
entry.abortController.abort(mode);
|
|
5189
|
+
if (mode === "graceful") {
|
|
5190
|
+
const partial = entry.getPartialResult?.() ?? "";
|
|
5191
|
+
const result = partial ? `[INTERRUPTED]
|
|
5192
|
+
|
|
5193
|
+
${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
5194
|
+
entry.settle(result, false);
|
|
5195
|
+
} else {
|
|
5196
|
+
entry.settle("Error: tool was cancelled", true);
|
|
5197
|
+
}
|
|
5198
|
+
this.onEvent?.({
|
|
5199
|
+
type: "tool_stopped",
|
|
5200
|
+
id: entry.id,
|
|
5201
|
+
name: entry.name,
|
|
5202
|
+
mode,
|
|
5203
|
+
...entry.parentToolId && { parentToolId: entry.parentToolId }
|
|
5204
|
+
});
|
|
5205
|
+
this.entries.delete(id);
|
|
5206
|
+
return true;
|
|
5207
|
+
}
|
|
5208
|
+
/**
|
|
5209
|
+
* Restart a running tool with the same or patched input.
|
|
5210
|
+
* The original controllable promise stays pending and settles
|
|
5211
|
+
* when the new execution finishes.
|
|
5212
|
+
*
|
|
5213
|
+
* Returns true if the tool was found and restarted.
|
|
5214
|
+
*/
|
|
5215
|
+
restart(id, patchedInput) {
|
|
5216
|
+
const entry = this.entries.get(id);
|
|
5217
|
+
if (!entry) {
|
|
5218
|
+
return false;
|
|
5219
|
+
}
|
|
5220
|
+
entry.abortController.abort("restart");
|
|
5221
|
+
const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
|
|
5222
|
+
this.onEvent?.({
|
|
5223
|
+
type: "tool_restarted",
|
|
5224
|
+
id: entry.id,
|
|
5225
|
+
name: entry.name,
|
|
5226
|
+
input: newInput,
|
|
5227
|
+
...entry.parentToolId && { parentToolId: entry.parentToolId }
|
|
5228
|
+
});
|
|
5229
|
+
entry.rerun(newInput);
|
|
5230
|
+
return true;
|
|
5231
|
+
}
|
|
5232
|
+
};
|
|
5233
|
+
}
|
|
5234
|
+
});
|
|
5235
|
+
|
|
4795
5236
|
// src/headless.ts
|
|
4796
5237
|
var headless_exports = {};
|
|
4797
5238
|
__export(headless_exports, {
|
|
4798
5239
|
startHeadless: () => startHeadless
|
|
4799
5240
|
});
|
|
4800
5241
|
import { createInterface } from "readline";
|
|
4801
|
-
import fs21 from "fs";
|
|
4802
|
-
import path14 from "path";
|
|
4803
5242
|
function loadActionPrompt(name) {
|
|
4804
|
-
return
|
|
5243
|
+
return readAsset("prompt", "actions", `${name}.md`);
|
|
4805
5244
|
}
|
|
4806
5245
|
function emit(event, data, requestId) {
|
|
4807
5246
|
const payload = { event, ...data };
|
|
@@ -4865,6 +5304,7 @@ async function startHeadless(opts = {}) {
|
|
|
4865
5304
|
const EXTERNAL_TOOL_TIMEOUT_MS = 3e5;
|
|
4866
5305
|
const pendingTools = /* @__PURE__ */ new Map();
|
|
4867
5306
|
const earlyResults = /* @__PURE__ */ new Map();
|
|
5307
|
+
const toolRegistry = new ToolRegistry();
|
|
4868
5308
|
const USER_FACING_TOOLS = /* @__PURE__ */ new Set([
|
|
4869
5309
|
"promptUser",
|
|
4870
5310
|
"confirmDestructiveAction",
|
|
@@ -4969,14 +5409,46 @@ async function startHeadless(opts = {}) {
|
|
|
4969
5409
|
rid
|
|
4970
5410
|
);
|
|
4971
5411
|
return;
|
|
5412
|
+
case "tool_stopped":
|
|
5413
|
+
emit(
|
|
5414
|
+
"tool_stopped",
|
|
5415
|
+
{
|
|
5416
|
+
id: e.id,
|
|
5417
|
+
name: e.name,
|
|
5418
|
+
mode: e.mode,
|
|
5419
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
5420
|
+
},
|
|
5421
|
+
rid
|
|
5422
|
+
);
|
|
5423
|
+
return;
|
|
5424
|
+
case "tool_restarted":
|
|
5425
|
+
emit(
|
|
5426
|
+
"tool_restarted",
|
|
5427
|
+
{
|
|
5428
|
+
id: e.id,
|
|
5429
|
+
name: e.name,
|
|
5430
|
+
input: e.input,
|
|
5431
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
5432
|
+
},
|
|
5433
|
+
rid
|
|
5434
|
+
);
|
|
5435
|
+
return;
|
|
4972
5436
|
case "status":
|
|
4973
|
-
emit(
|
|
5437
|
+
emit(
|
|
5438
|
+
"status",
|
|
5439
|
+
{
|
|
5440
|
+
message: e.message,
|
|
5441
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
5442
|
+
},
|
|
5443
|
+
rid
|
|
5444
|
+
);
|
|
4974
5445
|
return;
|
|
4975
5446
|
case "error":
|
|
4976
5447
|
emit("error", { error: e.error }, rid);
|
|
4977
5448
|
return;
|
|
4978
5449
|
}
|
|
4979
5450
|
}
|
|
5451
|
+
toolRegistry.onEvent = onEvent;
|
|
4980
5452
|
async function handleMessage(parsed, requestId) {
|
|
4981
5453
|
if (running) {
|
|
4982
5454
|
emit(
|
|
@@ -5028,7 +5500,8 @@ async function startHeadless(opts = {}) {
|
|
|
5028
5500
|
signal: currentAbort.signal,
|
|
5029
5501
|
onEvent,
|
|
5030
5502
|
resolveExternalTool,
|
|
5031
|
-
hidden: isCommand
|
|
5503
|
+
hidden: isCommand,
|
|
5504
|
+
toolRegistry
|
|
5032
5505
|
});
|
|
5033
5506
|
if (!completedEmitted) {
|
|
5034
5507
|
emit(
|
|
@@ -5082,6 +5555,36 @@ async function startHeadless(opts = {}) {
|
|
|
5082
5555
|
emit("completed", { success: true }, requestId);
|
|
5083
5556
|
return;
|
|
5084
5557
|
}
|
|
5558
|
+
if (action === "stop_tool") {
|
|
5559
|
+
const id = parsed.id;
|
|
5560
|
+
const mode = parsed.mode ?? "hard";
|
|
5561
|
+
const found = toolRegistry.stop(id, mode);
|
|
5562
|
+
if (found) {
|
|
5563
|
+
emit("completed", { success: true }, requestId);
|
|
5564
|
+
} else {
|
|
5565
|
+
emit(
|
|
5566
|
+
"completed",
|
|
5567
|
+
{ success: false, error: "Tool not found" },
|
|
5568
|
+
requestId
|
|
5569
|
+
);
|
|
5570
|
+
}
|
|
5571
|
+
return;
|
|
5572
|
+
}
|
|
5573
|
+
if (action === "restart_tool") {
|
|
5574
|
+
const id = parsed.id;
|
|
5575
|
+
const patchedInput = parsed.input;
|
|
5576
|
+
const found = toolRegistry.restart(id, patchedInput);
|
|
5577
|
+
if (found) {
|
|
5578
|
+
emit("completed", { success: true }, requestId);
|
|
5579
|
+
} else {
|
|
5580
|
+
emit(
|
|
5581
|
+
"completed",
|
|
5582
|
+
{ success: false, error: "Tool not found" },
|
|
5583
|
+
requestId
|
|
5584
|
+
);
|
|
5585
|
+
}
|
|
5586
|
+
return;
|
|
5587
|
+
}
|
|
5085
5588
|
if (action === "message") {
|
|
5086
5589
|
await handleMessage(parsed, requestId);
|
|
5087
5590
|
return;
|
|
@@ -5107,25 +5610,24 @@ async function startHeadless(opts = {}) {
|
|
|
5107
5610
|
process.on("SIGINT", shutdown);
|
|
5108
5611
|
emit("ready");
|
|
5109
5612
|
}
|
|
5110
|
-
var BASE_DIR, ACTIONS_DIR;
|
|
5111
5613
|
var init_headless = __esm({
|
|
5112
5614
|
"src/headless.ts"() {
|
|
5113
5615
|
"use strict";
|
|
5616
|
+
init_assets();
|
|
5114
5617
|
init_config();
|
|
5115
5618
|
init_prompt4();
|
|
5116
5619
|
init_lsp();
|
|
5117
5620
|
init_agent();
|
|
5118
5621
|
init_session();
|
|
5119
|
-
|
|
5120
|
-
ACTIONS_DIR = path14.join(BASE_DIR, "actions");
|
|
5622
|
+
init_toolRegistry();
|
|
5121
5623
|
}
|
|
5122
5624
|
});
|
|
5123
5625
|
|
|
5124
5626
|
// src/index.tsx
|
|
5125
5627
|
import { render } from "ink";
|
|
5126
5628
|
import os2 from "os";
|
|
5127
|
-
import
|
|
5128
|
-
import
|
|
5629
|
+
import fs18 from "fs";
|
|
5630
|
+
import path9 from "path";
|
|
5129
5631
|
|
|
5130
5632
|
// src/tui/App.tsx
|
|
5131
5633
|
import { useState as useState2, useCallback, useRef } from "react";
|
|
@@ -5442,8 +5944,8 @@ for (let i = 0; i < args.length; i++) {
|
|
|
5442
5944
|
}
|
|
5443
5945
|
function printDebugInfo(config) {
|
|
5444
5946
|
const pkg = JSON.parse(
|
|
5445
|
-
|
|
5446
|
-
|
|
5947
|
+
fs18.readFileSync(
|
|
5948
|
+
path9.join(import.meta.dirname, "..", "package.json"),
|
|
5447
5949
|
"utf-8"
|
|
5448
5950
|
)
|
|
5449
5951
|
);
|