@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/index.js
CHANGED
|
@@ -146,11 +146,35 @@ async function* streamChat(params) {
|
|
|
146
146
|
yield { type: "error", error: errorMessage };
|
|
147
147
|
return;
|
|
148
148
|
}
|
|
149
|
+
const STALL_TIMEOUT_MS = 3e5;
|
|
149
150
|
const reader = res.body.getReader();
|
|
150
151
|
const decoder = new TextDecoder();
|
|
151
152
|
let buffer = "";
|
|
152
153
|
while (true) {
|
|
153
|
-
|
|
154
|
+
let stallTimer;
|
|
155
|
+
let readResult;
|
|
156
|
+
try {
|
|
157
|
+
readResult = await Promise.race([
|
|
158
|
+
reader.read(),
|
|
159
|
+
new Promise((_, reject) => {
|
|
160
|
+
stallTimer = setTimeout(
|
|
161
|
+
() => reject(new Error("stream_stall")),
|
|
162
|
+
STALL_TIMEOUT_MS
|
|
163
|
+
);
|
|
164
|
+
})
|
|
165
|
+
]);
|
|
166
|
+
clearTimeout(stallTimer);
|
|
167
|
+
} catch {
|
|
168
|
+
clearTimeout(stallTimer);
|
|
169
|
+
await reader.cancel();
|
|
170
|
+
log.error("Stream stalled", { elapsed: `${Date.now() - startTime}ms` });
|
|
171
|
+
yield {
|
|
172
|
+
type: "error",
|
|
173
|
+
error: "Stream stalled \u2014 no data received for 5 minutes"
|
|
174
|
+
};
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const { done, value } = readResult;
|
|
154
178
|
if (done) {
|
|
155
179
|
break;
|
|
156
180
|
}
|
|
@@ -184,10 +208,55 @@ async function* streamChat(params) {
|
|
|
184
208
|
}
|
|
185
209
|
}
|
|
186
210
|
}
|
|
211
|
+
function isRetryableError(error) {
|
|
212
|
+
return /Network error/i.test(error) || /HTTP 5\d\d/i.test(error) || /Stream stalled/i.test(error);
|
|
213
|
+
}
|
|
214
|
+
function sleep(ms) {
|
|
215
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
216
|
+
}
|
|
217
|
+
async function* streamChatWithRetry(params, options) {
|
|
218
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
219
|
+
const buffer = [];
|
|
220
|
+
let retryableFailure = false;
|
|
221
|
+
for await (const event of streamChat(params)) {
|
|
222
|
+
if (event.type === "error") {
|
|
223
|
+
if (isRetryableError(event.error) && attempt < MAX_RETRIES - 1) {
|
|
224
|
+
log.warn("Retryable error, will retry", {
|
|
225
|
+
attempt: attempt + 1,
|
|
226
|
+
maxRetries: MAX_RETRIES,
|
|
227
|
+
error: event.error
|
|
228
|
+
});
|
|
229
|
+
options?.onRetry?.(attempt, event.error);
|
|
230
|
+
retryableFailure = true;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
yield event;
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
buffer.push(event);
|
|
237
|
+
}
|
|
238
|
+
if (retryableFailure) {
|
|
239
|
+
if (params.signal?.aborted) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const backoff = INITIAL_BACKOFF_MS * 2 ** attempt;
|
|
243
|
+
log.info("Retrying after backoff", { backoffMs: backoff });
|
|
244
|
+
await sleep(backoff);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
for (const event of buffer) {
|
|
248
|
+
yield event;
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
var MAX_RETRIES, INITIAL_BACKOFF_MS;
|
|
187
254
|
var init_api = __esm({
|
|
188
255
|
"src/api.ts"() {
|
|
189
256
|
"use strict";
|
|
190
257
|
init_logger();
|
|
258
|
+
MAX_RETRIES = 3;
|
|
259
|
+
INITIAL_BACKOFF_MS = 1e3;
|
|
191
260
|
}
|
|
192
261
|
});
|
|
193
262
|
|
|
@@ -634,10 +703,115 @@ var init_listSpecFiles = __esm({
|
|
|
634
703
|
}
|
|
635
704
|
});
|
|
636
705
|
|
|
637
|
-
// src/tools/spec/
|
|
706
|
+
// src/tools/spec/clearSyncStatus.ts
|
|
707
|
+
var clearSyncStatusTool;
|
|
708
|
+
var init_clearSyncStatus = __esm({
|
|
709
|
+
"src/tools/spec/clearSyncStatus.ts"() {
|
|
710
|
+
"use strict";
|
|
711
|
+
clearSyncStatusTool = {
|
|
712
|
+
definition: {
|
|
713
|
+
name: "clearSyncStatus",
|
|
714
|
+
description: "Clear the sync status flags after syncing spec and code. Call this after finishing a sync operation.",
|
|
715
|
+
inputSchema: {
|
|
716
|
+
type: "object",
|
|
717
|
+
properties: {}
|
|
718
|
+
}
|
|
719
|
+
},
|
|
720
|
+
async execute() {
|
|
721
|
+
return "ok";
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
// src/tools/spec/presentSyncPlan.ts
|
|
728
|
+
var presentSyncPlanTool;
|
|
729
|
+
var init_presentSyncPlan = __esm({
|
|
730
|
+
"src/tools/spec/presentSyncPlan.ts"() {
|
|
731
|
+
"use strict";
|
|
732
|
+
presentSyncPlanTool = {
|
|
733
|
+
definition: {
|
|
734
|
+
name: "presentSyncPlan",
|
|
735
|
+
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.",
|
|
736
|
+
inputSchema: {
|
|
737
|
+
type: "object",
|
|
738
|
+
properties: {
|
|
739
|
+
content: {
|
|
740
|
+
type: "string",
|
|
741
|
+
description: "Markdown plan describing what changed and what will be updated."
|
|
742
|
+
}
|
|
743
|
+
},
|
|
744
|
+
required: ["content"]
|
|
745
|
+
}
|
|
746
|
+
},
|
|
747
|
+
streaming: {},
|
|
748
|
+
async execute() {
|
|
749
|
+
return "approved";
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
// src/tools/spec/presentPublishPlan.ts
|
|
756
|
+
var presentPublishPlanTool;
|
|
757
|
+
var init_presentPublishPlan = __esm({
|
|
758
|
+
"src/tools/spec/presentPublishPlan.ts"() {
|
|
759
|
+
"use strict";
|
|
760
|
+
presentPublishPlanTool = {
|
|
761
|
+
definition: {
|
|
762
|
+
name: "presentPublishPlan",
|
|
763
|
+
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.",
|
|
764
|
+
inputSchema: {
|
|
765
|
+
type: "object",
|
|
766
|
+
properties: {
|
|
767
|
+
content: {
|
|
768
|
+
type: "string",
|
|
769
|
+
description: "Markdown changelog describing what changed and what will be deployed."
|
|
770
|
+
}
|
|
771
|
+
},
|
|
772
|
+
required: ["content"]
|
|
773
|
+
}
|
|
774
|
+
},
|
|
775
|
+
streaming: {},
|
|
776
|
+
async execute() {
|
|
777
|
+
return "approved";
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
// src/tools/spec/presentPlan.ts
|
|
784
|
+
var presentPlanTool;
|
|
785
|
+
var init_presentPlan = __esm({
|
|
786
|
+
"src/tools/spec/presentPlan.ts"() {
|
|
787
|
+
"use strict";
|
|
788
|
+
presentPlanTool = {
|
|
789
|
+
definition: {
|
|
790
|
+
name: "presentPlan",
|
|
791
|
+
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.",
|
|
792
|
+
inputSchema: {
|
|
793
|
+
type: "object",
|
|
794
|
+
properties: {
|
|
795
|
+
content: {
|
|
796
|
+
type: "string",
|
|
797
|
+
description: "Markdown plan describing what you intend to do."
|
|
798
|
+
}
|
|
799
|
+
},
|
|
800
|
+
required: ["content"]
|
|
801
|
+
}
|
|
802
|
+
},
|
|
803
|
+
streaming: {},
|
|
804
|
+
async execute() {
|
|
805
|
+
return "approved";
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
// src/tools/common/setProjectOnboardingState.ts
|
|
638
812
|
var setProjectOnboardingStateTool;
|
|
639
813
|
var init_setProjectOnboardingState = __esm({
|
|
640
|
-
"src/tools/
|
|
814
|
+
"src/tools/common/setProjectOnboardingState.ts"() {
|
|
641
815
|
"use strict";
|
|
642
816
|
setProjectOnboardingStateTool = {
|
|
643
817
|
definition: {
|
|
@@ -666,10 +840,10 @@ var init_setProjectOnboardingState = __esm({
|
|
|
666
840
|
}
|
|
667
841
|
});
|
|
668
842
|
|
|
669
|
-
// src/tools/
|
|
843
|
+
// src/tools/common/promptUser.ts
|
|
670
844
|
var promptUserTool;
|
|
671
845
|
var init_promptUser = __esm({
|
|
672
|
-
"src/tools/
|
|
846
|
+
"src/tools/common/promptUser.ts"() {
|
|
673
847
|
"use strict";
|
|
674
848
|
promptUserTool = {
|
|
675
849
|
definition: {
|
|
@@ -698,8 +872,8 @@ var init_promptUser = __esm({
|
|
|
698
872
|
},
|
|
699
873
|
type: {
|
|
700
874
|
type: "string",
|
|
701
|
-
enum: ["select", "checklist", "text", "file"
|
|
702
|
-
description:
|
|
875
|
+
enum: ["select", "checklist", "text", "file"],
|
|
876
|
+
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.'
|
|
703
877
|
},
|
|
704
878
|
helpText: {
|
|
705
879
|
type: "string",
|
|
@@ -736,10 +910,6 @@ var init_promptUser = __esm({
|
|
|
736
910
|
type: "boolean",
|
|
737
911
|
description: "For file type: allow multiple uploads (returns array of URLs). Defaults to false."
|
|
738
912
|
},
|
|
739
|
-
allowOther: {
|
|
740
|
-
type: "boolean",
|
|
741
|
-
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.'
|
|
742
|
-
},
|
|
743
913
|
format: {
|
|
744
914
|
type: "string",
|
|
745
915
|
enum: ["email", "url", "phone", "number"],
|
|
@@ -797,8 +967,6 @@ var init_promptUser = __esm({
|
|
|
797
967
|
line += q.type === "checklist" ? ` (pick one or more: ${opts.join(" / ")})` : ` (${opts.join(" / ")})`;
|
|
798
968
|
} else if (q.type === "file") {
|
|
799
969
|
line += " (upload file)";
|
|
800
|
-
} else if (q.type === "color") {
|
|
801
|
-
line += " (pick a color)";
|
|
802
970
|
}
|
|
803
971
|
return line;
|
|
804
972
|
});
|
|
@@ -809,141 +977,209 @@ ${lines.join("\n")}`;
|
|
|
809
977
|
}
|
|
810
978
|
});
|
|
811
979
|
|
|
812
|
-
// src/tools/
|
|
813
|
-
var
|
|
814
|
-
var
|
|
815
|
-
"src/tools/
|
|
980
|
+
// src/tools/common/confirmDestructiveAction.ts
|
|
981
|
+
var confirmDestructiveActionTool;
|
|
982
|
+
var init_confirmDestructiveAction = __esm({
|
|
983
|
+
"src/tools/common/confirmDestructiveAction.ts"() {
|
|
816
984
|
"use strict";
|
|
817
|
-
|
|
985
|
+
confirmDestructiveActionTool = {
|
|
818
986
|
definition: {
|
|
819
|
-
name: "
|
|
820
|
-
description: "
|
|
987
|
+
name: "confirmDestructiveAction",
|
|
988
|
+
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.",
|
|
821
989
|
inputSchema: {
|
|
822
990
|
type: "object",
|
|
823
|
-
properties: {
|
|
991
|
+
properties: {
|
|
992
|
+
message: {
|
|
993
|
+
type: "string",
|
|
994
|
+
description: "Explanation of what is about to happen and why confirmation is needed."
|
|
995
|
+
},
|
|
996
|
+
confirmLabel: {
|
|
997
|
+
type: "string",
|
|
998
|
+
description: 'Custom label for the confirm button (e.g., "Delete", "Reset Database"). Defaults to "Confirm".'
|
|
999
|
+
},
|
|
1000
|
+
dismissLabel: {
|
|
1001
|
+
type: "string",
|
|
1002
|
+
description: 'Custom label for the dismiss button (e.g., "Keep It", "Go Back"). Defaults to "Cancel".'
|
|
1003
|
+
}
|
|
1004
|
+
},
|
|
1005
|
+
required: ["message"]
|
|
824
1006
|
}
|
|
825
1007
|
},
|
|
826
1008
|
async execute() {
|
|
827
|
-
return "
|
|
1009
|
+
return "confirmed";
|
|
828
1010
|
}
|
|
829
1011
|
};
|
|
830
1012
|
}
|
|
831
1013
|
});
|
|
832
1014
|
|
|
833
|
-
// src/tools/
|
|
834
|
-
|
|
835
|
-
var
|
|
836
|
-
|
|
1015
|
+
// src/tools/common/askMindStudioSdk.ts
|
|
1016
|
+
import { exec } from "child_process";
|
|
1017
|
+
var askMindStudioSdkTool;
|
|
1018
|
+
var init_askMindStudioSdk = __esm({
|
|
1019
|
+
"src/tools/common/askMindStudioSdk.ts"() {
|
|
837
1020
|
"use strict";
|
|
838
|
-
|
|
1021
|
+
askMindStudioSdkTool = {
|
|
839
1022
|
definition: {
|
|
840
|
-
name: "
|
|
841
|
-
description: "
|
|
1023
|
+
name: "askMindStudioSdk",
|
|
1024
|
+
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.",
|
|
842
1025
|
inputSchema: {
|
|
843
1026
|
type: "object",
|
|
844
1027
|
properties: {
|
|
845
|
-
|
|
1028
|
+
query: {
|
|
846
1029
|
type: "string",
|
|
847
|
-
description: "
|
|
1030
|
+
description: "Natural language question about the SDK."
|
|
848
1031
|
}
|
|
849
1032
|
},
|
|
850
|
-
required: ["
|
|
1033
|
+
required: ["query"]
|
|
851
1034
|
}
|
|
852
1035
|
},
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
return
|
|
1036
|
+
async execute(input) {
|
|
1037
|
+
const query = input.query;
|
|
1038
|
+
return new Promise((resolve) => {
|
|
1039
|
+
exec(
|
|
1040
|
+
`mindstudio ask ${JSON.stringify(query)}`,
|
|
1041
|
+
{ timeout: 6e4, maxBuffer: 512 * 1024 },
|
|
1042
|
+
(err, stdout, stderr) => {
|
|
1043
|
+
if (stdout.trim()) {
|
|
1044
|
+
resolve(stdout.trim());
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
if (err) {
|
|
1048
|
+
resolve(`Error: ${stderr.trim() || err.message}`);
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
resolve("(no response)");
|
|
1052
|
+
}
|
|
1053
|
+
);
|
|
1054
|
+
});
|
|
856
1055
|
}
|
|
857
1056
|
};
|
|
858
1057
|
}
|
|
859
1058
|
});
|
|
860
1059
|
|
|
861
|
-
// src/tools/
|
|
862
|
-
|
|
863
|
-
var
|
|
864
|
-
|
|
1060
|
+
// src/tools/common/fetchUrl.ts
|
|
1061
|
+
import { exec as exec2 } from "child_process";
|
|
1062
|
+
var fetchUrlTool;
|
|
1063
|
+
var init_fetchUrl = __esm({
|
|
1064
|
+
"src/tools/common/fetchUrl.ts"() {
|
|
865
1065
|
"use strict";
|
|
866
|
-
|
|
1066
|
+
fetchUrlTool = {
|
|
867
1067
|
definition: {
|
|
868
|
-
name: "
|
|
869
|
-
description: "
|
|
1068
|
+
name: "scapeWebUrl",
|
|
1069
|
+
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",
|
|
870
1070
|
inputSchema: {
|
|
871
1071
|
type: "object",
|
|
872
1072
|
properties: {
|
|
873
|
-
|
|
1073
|
+
url: {
|
|
874
1074
|
type: "string",
|
|
875
|
-
description: "
|
|
1075
|
+
description: "The URL to fetch."
|
|
1076
|
+
},
|
|
1077
|
+
screenshot: {
|
|
1078
|
+
type: "boolean",
|
|
1079
|
+
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."
|
|
876
1080
|
}
|
|
877
1081
|
},
|
|
878
|
-
required: ["
|
|
1082
|
+
required: ["url"]
|
|
879
1083
|
}
|
|
880
1084
|
},
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1085
|
+
async execute(input) {
|
|
1086
|
+
const url = input.url;
|
|
1087
|
+
const screenshot = input.screenshot;
|
|
1088
|
+
const pageOptions = { onlyMainContent: true };
|
|
1089
|
+
if (screenshot) {
|
|
1090
|
+
pageOptions.screenshot = true;
|
|
1091
|
+
}
|
|
1092
|
+
const cmd = `mindstudio scrape-url --url ${JSON.stringify(url)} --page-options ${JSON.stringify(JSON.stringify(pageOptions))} --no-meta`;
|
|
1093
|
+
return new Promise((resolve) => {
|
|
1094
|
+
exec2(
|
|
1095
|
+
cmd,
|
|
1096
|
+
{ timeout: 6e4, maxBuffer: 1024 * 1024 },
|
|
1097
|
+
(err, stdout, stderr) => {
|
|
1098
|
+
if (stdout.trim()) {
|
|
1099
|
+
resolve(stdout.trim());
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
if (err) {
|
|
1103
|
+
resolve(`Error: ${stderr.trim() || err.message}`);
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
resolve("(no response)");
|
|
1107
|
+
}
|
|
1108
|
+
);
|
|
1109
|
+
});
|
|
884
1110
|
}
|
|
885
1111
|
};
|
|
886
1112
|
}
|
|
887
1113
|
});
|
|
888
1114
|
|
|
889
|
-
// src/tools/
|
|
890
|
-
|
|
891
|
-
var
|
|
892
|
-
|
|
1115
|
+
// src/tools/common/searchGoogle.ts
|
|
1116
|
+
import { exec as exec3 } from "child_process";
|
|
1117
|
+
var searchGoogleTool;
|
|
1118
|
+
var init_searchGoogle = __esm({
|
|
1119
|
+
"src/tools/common/searchGoogle.ts"() {
|
|
893
1120
|
"use strict";
|
|
894
|
-
|
|
1121
|
+
searchGoogleTool = {
|
|
895
1122
|
definition: {
|
|
896
|
-
name: "
|
|
897
|
-
description: "
|
|
1123
|
+
name: "searchGoogle",
|
|
1124
|
+
description: "Search Google and return results. Use for research, finding documentation, looking up APIs, or any task where web search would help.",
|
|
898
1125
|
inputSchema: {
|
|
899
1126
|
type: "object",
|
|
900
1127
|
properties: {
|
|
901
|
-
|
|
1128
|
+
query: {
|
|
902
1129
|
type: "string",
|
|
903
|
-
description: "
|
|
1130
|
+
description: "The search query."
|
|
904
1131
|
}
|
|
905
1132
|
},
|
|
906
|
-
required: ["
|
|
1133
|
+
required: ["query"]
|
|
907
1134
|
}
|
|
908
1135
|
},
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
1136
|
+
async execute(input) {
|
|
1137
|
+
const query = input.query;
|
|
1138
|
+
const cmd = `mindstudio search-google --query ${JSON.stringify(query)} --export-type json --output-key results --no-meta`;
|
|
1139
|
+
return new Promise((resolve) => {
|
|
1140
|
+
exec3(
|
|
1141
|
+
cmd,
|
|
1142
|
+
{ timeout: 6e4, maxBuffer: 512 * 1024 },
|
|
1143
|
+
(err, stdout, stderr) => {
|
|
1144
|
+
if (stdout.trim()) {
|
|
1145
|
+
resolve(stdout.trim());
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
if (err) {
|
|
1149
|
+
resolve(`Error: ${stderr.trim() || err.message}`);
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
resolve("(no response)");
|
|
1153
|
+
}
|
|
1154
|
+
);
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
// src/tools/common/setProjectName.ts
|
|
1162
|
+
var setProjectNameTool;
|
|
1163
|
+
var init_setProjectName = __esm({
|
|
1164
|
+
"src/tools/common/setProjectName.ts"() {
|
|
921
1165
|
"use strict";
|
|
922
|
-
|
|
1166
|
+
setProjectNameTool = {
|
|
923
1167
|
definition: {
|
|
924
|
-
name: "
|
|
925
|
-
description:
|
|
1168
|
+
name: "setProjectName",
|
|
1169
|
+
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").`,
|
|
926
1170
|
inputSchema: {
|
|
927
1171
|
type: "object",
|
|
928
1172
|
properties: {
|
|
929
|
-
|
|
930
|
-
type: "string",
|
|
931
|
-
description: "Explanation of what is about to happen and why confirmation is needed."
|
|
932
|
-
},
|
|
933
|
-
confirmLabel: {
|
|
934
|
-
type: "string",
|
|
935
|
-
description: 'Custom label for the confirm button (e.g., "Delete", "Reset Database"). Defaults to "Confirm".'
|
|
936
|
-
},
|
|
937
|
-
dismissLabel: {
|
|
1173
|
+
name: {
|
|
938
1174
|
type: "string",
|
|
939
|
-
description:
|
|
1175
|
+
description: "The project name."
|
|
940
1176
|
}
|
|
941
1177
|
},
|
|
942
|
-
required: ["
|
|
1178
|
+
required: ["name"]
|
|
943
1179
|
}
|
|
944
1180
|
},
|
|
945
1181
|
async execute() {
|
|
946
|
-
return "
|
|
1182
|
+
return "ok";
|
|
947
1183
|
}
|
|
948
1184
|
};
|
|
949
1185
|
}
|
|
@@ -952,9 +1188,9 @@ var init_confirmDestructiveAction = __esm({
|
|
|
952
1188
|
// src/tools/code/readFile.ts
|
|
953
1189
|
import fs6 from "fs/promises";
|
|
954
1190
|
function isBinary(buffer) {
|
|
955
|
-
const
|
|
956
|
-
for (let i = 0; i <
|
|
957
|
-
if (
|
|
1191
|
+
const sample2 = buffer.subarray(0, 8192);
|
|
1192
|
+
for (let i = 0; i < sample2.length; i++) {
|
|
1193
|
+
if (sample2[i] === 0) {
|
|
958
1194
|
return true;
|
|
959
1195
|
}
|
|
960
1196
|
}
|
|
@@ -1273,7 +1509,7 @@ ${unifiedDiff(input.path, content, updated)}`;
|
|
|
1273
1509
|
});
|
|
1274
1510
|
|
|
1275
1511
|
// src/tools/code/bash.ts
|
|
1276
|
-
import { exec } from "child_process";
|
|
1512
|
+
import { exec as exec4 } from "child_process";
|
|
1277
1513
|
var DEFAULT_TIMEOUT_MS, DEFAULT_MAX_LINES3, bashTool;
|
|
1278
1514
|
var init_bash = __esm({
|
|
1279
1515
|
"src/tools/code/bash.ts"() {
|
|
@@ -1311,7 +1547,7 @@ var init_bash = __esm({
|
|
|
1311
1547
|
const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES3;
|
|
1312
1548
|
const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
|
|
1313
1549
|
return new Promise((resolve) => {
|
|
1314
|
-
|
|
1550
|
+
exec4(
|
|
1315
1551
|
input.command,
|
|
1316
1552
|
{
|
|
1317
1553
|
timeout: timeoutMs,
|
|
@@ -1353,7 +1589,7 @@ var init_bash = __esm({
|
|
|
1353
1589
|
});
|
|
1354
1590
|
|
|
1355
1591
|
// src/tools/code/grep.ts
|
|
1356
|
-
import { exec as
|
|
1592
|
+
import { exec as exec5 } from "child_process";
|
|
1357
1593
|
function formatResults(stdout, max) {
|
|
1358
1594
|
const lines = stdout.trim().split("\n");
|
|
1359
1595
|
let result = lines.join("\n");
|
|
@@ -1404,12 +1640,12 @@ var init_grep = __esm({
|
|
|
1404
1640
|
const rgCmd = `rg -n --no-heading --max-count=${max}${globFlag} '${escaped}' ${searchPath}`;
|
|
1405
1641
|
const grepCmd = `grep -rn --max-count=${max} '${escaped}' ${searchPath} --include='*.ts' --include='*.tsx' --include='*.js' --include='*.json' --include='*.md'`;
|
|
1406
1642
|
return new Promise((resolve) => {
|
|
1407
|
-
|
|
1643
|
+
exec5(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
|
|
1408
1644
|
if (stdout?.trim()) {
|
|
1409
1645
|
resolve(formatResults(stdout, max));
|
|
1410
1646
|
return;
|
|
1411
1647
|
}
|
|
1412
|
-
|
|
1648
|
+
exec5(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
|
|
1413
1649
|
if (grepStdout?.trim()) {
|
|
1414
1650
|
resolve(formatResults(grepStdout, max));
|
|
1415
1651
|
} else {
|
|
@@ -1669,51 +1905,6 @@ var init_restartProcess = __esm({
|
|
|
1669
1905
|
}
|
|
1670
1906
|
});
|
|
1671
1907
|
|
|
1672
|
-
// src/tools/code/askMindStudioSdk.ts
|
|
1673
|
-
import { exec as exec3 } from "child_process";
|
|
1674
|
-
var askMindStudioSdkTool;
|
|
1675
|
-
var init_askMindStudioSdk = __esm({
|
|
1676
|
-
"src/tools/code/askMindStudioSdk.ts"() {
|
|
1677
|
-
"use strict";
|
|
1678
|
-
askMindStudioSdkTool = {
|
|
1679
|
-
definition: {
|
|
1680
|
-
name: "askMindStudioSdk",
|
|
1681
|
-
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.",
|
|
1682
|
-
inputSchema: {
|
|
1683
|
-
type: "object",
|
|
1684
|
-
properties: {
|
|
1685
|
-
query: {
|
|
1686
|
-
type: "string",
|
|
1687
|
-
description: "Natural language question about the SDK."
|
|
1688
|
-
}
|
|
1689
|
-
},
|
|
1690
|
-
required: ["query"]
|
|
1691
|
-
}
|
|
1692
|
-
},
|
|
1693
|
-
async execute(input) {
|
|
1694
|
-
const query = input.query;
|
|
1695
|
-
return new Promise((resolve) => {
|
|
1696
|
-
exec3(
|
|
1697
|
-
`mindstudio ask ${JSON.stringify(query)}`,
|
|
1698
|
-
{ timeout: 6e4, maxBuffer: 512 * 1024 },
|
|
1699
|
-
(err, stdout, stderr) => {
|
|
1700
|
-
if (stdout.trim()) {
|
|
1701
|
-
resolve(stdout.trim());
|
|
1702
|
-
return;
|
|
1703
|
-
}
|
|
1704
|
-
if (err) {
|
|
1705
|
-
resolve(`Error: ${stderr.trim() || err.message}`);
|
|
1706
|
-
return;
|
|
1707
|
-
}
|
|
1708
|
-
resolve("(no response)");
|
|
1709
|
-
}
|
|
1710
|
-
);
|
|
1711
|
-
});
|
|
1712
|
-
}
|
|
1713
|
-
};
|
|
1714
|
-
}
|
|
1715
|
-
});
|
|
1716
|
-
|
|
1717
1908
|
// src/tools/code/runScenario.ts
|
|
1718
1909
|
var runScenarioTool;
|
|
1719
1910
|
var init_runScenario = __esm({
|
|
@@ -1820,7 +2011,7 @@ async function runSubAgent(config) {
|
|
|
1820
2011
|
const toolCalls = [];
|
|
1821
2012
|
let stopReason = "end_turn";
|
|
1822
2013
|
try {
|
|
1823
|
-
for await (const event of
|
|
2014
|
+
for await (const event of streamChatWithRetry({
|
|
1824
2015
|
...apiConfig,
|
|
1825
2016
|
model,
|
|
1826
2017
|
system,
|
|
@@ -1912,133 +2103,567 @@ async function runSubAgent(config) {
|
|
|
1912
2103
|
});
|
|
1913
2104
|
return { id: tc.id, result: errorMsg, isError: true };
|
|
1914
2105
|
}
|
|
1915
|
-
})
|
|
1916
|
-
);
|
|
1917
|
-
for (const r of results) {
|
|
1918
|
-
messages.push({
|
|
1919
|
-
role: "user",
|
|
1920
|
-
content: r.result,
|
|
1921
|
-
toolCallId: r.id,
|
|
1922
|
-
isToolError: r.isError
|
|
1923
|
-
});
|
|
1924
|
-
}
|
|
1925
|
-
}
|
|
1926
|
-
}
|
|
1927
|
-
var init_runner = __esm({
|
|
1928
|
-
"src/subagents/runner.ts"() {
|
|
1929
|
-
"use strict";
|
|
1930
|
-
init_api();
|
|
1931
|
-
init_logger();
|
|
1932
|
-
}
|
|
1933
|
-
});
|
|
1934
|
-
|
|
1935
|
-
// src/subagents/browserAutomation/tools.ts
|
|
1936
|
-
var BROWSER_TOOLS, BROWSER_EXTERNAL_TOOLS;
|
|
1937
|
-
var init_tools = __esm({
|
|
1938
|
-
"src/subagents/browserAutomation/tools.ts"() {
|
|
1939
|
-
"use strict";
|
|
1940
|
-
BROWSER_TOOLS = [
|
|
2106
|
+
})
|
|
2107
|
+
);
|
|
2108
|
+
for (const r of results) {
|
|
2109
|
+
messages.push({
|
|
2110
|
+
role: "user",
|
|
2111
|
+
content: r.result,
|
|
2112
|
+
toolCallId: r.id,
|
|
2113
|
+
isToolError: r.isError
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
var init_runner = __esm({
|
|
2119
|
+
"src/subagents/runner.ts"() {
|
|
2120
|
+
"use strict";
|
|
2121
|
+
init_api();
|
|
2122
|
+
init_logger();
|
|
2123
|
+
}
|
|
2124
|
+
});
|
|
2125
|
+
|
|
2126
|
+
// src/subagents/browserAutomation/tools.ts
|
|
2127
|
+
var BROWSER_TOOLS, BROWSER_EXTERNAL_TOOLS;
|
|
2128
|
+
var init_tools = __esm({
|
|
2129
|
+
"src/subagents/browserAutomation/tools.ts"() {
|
|
2130
|
+
"use strict";
|
|
2131
|
+
BROWSER_TOOLS = [
|
|
2132
|
+
{
|
|
2133
|
+
name: "browserCommand",
|
|
2134
|
+
description: "Interact with the app's live preview by sending browser commands. Commands execute sequentially with an animated cursor. Always start with a snapshot to see the current state and get ref identifiers. The result includes a snapshot field with the final page state after all steps complete. On error, the failing step has an error field and execution stops. Timeout: 120s.",
|
|
2135
|
+
inputSchema: {
|
|
2136
|
+
type: "object",
|
|
2137
|
+
properties: {
|
|
2138
|
+
steps: {
|
|
2139
|
+
type: "array",
|
|
2140
|
+
items: {
|
|
2141
|
+
type: "object",
|
|
2142
|
+
properties: {
|
|
2143
|
+
command: {
|
|
2144
|
+
type: "string",
|
|
2145
|
+
enum: ["snapshot", "click", "type", "wait", "evaluate"],
|
|
2146
|
+
description: "snapshot: accessibility tree of the page (waits for network to settle). click: click an element (animated cursor, full event sequence). type: type text into input (one char at a time, works with React/Vue/Svelte). wait: wait for an element to appear (polls 100ms, waits for network). evaluate: run JS in the page."
|
|
2147
|
+
},
|
|
2148
|
+
ref: {
|
|
2149
|
+
type: "string",
|
|
2150
|
+
description: "Element ref from the last snapshot (most reliable targeting)."
|
|
2151
|
+
},
|
|
2152
|
+
text: {
|
|
2153
|
+
type: "string",
|
|
2154
|
+
description: "For click/wait: match by accessible name or visible text. For type: the text to type."
|
|
2155
|
+
},
|
|
2156
|
+
role: {
|
|
2157
|
+
type: "string",
|
|
2158
|
+
description: "ARIA role to match (used with text for role+text targeting)."
|
|
2159
|
+
},
|
|
2160
|
+
label: {
|
|
2161
|
+
type: "string",
|
|
2162
|
+
description: "Find an input by its associated label text."
|
|
2163
|
+
},
|
|
2164
|
+
selector: {
|
|
2165
|
+
type: "string",
|
|
2166
|
+
description: "CSS selector fallback (last resort)."
|
|
2167
|
+
},
|
|
2168
|
+
clear: {
|
|
2169
|
+
type: "boolean",
|
|
2170
|
+
description: "For type: clear the field before typing."
|
|
2171
|
+
},
|
|
2172
|
+
timeout: {
|
|
2173
|
+
type: "number",
|
|
2174
|
+
description: "For wait: timeout in ms (default 5000)."
|
|
2175
|
+
},
|
|
2176
|
+
script: {
|
|
2177
|
+
type: "string",
|
|
2178
|
+
description: "For evaluate: JavaScript to run in the page."
|
|
2179
|
+
}
|
|
2180
|
+
},
|
|
2181
|
+
required: ["command"]
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
},
|
|
2185
|
+
required: ["steps"]
|
|
2186
|
+
}
|
|
2187
|
+
},
|
|
2188
|
+
{
|
|
2189
|
+
name: "screenshot",
|
|
2190
|
+
description: "Capture a screenshot of the current page. Returns a CDN URL with dimensions.",
|
|
2191
|
+
inputSchema: {
|
|
2192
|
+
type: "object",
|
|
2193
|
+
properties: {}
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
];
|
|
2197
|
+
BROWSER_EXTERNAL_TOOLS = /* @__PURE__ */ new Set(["browserCommand", "screenshot"]);
|
|
2198
|
+
}
|
|
2199
|
+
});
|
|
2200
|
+
|
|
2201
|
+
// src/subagents/browserAutomation/prompt.ts
|
|
2202
|
+
import fs10 from "fs";
|
|
2203
|
+
import path4 from "path";
|
|
2204
|
+
var base, local, PROMPT_PATH, BROWSER_AUTOMATION_PROMPT;
|
|
2205
|
+
var init_prompt = __esm({
|
|
2206
|
+
"src/subagents/browserAutomation/prompt.ts"() {
|
|
2207
|
+
"use strict";
|
|
2208
|
+
base = import.meta.dirname ?? path4.dirname(new URL(import.meta.url).pathname);
|
|
2209
|
+
local = path4.join(base, "prompt.md");
|
|
2210
|
+
PROMPT_PATH = fs10.existsSync(local) ? local : path4.join(base, "subagents", "browserAutomation", "prompt.md");
|
|
2211
|
+
BROWSER_AUTOMATION_PROMPT = fs10.readFileSync(PROMPT_PATH, "utf-8").trim();
|
|
2212
|
+
}
|
|
2213
|
+
});
|
|
2214
|
+
|
|
2215
|
+
// src/subagents/browserAutomation/index.ts
|
|
2216
|
+
var browserAutomationTool;
|
|
2217
|
+
var init_browserAutomation = __esm({
|
|
2218
|
+
"src/subagents/browserAutomation/index.ts"() {
|
|
2219
|
+
"use strict";
|
|
2220
|
+
init_runner();
|
|
2221
|
+
init_tools();
|
|
2222
|
+
init_prompt();
|
|
2223
|
+
browserAutomationTool = {
|
|
2224
|
+
definition: {
|
|
2225
|
+
name: "runAutomatedBrowserTest",
|
|
2226
|
+
description: "Run an automated browser test against the live preview. The test agent always starts on the main page, so include navigation instructions if the test involves a sub-page. The browser uses the current user roles and dev database state, so run a scenario first if you need specific data or roles. Use after writing or modifying frontend code, to reproduce user-reported issues, or to test end-to-end flows.",
|
|
2227
|
+
inputSchema: {
|
|
2228
|
+
type: "object",
|
|
2229
|
+
properties: {
|
|
2230
|
+
task: {
|
|
2231
|
+
type: "string",
|
|
2232
|
+
description: "What to test, in natural language. Include how to navigate to the relevant page and what data/roles to expect."
|
|
2233
|
+
}
|
|
2234
|
+
},
|
|
2235
|
+
required: ["task"]
|
|
2236
|
+
}
|
|
2237
|
+
},
|
|
2238
|
+
async execute(input, context) {
|
|
2239
|
+
if (!context) {
|
|
2240
|
+
return "Error: browser automation requires execution context (only available in headless mode)";
|
|
2241
|
+
}
|
|
2242
|
+
return runSubAgent({
|
|
2243
|
+
system: BROWSER_AUTOMATION_PROMPT,
|
|
2244
|
+
task: input.task,
|
|
2245
|
+
tools: BROWSER_TOOLS,
|
|
2246
|
+
externalTools: BROWSER_EXTERNAL_TOOLS,
|
|
2247
|
+
executeTool: async () => "Error: no local tools in browser automation",
|
|
2248
|
+
apiConfig: context.apiConfig,
|
|
2249
|
+
model: context.model,
|
|
2250
|
+
signal: context.signal,
|
|
2251
|
+
parentToolId: context.toolCallId,
|
|
2252
|
+
onEvent: context.onEvent,
|
|
2253
|
+
resolveExternalTool: context.resolveExternalTool
|
|
2254
|
+
});
|
|
2255
|
+
}
|
|
2256
|
+
};
|
|
2257
|
+
}
|
|
2258
|
+
});
|
|
2259
|
+
|
|
2260
|
+
// src/subagents/designExpert/tools.ts
|
|
2261
|
+
import { exec as exec6 } from "child_process";
|
|
2262
|
+
function runCli(cmd) {
|
|
2263
|
+
return new Promise((resolve) => {
|
|
2264
|
+
exec6(
|
|
2265
|
+
cmd,
|
|
2266
|
+
{ timeout: 6e4, maxBuffer: 1024 * 1024 },
|
|
2267
|
+
(err, stdout, stderr) => {
|
|
2268
|
+
if (stdout.trim()) {
|
|
2269
|
+
resolve(stdout.trim());
|
|
2270
|
+
return;
|
|
2271
|
+
}
|
|
2272
|
+
if (err) {
|
|
2273
|
+
resolve(`Error: ${stderr.trim() || err.message}`);
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
resolve("(no response)");
|
|
2277
|
+
}
|
|
2278
|
+
);
|
|
2279
|
+
});
|
|
2280
|
+
}
|
|
2281
|
+
async function executeDesignTool(name, input) {
|
|
2282
|
+
switch (name) {
|
|
2283
|
+
case "searchGoogle":
|
|
2284
|
+
return runCli(
|
|
2285
|
+
`mindstudio search-google --query ${JSON.stringify(input.query)} --export-type json --output-key results --no-meta`
|
|
2286
|
+
);
|
|
2287
|
+
case "fetchUrl": {
|
|
2288
|
+
const pageOptions = { onlyMainContent: true };
|
|
2289
|
+
if (input.screenshot) {
|
|
2290
|
+
pageOptions.screenshot = true;
|
|
2291
|
+
}
|
|
2292
|
+
return runCli(
|
|
2293
|
+
`mindstudio scrape-url --url ${JSON.stringify(input.url)} --page-options ${JSON.stringify(JSON.stringify(pageOptions))} --no-meta`
|
|
2294
|
+
);
|
|
2295
|
+
}
|
|
2296
|
+
case "analyzeImage":
|
|
2297
|
+
return runCli(
|
|
2298
|
+
`mindstudio analyze-image --prompt ${JSON.stringify(input.prompt)} --image-url ${JSON.stringify(input.imageUrl)} --no-meta`
|
|
2299
|
+
);
|
|
2300
|
+
case "analyzeDesignReference":
|
|
2301
|
+
return runCli(
|
|
2302
|
+
`mindstudio analyze-image --prompt ${JSON.stringify(DESIGN_REFERENCE_PROMPT)} --image-url ${JSON.stringify(input.imageUrl)} --no-meta`
|
|
2303
|
+
);
|
|
2304
|
+
case "screenshotAndAnalyze": {
|
|
2305
|
+
const screenshotResult = await runCli(
|
|
2306
|
+
`mindstudio scrape-url --url ${JSON.stringify(input.url)} --page-options ${JSON.stringify(JSON.stringify({ onlyMainContent: true, screenshot: true }))} --no-meta`
|
|
2307
|
+
);
|
|
2308
|
+
const screenshotMatch = screenshotResult.match(
|
|
2309
|
+
/https:\/\/[^\s"']+(?:\.png|\.jpg|\.jpeg|\.webp|screenshot[^\s"']*)/i
|
|
2310
|
+
);
|
|
2311
|
+
if (!screenshotMatch) {
|
|
2312
|
+
try {
|
|
2313
|
+
const parsed = JSON.parse(screenshotResult);
|
|
2314
|
+
const ssUrl = parsed.screenshot || parsed.screenshotUrl || parsed.content?.screenshotUrl;
|
|
2315
|
+
if (ssUrl) {
|
|
2316
|
+
const analysisPrompt2 = input.prompt || DESIGN_REFERENCE_PROMPT;
|
|
2317
|
+
const analysis2 = await runCli(
|
|
2318
|
+
`mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt2)} --image-url ${JSON.stringify(ssUrl)} --no-meta`
|
|
2319
|
+
);
|
|
2320
|
+
return `Screenshot: ${ssUrl}
|
|
2321
|
+
|
|
2322
|
+
${analysis2}`;
|
|
2323
|
+
}
|
|
2324
|
+
} catch {
|
|
2325
|
+
}
|
|
2326
|
+
return `Fetched ${input.url} but could not extract screenshot URL.
|
|
2327
|
+
|
|
2328
|
+
Page content:
|
|
2329
|
+
${screenshotResult}`;
|
|
2330
|
+
}
|
|
2331
|
+
const analysisPrompt = input.prompt || DESIGN_REFERENCE_PROMPT;
|
|
2332
|
+
const analysis = await runCli(
|
|
2333
|
+
`mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(screenshotMatch[0])} --no-meta`
|
|
2334
|
+
);
|
|
2335
|
+
return `Screenshot: ${screenshotMatch[0]}
|
|
2336
|
+
|
|
2337
|
+
${analysis}`;
|
|
2338
|
+
}
|
|
2339
|
+
case "searchStockPhotos": {
|
|
2340
|
+
const encodedQuery = encodeURIComponent(input.query);
|
|
2341
|
+
return runCli(
|
|
2342
|
+
`mindstudio scrape-url --url "https://www.pexels.com/search/${encodedQuery}/" --page-options '{"onlyMainContent": true}' --no-meta`
|
|
2343
|
+
);
|
|
2344
|
+
}
|
|
2345
|
+
case "searchProductScreenshots": {
|
|
2346
|
+
const query = `${input.product} product screenshot UI 2026`;
|
|
2347
|
+
return runCli(
|
|
2348
|
+
`mindstudio search-google-images --query ${JSON.stringify(query)} --export-type json --output-key images --no-meta`
|
|
2349
|
+
);
|
|
2350
|
+
}
|
|
2351
|
+
case "generateImages": {
|
|
2352
|
+
const prompts = input.prompts;
|
|
2353
|
+
const width = input.width || 2048;
|
|
2354
|
+
const height = input.height || 2048;
|
|
2355
|
+
if (prompts.length === 1) {
|
|
2356
|
+
const step = JSON.stringify({
|
|
2357
|
+
prompt: prompts[0],
|
|
2358
|
+
imageModelOverride: {
|
|
2359
|
+
model: "seedream-4.5",
|
|
2360
|
+
config: { width, height }
|
|
2361
|
+
}
|
|
2362
|
+
});
|
|
2363
|
+
return runCli(
|
|
2364
|
+
`mindstudio generate-image '${step}' --output-key imageUrl --no-meta`
|
|
2365
|
+
);
|
|
2366
|
+
}
|
|
2367
|
+
const steps = prompts.map((prompt) => ({
|
|
2368
|
+
stepType: "generateImage",
|
|
2369
|
+
step: {
|
|
2370
|
+
prompt,
|
|
2371
|
+
imageModelOverride: {
|
|
2372
|
+
model: "seedream-4.5",
|
|
2373
|
+
config: { width, height }
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
}));
|
|
2377
|
+
return runCli(`mindstudio batch '${JSON.stringify(steps)}' --no-meta`);
|
|
2378
|
+
}
|
|
2379
|
+
default:
|
|
2380
|
+
return `Error: unknown tool "${name}"`;
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
var DESIGN_REFERENCE_PROMPT, DESIGN_RESEARCH_TOOLS;
|
|
2384
|
+
var init_tools2 = __esm({
|
|
2385
|
+
"src/subagents/designExpert/tools.ts"() {
|
|
2386
|
+
"use strict";
|
|
2387
|
+
DESIGN_REFERENCE_PROMPT = `Analyze this website/app screenshot as a design reference. Assess:
|
|
2388
|
+
1) Mood/aesthetic
|
|
2389
|
+
2) Color palette with approximate hex values and palette strategy
|
|
2390
|
+
3) Typography style
|
|
2391
|
+
4) Layout composition (symmetric/asymmetric, grid structure, whitespace usage, content density)
|
|
2392
|
+
5) What makes it distinctive and interesting vs generic AI-generated interfaces
|
|
2393
|
+
Be specific and concise.`;
|
|
2394
|
+
DESIGN_RESEARCH_TOOLS = [
|
|
2395
|
+
{
|
|
2396
|
+
name: "searchGoogle",
|
|
2397
|
+
description: "Search Google for web results. Use for finding design inspiration, font recommendations, UI patterns, real products in a domain, and reference material.",
|
|
2398
|
+
inputSchema: {
|
|
2399
|
+
type: "object",
|
|
2400
|
+
properties: {
|
|
2401
|
+
query: {
|
|
2402
|
+
type: "string",
|
|
2403
|
+
description: "The search query."
|
|
2404
|
+
}
|
|
2405
|
+
},
|
|
2406
|
+
required: ["query"]
|
|
2407
|
+
}
|
|
2408
|
+
},
|
|
2409
|
+
{
|
|
2410
|
+
name: "fetchUrl",
|
|
2411
|
+
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.",
|
|
2412
|
+
inputSchema: {
|
|
2413
|
+
type: "object",
|
|
2414
|
+
properties: {
|
|
2415
|
+
url: {
|
|
2416
|
+
type: "string",
|
|
2417
|
+
description: "The URL to fetch."
|
|
2418
|
+
},
|
|
2419
|
+
screenshot: {
|
|
2420
|
+
type: "boolean",
|
|
2421
|
+
description: "Capture a screenshot of the page. Use when you need to see the visual design, not just the text."
|
|
2422
|
+
}
|
|
2423
|
+
},
|
|
2424
|
+
required: ["url"]
|
|
2425
|
+
}
|
|
2426
|
+
},
|
|
2427
|
+
{
|
|
2428
|
+
name: "analyzeImage",
|
|
2429
|
+
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").',
|
|
2430
|
+
inputSchema: {
|
|
2431
|
+
type: "object",
|
|
2432
|
+
properties: {
|
|
2433
|
+
prompt: {
|
|
2434
|
+
type: "string",
|
|
2435
|
+
description: "What to analyze or extract from the image."
|
|
2436
|
+
},
|
|
2437
|
+
imageUrl: {
|
|
2438
|
+
type: "string",
|
|
2439
|
+
description: "URL of the image to analyze."
|
|
2440
|
+
}
|
|
2441
|
+
},
|
|
2442
|
+
required: ["prompt", "imageUrl"]
|
|
2443
|
+
}
|
|
2444
|
+
},
|
|
2445
|
+
{
|
|
2446
|
+
name: "analyzeDesignReference",
|
|
2447
|
+
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.",
|
|
2448
|
+
inputSchema: {
|
|
2449
|
+
type: "object",
|
|
2450
|
+
properties: {
|
|
2451
|
+
imageUrl: {
|
|
2452
|
+
type: "string",
|
|
2453
|
+
description: "URL of the screenshot or design image to analyze."
|
|
2454
|
+
}
|
|
2455
|
+
},
|
|
2456
|
+
required: ["imageUrl"]
|
|
2457
|
+
}
|
|
2458
|
+
},
|
|
2459
|
+
{
|
|
2460
|
+
name: "screenshotAndAnalyze",
|
|
2461
|
+
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.",
|
|
2462
|
+
inputSchema: {
|
|
2463
|
+
type: "object",
|
|
2464
|
+
properties: {
|
|
2465
|
+
url: {
|
|
2466
|
+
type: "string",
|
|
2467
|
+
description: "The URL to screenshot."
|
|
2468
|
+
},
|
|
2469
|
+
prompt: {
|
|
2470
|
+
type: "string",
|
|
2471
|
+
description: "Optional custom analysis prompt. If omitted, performs the standard design reference analysis."
|
|
2472
|
+
}
|
|
2473
|
+
},
|
|
2474
|
+
required: ["url"]
|
|
2475
|
+
}
|
|
2476
|
+
},
|
|
1941
2477
|
{
|
|
1942
|
-
name: "
|
|
1943
|
-
description:
|
|
2478
|
+
name: "searchStockPhotos",
|
|
2479
|
+
description: 'Search Pexels for stock photos. Returns image URLs with descriptions. Use concrete, descriptive queries ("person working at laptop in modern office" not "business").',
|
|
1944
2480
|
inputSchema: {
|
|
1945
2481
|
type: "object",
|
|
1946
2482
|
properties: {
|
|
1947
|
-
|
|
1948
|
-
type: "
|
|
1949
|
-
|
|
1950
|
-
type: "object",
|
|
1951
|
-
properties: {
|
|
1952
|
-
command: {
|
|
1953
|
-
type: "string",
|
|
1954
|
-
enum: ["snapshot", "click", "type", "wait", "evaluate"],
|
|
1955
|
-
description: "snapshot: accessibility tree of the page (waits for network to settle). click: click an element (animated cursor, full event sequence). type: type text into input (one char at a time, works with React/Vue/Svelte). wait: wait for an element to appear (polls 100ms, waits for network). evaluate: run JS in the page."
|
|
1956
|
-
},
|
|
1957
|
-
ref: {
|
|
1958
|
-
type: "string",
|
|
1959
|
-
description: "Element ref from the last snapshot (most reliable targeting)."
|
|
1960
|
-
},
|
|
1961
|
-
text: {
|
|
1962
|
-
type: "string",
|
|
1963
|
-
description: "For click/wait: match by accessible name or visible text. For type: the text to type."
|
|
1964
|
-
},
|
|
1965
|
-
role: {
|
|
1966
|
-
type: "string",
|
|
1967
|
-
description: "ARIA role to match (used with text for role+text targeting)."
|
|
1968
|
-
},
|
|
1969
|
-
label: {
|
|
1970
|
-
type: "string",
|
|
1971
|
-
description: "Find an input by its associated label text."
|
|
1972
|
-
},
|
|
1973
|
-
selector: {
|
|
1974
|
-
type: "string",
|
|
1975
|
-
description: "CSS selector fallback (last resort)."
|
|
1976
|
-
},
|
|
1977
|
-
clear: {
|
|
1978
|
-
type: "boolean",
|
|
1979
|
-
description: "For type: clear the field before typing."
|
|
1980
|
-
},
|
|
1981
|
-
timeout: {
|
|
1982
|
-
type: "number",
|
|
1983
|
-
description: "For wait: timeout in ms (default 5000)."
|
|
1984
|
-
},
|
|
1985
|
-
script: {
|
|
1986
|
-
type: "string",
|
|
1987
|
-
description: "For evaluate: JavaScript to run in the page."
|
|
1988
|
-
}
|
|
1989
|
-
},
|
|
1990
|
-
required: ["command"]
|
|
1991
|
-
}
|
|
2483
|
+
query: {
|
|
2484
|
+
type: "string",
|
|
2485
|
+
description: "What kind of photo to search for."
|
|
1992
2486
|
}
|
|
1993
2487
|
},
|
|
1994
|
-
required: ["
|
|
2488
|
+
required: ["query"]
|
|
1995
2489
|
}
|
|
1996
2490
|
},
|
|
1997
2491
|
{
|
|
1998
|
-
name: "
|
|
1999
|
-
description:
|
|
2492
|
+
name: "searchProductScreenshots",
|
|
2493
|
+
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.',
|
|
2000
2494
|
inputSchema: {
|
|
2001
2495
|
type: "object",
|
|
2002
|
-
properties: {
|
|
2496
|
+
properties: {
|
|
2497
|
+
product: {
|
|
2498
|
+
type: "string",
|
|
2499
|
+
description: 'The product or app to find screenshots of (e.g., "stripe dashboard", "figma editor", "mercury banking app").'
|
|
2500
|
+
}
|
|
2501
|
+
},
|
|
2502
|
+
required: ["product"]
|
|
2503
|
+
}
|
|
2504
|
+
},
|
|
2505
|
+
{
|
|
2506
|
+
name: "generateImages",
|
|
2507
|
+
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.",
|
|
2508
|
+
inputSchema: {
|
|
2509
|
+
type: "object",
|
|
2510
|
+
properties: {
|
|
2511
|
+
prompts: {
|
|
2512
|
+
type: "array",
|
|
2513
|
+
items: {
|
|
2514
|
+
type: "string"
|
|
2515
|
+
},
|
|
2516
|
+
description: "One or more image generation prompts. Be detailed: describe style, mood, composition, colors. Multiple prompts run in parallel."
|
|
2517
|
+
},
|
|
2518
|
+
width: {
|
|
2519
|
+
type: "number",
|
|
2520
|
+
description: "Image width in pixels. Default 2048. Range: 2048-4096."
|
|
2521
|
+
},
|
|
2522
|
+
height: {
|
|
2523
|
+
type: "number",
|
|
2524
|
+
description: "Image height in pixels. Default 2048. Range: 2048-4096."
|
|
2525
|
+
}
|
|
2526
|
+
},
|
|
2527
|
+
required: ["prompts"]
|
|
2003
2528
|
}
|
|
2004
2529
|
}
|
|
2005
2530
|
];
|
|
2006
|
-
BROWSER_EXTERNAL_TOOLS = /* @__PURE__ */ new Set(["browserCommand", "screenshot"]);
|
|
2007
2531
|
}
|
|
2008
2532
|
});
|
|
2009
2533
|
|
|
2010
|
-
// src/subagents/
|
|
2011
|
-
import
|
|
2012
|
-
import
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
"
|
|
2534
|
+
// src/subagents/designExpert/prompt.ts
|
|
2535
|
+
import fs11 from "fs";
|
|
2536
|
+
import path5 from "path";
|
|
2537
|
+
function resolvePath(filename) {
|
|
2538
|
+
const local2 = path5.join(base2, filename);
|
|
2539
|
+
return fs11.existsSync(local2) ? local2 : path5.join(base2, "subagents", "designExpert", filename);
|
|
2540
|
+
}
|
|
2541
|
+
function readFile(filename) {
|
|
2542
|
+
return fs11.readFileSync(resolvePath(filename), "utf-8").trim();
|
|
2543
|
+
}
|
|
2544
|
+
function readJson(filename, fallback) {
|
|
2545
|
+
try {
|
|
2546
|
+
return JSON.parse(fs11.readFileSync(resolvePath(filename), "utf-8"));
|
|
2547
|
+
} catch {
|
|
2548
|
+
return fallback;
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
function sample(arr, n) {
|
|
2552
|
+
if (arr.length <= n) {
|
|
2553
|
+
return [...arr];
|
|
2554
|
+
}
|
|
2555
|
+
const copy = [...arr];
|
|
2556
|
+
for (let i = copy.length - 1; i > 0; i--) {
|
|
2557
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
2558
|
+
[copy[i], copy[j]] = [copy[j], copy[i]];
|
|
2559
|
+
}
|
|
2560
|
+
return copy.slice(0, n);
|
|
2561
|
+
}
|
|
2562
|
+
function getDesignResearchPrompt() {
|
|
2563
|
+
const fonts = sample(fontData.fonts, 30);
|
|
2564
|
+
const pairings = sample(fontData.pairings, 20);
|
|
2565
|
+
const images = sample(inspirationImages, 40);
|
|
2566
|
+
const fontList = fonts.map((f) => {
|
|
2567
|
+
const tags = f.tags.length ? ` (${f.tags.join(", ")})` : "";
|
|
2568
|
+
let cssInfo = "";
|
|
2569
|
+
if (f.source === "fontshare") {
|
|
2570
|
+
cssInfo = ` CSS: ${fontData.cssUrlPattern.replace("{slug}", f.slug).replace("{weights}", f.weights.join(","))}`;
|
|
2571
|
+
} else if (f.cssUrl) {
|
|
2572
|
+
cssInfo = ` CSS: ${f.cssUrl}`;
|
|
2573
|
+
} else if (f.source === "open-foundry") {
|
|
2574
|
+
cssInfo = " (self-host required)";
|
|
2575
|
+
}
|
|
2576
|
+
return `- **${f.name}** \u2014 ${f.category}${tags}. Weights: ${f.weights.join(", ")}.${f.variable ? " Variable." : ""}${f.italics ? " Has italics." : ""}${cssInfo}`;
|
|
2577
|
+
}).join("\n");
|
|
2578
|
+
const pairingList = pairings.map(
|
|
2579
|
+
(p) => `- **${p.heading.font}** (${p.heading.weight}) heading + **${p.body.font}** (${p.body.weight}) body`
|
|
2580
|
+
).join("\n");
|
|
2581
|
+
const fontsSection = fonts.length ? `<fonts_to_consider>
|
|
2582
|
+
## Fonts to consider
|
|
2583
|
+
|
|
2584
|
+
A random sample from Fontshare, Open Foundry, and Google Fonts. Use these as starting points for font selection.
|
|
2585
|
+
CSS URL pattern: ${fontData.cssUrlPattern}
|
|
2586
|
+
|
|
2587
|
+
${fontList}
|
|
2588
|
+
|
|
2589
|
+
### Suggested pairings
|
|
2590
|
+
|
|
2591
|
+
${pairingList}
|
|
2592
|
+
</fonts_to_consider>` : "";
|
|
2593
|
+
const imageList = images.map((img) => `- ${img.analysis}`).join("\n\n");
|
|
2594
|
+
const inspirationSection = images.length ? `<inspiration_images>
|
|
2595
|
+
## Design inspiration
|
|
2596
|
+
|
|
2597
|
+
A random sample of pre-analyzed design references. Use these observations to inform your recommendations.
|
|
2598
|
+
|
|
2599
|
+
${imageList}
|
|
2600
|
+
</inspiration_images>` : "";
|
|
2601
|
+
return PROMPT_TEMPLATE.replace("{{fonts_to_consider}}", fontsSection).replace(
|
|
2602
|
+
"{{inspiration_images}}",
|
|
2603
|
+
inspirationSection
|
|
2604
|
+
);
|
|
2605
|
+
}
|
|
2606
|
+
var base2, RUNTIME_PLACEHOLDERS, PROMPT_TEMPLATE, fontData, inspirationImages;
|
|
2607
|
+
var init_prompt2 = __esm({
|
|
2608
|
+
"src/subagents/designExpert/prompt.ts"() {
|
|
2016
2609
|
"use strict";
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2610
|
+
base2 = import.meta.dirname ?? path5.dirname(new URL(import.meta.url).pathname);
|
|
2611
|
+
RUNTIME_PLACEHOLDERS = /* @__PURE__ */ new Set([
|
|
2612
|
+
"fonts_to_consider",
|
|
2613
|
+
"inspiration_images"
|
|
2614
|
+
]);
|
|
2615
|
+
PROMPT_TEMPLATE = readFile("prompt.md").replace(/\{\{([^}]+)\}\}/g, (match, key) => {
|
|
2616
|
+
const k = key.trim();
|
|
2617
|
+
return RUNTIME_PLACEHOLDERS.has(k) ? match : readFile(k);
|
|
2618
|
+
}).replace(/\n{3,}/g, "\n\n");
|
|
2619
|
+
fontData = readJson("data/fonts.json", {
|
|
2620
|
+
cssUrlPattern: "",
|
|
2621
|
+
fonts: [],
|
|
2622
|
+
pairings: []
|
|
2623
|
+
});
|
|
2624
|
+
inspirationImages = readJson("data/inspiration.json", {
|
|
2625
|
+
images: []
|
|
2626
|
+
}).images;
|
|
2021
2627
|
}
|
|
2022
2628
|
});
|
|
2023
2629
|
|
|
2024
|
-
// src/subagents/
|
|
2025
|
-
var
|
|
2026
|
-
var
|
|
2027
|
-
"src/subagents/
|
|
2630
|
+
// src/subagents/designExpert/index.ts
|
|
2631
|
+
var DESCRIPTION, designExpertTool;
|
|
2632
|
+
var init_designExpert = __esm({
|
|
2633
|
+
"src/subagents/designExpert/index.ts"() {
|
|
2028
2634
|
"use strict";
|
|
2029
2635
|
init_runner();
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2636
|
+
init_tools2();
|
|
2637
|
+
init_prompt2();
|
|
2638
|
+
DESCRIPTION = `
|
|
2639
|
+
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.
|
|
2640
|
+
|
|
2641
|
+
The visual design expert can be used for all things visual design, from quick questions to comprehensive plans:
|
|
2642
|
+
- Font selection and pairings ("suggest fonts for a <x> app")
|
|
2643
|
+
- Color palettes from a mood, domain, or seed color ("earthy tones for a <x> brand")
|
|
2644
|
+
- Gradient, animation, and visual effect recommendations
|
|
2645
|
+
- Layout and composition ideas that go beyond generic AI defaults
|
|
2646
|
+
- Analyzing a reference site or screenshot for design insights (it can take screenshots and do research on its own)
|
|
2647
|
+
- Sourcing imagery: stock photos, AI-generated images, or product screenshots for reference
|
|
2648
|
+
- Icon recommendations
|
|
2649
|
+
- Proposing full visual directions during intake
|
|
2650
|
+
|
|
2651
|
+
**How to write the task:**
|
|
2652
|
+
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.
|
|
2653
|
+
|
|
2654
|
+
**What it returns:**
|
|
2655
|
+
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.
|
|
2656
|
+
`.trim();
|
|
2657
|
+
designExpertTool = {
|
|
2033
2658
|
definition: {
|
|
2034
|
-
name: "
|
|
2035
|
-
description:
|
|
2659
|
+
name: "visualDesignExpert",
|
|
2660
|
+
description: DESCRIPTION,
|
|
2036
2661
|
inputSchema: {
|
|
2037
2662
|
type: "object",
|
|
2038
2663
|
properties: {
|
|
2039
2664
|
task: {
|
|
2040
2665
|
type: "string",
|
|
2041
|
-
description: "What
|
|
2666
|
+
description: "What you need, in natural language. Include context about the app when relevant."
|
|
2042
2667
|
}
|
|
2043
2668
|
},
|
|
2044
2669
|
required: ["task"]
|
|
@@ -2046,14 +2671,14 @@ var init_browserAutomation = __esm({
|
|
|
2046
2671
|
},
|
|
2047
2672
|
async execute(input, context) {
|
|
2048
2673
|
if (!context) {
|
|
2049
|
-
return "Error:
|
|
2674
|
+
return "Error: design research requires execution context";
|
|
2050
2675
|
}
|
|
2051
2676
|
return runSubAgent({
|
|
2052
|
-
system:
|
|
2677
|
+
system: getDesignResearchPrompt(),
|
|
2053
2678
|
task: input.task,
|
|
2054
|
-
tools:
|
|
2055
|
-
externalTools:
|
|
2056
|
-
executeTool:
|
|
2679
|
+
tools: DESIGN_RESEARCH_TOOLS,
|
|
2680
|
+
externalTools: /* @__PURE__ */ new Set(),
|
|
2681
|
+
executeTool: executeDesignTool,
|
|
2057
2682
|
apiConfig: context.apiConfig,
|
|
2058
2683
|
model: context.model,
|
|
2059
2684
|
signal: context.signal,
|
|
@@ -2080,7 +2705,6 @@ function getCodeTools() {
|
|
|
2080
2705
|
globTool,
|
|
2081
2706
|
listDirTool,
|
|
2082
2707
|
editsFinishedTool,
|
|
2083
|
-
askMindStudioSdkTool,
|
|
2084
2708
|
runScenarioTool,
|
|
2085
2709
|
runMethodTool,
|
|
2086
2710
|
screenshotTool,
|
|
@@ -2095,7 +2719,12 @@ function getCommonTools() {
|
|
|
2095
2719
|
return [
|
|
2096
2720
|
setProjectOnboardingStateTool,
|
|
2097
2721
|
promptUserTool,
|
|
2098
|
-
confirmDestructiveActionTool
|
|
2722
|
+
confirmDestructiveActionTool,
|
|
2723
|
+
askMindStudioSdkTool,
|
|
2724
|
+
fetchUrlTool,
|
|
2725
|
+
searchGoogleTool,
|
|
2726
|
+
setProjectNameTool,
|
|
2727
|
+
designExpertTool
|
|
2099
2728
|
];
|
|
2100
2729
|
}
|
|
2101
2730
|
function getPostOnboardingTools() {
|
|
@@ -2140,20 +2769,24 @@ function executeTool(name, input, context) {
|
|
|
2140
2769
|
}
|
|
2141
2770
|
return tool.execute(input, context);
|
|
2142
2771
|
}
|
|
2143
|
-
var
|
|
2772
|
+
var init_tools3 = __esm({
|
|
2144
2773
|
"src/tools/index.ts"() {
|
|
2145
2774
|
"use strict";
|
|
2146
2775
|
init_readSpec();
|
|
2147
2776
|
init_writeSpec();
|
|
2148
2777
|
init_editSpec();
|
|
2149
2778
|
init_listSpecFiles();
|
|
2150
|
-
init_setProjectOnboardingState();
|
|
2151
|
-
init_promptUser();
|
|
2152
2779
|
init_clearSyncStatus();
|
|
2153
2780
|
init_presentSyncPlan();
|
|
2154
2781
|
init_presentPublishPlan();
|
|
2155
2782
|
init_presentPlan();
|
|
2783
|
+
init_setProjectOnboardingState();
|
|
2784
|
+
init_promptUser();
|
|
2156
2785
|
init_confirmDestructiveAction();
|
|
2786
|
+
init_askMindStudioSdk();
|
|
2787
|
+
init_fetchUrl();
|
|
2788
|
+
init_searchGoogle();
|
|
2789
|
+
init_setProjectName();
|
|
2157
2790
|
init_readFile();
|
|
2158
2791
|
init_writeFile();
|
|
2159
2792
|
init_editFile();
|
|
@@ -2165,19 +2798,19 @@ var init_tools2 = __esm({
|
|
|
2165
2798
|
init_lsp();
|
|
2166
2799
|
init_lspDiagnostics();
|
|
2167
2800
|
init_restartProcess();
|
|
2168
|
-
init_askMindStudioSdk();
|
|
2169
2801
|
init_runScenario();
|
|
2170
2802
|
init_runMethod();
|
|
2171
2803
|
init_screenshot();
|
|
2172
2804
|
init_browserAutomation();
|
|
2805
|
+
init_designExpert();
|
|
2173
2806
|
}
|
|
2174
2807
|
});
|
|
2175
2808
|
|
|
2176
2809
|
// src/session.ts
|
|
2177
|
-
import
|
|
2810
|
+
import fs12 from "fs";
|
|
2178
2811
|
function loadSession(state) {
|
|
2179
2812
|
try {
|
|
2180
|
-
const raw =
|
|
2813
|
+
const raw = fs12.readFileSync(SESSION_FILE, "utf-8");
|
|
2181
2814
|
const data = JSON.parse(raw);
|
|
2182
2815
|
if (Array.isArray(data.messages) && data.messages.length > 0) {
|
|
2183
2816
|
state.messages = sanitizeMessages(data.messages);
|
|
@@ -2219,7 +2852,7 @@ function sanitizeMessages(messages) {
|
|
|
2219
2852
|
}
|
|
2220
2853
|
function saveSession(state) {
|
|
2221
2854
|
try {
|
|
2222
|
-
|
|
2855
|
+
fs12.writeFileSync(
|
|
2223
2856
|
SESSION_FILE,
|
|
2224
2857
|
JSON.stringify({ messages: state.messages }, null, 2),
|
|
2225
2858
|
"utf-8"
|
|
@@ -2230,7 +2863,7 @@ function saveSession(state) {
|
|
|
2230
2863
|
function clearSession(state) {
|
|
2231
2864
|
state.messages = [];
|
|
2232
2865
|
try {
|
|
2233
|
-
|
|
2866
|
+
fs12.unlinkSync(SESSION_FILE);
|
|
2234
2867
|
} catch {
|
|
2235
2868
|
}
|
|
2236
2869
|
}
|
|
@@ -2411,6 +3044,116 @@ var init_parsePartialJson = __esm({
|
|
|
2411
3044
|
}
|
|
2412
3045
|
});
|
|
2413
3046
|
|
|
3047
|
+
// src/statusWatcher.ts
|
|
3048
|
+
function startStatusWatcher(config) {
|
|
3049
|
+
const { apiConfig, getContext, onStatus, interval = 3e3, signal } = config;
|
|
3050
|
+
let lastLabel = "";
|
|
3051
|
+
let inflight = false;
|
|
3052
|
+
const url = `${apiConfig.baseUrl}/_internal/v2/agent/remy/generate-status`;
|
|
3053
|
+
async function tick() {
|
|
3054
|
+
if (signal?.aborted || inflight) {
|
|
3055
|
+
return;
|
|
3056
|
+
}
|
|
3057
|
+
inflight = true;
|
|
3058
|
+
try {
|
|
3059
|
+
const ctx = getContext();
|
|
3060
|
+
if (!ctx.assistantText && !ctx.lastToolName) {
|
|
3061
|
+
log.debug("Status watcher: no context, skipping");
|
|
3062
|
+
return;
|
|
3063
|
+
}
|
|
3064
|
+
log.debug("Status watcher: requesting label", {
|
|
3065
|
+
textLength: ctx.assistantText.length,
|
|
3066
|
+
lastToolName: ctx.lastToolName
|
|
3067
|
+
});
|
|
3068
|
+
const res = await fetch(url, {
|
|
3069
|
+
method: "POST",
|
|
3070
|
+
headers: {
|
|
3071
|
+
"Content-Type": "application/json",
|
|
3072
|
+
Authorization: `Bearer ${apiConfig.apiKey}`
|
|
3073
|
+
},
|
|
3074
|
+
body: JSON.stringify({
|
|
3075
|
+
assistantText: ctx.assistantText.slice(-500),
|
|
3076
|
+
lastToolName: ctx.lastToolName,
|
|
3077
|
+
lastToolResult: ctx.lastToolResult?.slice(-200)
|
|
3078
|
+
}),
|
|
3079
|
+
signal
|
|
3080
|
+
});
|
|
3081
|
+
if (!res.ok) {
|
|
3082
|
+
log.debug("Status watcher: endpoint returned non-ok", {
|
|
3083
|
+
status: res.status
|
|
3084
|
+
});
|
|
3085
|
+
return;
|
|
3086
|
+
}
|
|
3087
|
+
const data = await res.json();
|
|
3088
|
+
if (!data.label) {
|
|
3089
|
+
log.debug("Status watcher: no label in response");
|
|
3090
|
+
return;
|
|
3091
|
+
}
|
|
3092
|
+
if (data.label === lastLabel) {
|
|
3093
|
+
log.debug("Status watcher: duplicate label, skipping", {
|
|
3094
|
+
label: data.label
|
|
3095
|
+
});
|
|
3096
|
+
return;
|
|
3097
|
+
}
|
|
3098
|
+
lastLabel = data.label;
|
|
3099
|
+
log.debug("Status watcher: emitting", { label: data.label });
|
|
3100
|
+
onStatus(data.label);
|
|
3101
|
+
} catch (err) {
|
|
3102
|
+
log.debug("Status watcher: error", { error: err?.message ?? "unknown" });
|
|
3103
|
+
} finally {
|
|
3104
|
+
inflight = false;
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
const timer = setInterval(tick, interval);
|
|
3108
|
+
tick().catch(() => {
|
|
3109
|
+
});
|
|
3110
|
+
log.debug("Status watcher started", { interval });
|
|
3111
|
+
return {
|
|
3112
|
+
stop() {
|
|
3113
|
+
clearInterval(timer);
|
|
3114
|
+
log.debug("Status watcher stopped");
|
|
3115
|
+
}
|
|
3116
|
+
};
|
|
3117
|
+
}
|
|
3118
|
+
var init_statusWatcher = __esm({
|
|
3119
|
+
"src/statusWatcher.ts"() {
|
|
3120
|
+
"use strict";
|
|
3121
|
+
init_logger();
|
|
3122
|
+
}
|
|
3123
|
+
});
|
|
3124
|
+
|
|
3125
|
+
// src/errors.ts
|
|
3126
|
+
function friendlyError(raw) {
|
|
3127
|
+
for (const [pattern, message] of patterns) {
|
|
3128
|
+
if (pattern.test(raw)) {
|
|
3129
|
+
return message;
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
return `Something went wrong: ${raw}`;
|
|
3133
|
+
}
|
|
3134
|
+
var patterns;
|
|
3135
|
+
var init_errors = __esm({
|
|
3136
|
+
"src/errors.ts"() {
|
|
3137
|
+
"use strict";
|
|
3138
|
+
patterns = [
|
|
3139
|
+
[
|
|
3140
|
+
/Network error/i,
|
|
3141
|
+
"Lost connection to the server. Please check your internet connection and try again."
|
|
3142
|
+
],
|
|
3143
|
+
[
|
|
3144
|
+
/HTTP 429|rate limit/i,
|
|
3145
|
+
"Too many requests. Please wait a moment and try again."
|
|
3146
|
+
],
|
|
3147
|
+
[/HTTP 40[13]/i, "Authentication failed. Please check your API key."],
|
|
3148
|
+
[
|
|
3149
|
+
/HTTP 5\d\d/i,
|
|
3150
|
+
"The AI service is temporarily unavailable. Please try again."
|
|
3151
|
+
],
|
|
3152
|
+
[/Stream stalled/i, "The connection was interrupted. Please try again."]
|
|
3153
|
+
];
|
|
3154
|
+
}
|
|
3155
|
+
});
|
|
3156
|
+
|
|
2414
3157
|
// src/agent.ts
|
|
2415
3158
|
function createAgentState() {
|
|
2416
3159
|
return { messages: [] };
|
|
@@ -2440,7 +3183,8 @@ async function runTurn(params) {
|
|
|
2440
3183
|
}
|
|
2441
3184
|
});
|
|
2442
3185
|
onEvent({ type: "turn_started" });
|
|
2443
|
-
const
|
|
3186
|
+
const cleanMessage = userMessage.replace(/^@@automated::[^@]*@@/, "");
|
|
3187
|
+
const userMsg = { role: "user", content: cleanMessage };
|
|
2444
3188
|
if (hidden) {
|
|
2445
3189
|
userMsg.hidden = true;
|
|
2446
3190
|
}
|
|
@@ -2534,15 +3278,34 @@ async function runTurn(params) {
|
|
|
2534
3278
|
onEvent({ type: "tool_input_delta", id, name, result: content });
|
|
2535
3279
|
}
|
|
2536
3280
|
}
|
|
3281
|
+
const statusWatcher = startStatusWatcher({
|
|
3282
|
+
apiConfig,
|
|
3283
|
+
getContext: () => ({
|
|
3284
|
+
assistantText: assistantText.slice(-500),
|
|
3285
|
+
lastToolName: toolCalls.at(-1)?.name
|
|
3286
|
+
}),
|
|
3287
|
+
onStatus: (label) => onEvent({ type: "status", message: label }),
|
|
3288
|
+
signal
|
|
3289
|
+
});
|
|
2537
3290
|
try {
|
|
2538
|
-
for await (const event of
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
3291
|
+
for await (const event of streamChatWithRetry(
|
|
3292
|
+
{
|
|
3293
|
+
...apiConfig,
|
|
3294
|
+
model,
|
|
3295
|
+
system,
|
|
3296
|
+
messages: state.messages,
|
|
3297
|
+
tools,
|
|
3298
|
+
signal
|
|
3299
|
+
},
|
|
3300
|
+
{
|
|
3301
|
+
onRetry: (attempt) => {
|
|
3302
|
+
onEvent({
|
|
3303
|
+
type: "status",
|
|
3304
|
+
message: `Lost connection, retrying (attempt ${attempt + 2} of 3)...`
|
|
3305
|
+
});
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
)) {
|
|
2546
3309
|
if (signal?.aborted) {
|
|
2547
3310
|
break;
|
|
2548
3311
|
}
|
|
@@ -2610,7 +3373,7 @@ async function runTurn(params) {
|
|
|
2610
3373
|
stopReason = event.stopReason;
|
|
2611
3374
|
break;
|
|
2612
3375
|
case "error":
|
|
2613
|
-
onEvent({ type: "error", error: event.error });
|
|
3376
|
+
onEvent({ type: "error", error: friendlyError(event.error) });
|
|
2614
3377
|
return;
|
|
2615
3378
|
}
|
|
2616
3379
|
}
|
|
@@ -2619,6 +3382,8 @@ async function runTurn(params) {
|
|
|
2619
3382
|
} else {
|
|
2620
3383
|
throw err;
|
|
2621
3384
|
}
|
|
3385
|
+
} finally {
|
|
3386
|
+
statusWatcher.stop();
|
|
2622
3387
|
}
|
|
2623
3388
|
if (signal?.aborted) {
|
|
2624
3389
|
if (assistantText) {
|
|
@@ -2646,6 +3411,15 @@ async function runTurn(params) {
|
|
|
2646
3411
|
count: toolCalls.length,
|
|
2647
3412
|
tools: toolCalls.map((tc) => tc.name)
|
|
2648
3413
|
});
|
|
3414
|
+
const toolStatusWatcher = startStatusWatcher({
|
|
3415
|
+
apiConfig,
|
|
3416
|
+
getContext: () => ({
|
|
3417
|
+
assistantText: assistantText.slice(-500),
|
|
3418
|
+
lastToolName: toolCalls.map((tc) => tc.name).join(", ")
|
|
3419
|
+
}),
|
|
3420
|
+
onStatus: (label) => onEvent({ type: "status", message: label }),
|
|
3421
|
+
signal
|
|
3422
|
+
});
|
|
2649
3423
|
const results = await Promise.all(
|
|
2650
3424
|
toolCalls.map(async (tc) => {
|
|
2651
3425
|
if (signal?.aborted) {
|
|
@@ -2703,6 +3477,7 @@ async function runTurn(params) {
|
|
|
2703
3477
|
}
|
|
2704
3478
|
})
|
|
2705
3479
|
);
|
|
3480
|
+
toolStatusWatcher.stop();
|
|
2706
3481
|
for (const r of results) {
|
|
2707
3482
|
state.messages.push({
|
|
2708
3483
|
role: "user",
|
|
@@ -2723,10 +3498,12 @@ var init_agent = __esm({
|
|
|
2723
3498
|
"src/agent.ts"() {
|
|
2724
3499
|
"use strict";
|
|
2725
3500
|
init_api();
|
|
2726
|
-
|
|
3501
|
+
init_tools3();
|
|
2727
3502
|
init_session();
|
|
2728
3503
|
init_logger();
|
|
2729
3504
|
init_parsePartialJson();
|
|
3505
|
+
init_statusWatcher();
|
|
3506
|
+
init_errors();
|
|
2730
3507
|
EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
|
|
2731
3508
|
"promptUser",
|
|
2732
3509
|
"setProjectOnboardingState",
|
|
@@ -2738,18 +3515,19 @@ var init_agent = __esm({
|
|
|
2738
3515
|
"runScenario",
|
|
2739
3516
|
"runMethod",
|
|
2740
3517
|
"browserCommand",
|
|
2741
|
-
"screenshot"
|
|
3518
|
+
"screenshot",
|
|
3519
|
+
"setProjectName"
|
|
2742
3520
|
]);
|
|
2743
3521
|
}
|
|
2744
3522
|
});
|
|
2745
3523
|
|
|
2746
3524
|
// src/prompt/static/projectContext.ts
|
|
2747
|
-
import
|
|
2748
|
-
import
|
|
3525
|
+
import fs13 from "fs";
|
|
3526
|
+
import path6 from "path";
|
|
2749
3527
|
function loadProjectInstructions() {
|
|
2750
3528
|
for (const file of AGENT_INSTRUCTION_FILES) {
|
|
2751
3529
|
try {
|
|
2752
|
-
const content =
|
|
3530
|
+
const content = fs13.readFileSync(file, "utf-8").trim();
|
|
2753
3531
|
if (content) {
|
|
2754
3532
|
return `
|
|
2755
3533
|
## Project Instructions (${file})
|
|
@@ -2762,7 +3540,7 @@ ${content}`;
|
|
|
2762
3540
|
}
|
|
2763
3541
|
function loadProjectManifest() {
|
|
2764
3542
|
try {
|
|
2765
|
-
const manifest =
|
|
3543
|
+
const manifest = fs13.readFileSync("mindstudio.json", "utf-8");
|
|
2766
3544
|
return `
|
|
2767
3545
|
## Project Manifest (mindstudio.json)
|
|
2768
3546
|
\`\`\`json
|
|
@@ -2780,11 +3558,14 @@ function loadSpecFileMetadata() {
|
|
|
2780
3558
|
}
|
|
2781
3559
|
const entries = [];
|
|
2782
3560
|
for (const filePath of files) {
|
|
2783
|
-
const { name, description } = parseFrontmatter(filePath);
|
|
3561
|
+
const { name, description, type } = parseFrontmatter(filePath);
|
|
2784
3562
|
let line = `- ${filePath}`;
|
|
2785
3563
|
if (name) {
|
|
2786
3564
|
line += ` \u2014 "${name}"`;
|
|
2787
3565
|
}
|
|
3566
|
+
if (type) {
|
|
3567
|
+
line += ` (${type})`;
|
|
3568
|
+
}
|
|
2788
3569
|
if (description) {
|
|
2789
3570
|
line += ` \u2014 ${description}`;
|
|
2790
3571
|
}
|
|
@@ -2800,9 +3581,9 @@ ${entries.join("\n")}`;
|
|
|
2800
3581
|
function walkMdFiles(dir) {
|
|
2801
3582
|
const results = [];
|
|
2802
3583
|
try {
|
|
2803
|
-
const entries =
|
|
3584
|
+
const entries = fs13.readdirSync(dir, { withFileTypes: true });
|
|
2804
3585
|
for (const entry of entries) {
|
|
2805
|
-
const full =
|
|
3586
|
+
const full = path6.join(dir, entry.name);
|
|
2806
3587
|
if (entry.isDirectory()) {
|
|
2807
3588
|
results.push(...walkMdFiles(full));
|
|
2808
3589
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -2815,22 +3596,23 @@ function walkMdFiles(dir) {
|
|
|
2815
3596
|
}
|
|
2816
3597
|
function parseFrontmatter(filePath) {
|
|
2817
3598
|
try {
|
|
2818
|
-
const content =
|
|
3599
|
+
const content = fs13.readFileSync(filePath, "utf-8");
|
|
2819
3600
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
2820
3601
|
if (!match) {
|
|
2821
|
-
return { name: "", description: "" };
|
|
3602
|
+
return { name: "", description: "", type: "" };
|
|
2822
3603
|
}
|
|
2823
3604
|
const fm = match[1];
|
|
2824
3605
|
const name = fm.match(/^name:\s*(.+)$/m)?.[1]?.trim() ?? "";
|
|
2825
3606
|
const description = fm.match(/^description:\s*(.+)$/m)?.[1]?.trim() ?? "";
|
|
2826
|
-
|
|
3607
|
+
const type = fm.match(/^type:\s*(.+)$/m)?.[1]?.trim() ?? "";
|
|
3608
|
+
return { name, description, type };
|
|
2827
3609
|
} catch {
|
|
2828
|
-
return { name: "", description: "" };
|
|
3610
|
+
return { name: "", description: "", type: "" };
|
|
2829
3611
|
}
|
|
2830
3612
|
}
|
|
2831
3613
|
function loadProjectFileListing() {
|
|
2832
3614
|
try {
|
|
2833
|
-
const entries =
|
|
3615
|
+
const entries = fs13.readdirSync(".", { withFileTypes: true });
|
|
2834
3616
|
const listing = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules").sort((a, b) => {
|
|
2835
3617
|
if (a.isDirectory() && !b.isDirectory()) {
|
|
2836
3618
|
return -1;
|
|
@@ -2873,12 +3655,12 @@ var init_projectContext = __esm({
|
|
|
2873
3655
|
});
|
|
2874
3656
|
|
|
2875
3657
|
// src/prompt/index.ts
|
|
2876
|
-
import
|
|
2877
|
-
import
|
|
3658
|
+
import fs14 from "fs";
|
|
3659
|
+
import path7 from "path";
|
|
2878
3660
|
function requireFile(filePath) {
|
|
2879
|
-
const full =
|
|
3661
|
+
const full = path7.join(PROMPT_DIR, filePath);
|
|
2880
3662
|
try {
|
|
2881
|
-
return
|
|
3663
|
+
return fs14.readFileSync(full, "utf-8").trim();
|
|
2882
3664
|
} catch {
|
|
2883
3665
|
throw new Error(`Required prompt file missing: ${full}`);
|
|
2884
3666
|
}
|
|
@@ -2998,22 +3780,22 @@ ${viewContext?.activeFile ? `Active file: ${viewContext.activeFile}` : ""}
|
|
|
2998
3780
|
return resolveIncludes(template);
|
|
2999
3781
|
}
|
|
3000
3782
|
var PROMPT_DIR;
|
|
3001
|
-
var
|
|
3783
|
+
var init_prompt3 = __esm({
|
|
3002
3784
|
"src/prompt/index.ts"() {
|
|
3003
3785
|
"use strict";
|
|
3004
3786
|
init_lsp();
|
|
3005
3787
|
init_projectContext();
|
|
3006
|
-
PROMPT_DIR = import.meta.dirname ??
|
|
3788
|
+
PROMPT_DIR = import.meta.dirname ?? path7.dirname(new URL(import.meta.url).pathname);
|
|
3007
3789
|
}
|
|
3008
3790
|
});
|
|
3009
3791
|
|
|
3010
3792
|
// src/config.ts
|
|
3011
|
-
import
|
|
3012
|
-
import
|
|
3793
|
+
import fs15 from "fs";
|
|
3794
|
+
import path8 from "path";
|
|
3013
3795
|
import os from "os";
|
|
3014
3796
|
function loadConfigFile() {
|
|
3015
3797
|
try {
|
|
3016
|
-
const raw =
|
|
3798
|
+
const raw = fs15.readFileSync(CONFIG_PATH, "utf-8");
|
|
3017
3799
|
log.debug("Loaded config file", { path: CONFIG_PATH });
|
|
3018
3800
|
return JSON.parse(raw);
|
|
3019
3801
|
} catch (err) {
|
|
@@ -3049,7 +3831,7 @@ var init_config = __esm({
|
|
|
3049
3831
|
"src/config.ts"() {
|
|
3050
3832
|
"use strict";
|
|
3051
3833
|
init_logger();
|
|
3052
|
-
CONFIG_PATH =
|
|
3834
|
+
CONFIG_PATH = path8.join(
|
|
3053
3835
|
os.homedir(),
|
|
3054
3836
|
".mindstudio-local-tunnel",
|
|
3055
3837
|
"config.json"
|
|
@@ -3064,10 +3846,10 @@ __export(headless_exports, {
|
|
|
3064
3846
|
startHeadless: () => startHeadless
|
|
3065
3847
|
});
|
|
3066
3848
|
import { createInterface } from "readline";
|
|
3067
|
-
import
|
|
3068
|
-
import
|
|
3849
|
+
import fs16 from "fs";
|
|
3850
|
+
import path9 from "path";
|
|
3069
3851
|
function loadActionPrompt(name) {
|
|
3070
|
-
return
|
|
3852
|
+
return fs16.readFileSync(path9.join(ACTIONS_DIR, `${name}.md`), "utf-8").trim();
|
|
3071
3853
|
}
|
|
3072
3854
|
function emit(event, data) {
|
|
3073
3855
|
process.stdout.write(JSON.stringify({ event, ...data }) + "\n");
|
|
@@ -3095,6 +3877,7 @@ async function startHeadless(opts = {}) {
|
|
|
3095
3877
|
}
|
|
3096
3878
|
let running = false;
|
|
3097
3879
|
let currentAbort = null;
|
|
3880
|
+
const EXTERNAL_TOOL_TIMEOUT_MS = 3e5;
|
|
3098
3881
|
const pendingTools = /* @__PURE__ */ new Map();
|
|
3099
3882
|
const earlyResults = /* @__PURE__ */ new Map();
|
|
3100
3883
|
function onEvent(e) {
|
|
@@ -3149,6 +3932,9 @@ async function startHeadless(opts = {}) {
|
|
|
3149
3932
|
case "error":
|
|
3150
3933
|
emit("error", { error: e.error });
|
|
3151
3934
|
break;
|
|
3935
|
+
case "status":
|
|
3936
|
+
emit("status", { message: e.message });
|
|
3937
|
+
break;
|
|
3152
3938
|
}
|
|
3153
3939
|
}
|
|
3154
3940
|
function resolveExternalTool(id, _name, _input) {
|
|
@@ -3158,7 +3944,19 @@ async function startHeadless(opts = {}) {
|
|
|
3158
3944
|
return Promise.resolve(early);
|
|
3159
3945
|
}
|
|
3160
3946
|
return new Promise((resolve) => {
|
|
3161
|
-
|
|
3947
|
+
const timeout = setTimeout(() => {
|
|
3948
|
+
pendingTools.delete(id);
|
|
3949
|
+
resolve(
|
|
3950
|
+
"Error: Tool timed out \u2014 no response from the app environment after 5 minutes."
|
|
3951
|
+
);
|
|
3952
|
+
}, EXTERNAL_TOOL_TIMEOUT_MS);
|
|
3953
|
+
pendingTools.set(id, {
|
|
3954
|
+
resolve: (result) => {
|
|
3955
|
+
clearTimeout(timeout);
|
|
3956
|
+
resolve(result);
|
|
3957
|
+
},
|
|
3958
|
+
timeout
|
|
3959
|
+
});
|
|
3162
3960
|
});
|
|
3163
3961
|
}
|
|
3164
3962
|
const rl = createInterface({ input: process.stdin });
|
|
@@ -3196,6 +3994,7 @@ async function startHeadless(opts = {}) {
|
|
|
3196
3994
|
currentAbort.abort();
|
|
3197
3995
|
}
|
|
3198
3996
|
for (const [id, pending] of pendingTools) {
|
|
3997
|
+
clearTimeout(pending.timeout);
|
|
3199
3998
|
pending.resolve("Error: cancelled");
|
|
3200
3999
|
pendingTools.delete(id);
|
|
3201
4000
|
}
|
|
@@ -3265,20 +4064,20 @@ var init_headless = __esm({
|
|
|
3265
4064
|
"src/headless.ts"() {
|
|
3266
4065
|
"use strict";
|
|
3267
4066
|
init_config();
|
|
3268
|
-
|
|
4067
|
+
init_prompt3();
|
|
3269
4068
|
init_lsp();
|
|
3270
4069
|
init_agent();
|
|
3271
4070
|
init_session();
|
|
3272
|
-
BASE_DIR = import.meta.dirname ??
|
|
3273
|
-
ACTIONS_DIR =
|
|
4071
|
+
BASE_DIR = import.meta.dirname ?? path9.dirname(new URL(import.meta.url).pathname);
|
|
4072
|
+
ACTIONS_DIR = path9.join(BASE_DIR, "actions");
|
|
3274
4073
|
}
|
|
3275
4074
|
});
|
|
3276
4075
|
|
|
3277
4076
|
// src/index.tsx
|
|
3278
4077
|
import { render } from "ink";
|
|
3279
4078
|
import os2 from "os";
|
|
3280
|
-
import
|
|
3281
|
-
import
|
|
4079
|
+
import fs17 from "fs";
|
|
4080
|
+
import path10 from "path";
|
|
3282
4081
|
|
|
3283
4082
|
// src/tui/App.tsx
|
|
3284
4083
|
import { useState as useState2, useCallback, useRef } from "react";
|
|
@@ -3413,7 +4212,7 @@ function MessageList({ turns }) {
|
|
|
3413
4212
|
|
|
3414
4213
|
// src/tui/App.tsx
|
|
3415
4214
|
init_agent();
|
|
3416
|
-
|
|
4215
|
+
init_prompt3();
|
|
3417
4216
|
init_session();
|
|
3418
4217
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
3419
4218
|
function App({ apiConfig, model }) {
|
|
@@ -3595,8 +4394,8 @@ for (let i = 0; i < args.length; i++) {
|
|
|
3595
4394
|
}
|
|
3596
4395
|
function printDebugInfo(config) {
|
|
3597
4396
|
const pkg = JSON.parse(
|
|
3598
|
-
|
|
3599
|
-
|
|
4397
|
+
fs17.readFileSync(
|
|
4398
|
+
path10.join(import.meta.dirname, "..", "package.json"),
|
|
3600
4399
|
"utf-8"
|
|
3601
4400
|
)
|
|
3602
4401
|
);
|