@mindstudio-ai/remy 0.1.8 → 0.1.10
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/compiled/design.md +34 -38
- package/dist/compiled/media-cdn.md +2 -2
- package/dist/compiled/msfm.md +58 -2
- package/dist/compiled/platform.md +4 -2
- package/dist/headless.js +881 -140
- package/dist/index.js +1097 -298
- package/dist/prompt/.notes.md +138 -0
- package/dist/prompt/actions/buildFromInitialSpec.md +7 -0
- package/dist/prompt/actions/publish.md +12 -0
- package/dist/prompt/actions/sync.md +19 -0
- package/dist/prompt/compiled/README.md +100 -0
- package/dist/prompt/compiled/auth.md +77 -0
- package/dist/prompt/compiled/design.md +250 -0
- package/dist/prompt/compiled/dev-and-deploy.md +69 -0
- package/dist/prompt/compiled/interfaces.md +238 -0
- package/dist/prompt/compiled/manifest.md +107 -0
- package/dist/prompt/compiled/media-cdn.md +51 -0
- package/dist/prompt/compiled/methods.md +225 -0
- package/dist/prompt/compiled/msfm.md +189 -0
- package/dist/prompt/compiled/platform.md +103 -0
- package/dist/prompt/compiled/scenarios.md +103 -0
- package/dist/prompt/compiled/sdk-actions.md +146 -0
- package/dist/prompt/compiled/tables.md +211 -0
- package/dist/prompt/sources/frontend-design-notes.md +162 -0
- package/dist/prompt/sources/media-cdn.md +46 -0
- package/dist/prompt/static/authoring.md +57 -0
- package/dist/prompt/static/coding.md +29 -0
- package/dist/prompt/static/identity.md +1 -0
- package/dist/prompt/static/instructions.md +29 -0
- package/dist/prompt/static/intake.md +44 -0
- package/dist/prompt/static/lsp.md +4 -0
- package/dist/static/authoring.md +6 -2
- package/dist/static/instructions.md +2 -1
- package/dist/static/projectContext.ts +9 -4
- package/dist/subagents/browserAutomation/prompt.md +2 -1
- package/dist/subagents/designExpert/.notes.md +253 -0
- package/dist/subagents/designExpert/data/compile-inspiration.sh +126 -0
- package/dist/subagents/designExpert/data/fonts.json +2855 -0
- package/dist/subagents/designExpert/data/inspiration.json +540 -0
- package/dist/subagents/designExpert/data/inspiration.raw.json +112 -0
- package/dist/subagents/designExpert/prompt.md +19 -0
- package/dist/subagents/designExpert/prompts/animation.md +19 -0
- package/dist/subagents/designExpert/prompts/color.md +35 -0
- package/dist/subagents/designExpert/prompts/frontend-design-notes.md +162 -0
- package/dist/subagents/designExpert/prompts/icons.md +27 -0
- package/dist/subagents/designExpert/prompts/identity.md +71 -0
- package/dist/subagents/designExpert/prompts/images.md +50 -0
- package/dist/subagents/designExpert/prompts/instructions.md +16 -0
- package/dist/subagents/designExpert/prompts/layout.md +34 -0
- package/package.json +1 -1
package/dist/headless.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/headless.ts
|
|
2
2
|
import { createInterface } from "readline";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import fs16 from "fs";
|
|
4
|
+
import path9 from "path";
|
|
5
5
|
|
|
6
6
|
// src/config.ts
|
|
7
7
|
import fs2 from "fs";
|
|
@@ -194,11 +194,14 @@ function loadSpecFileMetadata() {
|
|
|
194
194
|
}
|
|
195
195
|
const entries = [];
|
|
196
196
|
for (const filePath of files) {
|
|
197
|
-
const { name, description } = parseFrontmatter(filePath);
|
|
197
|
+
const { name, description, type } = parseFrontmatter(filePath);
|
|
198
198
|
let line = `- ${filePath}`;
|
|
199
199
|
if (name) {
|
|
200
200
|
line += ` \u2014 "${name}"`;
|
|
201
201
|
}
|
|
202
|
+
if (type) {
|
|
203
|
+
line += ` (${type})`;
|
|
204
|
+
}
|
|
202
205
|
if (description) {
|
|
203
206
|
line += ` \u2014 ${description}`;
|
|
204
207
|
}
|
|
@@ -232,14 +235,15 @@ function parseFrontmatter(filePath) {
|
|
|
232
235
|
const content = fs3.readFileSync(filePath, "utf-8");
|
|
233
236
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
234
237
|
if (!match) {
|
|
235
|
-
return { name: "", description: "" };
|
|
238
|
+
return { name: "", description: "", type: "" };
|
|
236
239
|
}
|
|
237
240
|
const fm = match[1];
|
|
238
241
|
const name = fm.match(/^name:\s*(.+)$/m)?.[1]?.trim() ?? "";
|
|
239
242
|
const description = fm.match(/^description:\s*(.+)$/m)?.[1]?.trim() ?? "";
|
|
240
|
-
|
|
243
|
+
const type = fm.match(/^type:\s*(.+)$/m)?.[1]?.trim() ?? "";
|
|
244
|
+
return { name, description, type };
|
|
241
245
|
} catch {
|
|
242
|
-
return { name: "", description: "" };
|
|
246
|
+
return { name: "", description: "", type: "" };
|
|
243
247
|
}
|
|
244
248
|
}
|
|
245
249
|
function loadProjectFileListing() {
|
|
@@ -448,11 +452,35 @@ async function* streamChat(params) {
|
|
|
448
452
|
yield { type: "error", error: errorMessage };
|
|
449
453
|
return;
|
|
450
454
|
}
|
|
455
|
+
const STALL_TIMEOUT_MS = 3e5;
|
|
451
456
|
const reader = res.body.getReader();
|
|
452
457
|
const decoder = new TextDecoder();
|
|
453
458
|
let buffer = "";
|
|
454
459
|
while (true) {
|
|
455
|
-
|
|
460
|
+
let stallTimer;
|
|
461
|
+
let readResult;
|
|
462
|
+
try {
|
|
463
|
+
readResult = await Promise.race([
|
|
464
|
+
reader.read(),
|
|
465
|
+
new Promise((_, reject) => {
|
|
466
|
+
stallTimer = setTimeout(
|
|
467
|
+
() => reject(new Error("stream_stall")),
|
|
468
|
+
STALL_TIMEOUT_MS
|
|
469
|
+
);
|
|
470
|
+
})
|
|
471
|
+
]);
|
|
472
|
+
clearTimeout(stallTimer);
|
|
473
|
+
} catch {
|
|
474
|
+
clearTimeout(stallTimer);
|
|
475
|
+
await reader.cancel();
|
|
476
|
+
log.error("Stream stalled", { elapsed: `${Date.now() - startTime}ms` });
|
|
477
|
+
yield {
|
|
478
|
+
type: "error",
|
|
479
|
+
error: "Stream stalled \u2014 no data received for 5 minutes"
|
|
480
|
+
};
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
const { done, value } = readResult;
|
|
456
484
|
if (done) {
|
|
457
485
|
break;
|
|
458
486
|
}
|
|
@@ -486,6 +514,50 @@ async function* streamChat(params) {
|
|
|
486
514
|
}
|
|
487
515
|
}
|
|
488
516
|
}
|
|
517
|
+
var MAX_RETRIES = 3;
|
|
518
|
+
var INITIAL_BACKOFF_MS = 1e3;
|
|
519
|
+
function isRetryableError(error) {
|
|
520
|
+
return /Network error/i.test(error) || /HTTP 5\d\d/i.test(error) || /Stream stalled/i.test(error);
|
|
521
|
+
}
|
|
522
|
+
function sleep(ms) {
|
|
523
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
524
|
+
}
|
|
525
|
+
async function* streamChatWithRetry(params, options) {
|
|
526
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
527
|
+
const buffer = [];
|
|
528
|
+
let retryableFailure = false;
|
|
529
|
+
for await (const event of streamChat(params)) {
|
|
530
|
+
if (event.type === "error") {
|
|
531
|
+
if (isRetryableError(event.error) && attempt < MAX_RETRIES - 1) {
|
|
532
|
+
log.warn("Retryable error, will retry", {
|
|
533
|
+
attempt: attempt + 1,
|
|
534
|
+
maxRetries: MAX_RETRIES,
|
|
535
|
+
error: event.error
|
|
536
|
+
});
|
|
537
|
+
options?.onRetry?.(attempt, event.error);
|
|
538
|
+
retryableFailure = true;
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
yield event;
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
buffer.push(event);
|
|
545
|
+
}
|
|
546
|
+
if (retryableFailure) {
|
|
547
|
+
if (params.signal?.aborted) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
const backoff = INITIAL_BACKOFF_MS * 2 ** attempt;
|
|
551
|
+
log.info("Retrying after backoff", { backoffMs: backoff });
|
|
552
|
+
await sleep(backoff);
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
for (const event of buffer) {
|
|
556
|
+
yield event;
|
|
557
|
+
}
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
489
561
|
|
|
490
562
|
// src/tools/spec/readSpec.ts
|
|
491
563
|
import fs5 from "fs/promises";
|
|
@@ -893,7 +965,88 @@ async function listRecursive(dir) {
|
|
|
893
965
|
return results;
|
|
894
966
|
}
|
|
895
967
|
|
|
896
|
-
// src/tools/spec/
|
|
968
|
+
// src/tools/spec/clearSyncStatus.ts
|
|
969
|
+
var clearSyncStatusTool = {
|
|
970
|
+
definition: {
|
|
971
|
+
name: "clearSyncStatus",
|
|
972
|
+
description: "Clear the sync status flags after syncing spec and code. Call this after finishing a sync operation.",
|
|
973
|
+
inputSchema: {
|
|
974
|
+
type: "object",
|
|
975
|
+
properties: {}
|
|
976
|
+
}
|
|
977
|
+
},
|
|
978
|
+
async execute() {
|
|
979
|
+
return "ok";
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
|
|
983
|
+
// src/tools/spec/presentSyncPlan.ts
|
|
984
|
+
var presentSyncPlanTool = {
|
|
985
|
+
definition: {
|
|
986
|
+
name: "presentSyncPlan",
|
|
987
|
+
description: "Present a structured sync plan to the user for approval. Write a clear markdown summary of what changed and what you intend to update. The user will see this in a full-screen view and can approve or dismiss. Call this BEFORE making any sync edits.",
|
|
988
|
+
inputSchema: {
|
|
989
|
+
type: "object",
|
|
990
|
+
properties: {
|
|
991
|
+
content: {
|
|
992
|
+
type: "string",
|
|
993
|
+
description: "Markdown plan describing what changed and what will be updated."
|
|
994
|
+
}
|
|
995
|
+
},
|
|
996
|
+
required: ["content"]
|
|
997
|
+
}
|
|
998
|
+
},
|
|
999
|
+
streaming: {},
|
|
1000
|
+
async execute() {
|
|
1001
|
+
return "approved";
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
// src/tools/spec/presentPublishPlan.ts
|
|
1006
|
+
var presentPublishPlanTool = {
|
|
1007
|
+
definition: {
|
|
1008
|
+
name: "presentPublishPlan",
|
|
1009
|
+
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.",
|
|
1010
|
+
inputSchema: {
|
|
1011
|
+
type: "object",
|
|
1012
|
+
properties: {
|
|
1013
|
+
content: {
|
|
1014
|
+
type: "string",
|
|
1015
|
+
description: "Markdown changelog describing what changed and what will be deployed."
|
|
1016
|
+
}
|
|
1017
|
+
},
|
|
1018
|
+
required: ["content"]
|
|
1019
|
+
}
|
|
1020
|
+
},
|
|
1021
|
+
streaming: {},
|
|
1022
|
+
async execute() {
|
|
1023
|
+
return "approved";
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
|
|
1027
|
+
// src/tools/spec/presentPlan.ts
|
|
1028
|
+
var presentPlanTool = {
|
|
1029
|
+
definition: {
|
|
1030
|
+
name: "presentPlan",
|
|
1031
|
+
description: "Present an implementation plan for user approval before making changes. Use this only for large, multi-step changes 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. If the user rejects with feedback, revise and present again.",
|
|
1032
|
+
inputSchema: {
|
|
1033
|
+
type: "object",
|
|
1034
|
+
properties: {
|
|
1035
|
+
content: {
|
|
1036
|
+
type: "string",
|
|
1037
|
+
description: "Markdown plan describing what you intend to do."
|
|
1038
|
+
}
|
|
1039
|
+
},
|
|
1040
|
+
required: ["content"]
|
|
1041
|
+
}
|
|
1042
|
+
},
|
|
1043
|
+
streaming: {},
|
|
1044
|
+
async execute() {
|
|
1045
|
+
return "approved";
|
|
1046
|
+
}
|
|
1047
|
+
};
|
|
1048
|
+
|
|
1049
|
+
// src/tools/common/setProjectOnboardingState.ts
|
|
897
1050
|
var setProjectOnboardingStateTool = {
|
|
898
1051
|
definition: {
|
|
899
1052
|
name: "setProjectOnboardingState",
|
|
@@ -919,7 +1072,7 @@ var setProjectOnboardingStateTool = {
|
|
|
919
1072
|
}
|
|
920
1073
|
};
|
|
921
1074
|
|
|
922
|
-
// src/tools/
|
|
1075
|
+
// src/tools/common/promptUser.ts
|
|
923
1076
|
var promptUserTool = {
|
|
924
1077
|
definition: {
|
|
925
1078
|
name: "promptUser",
|
|
@@ -947,8 +1100,8 @@ var promptUserTool = {
|
|
|
947
1100
|
},
|
|
948
1101
|
type: {
|
|
949
1102
|
type: "string",
|
|
950
|
-
enum: ["select", "checklist", "text", "file"
|
|
951
|
-
description:
|
|
1103
|
+
enum: ["select", "checklist", "text", "file"],
|
|
1104
|
+
description: 'select: pick one from a list. checklist: pick one or more from a list. The user can always provide a custom "Other" answer for select and checklist questions, so there is no need to include an "Other" option. text: free-form input. file: file/image upload, returns CDN URL(s) that can be referenced directly or curled onto disk.'
|
|
952
1105
|
},
|
|
953
1106
|
helpText: {
|
|
954
1107
|
type: "string",
|
|
@@ -985,10 +1138,6 @@ var promptUserTool = {
|
|
|
985
1138
|
type: "boolean",
|
|
986
1139
|
description: "For file type: allow multiple uploads (returns array of URLs). Defaults to false."
|
|
987
1140
|
},
|
|
988
|
-
allowOther: {
|
|
989
|
-
type: "boolean",
|
|
990
|
-
description: 'For select and checklist types: adds an "Other" option that lets the user type a custom answer. Use this instead of adding a separate follow-up text field for custom input. Defaults to false.'
|
|
991
|
-
},
|
|
992
1141
|
format: {
|
|
993
1142
|
type: "string",
|
|
994
1143
|
enum: ["email", "url", "phone", "number"],
|
|
@@ -1046,8 +1195,6 @@ var promptUserTool = {
|
|
|
1046
1195
|
line += q.type === "checklist" ? ` (pick one or more: ${opts.join(" / ")})` : ` (${opts.join(" / ")})`;
|
|
1047
1196
|
} else if (q.type === "file") {
|
|
1048
1197
|
line += " (upload file)";
|
|
1049
|
-
} else if (q.type === "color") {
|
|
1050
|
-
line += " (pick a color)";
|
|
1051
1198
|
}
|
|
1052
1199
|
return line;
|
|
1053
1200
|
});
|
|
@@ -1056,113 +1203,181 @@ ${lines.join("\n")}`;
|
|
|
1056
1203
|
}
|
|
1057
1204
|
};
|
|
1058
1205
|
|
|
1059
|
-
// src/tools/
|
|
1060
|
-
var
|
|
1206
|
+
// src/tools/common/confirmDestructiveAction.ts
|
|
1207
|
+
var confirmDestructiveActionTool = {
|
|
1061
1208
|
definition: {
|
|
1062
|
-
name: "
|
|
1063
|
-
description: "
|
|
1209
|
+
name: "confirmDestructiveAction",
|
|
1210
|
+
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 presentSyncPlan, presentPublishPlan, or presentPlan (those already include approval). Do not use before onboarding state transitions.",
|
|
1064
1211
|
inputSchema: {
|
|
1065
1212
|
type: "object",
|
|
1066
|
-
properties: {
|
|
1213
|
+
properties: {
|
|
1214
|
+
message: {
|
|
1215
|
+
type: "string",
|
|
1216
|
+
description: "Explanation of what is about to happen and why confirmation is needed."
|
|
1217
|
+
},
|
|
1218
|
+
confirmLabel: {
|
|
1219
|
+
type: "string",
|
|
1220
|
+
description: 'Custom label for the confirm button (e.g., "Delete", "Reset Database"). Defaults to "Confirm".'
|
|
1221
|
+
},
|
|
1222
|
+
dismissLabel: {
|
|
1223
|
+
type: "string",
|
|
1224
|
+
description: 'Custom label for the dismiss button (e.g., "Keep It", "Go Back"). Defaults to "Cancel".'
|
|
1225
|
+
}
|
|
1226
|
+
},
|
|
1227
|
+
required: ["message"]
|
|
1067
1228
|
}
|
|
1068
1229
|
},
|
|
1069
1230
|
async execute() {
|
|
1070
|
-
return "
|
|
1231
|
+
return "confirmed";
|
|
1071
1232
|
}
|
|
1072
1233
|
};
|
|
1073
1234
|
|
|
1074
|
-
// src/tools/
|
|
1075
|
-
|
|
1235
|
+
// src/tools/common/askMindStudioSdk.ts
|
|
1236
|
+
import { exec } from "child_process";
|
|
1237
|
+
var askMindStudioSdkTool = {
|
|
1076
1238
|
definition: {
|
|
1077
|
-
name: "
|
|
1078
|
-
description: "
|
|
1239
|
+
name: "askMindStudioSdk",
|
|
1240
|
+
description: "Ask the MindStudio SDK assistant about available actions, AI models, connectors, and integrations using natural language. Returns code examples with correct method signatures, model IDs, and config options. Always use this to verify correct SDK usage, especially model IDs and configuration options. Describe what you need, not what API methods you need; the assistant will figure out the right approach. This runs its own LLM call so it has a few seconds of latency; batch multiple questions into a single query.",
|
|
1079
1241
|
inputSchema: {
|
|
1080
1242
|
type: "object",
|
|
1081
1243
|
properties: {
|
|
1082
|
-
|
|
1244
|
+
query: {
|
|
1083
1245
|
type: "string",
|
|
1084
|
-
description: "
|
|
1246
|
+
description: "Natural language question about the SDK."
|
|
1085
1247
|
}
|
|
1086
1248
|
},
|
|
1087
|
-
required: ["
|
|
1249
|
+
required: ["query"]
|
|
1088
1250
|
}
|
|
1089
1251
|
},
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
return
|
|
1252
|
+
async execute(input) {
|
|
1253
|
+
const query = input.query;
|
|
1254
|
+
return new Promise((resolve) => {
|
|
1255
|
+
exec(
|
|
1256
|
+
`mindstudio ask ${JSON.stringify(query)}`,
|
|
1257
|
+
{ timeout: 6e4, maxBuffer: 512 * 1024 },
|
|
1258
|
+
(err, stdout, stderr) => {
|
|
1259
|
+
if (stdout.trim()) {
|
|
1260
|
+
resolve(stdout.trim());
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
if (err) {
|
|
1264
|
+
resolve(`Error: ${stderr.trim() || err.message}`);
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
resolve("(no response)");
|
|
1268
|
+
}
|
|
1269
|
+
);
|
|
1270
|
+
});
|
|
1093
1271
|
}
|
|
1094
1272
|
};
|
|
1095
1273
|
|
|
1096
|
-
// src/tools/
|
|
1097
|
-
|
|
1274
|
+
// src/tools/common/fetchUrl.ts
|
|
1275
|
+
import { exec as exec2 } from "child_process";
|
|
1276
|
+
var fetchUrlTool = {
|
|
1098
1277
|
definition: {
|
|
1099
|
-
name: "
|
|
1100
|
-
description: "
|
|
1278
|
+
name: "scapeWebUrl",
|
|
1279
|
+
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",
|
|
1101
1280
|
inputSchema: {
|
|
1102
1281
|
type: "object",
|
|
1103
1282
|
properties: {
|
|
1104
|
-
|
|
1283
|
+
url: {
|
|
1105
1284
|
type: "string",
|
|
1106
|
-
description: "
|
|
1285
|
+
description: "The URL to fetch."
|
|
1286
|
+
},
|
|
1287
|
+
screenshot: {
|
|
1288
|
+
type: "boolean",
|
|
1289
|
+
description: "Capture a screenshot of the page in addition to the text content. Adds latency; only use when you need to see the visual design."
|
|
1107
1290
|
}
|
|
1108
1291
|
},
|
|
1109
|
-
required: ["
|
|
1292
|
+
required: ["url"]
|
|
1110
1293
|
}
|
|
1111
1294
|
},
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1295
|
+
async execute(input) {
|
|
1296
|
+
const url = input.url;
|
|
1297
|
+
const screenshot = input.screenshot;
|
|
1298
|
+
const pageOptions = { onlyMainContent: true };
|
|
1299
|
+
if (screenshot) {
|
|
1300
|
+
pageOptions.screenshot = true;
|
|
1301
|
+
}
|
|
1302
|
+
const cmd = `mindstudio scrape-url --url ${JSON.stringify(url)} --page-options ${JSON.stringify(JSON.stringify(pageOptions))} --no-meta`;
|
|
1303
|
+
return new Promise((resolve) => {
|
|
1304
|
+
exec2(
|
|
1305
|
+
cmd,
|
|
1306
|
+
{ timeout: 6e4, maxBuffer: 1024 * 1024 },
|
|
1307
|
+
(err, stdout, stderr) => {
|
|
1308
|
+
if (stdout.trim()) {
|
|
1309
|
+
resolve(stdout.trim());
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
if (err) {
|
|
1313
|
+
resolve(`Error: ${stderr.trim() || err.message}`);
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
resolve("(no response)");
|
|
1317
|
+
}
|
|
1318
|
+
);
|
|
1319
|
+
});
|
|
1115
1320
|
}
|
|
1116
1321
|
};
|
|
1117
1322
|
|
|
1118
|
-
// src/tools/
|
|
1119
|
-
|
|
1323
|
+
// src/tools/common/searchGoogle.ts
|
|
1324
|
+
import { exec as exec3 } from "child_process";
|
|
1325
|
+
var searchGoogleTool = {
|
|
1120
1326
|
definition: {
|
|
1121
|
-
name: "
|
|
1122
|
-
description: "
|
|
1327
|
+
name: "searchGoogle",
|
|
1328
|
+
description: "Search Google and return results. Use for research, finding documentation, looking up APIs, or any task where web search would help.",
|
|
1123
1329
|
inputSchema: {
|
|
1124
1330
|
type: "object",
|
|
1125
1331
|
properties: {
|
|
1126
|
-
|
|
1332
|
+
query: {
|
|
1127
1333
|
type: "string",
|
|
1128
|
-
description: "
|
|
1334
|
+
description: "The search query."
|
|
1129
1335
|
}
|
|
1130
1336
|
},
|
|
1131
|
-
required: ["
|
|
1337
|
+
required: ["query"]
|
|
1132
1338
|
}
|
|
1133
1339
|
},
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1340
|
+
async execute(input) {
|
|
1341
|
+
const query = input.query;
|
|
1342
|
+
const cmd = `mindstudio search-google --query ${JSON.stringify(query)} --export-type json --output-key results --no-meta`;
|
|
1343
|
+
return new Promise((resolve) => {
|
|
1344
|
+
exec3(
|
|
1345
|
+
cmd,
|
|
1346
|
+
{ timeout: 6e4, maxBuffer: 512 * 1024 },
|
|
1347
|
+
(err, stdout, stderr) => {
|
|
1348
|
+
if (stdout.trim()) {
|
|
1349
|
+
resolve(stdout.trim());
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
if (err) {
|
|
1353
|
+
resolve(`Error: ${stderr.trim() || err.message}`);
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
resolve("(no response)");
|
|
1357
|
+
}
|
|
1358
|
+
);
|
|
1359
|
+
});
|
|
1137
1360
|
}
|
|
1138
1361
|
};
|
|
1139
1362
|
|
|
1140
|
-
// src/tools/
|
|
1141
|
-
var
|
|
1363
|
+
// src/tools/common/setProjectName.ts
|
|
1364
|
+
var setProjectNameTool = {
|
|
1142
1365
|
definition: {
|
|
1143
|
-
name: "
|
|
1144
|
-
description:
|
|
1366
|
+
name: "setProjectName",
|
|
1367
|
+
description: `Set the project display name. Call this after intake once you have enough context to give the project a clear, descriptive name. Keep it short (2-4 words). Use the app's actual name if the user mentioned one, otherwise pick something descriptive ("Vendor Procurement App", "Recipe Manager").`,
|
|
1145
1368
|
inputSchema: {
|
|
1146
1369
|
type: "object",
|
|
1147
1370
|
properties: {
|
|
1148
|
-
|
|
1149
|
-
type: "string",
|
|
1150
|
-
description: "Explanation of what is about to happen and why confirmation is needed."
|
|
1151
|
-
},
|
|
1152
|
-
confirmLabel: {
|
|
1153
|
-
type: "string",
|
|
1154
|
-
description: 'Custom label for the confirm button (e.g., "Delete", "Reset Database"). Defaults to "Confirm".'
|
|
1155
|
-
},
|
|
1156
|
-
dismissLabel: {
|
|
1371
|
+
name: {
|
|
1157
1372
|
type: "string",
|
|
1158
|
-
description:
|
|
1373
|
+
description: "The project name."
|
|
1159
1374
|
}
|
|
1160
1375
|
},
|
|
1161
|
-
required: ["
|
|
1376
|
+
required: ["name"]
|
|
1162
1377
|
}
|
|
1163
1378
|
},
|
|
1164
1379
|
async execute() {
|
|
1165
|
-
return "
|
|
1380
|
+
return "ok";
|
|
1166
1381
|
}
|
|
1167
1382
|
};
|
|
1168
1383
|
|
|
@@ -1170,9 +1385,9 @@ var confirmDestructiveActionTool = {
|
|
|
1170
1385
|
import fs9 from "fs/promises";
|
|
1171
1386
|
var DEFAULT_MAX_LINES2 = 500;
|
|
1172
1387
|
function isBinary(buffer) {
|
|
1173
|
-
const
|
|
1174
|
-
for (let i = 0; i <
|
|
1175
|
-
if (
|
|
1388
|
+
const sample2 = buffer.subarray(0, 8192);
|
|
1389
|
+
for (let i = 0; i < sample2.length; i++) {
|
|
1390
|
+
if (sample2[i] === 0) {
|
|
1176
1391
|
return true;
|
|
1177
1392
|
}
|
|
1178
1393
|
}
|
|
@@ -1466,7 +1681,7 @@ ${unifiedDiff(input.path, content, updated)}`;
|
|
|
1466
1681
|
};
|
|
1467
1682
|
|
|
1468
1683
|
// src/tools/code/bash.ts
|
|
1469
|
-
import { exec } from "child_process";
|
|
1684
|
+
import { exec as exec4 } from "child_process";
|
|
1470
1685
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
1471
1686
|
var DEFAULT_MAX_LINES3 = 500;
|
|
1472
1687
|
var bashTool = {
|
|
@@ -1500,7 +1715,7 @@ var bashTool = {
|
|
|
1500
1715
|
const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES3;
|
|
1501
1716
|
const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
|
|
1502
1717
|
return new Promise((resolve) => {
|
|
1503
|
-
|
|
1718
|
+
exec4(
|
|
1504
1719
|
input.command,
|
|
1505
1720
|
{
|
|
1506
1721
|
timeout: timeoutMs,
|
|
@@ -1540,7 +1755,7 @@ var bashTool = {
|
|
|
1540
1755
|
};
|
|
1541
1756
|
|
|
1542
1757
|
// src/tools/code/grep.ts
|
|
1543
|
-
import { exec as
|
|
1758
|
+
import { exec as exec5 } from "child_process";
|
|
1544
1759
|
var DEFAULT_MAX = 50;
|
|
1545
1760
|
function formatResults(stdout, max) {
|
|
1546
1761
|
const lines = stdout.trim().split("\n");
|
|
@@ -1587,12 +1802,12 @@ var grepTool = {
|
|
|
1587
1802
|
const rgCmd = `rg -n --no-heading --max-count=${max}${globFlag} '${escaped}' ${searchPath}`;
|
|
1588
1803
|
const grepCmd = `grep -rn --max-count=${max} '${escaped}' ${searchPath} --include='*.ts' --include='*.tsx' --include='*.js' --include='*.json' --include='*.md'`;
|
|
1589
1804
|
return new Promise((resolve) => {
|
|
1590
|
-
|
|
1805
|
+
exec5(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
|
|
1591
1806
|
if (stdout?.trim()) {
|
|
1592
1807
|
resolve(formatResults(stdout, max));
|
|
1593
1808
|
return;
|
|
1594
1809
|
}
|
|
1595
|
-
|
|
1810
|
+
exec5(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
|
|
1596
1811
|
if (grepStdout?.trim()) {
|
|
1597
1812
|
resolve(formatResults(grepStdout, max));
|
|
1598
1813
|
} else {
|
|
@@ -1776,47 +1991,8 @@ var restartProcessTool = {
|
|
|
1776
1991
|
}
|
|
1777
1992
|
};
|
|
1778
1993
|
|
|
1779
|
-
// src/tools/code/
|
|
1780
|
-
|
|
1781
|
-
var askMindStudioSdkTool = {
|
|
1782
|
-
definition: {
|
|
1783
|
-
name: "askMindStudioSdk",
|
|
1784
|
-
description: "Ask the MindStudio SDK assistant about available actions, AI models, connectors, and integrations using natural language. Returns code examples with correct method signatures, model IDs, and config options. Always use this to verify correct SDK usage, especially model IDs and configuration options. Describe what you need, not what API methods you need; the assistant will figure out the right approach. This runs its own LLM call so it has a few seconds of latency; batch multiple questions into a single query.",
|
|
1785
|
-
inputSchema: {
|
|
1786
|
-
type: "object",
|
|
1787
|
-
properties: {
|
|
1788
|
-
query: {
|
|
1789
|
-
type: "string",
|
|
1790
|
-
description: "Natural language question about the SDK."
|
|
1791
|
-
}
|
|
1792
|
-
},
|
|
1793
|
-
required: ["query"]
|
|
1794
|
-
}
|
|
1795
|
-
},
|
|
1796
|
-
async execute(input) {
|
|
1797
|
-
const query = input.query;
|
|
1798
|
-
return new Promise((resolve) => {
|
|
1799
|
-
exec3(
|
|
1800
|
-
`mindstudio ask ${JSON.stringify(query)}`,
|
|
1801
|
-
{ timeout: 6e4, maxBuffer: 512 * 1024 },
|
|
1802
|
-
(err, stdout, stderr) => {
|
|
1803
|
-
if (stdout.trim()) {
|
|
1804
|
-
resolve(stdout.trim());
|
|
1805
|
-
return;
|
|
1806
|
-
}
|
|
1807
|
-
if (err) {
|
|
1808
|
-
resolve(`Error: ${stderr.trim() || err.message}`);
|
|
1809
|
-
return;
|
|
1810
|
-
}
|
|
1811
|
-
resolve("(no response)");
|
|
1812
|
-
}
|
|
1813
|
-
);
|
|
1814
|
-
});
|
|
1815
|
-
}
|
|
1816
|
-
};
|
|
1817
|
-
|
|
1818
|
-
// src/tools/code/runScenario.ts
|
|
1819
|
-
var runScenarioTool = {
|
|
1994
|
+
// src/tools/code/runScenario.ts
|
|
1995
|
+
var runScenarioTool = {
|
|
1820
1996
|
definition: {
|
|
1821
1997
|
name: "runScenario",
|
|
1822
1998
|
description: "Run a scenario to seed the dev database with test data. Truncates all tables first, then executes the seed function and impersonates the scenario roles. Blocks until complete. Scenario IDs are defined in mindstudio.json. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for details.",
|
|
@@ -1903,7 +2079,7 @@ async function runSubAgent(config) {
|
|
|
1903
2079
|
const toolCalls = [];
|
|
1904
2080
|
let stopReason = "end_turn";
|
|
1905
2081
|
try {
|
|
1906
|
-
for await (const event of
|
|
2082
|
+
for await (const event of streamChatWithRetry({
|
|
1907
2083
|
...apiConfig,
|
|
1908
2084
|
model,
|
|
1909
2085
|
system,
|
|
@@ -2121,6 +2297,419 @@ var browserAutomationTool = {
|
|
|
2121
2297
|
}
|
|
2122
2298
|
};
|
|
2123
2299
|
|
|
2300
|
+
// src/subagents/designExpert/tools.ts
|
|
2301
|
+
import { exec as exec6 } from "child_process";
|
|
2302
|
+
var DESIGN_REFERENCE_PROMPT = `Analyze this website/app screenshot as a design reference. Assess:
|
|
2303
|
+
1) Mood/aesthetic
|
|
2304
|
+
2) Color palette with approximate hex values and palette strategy
|
|
2305
|
+
3) Typography style
|
|
2306
|
+
4) Layout composition (symmetric/asymmetric, grid structure, whitespace usage, content density)
|
|
2307
|
+
5) What makes it distinctive and interesting vs generic AI-generated interfaces
|
|
2308
|
+
Be specific and concise.`;
|
|
2309
|
+
var DESIGN_RESEARCH_TOOLS = [
|
|
2310
|
+
{
|
|
2311
|
+
name: "searchGoogle",
|
|
2312
|
+
description: "Search Google for web results. Use for finding design inspiration, font recommendations, UI patterns, real products in a domain, and reference material.",
|
|
2313
|
+
inputSchema: {
|
|
2314
|
+
type: "object",
|
|
2315
|
+
properties: {
|
|
2316
|
+
query: {
|
|
2317
|
+
type: "string",
|
|
2318
|
+
description: "The search query."
|
|
2319
|
+
}
|
|
2320
|
+
},
|
|
2321
|
+
required: ["query"]
|
|
2322
|
+
}
|
|
2323
|
+
},
|
|
2324
|
+
{
|
|
2325
|
+
name: "fetchUrl",
|
|
2326
|
+
description: "Fetch the content of a web page as markdown. Optionally capture a screenshot to see the visual design. Use to analyze reference sites, read font specimen pages, or extract design details.",
|
|
2327
|
+
inputSchema: {
|
|
2328
|
+
type: "object",
|
|
2329
|
+
properties: {
|
|
2330
|
+
url: {
|
|
2331
|
+
type: "string",
|
|
2332
|
+
description: "The URL to fetch."
|
|
2333
|
+
},
|
|
2334
|
+
screenshot: {
|
|
2335
|
+
type: "boolean",
|
|
2336
|
+
description: "Capture a screenshot of the page. Use when you need to see the visual design, not just the text."
|
|
2337
|
+
}
|
|
2338
|
+
},
|
|
2339
|
+
required: ["url"]
|
|
2340
|
+
}
|
|
2341
|
+
},
|
|
2342
|
+
{
|
|
2343
|
+
name: "analyzeImage",
|
|
2344
|
+
description: 'Analyze an image using a vision model with a custom prompt. Use when you have a specific question about an image (e.g., "what colors dominate this image?", "describe the typography choices").',
|
|
2345
|
+
inputSchema: {
|
|
2346
|
+
type: "object",
|
|
2347
|
+
properties: {
|
|
2348
|
+
prompt: {
|
|
2349
|
+
type: "string",
|
|
2350
|
+
description: "What to analyze or extract from the image."
|
|
2351
|
+
},
|
|
2352
|
+
imageUrl: {
|
|
2353
|
+
type: "string",
|
|
2354
|
+
description: "URL of the image to analyze."
|
|
2355
|
+
}
|
|
2356
|
+
},
|
|
2357
|
+
required: ["prompt", "imageUrl"]
|
|
2358
|
+
}
|
|
2359
|
+
},
|
|
2360
|
+
{
|
|
2361
|
+
name: "analyzeDesignReference",
|
|
2362
|
+
description: "Analyze a screenshot or design image for design inspiration. Returns a structured analysis: mood/aesthetic, color palette with hex values, typography style, layout composition, and what makes it distinctive. Use this instead of analyzeImage when studying a design reference.",
|
|
2363
|
+
inputSchema: {
|
|
2364
|
+
type: "object",
|
|
2365
|
+
properties: {
|
|
2366
|
+
imageUrl: {
|
|
2367
|
+
type: "string",
|
|
2368
|
+
description: "URL of the screenshot or design image to analyze."
|
|
2369
|
+
}
|
|
2370
|
+
},
|
|
2371
|
+
required: ["imageUrl"]
|
|
2372
|
+
}
|
|
2373
|
+
},
|
|
2374
|
+
{
|
|
2375
|
+
name: "screenshotAndAnalyze",
|
|
2376
|
+
description: "Screenshot a live URL and analyze it in one step. If no prompt is provided, performs a full design reference analysis (mood, color, typography, layout, distinctiveness). Provide a custom prompt to ask a specific question about the visual design instead.",
|
|
2377
|
+
inputSchema: {
|
|
2378
|
+
type: "object",
|
|
2379
|
+
properties: {
|
|
2380
|
+
url: {
|
|
2381
|
+
type: "string",
|
|
2382
|
+
description: "The URL to screenshot."
|
|
2383
|
+
},
|
|
2384
|
+
prompt: {
|
|
2385
|
+
type: "string",
|
|
2386
|
+
description: "Optional custom analysis prompt. If omitted, performs the standard design reference analysis."
|
|
2387
|
+
}
|
|
2388
|
+
},
|
|
2389
|
+
required: ["url"]
|
|
2390
|
+
}
|
|
2391
|
+
},
|
|
2392
|
+
{
|
|
2393
|
+
name: "searchStockPhotos",
|
|
2394
|
+
description: 'Search Pexels for stock photos. Returns image URLs with descriptions. Use concrete, descriptive queries ("person working at laptop in modern office" not "business").',
|
|
2395
|
+
inputSchema: {
|
|
2396
|
+
type: "object",
|
|
2397
|
+
properties: {
|
|
2398
|
+
query: {
|
|
2399
|
+
type: "string",
|
|
2400
|
+
description: "What kind of photo to search for."
|
|
2401
|
+
}
|
|
2402
|
+
},
|
|
2403
|
+
required: ["query"]
|
|
2404
|
+
}
|
|
2405
|
+
},
|
|
2406
|
+
{
|
|
2407
|
+
name: "searchProductScreenshots",
|
|
2408
|
+
description: 'Search for screenshots of real products and apps. Use to find what existing products look like ("stripe dashboard", "linear app", "notion workspace"). Returns image results of actual product UI. Use this for layout and design research on real products, NOT for abstract design inspiration.',
|
|
2409
|
+
inputSchema: {
|
|
2410
|
+
type: "object",
|
|
2411
|
+
properties: {
|
|
2412
|
+
product: {
|
|
2413
|
+
type: "string",
|
|
2414
|
+
description: 'The product or app to find screenshots of (e.g., "stripe dashboard", "figma editor", "mercury banking app").'
|
|
2415
|
+
}
|
|
2416
|
+
},
|
|
2417
|
+
required: ["product"]
|
|
2418
|
+
}
|
|
2419
|
+
},
|
|
2420
|
+
{
|
|
2421
|
+
name: "generateImages",
|
|
2422
|
+
description: "Generate images using AI (Seedream). Returns CDN URLs. Produces high-quality results for both photorealistic images and abstract/creative visuals. Pass multiple prompts to generate in parallel.",
|
|
2423
|
+
inputSchema: {
|
|
2424
|
+
type: "object",
|
|
2425
|
+
properties: {
|
|
2426
|
+
prompts: {
|
|
2427
|
+
type: "array",
|
|
2428
|
+
items: {
|
|
2429
|
+
type: "string"
|
|
2430
|
+
},
|
|
2431
|
+
description: "One or more image generation prompts. Be detailed: describe style, mood, composition, colors. Multiple prompts run in parallel."
|
|
2432
|
+
},
|
|
2433
|
+
width: {
|
|
2434
|
+
type: "number",
|
|
2435
|
+
description: "Image width in pixels. Default 2048. Range: 2048-4096."
|
|
2436
|
+
},
|
|
2437
|
+
height: {
|
|
2438
|
+
type: "number",
|
|
2439
|
+
description: "Image height in pixels. Default 2048. Range: 2048-4096."
|
|
2440
|
+
}
|
|
2441
|
+
},
|
|
2442
|
+
required: ["prompts"]
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
];
|
|
2446
|
+
function runCli(cmd) {
|
|
2447
|
+
return new Promise((resolve) => {
|
|
2448
|
+
exec6(
|
|
2449
|
+
cmd,
|
|
2450
|
+
{ timeout: 6e4, maxBuffer: 1024 * 1024 },
|
|
2451
|
+
(err, stdout, stderr) => {
|
|
2452
|
+
if (stdout.trim()) {
|
|
2453
|
+
resolve(stdout.trim());
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
if (err) {
|
|
2457
|
+
resolve(`Error: ${stderr.trim() || err.message}`);
|
|
2458
|
+
return;
|
|
2459
|
+
}
|
|
2460
|
+
resolve("(no response)");
|
|
2461
|
+
}
|
|
2462
|
+
);
|
|
2463
|
+
});
|
|
2464
|
+
}
|
|
2465
|
+
async function executeDesignTool(name, input) {
|
|
2466
|
+
switch (name) {
|
|
2467
|
+
case "searchGoogle":
|
|
2468
|
+
return runCli(
|
|
2469
|
+
`mindstudio search-google --query ${JSON.stringify(input.query)} --export-type json --output-key results --no-meta`
|
|
2470
|
+
);
|
|
2471
|
+
case "fetchUrl": {
|
|
2472
|
+
const pageOptions = { onlyMainContent: true };
|
|
2473
|
+
if (input.screenshot) {
|
|
2474
|
+
pageOptions.screenshot = true;
|
|
2475
|
+
}
|
|
2476
|
+
return runCli(
|
|
2477
|
+
`mindstudio scrape-url --url ${JSON.stringify(input.url)} --page-options ${JSON.stringify(JSON.stringify(pageOptions))} --no-meta`
|
|
2478
|
+
);
|
|
2479
|
+
}
|
|
2480
|
+
case "analyzeImage":
|
|
2481
|
+
return runCli(
|
|
2482
|
+
`mindstudio analyze-image --prompt ${JSON.stringify(input.prompt)} --image-url ${JSON.stringify(input.imageUrl)} --no-meta`
|
|
2483
|
+
);
|
|
2484
|
+
case "analyzeDesignReference":
|
|
2485
|
+
return runCli(
|
|
2486
|
+
`mindstudio analyze-image --prompt ${JSON.stringify(DESIGN_REFERENCE_PROMPT)} --image-url ${JSON.stringify(input.imageUrl)} --no-meta`
|
|
2487
|
+
);
|
|
2488
|
+
case "screenshotAndAnalyze": {
|
|
2489
|
+
const screenshotResult = await runCli(
|
|
2490
|
+
`mindstudio scrape-url --url ${JSON.stringify(input.url)} --page-options ${JSON.stringify(JSON.stringify({ onlyMainContent: true, screenshot: true }))} --no-meta`
|
|
2491
|
+
);
|
|
2492
|
+
const screenshotMatch = screenshotResult.match(
|
|
2493
|
+
/https:\/\/[^\s"']+(?:\.png|\.jpg|\.jpeg|\.webp|screenshot[^\s"']*)/i
|
|
2494
|
+
);
|
|
2495
|
+
if (!screenshotMatch) {
|
|
2496
|
+
try {
|
|
2497
|
+
const parsed = JSON.parse(screenshotResult);
|
|
2498
|
+
const ssUrl = parsed.screenshot || parsed.screenshotUrl || parsed.content?.screenshotUrl;
|
|
2499
|
+
if (ssUrl) {
|
|
2500
|
+
const analysisPrompt2 = input.prompt || DESIGN_REFERENCE_PROMPT;
|
|
2501
|
+
const analysis2 = await runCli(
|
|
2502
|
+
`mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt2)} --image-url ${JSON.stringify(ssUrl)} --no-meta`
|
|
2503
|
+
);
|
|
2504
|
+
return `Screenshot: ${ssUrl}
|
|
2505
|
+
|
|
2506
|
+
${analysis2}`;
|
|
2507
|
+
}
|
|
2508
|
+
} catch {
|
|
2509
|
+
}
|
|
2510
|
+
return `Fetched ${input.url} but could not extract screenshot URL.
|
|
2511
|
+
|
|
2512
|
+
Page content:
|
|
2513
|
+
${screenshotResult}`;
|
|
2514
|
+
}
|
|
2515
|
+
const analysisPrompt = input.prompt || DESIGN_REFERENCE_PROMPT;
|
|
2516
|
+
const analysis = await runCli(
|
|
2517
|
+
`mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(screenshotMatch[0])} --no-meta`
|
|
2518
|
+
);
|
|
2519
|
+
return `Screenshot: ${screenshotMatch[0]}
|
|
2520
|
+
|
|
2521
|
+
${analysis}`;
|
|
2522
|
+
}
|
|
2523
|
+
case "searchStockPhotos": {
|
|
2524
|
+
const encodedQuery = encodeURIComponent(input.query);
|
|
2525
|
+
return runCli(
|
|
2526
|
+
`mindstudio scrape-url --url "https://www.pexels.com/search/${encodedQuery}/" --page-options '{"onlyMainContent": true}' --no-meta`
|
|
2527
|
+
);
|
|
2528
|
+
}
|
|
2529
|
+
case "searchProductScreenshots": {
|
|
2530
|
+
const query = `${input.product} product screenshot UI 2026`;
|
|
2531
|
+
return runCli(
|
|
2532
|
+
`mindstudio search-google-images --query ${JSON.stringify(query)} --export-type json --output-key images --no-meta`
|
|
2533
|
+
);
|
|
2534
|
+
}
|
|
2535
|
+
case "generateImages": {
|
|
2536
|
+
const prompts = input.prompts;
|
|
2537
|
+
const width = input.width || 2048;
|
|
2538
|
+
const height = input.height || 2048;
|
|
2539
|
+
if (prompts.length === 1) {
|
|
2540
|
+
const step = JSON.stringify({
|
|
2541
|
+
prompt: prompts[0],
|
|
2542
|
+
imageModelOverride: {
|
|
2543
|
+
model: "seedream-4.5",
|
|
2544
|
+
config: { width, height }
|
|
2545
|
+
}
|
|
2546
|
+
});
|
|
2547
|
+
return runCli(
|
|
2548
|
+
`mindstudio generate-image '${step}' --output-key imageUrl --no-meta`
|
|
2549
|
+
);
|
|
2550
|
+
}
|
|
2551
|
+
const steps = prompts.map((prompt) => ({
|
|
2552
|
+
stepType: "generateImage",
|
|
2553
|
+
step: {
|
|
2554
|
+
prompt,
|
|
2555
|
+
imageModelOverride: {
|
|
2556
|
+
model: "seedream-4.5",
|
|
2557
|
+
config: { width, height }
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
}));
|
|
2561
|
+
return runCli(`mindstudio batch '${JSON.stringify(steps)}' --no-meta`);
|
|
2562
|
+
}
|
|
2563
|
+
default:
|
|
2564
|
+
return `Error: unknown tool "${name}"`;
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
// src/subagents/designExpert/prompt.ts
|
|
2569
|
+
import fs14 from "fs";
|
|
2570
|
+
import path8 from "path";
|
|
2571
|
+
var base2 = import.meta.dirname ?? path8.dirname(new URL(import.meta.url).pathname);
|
|
2572
|
+
function resolvePath(filename) {
|
|
2573
|
+
const local2 = path8.join(base2, filename);
|
|
2574
|
+
return fs14.existsSync(local2) ? local2 : path8.join(base2, "subagents", "designExpert", filename);
|
|
2575
|
+
}
|
|
2576
|
+
function readFile(filename) {
|
|
2577
|
+
return fs14.readFileSync(resolvePath(filename), "utf-8").trim();
|
|
2578
|
+
}
|
|
2579
|
+
function readJson(filename, fallback) {
|
|
2580
|
+
try {
|
|
2581
|
+
return JSON.parse(fs14.readFileSync(resolvePath(filename), "utf-8"));
|
|
2582
|
+
} catch {
|
|
2583
|
+
return fallback;
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
var RUNTIME_PLACEHOLDERS = /* @__PURE__ */ new Set([
|
|
2587
|
+
"fonts_to_consider",
|
|
2588
|
+
"inspiration_images"
|
|
2589
|
+
]);
|
|
2590
|
+
var PROMPT_TEMPLATE = readFile("prompt.md").replace(/\{\{([^}]+)\}\}/g, (match, key) => {
|
|
2591
|
+
const k = key.trim();
|
|
2592
|
+
return RUNTIME_PLACEHOLDERS.has(k) ? match : readFile(k);
|
|
2593
|
+
}).replace(/\n{3,}/g, "\n\n");
|
|
2594
|
+
var fontData = readJson("data/fonts.json", {
|
|
2595
|
+
cssUrlPattern: "",
|
|
2596
|
+
fonts: [],
|
|
2597
|
+
pairings: []
|
|
2598
|
+
});
|
|
2599
|
+
var inspirationImages = readJson("data/inspiration.json", {
|
|
2600
|
+
images: []
|
|
2601
|
+
}).images;
|
|
2602
|
+
function sample(arr, n) {
|
|
2603
|
+
if (arr.length <= n) {
|
|
2604
|
+
return [...arr];
|
|
2605
|
+
}
|
|
2606
|
+
const copy = [...arr];
|
|
2607
|
+
for (let i = copy.length - 1; i > 0; i--) {
|
|
2608
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
2609
|
+
[copy[i], copy[j]] = [copy[j], copy[i]];
|
|
2610
|
+
}
|
|
2611
|
+
return copy.slice(0, n);
|
|
2612
|
+
}
|
|
2613
|
+
function getDesignResearchPrompt() {
|
|
2614
|
+
const fonts = sample(fontData.fonts, 30);
|
|
2615
|
+
const pairings = sample(fontData.pairings, 20);
|
|
2616
|
+
const images = sample(inspirationImages, 40);
|
|
2617
|
+
const fontList = fonts.map((f) => {
|
|
2618
|
+
const tags = f.tags.length ? ` (${f.tags.join(", ")})` : "";
|
|
2619
|
+
let cssInfo = "";
|
|
2620
|
+
if (f.source === "fontshare") {
|
|
2621
|
+
cssInfo = ` CSS: ${fontData.cssUrlPattern.replace("{slug}", f.slug).replace("{weights}", f.weights.join(","))}`;
|
|
2622
|
+
} else if (f.cssUrl) {
|
|
2623
|
+
cssInfo = ` CSS: ${f.cssUrl}`;
|
|
2624
|
+
} else if (f.source === "open-foundry") {
|
|
2625
|
+
cssInfo = " (self-host required)";
|
|
2626
|
+
}
|
|
2627
|
+
return `- **${f.name}** \u2014 ${f.category}${tags}. Weights: ${f.weights.join(", ")}.${f.variable ? " Variable." : ""}${f.italics ? " Has italics." : ""}${cssInfo}`;
|
|
2628
|
+
}).join("\n");
|
|
2629
|
+
const pairingList = pairings.map(
|
|
2630
|
+
(p) => `- **${p.heading.font}** (${p.heading.weight}) heading + **${p.body.font}** (${p.body.weight}) body`
|
|
2631
|
+
).join("\n");
|
|
2632
|
+
const fontsSection = fonts.length ? `<fonts_to_consider>
|
|
2633
|
+
## Fonts to consider
|
|
2634
|
+
|
|
2635
|
+
A random sample from Fontshare, Open Foundry, and Google Fonts. Use these as starting points for font selection.
|
|
2636
|
+
CSS URL pattern: ${fontData.cssUrlPattern}
|
|
2637
|
+
|
|
2638
|
+
${fontList}
|
|
2639
|
+
|
|
2640
|
+
### Suggested pairings
|
|
2641
|
+
|
|
2642
|
+
${pairingList}
|
|
2643
|
+
</fonts_to_consider>` : "";
|
|
2644
|
+
const imageList = images.map((img) => `- ${img.analysis}`).join("\n\n");
|
|
2645
|
+
const inspirationSection = images.length ? `<inspiration_images>
|
|
2646
|
+
## Design inspiration
|
|
2647
|
+
|
|
2648
|
+
A random sample of pre-analyzed design references. Use these observations to inform your recommendations.
|
|
2649
|
+
|
|
2650
|
+
${imageList}
|
|
2651
|
+
</inspiration_images>` : "";
|
|
2652
|
+
return PROMPT_TEMPLATE.replace("{{fonts_to_consider}}", fontsSection).replace(
|
|
2653
|
+
"{{inspiration_images}}",
|
|
2654
|
+
inspirationSection
|
|
2655
|
+
);
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
// src/subagents/designExpert/index.ts
|
|
2659
|
+
var DESCRIPTION = `
|
|
2660
|
+
A dedicated visual design expert. You have a lot on your plate as a coding agent, and design is a specialized skill \u2014 delegate visual design questions here rather than making those decisions yourself. This agent has curated font catalogs, color theory knowledge, access to design inspiration galleries, and strong opinions about what looks good. It can answer from expertise alone or research the web when needed.
|
|
2661
|
+
|
|
2662
|
+
The visual design expert can be used for all things visual design, from quick questions to comprehensive plans:
|
|
2663
|
+
- Font selection and pairings ("suggest fonts for a <x> app")
|
|
2664
|
+
- Color palettes from a mood, domain, or seed color ("earthy tones for a <x> brand")
|
|
2665
|
+
- Gradient, animation, and visual effect recommendations
|
|
2666
|
+
- Layout and composition ideas that go beyond generic AI defaults
|
|
2667
|
+
- Analyzing a reference site or screenshot for design insights (it can take screenshots and do research on its own)
|
|
2668
|
+
- Sourcing imagery: stock photos, AI-generated images, or product screenshots for reference
|
|
2669
|
+
- Icon recommendations
|
|
2670
|
+
- Proposing full visual directions during intake
|
|
2671
|
+
|
|
2672
|
+
**How to write the task:**
|
|
2673
|
+
Include context about the app \u2014 what it does, who uses it, what mood or feeling the interface should convey. More context produces better results. For quick questions ("three font pairings for a <x> app"), brief is fine. You can ask for multiple topics, multiple options, etc.
|
|
2674
|
+
|
|
2675
|
+
**What it returns:**
|
|
2676
|
+
Concrete resources: hex values, font names with CSS URLs, image URLs, layout descriptions. Use the results directly in brand spec files or as guidance when building the interface.
|
|
2677
|
+
`.trim();
|
|
2678
|
+
var designExpertTool = {
|
|
2679
|
+
definition: {
|
|
2680
|
+
name: "visualDesignExpert",
|
|
2681
|
+
description: DESCRIPTION,
|
|
2682
|
+
inputSchema: {
|
|
2683
|
+
type: "object",
|
|
2684
|
+
properties: {
|
|
2685
|
+
task: {
|
|
2686
|
+
type: "string",
|
|
2687
|
+
description: "What you need, in natural language. Include context about the app when relevant."
|
|
2688
|
+
}
|
|
2689
|
+
},
|
|
2690
|
+
required: ["task"]
|
|
2691
|
+
}
|
|
2692
|
+
},
|
|
2693
|
+
async execute(input, context) {
|
|
2694
|
+
if (!context) {
|
|
2695
|
+
return "Error: design research requires execution context";
|
|
2696
|
+
}
|
|
2697
|
+
return runSubAgent({
|
|
2698
|
+
system: getDesignResearchPrompt(),
|
|
2699
|
+
task: input.task,
|
|
2700
|
+
tools: DESIGN_RESEARCH_TOOLS,
|
|
2701
|
+
externalTools: /* @__PURE__ */ new Set(),
|
|
2702
|
+
executeTool: executeDesignTool,
|
|
2703
|
+
apiConfig: context.apiConfig,
|
|
2704
|
+
model: context.model,
|
|
2705
|
+
signal: context.signal,
|
|
2706
|
+
parentToolId: context.toolCallId,
|
|
2707
|
+
onEvent: context.onEvent,
|
|
2708
|
+
resolveExternalTool: context.resolveExternalTool
|
|
2709
|
+
});
|
|
2710
|
+
}
|
|
2711
|
+
};
|
|
2712
|
+
|
|
2124
2713
|
// src/tools/index.ts
|
|
2125
2714
|
function getSpecTools() {
|
|
2126
2715
|
return [readSpecTool, writeSpecTool, editSpecTool, listSpecFilesTool];
|
|
@@ -2135,7 +2724,6 @@ function getCodeTools() {
|
|
|
2135
2724
|
globTool,
|
|
2136
2725
|
listDirTool,
|
|
2137
2726
|
editsFinishedTool,
|
|
2138
|
-
askMindStudioSdkTool,
|
|
2139
2727
|
runScenarioTool,
|
|
2140
2728
|
runMethodTool,
|
|
2141
2729
|
screenshotTool,
|
|
@@ -2150,7 +2738,12 @@ function getCommonTools() {
|
|
|
2150
2738
|
return [
|
|
2151
2739
|
setProjectOnboardingStateTool,
|
|
2152
2740
|
promptUserTool,
|
|
2153
|
-
confirmDestructiveActionTool
|
|
2741
|
+
confirmDestructiveActionTool,
|
|
2742
|
+
askMindStudioSdkTool,
|
|
2743
|
+
fetchUrlTool,
|
|
2744
|
+
searchGoogleTool,
|
|
2745
|
+
setProjectNameTool,
|
|
2746
|
+
designExpertTool
|
|
2154
2747
|
];
|
|
2155
2748
|
}
|
|
2156
2749
|
function getPostOnboardingTools() {
|
|
@@ -2197,11 +2790,11 @@ function executeTool(name, input, context) {
|
|
|
2197
2790
|
}
|
|
2198
2791
|
|
|
2199
2792
|
// src/session.ts
|
|
2200
|
-
import
|
|
2793
|
+
import fs15 from "fs";
|
|
2201
2794
|
var SESSION_FILE = ".remy-session.json";
|
|
2202
2795
|
function loadSession(state) {
|
|
2203
2796
|
try {
|
|
2204
|
-
const raw =
|
|
2797
|
+
const raw = fs15.readFileSync(SESSION_FILE, "utf-8");
|
|
2205
2798
|
const data = JSON.parse(raw);
|
|
2206
2799
|
if (Array.isArray(data.messages) && data.messages.length > 0) {
|
|
2207
2800
|
state.messages = sanitizeMessages(data.messages);
|
|
@@ -2243,7 +2836,7 @@ function sanitizeMessages(messages) {
|
|
|
2243
2836
|
}
|
|
2244
2837
|
function saveSession(state) {
|
|
2245
2838
|
try {
|
|
2246
|
-
|
|
2839
|
+
fs15.writeFileSync(
|
|
2247
2840
|
SESSION_FILE,
|
|
2248
2841
|
JSON.stringify({ messages: state.messages }, null, 2),
|
|
2249
2842
|
"utf-8"
|
|
@@ -2254,7 +2847,7 @@ function saveSession(state) {
|
|
|
2254
2847
|
function clearSession(state) {
|
|
2255
2848
|
state.messages = [];
|
|
2256
2849
|
try {
|
|
2257
|
-
|
|
2850
|
+
fs15.unlinkSync(SESSION_FILE);
|
|
2258
2851
|
} catch {
|
|
2259
2852
|
}
|
|
2260
2853
|
}
|
|
@@ -2422,6 +3015,104 @@ function parsePartialJson(jsonString) {
|
|
|
2422
3015
|
return parseAny();
|
|
2423
3016
|
}
|
|
2424
3017
|
|
|
3018
|
+
// src/statusWatcher.ts
|
|
3019
|
+
function startStatusWatcher(config) {
|
|
3020
|
+
const { apiConfig, getContext, onStatus, interval = 3e3, signal } = config;
|
|
3021
|
+
let lastLabel = "";
|
|
3022
|
+
let inflight = false;
|
|
3023
|
+
const url = `${apiConfig.baseUrl}/_internal/v2/agent/remy/generate-status`;
|
|
3024
|
+
async function tick() {
|
|
3025
|
+
if (signal?.aborted || inflight) {
|
|
3026
|
+
return;
|
|
3027
|
+
}
|
|
3028
|
+
inflight = true;
|
|
3029
|
+
try {
|
|
3030
|
+
const ctx = getContext();
|
|
3031
|
+
if (!ctx.assistantText && !ctx.lastToolName) {
|
|
3032
|
+
log.debug("Status watcher: no context, skipping");
|
|
3033
|
+
return;
|
|
3034
|
+
}
|
|
3035
|
+
log.debug("Status watcher: requesting label", {
|
|
3036
|
+
textLength: ctx.assistantText.length,
|
|
3037
|
+
lastToolName: ctx.lastToolName
|
|
3038
|
+
});
|
|
3039
|
+
const res = await fetch(url, {
|
|
3040
|
+
method: "POST",
|
|
3041
|
+
headers: {
|
|
3042
|
+
"Content-Type": "application/json",
|
|
3043
|
+
Authorization: `Bearer ${apiConfig.apiKey}`
|
|
3044
|
+
},
|
|
3045
|
+
body: JSON.stringify({
|
|
3046
|
+
assistantText: ctx.assistantText.slice(-500),
|
|
3047
|
+
lastToolName: ctx.lastToolName,
|
|
3048
|
+
lastToolResult: ctx.lastToolResult?.slice(-200)
|
|
3049
|
+
}),
|
|
3050
|
+
signal
|
|
3051
|
+
});
|
|
3052
|
+
if (!res.ok) {
|
|
3053
|
+
log.debug("Status watcher: endpoint returned non-ok", {
|
|
3054
|
+
status: res.status
|
|
3055
|
+
});
|
|
3056
|
+
return;
|
|
3057
|
+
}
|
|
3058
|
+
const data = await res.json();
|
|
3059
|
+
if (!data.label) {
|
|
3060
|
+
log.debug("Status watcher: no label in response");
|
|
3061
|
+
return;
|
|
3062
|
+
}
|
|
3063
|
+
if (data.label === lastLabel) {
|
|
3064
|
+
log.debug("Status watcher: duplicate label, skipping", {
|
|
3065
|
+
label: data.label
|
|
3066
|
+
});
|
|
3067
|
+
return;
|
|
3068
|
+
}
|
|
3069
|
+
lastLabel = data.label;
|
|
3070
|
+
log.debug("Status watcher: emitting", { label: data.label });
|
|
3071
|
+
onStatus(data.label);
|
|
3072
|
+
} catch (err) {
|
|
3073
|
+
log.debug("Status watcher: error", { error: err?.message ?? "unknown" });
|
|
3074
|
+
} finally {
|
|
3075
|
+
inflight = false;
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
const timer = setInterval(tick, interval);
|
|
3079
|
+
tick().catch(() => {
|
|
3080
|
+
});
|
|
3081
|
+
log.debug("Status watcher started", { interval });
|
|
3082
|
+
return {
|
|
3083
|
+
stop() {
|
|
3084
|
+
clearInterval(timer);
|
|
3085
|
+
log.debug("Status watcher stopped");
|
|
3086
|
+
}
|
|
3087
|
+
};
|
|
3088
|
+
}
|
|
3089
|
+
|
|
3090
|
+
// src/errors.ts
|
|
3091
|
+
var patterns = [
|
|
3092
|
+
[
|
|
3093
|
+
/Network error/i,
|
|
3094
|
+
"Lost connection to the server. Please check your internet connection and try again."
|
|
3095
|
+
],
|
|
3096
|
+
[
|
|
3097
|
+
/HTTP 429|rate limit/i,
|
|
3098
|
+
"Too many requests. Please wait a moment and try again."
|
|
3099
|
+
],
|
|
3100
|
+
[/HTTP 40[13]/i, "Authentication failed. Please check your API key."],
|
|
3101
|
+
[
|
|
3102
|
+
/HTTP 5\d\d/i,
|
|
3103
|
+
"The AI service is temporarily unavailable. Please try again."
|
|
3104
|
+
],
|
|
3105
|
+
[/Stream stalled/i, "The connection was interrupted. Please try again."]
|
|
3106
|
+
];
|
|
3107
|
+
function friendlyError(raw) {
|
|
3108
|
+
for (const [pattern, message] of patterns) {
|
|
3109
|
+
if (pattern.test(raw)) {
|
|
3110
|
+
return message;
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
return `Something went wrong: ${raw}`;
|
|
3114
|
+
}
|
|
3115
|
+
|
|
2425
3116
|
// src/agent.ts
|
|
2426
3117
|
var EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
|
|
2427
3118
|
"promptUser",
|
|
@@ -2434,7 +3125,8 @@ var EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
|
|
|
2434
3125
|
"runScenario",
|
|
2435
3126
|
"runMethod",
|
|
2436
3127
|
"browserCommand",
|
|
2437
|
-
"screenshot"
|
|
3128
|
+
"screenshot",
|
|
3129
|
+
"setProjectName"
|
|
2438
3130
|
]);
|
|
2439
3131
|
function createAgentState() {
|
|
2440
3132
|
return { messages: [] };
|
|
@@ -2464,7 +3156,8 @@ async function runTurn(params) {
|
|
|
2464
3156
|
}
|
|
2465
3157
|
});
|
|
2466
3158
|
onEvent({ type: "turn_started" });
|
|
2467
|
-
const
|
|
3159
|
+
const cleanMessage = userMessage.replace(/^@@automated::[^@]*@@/, "");
|
|
3160
|
+
const userMsg = { role: "user", content: cleanMessage };
|
|
2468
3161
|
if (hidden) {
|
|
2469
3162
|
userMsg.hidden = true;
|
|
2470
3163
|
}
|
|
@@ -2558,15 +3251,34 @@ async function runTurn(params) {
|
|
|
2558
3251
|
onEvent({ type: "tool_input_delta", id, name, result: content });
|
|
2559
3252
|
}
|
|
2560
3253
|
}
|
|
3254
|
+
const statusWatcher = startStatusWatcher({
|
|
3255
|
+
apiConfig,
|
|
3256
|
+
getContext: () => ({
|
|
3257
|
+
assistantText: assistantText.slice(-500),
|
|
3258
|
+
lastToolName: toolCalls.at(-1)?.name
|
|
3259
|
+
}),
|
|
3260
|
+
onStatus: (label) => onEvent({ type: "status", message: label }),
|
|
3261
|
+
signal
|
|
3262
|
+
});
|
|
2561
3263
|
try {
|
|
2562
|
-
for await (const event of
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
3264
|
+
for await (const event of streamChatWithRetry(
|
|
3265
|
+
{
|
|
3266
|
+
...apiConfig,
|
|
3267
|
+
model,
|
|
3268
|
+
system,
|
|
3269
|
+
messages: state.messages,
|
|
3270
|
+
tools,
|
|
3271
|
+
signal
|
|
3272
|
+
},
|
|
3273
|
+
{
|
|
3274
|
+
onRetry: (attempt) => {
|
|
3275
|
+
onEvent({
|
|
3276
|
+
type: "status",
|
|
3277
|
+
message: `Lost connection, retrying (attempt ${attempt + 2} of 3)...`
|
|
3278
|
+
});
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
)) {
|
|
2570
3282
|
if (signal?.aborted) {
|
|
2571
3283
|
break;
|
|
2572
3284
|
}
|
|
@@ -2634,7 +3346,7 @@ async function runTurn(params) {
|
|
|
2634
3346
|
stopReason = event.stopReason;
|
|
2635
3347
|
break;
|
|
2636
3348
|
case "error":
|
|
2637
|
-
onEvent({ type: "error", error: event.error });
|
|
3349
|
+
onEvent({ type: "error", error: friendlyError(event.error) });
|
|
2638
3350
|
return;
|
|
2639
3351
|
}
|
|
2640
3352
|
}
|
|
@@ -2643,6 +3355,8 @@ async function runTurn(params) {
|
|
|
2643
3355
|
} else {
|
|
2644
3356
|
throw err;
|
|
2645
3357
|
}
|
|
3358
|
+
} finally {
|
|
3359
|
+
statusWatcher.stop();
|
|
2646
3360
|
}
|
|
2647
3361
|
if (signal?.aborted) {
|
|
2648
3362
|
if (assistantText) {
|
|
@@ -2670,6 +3384,15 @@ async function runTurn(params) {
|
|
|
2670
3384
|
count: toolCalls.length,
|
|
2671
3385
|
tools: toolCalls.map((tc) => tc.name)
|
|
2672
3386
|
});
|
|
3387
|
+
const toolStatusWatcher = startStatusWatcher({
|
|
3388
|
+
apiConfig,
|
|
3389
|
+
getContext: () => ({
|
|
3390
|
+
assistantText: assistantText.slice(-500),
|
|
3391
|
+
lastToolName: toolCalls.map((tc) => tc.name).join(", ")
|
|
3392
|
+
}),
|
|
3393
|
+
onStatus: (label) => onEvent({ type: "status", message: label }),
|
|
3394
|
+
signal
|
|
3395
|
+
});
|
|
2673
3396
|
const results = await Promise.all(
|
|
2674
3397
|
toolCalls.map(async (tc) => {
|
|
2675
3398
|
if (signal?.aborted) {
|
|
@@ -2727,6 +3450,7 @@ async function runTurn(params) {
|
|
|
2727
3450
|
}
|
|
2728
3451
|
})
|
|
2729
3452
|
);
|
|
3453
|
+
toolStatusWatcher.stop();
|
|
2730
3454
|
for (const r of results) {
|
|
2731
3455
|
state.messages.push({
|
|
2732
3456
|
role: "user",
|
|
@@ -2744,10 +3468,10 @@ async function runTurn(params) {
|
|
|
2744
3468
|
}
|
|
2745
3469
|
|
|
2746
3470
|
// src/headless.ts
|
|
2747
|
-
var BASE_DIR = import.meta.dirname ??
|
|
2748
|
-
var ACTIONS_DIR =
|
|
3471
|
+
var BASE_DIR = import.meta.dirname ?? path9.dirname(new URL(import.meta.url).pathname);
|
|
3472
|
+
var ACTIONS_DIR = path9.join(BASE_DIR, "actions");
|
|
2749
3473
|
function loadActionPrompt(name) {
|
|
2750
|
-
return
|
|
3474
|
+
return fs16.readFileSync(path9.join(ACTIONS_DIR, `${name}.md`), "utf-8").trim();
|
|
2751
3475
|
}
|
|
2752
3476
|
function emit(event, data) {
|
|
2753
3477
|
process.stdout.write(JSON.stringify({ event, ...data }) + "\n");
|
|
@@ -2775,6 +3499,7 @@ async function startHeadless(opts = {}) {
|
|
|
2775
3499
|
}
|
|
2776
3500
|
let running = false;
|
|
2777
3501
|
let currentAbort = null;
|
|
3502
|
+
const EXTERNAL_TOOL_TIMEOUT_MS = 3e5;
|
|
2778
3503
|
const pendingTools = /* @__PURE__ */ new Map();
|
|
2779
3504
|
const earlyResults = /* @__PURE__ */ new Map();
|
|
2780
3505
|
function onEvent(e) {
|
|
@@ -2829,6 +3554,9 @@ async function startHeadless(opts = {}) {
|
|
|
2829
3554
|
case "error":
|
|
2830
3555
|
emit("error", { error: e.error });
|
|
2831
3556
|
break;
|
|
3557
|
+
case "status":
|
|
3558
|
+
emit("status", { message: e.message });
|
|
3559
|
+
break;
|
|
2832
3560
|
}
|
|
2833
3561
|
}
|
|
2834
3562
|
function resolveExternalTool(id, _name, _input) {
|
|
@@ -2838,7 +3566,19 @@ async function startHeadless(opts = {}) {
|
|
|
2838
3566
|
return Promise.resolve(early);
|
|
2839
3567
|
}
|
|
2840
3568
|
return new Promise((resolve) => {
|
|
2841
|
-
|
|
3569
|
+
const timeout = setTimeout(() => {
|
|
3570
|
+
pendingTools.delete(id);
|
|
3571
|
+
resolve(
|
|
3572
|
+
"Error: Tool timed out \u2014 no response from the app environment after 5 minutes."
|
|
3573
|
+
);
|
|
3574
|
+
}, EXTERNAL_TOOL_TIMEOUT_MS);
|
|
3575
|
+
pendingTools.set(id, {
|
|
3576
|
+
resolve: (result) => {
|
|
3577
|
+
clearTimeout(timeout);
|
|
3578
|
+
resolve(result);
|
|
3579
|
+
},
|
|
3580
|
+
timeout
|
|
3581
|
+
});
|
|
2842
3582
|
});
|
|
2843
3583
|
}
|
|
2844
3584
|
const rl = createInterface({ input: process.stdin });
|
|
@@ -2876,6 +3616,7 @@ async function startHeadless(opts = {}) {
|
|
|
2876
3616
|
currentAbort.abort();
|
|
2877
3617
|
}
|
|
2878
3618
|
for (const [id, pending] of pendingTools) {
|
|
3619
|
+
clearTimeout(pending.timeout);
|
|
2879
3620
|
pending.resolve("Error: cancelled");
|
|
2880
3621
|
pendingTools.delete(id);
|
|
2881
3622
|
}
|