@mindstudio-ai/remy 0.1.255 → 0.1.257
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 +674 -537
- package/dist/index.js +685 -528
- package/dist/prompt/compiled/files.md +17 -3
- package/dist/prompt/compiled/interfaces.md +10 -11
- package/dist/prompt/compiled/methods.md +4 -5
- package/dist/prompt/compiled/scenarios.md +16 -0
- package/dist/prompt/compiled/sdk-actions.md +1 -2
- package/dist/prompt/{compiled/agent-interfaces.md → skills/agentInterfaces.md} +29 -10
- package/dist/prompt/skills/dataSources.md +131 -0
- package/dist/prompt/skills/mcpInterfaces.md +80 -0
- package/dist/prompt/{compiled/task-agents.md → skills/taskAgents.md} +10 -8
- package/dist/prompt/static/instructions.md +1 -1
- package/dist/subagents/browserAutomation/prompt.md +2 -1
- package/dist/subagents/designExpert/prompts/images.md +1 -1
- package/package.json +2 -2
- package/dist/prompt/.notes.md +0 -194
- package/dist/prompt/compiled/README.md +0 -100
- package/dist/prompt/compiled/mcp-interfaces.md +0 -34
- package/dist/prompt/compiled/media-cdn.md +0 -51
- package/dist/prompt/sources/llms.txt +0 -1618
- package/dist/subagents/.notes-background-agents.md +0 -64
- package/dist/subagents/codeSanityCheck/.notes.md +0 -44
- package/dist/subagents/designExpert/.notes.md +0 -265
- package/dist/subagents/productVision/.notes.md +0 -79
package/dist/index.js
CHANGED
|
@@ -266,7 +266,7 @@ function isRetryableError(error) {
|
|
|
266
266
|
return /Network error/i.test(error) || /HTTP 5\d\d/i.test(error) || /Stream stalled/i.test(error) || /overloaded/i.test(error) || /terminated/i.test(error);
|
|
267
267
|
}
|
|
268
268
|
function sleep(ms) {
|
|
269
|
-
return new Promise((
|
|
269
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
270
270
|
}
|
|
271
271
|
async function* streamChatWithRetry(params, options) {
|
|
272
272
|
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
@@ -402,8 +402,8 @@ var init_readSpec = __esm({
|
|
|
402
402
|
init_helpers();
|
|
403
403
|
DEFAULT_MAX_LINES = 500;
|
|
404
404
|
readSpecTool = {
|
|
405
|
-
clearable: true,
|
|
406
405
|
definition: {
|
|
406
|
+
clearable: true,
|
|
407
407
|
name: "readSpec",
|
|
408
408
|
description: "Read a spec file from src/ with line numbers. Always read a spec file before editing it. Paths are relative to the project root and must start with src/ (e.g., src/app.md, src/interfaces/web.md).",
|
|
409
409
|
inputSchema: {
|
|
@@ -535,8 +535,8 @@ var init_writeSpec = __esm({
|
|
|
535
535
|
init_diff();
|
|
536
536
|
init_fileLock();
|
|
537
537
|
writeSpecTool = {
|
|
538
|
-
clearable: true,
|
|
539
538
|
definition: {
|
|
539
|
+
clearable: true,
|
|
540
540
|
name: "writeSpec",
|
|
541
541
|
description: "Create a new spec file or completely overwrite an existing one in src/. Parent directories are created automatically. Use this for new spec files or full rewrites. For targeted changes to existing specs, use editSpec instead.",
|
|
542
542
|
inputSchema: {
|
|
@@ -686,8 +686,8 @@ var init_editSpec = __esm({
|
|
|
686
686
|
init_diff();
|
|
687
687
|
init_fileLock();
|
|
688
688
|
editSpecTool = {
|
|
689
|
-
clearable: true,
|
|
690
689
|
definition: {
|
|
690
|
+
clearable: true,
|
|
691
691
|
name: "editSpec",
|
|
692
692
|
description: "Make a targeted find/replace edit to a spec file (src/*.md). old_string must appear exactly once (minor indentation differences are handled automatically); set replace_all to true to replace every occurrence. Read the file with readSpec first so you match the exact text, and include the full enclosing structure rather than an inner fragment. The file's YAML frontmatter (the leading --- \u2026 --- block, which holds required fields like name) is protected \u2014 an edit that would remove or malform it is refused. For a full rewrite, use writeSpec.",
|
|
693
693
|
inputSchema: {
|
|
@@ -820,8 +820,8 @@ var init_listSpecFiles = __esm({
|
|
|
820
820
|
"src/tools/spec/listSpecFiles.ts"() {
|
|
821
821
|
"use strict";
|
|
822
822
|
listSpecFilesTool = {
|
|
823
|
-
clearable: false,
|
|
824
823
|
definition: {
|
|
824
|
+
clearable: false,
|
|
825
825
|
name: "listSpecFiles",
|
|
826
826
|
description: "List all files in the src/ directory (spec files, brand guidelines, interface specs, references). Use this to understand what spec files exist before reading or editing them.",
|
|
827
827
|
inputSchema: {
|
|
@@ -854,8 +854,8 @@ var init_presentPublishPlan = __esm({
|
|
|
854
854
|
"src/tools/spec/presentPublishPlan.ts"() {
|
|
855
855
|
"use strict";
|
|
856
856
|
presentPublishPlanTool = {
|
|
857
|
-
clearable: false,
|
|
858
857
|
definition: {
|
|
858
|
+
clearable: false,
|
|
859
859
|
name: "presentPublishPlan",
|
|
860
860
|
description: "Present a publish changelog to the user for approval. Write a clear markdown summary of what changed since the last deploy. The user will see this in a full-screen view and can approve or dismiss. Call this BEFORE committing or pushing.",
|
|
861
861
|
inputSchema: {
|
|
@@ -885,8 +885,8 @@ var init_writePlan = __esm({
|
|
|
885
885
|
"use strict";
|
|
886
886
|
PLAN_FILE = ".remy-plan.md";
|
|
887
887
|
writePlanTool = {
|
|
888
|
-
clearable: false,
|
|
889
888
|
definition: {
|
|
889
|
+
clearable: false,
|
|
890
890
|
name: "writePlan",
|
|
891
891
|
description: "Write an implementation plan for user approval before making changes. Use this only for large, multi-step changes like new features, new interface types, or when the user explicitly asks to see a plan. Most work should be done autonomously without a plan. Write a clear markdown summary of what you intend to do in plain language \u2014 describe the changes from the user's perspective, not as a list of files and code paths. The plan is displayed standalone in the UI with approve/reject buttons attached. The plan body ends with its last substantive section \u2014 do not write any closing line addressed to the reader. No 'If this lands, I'll start building,' no 'Let me know what to adjust,' no 'Approve when ready,' no trailing horizontal rule with a sign-off. Wrap-up text belongs in your chat message alongside the plan, not in the plan file. If the user asks for revisions, call this tool again with updated content to overwrite the plan.",
|
|
892
892
|
inputSchema: {
|
|
@@ -922,8 +922,8 @@ var init_updatePlanStatus = __esm({
|
|
|
922
922
|
"use strict";
|
|
923
923
|
PLAN_FILE2 = ".remy-plan.md";
|
|
924
924
|
updatePlanStatusTool = {
|
|
925
|
-
clearable: false,
|
|
926
925
|
definition: {
|
|
926
|
+
clearable: false,
|
|
927
927
|
name: "updatePlanStatus",
|
|
928
928
|
description: 'Update the status of the current implementation plan. Use when the user approves or rejects the plan via chat (e.g. "looks good, go ahead" or "scrap it"). Approving sets the plan to active so you can begin implementation. Rejecting deletes the plan.',
|
|
929
929
|
inputSchema: {
|
|
@@ -970,8 +970,8 @@ var init_setProjectOnboardingState = __esm({
|
|
|
970
970
|
"src/tools/common/setProjectOnboardingState.ts"() {
|
|
971
971
|
"use strict";
|
|
972
972
|
setProjectOnboardingStateTool = {
|
|
973
|
-
clearable: false,
|
|
974
973
|
definition: {
|
|
974
|
+
clearable: false,
|
|
975
975
|
name: "setProjectOnboardingState",
|
|
976
976
|
description: "Advance the project onboarding state. Forward-only: building \u2192 buildComplete \u2192 onboardingFinished. Normally driven by automated actions \u2014 don't call this out of order during a normal build, or you'll skip stages the user hasn't experienced. Exception: if the project has been in `building` for a while, the build is clearly done (the user is iterating on a working app, deploying, etc.), and the user reports the editor seems stuck \u2014 disabled Preview/Spec/Code tabs, no reveal, etc. \u2014 call `setProjectOnboardingState({ state: 'buildComplete' })` to unstick them. `onboardingFinished` is always set by the frontend after the user dismisses the reveal; never call it yourself.",
|
|
977
977
|
inputSchema: {
|
|
@@ -999,8 +999,8 @@ var init_promptUser = __esm({
|
|
|
999
999
|
"src/tools/common/promptUser.ts"() {
|
|
1000
1000
|
"use strict";
|
|
1001
1001
|
promptUserTool = {
|
|
1002
|
-
clearable: false,
|
|
1003
1002
|
definition: {
|
|
1003
|
+
clearable: false,
|
|
1004
1004
|
name: "promptUser",
|
|
1005
1005
|
description: 'Ask the user structured questions. Choose type first: "form" for structured intake (5+ questions, takes over screen), "inline" for quick clarifications or confirmations. Blocks until the user responds. Result contains `_dismissed: true` if the user dismisses without answering.',
|
|
1006
1006
|
inputSchema: {
|
|
@@ -1137,8 +1137,8 @@ var init_confirmDestructiveAction = __esm({
|
|
|
1137
1137
|
"src/tools/common/confirmDestructiveAction.ts"() {
|
|
1138
1138
|
"use strict";
|
|
1139
1139
|
confirmDestructiveActionTool = {
|
|
1140
|
-
clearable: false,
|
|
1141
1140
|
definition: {
|
|
1141
|
+
clearable: false,
|
|
1142
1142
|
name: "confirmDestructiveAction",
|
|
1143
1143
|
description: "Confirm a destructive or irreversible action with the user. Use for things like deleting data, resetting the database, or discarding draft work. Do not use after presentPublishPlan or writePlan (those already include approval). Do not use before onboarding state transitions.",
|
|
1144
1144
|
inputSchema: {
|
|
@@ -1176,7 +1176,7 @@ function formatCliResult(r) {
|
|
|
1176
1176
|
return logBlock + body + truncNote;
|
|
1177
1177
|
}
|
|
1178
1178
|
function runCli(command, args2, options) {
|
|
1179
|
-
return new Promise((
|
|
1179
|
+
return new Promise((resolve3) => {
|
|
1180
1180
|
const timeout = options?.timeout ?? 6e4;
|
|
1181
1181
|
const maxBuffer = options?.maxBuffer ?? 1024 * 1024;
|
|
1182
1182
|
let finalArgs = args2;
|
|
@@ -1205,7 +1205,7 @@ function runCli(command, args2, options) {
|
|
|
1205
1205
|
if (killTimer) {
|
|
1206
1206
|
clearTimeout(killTimer);
|
|
1207
1207
|
}
|
|
1208
|
-
|
|
1208
|
+
resolve3(result);
|
|
1209
1209
|
};
|
|
1210
1210
|
const child = spawn(command, finalArgs, {
|
|
1211
1211
|
stdio: [options?.stdin ? "pipe" : "ignore", "pipe", "pipe"]
|
|
@@ -1324,8 +1324,8 @@ var init_sdkConsultant = __esm({
|
|
|
1324
1324
|
"use strict";
|
|
1325
1325
|
init_runCli();
|
|
1326
1326
|
askMindStudioSdkTool = {
|
|
1327
|
-
clearable: false,
|
|
1328
1327
|
definition: {
|
|
1328
|
+
clearable: false,
|
|
1329
1329
|
name: "askMindStudioSdk",
|
|
1330
1330
|
description: "@mindstudio-ai/agent backend SDK expert. Knows every backend action, AI model, connector, and configuration option. Returns architectural guidance and working code. Only covers the backend SDK (@mindstudio-ai/agent) \u2014 do NOT use for frontend/interface SDK questions (@mindstudio-ai/interface) like file uploads, auth, or client-side APIs. Describe what you want to build, not just what API method you need. Batch related questions into a single query.",
|
|
1331
1331
|
inputSchema: {
|
|
@@ -1486,8 +1486,8 @@ var init_searchGoogle = __esm({
|
|
|
1486
1486
|
init_runMindstudioCli();
|
|
1487
1487
|
init_runCli();
|
|
1488
1488
|
searchGoogleTool = {
|
|
1489
|
-
clearable: false,
|
|
1490
1489
|
definition: {
|
|
1490
|
+
clearable: false,
|
|
1491
1491
|
name: "searchGoogle",
|
|
1492
1492
|
description: "Search Google and return results. Use for research, finding documentation, looking up APIs, or any task where web search would help.",
|
|
1493
1493
|
inputSchema: {
|
|
@@ -1523,8 +1523,8 @@ var init_setProjectMetadata = __esm({
|
|
|
1523
1523
|
"src/tools/common/setProjectMetadata.ts"() {
|
|
1524
1524
|
"use strict";
|
|
1525
1525
|
setProjectMetadataTool = {
|
|
1526
|
-
clearable: false,
|
|
1527
1526
|
definition: {
|
|
1527
|
+
clearable: false,
|
|
1528
1528
|
name: "setProjectMetadata",
|
|
1529
1529
|
description: "Set project metadata. Can update any combination of: display name, short description, app icon, and Open Graph share image. Provide only the fields you want to change.",
|
|
1530
1530
|
inputSchema: {
|
|
@@ -1559,16 +1559,6 @@ var init_setProjectMetadata = __esm({
|
|
|
1559
1559
|
// src/assets.ts
|
|
1560
1560
|
import fs9 from "fs";
|
|
1561
1561
|
import path3 from "path";
|
|
1562
|
-
function findRoot(start) {
|
|
1563
|
-
let dir = start;
|
|
1564
|
-
while (dir !== path3.dirname(dir)) {
|
|
1565
|
-
if (fs9.existsSync(path3.join(dir, "package.json"))) {
|
|
1566
|
-
return dir;
|
|
1567
|
-
}
|
|
1568
|
-
dir = path3.dirname(dir);
|
|
1569
|
-
}
|
|
1570
|
-
return start;
|
|
1571
|
-
}
|
|
1572
1562
|
function assetPath(...segments) {
|
|
1573
1563
|
return path3.join(ASSETS_BASE, ...segments);
|
|
1574
1564
|
}
|
|
@@ -1588,14 +1578,11 @@ function readJsonAsset(fallback, ...segments) {
|
|
|
1588
1578
|
return fallback;
|
|
1589
1579
|
}
|
|
1590
1580
|
}
|
|
1591
|
-
var
|
|
1581
|
+
var ASSETS_BASE;
|
|
1592
1582
|
var init_assets = __esm({
|
|
1593
1583
|
"src/assets.ts"() {
|
|
1594
1584
|
"use strict";
|
|
1595
|
-
|
|
1596
|
-
import.meta.dirname ?? path3.dirname(new URL(import.meta.url).pathname)
|
|
1597
|
-
);
|
|
1598
|
-
ASSETS_BASE = fs9.existsSync(path3.join(ROOT, "dist", "prompt")) ? path3.join(ROOT, "dist") : path3.join(ROOT, "src");
|
|
1585
|
+
ASSETS_BASE = import.meta.dirname ?? path3.dirname(new URL(import.meta.url).pathname);
|
|
1599
1586
|
}
|
|
1600
1587
|
});
|
|
1601
1588
|
|
|
@@ -2238,6 +2225,9 @@ function findLastSummaryCheckpoint(messages, name) {
|
|
|
2238
2225
|
}
|
|
2239
2226
|
return -1;
|
|
2240
2227
|
}
|
|
2228
|
+
function portableToolCallId(id) {
|
|
2229
|
+
return id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2230
|
+
}
|
|
2241
2231
|
function fixOrphanedToolCalls(messages) {
|
|
2242
2232
|
const toolResultIds = /* @__PURE__ */ new Set();
|
|
2243
2233
|
for (const msg of messages) {
|
|
@@ -2329,14 +2319,30 @@ ${summaryBlock.text}
|
|
|
2329
2319
|
|
|
2330
2320
|
${content}` : attachmentHeader;
|
|
2331
2321
|
}
|
|
2332
|
-
return {
|
|
2322
|
+
return {
|
|
2323
|
+
...rest,
|
|
2324
|
+
content,
|
|
2325
|
+
...msg.toolCallId && {
|
|
2326
|
+
toolCallId: portableToolCallId(msg.toolCallId)
|
|
2327
|
+
}
|
|
2328
|
+
};
|
|
2333
2329
|
}
|
|
2334
2330
|
if (!Array.isArray(msg.content)) {
|
|
2335
2331
|
return msg;
|
|
2336
2332
|
}
|
|
2337
2333
|
const blocks = msg.content;
|
|
2338
2334
|
const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
2339
|
-
const
|
|
2335
|
+
const toolBlocks = blocks.filter(
|
|
2336
|
+
(b) => b.type === "tool"
|
|
2337
|
+
);
|
|
2338
|
+
const toolCalls = toolBlocks.map((b) => ({
|
|
2339
|
+
id: portableToolCallId(b.id),
|
|
2340
|
+
name: b.name,
|
|
2341
|
+
input: b.input
|
|
2342
|
+
}));
|
|
2343
|
+
const rewroteToolIds = toolBlocks.some(
|
|
2344
|
+
(b) => portableToolCallId(b.id) !== b.id
|
|
2345
|
+
);
|
|
2340
2346
|
const cleaned2 = {
|
|
2341
2347
|
role: msg.role,
|
|
2342
2348
|
content: text
|
|
@@ -2344,7 +2350,7 @@ ${content}` : attachmentHeader;
|
|
|
2344
2350
|
if (toolCalls.length > 0) {
|
|
2345
2351
|
cleaned2.toolCalls = toolCalls;
|
|
2346
2352
|
}
|
|
2347
|
-
if (msg.providerMetadata) {
|
|
2353
|
+
if (msg.providerMetadata && !rewroteToolIds) {
|
|
2348
2354
|
cleaned2.providerMetadata = msg.providerMetadata;
|
|
2349
2355
|
}
|
|
2350
2356
|
if (msg.hidden) {
|
|
@@ -2800,8 +2806,8 @@ var init_compactConversation = __esm({
|
|
|
2800
2806
|
"use strict";
|
|
2801
2807
|
init_trigger();
|
|
2802
2808
|
compactConversationTool = {
|
|
2803
|
-
clearable: false,
|
|
2804
2809
|
definition: {
|
|
2810
|
+
clearable: false,
|
|
2805
2811
|
name: "compactConversation",
|
|
2806
2812
|
description: "Compact the conversation history by summarizing older messages into a checkpoint. The summary preserves key decisions, what was built, and the current state of the project, but drops the verbose tool results, diffs, and intermediate steps that are no longer useful. Runs in the background.",
|
|
2807
2813
|
inputSchema: {
|
|
@@ -2825,8 +2831,136 @@ var init_compactConversation = __esm({
|
|
|
2825
2831
|
}
|
|
2826
2832
|
});
|
|
2827
2833
|
|
|
2834
|
+
// src/prompt/skills/catalog.ts
|
|
2835
|
+
import fs11 from "fs";
|
|
2836
|
+
import path5 from "path";
|
|
2837
|
+
function parseFrontmatter(content) {
|
|
2838
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
2839
|
+
if (!match) {
|
|
2840
|
+
return {};
|
|
2841
|
+
}
|
|
2842
|
+
const fields = {};
|
|
2843
|
+
for (const line of match[1].split("\n")) {
|
|
2844
|
+
const sep = line.indexOf(":");
|
|
2845
|
+
if (sep > 0) {
|
|
2846
|
+
fields[line.slice(0, sep).trim()] = line.slice(sep + 1).trim();
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
return fields;
|
|
2850
|
+
}
|
|
2851
|
+
function loadCatalog() {
|
|
2852
|
+
let files;
|
|
2853
|
+
try {
|
|
2854
|
+
files = fs11.readdirSync(SKILLS_DIR).filter((f) => f.endsWith(".md"));
|
|
2855
|
+
} catch {
|
|
2856
|
+
return [];
|
|
2857
|
+
}
|
|
2858
|
+
const skills = [];
|
|
2859
|
+
for (const file of files.sort()) {
|
|
2860
|
+
const full = path5.join(SKILLS_DIR, file);
|
|
2861
|
+
const id = file.replace(/\.md$/, "");
|
|
2862
|
+
const fields = parseFrontmatter(fs11.readFileSync(full, "utf-8"));
|
|
2863
|
+
if (!fields.name || !fields.what || !fields.when) {
|
|
2864
|
+
continue;
|
|
2865
|
+
}
|
|
2866
|
+
skills.push({
|
|
2867
|
+
id,
|
|
2868
|
+
name: fields.name,
|
|
2869
|
+
what: fields.what,
|
|
2870
|
+
when: fields.when,
|
|
2871
|
+
path: full
|
|
2872
|
+
});
|
|
2873
|
+
}
|
|
2874
|
+
return skills;
|
|
2875
|
+
}
|
|
2876
|
+
function getSkill(id) {
|
|
2877
|
+
return SKILLS.find((s) => s.id === id);
|
|
2878
|
+
}
|
|
2879
|
+
function readSkillBody(skill) {
|
|
2880
|
+
return fs11.readFileSync(skill.path, "utf-8").replace(/^---[\s\S]*?---\s*/, "").trim();
|
|
2881
|
+
}
|
|
2882
|
+
function loadSkillsCatalog() {
|
|
2883
|
+
if (SKILLS.length === 0) {
|
|
2884
|
+
return "";
|
|
2885
|
+
}
|
|
2886
|
+
const entries = SKILLS.map(
|
|
2887
|
+
(s) => [
|
|
2888
|
+
`### ${s.name} (\`${s.id}\`)`,
|
|
2889
|
+
s.what,
|
|
2890
|
+
"",
|
|
2891
|
+
`When to load: ${s.when}`,
|
|
2892
|
+
`Reference: ${s.path}`
|
|
2893
|
+
].join("\n")
|
|
2894
|
+
);
|
|
2895
|
+
return `<available_skills>
|
|
2896
|
+
Platform capabilities most apps don't use, so their references are kept out of this prompt rather than competing for your attention on every task \u2014 not because they're marginal.
|
|
2897
|
+
|
|
2898
|
+
Read what follows as part of what the platform can do, not as a lookup table. Recognising that one of these fits a feature is your job, and proposing one is fair game \u2014 several of them are the difference between an app that works and an app worth showing off. When a trigger fires, load the reference with loadSkill before writing the code rather than after. Loading is cheap and expected; guessing at one of these APIs is not.
|
|
2899
|
+
|
|
2900
|
+
A loaded reference drops out of the conversation once it ages out. Re-read it at the path listed with readFile whenever you need it again.
|
|
2901
|
+
|
|
2902
|
+
${entries.join("\n\n")}
|
|
2903
|
+
</available_skills>`;
|
|
2904
|
+
}
|
|
2905
|
+
var SKILLS_DIR, SKILLS, SKILL_IDS;
|
|
2906
|
+
var init_catalog = __esm({
|
|
2907
|
+
"src/prompt/skills/catalog.ts"() {
|
|
2908
|
+
"use strict";
|
|
2909
|
+
init_assets();
|
|
2910
|
+
SKILLS_DIR = assetPath("prompt", "skills");
|
|
2911
|
+
SKILLS = loadCatalog();
|
|
2912
|
+
SKILL_IDS = SKILLS.map((s) => s.id);
|
|
2913
|
+
}
|
|
2914
|
+
});
|
|
2915
|
+
|
|
2916
|
+
// src/tools/common/loadSkill.ts
|
|
2917
|
+
var loadSkillTool;
|
|
2918
|
+
var init_loadSkill = __esm({
|
|
2919
|
+
"src/tools/common/loadSkill.ts"() {
|
|
2920
|
+
"use strict";
|
|
2921
|
+
init_catalog();
|
|
2922
|
+
loadSkillTool = {
|
|
2923
|
+
definition: {
|
|
2924
|
+
clearable: true,
|
|
2925
|
+
name: "loadSkill",
|
|
2926
|
+
description: "Load the full reference for a platform capability that isn't in your system prompt \u2014 task agents, agent interfaces, MCP interfaces, data sources. The available skills and the trigger for each are listed in <available_skills>. Load one before writing code in its area, not after: these are APIs where a plausible-looking guess is usually wrong. Calling this is cheap and expected \u2014 if you're unsure whether you need it, load it. Only covers the capabilities listed in the catalog; for backend SDK actions and model IDs use askMindStudioSdk.",
|
|
2927
|
+
inputSchema: {
|
|
2928
|
+
type: "object",
|
|
2929
|
+
properties: {
|
|
2930
|
+
skill: {
|
|
2931
|
+
type: "string",
|
|
2932
|
+
// Omitted when the catalog is empty: an empty enum is a schema no
|
|
2933
|
+
// provider accepts, and failing one tool call beats failing every
|
|
2934
|
+
// request if the docs ever go missing from a build.
|
|
2935
|
+
...SKILL_IDS.length > 0 ? { enum: SKILL_IDS } : {},
|
|
2936
|
+
description: "The skill id, as listed in <available_skills>."
|
|
2937
|
+
}
|
|
2938
|
+
},
|
|
2939
|
+
required: ["skill"]
|
|
2940
|
+
}
|
|
2941
|
+
},
|
|
2942
|
+
async execute(input) {
|
|
2943
|
+
const id = String(input.skill ?? "");
|
|
2944
|
+
const skill = getSkill(id);
|
|
2945
|
+
if (!skill) {
|
|
2946
|
+
return `Error: unknown skill "${id}". Available: ${SKILL_IDS.join(", ") || "(none)"}`;
|
|
2947
|
+
}
|
|
2948
|
+
try {
|
|
2949
|
+
const body = readSkillBody(skill);
|
|
2950
|
+
return `${body}
|
|
2951
|
+
|
|
2952
|
+
---
|
|
2953
|
+
This reference lives at ${skill.path}. Re-read it with readFile if you need it again later \u2014 it won't stay in the conversation.`;
|
|
2954
|
+
} catch (err) {
|
|
2955
|
+
return `Error loading skill "${id}": ${err.message}`;
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
};
|
|
2959
|
+
}
|
|
2960
|
+
});
|
|
2961
|
+
|
|
2828
2962
|
// src/tools/code/readFile.ts
|
|
2829
|
-
import
|
|
2963
|
+
import fs12 from "fs/promises";
|
|
2830
2964
|
function isBinary(buffer) {
|
|
2831
2965
|
const sample = buffer.subarray(0, 8192);
|
|
2832
2966
|
for (let i = 0; i < sample.length; i++) {
|
|
@@ -2843,8 +2977,8 @@ var init_readFile = __esm({
|
|
|
2843
2977
|
DEFAULT_WINDOW = 500;
|
|
2844
2978
|
MAX_BYTES = 64 * 1024;
|
|
2845
2979
|
readFileTool = {
|
|
2846
|
-
clearable: true,
|
|
2847
2980
|
definition: {
|
|
2981
|
+
clearable: true,
|
|
2848
2982
|
name: "readFile",
|
|
2849
2983
|
description: "Read a file's contents with line numbers. Always read a file before editing it \u2014 never guess at contents. By default returns the first 500 lines, and at most 64KB \u2014 a file with very wide lines (a CSV, a minified bundle) comes back short of 500 lines, so read a narrower range or grep rather than paging through it. To read a specific range, pass startLine and endLine (1-indexed, inclusive) \u2014 e.g. to read lines 253\u2013343, pass startLine: 253, endLine: 343. To read the end of a file or log, pass tail (the number of lines from the end). Line numbers in the output correspond to what editFile expects. For a large file, locate the relevant section first (symbols or grep), then read just that range.",
|
|
2850
2984
|
inputSchema: {
|
|
@@ -2852,7 +2986,7 @@ var init_readFile = __esm({
|
|
|
2852
2986
|
properties: {
|
|
2853
2987
|
path: {
|
|
2854
2988
|
type: "string",
|
|
2855
|
-
description: "The file path to read
|
|
2989
|
+
description: "The file path to read. Relative paths resolve from the project root; an absolute path is also accepted, which is how you re-read a reference doc from the path a loadSkill result gave you."
|
|
2856
2990
|
},
|
|
2857
2991
|
startLine: {
|
|
2858
2992
|
type: "number",
|
|
@@ -2872,7 +3006,7 @@ var init_readFile = __esm({
|
|
|
2872
3006
|
},
|
|
2873
3007
|
async execute(input) {
|
|
2874
3008
|
try {
|
|
2875
|
-
const buffer = await
|
|
3009
|
+
const buffer = await fs12.readFile(input.path);
|
|
2876
3010
|
if (isBinary(buffer)) {
|
|
2877
3011
|
const size = buffer.length;
|
|
2878
3012
|
const unit = size > 1024 * 1024 ? `${(size / (1024 * 1024)).toFixed(1)}MB` : `${(size / 1024).toFixed(1)}KB`;
|
|
@@ -2946,8 +3080,8 @@ var init_readFile = __esm({
|
|
|
2946
3080
|
});
|
|
2947
3081
|
|
|
2948
3082
|
// src/tools/code/writeFile.ts
|
|
2949
|
-
import
|
|
2950
|
-
import
|
|
3083
|
+
import fs13 from "fs/promises";
|
|
3084
|
+
import path6 from "path";
|
|
2951
3085
|
var writeFileTool;
|
|
2952
3086
|
var init_writeFile = __esm({
|
|
2953
3087
|
"src/tools/code/writeFile.ts"() {
|
|
@@ -2955,8 +3089,8 @@ var init_writeFile = __esm({
|
|
|
2955
3089
|
init_diff();
|
|
2956
3090
|
init_fileLock();
|
|
2957
3091
|
writeFileTool = {
|
|
2958
|
-
clearable: true,
|
|
2959
3092
|
definition: {
|
|
3093
|
+
clearable: true,
|
|
2960
3094
|
name: "writeFile",
|
|
2961
3095
|
description: "Create a new file or completely overwrite an existing one. Parent directories are created automatically. Use this for new files or full rewrites. For targeted changes to existing files, use editFile instead \u2014 it preserves the parts you don't want to change and avoids errors from forgetting to include unchanged code.",
|
|
2962
3096
|
inputSchema: {
|
|
@@ -2990,7 +3124,7 @@ var init_writeFile = __esm({
|
|
|
2990
3124
|
lastNewlineCount = newlineCount;
|
|
2991
3125
|
const lastNewline = partial.content.lastIndexOf("\n");
|
|
2992
3126
|
const completeContent = partial.content.substring(0, lastNewline + 1);
|
|
2993
|
-
const oldContent = await
|
|
3127
|
+
const oldContent = await fs13.readFile(partial.path, "utf-8").catch(() => "");
|
|
2994
3128
|
return `Writing ${partial.path} (${newlineCount} lines)
|
|
2995
3129
|
${unifiedDiff(partial.path, oldContent, completeContent)}`;
|
|
2996
3130
|
}
|
|
@@ -2999,13 +3133,13 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
|
|
|
2999
3133
|
async execute(input) {
|
|
3000
3134
|
const release = await acquireFileLock(input.path);
|
|
3001
3135
|
try {
|
|
3002
|
-
await
|
|
3136
|
+
await fs13.mkdir(path6.dirname(input.path), { recursive: true });
|
|
3003
3137
|
let oldContent = null;
|
|
3004
3138
|
try {
|
|
3005
|
-
oldContent = await
|
|
3139
|
+
oldContent = await fs13.readFile(input.path, "utf-8");
|
|
3006
3140
|
} catch {
|
|
3007
3141
|
}
|
|
3008
|
-
await
|
|
3142
|
+
await fs13.writeFile(input.path, input.content, "utf-8");
|
|
3009
3143
|
const lineCount = input.content.split("\n").length;
|
|
3010
3144
|
const label = oldContent !== null ? "Wrote" : "Created";
|
|
3011
3145
|
return `${label} ${input.path} (${lineCount} lines)
|
|
@@ -3021,7 +3155,7 @@ ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
|
|
|
3021
3155
|
});
|
|
3022
3156
|
|
|
3023
3157
|
// src/tools/code/editFile/index.ts
|
|
3024
|
-
import
|
|
3158
|
+
import fs14 from "fs/promises";
|
|
3025
3159
|
var editFileTool;
|
|
3026
3160
|
var init_editFile = __esm({
|
|
3027
3161
|
"src/tools/code/editFile/index.ts"() {
|
|
@@ -3030,8 +3164,8 @@ var init_editFile = __esm({
|
|
|
3030
3164
|
init_fileLock();
|
|
3031
3165
|
init_helpers2();
|
|
3032
3166
|
editFileTool = {
|
|
3033
|
-
clearable: true,
|
|
3034
3167
|
definition: {
|
|
3168
|
+
clearable: true,
|
|
3035
3169
|
name: "editFile",
|
|
3036
3170
|
description: "Replace a string in a file. old_string must appear exactly once (minor indentation differences are handled automatically). Set replace_all to true to replace every occurrence at once. For bulk mechanical substitutions (renaming a variable, swapping colors), prefer replace_all. Always read the file first so you know the exact text to match. When editing nested structures (objects, function bodies, arrays, template literals), always include the full enclosing structure in old_string rather than just an inner fragment. Replacing a partial slice from the middle of nested code is the most common source of syntax errors.",
|
|
3037
3171
|
inputSchema: {
|
|
@@ -3060,7 +3194,7 @@ var init_editFile = __esm({
|
|
|
3060
3194
|
async execute(input) {
|
|
3061
3195
|
const release = await acquireFileLock(input.path);
|
|
3062
3196
|
try {
|
|
3063
|
-
const content = await
|
|
3197
|
+
const content = await fs14.readFile(input.path, "utf-8");
|
|
3064
3198
|
const { old_string, new_string, replace_all } = input;
|
|
3065
3199
|
const occurrences = findOccurrences(content, old_string);
|
|
3066
3200
|
if (replace_all) {
|
|
@@ -3076,7 +3210,7 @@ var init_editFile = __esm({
|
|
|
3076
3210
|
new_string
|
|
3077
3211
|
);
|
|
3078
3212
|
}
|
|
3079
|
-
await
|
|
3213
|
+
await fs14.writeFile(input.path, updated, "utf-8");
|
|
3080
3214
|
return `Replaced ${occurrences.length} occurrence${occurrences.length > 1 ? "s" : ""} in ${input.path}
|
|
3081
3215
|
${unifiedDiff(input.path, content, updated)}`;
|
|
3082
3216
|
}
|
|
@@ -3087,7 +3221,7 @@ ${unifiedDiff(input.path, content, updated)}`;
|
|
|
3087
3221
|
old_string.length,
|
|
3088
3222
|
new_string
|
|
3089
3223
|
);
|
|
3090
|
-
await
|
|
3224
|
+
await fs14.writeFile(input.path, updated, "utf-8");
|
|
3091
3225
|
return `Updated ${input.path}
|
|
3092
3226
|
${unifiedDiff(input.path, content, updated)}`;
|
|
3093
3227
|
}
|
|
@@ -3103,7 +3237,7 @@ ${unifiedDiff(input.path, content, updated)}`;
|
|
|
3103
3237
|
flex.matchedText.length,
|
|
3104
3238
|
new_string
|
|
3105
3239
|
);
|
|
3106
|
-
await
|
|
3240
|
+
await fs14.writeFile(input.path, updated, "utf-8");
|
|
3107
3241
|
return `Updated ${input.path} (matched with flexible whitespace at line ${flex.line})
|
|
3108
3242
|
${unifiedDiff(input.path, content, updated)}`;
|
|
3109
3243
|
}
|
|
@@ -3129,7 +3263,7 @@ var init_projectRoot = __esm({
|
|
|
3129
3263
|
|
|
3130
3264
|
// src/tools/code/bash.ts
|
|
3131
3265
|
import { spawn as spawn2 } from "child_process";
|
|
3132
|
-
import
|
|
3266
|
+
import path7 from "path";
|
|
3133
3267
|
var DEFAULT_TIMEOUT_MS, DEFAULT_MAX_LINES2, MAX_OUTPUT_BYTES, bashTool;
|
|
3134
3268
|
var init_bash = __esm({
|
|
3135
3269
|
"src/tools/code/bash.ts"() {
|
|
@@ -3139,8 +3273,8 @@ var init_bash = __esm({
|
|
|
3139
3273
|
DEFAULT_MAX_LINES2 = 500;
|
|
3140
3274
|
MAX_OUTPUT_BYTES = 3e4;
|
|
3141
3275
|
bashTool = {
|
|
3142
|
-
clearable: true,
|
|
3143
3276
|
definition: {
|
|
3277
|
+
clearable: true,
|
|
3144
3278
|
name: "bash",
|
|
3145
3279
|
description: "Run a shell command and return stdout + stderr. 120-second timeout by default (configurable). Use for: npm install/build/test, git operations, tsc --noEmit, or any CLI tool. Prefer dedicated tools over bash when available (use grep instead of bash + rg, readFile instead of bash + cat). Output is truncated to 500 lines or 30KB, whichever comes first. If a command would emit a lot of data, narrow it down (grep, head/tail, --short flags) rather than reading everything.",
|
|
3146
3280
|
inputSchema: {
|
|
@@ -3169,11 +3303,11 @@ var init_bash = __esm({
|
|
|
3169
3303
|
async execute(input, context) {
|
|
3170
3304
|
const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES2;
|
|
3171
3305
|
const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
|
|
3172
|
-
return new Promise((
|
|
3306
|
+
return new Promise((resolve3) => {
|
|
3173
3307
|
const child = spawn2("sh", ["-c", input.command], {
|
|
3174
3308
|
// Pinned rather than inherited. `undefined` here means "wherever the
|
|
3175
3309
|
// process happens to be", which is the project root only by luck.
|
|
3176
|
-
cwd: input.cwd ?
|
|
3310
|
+
cwd: input.cwd ? path7.resolve(PROJECT_ROOT, input.cwd) : PROJECT_ROOT,
|
|
3177
3311
|
env: { ...process.env, FORCE_COLOR: "1" }
|
|
3178
3312
|
});
|
|
3179
3313
|
let output = "";
|
|
@@ -3194,9 +3328,9 @@ var init_bash = __esm({
|
|
|
3194
3328
|
clearTimeout(timer);
|
|
3195
3329
|
if (!output) {
|
|
3196
3330
|
if (code && code !== 0) {
|
|
3197
|
-
|
|
3331
|
+
resolve3(`Error: process exited with code ${code}`);
|
|
3198
3332
|
} else {
|
|
3199
|
-
|
|
3333
|
+
resolve3("(no output)");
|
|
3200
3334
|
}
|
|
3201
3335
|
return;
|
|
3202
3336
|
}
|
|
@@ -3222,18 +3356,18 @@ var init_bash = __esm({
|
|
|
3222
3356
|
`${(MAX_OUTPUT_BYTES / 1024).toFixed(0)}KB of ${(totalBytes / 1024).toFixed(0)}KB`
|
|
3223
3357
|
);
|
|
3224
3358
|
}
|
|
3225
|
-
|
|
3359
|
+
resolve3(
|
|
3226
3360
|
truncated + `
|
|
3227
3361
|
|
|
3228
3362
|
(truncated at ${reasons.join(" / ")} \u2014 narrow the command (grep, head/tail, smaller paths) instead of increasing limits)`
|
|
3229
3363
|
);
|
|
3230
3364
|
} else {
|
|
3231
|
-
|
|
3365
|
+
resolve3(output);
|
|
3232
3366
|
}
|
|
3233
3367
|
});
|
|
3234
3368
|
child.on("error", (err) => {
|
|
3235
3369
|
clearTimeout(timer);
|
|
3236
|
-
|
|
3370
|
+
resolve3(`Error: ${err.message}`);
|
|
3237
3371
|
});
|
|
3238
3372
|
});
|
|
3239
3373
|
}
|
|
@@ -3267,8 +3401,8 @@ var init_grep = __esm({
|
|
|
3267
3401
|
"use strict";
|
|
3268
3402
|
DEFAULT_MAX = 50;
|
|
3269
3403
|
grepTool = {
|
|
3270
|
-
clearable: true,
|
|
3271
3404
|
definition: {
|
|
3405
|
+
clearable: true,
|
|
3272
3406
|
name: "grep",
|
|
3273
3407
|
description: "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers (default 50 results). Use this to find where something is used, locate function definitions, or search for patterns across the codebase. Set outputMode to 'count' for per-file match counts (like grep -c) or 'filesWithMatches' for just the file paths (like grep -l). Add context (like grep -C), or contextBefore/contextAfter (like grep -B/-A), to include surrounding lines. Set caseInsensitive (like grep -i) for a case-insensitive search. For finding a symbol's definition precisely, prefer the definition tool if LSP is available. Automatically excludes node_modules and .git.",
|
|
3274
3408
|
inputSchema: {
|
|
@@ -3357,17 +3491,17 @@ var init_grep = __esm({
|
|
|
3357
3491
|
}
|
|
3358
3492
|
const rgCmd = `rg ${rgFlags}${globFlag} '${escaped}' ${searchPath}`;
|
|
3359
3493
|
const grepCmd = `grep ${grepFlags} '${escaped}' ${searchPath} --include='*.ts' --include='*.tsx' --include='*.js' --include='*.json' --include='*.md'`;
|
|
3360
|
-
return new Promise((
|
|
3494
|
+
return new Promise((resolve3) => {
|
|
3361
3495
|
exec(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
|
|
3362
3496
|
if (stdout?.trim()) {
|
|
3363
|
-
|
|
3497
|
+
resolve3(formatResults(stdout, max, mode));
|
|
3364
3498
|
return;
|
|
3365
3499
|
}
|
|
3366
3500
|
exec(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
|
|
3367
3501
|
if (grepStdout?.trim()) {
|
|
3368
|
-
|
|
3502
|
+
resolve3(formatResults(grepStdout, max, mode));
|
|
3369
3503
|
} else {
|
|
3370
|
-
|
|
3504
|
+
resolve3("No matches found.");
|
|
3371
3505
|
}
|
|
3372
3506
|
});
|
|
3373
3507
|
});
|
|
@@ -3385,8 +3519,8 @@ var init_glob = __esm({
|
|
|
3385
3519
|
"use strict";
|
|
3386
3520
|
DEFAULT_MAX2 = 200;
|
|
3387
3521
|
globTool = {
|
|
3388
|
-
clearable: true,
|
|
3389
3522
|
definition: {
|
|
3523
|
+
clearable: true,
|
|
3390
3524
|
name: "glob",
|
|
3391
3525
|
description: 'Find files matching a glob pattern. Returns matching file paths sorted alphabetically (default 200 results). Use this to discover project structure, find files by name or extension, or check if a file exists. Common patterns: "**/*.ts" (all TypeScript files), "src/**/*.tsx" (React components in src), "*.json" (root-level JSON files). Automatically excludes node_modules and .git.',
|
|
3392
3526
|
inputSchema: {
|
|
@@ -3432,10 +3566,10 @@ var init_glob = __esm({
|
|
|
3432
3566
|
});
|
|
3433
3567
|
|
|
3434
3568
|
// src/tools/code/listDir.ts
|
|
3435
|
-
import
|
|
3436
|
-
import
|
|
3569
|
+
import fs15 from "fs/promises";
|
|
3570
|
+
import path8 from "path";
|
|
3437
3571
|
async function readAndSort(dirPath) {
|
|
3438
|
-
const entries = await
|
|
3572
|
+
const entries = await fs15.readdir(dirPath, { withFileTypes: true });
|
|
3439
3573
|
return entries.filter((e) => !EXCLUDE.has(e.name)).sort((a, b) => {
|
|
3440
3574
|
if (a.isDirectory() && !b.isDirectory()) {
|
|
3441
3575
|
return -1;
|
|
@@ -3448,7 +3582,7 @@ async function readAndSort(dirPath) {
|
|
|
3448
3582
|
}
|
|
3449
3583
|
async function collapsePath(basePath, name) {
|
|
3450
3584
|
let display = name;
|
|
3451
|
-
let current =
|
|
3585
|
+
let current = path8.join(basePath, name);
|
|
3452
3586
|
for (; ; ) {
|
|
3453
3587
|
let children;
|
|
3454
3588
|
try {
|
|
@@ -3458,7 +3592,7 @@ async function collapsePath(basePath, name) {
|
|
|
3458
3592
|
}
|
|
3459
3593
|
if (children.length === 1 && children[0].isDirectory()) {
|
|
3460
3594
|
display += "/" + children[0].name;
|
|
3461
|
-
current =
|
|
3595
|
+
current = path8.join(current, children[0].name);
|
|
3462
3596
|
} else {
|
|
3463
3597
|
break;
|
|
3464
3598
|
}
|
|
@@ -3476,8 +3610,8 @@ function formatSize(bytes) {
|
|
|
3476
3610
|
}
|
|
3477
3611
|
async function formatFile(dirPath, name, indent) {
|
|
3478
3612
|
try {
|
|
3479
|
-
const
|
|
3480
|
-
return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(
|
|
3613
|
+
const stat2 = await fs15.stat(path8.join(dirPath, name));
|
|
3614
|
+
return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat2.size)}`;
|
|
3481
3615
|
} catch {
|
|
3482
3616
|
return `${indent}${name}`;
|
|
3483
3617
|
}
|
|
@@ -3489,8 +3623,8 @@ var init_listDir = __esm({
|
|
|
3489
3623
|
EXCLUDE = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
3490
3624
|
MAX_CHILDREN = 15;
|
|
3491
3625
|
listDirTool = {
|
|
3492
|
-
clearable: true,
|
|
3493
3626
|
definition: {
|
|
3627
|
+
clearable: true,
|
|
3494
3628
|
name: "listDir",
|
|
3495
3629
|
description: "List the contents of a directory with one level of subdirectory expansion. Shows file sizes and collapses single-child directory chains (a/b/c/ shown as one entry). Use this for a quick overview of a directory's structure. For finding files across the whole project, use glob instead.",
|
|
3496
3630
|
inputSchema: {
|
|
@@ -3572,8 +3706,8 @@ var init_editsFinished = __esm({
|
|
|
3572
3706
|
"src/tools/code/editsFinished.ts"() {
|
|
3573
3707
|
"use strict";
|
|
3574
3708
|
editsFinishedTool = {
|
|
3575
|
-
clearable: false,
|
|
3576
3709
|
definition: {
|
|
3710
|
+
clearable: false,
|
|
3577
3711
|
name: "editsFinished",
|
|
3578
3712
|
description: "Signal that file edits are complete. Call this after you finish writing/editing files so the live preview updates cleanly. The preview is paused while you edit to avoid showing broken intermediate states \u2014 this unpauses it. If you forget to call this, the preview updates when your turn ends.",
|
|
3579
3713
|
inputSchema: {
|
|
@@ -3599,6 +3733,7 @@ async function sidecarRequest(endpoint, body = {}, options) {
|
|
|
3599
3733
|
throw new Error("Sidecar not available");
|
|
3600
3734
|
}
|
|
3601
3735
|
const url = `${baseUrl}${endpoint}`;
|
|
3736
|
+
let data;
|
|
3602
3737
|
try {
|
|
3603
3738
|
const res = await fetch(url, {
|
|
3604
3739
|
method: "POST",
|
|
@@ -3610,12 +3745,7 @@ async function sidecarRequest(endpoint, body = {}, options) {
|
|
|
3610
3745
|
log5.error("Sidecar error", { endpoint, status: res.status });
|
|
3611
3746
|
throw new Error(`Sidecar error: ${res.status}`);
|
|
3612
3747
|
}
|
|
3613
|
-
|
|
3614
|
-
if (data?.success === false) {
|
|
3615
|
-
const code = data.errorCode ? ` [${data.errorCode}]` : "";
|
|
3616
|
-
throw new Error(`${data.error || "Unknown error"}${code}`);
|
|
3617
|
-
}
|
|
3618
|
-
return data;
|
|
3748
|
+
data = await res.json();
|
|
3619
3749
|
} catch (err) {
|
|
3620
3750
|
if (err.message.startsWith("Sidecar error")) {
|
|
3621
3751
|
throw err;
|
|
@@ -3623,6 +3753,16 @@ async function sidecarRequest(endpoint, body = {}, options) {
|
|
|
3623
3753
|
log5.error("Sidecar connection error", { endpoint, error: err.message });
|
|
3624
3754
|
throw new Error(`Sidecar connection error: ${err.message}`);
|
|
3625
3755
|
}
|
|
3756
|
+
if (data?.success === false) {
|
|
3757
|
+
const code = data.errorCode ? ` [${data.errorCode}]` : "";
|
|
3758
|
+
log5.error("Sidecar command failed", {
|
|
3759
|
+
endpoint,
|
|
3760
|
+
error: data.error,
|
|
3761
|
+
errorCode: data.errorCode
|
|
3762
|
+
});
|
|
3763
|
+
throw new Error(`${data.error || "Unknown error"}${code}`);
|
|
3764
|
+
}
|
|
3765
|
+
return data;
|
|
3626
3766
|
}
|
|
3627
3767
|
var log5, baseUrl;
|
|
3628
3768
|
var init_sidecar = __esm({
|
|
@@ -3654,8 +3794,8 @@ var init_lspDiagnostics = __esm({
|
|
|
3654
3794
|
"use strict";
|
|
3655
3795
|
init_lsp();
|
|
3656
3796
|
lspDiagnosticsTool = {
|
|
3657
|
-
clearable: true,
|
|
3658
3797
|
definition: {
|
|
3798
|
+
clearable: true,
|
|
3659
3799
|
name: "lspDiagnostics",
|
|
3660
3800
|
description: "Get TypeScript diagnostics (type errors, warnings) for a file, with suggested fixes when available. Use this after editing a file to check for errors.",
|
|
3661
3801
|
inputSchema: {
|
|
@@ -3710,8 +3850,8 @@ var init_restartProcess = __esm({
|
|
|
3710
3850
|
"use strict";
|
|
3711
3851
|
init_lsp();
|
|
3712
3852
|
restartProcessTool = {
|
|
3713
|
-
clearable: false,
|
|
3714
3853
|
definition: {
|
|
3854
|
+
clearable: false,
|
|
3715
3855
|
name: "restartProcess",
|
|
3716
3856
|
description: "Restart a managed sandbox process. Use this after running npm install or changing package.json to restart the dev server so it picks up new dependencies.",
|
|
3717
3857
|
inputSchema: {
|
|
@@ -3728,7 +3868,7 @@ var init_restartProcess = __esm({
|
|
|
3728
3868
|
async execute(input) {
|
|
3729
3869
|
const data = await lspRequest("/restart-process", { name: input.name });
|
|
3730
3870
|
if (data.ok) {
|
|
3731
|
-
await new Promise((
|
|
3871
|
+
await new Promise((resolve3) => setTimeout(resolve3, 5e3));
|
|
3732
3872
|
return `Restarted ${input.name}.`;
|
|
3733
3873
|
}
|
|
3734
3874
|
return `Error: unexpected response: ${JSON.stringify(data)}`;
|
|
@@ -3743,8 +3883,8 @@ var init_runScenario = __esm({
|
|
|
3743
3883
|
"src/tools/code/runScenario.ts"() {
|
|
3744
3884
|
"use strict";
|
|
3745
3885
|
runScenarioTool = {
|
|
3746
|
-
clearable: true,
|
|
3747
3886
|
definition: {
|
|
3887
|
+
clearable: true,
|
|
3748
3888
|
name: "runScenario",
|
|
3749
3889
|
description: "Run a scenario to seed the dev database with test data. By default truncates all tables first, then executes the seed function and impersonates the scenario roles. Use skipTruncate to run the seed function against existing data without resetting. Blocks until complete. Scenario IDs are defined in mindstudio.json. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for details. Return synchronously - no need to sleep before checking results.",
|
|
3750
3890
|
inputSchema: {
|
|
@@ -3775,8 +3915,8 @@ var init_runMethod = __esm({
|
|
|
3775
3915
|
"src/tools/code/runMethod.ts"() {
|
|
3776
3916
|
"use strict";
|
|
3777
3917
|
runMethodTool = {
|
|
3778
|
-
clearable: true,
|
|
3779
3918
|
definition: {
|
|
3919
|
+
clearable: true,
|
|
3780
3920
|
name: "runMethod",
|
|
3781
3921
|
description: 'Run a method in the dev environment and return the result. Use for testing methods after writing or modifying them. Returns output, captured console output, errors with stack traces, and duration. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for more details. Returns synchronously \u2014 no need to sleep before checking results.\n\nBy default methods run unauthenticated. If the method is auth-gated (calls `auth.requireRole()`, filters on `auth.userId`, etc.), pass `userId: "testUser"` to run as the default test user \u2014 no scenario setup required, no userId lookup.',
|
|
3782
3922
|
inputSchema: {
|
|
@@ -3816,8 +3956,8 @@ var init_queryDatabase = __esm({
|
|
|
3816
3956
|
"src/tools/code/queryDatabase.ts"() {
|
|
3817
3957
|
"use strict";
|
|
3818
3958
|
queryDatabaseTool = {
|
|
3819
|
-
clearable: true,
|
|
3820
3959
|
definition: {
|
|
3960
|
+
clearable: true,
|
|
3821
3961
|
name: "queryDatabase",
|
|
3822
3962
|
description: "Execute a raw SQL query against the dev database and return the results. Use for inspecting data and debugging issues.",
|
|
3823
3963
|
inputSchema: {
|
|
@@ -3838,26 +3978,136 @@ var init_queryDatabase = __esm({
|
|
|
3838
3978
|
}
|
|
3839
3979
|
});
|
|
3840
3980
|
|
|
3981
|
+
// src/tools/_helpers/uploadImage.ts
|
|
3982
|
+
import { readFile, stat } from "fs/promises";
|
|
3983
|
+
import { basename, extname, resolve } from "path";
|
|
3984
|
+
function isFetchableUrl(ref) {
|
|
3985
|
+
return /^(?:https?:|data:)/i.test(ref);
|
|
3986
|
+
}
|
|
3987
|
+
async function resolveImageRef(ref, apiConfig) {
|
|
3988
|
+
const trimmed = ref.trim();
|
|
3989
|
+
return isFetchableUrl(trimmed) ? trimmed : uploadLocalImage(trimmed, apiConfig);
|
|
3990
|
+
}
|
|
3991
|
+
function resolveImageRefs(refs, apiConfig) {
|
|
3992
|
+
return Promise.all(refs.map((ref) => resolveImageRef(ref, apiConfig)));
|
|
3993
|
+
}
|
|
3994
|
+
async function uploadLocalImage(localPath, apiConfig) {
|
|
3995
|
+
const absolute = resolve(PROJECT_ROOT, localPath);
|
|
3996
|
+
const ext = extname(absolute).toLowerCase();
|
|
3997
|
+
const contentType = CONTENT_TYPES[ext];
|
|
3998
|
+
if (!contentType) {
|
|
3999
|
+
throw new Error(
|
|
4000
|
+
`Cannot use "${localPath}" as an image \u2014 vision models read ${Object.keys(
|
|
4001
|
+
CONTENT_TYPES
|
|
4002
|
+
).join(", ")}. Convert it first, or pass an image URL.`
|
|
4003
|
+
);
|
|
4004
|
+
}
|
|
4005
|
+
let stats;
|
|
4006
|
+
try {
|
|
4007
|
+
stats = await stat(absolute);
|
|
4008
|
+
} catch {
|
|
4009
|
+
throw new Error(
|
|
4010
|
+
`No file at "${localPath}". Paths resolve from the project root; list the directory to check the name, or pass an image URL.`
|
|
4011
|
+
);
|
|
4012
|
+
}
|
|
4013
|
+
const cacheKey = `${absolute}:${stats.mtimeMs}:${stats.size}`;
|
|
4014
|
+
const cached3 = hosted.get(cacheKey);
|
|
4015
|
+
if (cached3) {
|
|
4016
|
+
return cached3;
|
|
4017
|
+
}
|
|
4018
|
+
if (!apiConfig?.appId) {
|
|
4019
|
+
throw new Error(
|
|
4020
|
+
`Cannot host "${localPath}" for analysis \u2014 this session has no app id. Pass an image URL instead.`
|
|
4021
|
+
);
|
|
4022
|
+
}
|
|
4023
|
+
const bytes = await readFile(absolute);
|
|
4024
|
+
const target = await requestUploadTarget(apiConfig, ext.slice(1));
|
|
4025
|
+
const form = new FormData();
|
|
4026
|
+
for (const [field, value] of Object.entries(target.uploadFields)) {
|
|
4027
|
+
form.append(field, value);
|
|
4028
|
+
}
|
|
4029
|
+
form.append(
|
|
4030
|
+
"file",
|
|
4031
|
+
new Blob([bytes], { type: contentType }),
|
|
4032
|
+
basename(absolute)
|
|
4033
|
+
);
|
|
4034
|
+
const res = await fetch(target.uploadUrl, {
|
|
4035
|
+
method: "POST",
|
|
4036
|
+
body: form,
|
|
4037
|
+
signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS)
|
|
4038
|
+
});
|
|
4039
|
+
if (!res.ok) {
|
|
4040
|
+
throw new Error(`Upload of "${localPath}" failed: HTTP ${res.status}`);
|
|
4041
|
+
}
|
|
4042
|
+
log6.info("Local image hosted", {
|
|
4043
|
+
path: localPath,
|
|
4044
|
+
bytes: stats.size,
|
|
4045
|
+
url: target.publicUrl
|
|
4046
|
+
});
|
|
4047
|
+
hosted.set(cacheKey, target.publicUrl);
|
|
4048
|
+
return target.publicUrl;
|
|
4049
|
+
}
|
|
4050
|
+
async function requestUploadTarget(apiConfig, extension) {
|
|
4051
|
+
const url = `${apiConfig.baseUrl}/_internal/v2/apps/${apiConfig.appId}/dev/manage/upload`;
|
|
4052
|
+
const res = await fetch(url, {
|
|
4053
|
+
method: "POST",
|
|
4054
|
+
headers: {
|
|
4055
|
+
"Content-Type": "application/json",
|
|
4056
|
+
Authorization: `Bearer ${apiConfig.apiKey}`
|
|
4057
|
+
},
|
|
4058
|
+
body: JSON.stringify({ extension }),
|
|
4059
|
+
signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS)
|
|
4060
|
+
});
|
|
4061
|
+
if (!res.ok) {
|
|
4062
|
+
throw new Error(`Could not get an upload URL: HTTP ${res.status}`);
|
|
4063
|
+
}
|
|
4064
|
+
return await res.json();
|
|
4065
|
+
}
|
|
4066
|
+
var log6, UPLOAD_TIMEOUT_MS, CONTENT_TYPES, hosted;
|
|
4067
|
+
var init_uploadImage = __esm({
|
|
4068
|
+
"src/tools/_helpers/uploadImage.ts"() {
|
|
4069
|
+
"use strict";
|
|
4070
|
+
init_projectRoot();
|
|
4071
|
+
init_logger();
|
|
4072
|
+
log6 = createLogger("uploadImage");
|
|
4073
|
+
UPLOAD_TIMEOUT_MS = 6e4;
|
|
4074
|
+
CONTENT_TYPES = {
|
|
4075
|
+
".png": "image/png",
|
|
4076
|
+
".jpg": "image/jpeg",
|
|
4077
|
+
".jpeg": "image/jpeg",
|
|
4078
|
+
".gif": "image/gif",
|
|
4079
|
+
".webp": "image/webp"
|
|
4080
|
+
};
|
|
4081
|
+
hosted = /* @__PURE__ */ new Map();
|
|
4082
|
+
}
|
|
4083
|
+
});
|
|
4084
|
+
|
|
3841
4085
|
// src/subagents/common/analyzeImage.ts
|
|
3842
4086
|
async function analyzeImage(params) {
|
|
3843
|
-
const { prompt,
|
|
3844
|
-
|
|
4087
|
+
const { prompt, image, apiConfig, model, timeout = 2e5, onLog } = params;
|
|
4088
|
+
const url = await resolveImageRef(image, apiConfig);
|
|
4089
|
+
const result = await runMindstudioCliResult(
|
|
3845
4090
|
[
|
|
3846
4091
|
"analyze-image",
|
|
3847
4092
|
"--prompt",
|
|
3848
4093
|
prompt,
|
|
3849
4094
|
"--image-url",
|
|
3850
|
-
|
|
4095
|
+
url,
|
|
3851
4096
|
"--vision-model-override",
|
|
3852
4097
|
JSON.stringify({ model })
|
|
3853
4098
|
],
|
|
3854
4099
|
{ outputKey: "analysis", timeout, onLog }
|
|
3855
4100
|
);
|
|
4101
|
+
if (!result.ok) {
|
|
4102
|
+
throw new Error(`Could not analyze ${url}: ${result.value}`);
|
|
4103
|
+
}
|
|
4104
|
+
return { url, analysis: result.value };
|
|
3856
4105
|
}
|
|
3857
4106
|
var init_analyzeImage = __esm({
|
|
3858
4107
|
"src/subagents/common/analyzeImage.ts"() {
|
|
3859
4108
|
"use strict";
|
|
3860
4109
|
init_runMindstudioCli();
|
|
4110
|
+
init_uploadImage();
|
|
3861
4111
|
}
|
|
3862
4112
|
});
|
|
3863
4113
|
|
|
@@ -3879,13 +4129,14 @@ ${ANALYSIS_RESPONSE_FORMAT}`;
|
|
|
3879
4129
|
return p;
|
|
3880
4130
|
}
|
|
3881
4131
|
async function streamScreenshotAnalysis(opts) {
|
|
3882
|
-
const {
|
|
4132
|
+
const { image, prompt, styleMap, onLog, model, apiConfig } = opts;
|
|
4133
|
+
const url = await resolveImageRef(image, apiConfig);
|
|
3883
4134
|
onLog?.(JSON.stringify({ url, analysis: null }));
|
|
3884
4135
|
const analysisPrompt = buildScreenshotAnalysisPrompt({ prompt, styleMap });
|
|
3885
4136
|
let accumulated = "";
|
|
3886
|
-
const analysis = await analyzeImage({
|
|
4137
|
+
const { analysis } = await analyzeImage({
|
|
3887
4138
|
prompt: analysisPrompt,
|
|
3888
|
-
|
|
4139
|
+
image: url,
|
|
3889
4140
|
model,
|
|
3890
4141
|
onLog: (chunk) => {
|
|
3891
4142
|
accumulated += chunk;
|
|
@@ -3896,18 +4147,19 @@ async function streamScreenshotAnalysis(opts) {
|
|
|
3896
4147
|
}
|
|
3897
4148
|
async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
3898
4149
|
let prompt;
|
|
3899
|
-
let
|
|
4150
|
+
let existingImage;
|
|
3900
4151
|
let onLog;
|
|
3901
4152
|
let model;
|
|
3902
|
-
let
|
|
4153
|
+
let apiConfig;
|
|
4154
|
+
let path15;
|
|
3903
4155
|
let fullPage = true;
|
|
3904
4156
|
let width;
|
|
3905
4157
|
let height;
|
|
3906
4158
|
let format;
|
|
3907
4159
|
if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
|
|
3908
4160
|
prompt = promptOrOptions.prompt;
|
|
3909
|
-
|
|
3910
|
-
|
|
4161
|
+
existingImage = promptOrOptions.image;
|
|
4162
|
+
path15 = promptOrOptions.path;
|
|
3911
4163
|
if (promptOrOptions.fullPage !== void 0) {
|
|
3912
4164
|
fullPage = promptOrOptions.fullPage;
|
|
3913
4165
|
}
|
|
@@ -3916,6 +4168,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
3916
4168
|
format = promptOrOptions.format;
|
|
3917
4169
|
onLog = promptOrOptions.onLog;
|
|
3918
4170
|
model = promptOrOptions.model;
|
|
4171
|
+
apiConfig = promptOrOptions.apiConfig;
|
|
3919
4172
|
} else {
|
|
3920
4173
|
prompt = promptOrOptions;
|
|
3921
4174
|
}
|
|
@@ -3924,18 +4177,20 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
3924
4177
|
}
|
|
3925
4178
|
let url;
|
|
3926
4179
|
let styleMap;
|
|
3927
|
-
if (
|
|
3928
|
-
url =
|
|
4180
|
+
if (existingImage) {
|
|
4181
|
+
url = existingImage;
|
|
3929
4182
|
} else {
|
|
3930
4183
|
const ssResult = await sidecarRequest(
|
|
3931
4184
|
fullPage ? "/screenshot-full-page" : "/screenshot-viewport",
|
|
3932
4185
|
{
|
|
3933
|
-
...
|
|
4186
|
+
...path15 ? { path: path15 } : {},
|
|
3934
4187
|
...width != null ? { width } : {},
|
|
3935
4188
|
...height != null ? { height } : {},
|
|
3936
4189
|
...format ? { format } : {}
|
|
3937
4190
|
},
|
|
3938
|
-
{
|
|
4191
|
+
{
|
|
4192
|
+
timeout: fullPage ? FULLPAGE_CAPTURE_TIMEOUT_MS : VIEWPORT_CAPTURE_TIMEOUT_MS
|
|
4193
|
+
}
|
|
3939
4194
|
);
|
|
3940
4195
|
url = ssResult?.url || ssResult?.screenshotUrl;
|
|
3941
4196
|
if (!url) {
|
|
@@ -3946,7 +4201,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
3946
4201
|
styleMap = ssResult?.styleMap;
|
|
3947
4202
|
}
|
|
3948
4203
|
if (prompt === false) {
|
|
3949
|
-
return url;
|
|
4204
|
+
return resolveImageRef(url, apiConfig);
|
|
3950
4205
|
}
|
|
3951
4206
|
if (!model) {
|
|
3952
4207
|
throw new Error(
|
|
@@ -3954,19 +4209,23 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
3954
4209
|
);
|
|
3955
4210
|
}
|
|
3956
4211
|
return streamScreenshotAnalysis({
|
|
3957
|
-
url,
|
|
4212
|
+
image: url,
|
|
4213
|
+
apiConfig,
|
|
3958
4214
|
prompt: prompt || void 0,
|
|
3959
4215
|
styleMap,
|
|
3960
4216
|
onLog,
|
|
3961
4217
|
model
|
|
3962
4218
|
});
|
|
3963
4219
|
}
|
|
3964
|
-
var SCREENSHOT_ANALYSIS_PROMPT, ANALYSIS_RESPONSE_FORMAT;
|
|
4220
|
+
var VIEWPORT_CAPTURE_TIMEOUT_MS, FULLPAGE_CAPTURE_TIMEOUT_MS, SCREENSHOT_ANALYSIS_PROMPT, ANALYSIS_RESPONSE_FORMAT;
|
|
3965
4221
|
var init_screenshot = __esm({
|
|
3966
4222
|
"src/tools/_helpers/screenshot.ts"() {
|
|
3967
4223
|
"use strict";
|
|
3968
4224
|
init_sidecar();
|
|
3969
4225
|
init_analyzeImage();
|
|
4226
|
+
init_uploadImage();
|
|
4227
|
+
VIEWPORT_CAPTURE_TIMEOUT_MS = 45e3;
|
|
4228
|
+
FULLPAGE_CAPTURE_TIMEOUT_MS = 135e3;
|
|
3970
4229
|
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).`;
|
|
3971
4230
|
ANALYSIS_RESPONSE_FORMAT = `Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.`;
|
|
3972
4231
|
}
|
|
@@ -3991,12 +4250,12 @@ var init_browserLock = __esm({
|
|
|
3991
4250
|
});
|
|
3992
4251
|
|
|
3993
4252
|
// src/toolRegistry.ts
|
|
3994
|
-
var
|
|
4253
|
+
var log7, USER_CANCELLED_RESULT, ToolRegistry;
|
|
3995
4254
|
var init_toolRegistry = __esm({
|
|
3996
4255
|
"src/toolRegistry.ts"() {
|
|
3997
4256
|
"use strict";
|
|
3998
4257
|
init_logger();
|
|
3999
|
-
|
|
4258
|
+
log7 = createLogger("tool-registry");
|
|
4000
4259
|
USER_CANCELLED_RESULT = "[USER CANCELLED] The user manually cancelled this tool. Do not retry it automatically \u2014 wait for the user\u2019s next message for direction.";
|
|
4001
4260
|
ToolRegistry = class {
|
|
4002
4261
|
entries = /* @__PURE__ */ new Map();
|
|
@@ -4023,7 +4282,7 @@ var init_toolRegistry = __esm({
|
|
|
4023
4282
|
if (!entry) {
|
|
4024
4283
|
return false;
|
|
4025
4284
|
}
|
|
4026
|
-
|
|
4285
|
+
log7.info("Tool stopped", { toolCallId: id, name: entry.name, mode });
|
|
4027
4286
|
entry.abortController.abort(mode);
|
|
4028
4287
|
if (mode === "graceful") {
|
|
4029
4288
|
const partial = entry.getPartialResult?.() ?? "";
|
|
@@ -4056,7 +4315,7 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
|
4056
4315
|
if (!entry) {
|
|
4057
4316
|
return false;
|
|
4058
4317
|
}
|
|
4059
|
-
|
|
4318
|
+
log7.info("Tool restarted", { toolCallId: id, name: entry.name });
|
|
4060
4319
|
entry.abortController.abort("restart");
|
|
4061
4320
|
const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
|
|
4062
4321
|
this.onEvent?.({
|
|
@@ -4165,7 +4424,7 @@ async function runSubAgent(config) {
|
|
|
4165
4424
|
const signal = background ? bgAbort.signal : parentSignal;
|
|
4166
4425
|
const agentName = subAgentId || "sub-agent";
|
|
4167
4426
|
const runStart = Date.now();
|
|
4168
|
-
|
|
4427
|
+
log8.info("Sub-agent started", { requestId, parentToolId, agentName });
|
|
4169
4428
|
const emit = (e) => {
|
|
4170
4429
|
onEvent({ ...e, parentToolId });
|
|
4171
4430
|
};
|
|
@@ -4177,7 +4436,6 @@ async function runSubAgent(config) {
|
|
|
4177
4436
|
const fullSystem = `${system}
|
|
4178
4437
|
|
|
4179
4438
|
Current date: ${dateStr}`;
|
|
4180
|
-
const excludeToolsFromClearing = tools2.filter((t) => t.clearable === false).map((t) => t.name);
|
|
4181
4439
|
let turns = 0;
|
|
4182
4440
|
const run = async () => {
|
|
4183
4441
|
const historyLen = (history ?? []).length;
|
|
@@ -4249,7 +4507,6 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4249
4507
|
system: fullSystem,
|
|
4250
4508
|
messages: cleanMessagesForApi(messages),
|
|
4251
4509
|
tools: tools2,
|
|
4252
|
-
excludeToolsFromClearing,
|
|
4253
4510
|
signal
|
|
4254
4511
|
},
|
|
4255
4512
|
{
|
|
@@ -4385,7 +4642,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4385
4642
|
...hasArtifacts ? { artifacts } : {}
|
|
4386
4643
|
};
|
|
4387
4644
|
}
|
|
4388
|
-
|
|
4645
|
+
log8.info("Tools executing", {
|
|
4389
4646
|
requestId,
|
|
4390
4647
|
parentToolId,
|
|
4391
4648
|
count: toolCalls.length,
|
|
@@ -4462,7 +4719,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4462
4719
|
run2(tc.input);
|
|
4463
4720
|
const r = await resultPromise;
|
|
4464
4721
|
toolRegistry?.unregister(tc.id);
|
|
4465
|
-
|
|
4722
|
+
log8.info("Tool completed", {
|
|
4466
4723
|
requestId,
|
|
4467
4724
|
parentToolId,
|
|
4468
4725
|
toolCallId: tc.id,
|
|
@@ -4513,7 +4770,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4513
4770
|
const wrapRun = async () => {
|
|
4514
4771
|
try {
|
|
4515
4772
|
const result = await run();
|
|
4516
|
-
|
|
4773
|
+
log8.info("Sub-agent complete", {
|
|
4517
4774
|
requestId,
|
|
4518
4775
|
parentToolId,
|
|
4519
4776
|
agentName,
|
|
@@ -4522,7 +4779,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4522
4779
|
});
|
|
4523
4780
|
return result;
|
|
4524
4781
|
} catch (err) {
|
|
4525
|
-
|
|
4782
|
+
log8.warn("Sub-agent error", {
|
|
4526
4783
|
requestId,
|
|
4527
4784
|
parentToolId,
|
|
4528
4785
|
agentName,
|
|
@@ -4534,7 +4791,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4534
4791
|
if (!background) {
|
|
4535
4792
|
return wrapRun();
|
|
4536
4793
|
}
|
|
4537
|
-
|
|
4794
|
+
log8.info("Sub-agent backgrounded", { requestId, parentToolId, agentName });
|
|
4538
4795
|
toolRegistry?.register({
|
|
4539
4796
|
id: parentToolId,
|
|
4540
4797
|
name: agentName,
|
|
@@ -4569,7 +4826,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4569
4826
|
});
|
|
4570
4827
|
return { text: ack, messages: [], backgrounded: true };
|
|
4571
4828
|
}
|
|
4572
|
-
var
|
|
4829
|
+
var log8;
|
|
4573
4830
|
var init_runner = __esm({
|
|
4574
4831
|
"src/subagents/runner.ts"() {
|
|
4575
4832
|
"use strict";
|
|
@@ -4580,7 +4837,7 @@ var init_runner = __esm({
|
|
|
4580
4837
|
init_toolResultCap();
|
|
4581
4838
|
init_statusWatcher();
|
|
4582
4839
|
init_cleanMessages();
|
|
4583
|
-
|
|
4840
|
+
log8 = createLogger("sub-agent");
|
|
4584
4841
|
}
|
|
4585
4842
|
});
|
|
4586
4843
|
|
|
@@ -4673,7 +4930,7 @@ var init_tools2 = __esm({
|
|
|
4673
4930
|
"screenshotViewport",
|
|
4674
4931
|
"setViewport"
|
|
4675
4932
|
],
|
|
4676
|
-
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). screenshotFullPage:
|
|
4933
|
+
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). screenshotFullPage: screenshot of the whole page top-to-bottom (returns a CDN url with dimensions and a written analysis). screenshotViewport: screenshot of just the visible viewport \u2014 pass `scrollToSelector` (or `scrollY`) on this step to scroll a section into view and capture it in one atomic step (no separate scroll needed). setViewport: switch the browser between desktop and mobile rendering (pass `mode`: "desktop" or "mobile"). Reloads the page so responsive layouts, media queries, and matchMedia re-evaluate \u2014 use it to QA mobile/responsive views.'
|
|
4677
4934
|
},
|
|
4678
4935
|
ref: {
|
|
4679
4936
|
type: "string",
|
|
@@ -4741,20 +4998,11 @@ var init_tools2 = __esm({
|
|
|
4741
4998
|
required: ["steps"]
|
|
4742
4999
|
}
|
|
4743
5000
|
},
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
type: "object",
|
|
4750
|
-
properties: {
|
|
4751
|
-
path: {
|
|
4752
|
-
type: "string",
|
|
4753
|
-
description: 'Navigate to this path before capturing (e.g. "/settings"). If omitted, screenshots the current page.'
|
|
4754
|
-
}
|
|
4755
|
-
}
|
|
4756
|
-
}
|
|
4757
|
-
},
|
|
5001
|
+
// Captures are `browserCommand` steps only — there is deliberately no
|
|
5002
|
+
// standalone screenshot tool here. Both used to exist for full-page, with
|
|
5003
|
+
// different budgets, different result plumbing, and analysis on only one of
|
|
5004
|
+
// them, so which door you picked changed what you got back.
|
|
5005
|
+
//
|
|
4758
5006
|
// Read tools so the QA agent can pull full spec detail on demand — the spec
|
|
4759
5007
|
// context in its prompt is a lightweight index (see prompt.ts) that points
|
|
4760
5008
|
// here. Routed to the global executeTool in index.ts, mirroring specSync.
|
|
@@ -4766,13 +5014,13 @@ var init_tools2 = __esm({
|
|
|
4766
5014
|
});
|
|
4767
5015
|
|
|
4768
5016
|
// src/subagents/common/context.ts
|
|
4769
|
-
import
|
|
4770
|
-
import
|
|
5017
|
+
import fs16 from "fs";
|
|
5018
|
+
import path9 from "path";
|
|
4771
5019
|
function walkMdFiles(dir, skip) {
|
|
4772
5020
|
const files = [];
|
|
4773
5021
|
try {
|
|
4774
|
-
for (const entry of
|
|
4775
|
-
const full =
|
|
5022
|
+
for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
|
|
5023
|
+
const full = path9.join(dir, entry.name);
|
|
4776
5024
|
if (entry.isDirectory()) {
|
|
4777
5025
|
if (!skip?.has(entry.name)) {
|
|
4778
5026
|
files.push(...walkMdFiles(full, skip));
|
|
@@ -4785,9 +5033,9 @@ function walkMdFiles(dir, skip) {
|
|
|
4785
5033
|
}
|
|
4786
5034
|
return files.sort();
|
|
4787
5035
|
}
|
|
4788
|
-
function
|
|
5036
|
+
function parseFrontmatter2(filePath) {
|
|
4789
5037
|
try {
|
|
4790
|
-
const content =
|
|
5038
|
+
const content = fs16.readFileSync(filePath, "utf-8");
|
|
4791
5039
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
4792
5040
|
if (!match) {
|
|
4793
5041
|
return {};
|
|
@@ -4812,7 +5060,7 @@ function loadSpecIndex() {
|
|
|
4812
5060
|
return "";
|
|
4813
5061
|
}
|
|
4814
5062
|
const lines = files.map((f) => {
|
|
4815
|
-
const fm =
|
|
5063
|
+
const fm = parseFrontmatter2(f);
|
|
4816
5064
|
let line = `- ${f}`;
|
|
4817
5065
|
if (fm.name) {
|
|
4818
5066
|
line += ` \u2014 "${fm.name}"`;
|
|
@@ -4833,7 +5081,7 @@ function loadRoadmapIndex() {
|
|
|
4833
5081
|
const parts = [];
|
|
4834
5082
|
try {
|
|
4835
5083
|
const indexJson = JSON.parse(
|
|
4836
|
-
|
|
5084
|
+
fs16.readFileSync("src/roadmap/index.json", "utf-8")
|
|
4837
5085
|
);
|
|
4838
5086
|
if (indexJson.lanes?.length > 0) {
|
|
4839
5087
|
const laneLines = indexJson.lanes.map(
|
|
@@ -4853,7 +5101,7 @@ ${indexJson.standalone.map((s) => `- ${s}`).join("\n")}`
|
|
|
4853
5101
|
const files = walkMdFiles("src/roadmap");
|
|
4854
5102
|
if (files.length > 0) {
|
|
4855
5103
|
const lines = files.map((f) => {
|
|
4856
|
-
const fm =
|
|
5104
|
+
const fm = parseFrontmatter2(f);
|
|
4857
5105
|
let line = `- ${f}`;
|
|
4858
5106
|
if (fm.name) {
|
|
4859
5107
|
line += ` \u2014 "${fm.name}"`;
|
|
@@ -4973,7 +5221,7 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
4973
5221
|
);
|
|
4974
5222
|
} catch {
|
|
4975
5223
|
}
|
|
4976
|
-
let
|
|
5224
|
+
let lastCapture = {};
|
|
4977
5225
|
const result = await runSubAgent({
|
|
4978
5226
|
system: getBrowserAutomationPrompt(),
|
|
4979
5227
|
task,
|
|
@@ -4995,22 +5243,6 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
4995
5243
|
return `Error setting up browser: ${err.message}`;
|
|
4996
5244
|
}
|
|
4997
5245
|
}
|
|
4998
|
-
if (name === "screenshotFullPage") {
|
|
4999
|
-
try {
|
|
5000
|
-
return await captureAndAnalyzeScreenshot({
|
|
5001
|
-
path: _input.path,
|
|
5002
|
-
fullPage: true,
|
|
5003
|
-
onLog,
|
|
5004
|
-
model: resolveModel(
|
|
5005
|
-
"imageAnalysis",
|
|
5006
|
-
context.models,
|
|
5007
|
-
context.model
|
|
5008
|
-
)
|
|
5009
|
-
});
|
|
5010
|
-
} catch (err) {
|
|
5011
|
-
return `Error taking screenshot: ${err.message}`;
|
|
5012
|
-
}
|
|
5013
|
-
}
|
|
5014
5246
|
if (COMMON_READ_TOOL_NAMES.has(name) || name === readSpecTool.definition.name) {
|
|
5015
5247
|
return executeTool(name, _input, context);
|
|
5016
5248
|
}
|
|
@@ -5032,14 +5264,16 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
5032
5264
|
try {
|
|
5033
5265
|
const parsed = JSON.parse(result2);
|
|
5034
5266
|
const screenshotSteps = (parsed.steps || []).filter(
|
|
5035
|
-
(s) => s.command
|
|
5267
|
+
(s) => CAPTURE_COMMANDS.has(s.command) && s.result?.url
|
|
5036
5268
|
);
|
|
5037
5269
|
if (screenshotSteps.length > 0) {
|
|
5038
|
-
const
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5270
|
+
for (const step of screenshotSteps) {
|
|
5271
|
+
const kind = step.command === "screenshotFullPage" ? "fullPage" : "viewport";
|
|
5272
|
+
lastCapture[kind] = {
|
|
5273
|
+
url: step.result.url,
|
|
5274
|
+
styleMap: step.result.styleMap
|
|
5275
|
+
};
|
|
5276
|
+
}
|
|
5043
5277
|
const visionOverride = {
|
|
5044
5278
|
model: resolveModel(
|
|
5045
5279
|
"imageAnalysis",
|
|
@@ -5063,15 +5297,14 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
5063
5297
|
);
|
|
5064
5298
|
try {
|
|
5065
5299
|
const analyses = JSON.parse(batchResult);
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
step.result.analysis = analyses[ai]?.output?.analysis || analyses[ai]?.output || "";
|
|
5070
|
-
ai++;
|
|
5300
|
+
screenshotSteps.forEach((step, i) => {
|
|
5301
|
+
if (i >= analyses.length) {
|
|
5302
|
+
return;
|
|
5071
5303
|
}
|
|
5072
|
-
|
|
5304
|
+
step.result.analysis = analyses[i]?.output?.analysis || analyses[i]?.output || "";
|
|
5305
|
+
});
|
|
5073
5306
|
} catch {
|
|
5074
|
-
|
|
5307
|
+
log9.debug("Failed to parse batch analysis result", {
|
|
5075
5308
|
batchResult
|
|
5076
5309
|
});
|
|
5077
5310
|
}
|
|
@@ -5082,13 +5315,10 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
5082
5315
|
}
|
|
5083
5316
|
return result2;
|
|
5084
5317
|
},
|
|
5085
|
-
toolRegistry: context.toolRegistry
|
|
5086
|
-
captureArtifacts: ["screenshotFullPage"]
|
|
5318
|
+
toolRegistry: context.toolRegistry
|
|
5087
5319
|
});
|
|
5088
5320
|
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
5089
|
-
const
|
|
5090
|
-
const viewport = lastBrowserCommandViewport;
|
|
5091
|
-
const preferred = opts?.capture === "viewport" ? viewport ?? fullPage : fullPage ?? viewport;
|
|
5321
|
+
const preferred = opts?.capture === "viewport" ? lastCapture.viewport ?? lastCapture.fullPage : lastCapture.fullPage ?? lastCapture.viewport;
|
|
5092
5322
|
return {
|
|
5093
5323
|
text: result.text,
|
|
5094
5324
|
...preferred?.url ? { screenshot: { url: preferred.url, styleMap: preferred.styleMap } } : {}
|
|
@@ -5097,7 +5327,7 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
5097
5327
|
release();
|
|
5098
5328
|
}
|
|
5099
5329
|
}
|
|
5100
|
-
var
|
|
5330
|
+
var log9, CAPTURE_COMMANDS, browserAutomationTool;
|
|
5101
5331
|
var init_browserAutomation = __esm({
|
|
5102
5332
|
"src/subagents/browserAutomation/index.ts"() {
|
|
5103
5333
|
"use strict";
|
|
@@ -5113,10 +5343,11 @@ var init_browserAutomation = __esm({
|
|
|
5113
5343
|
init_runMindstudioCli();
|
|
5114
5344
|
init_surfaces();
|
|
5115
5345
|
init_logger();
|
|
5116
|
-
|
|
5346
|
+
log9 = createLogger("browser-automation");
|
|
5347
|
+
CAPTURE_COMMANDS = /* @__PURE__ */ new Set(["screenshotViewport", "screenshotFullPage"]);
|
|
5117
5348
|
browserAutomationTool = {
|
|
5118
|
-
clearable: true,
|
|
5119
5349
|
definition: {
|
|
5350
|
+
clearable: true,
|
|
5120
5351
|
name: "runAutomatedBrowserTest",
|
|
5121
5352
|
description: "Run an automated browser test against the live preview. Describe what to test \u2014 the agent figures out how. Use after meaningful changes to frontend code, to reproduce user-reported issues, or to test end-to-end flows. Never give it explicit values to use when filling out forms or creating accounts \u2014 it will use its own judgement (often it needs specific values to trigger dev-mode bypasses of things like login verification codes).",
|
|
5122
5353
|
inputSchema: {
|
|
@@ -5147,7 +5378,58 @@ var init_browserAutomation = __esm({
|
|
|
5147
5378
|
});
|
|
5148
5379
|
|
|
5149
5380
|
// src/tools/code/screenshot.ts
|
|
5150
|
-
|
|
5381
|
+
async function executeScreenshot(input, onLog, context) {
|
|
5382
|
+
const fullPage = input.fullPage === true;
|
|
5383
|
+
const model = resolveModel("imageAnalysis", context?.models, context?.model);
|
|
5384
|
+
try {
|
|
5385
|
+
if (input.imageUrl) {
|
|
5386
|
+
return await captureAndAnalyzeScreenshot({
|
|
5387
|
+
prompt: input.prompt,
|
|
5388
|
+
image: input.imageUrl,
|
|
5389
|
+
onLog,
|
|
5390
|
+
model,
|
|
5391
|
+
apiConfig: context?.apiConfig
|
|
5392
|
+
});
|
|
5393
|
+
}
|
|
5394
|
+
if (input.instructions && context) {
|
|
5395
|
+
const shotKind = fullPage ? "full-page" : "viewport";
|
|
5396
|
+
const task = input.path ? `Navigate to "${input.path}", then: ${input.instructions}. After completing these steps, take a ${shotKind} screenshot.` : `${input.instructions}. After completing these steps, take a ${shotKind} screenshot.`;
|
|
5397
|
+
const result = await runBrowserAutomation(task, context, {
|
|
5398
|
+
capture: fullPage ? "fullPage" : "viewport"
|
|
5399
|
+
});
|
|
5400
|
+
if (!result.screenshot) {
|
|
5401
|
+
return result.text;
|
|
5402
|
+
}
|
|
5403
|
+
return await streamScreenshotAnalysis({
|
|
5404
|
+
image: result.screenshot.url,
|
|
5405
|
+
prompt: input.prompt,
|
|
5406
|
+
styleMap: result.screenshot.styleMap,
|
|
5407
|
+
onLog,
|
|
5408
|
+
model,
|
|
5409
|
+
apiConfig: context?.apiConfig
|
|
5410
|
+
});
|
|
5411
|
+
}
|
|
5412
|
+
const release = await acquireBrowserLock();
|
|
5413
|
+
try {
|
|
5414
|
+
return await captureAndAnalyzeScreenshot({
|
|
5415
|
+
prompt: input.prompt,
|
|
5416
|
+
path: input.path,
|
|
5417
|
+
fullPage,
|
|
5418
|
+
width: input.width,
|
|
5419
|
+
height: input.height,
|
|
5420
|
+
format: input.format,
|
|
5421
|
+
onLog,
|
|
5422
|
+
model,
|
|
5423
|
+
apiConfig: context?.apiConfig
|
|
5424
|
+
});
|
|
5425
|
+
} finally {
|
|
5426
|
+
release();
|
|
5427
|
+
}
|
|
5428
|
+
} catch (err) {
|
|
5429
|
+
return `Error taking screenshot: ${err.message}`;
|
|
5430
|
+
}
|
|
5431
|
+
}
|
|
5432
|
+
var screenshotDefinition, screenshotTool;
|
|
5151
5433
|
var init_screenshot2 = __esm({
|
|
5152
5434
|
"src/tools/code/screenshot.ts"() {
|
|
5153
5435
|
"use strict";
|
|
@@ -5155,99 +5437,54 @@ var init_screenshot2 = __esm({
|
|
|
5155
5437
|
init_browserLock();
|
|
5156
5438
|
init_browserAutomation();
|
|
5157
5439
|
init_surfaces();
|
|
5158
|
-
|
|
5440
|
+
screenshotDefinition = {
|
|
5159
5441
|
clearable: true,
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
description: "true = full-height capture of the entire page; false = just the visible viewport. Pick based on whether you need the whole page or a specific section."
|
|
5169
|
-
},
|
|
5170
|
-
prompt: {
|
|
5171
|
-
type: "string",
|
|
5172
|
-
description: "Optional question about the screenshot. If omitted, returns a general description of what's visible."
|
|
5173
|
-
},
|
|
5174
|
-
imageUrl: {
|
|
5175
|
-
type: "string",
|
|
5176
|
-
description: "URL of an existing screenshot to analyze instead of capturing a new one. Use this for additional questions about a previous screenshot."
|
|
5177
|
-
},
|
|
5178
|
-
path: {
|
|
5179
|
-
type: "string",
|
|
5180
|
-
description: 'Navigate to this path before capturing (e.g. "/settings", "/dashboard"). If omitted, screenshots the current page.'
|
|
5181
|
-
},
|
|
5182
|
-
width: {
|
|
5183
|
-
type: "number",
|
|
5184
|
-
description: "Exact capture width in pixels. Set together with `height` to render a fixed-size image; clips to exactly this viewport instead of the default preview size."
|
|
5185
|
-
},
|
|
5186
|
-
height: {
|
|
5187
|
-
type: "number",
|
|
5188
|
-
description: "Exact capture height in pixels. Set together with `width`."
|
|
5189
|
-
},
|
|
5190
|
-
format: {
|
|
5191
|
-
type: "string",
|
|
5192
|
-
enum: ["png", "jpeg"],
|
|
5193
|
-
description: "Output image format. Defaults to 'jpeg'. Use 'png' for crisp flat graphics like share cards, where JPEG artifacts show on sharp type and edges."
|
|
5194
|
-
},
|
|
5195
|
-
instructions: {
|
|
5196
|
-
type: "string",
|
|
5197
|
-
description: "If the screenshot you need requires interaction first (dismissing a modal, clicking a tab, filling out a form, navigating a flow, scrolling to a section, getting through a login/auth checkpoint), describe the steps to get there. A browser automation agent will follow these instructions, then capture per your `fullPage` choice \u2014 so with `fullPage: false` you can scroll to a section and capture just that viewport. It can bypass auth and get right to where it needs to be if you tell it to authenticate as a test user and give it the path/screen to start its test at. Never describe what names or values to use when applying the instructions - the browser automation agent must use its own values for it to work properly. If a specific auth role is required to access the content, be sure to note that - it can automatically assume it for the purpose of testing. Use only when interaction is required to *reach* the state you want to capture \u2014 log in, dismiss a modal, switch a tab, follow a route, scroll to a section. If your steps are exercising the app's functionality across multiple states (running flows, asserting behavior under interaction, multi-step QA), use `runAutomatedBrowserTest` instead."
|
|
5198
|
-
}
|
|
5442
|
+
name: "screenshot",
|
|
5443
|
+
description: "Capture a screenshot of the app preview and get a description of what's on screen. Choose `fullPage`: `false` captures just the visible viewport (fast \u2014 for a specific section the page is scrolled to), `true` captures the entire page top-to-bottom (slower \u2014 for overall composition or content past the fold). Captures the settled page state \u2014 it cannot catch animations, transitions, or transient state. The analysis is not precise about every detail \u2014 for example it cannot reliably identify specific fonts by name, only describe what the letterforms look like. Optionally provide specific questions about what you're looking for. Use a bulleted list to ask many questions at once. To ask additional questions about a screenshot you have already captured, pass its URL as `imageUrl` to skip recapture; `imageUrl` also accepts the disk path of an image file (a user upload, a saved asset) to analyze that instead of the preview. If the screenshot requires interaction first (logging in, clicking a tab, dismissing a modal, scrolling to a section), use the instructions param to describe the steps. To render a fixed-size image such as an Open Graph share card, set `width` and `height` (e.g. 1200 \xD7 630) and `format: 'png'`: the tool navigates to `path`, clips to exactly those pixel dimensions, and returns the image URL.",
|
|
5444
|
+
inputSchema: {
|
|
5445
|
+
type: "object",
|
|
5446
|
+
properties: {
|
|
5447
|
+
fullPage: {
|
|
5448
|
+
type: "boolean",
|
|
5449
|
+
description: "true = full-height capture of the entire page; false = just the visible viewport. Pick based on whether you need the whole page or a specific section."
|
|
5199
5450
|
},
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
model: resolveModel("imageAnalysis", context?.models, context?.model)
|
|
5229
|
-
});
|
|
5230
|
-
}
|
|
5231
|
-
const release = await acquireBrowserLock();
|
|
5232
|
-
try {
|
|
5233
|
-
return await captureAndAnalyzeScreenshot({
|
|
5234
|
-
prompt: input.prompt,
|
|
5235
|
-
path: input.path,
|
|
5236
|
-
fullPage,
|
|
5237
|
-
width: input.width,
|
|
5238
|
-
height: input.height,
|
|
5239
|
-
format: input.format,
|
|
5240
|
-
onLog: context?.onLog,
|
|
5241
|
-
model: resolveModel("imageAnalysis", context?.models, context?.model)
|
|
5242
|
-
});
|
|
5243
|
-
} finally {
|
|
5244
|
-
release();
|
|
5451
|
+
prompt: {
|
|
5452
|
+
type: "string",
|
|
5453
|
+
description: "Optional question about the screenshot. If omitted, returns a general description of what's visible."
|
|
5454
|
+
},
|
|
5455
|
+
imageUrl: {
|
|
5456
|
+
type: "string",
|
|
5457
|
+
description: "An existing image to analyze instead of capturing a new one \u2014 the URL of a previous screenshot (for follow-up questions about it), or the path of an image file on disk such as a user upload. Local files are hosted automatically and the returned URL can be reused."
|
|
5458
|
+
},
|
|
5459
|
+
path: {
|
|
5460
|
+
type: "string",
|
|
5461
|
+
description: 'Navigate to this path before capturing (e.g. "/settings", "/dashboard"). If omitted, screenshots the current page.'
|
|
5462
|
+
},
|
|
5463
|
+
width: {
|
|
5464
|
+
type: "number",
|
|
5465
|
+
description: "Exact capture width in pixels. Set together with `height` to render a fixed-size image; clips to exactly this viewport instead of the default preview size."
|
|
5466
|
+
},
|
|
5467
|
+
height: {
|
|
5468
|
+
type: "number",
|
|
5469
|
+
description: "Exact capture height in pixels. Set together with `width`."
|
|
5470
|
+
},
|
|
5471
|
+
format: {
|
|
5472
|
+
type: "string",
|
|
5473
|
+
enum: ["png", "jpeg"],
|
|
5474
|
+
description: "Output image format. Defaults to 'jpeg'. Use 'png' for crisp flat graphics like share cards, where JPEG artifacts show on sharp type and edges."
|
|
5475
|
+
},
|
|
5476
|
+
instructions: {
|
|
5477
|
+
type: "string",
|
|
5478
|
+
description: "If the screenshot you need requires interaction first (dismissing a modal, clicking a tab, filling out a form, navigating a flow, scrolling to a section, getting through a login/auth checkpoint), describe the steps to get there. A browser automation agent will follow these instructions, then capture per your `fullPage` choice \u2014 so with `fullPage: false` you can scroll to a section and capture just that viewport. It can bypass auth and get right to where it needs to be if you tell it to authenticate as a test user and give it the path/screen to start its test at. Never describe what names or values to use when applying the instructions - the browser automation agent must use its own values for it to work properly. If a specific auth role is required to access the content, be sure to note that - it can automatically assume it for the purpose of testing. Use only when interaction is required to *reach* the state you want to capture \u2014 log in, dismiss a modal, switch a tab, follow a route, scroll to a section. If your steps are exercising the app's functionality across multiple states (running flows, asserting behavior under interaction, multi-step QA), use `runAutomatedBrowserTest` instead."
|
|
5245
5479
|
}
|
|
5246
|
-
}
|
|
5247
|
-
|
|
5248
|
-
}
|
|
5480
|
+
},
|
|
5481
|
+
required: ["fullPage"]
|
|
5249
5482
|
}
|
|
5250
5483
|
};
|
|
5484
|
+
screenshotTool = {
|
|
5485
|
+
definition: screenshotDefinition,
|
|
5486
|
+
execute: (input, context) => executeScreenshot(input, context?.onLog, context)
|
|
5487
|
+
};
|
|
5251
5488
|
}
|
|
5252
5489
|
});
|
|
5253
5490
|
|
|
@@ -5344,9 +5581,9 @@ __export(analyzeDesign_exports, {
|
|
|
5344
5581
|
async function execute3(input, onLog, context) {
|
|
5345
5582
|
const url = input.url;
|
|
5346
5583
|
const analysisPrompt = input.prompt || DESIGN_REFERENCE_PROMPT;
|
|
5347
|
-
const
|
|
5348
|
-
let
|
|
5349
|
-
if (!
|
|
5584
|
+
const isImage = /\.(png|jpe?g|webp|gif|svg|avif)(\?|$)/i.test(url);
|
|
5585
|
+
let image = url;
|
|
5586
|
+
if (!isImage) {
|
|
5350
5587
|
const ss = await runMindstudioCliResult(
|
|
5351
5588
|
[
|
|
5352
5589
|
"screenshot-url",
|
|
@@ -5369,15 +5606,16 @@ async function execute3(input, onLog, context) {
|
|
|
5369
5606
|
if (!ss.ok) {
|
|
5370
5607
|
return `Could not screenshot ${url}: ${ss.value}`;
|
|
5371
5608
|
}
|
|
5372
|
-
|
|
5609
|
+
image = ss.value;
|
|
5373
5610
|
}
|
|
5374
|
-
const
|
|
5611
|
+
const analyzed = await analyzeImage({
|
|
5375
5612
|
prompt: analysisPrompt,
|
|
5376
|
-
|
|
5613
|
+
image,
|
|
5614
|
+
apiConfig: context?.apiConfig,
|
|
5377
5615
|
onLog,
|
|
5378
5616
|
model: resolveModel("imageAnalysis", context?.models, context?.model)
|
|
5379
5617
|
});
|
|
5380
|
-
return JSON.stringify({ url:
|
|
5618
|
+
return JSON.stringify({ url: analyzed.url, analysis: analyzed.analysis });
|
|
5381
5619
|
}
|
|
5382
5620
|
var DESIGN_REFERENCE_PROMPT, definition3;
|
|
5383
5621
|
var init_analyzeDesign = __esm({
|
|
@@ -5412,13 +5650,13 @@ Respond only with your analysis as Markdown and absolutely no other text. Do not
|
|
|
5412
5650
|
definition3 = {
|
|
5413
5651
|
clearable: false,
|
|
5414
5652
|
name: "analyzeDesign",
|
|
5415
|
-
description: "Analyze the visual design of a website or image
|
|
5653
|
+
description: "Analyze the visual design of a website, an image URL, or an image file on disk. Websites are automatically screenshotted first. Provides static image analysis only, will not capture animations or video. 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. Use a bulleted list to ask many questions at once.",
|
|
5416
5654
|
inputSchema: {
|
|
5417
5655
|
type: "object",
|
|
5418
5656
|
properties: {
|
|
5419
5657
|
url: {
|
|
5420
5658
|
type: "string",
|
|
5421
|
-
description: "
|
|
5659
|
+
description: "What to analyze: a website URL (will be screenshotted), an image URL, or the path of an image file on disk (e.g. a reference the user uploaded, under src/.user-uploads/). Local files are hosted automatically and their URL comes back in the result."
|
|
5422
5660
|
},
|
|
5423
5661
|
prompt: {
|
|
5424
5662
|
type: "string",
|
|
@@ -5438,17 +5676,17 @@ __export(analyzeImage_exports, {
|
|
|
5438
5676
|
execute: () => execute4
|
|
5439
5677
|
});
|
|
5440
5678
|
async function execute4(input, onLog, context) {
|
|
5441
|
-
const imageUrl = input.imageUrl;
|
|
5442
5679
|
const prompt = buildScreenshotAnalysisPrompt({
|
|
5443
5680
|
prompt: input.prompt
|
|
5444
5681
|
});
|
|
5445
|
-
const analysis = await analyzeImage({
|
|
5682
|
+
const { url, analysis } = await analyzeImage({
|
|
5446
5683
|
prompt,
|
|
5447
|
-
imageUrl,
|
|
5684
|
+
image: input.imageUrl,
|
|
5685
|
+
apiConfig: context?.apiConfig,
|
|
5448
5686
|
onLog,
|
|
5449
5687
|
model: resolveModel("imageAnalysis", context?.models, context?.model)
|
|
5450
5688
|
});
|
|
5451
|
-
return JSON.stringify({ url
|
|
5689
|
+
return JSON.stringify({ url, analysis });
|
|
5452
5690
|
}
|
|
5453
5691
|
var definition4;
|
|
5454
5692
|
var init_analyzeImage2 = __esm({
|
|
@@ -5460,13 +5698,16 @@ var init_analyzeImage2 = __esm({
|
|
|
5460
5698
|
definition4 = {
|
|
5461
5699
|
clearable: true,
|
|
5462
5700
|
name: "analyzeImage",
|
|
5463
|
-
description: "Analyze an image
|
|
5701
|
+
description: "Analyze an image using a vision model. Provides static image analysis only, will not capture animations or video. Returns an objective description of what is visible \u2014 shapes, colors, layout, text, artifacts. Use for factual inventory of image contents, not for subjective design judgment - the vision model providing the analysis has no sense of design. You are the design expert - use the analysis tool for factual inventory, then apply your own expertise for quality and suitability assessments. Optionally provide specific questions about what you're looking for. Use a bulleted list to ask many questions at once. If you are analyzing a screenshot of the app preview, you can reuse the same screenshot URL multiple times to ask multiple questions.",
|
|
5464
5702
|
inputSchema: {
|
|
5465
5703
|
type: "object",
|
|
5466
5704
|
properties: {
|
|
5705
|
+
// Still `imageUrl` even though a path works: the builder renders this
|
|
5706
|
+
// tool's thumbnail from `input.imageUrl` and deploys separately, so
|
|
5707
|
+
// renaming the property would blank the view under version skew.
|
|
5467
5708
|
imageUrl: {
|
|
5468
5709
|
type: "string",
|
|
5469
|
-
description: "The image URL
|
|
5710
|
+
description: "The image to analyze: either a URL, or the path of an image file on disk (e.g. a reference screenshot the user uploaded, under src/.user-uploads/). Local files are hosted automatically; the URL comes back in the result and can be reused for follow-up questions or embedded in a spec."
|
|
5470
5711
|
},
|
|
5471
5712
|
prompt: {
|
|
5472
5713
|
type: "string",
|
|
@@ -5479,88 +5720,6 @@ var init_analyzeImage2 = __esm({
|
|
|
5479
5720
|
}
|
|
5480
5721
|
});
|
|
5481
5722
|
|
|
5482
|
-
// src/subagents/designExpert/tools/screenshot.ts
|
|
5483
|
-
var screenshot_exports = {};
|
|
5484
|
-
__export(screenshot_exports, {
|
|
5485
|
-
definition: () => definition5,
|
|
5486
|
-
execute: () => execute5
|
|
5487
|
-
});
|
|
5488
|
-
async function execute5(input, onLog, context) {
|
|
5489
|
-
const fullPage = input.fullPage === true;
|
|
5490
|
-
const shotKind = fullPage ? "full-page" : "viewport";
|
|
5491
|
-
if (input.instructions && context) {
|
|
5492
|
-
try {
|
|
5493
|
-
const task = input.path ? `Navigate to "${input.path}", then: ${input.instructions}. After completing these steps, take a ${shotKind} screenshot.` : `${input.instructions}. After completing these steps, take a ${shotKind} screenshot.`;
|
|
5494
|
-
const result = await runBrowserAutomation(task, context, {
|
|
5495
|
-
capture: fullPage ? "fullPage" : "viewport"
|
|
5496
|
-
});
|
|
5497
|
-
if (!result.screenshot) {
|
|
5498
|
-
return result.text;
|
|
5499
|
-
}
|
|
5500
|
-
return await streamScreenshotAnalysis({
|
|
5501
|
-
url: result.screenshot.url,
|
|
5502
|
-
prompt: input.prompt,
|
|
5503
|
-
styleMap: result.screenshot.styleMap,
|
|
5504
|
-
onLog,
|
|
5505
|
-
model: resolveModel("imageAnalysis", context?.models, context?.model)
|
|
5506
|
-
});
|
|
5507
|
-
} catch (err) {
|
|
5508
|
-
return `Error taking interactive screenshot: ${err.message}`;
|
|
5509
|
-
}
|
|
5510
|
-
}
|
|
5511
|
-
const release = await acquireBrowserLock();
|
|
5512
|
-
try {
|
|
5513
|
-
return await captureAndAnalyzeScreenshot({
|
|
5514
|
-
prompt: input.prompt,
|
|
5515
|
-
path: input.path,
|
|
5516
|
-
fullPage,
|
|
5517
|
-
onLog,
|
|
5518
|
-
model: resolveModel("imageAnalysis", context?.models, context?.model)
|
|
5519
|
-
});
|
|
5520
|
-
} catch (err) {
|
|
5521
|
-
return `Error taking screenshot: ${err.message}`;
|
|
5522
|
-
} finally {
|
|
5523
|
-
release();
|
|
5524
|
-
}
|
|
5525
|
-
}
|
|
5526
|
-
var definition5;
|
|
5527
|
-
var init_screenshot3 = __esm({
|
|
5528
|
-
"src/subagents/designExpert/tools/screenshot.ts"() {
|
|
5529
|
-
"use strict";
|
|
5530
|
-
init_screenshot();
|
|
5531
|
-
init_browserLock();
|
|
5532
|
-
init_browserAutomation();
|
|
5533
|
-
init_surfaces();
|
|
5534
|
-
definition5 = {
|
|
5535
|
-
clearable: true,
|
|
5536
|
-
name: "screenshot",
|
|
5537
|
-
description: "Capture a screenshot of the current app preview and get it back with visual analysis. Choose `fullPage`: `false` captures just the visible viewport (fast \u2014 use it to review a specific section the page is scrolled to), `true` captures the entire page top-to-bottom (slower \u2014 use it to review overall composition or a layout you can't see in one screen). 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.",
|
|
5538
|
-
inputSchema: {
|
|
5539
|
-
type: "object",
|
|
5540
|
-
properties: {
|
|
5541
|
-
fullPage: {
|
|
5542
|
-
type: "boolean",
|
|
5543
|
-
description: "true = full-height capture of the entire page; false = just the visible viewport. Pick based on whether you need the whole page or a specific section."
|
|
5544
|
-
},
|
|
5545
|
-
prompt: {
|
|
5546
|
-
type: "string",
|
|
5547
|
-
description: "Optional specific question about the screenshot. Use a bulleted list to ask many questions at once."
|
|
5548
|
-
},
|
|
5549
|
-
path: {
|
|
5550
|
-
type: "string",
|
|
5551
|
-
description: 'Navigate to this path before capturing (e.g. "/settings"). If omitted, screenshots the current page.'
|
|
5552
|
-
},
|
|
5553
|
-
instructions: {
|
|
5554
|
-
type: "string",
|
|
5555
|
-
description: "If the screenshot you need requires interaction first (dismissing a modal, clicking a tab, filling out a form, scrolling to a specific section, getting through a login/auth checkpoint), describe the steps to get there. A browser automation agent will follow these instructions, then capture per your `fullPage` choice \u2014 so with `fullPage: false` you can scroll to a section and capture just that viewport. It can bypass auth and get right to where it needs to be if you tell it to authenticate as a test user and give it the path/screen to start at. Never describe what names or values to use when applying the instructions - the browser automation agent must use its own values for it to work properly. If a specific auth role is required to access the content, be sure to note that - it can automatically assume it for the purpose of testing."
|
|
5556
|
-
}
|
|
5557
|
-
},
|
|
5558
|
-
required: ["fullPage"]
|
|
5559
|
-
}
|
|
5560
|
-
};
|
|
5561
|
-
}
|
|
5562
|
-
});
|
|
5563
|
-
|
|
5564
5723
|
// src/subagents/designExpert/tools/images/enhancePrompt.ts
|
|
5565
5724
|
async function enhanceImagePrompt(params) {
|
|
5566
5725
|
const {
|
|
@@ -5627,14 +5786,15 @@ var init_enhancePrompt = __esm({
|
|
|
5627
5786
|
async function generateImageAssets(opts) {
|
|
5628
5787
|
const {
|
|
5629
5788
|
prompts,
|
|
5630
|
-
sourceImages,
|
|
5631
5789
|
transparentBackground,
|
|
5632
5790
|
enhancePrompts,
|
|
5633
5791
|
onLog,
|
|
5792
|
+
apiConfig,
|
|
5634
5793
|
imageGenerationModel: genModel,
|
|
5635
5794
|
imageAnalysisModel,
|
|
5636
5795
|
imagePromptEnhancerModel
|
|
5637
5796
|
} = opts;
|
|
5797
|
+
const sourceImages = opts.sourceImages?.length ? await resolveImageRefs(opts.sourceImages, apiConfig) : void 0;
|
|
5638
5798
|
const width = opts.width || 2048;
|
|
5639
5799
|
const height = opts.height || 2048;
|
|
5640
5800
|
const config = { width, height };
|
|
@@ -5740,10 +5900,10 @@ async function generateImageAssets(opts) {
|
|
|
5740
5900
|
}
|
|
5741
5901
|
const analysis = await analyzeImage({
|
|
5742
5902
|
prompt: ANALYZE_PROMPT,
|
|
5743
|
-
|
|
5903
|
+
image: url,
|
|
5744
5904
|
onLog,
|
|
5745
5905
|
model: imageAnalysisModel
|
|
5746
|
-
});
|
|
5906
|
+
}).then((r) => r.analysis).catch((err) => `Could not review this image: ${err.message}`);
|
|
5747
5907
|
return {
|
|
5748
5908
|
url,
|
|
5749
5909
|
prompt: prompts[i],
|
|
@@ -5762,6 +5922,7 @@ var init_imageGenerator = __esm({
|
|
|
5762
5922
|
"use strict";
|
|
5763
5923
|
init_runMindstudioCli();
|
|
5764
5924
|
init_analyzeImage();
|
|
5925
|
+
init_uploadImage();
|
|
5765
5926
|
init_enhancePrompt();
|
|
5766
5927
|
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, any text present in the image, whether there are any issues (artifacts, distortions), and how it could be used in a layout for an app or website. Be concise and practical. Respond only with your analysis as Markdown (starting with the title "Asset Review") and absolutely no other text. Do not use emojis - use unicode if you need symbols.';
|
|
5767
5928
|
}
|
|
@@ -5770,10 +5931,10 @@ var init_imageGenerator = __esm({
|
|
|
5770
5931
|
// src/subagents/designExpert/tools/images/generateImages.ts
|
|
5771
5932
|
var generateImages_exports = {};
|
|
5772
5933
|
__export(generateImages_exports, {
|
|
5773
|
-
definition: () =>
|
|
5774
|
-
execute: () =>
|
|
5934
|
+
definition: () => definition5,
|
|
5935
|
+
execute: () => execute5
|
|
5775
5936
|
});
|
|
5776
|
-
async function
|
|
5937
|
+
async function execute5(input, onLog, context) {
|
|
5777
5938
|
return generateImageAssets({
|
|
5778
5939
|
prompts: input.prompts,
|
|
5779
5940
|
width: input.width,
|
|
@@ -5782,6 +5943,7 @@ async function execute6(input, onLog, context) {
|
|
|
5782
5943
|
sourceImages: input.referenceImage ? [input.referenceImage] : void 0,
|
|
5783
5944
|
enhancePrompts: true,
|
|
5784
5945
|
onLog,
|
|
5946
|
+
apiConfig: context?.apiConfig,
|
|
5785
5947
|
imageGenerationModel: resolveModel(
|
|
5786
5948
|
"imageGeneration",
|
|
5787
5949
|
context?.models,
|
|
@@ -5799,13 +5961,13 @@ async function execute6(input, onLog, context) {
|
|
|
5799
5961
|
)
|
|
5800
5962
|
});
|
|
5801
5963
|
}
|
|
5802
|
-
var
|
|
5964
|
+
var definition5;
|
|
5803
5965
|
var init_generateImages = __esm({
|
|
5804
5966
|
"src/subagents/designExpert/tools/images/generateImages.ts"() {
|
|
5805
5967
|
"use strict";
|
|
5806
5968
|
init_imageGenerator();
|
|
5807
5969
|
init_surfaces();
|
|
5808
|
-
|
|
5970
|
+
definition5 = {
|
|
5809
5971
|
clearable: false,
|
|
5810
5972
|
name: "generateImages",
|
|
5811
5973
|
description: "Generate images. 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.",
|
|
@@ -5821,7 +5983,7 @@ var init_generateImages = __esm({
|
|
|
5821
5983
|
},
|
|
5822
5984
|
referenceImage: {
|
|
5823
5985
|
type: "string",
|
|
5824
|
-
description: "Optional
|
|
5986
|
+
description: "Optional single reference image to guide the generation \u2014 for style, subject, character consistency, or composition. Either a URL or the path of an image file on disk (e.g. a user upload under src/.user-uploads/). Your prompt still describes the desired result; the reference conditions it. Applies to every prompt in the batch."
|
|
5825
5987
|
},
|
|
5826
5988
|
width: {
|
|
5827
5989
|
type: "number",
|
|
@@ -5845,10 +6007,10 @@ var init_generateImages = __esm({
|
|
|
5845
6007
|
// src/subagents/designExpert/tools/images/editImages.ts
|
|
5846
6008
|
var editImages_exports = {};
|
|
5847
6009
|
__export(editImages_exports, {
|
|
5848
|
-
definition: () =>
|
|
5849
|
-
execute: () =>
|
|
6010
|
+
definition: () => definition6,
|
|
6011
|
+
execute: () => execute6
|
|
5850
6012
|
});
|
|
5851
|
-
async function
|
|
6013
|
+
async function execute6(input, onLog, context) {
|
|
5852
6014
|
return generateImageAssets({
|
|
5853
6015
|
prompts: input.prompts,
|
|
5854
6016
|
sourceImages: input.sourceImages,
|
|
@@ -5857,6 +6019,7 @@ async function execute7(input, onLog, context) {
|
|
|
5857
6019
|
transparentBackground: input.transparentBackground,
|
|
5858
6020
|
enhancePrompts: false,
|
|
5859
6021
|
onLog,
|
|
6022
|
+
apiConfig: context?.apiConfig,
|
|
5860
6023
|
imageGenerationModel: resolveModel(
|
|
5861
6024
|
"imageGeneration",
|
|
5862
6025
|
context?.models,
|
|
@@ -5874,13 +6037,13 @@ async function execute7(input, onLog, context) {
|
|
|
5874
6037
|
)
|
|
5875
6038
|
});
|
|
5876
6039
|
}
|
|
5877
|
-
var
|
|
6040
|
+
var definition6;
|
|
5878
6041
|
var init_editImages = __esm({
|
|
5879
6042
|
"src/subagents/designExpert/tools/images/editImages.ts"() {
|
|
5880
6043
|
"use strict";
|
|
5881
6044
|
init_imageGenerator();
|
|
5882
6045
|
init_surfaces();
|
|
5883
|
-
|
|
6046
|
+
definition6 = {
|
|
5884
6047
|
clearable: false,
|
|
5885
6048
|
name: "editImages",
|
|
5886
6049
|
description: "Edit or transform existing images. 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 references into something new. Returns CDN URLs with analysis.",
|
|
@@ -5899,7 +6062,7 @@ var init_editImages = __esm({
|
|
|
5899
6062
|
items: {
|
|
5900
6063
|
type: "string"
|
|
5901
6064
|
},
|
|
5902
|
-
description: "One or more source/reference image
|
|
6065
|
+
description: "One or more source/reference images, each a URL or the path of an image file on disk (e.g. a user upload under src/.user-uploads/). These are used as the basis for the edit \u2014 the AI will use them as reference for style, subject, or composition."
|
|
5903
6066
|
},
|
|
5904
6067
|
width: {
|
|
5905
6068
|
type: "number",
|
|
@@ -5943,8 +6106,8 @@ var init_copyEditor = __esm({
|
|
|
5943
6106
|
init_surfaces();
|
|
5944
6107
|
BASE_PROMPT2 = readAsset("subagents/copyEditor", "prompt.md");
|
|
5945
6108
|
copyEditorTool = {
|
|
5946
|
-
clearable: false,
|
|
5947
6109
|
definition: {
|
|
6110
|
+
clearable: false,
|
|
5948
6111
|
name: "copyEditor",
|
|
5949
6112
|
description: "Hand it user-facing copy and it hands back a sharper version \u2014 better structured for its audience and free of the overused words, telltale constructions, and rhythms that make text read as AI-generated. Think of it as a design expert for words: it elevates how the copy communicates and strips the AI fingerprints, but it never invents facts or claims you didn't give it. Use it on anything users will read: in-app strings, empty states, errors, the Build Overview, deck copy, launch posts, Slack announcements. Readonly.",
|
|
5950
6113
|
inputSchema: {
|
|
@@ -5995,18 +6158,18 @@ var init_copyEditor = __esm({
|
|
|
5995
6158
|
// src/subagents/designExpert/tools/polishCopy.ts
|
|
5996
6159
|
var polishCopy_exports = {};
|
|
5997
6160
|
__export(polishCopy_exports, {
|
|
5998
|
-
definition: () =>
|
|
5999
|
-
execute: () =>
|
|
6161
|
+
definition: () => definition7,
|
|
6162
|
+
execute: () => execute7
|
|
6000
6163
|
});
|
|
6001
|
-
async function
|
|
6164
|
+
async function execute7(input, _onLog, context) {
|
|
6002
6165
|
return copyEditorTool.execute(input, context);
|
|
6003
6166
|
}
|
|
6004
|
-
var
|
|
6167
|
+
var definition7;
|
|
6005
6168
|
var init_polishCopy = __esm({
|
|
6006
6169
|
"src/subagents/designExpert/tools/polishCopy.ts"() {
|
|
6007
6170
|
"use strict";
|
|
6008
6171
|
init_copyEditor();
|
|
6009
|
-
|
|
6172
|
+
definition7 = {
|
|
6010
6173
|
clearable: false,
|
|
6011
6174
|
name: "polishCopy",
|
|
6012
6175
|
description: "Hand off any user-facing copy you've written \u2014 headlines, captions, labels, body text \u2014 and get back a sharper version: better built for its audience and free of the fingerprints that make writing read as AI. It elevates how the copy communicates without inventing facts or claims you didn't give it. Give it the text plus what it's for (where it appears, the audience).",
|
|
@@ -6043,16 +6206,19 @@ var init_tools4 = __esm({
|
|
|
6043
6206
|
init_scrapeWebUrl();
|
|
6044
6207
|
init_analyzeDesign();
|
|
6045
6208
|
init_analyzeImage2();
|
|
6046
|
-
init_screenshot3();
|
|
6047
6209
|
init_generateImages();
|
|
6048
6210
|
init_editImages();
|
|
6049
6211
|
init_polishCopy();
|
|
6212
|
+
init_screenshot2();
|
|
6050
6213
|
tools = {
|
|
6051
6214
|
searchGoogle: searchGoogle_exports,
|
|
6052
6215
|
scrapeWebUrl: scrapeWebUrl_exports,
|
|
6053
6216
|
analyzeDesign: analyzeDesign_exports,
|
|
6054
6217
|
analyzeImage: analyzeImage_exports,
|
|
6055
|
-
|
|
6218
|
+
// Same tool the main agent offers, imported rather than reimplemented — the
|
|
6219
|
+
// two used to be near-identical copies and had already drifted apart. Its core
|
|
6220
|
+
// already takes (input, onLog, context), which is this registry's convention.
|
|
6221
|
+
screenshot: { definition: screenshotDefinition, execute: executeScreenshot },
|
|
6056
6222
|
generateImages: generateImages_exports,
|
|
6057
6223
|
editImages: editImages_exports,
|
|
6058
6224
|
polishCopy: polishCopy_exports
|
|
@@ -6070,7 +6236,7 @@ async function initOrgContext(config) {
|
|
|
6070
6236
|
cached = await fetchRemyContext(config);
|
|
6071
6237
|
const orgDefaultModels2 = filterModelPicks(cached?.defaultModels);
|
|
6072
6238
|
setOrgDefaultModels(orgDefaultModels2);
|
|
6073
|
-
|
|
6239
|
+
log10.debug("org context loaded", {
|
|
6074
6240
|
delegatedAvailable: cached?.auth?.delegatedAvailable ?? false,
|
|
6075
6241
|
requireDelegatedOnly: cached?.auth?.requireDelegatedOnly ?? false,
|
|
6076
6242
|
hasOrgName: !!cached?.org?.name,
|
|
@@ -6079,7 +6245,7 @@ async function initOrgContext(config) {
|
|
|
6079
6245
|
} catch (err) {
|
|
6080
6246
|
cached = null;
|
|
6081
6247
|
setOrgDefaultModels({});
|
|
6082
|
-
|
|
6248
|
+
log10.debug("org context init failed", { error: err.message });
|
|
6083
6249
|
}
|
|
6084
6250
|
}
|
|
6085
6251
|
function getOrgContext() {
|
|
@@ -6115,20 +6281,20 @@ function renderOrgContextBlock() {
|
|
|
6115
6281
|
lines.push("</org_context>");
|
|
6116
6282
|
return lines.join("\n");
|
|
6117
6283
|
}
|
|
6118
|
-
var
|
|
6284
|
+
var log10, cached;
|
|
6119
6285
|
var init_orgContext = __esm({
|
|
6120
6286
|
"src/orgContext.ts"() {
|
|
6121
6287
|
"use strict";
|
|
6122
6288
|
init_api();
|
|
6123
6289
|
init_surfaces();
|
|
6124
6290
|
init_logger();
|
|
6125
|
-
|
|
6291
|
+
log10 = createLogger("orgContext");
|
|
6126
6292
|
cached = null;
|
|
6127
6293
|
}
|
|
6128
6294
|
});
|
|
6129
6295
|
|
|
6130
6296
|
// src/subagents/designExpert/data/sampleCache.ts
|
|
6131
|
-
import
|
|
6297
|
+
import fs17 from "fs";
|
|
6132
6298
|
function generateIndices(poolSize, sampleSize) {
|
|
6133
6299
|
const n = Math.min(sampleSize, poolSize);
|
|
6134
6300
|
const indices = Array.from({ length: poolSize }, (_, i) => i);
|
|
@@ -6140,14 +6306,14 @@ function generateIndices(poolSize, sampleSize) {
|
|
|
6140
6306
|
}
|
|
6141
6307
|
function load() {
|
|
6142
6308
|
try {
|
|
6143
|
-
return JSON.parse(
|
|
6309
|
+
return JSON.parse(fs17.readFileSync(SAMPLE_FILE, "utf-8"));
|
|
6144
6310
|
} catch {
|
|
6145
6311
|
return null;
|
|
6146
6312
|
}
|
|
6147
6313
|
}
|
|
6148
6314
|
function save(indices) {
|
|
6149
6315
|
try {
|
|
6150
|
-
|
|
6316
|
+
fs17.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
|
|
6151
6317
|
} catch {
|
|
6152
6318
|
}
|
|
6153
6319
|
}
|
|
@@ -6497,8 +6663,8 @@ Visual design expert. Describe the situation and what you need \u2014 the agent
|
|
|
6497
6663
|
editFileTool.definition
|
|
6498
6664
|
];
|
|
6499
6665
|
designExpertTool = {
|
|
6500
|
-
clearable: false,
|
|
6501
6666
|
definition: {
|
|
6667
|
+
clearable: false,
|
|
6502
6668
|
name: "visualDesignExpert",
|
|
6503
6669
|
description: DESCRIPTION,
|
|
6504
6670
|
inputSchema: {
|
|
@@ -6593,23 +6759,23 @@ var init_tools5 = __esm({
|
|
|
6593
6759
|
});
|
|
6594
6760
|
|
|
6595
6761
|
// src/subagents/productVision/executor.ts
|
|
6596
|
-
import
|
|
6597
|
-
import
|
|
6598
|
-
function
|
|
6599
|
-
return
|
|
6762
|
+
import fs18 from "fs";
|
|
6763
|
+
import path10 from "path";
|
|
6764
|
+
function resolve2(filePath) {
|
|
6765
|
+
return path10.join(ROADMAP_DIR, filePath);
|
|
6600
6766
|
}
|
|
6601
6767
|
async function executeVisionTool(name, input, context) {
|
|
6602
6768
|
switch (name) {
|
|
6603
6769
|
case "writeFile": {
|
|
6604
|
-
const filePath =
|
|
6770
|
+
const filePath = resolve2(input.path);
|
|
6605
6771
|
try {
|
|
6606
|
-
|
|
6772
|
+
fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
6607
6773
|
let oldContent = null;
|
|
6608
6774
|
try {
|
|
6609
|
-
oldContent =
|
|
6775
|
+
oldContent = fs18.readFileSync(filePath, "utf-8");
|
|
6610
6776
|
} catch {
|
|
6611
6777
|
}
|
|
6612
|
-
|
|
6778
|
+
fs18.writeFileSync(filePath, input.content, "utf-8");
|
|
6613
6779
|
const lineCount = input.content.split("\n").length;
|
|
6614
6780
|
const label = oldContent !== null ? "Wrote" : "Created";
|
|
6615
6781
|
return `${label} ${filePath} (${lineCount} lines)
|
|
@@ -6619,13 +6785,13 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
|
|
|
6619
6785
|
}
|
|
6620
6786
|
}
|
|
6621
6787
|
case "deleteFile": {
|
|
6622
|
-
const filePath =
|
|
6788
|
+
const filePath = resolve2(input.path);
|
|
6623
6789
|
try {
|
|
6624
|
-
if (!
|
|
6790
|
+
if (!fs18.existsSync(filePath)) {
|
|
6625
6791
|
return `Error: ${filePath} does not exist`;
|
|
6626
6792
|
}
|
|
6627
|
-
const oldContent =
|
|
6628
|
-
|
|
6793
|
+
const oldContent = fs18.readFileSync(filePath, "utf-8");
|
|
6794
|
+
fs18.unlinkSync(filePath);
|
|
6629
6795
|
return `Deleted ${filePath}
|
|
6630
6796
|
${unifiedDiff(filePath, oldContent, "")}`;
|
|
6631
6797
|
} catch (err) {
|
|
@@ -6636,11 +6802,11 @@ ${unifiedDiff(filePath, oldContent, "")}`;
|
|
|
6636
6802
|
if (!context) {
|
|
6637
6803
|
return "Error: writePitchDeck requires execution context for design expert delegation";
|
|
6638
6804
|
}
|
|
6639
|
-
const filePath =
|
|
6805
|
+
const filePath = resolve2("pitch.html");
|
|
6640
6806
|
try {
|
|
6641
|
-
|
|
6642
|
-
const exists =
|
|
6643
|
-
const before = exists ?
|
|
6807
|
+
fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
6808
|
+
const exists = fs18.existsSync(filePath);
|
|
6809
|
+
const before = exists ? fs18.statSync(filePath).mtimeMs : null;
|
|
6644
6810
|
const delivery = exists ? `### Your deliverable
|
|
6645
6811
|
The pitch deck already exists at \`${filePath}\`. Read it, then update it for the new <pitch_content>, keeping the presentation scaffolding intact \u2014 change only what needs to change.
|
|
6646
6812
|
|
|
@@ -6670,11 +6836,11 @@ Maintain the bones of the presentation scaffolding. Always keep the progress bar
|
|
|
6670
6836
|
${delivery}`;
|
|
6671
6837
|
const result = await runDesignExpertRender({ task }, context);
|
|
6672
6838
|
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
6673
|
-
if (!
|
|
6839
|
+
if (!fs18.existsSync(filePath)) {
|
|
6674
6840
|
return `Error: the design expert did not write ${filePath}. Its reply was:
|
|
6675
6841
|
${result.text}`;
|
|
6676
6842
|
}
|
|
6677
|
-
if (before !== null &&
|
|
6843
|
+
if (before !== null && fs18.statSync(filePath).mtimeMs === before) {
|
|
6678
6844
|
return `Error: the pitch deck at ${filePath} was not modified. The design expert's reply was:
|
|
6679
6845
|
${result.text}`;
|
|
6680
6846
|
}
|
|
@@ -6740,8 +6906,8 @@ var init_productVision = __esm({
|
|
|
6740
6906
|
init_history();
|
|
6741
6907
|
init_surfaces();
|
|
6742
6908
|
productVisionTool = {
|
|
6743
|
-
clearable: false,
|
|
6744
6909
|
definition: {
|
|
6910
|
+
clearable: false,
|
|
6745
6911
|
name: "productVision",
|
|
6746
6912
|
description: "Owns the product roadmap. Reads spec and roadmap files automatically. Creates, updates, and deletes roadmap items in src/roadmap/. Describe the situation and what needs to happen.",
|
|
6747
6913
|
inputSchema: {
|
|
@@ -6872,8 +7038,8 @@ var init_codeSanityCheck = __esm({
|
|
|
6872
7038
|
init_surfaces();
|
|
6873
7039
|
BASE_PROMPT4 = readAsset("subagents/codeSanityCheck", "prompt.md");
|
|
6874
7040
|
codeSanityCheckTool = {
|
|
6875
|
-
clearable: false,
|
|
6876
7041
|
definition: {
|
|
7042
|
+
clearable: false,
|
|
6877
7043
|
name: "codeSanityCheck",
|
|
6878
7044
|
description: 'Quick sanity check on an approach before building. Reviews architecture, package choices, and flags potential issues. Usually responds with "looks good." Occasionally catches something important. Readonly \u2014 can search the web and read code but cannot modify anything.',
|
|
6879
7045
|
inputSchema: {
|
|
@@ -6977,9 +7143,9 @@ ${readAsset(
|
|
|
6977
7143
|
)}
|
|
6978
7144
|
</mindstudio_flavored_markdown_spec_docs>`;
|
|
6979
7145
|
specSyncTool = {
|
|
6980
|
-
clearable: false,
|
|
6981
7146
|
backgroundOnly: true,
|
|
6982
7147
|
definition: {
|
|
7148
|
+
clearable: false,
|
|
6983
7149
|
name: "specSync",
|
|
6984
7150
|
description: "Reconcile the spec to bring it in line with code changes you have made. Provide a brief, bulleted list of what changed and why; it finds the affected spec sections and updates them to match. Always runs in the background \u2014 it returns immediately and reports back when done.",
|
|
6985
7151
|
inputSchema: {
|
|
@@ -7054,8 +7220,8 @@ var init_scrapeWebUrl2 = __esm({
|
|
|
7054
7220
|
init_runMindstudioCli();
|
|
7055
7221
|
init_runCli();
|
|
7056
7222
|
scrapeWebUrlTool = {
|
|
7057
|
-
clearable: false,
|
|
7058
7223
|
definition: {
|
|
7224
|
+
clearable: false,
|
|
7059
7225
|
name: "scrapeWebUrl",
|
|
7060
7226
|
description: "Scrape the content of a web page. Returns the HTML of the page as markdown text. Optionally capture a screenshot if you need see the visual design. Use this when you need to fetch or analyze content from a website",
|
|
7061
7227
|
inputSchema: {
|
|
@@ -7100,7 +7266,7 @@ var init_scrapeWebUrl2 = __esm({
|
|
|
7100
7266
|
});
|
|
7101
7267
|
|
|
7102
7268
|
// src/tools/spec/writeBuildOverview.ts
|
|
7103
|
-
import
|
|
7269
|
+
import fs19 from "fs";
|
|
7104
7270
|
function initialDelivery() {
|
|
7105
7271
|
return `### Your deliverable
|
|
7106
7272
|
Write the complete Build Overview to \`${OVERVIEW_FILE}\`. The file does not exist yet \u2014 start from this scaffold, which carries the technical hygiene for the iframed render context (keep the viewport meta and the reset). Layout, sections, type, and styling are yours to compose.
|
|
@@ -7162,8 +7328,8 @@ The copy in <overview_copy> is final \u2014 it was authored and edited before it
|
|
|
7162
7328
|
</body>
|
|
7163
7329
|
</html>`;
|
|
7164
7330
|
buildOverviewTool = {
|
|
7165
|
-
clearable: false,
|
|
7166
7331
|
definition: {
|
|
7332
|
+
clearable: false,
|
|
7167
7333
|
name: "writeBuildOverview",
|
|
7168
7334
|
description: "Generate or refresh the Build Overview \u2014 the project's home page in the Spec tab: a single-page, plain-language reference of everything the app actually contains, including the parts the user can't see (data stores, backend operations, access and roles, background jobs, seeded scenarios, the design system). You author the full copy: read the manifest and spec and state, plainly and exactly, what genuinely exists \u2014 real names and accurate counts \u2014 in calm, declarative, present-tense outcome language, with no persuasion or hype. Describe only what exists. Pass the complete copy as `content`; the design expert lays it out and skins it to the app's brand using your copy verbatim \u2014 it typesets your words, it does not rewrite them, so polish the copy before you pass it. Generate it at the end of a build and refresh it after meaningful work.",
|
|
7169
7335
|
inputSchema: {
|
|
@@ -7185,7 +7351,7 @@ The copy in <overview_copy> is final \u2014 it was authored and edited before it
|
|
|
7185
7351
|
if (!content) {
|
|
7186
7352
|
return "Error: writeBuildOverview requires non-empty `content` (the overview copy).";
|
|
7187
7353
|
}
|
|
7188
|
-
const exists =
|
|
7354
|
+
const exists = fs19.existsSync(OVERVIEW_FILE);
|
|
7189
7355
|
const task = `<overview_copy>${content}</overview_copy>
|
|
7190
7356
|
|
|
7191
7357
|
${DESIGN_BRIEF}
|
|
@@ -7203,7 +7369,7 @@ ${exists ? refreshDelivery() : initialDelivery()}`;
|
|
|
7203
7369
|
}
|
|
7204
7370
|
const result = await runDesignExpertRender({ task }, context);
|
|
7205
7371
|
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
7206
|
-
if (!
|
|
7372
|
+
if (!fs19.existsSync(OVERVIEW_FILE)) {
|
|
7207
7373
|
return `Error: the design expert did not write ${OVERVIEW_FILE}. Its reply was:
|
|
7208
7374
|
${result.text}`;
|
|
7209
7375
|
}
|
|
@@ -7233,7 +7399,7 @@ function executeTool(name, input, context) {
|
|
|
7233
7399
|
}
|
|
7234
7400
|
return tool.execute(input, context);
|
|
7235
7401
|
}
|
|
7236
|
-
var ALL_TOOLS,
|
|
7402
|
+
var ALL_TOOLS, SUBAGENT_TOOL_NAMES;
|
|
7237
7403
|
var init_tools8 = __esm({
|
|
7238
7404
|
"src/tools/index.ts"() {
|
|
7239
7405
|
"use strict";
|
|
@@ -7251,6 +7417,7 @@ var init_tools8 = __esm({
|
|
|
7251
7417
|
init_searchGoogle();
|
|
7252
7418
|
init_setProjectMetadata();
|
|
7253
7419
|
init_compactConversation();
|
|
7420
|
+
init_loadSkill();
|
|
7254
7421
|
init_readFile();
|
|
7255
7422
|
init_writeFile();
|
|
7256
7423
|
init_editFile();
|
|
@@ -7315,11 +7482,11 @@ var init_tools8 = __esm({
|
|
|
7315
7482
|
browserAutomationTool,
|
|
7316
7483
|
// LSP
|
|
7317
7484
|
lspDiagnosticsTool,
|
|
7318
|
-
restartProcessTool
|
|
7485
|
+
restartProcessTool,
|
|
7486
|
+
// Appended rather than grouped: position is part of the cache prefix, so a
|
|
7487
|
+
// new tool goes at the end to leave every existing session's prefix intact.
|
|
7488
|
+
loadSkillTool
|
|
7319
7489
|
];
|
|
7320
|
-
CLEARABLE_TOOLS = new Set(
|
|
7321
|
-
ALL_TOOLS.filter((t) => t.clearable).map((t) => t.definition.name)
|
|
7322
|
-
);
|
|
7323
7490
|
SUBAGENT_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
7324
7491
|
"visualDesignExpert",
|
|
7325
7492
|
"productVision",
|
|
@@ -7542,35 +7709,35 @@ var init_errors = __esm({
|
|
|
7542
7709
|
});
|
|
7543
7710
|
|
|
7544
7711
|
// src/brandExtraction/index.ts
|
|
7545
|
-
import
|
|
7546
|
-
import
|
|
7712
|
+
import fs20 from "fs";
|
|
7713
|
+
import path11 from "path";
|
|
7547
7714
|
import { createHash } from "crypto";
|
|
7548
7715
|
async function runExtraction(apiConfig, model) {
|
|
7549
7716
|
const inputHash = computeInputHash();
|
|
7550
7717
|
const cached3 = readCache();
|
|
7551
7718
|
if (cached3 && cached3.inputHash === inputHash) {
|
|
7552
|
-
|
|
7719
|
+
log11.debug("Brand inputs unchanged \u2014 skipping extraction", { inputHash });
|
|
7553
7720
|
return null;
|
|
7554
7721
|
}
|
|
7555
|
-
|
|
7722
|
+
log11.info("Extracting brand", { inputHash });
|
|
7556
7723
|
const brand = await extractBrand(apiConfig, model);
|
|
7557
7724
|
if (!brand) {
|
|
7558
|
-
|
|
7725
|
+
log11.warn("Brand extraction failed \u2014 leaving cache untouched");
|
|
7559
7726
|
return null;
|
|
7560
7727
|
}
|
|
7561
7728
|
persistBrand(brand, inputHash);
|
|
7562
|
-
|
|
7729
|
+
log11.info("Brand persisted", { inputHash });
|
|
7563
7730
|
return brand;
|
|
7564
7731
|
}
|
|
7565
7732
|
function isDedicatedBrandFile(filePath) {
|
|
7566
|
-
if (filePath.split(
|
|
7733
|
+
if (filePath.split(path11.sep).includes("@brand")) {
|
|
7567
7734
|
return true;
|
|
7568
7735
|
}
|
|
7569
|
-
const { type } =
|
|
7736
|
+
const { type } = parseFrontmatter3(filePath);
|
|
7570
7737
|
return type.startsWith("design/color") || type.startsWith("design/typography");
|
|
7571
7738
|
}
|
|
7572
7739
|
function isBrandRelevant(filePath) {
|
|
7573
|
-
return filePath ===
|
|
7740
|
+
return filePath === path11.join("src", "app.md") || isDedicatedBrandFile(filePath);
|
|
7574
7741
|
}
|
|
7575
7742
|
function computeInputHash() {
|
|
7576
7743
|
const entries = [];
|
|
@@ -7592,7 +7759,7 @@ function sha256(input) {
|
|
|
7592
7759
|
}
|
|
7593
7760
|
function readSafe(filePath) {
|
|
7594
7761
|
try {
|
|
7595
|
-
return
|
|
7762
|
+
return fs20.readFileSync(filePath, "utf-8");
|
|
7596
7763
|
} catch {
|
|
7597
7764
|
return "";
|
|
7598
7765
|
}
|
|
@@ -7618,9 +7785,9 @@ function readBrandManifest() {
|
|
|
7618
7785
|
function walkMdFiles2(dir) {
|
|
7619
7786
|
const results = [];
|
|
7620
7787
|
try {
|
|
7621
|
-
const entries =
|
|
7788
|
+
const entries = fs20.readdirSync(dir, { withFileTypes: true });
|
|
7622
7789
|
for (const entry of entries) {
|
|
7623
|
-
const full =
|
|
7790
|
+
const full = path11.join(dir, entry.name);
|
|
7624
7791
|
if (entry.isDirectory()) {
|
|
7625
7792
|
results.push(...walkMdFiles2(full));
|
|
7626
7793
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -7631,9 +7798,9 @@ function walkMdFiles2(dir) {
|
|
|
7631
7798
|
}
|
|
7632
7799
|
return results.sort();
|
|
7633
7800
|
}
|
|
7634
|
-
function
|
|
7801
|
+
function parseFrontmatter3(filePath) {
|
|
7635
7802
|
try {
|
|
7636
|
-
const content =
|
|
7803
|
+
const content = fs20.readFileSync(filePath, "utf-8");
|
|
7637
7804
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
7638
7805
|
if (!match) {
|
|
7639
7806
|
return { type: "" };
|
|
@@ -7648,7 +7815,7 @@ function parseFrontmatter2(filePath) {
|
|
|
7648
7815
|
async function extractBrand(apiConfig, model) {
|
|
7649
7816
|
const corpus = buildCorpus();
|
|
7650
7817
|
if (!corpus.trim()) {
|
|
7651
|
-
|
|
7818
|
+
log11.debug("No spec corpus \u2014 emitting empty brand");
|
|
7652
7819
|
return { version: 1 };
|
|
7653
7820
|
}
|
|
7654
7821
|
let responseText = "";
|
|
@@ -7679,17 +7846,17 @@ async function extractBrand(apiConfig, model) {
|
|
|
7679
7846
|
toolNames: []
|
|
7680
7847
|
});
|
|
7681
7848
|
} else if (event.type === "error") {
|
|
7682
|
-
|
|
7849
|
+
log11.error("Brand extraction stream error", { error: event.error });
|
|
7683
7850
|
return null;
|
|
7684
7851
|
}
|
|
7685
7852
|
}
|
|
7686
7853
|
} catch (err) {
|
|
7687
|
-
|
|
7854
|
+
log11.error("Brand extraction threw", { error: err?.message });
|
|
7688
7855
|
return null;
|
|
7689
7856
|
}
|
|
7690
7857
|
const parsed = parseJsonResponse(responseText);
|
|
7691
7858
|
if (!parsed) {
|
|
7692
|
-
|
|
7859
|
+
log11.warn("Brand extraction returned unparseable JSON", {
|
|
7693
7860
|
preview: responseText.slice(0, 200)
|
|
7694
7861
|
});
|
|
7695
7862
|
return null;
|
|
@@ -7841,14 +8008,14 @@ function pickFont(raw) {
|
|
|
7841
8008
|
}
|
|
7842
8009
|
function persistBrand(brand, inputHash) {
|
|
7843
8010
|
const tmp = `${BRAND_FILE}.tmp`;
|
|
7844
|
-
|
|
7845
|
-
|
|
8011
|
+
fs20.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
|
|
8012
|
+
fs20.renameSync(tmp, BRAND_FILE);
|
|
7846
8013
|
const cache = { inputHash, generatedAt: Date.now() };
|
|
7847
|
-
|
|
8014
|
+
fs20.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
|
|
7848
8015
|
}
|
|
7849
8016
|
function readCache() {
|
|
7850
8017
|
try {
|
|
7851
|
-
const raw =
|
|
8018
|
+
const raw = fs20.readFileSync(CACHE_FILE, "utf-8");
|
|
7852
8019
|
const parsed = JSON.parse(raw);
|
|
7853
8020
|
if (parsed && typeof parsed.inputHash === "string" && typeof parsed.generatedAt === "number") {
|
|
7854
8021
|
return parsed;
|
|
@@ -7858,7 +8025,7 @@ function readCache() {
|
|
|
7858
8025
|
return null;
|
|
7859
8026
|
}
|
|
7860
8027
|
}
|
|
7861
|
-
var
|
|
8028
|
+
var log11, EXTRACT_PROMPT, BRAND_FILE, CACHE_FILE, HEAD_SLICE_CHARS, BRAND_CORPUS_CHAR_LIMIT;
|
|
7862
8029
|
var init_brandExtraction = __esm({
|
|
7863
8030
|
"src/brandExtraction/index.ts"() {
|
|
7864
8031
|
"use strict";
|
|
@@ -7866,7 +8033,7 @@ var init_brandExtraction = __esm({
|
|
|
7866
8033
|
init_assets();
|
|
7867
8034
|
init_logger();
|
|
7868
8035
|
init_usageLedger();
|
|
7869
|
-
|
|
8036
|
+
log11 = createLogger("brandExtraction");
|
|
7870
8037
|
EXTRACT_PROMPT = readAsset("brandExtraction", "extract.md");
|
|
7871
8038
|
BRAND_FILE = ".remy-brand.json";
|
|
7872
8039
|
CACHE_FILE = ".remy-brand.cache.json";
|
|
@@ -7883,7 +8050,7 @@ function triggerBrandExtraction(apiConfig, model) {
|
|
|
7883
8050
|
}
|
|
7884
8051
|
inflight = true;
|
|
7885
8052
|
void runExtraction(apiConfig, model).catch((err) => {
|
|
7886
|
-
|
|
8053
|
+
log12.error("Brand extraction failed", { error: err?.message });
|
|
7887
8054
|
}).finally(() => {
|
|
7888
8055
|
inflight = false;
|
|
7889
8056
|
if (dirty) {
|
|
@@ -7892,13 +8059,13 @@ function triggerBrandExtraction(apiConfig, model) {
|
|
|
7892
8059
|
}
|
|
7893
8060
|
});
|
|
7894
8061
|
}
|
|
7895
|
-
var
|
|
8062
|
+
var log12, inflight, dirty;
|
|
7896
8063
|
var init_trigger2 = __esm({
|
|
7897
8064
|
"src/brandExtraction/trigger.ts"() {
|
|
7898
8065
|
"use strict";
|
|
7899
8066
|
init_brandExtraction();
|
|
7900
8067
|
init_logger();
|
|
7901
|
-
|
|
8068
|
+
log12 = createLogger("brandExtraction:trigger");
|
|
7902
8069
|
inflight = false;
|
|
7903
8070
|
dirty = false;
|
|
7904
8071
|
}
|
|
@@ -7941,12 +8108,11 @@ async function runTurn(params) {
|
|
|
7941
8108
|
onBackgroundComplete
|
|
7942
8109
|
} = params;
|
|
7943
8110
|
const tools2 = getToolDefinitions(onboardingState);
|
|
7944
|
-
const excludeToolsFromClearing = tools2.filter((t) => !CLEARABLE_TOOLS.has(t.name)).map((t) => t.name);
|
|
7945
8111
|
const buildModelOverride = buildModel ? filterModelPicks({ parent: buildModel }).parent : void 0;
|
|
7946
8112
|
const baseline = resolveModel("parent", state.models, model);
|
|
7947
8113
|
const parentModel = buildModelOverride ?? baseline;
|
|
7948
8114
|
const modelOverride = buildModelOverride && buildModelOverride !== baseline ? { from: baseline } : void 0;
|
|
7949
|
-
|
|
8115
|
+
log13.info("Turn started", {
|
|
7950
8116
|
requestId,
|
|
7951
8117
|
model,
|
|
7952
8118
|
buildModel: buildModelOverride,
|
|
@@ -8117,7 +8283,6 @@ async function runTurn(params) {
|
|
|
8117
8283
|
system,
|
|
8118
8284
|
messages: cleanMessagesForApi(state.messages),
|
|
8119
8285
|
tools: tools2,
|
|
8120
|
-
excludeToolsFromClearing,
|
|
8121
8286
|
signal
|
|
8122
8287
|
},
|
|
8123
8288
|
{
|
|
@@ -8209,7 +8374,7 @@ async function runTurn(params) {
|
|
|
8209
8374
|
const acc = toolInputAccumulators.get(event.id);
|
|
8210
8375
|
const wasStreamed = acc?.started ?? false;
|
|
8211
8376
|
const isInputStreaming = !!tool?.streaming?.partialInput;
|
|
8212
|
-
|
|
8377
|
+
log13.info("Tool received", {
|
|
8213
8378
|
requestId,
|
|
8214
8379
|
toolCallId: event.id,
|
|
8215
8380
|
name: event.name
|
|
@@ -8332,7 +8497,7 @@ async function runTurn(params) {
|
|
|
8332
8497
|
});
|
|
8333
8498
|
return;
|
|
8334
8499
|
}
|
|
8335
|
-
|
|
8500
|
+
log13.info("Tools executing", {
|
|
8336
8501
|
requestId,
|
|
8337
8502
|
count: toolCalls.length,
|
|
8338
8503
|
tools: toolCalls.map((tc) => tc.name)
|
|
@@ -8379,7 +8544,7 @@ async function runTurn(params) {
|
|
|
8379
8544
|
let result;
|
|
8380
8545
|
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
8381
8546
|
saveSession(state);
|
|
8382
|
-
|
|
8547
|
+
log13.info("Waiting for external tool result", {
|
|
8383
8548
|
requestId,
|
|
8384
8549
|
toolCallId: tc.id,
|
|
8385
8550
|
name: tc.name
|
|
@@ -8447,7 +8612,7 @@ async function runTurn(params) {
|
|
|
8447
8612
|
if (!isBackgroundCall(tc)) {
|
|
8448
8613
|
toolRegistry?.unregister(tc.id);
|
|
8449
8614
|
}
|
|
8450
|
-
|
|
8615
|
+
log13.info("Tool completed", {
|
|
8451
8616
|
requestId,
|
|
8452
8617
|
toolCallId: tc.id,
|
|
8453
8618
|
name: tc.name,
|
|
@@ -8506,7 +8671,7 @@ async function runTurn(params) {
|
|
|
8506
8671
|
}
|
|
8507
8672
|
}
|
|
8508
8673
|
}
|
|
8509
|
-
var
|
|
8674
|
+
var log13, BRAND_TRIGGERING_TOOLS, EXTERNAL_TOOLS, USER_BLOCKING_EXTERNAL_TOOLS;
|
|
8510
8675
|
var init_agent = __esm({
|
|
8511
8676
|
"src/agent.ts"() {
|
|
8512
8677
|
"use strict";
|
|
@@ -8519,13 +8684,12 @@ var init_agent = __esm({
|
|
|
8519
8684
|
init_statusWatcher();
|
|
8520
8685
|
init_errors();
|
|
8521
8686
|
init_cleanMessages();
|
|
8522
|
-
init_tools8();
|
|
8523
8687
|
init_sentinel();
|
|
8524
8688
|
init_trigger2();
|
|
8525
8689
|
init_surfaces();
|
|
8526
8690
|
init_toolRegistry();
|
|
8527
8691
|
init_toolResultCap();
|
|
8528
|
-
|
|
8692
|
+
log13 = createLogger("agent");
|
|
8529
8693
|
BRAND_TRIGGERING_TOOLS = /* @__PURE__ */ new Set(["writeSpec", "editSpec"]);
|
|
8530
8694
|
EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
|
|
8531
8695
|
"promptUser",
|
|
@@ -8547,8 +8711,8 @@ var init_agent = __esm({
|
|
|
8547
8711
|
});
|
|
8548
8712
|
|
|
8549
8713
|
// src/prompt/static/projectContext.ts
|
|
8550
|
-
import
|
|
8551
|
-
import
|
|
8714
|
+
import fs21 from "fs";
|
|
8715
|
+
import path12 from "path";
|
|
8552
8716
|
function loadProjectRoot() {
|
|
8553
8717
|
return `
|
|
8554
8718
|
## Project Root
|
|
@@ -8558,7 +8722,7 @@ File paths are relative to this directory. Every tool operates here and bash com
|
|
|
8558
8722
|
}
|
|
8559
8723
|
function loadProjectManifest() {
|
|
8560
8724
|
try {
|
|
8561
|
-
const manifest =
|
|
8725
|
+
const manifest = fs21.readFileSync("mindstudio.json", "utf-8");
|
|
8562
8726
|
return `
|
|
8563
8727
|
## Project Manifest (mindstudio.json)
|
|
8564
8728
|
\`\`\`json
|
|
@@ -8576,7 +8740,7 @@ function loadSpecFileMetadata() {
|
|
|
8576
8740
|
}
|
|
8577
8741
|
const entries = [];
|
|
8578
8742
|
for (const filePath of files) {
|
|
8579
|
-
const { name, description, type } =
|
|
8743
|
+
const { name, description, type } = parseFrontmatter4(filePath);
|
|
8580
8744
|
let line = `- ${filePath}`;
|
|
8581
8745
|
if (name) {
|
|
8582
8746
|
line += ` \u2014 "${name}"`;
|
|
@@ -8599,9 +8763,9 @@ ${entries.join("\n")}`;
|
|
|
8599
8763
|
function walkMdFiles3(dir) {
|
|
8600
8764
|
const results = [];
|
|
8601
8765
|
try {
|
|
8602
|
-
const entries =
|
|
8766
|
+
const entries = fs21.readdirSync(dir, { withFileTypes: true });
|
|
8603
8767
|
for (const entry of entries) {
|
|
8604
|
-
const full =
|
|
8768
|
+
const full = path12.join(dir, entry.name);
|
|
8605
8769
|
if (entry.isDirectory()) {
|
|
8606
8770
|
results.push(...walkMdFiles3(full));
|
|
8607
8771
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -8612,9 +8776,9 @@ function walkMdFiles3(dir) {
|
|
|
8612
8776
|
}
|
|
8613
8777
|
return results.sort();
|
|
8614
8778
|
}
|
|
8615
|
-
function
|
|
8779
|
+
function parseFrontmatter4(filePath) {
|
|
8616
8780
|
try {
|
|
8617
|
-
const content =
|
|
8781
|
+
const content = fs21.readFileSync(filePath, "utf-8");
|
|
8618
8782
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
8619
8783
|
if (!match) {
|
|
8620
8784
|
return { name: "", description: "", type: "" };
|
|
@@ -8630,7 +8794,7 @@ function parseFrontmatter3(filePath) {
|
|
|
8630
8794
|
}
|
|
8631
8795
|
function loadPlanStatus(onboardingState) {
|
|
8632
8796
|
try {
|
|
8633
|
-
const content =
|
|
8797
|
+
const content = fs21.readFileSync(".remy-plan.md", "utf-8");
|
|
8634
8798
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
8635
8799
|
const status = match?.[1]?.match(/^status:\s*(.+)$/m)?.[1]?.trim();
|
|
8636
8800
|
if (status === "pending" && onboardingState === "intake") {
|
|
@@ -8658,7 +8822,7 @@ The user has approved your implementation plan in .remy-plan.md. You may referen
|
|
|
8658
8822
|
}
|
|
8659
8823
|
function loadProjectFileListing() {
|
|
8660
8824
|
try {
|
|
8661
|
-
const entries =
|
|
8825
|
+
const entries = fs21.readdirSync(".", { withFileTypes: true });
|
|
8662
8826
|
const listing = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules").sort((a, b) => {
|
|
8663
8827
|
if (a.isDirectory() && !b.isDirectory()) {
|
|
8664
8828
|
return -1;
|
|
@@ -8738,18 +8902,6 @@ Current date: ${now}
|
|
|
8738
8902
|
{{compiled/design.md}}
|
|
8739
8903
|
</design>
|
|
8740
8904
|
|
|
8741
|
-
<building_agent_interfaces>
|
|
8742
|
-
{{compiled/agent-interfaces.md}}
|
|
8743
|
-
</building_agent_interfaces>
|
|
8744
|
-
|
|
8745
|
-
<building_mcp_interfaces>
|
|
8746
|
-
{{compiled/mcp-interfaces.md}}
|
|
8747
|
-
</building_mcp_interfaces>
|
|
8748
|
-
|
|
8749
|
-
<media_cdn>
|
|
8750
|
-
{{compiled/media-cdn.md}}
|
|
8751
|
-
</media_cdn>
|
|
8752
|
-
|
|
8753
8905
|
<app_files>
|
|
8754
8906
|
{{compiled/files.md}}
|
|
8755
8907
|
</app_files>
|
|
@@ -8767,10 +8919,10 @@ Current date: ${now}
|
|
|
8767
8919
|
</secrets>
|
|
8768
8920
|
</platform_docs>
|
|
8769
8921
|
|
|
8922
|
+
${loadSkillsCatalog()}
|
|
8923
|
+
|
|
8770
8924
|
<mindstudio_agent_sdk_docs>
|
|
8771
8925
|
{{compiled/sdk-actions.md}}
|
|
8772
|
-
|
|
8773
|
-
{{compiled/task-agents.md}}
|
|
8774
8926
|
</mindstudio_agent_sdk_docs>
|
|
8775
8927
|
|
|
8776
8928
|
<mindstudio_flavored_markdown_spec_docs>
|
|
@@ -8841,20 +8993,21 @@ var init_prompt4 = __esm({
|
|
|
8841
8993
|
init_assets();
|
|
8842
8994
|
init_projectContext();
|
|
8843
8995
|
init_orgContext();
|
|
8996
|
+
init_catalog();
|
|
8844
8997
|
}
|
|
8845
8998
|
});
|
|
8846
8999
|
|
|
8847
9000
|
// src/config.ts
|
|
8848
|
-
import
|
|
8849
|
-
import
|
|
9001
|
+
import fs22 from "fs";
|
|
9002
|
+
import path13 from "path";
|
|
8850
9003
|
import os from "os";
|
|
8851
9004
|
function loadConfigFile() {
|
|
8852
9005
|
try {
|
|
8853
|
-
const raw =
|
|
8854
|
-
|
|
9006
|
+
const raw = fs22.readFileSync(CONFIG_PATH, "utf-8");
|
|
9007
|
+
log14.debug("Loaded config file", { path: CONFIG_PATH });
|
|
8855
9008
|
return JSON.parse(raw);
|
|
8856
9009
|
} catch (err) {
|
|
8857
|
-
|
|
9010
|
+
log14.debug("No config file found", {
|
|
8858
9011
|
path: CONFIG_PATH,
|
|
8859
9012
|
error: err.message
|
|
8860
9013
|
});
|
|
@@ -8869,13 +9022,13 @@ function resolveConfig(flags2) {
|
|
|
8869
9022
|
const baseUrl2 = flags2?.baseUrl || process.env.MINDSTUDIO_BASE_URL || env?.apiBaseUrl || DEFAULT_BASE_URL;
|
|
8870
9023
|
const appId = process.env.MINDSTUDIO_APP_ID || void 0;
|
|
8871
9024
|
if (!apiKey) {
|
|
8872
|
-
|
|
9025
|
+
log14.error("No API key found");
|
|
8873
9026
|
throw new Error(
|
|
8874
9027
|
"No API key found. Set MINDSTUDIO_API_KEY or configure ~/.mindstudio-local-tunnel/config.json."
|
|
8875
9028
|
);
|
|
8876
9029
|
}
|
|
8877
9030
|
const keySource = flags2?.apiKey ? "cli flag" : process.env.MINDSTUDIO_API_KEY ? "env var" : "config file";
|
|
8878
|
-
|
|
9031
|
+
log14.info("Config resolved", {
|
|
8879
9032
|
baseUrl: baseUrl2,
|
|
8880
9033
|
keySource,
|
|
8881
9034
|
environment: activeEnv,
|
|
@@ -8883,13 +9036,13 @@ function resolveConfig(flags2) {
|
|
|
8883
9036
|
});
|
|
8884
9037
|
return { apiKey, baseUrl: baseUrl2, appId };
|
|
8885
9038
|
}
|
|
8886
|
-
var
|
|
9039
|
+
var log14, CONFIG_PATH, DEFAULT_BASE_URL;
|
|
8887
9040
|
var init_config = __esm({
|
|
8888
9041
|
"src/config.ts"() {
|
|
8889
9042
|
"use strict";
|
|
8890
9043
|
init_logger();
|
|
8891
|
-
|
|
8892
|
-
CONFIG_PATH =
|
|
9044
|
+
log14 = createLogger("config");
|
|
9045
|
+
CONFIG_PATH = path13.join(
|
|
8893
9046
|
os.homedir(),
|
|
8894
9047
|
".mindstudio-local-tunnel",
|
|
8895
9048
|
"config.json"
|
|
@@ -8901,11 +9054,11 @@ var init_config = __esm({
|
|
|
8901
9054
|
// src/headless/attachments.ts
|
|
8902
9055
|
import { mkdirSync, existsSync } from "fs";
|
|
8903
9056
|
import { writeFile } from "fs/promises";
|
|
8904
|
-
import { basename, join, extname } from "path";
|
|
9057
|
+
import { basename as basename2, join, extname as extname2 } from "path";
|
|
8905
9058
|
function filenameFromUrl(url) {
|
|
8906
9059
|
try {
|
|
8907
9060
|
const pathname = new URL(url).pathname;
|
|
8908
|
-
const name =
|
|
9061
|
+
const name = basename2(pathname);
|
|
8909
9062
|
return name && name !== "/" ? decodeURIComponent(name) : `upload-${Date.now()}`;
|
|
8910
9063
|
} catch {
|
|
8911
9064
|
return `upload-${Date.now()}`;
|
|
@@ -8916,7 +9069,7 @@ function resolveUniqueFilename(name, claimed) {
|
|
|
8916
9069
|
if (isFree(name)) {
|
|
8917
9070
|
return name;
|
|
8918
9071
|
}
|
|
8919
|
-
const ext =
|
|
9072
|
+
const ext = extname2(name);
|
|
8920
9073
|
const base = name.slice(0, name.length - ext.length);
|
|
8921
9074
|
let counter = 1;
|
|
8922
9075
|
while (!isFree(`${base}-${counter}${ext}`)) {
|
|
@@ -8926,7 +9079,7 @@ function resolveUniqueFilename(name, claimed) {
|
|
|
8926
9079
|
}
|
|
8927
9080
|
function isImageAttachment(att) {
|
|
8928
9081
|
const name = att.filename || filenameFromUrl(att.url);
|
|
8929
|
-
return IMAGE_EXTENSIONS.has(
|
|
9082
|
+
return IMAGE_EXTENSIONS.has(extname2(name).toLowerCase());
|
|
8930
9083
|
}
|
|
8931
9084
|
async function persistAttachments(attachments) {
|
|
8932
9085
|
const nonVoice = attachments.filter((a) => !a.isVoice);
|
|
@@ -8955,7 +9108,7 @@ async function persistAttachments(attachments) {
|
|
|
8955
9108
|
}
|
|
8956
9109
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
8957
9110
|
await writeFile(localPath, buffer);
|
|
8958
|
-
|
|
9111
|
+
log15.info("Attachment saved", {
|
|
8959
9112
|
filename: name,
|
|
8960
9113
|
path: localPath,
|
|
8961
9114
|
bytes: buffer.length
|
|
@@ -8969,7 +9122,7 @@ async function persistAttachments(attachments) {
|
|
|
8969
9122
|
if (textRes.ok) {
|
|
8970
9123
|
extractedTextPath = `${localPath}.txt`;
|
|
8971
9124
|
await writeFile(extractedTextPath, await textRes.text(), "utf-8");
|
|
8972
|
-
|
|
9125
|
+
log15.info("Extracted text saved", { path: extractedTextPath });
|
|
8973
9126
|
}
|
|
8974
9127
|
} catch {
|
|
8975
9128
|
}
|
|
@@ -9014,12 +9167,12 @@ function buildUploadHeader(results) {
|
|
|
9014
9167
|
return `[Uploaded files]
|
|
9015
9168
|
${lines.join("\n")}`;
|
|
9016
9169
|
}
|
|
9017
|
-
var
|
|
9170
|
+
var log15, UPLOADS_DIR, IMAGE_EXTENSIONS;
|
|
9018
9171
|
var init_attachments = __esm({
|
|
9019
9172
|
"src/headless/attachments.ts"() {
|
|
9020
9173
|
"use strict";
|
|
9021
9174
|
init_logger();
|
|
9022
|
-
|
|
9175
|
+
log15 = createLogger("headless:attachments");
|
|
9023
9176
|
UPLOADS_DIR = "src/.user-uploads";
|
|
9024
9177
|
IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
|
|
9025
9178
|
}
|
|
@@ -9233,7 +9386,7 @@ var headless_exports = {};
|
|
|
9233
9386
|
__export(headless_exports, {
|
|
9234
9387
|
HeadlessSession: () => HeadlessSession
|
|
9235
9388
|
});
|
|
9236
|
-
var
|
|
9389
|
+
var log16, EXTERNAL_TOOL_TIMEOUT_MS, USER_FACING_TOOLS, FORCED_COMPACTION_THRESHOLD_TOKENS, HeadlessSession;
|
|
9237
9390
|
var init_headless = __esm({
|
|
9238
9391
|
"src/headless/index.ts"() {
|
|
9239
9392
|
"use strict";
|
|
@@ -9254,7 +9407,7 @@ var init_headless = __esm({
|
|
|
9254
9407
|
init_messageQueue();
|
|
9255
9408
|
init_resolve();
|
|
9256
9409
|
init_sentinel();
|
|
9257
|
-
|
|
9410
|
+
log16 = createLogger("headless");
|
|
9258
9411
|
EXTERNAL_TOOL_TIMEOUT_MS = 3e5;
|
|
9259
9412
|
USER_FACING_TOOLS = /* @__PURE__ */ new Set([
|
|
9260
9413
|
"promptUser",
|
|
@@ -9399,7 +9552,7 @@ var init_headless = __esm({
|
|
|
9399
9552
|
}
|
|
9400
9553
|
const line = JSON.stringify(payload) + "\n";
|
|
9401
9554
|
if (event === "history") {
|
|
9402
|
-
|
|
9555
|
+
log16.info("Wrote history event to stdout", {
|
|
9403
9556
|
requestId,
|
|
9404
9557
|
bytes: line.length
|
|
9405
9558
|
});
|
|
@@ -9476,7 +9629,7 @@ var init_headless = __esm({
|
|
|
9476
9629
|
if (this.sessionStats.lastContextSize <= FORCED_COMPACTION_THRESHOLD_TOKENS) {
|
|
9477
9630
|
return;
|
|
9478
9631
|
}
|
|
9479
|
-
|
|
9632
|
+
log16.info("Forced compaction gate triggered", {
|
|
9480
9633
|
contextSize: this.sessionStats.lastContextSize,
|
|
9481
9634
|
threshold: FORCED_COMPACTION_THRESHOLD_TOKENS,
|
|
9482
9635
|
requestId
|
|
@@ -9493,7 +9646,7 @@ var init_headless = __esm({
|
|
|
9493
9646
|
}
|
|
9494
9647
|
onBackgroundComplete = (toolCallId, name, result, subAgentMessages) => {
|
|
9495
9648
|
this.pendingBlockUpdates.push({ toolCallId, result, subAgentMessages });
|
|
9496
|
-
|
|
9649
|
+
log16.info("Background complete", {
|
|
9497
9650
|
toolCallId,
|
|
9498
9651
|
name,
|
|
9499
9652
|
requestId: this.currentRequestId
|
|
@@ -9530,17 +9683,17 @@ var init_headless = __esm({
|
|
|
9530
9683
|
return Promise.resolve(early);
|
|
9531
9684
|
}
|
|
9532
9685
|
const shouldTimeout = !USER_FACING_TOOLS.has(name);
|
|
9533
|
-
return new Promise((
|
|
9686
|
+
return new Promise((resolve3) => {
|
|
9534
9687
|
const timeout = shouldTimeout ? setTimeout(() => {
|
|
9535
9688
|
this.pendingTools.delete(id);
|
|
9536
|
-
|
|
9689
|
+
resolve3(
|
|
9537
9690
|
"Error: Tool timed out \u2014 no response from the app environment after 5 minutes."
|
|
9538
9691
|
);
|
|
9539
9692
|
}, EXTERNAL_TOOL_TIMEOUT_MS) : void 0;
|
|
9540
9693
|
this.pendingTools.set(id, {
|
|
9541
9694
|
resolve: (result) => {
|
|
9542
9695
|
clearTimeout(timeout);
|
|
9543
|
-
|
|
9696
|
+
resolve3(result);
|
|
9544
9697
|
},
|
|
9545
9698
|
timeout
|
|
9546
9699
|
});
|
|
@@ -9731,7 +9884,7 @@ var init_headless = __esm({
|
|
|
9731
9884
|
await this.runForcedCompactionIfNeeded(requestId);
|
|
9732
9885
|
const attachments = parsed.attachments;
|
|
9733
9886
|
if (attachments?.length) {
|
|
9734
|
-
|
|
9887
|
+
log16.info("Message has attachments", {
|
|
9735
9888
|
count: attachments.length,
|
|
9736
9889
|
urls: attachments.map((a) => a.url)
|
|
9737
9890
|
});
|
|
@@ -9747,7 +9900,7 @@ var init_headless = __esm({
|
|
|
9747
9900
|
attachmentHeader = header;
|
|
9748
9901
|
}
|
|
9749
9902
|
} catch (err) {
|
|
9750
|
-
|
|
9903
|
+
log16.warn("Attachment persistence failed", { error: err.message });
|
|
9751
9904
|
}
|
|
9752
9905
|
}
|
|
9753
9906
|
let resolved = null;
|
|
@@ -9811,7 +9964,7 @@ var init_headless = __esm({
|
|
|
9811
9964
|
error: "Turn ended unexpectedly"
|
|
9812
9965
|
});
|
|
9813
9966
|
}
|
|
9814
|
-
|
|
9967
|
+
log16.info("Turn complete", {
|
|
9815
9968
|
requestId,
|
|
9816
9969
|
durationMs: Date.now() - this.turnStart
|
|
9817
9970
|
});
|
|
@@ -9823,7 +9976,7 @@ var init_headless = __esm({
|
|
|
9823
9976
|
error: err.message
|
|
9824
9977
|
});
|
|
9825
9978
|
}
|
|
9826
|
-
|
|
9979
|
+
log16.warn("Command failed", {
|
|
9827
9980
|
action: "message",
|
|
9828
9981
|
requestId,
|
|
9829
9982
|
error: err.message
|
|
@@ -9980,7 +10133,7 @@ var init_headless = __esm({
|
|
|
9980
10133
|
try {
|
|
9981
10134
|
parsed = JSON.parse(line);
|
|
9982
10135
|
} catch (err) {
|
|
9983
|
-
|
|
10136
|
+
log16.warn("Invalid JSON on stdin", {
|
|
9984
10137
|
error: err.message,
|
|
9985
10138
|
lineLength: line.length,
|
|
9986
10139
|
preview: line.slice(0, 200)
|
|
@@ -9989,7 +10142,7 @@ var init_headless = __esm({
|
|
|
9989
10142
|
return;
|
|
9990
10143
|
}
|
|
9991
10144
|
const { action, requestId } = parsed;
|
|
9992
|
-
|
|
10145
|
+
log16.info("Command received", { action, requestId });
|
|
9993
10146
|
if (action === "tool_result" && parsed.id) {
|
|
9994
10147
|
const id = parsed.id;
|
|
9995
10148
|
const result = parsed.result ?? "";
|
|
@@ -9998,7 +10151,7 @@ var init_headless = __esm({
|
|
|
9998
10151
|
this.pendingTools.delete(id);
|
|
9999
10152
|
pending2.resolve(result);
|
|
10000
10153
|
} else if (!this.running) {
|
|
10001
|
-
|
|
10154
|
+
log16.info("Late tool_result while idle, dismissing", { id });
|
|
10002
10155
|
this.emit("completed", { success: true }, requestId);
|
|
10003
10156
|
} else {
|
|
10004
10157
|
this.earlyResults.set(id, result);
|
|
@@ -10011,7 +10164,7 @@ var init_headless = __esm({
|
|
|
10011
10164
|
...typeof parsed.before === "number" ? { before: parsed.before } : {},
|
|
10012
10165
|
...typeof parsed.limit === "number" ? { limit: parsed.limit } : {}
|
|
10013
10166
|
});
|
|
10014
|
-
|
|
10167
|
+
log16.info("History response", {
|
|
10015
10168
|
requestId,
|
|
10016
10169
|
startIndex: page.startIndex,
|
|
10017
10170
|
endIndex: page.endIndex,
|
|
@@ -10171,8 +10324,8 @@ var init_headless = __esm({
|
|
|
10171
10324
|
// src/index.tsx
|
|
10172
10325
|
import { render } from "ink";
|
|
10173
10326
|
import os2 from "os";
|
|
10174
|
-
import
|
|
10175
|
-
import
|
|
10327
|
+
import fs23 from "fs";
|
|
10328
|
+
import path14 from "path";
|
|
10176
10329
|
|
|
10177
10330
|
// src/tui/App.tsx
|
|
10178
10331
|
import { useState as useState2, useCallback, useRef } from "react";
|
|
@@ -10227,6 +10380,8 @@ function summarizeInput(name, input) {
|
|
|
10227
10380
|
return input.pattern || "";
|
|
10228
10381
|
case "listDir":
|
|
10229
10382
|
return input.path || ".";
|
|
10383
|
+
case "loadSkill":
|
|
10384
|
+
return input.skill || "";
|
|
10230
10385
|
default:
|
|
10231
10386
|
return JSON.stringify(input).slice(0, 60);
|
|
10232
10387
|
}
|
|
@@ -10251,6 +10406,8 @@ function summarizeResult(name, result) {
|
|
|
10251
10406
|
return `${lines} files`;
|
|
10252
10407
|
case "listDir":
|
|
10253
10408
|
return `${lines} entries`;
|
|
10409
|
+
case "loadSkill":
|
|
10410
|
+
return "reference loaded";
|
|
10254
10411
|
default:
|
|
10255
10412
|
return `${lines} lines`;
|
|
10256
10413
|
}
|
|
@@ -10493,8 +10650,8 @@ for (let i = 0; i < args.length; i++) {
|
|
|
10493
10650
|
var startupLog = createLogger("startup");
|
|
10494
10651
|
function printDebugInfo(config) {
|
|
10495
10652
|
const pkg = JSON.parse(
|
|
10496
|
-
|
|
10497
|
-
|
|
10653
|
+
fs23.readFileSync(
|
|
10654
|
+
path14.join(import.meta.dirname, "..", "package.json"),
|
|
10498
10655
|
"utf-8"
|
|
10499
10656
|
)
|
|
10500
10657
|
);
|