@mindstudio-ai/remy 0.1.256 → 0.1.258
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/headless.js +514 -315
- package/dist/index.js +517 -288
- package/dist/prompt/compiled/files.md +17 -3
- package/dist/prompt/compiled/interfaces.md +26 -472
- package/dist/prompt/compiled/methods.md +10 -15
- package/dist/prompt/compiled/scenarios.md +16 -0
- package/dist/prompt/compiled/sdk-actions.md +1 -2
- package/dist/prompt/{compiled/agent-interfaces.md → skills/agentInterfaces.md} +118 -10
- package/dist/prompt/skills/dataSources.md +131 -0
- package/dist/prompt/skills/inboundEmail.md +116 -0
- package/dist/prompt/skills/mcpInterfaces.md +280 -0
- package/dist/prompt/skills/restApi.md +149 -0
- package/dist/prompt/skills/scheduledJobs.md +51 -0
- package/dist/prompt/{compiled/task-agents.md → skills/taskAgents.md} +10 -8
- package/dist/prompt/skills/webhooks.md +108 -0
- package/dist/prompt/static/authoring.md +1 -1
- package/dist/prompt/static/instructions.md +1 -1
- package/dist/subagents/designExpert/prompts/images.md +1 -1
- package/package.json +2 -2
- package/dist/prompt/.notes.md +0 -194
- package/dist/prompt/compiled/README.md +0 -100
- package/dist/prompt/compiled/mcp-interfaces.md +0 -34
- package/dist/prompt/compiled/media-cdn.md +0 -51
- package/dist/prompt/sources/llms.txt +0 -1618
- package/dist/subagents/.notes-background-agents.md +0 -64
- package/dist/subagents/codeSanityCheck/.notes.md +0 -44
- package/dist/subagents/designExpert/.notes.md +0 -265
- package/dist/subagents/productVision/.notes.md +0 -79
package/dist/index.js
CHANGED
|
@@ -266,7 +266,7 @@ function isRetryableError(error) {
|
|
|
266
266
|
return /Network error/i.test(error) || /HTTP 5\d\d/i.test(error) || /Stream stalled/i.test(error) || /overloaded/i.test(error) || /terminated/i.test(error);
|
|
267
267
|
}
|
|
268
268
|
function sleep(ms) {
|
|
269
|
-
return new Promise((
|
|
269
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
270
270
|
}
|
|
271
271
|
async function* streamChatWithRetry(params, options) {
|
|
272
272
|
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
@@ -402,8 +402,8 @@ var init_readSpec = __esm({
|
|
|
402
402
|
init_helpers();
|
|
403
403
|
DEFAULT_MAX_LINES = 500;
|
|
404
404
|
readSpecTool = {
|
|
405
|
-
clearable: true,
|
|
406
405
|
definition: {
|
|
406
|
+
clearable: true,
|
|
407
407
|
name: "readSpec",
|
|
408
408
|
description: "Read a spec file from src/ with line numbers. Always read a spec file before editing it. Paths are relative to the project root and must start with src/ (e.g., src/app.md, src/interfaces/web.md).",
|
|
409
409
|
inputSchema: {
|
|
@@ -535,8 +535,8 @@ var init_writeSpec = __esm({
|
|
|
535
535
|
init_diff();
|
|
536
536
|
init_fileLock();
|
|
537
537
|
writeSpecTool = {
|
|
538
|
-
clearable: true,
|
|
539
538
|
definition: {
|
|
539
|
+
clearable: true,
|
|
540
540
|
name: "writeSpec",
|
|
541
541
|
description: "Create a new spec file or completely overwrite an existing one in src/. Parent directories are created automatically. Use this for new spec files or full rewrites. For targeted changes to existing specs, use editSpec instead.",
|
|
542
542
|
inputSchema: {
|
|
@@ -686,8 +686,8 @@ var init_editSpec = __esm({
|
|
|
686
686
|
init_diff();
|
|
687
687
|
init_fileLock();
|
|
688
688
|
editSpecTool = {
|
|
689
|
-
clearable: true,
|
|
690
689
|
definition: {
|
|
690
|
+
clearable: true,
|
|
691
691
|
name: "editSpec",
|
|
692
692
|
description: "Make a targeted find/replace edit to a spec file (src/*.md). old_string must appear exactly once (minor indentation differences are handled automatically); set replace_all to true to replace every occurrence. Read the file with readSpec first so you match the exact text, and include the full enclosing structure rather than an inner fragment. The file's YAML frontmatter (the leading --- \u2026 --- block, which holds required fields like name) is protected \u2014 an edit that would remove or malform it is refused. For a full rewrite, use writeSpec.",
|
|
693
693
|
inputSchema: {
|
|
@@ -820,8 +820,8 @@ var init_listSpecFiles = __esm({
|
|
|
820
820
|
"src/tools/spec/listSpecFiles.ts"() {
|
|
821
821
|
"use strict";
|
|
822
822
|
listSpecFilesTool = {
|
|
823
|
-
clearable: false,
|
|
824
823
|
definition: {
|
|
824
|
+
clearable: false,
|
|
825
825
|
name: "listSpecFiles",
|
|
826
826
|
description: "List all files in the src/ directory (spec files, brand guidelines, interface specs, references). Use this to understand what spec files exist before reading or editing them.",
|
|
827
827
|
inputSchema: {
|
|
@@ -854,8 +854,8 @@ var init_presentPublishPlan = __esm({
|
|
|
854
854
|
"src/tools/spec/presentPublishPlan.ts"() {
|
|
855
855
|
"use strict";
|
|
856
856
|
presentPublishPlanTool = {
|
|
857
|
-
clearable: false,
|
|
858
857
|
definition: {
|
|
858
|
+
clearable: false,
|
|
859
859
|
name: "presentPublishPlan",
|
|
860
860
|
description: "Present a publish changelog to the user for approval. Write a clear markdown summary of what changed since the last deploy. The user will see this in a full-screen view and can approve or dismiss. Call this BEFORE committing or pushing.",
|
|
861
861
|
inputSchema: {
|
|
@@ -885,8 +885,8 @@ var init_writePlan = __esm({
|
|
|
885
885
|
"use strict";
|
|
886
886
|
PLAN_FILE = ".remy-plan.md";
|
|
887
887
|
writePlanTool = {
|
|
888
|
-
clearable: false,
|
|
889
888
|
definition: {
|
|
889
|
+
clearable: false,
|
|
890
890
|
name: "writePlan",
|
|
891
891
|
description: "Write an implementation plan for user approval before making changes. Use this only for large, multi-step changes like new features, new interface types, or when the user explicitly asks to see a plan. Most work should be done autonomously without a plan. Write a clear markdown summary of what you intend to do in plain language \u2014 describe the changes from the user's perspective, not as a list of files and code paths. The plan is displayed standalone in the UI with approve/reject buttons attached. The plan body ends with its last substantive section \u2014 do not write any closing line addressed to the reader. No 'If this lands, I'll start building,' no 'Let me know what to adjust,' no 'Approve when ready,' no trailing horizontal rule with a sign-off. Wrap-up text belongs in your chat message alongside the plan, not in the plan file. If the user asks for revisions, call this tool again with updated content to overwrite the plan.",
|
|
892
892
|
inputSchema: {
|
|
@@ -922,8 +922,8 @@ var init_updatePlanStatus = __esm({
|
|
|
922
922
|
"use strict";
|
|
923
923
|
PLAN_FILE2 = ".remy-plan.md";
|
|
924
924
|
updatePlanStatusTool = {
|
|
925
|
-
clearable: false,
|
|
926
925
|
definition: {
|
|
926
|
+
clearable: false,
|
|
927
927
|
name: "updatePlanStatus",
|
|
928
928
|
description: 'Update the status of the current implementation plan. Use when the user approves or rejects the plan via chat (e.g. "looks good, go ahead" or "scrap it"). Approving sets the plan to active so you can begin implementation. Rejecting deletes the plan.',
|
|
929
929
|
inputSchema: {
|
|
@@ -970,8 +970,8 @@ var init_setProjectOnboardingState = __esm({
|
|
|
970
970
|
"src/tools/common/setProjectOnboardingState.ts"() {
|
|
971
971
|
"use strict";
|
|
972
972
|
setProjectOnboardingStateTool = {
|
|
973
|
-
clearable: false,
|
|
974
973
|
definition: {
|
|
974
|
+
clearable: false,
|
|
975
975
|
name: "setProjectOnboardingState",
|
|
976
976
|
description: "Advance the project onboarding state. Forward-only: building \u2192 buildComplete \u2192 onboardingFinished. Normally driven by automated actions \u2014 don't call this out of order during a normal build, or you'll skip stages the user hasn't experienced. Exception: if the project has been in `building` for a while, the build is clearly done (the user is iterating on a working app, deploying, etc.), and the user reports the editor seems stuck \u2014 disabled Preview/Spec/Code tabs, no reveal, etc. \u2014 call `setProjectOnboardingState({ state: 'buildComplete' })` to unstick them. `onboardingFinished` is always set by the frontend after the user dismisses the reveal; never call it yourself.",
|
|
977
977
|
inputSchema: {
|
|
@@ -999,8 +999,8 @@ var init_promptUser = __esm({
|
|
|
999
999
|
"src/tools/common/promptUser.ts"() {
|
|
1000
1000
|
"use strict";
|
|
1001
1001
|
promptUserTool = {
|
|
1002
|
-
clearable: false,
|
|
1003
1002
|
definition: {
|
|
1003
|
+
clearable: false,
|
|
1004
1004
|
name: "promptUser",
|
|
1005
1005
|
description: 'Ask the user structured questions. Choose type first: "form" for structured intake (5+ questions, takes over screen), "inline" for quick clarifications or confirmations. Blocks until the user responds. Result contains `_dismissed: true` if the user dismisses without answering.',
|
|
1006
1006
|
inputSchema: {
|
|
@@ -1137,8 +1137,8 @@ var init_confirmDestructiveAction = __esm({
|
|
|
1137
1137
|
"src/tools/common/confirmDestructiveAction.ts"() {
|
|
1138
1138
|
"use strict";
|
|
1139
1139
|
confirmDestructiveActionTool = {
|
|
1140
|
-
clearable: false,
|
|
1141
1140
|
definition: {
|
|
1141
|
+
clearable: false,
|
|
1142
1142
|
name: "confirmDestructiveAction",
|
|
1143
1143
|
description: "Confirm a destructive or irreversible action with the user. Use for things like deleting data, resetting the database, or discarding draft work. Do not use after presentPublishPlan or writePlan (those already include approval). Do not use before onboarding state transitions.",
|
|
1144
1144
|
inputSchema: {
|
|
@@ -1176,7 +1176,7 @@ function formatCliResult(r) {
|
|
|
1176
1176
|
return logBlock + body + truncNote;
|
|
1177
1177
|
}
|
|
1178
1178
|
function runCli(command, args2, options) {
|
|
1179
|
-
return new Promise((
|
|
1179
|
+
return new Promise((resolve3) => {
|
|
1180
1180
|
const timeout = options?.timeout ?? 6e4;
|
|
1181
1181
|
const maxBuffer = options?.maxBuffer ?? 1024 * 1024;
|
|
1182
1182
|
let finalArgs = args2;
|
|
@@ -1205,7 +1205,7 @@ function runCli(command, args2, options) {
|
|
|
1205
1205
|
if (killTimer) {
|
|
1206
1206
|
clearTimeout(killTimer);
|
|
1207
1207
|
}
|
|
1208
|
-
|
|
1208
|
+
resolve3(result);
|
|
1209
1209
|
};
|
|
1210
1210
|
const child = spawn(command, finalArgs, {
|
|
1211
1211
|
stdio: [options?.stdin ? "pipe" : "ignore", "pipe", "pipe"]
|
|
@@ -1324,8 +1324,8 @@ var init_sdkConsultant = __esm({
|
|
|
1324
1324
|
"use strict";
|
|
1325
1325
|
init_runCli();
|
|
1326
1326
|
askMindStudioSdkTool = {
|
|
1327
|
-
clearable: false,
|
|
1328
1327
|
definition: {
|
|
1328
|
+
clearable: false,
|
|
1329
1329
|
name: "askMindStudioSdk",
|
|
1330
1330
|
description: "@mindstudio-ai/agent backend SDK expert. Knows every backend action, AI model, connector, and configuration option. Returns architectural guidance and working code. Only covers the backend SDK (@mindstudio-ai/agent) \u2014 do NOT use for frontend/interface SDK questions (@mindstudio-ai/interface) like file uploads, auth, or client-side APIs. Describe what you want to build, not just what API method you need. Batch related questions into a single query.",
|
|
1331
1331
|
inputSchema: {
|
|
@@ -1486,8 +1486,8 @@ var init_searchGoogle = __esm({
|
|
|
1486
1486
|
init_runMindstudioCli();
|
|
1487
1487
|
init_runCli();
|
|
1488
1488
|
searchGoogleTool = {
|
|
1489
|
-
clearable: false,
|
|
1490
1489
|
definition: {
|
|
1490
|
+
clearable: false,
|
|
1491
1491
|
name: "searchGoogle",
|
|
1492
1492
|
description: "Search Google and return results. Use for research, finding documentation, looking up APIs, or any task where web search would help.",
|
|
1493
1493
|
inputSchema: {
|
|
@@ -1523,8 +1523,8 @@ var init_setProjectMetadata = __esm({
|
|
|
1523
1523
|
"src/tools/common/setProjectMetadata.ts"() {
|
|
1524
1524
|
"use strict";
|
|
1525
1525
|
setProjectMetadataTool = {
|
|
1526
|
-
clearable: false,
|
|
1527
1526
|
definition: {
|
|
1527
|
+
clearable: false,
|
|
1528
1528
|
name: "setProjectMetadata",
|
|
1529
1529
|
description: "Set project metadata. Can update any combination of: display name, short description, app icon, and Open Graph share image. Provide only the fields you want to change.",
|
|
1530
1530
|
inputSchema: {
|
|
@@ -1559,16 +1559,6 @@ var init_setProjectMetadata = __esm({
|
|
|
1559
1559
|
// src/assets.ts
|
|
1560
1560
|
import fs9 from "fs";
|
|
1561
1561
|
import path3 from "path";
|
|
1562
|
-
function findRoot(start) {
|
|
1563
|
-
let dir = start;
|
|
1564
|
-
while (dir !== path3.dirname(dir)) {
|
|
1565
|
-
if (fs9.existsSync(path3.join(dir, "package.json"))) {
|
|
1566
|
-
return dir;
|
|
1567
|
-
}
|
|
1568
|
-
dir = path3.dirname(dir);
|
|
1569
|
-
}
|
|
1570
|
-
return start;
|
|
1571
|
-
}
|
|
1572
1562
|
function assetPath(...segments) {
|
|
1573
1563
|
return path3.join(ASSETS_BASE, ...segments);
|
|
1574
1564
|
}
|
|
@@ -1588,14 +1578,11 @@ function readJsonAsset(fallback, ...segments) {
|
|
|
1588
1578
|
return fallback;
|
|
1589
1579
|
}
|
|
1590
1580
|
}
|
|
1591
|
-
var
|
|
1581
|
+
var ASSETS_BASE;
|
|
1592
1582
|
var init_assets = __esm({
|
|
1593
1583
|
"src/assets.ts"() {
|
|
1594
1584
|
"use strict";
|
|
1595
|
-
|
|
1596
|
-
import.meta.dirname ?? path3.dirname(new URL(import.meta.url).pathname)
|
|
1597
|
-
);
|
|
1598
|
-
ASSETS_BASE = fs9.existsSync(path3.join(ROOT, "dist", "prompt")) ? path3.join(ROOT, "dist") : path3.join(ROOT, "src");
|
|
1585
|
+
ASSETS_BASE = import.meta.dirname ?? path3.dirname(new URL(import.meta.url).pathname);
|
|
1599
1586
|
}
|
|
1600
1587
|
});
|
|
1601
1588
|
|
|
@@ -2819,8 +2806,8 @@ var init_compactConversation = __esm({
|
|
|
2819
2806
|
"use strict";
|
|
2820
2807
|
init_trigger();
|
|
2821
2808
|
compactConversationTool = {
|
|
2822
|
-
clearable: false,
|
|
2823
2809
|
definition: {
|
|
2810
|
+
clearable: false,
|
|
2824
2811
|
name: "compactConversation",
|
|
2825
2812
|
description: "Compact the conversation history by summarizing older messages into a checkpoint. The summary preserves key decisions, what was built, and the current state of the project, but drops the verbose tool results, diffs, and intermediate steps that are no longer useful. Runs in the background.",
|
|
2826
2813
|
inputSchema: {
|
|
@@ -2844,8 +2831,136 @@ var init_compactConversation = __esm({
|
|
|
2844
2831
|
}
|
|
2845
2832
|
});
|
|
2846
2833
|
|
|
2834
|
+
// src/prompt/skills/catalog.ts
|
|
2835
|
+
import fs11 from "fs";
|
|
2836
|
+
import path5 from "path";
|
|
2837
|
+
function parseFrontmatter(content) {
|
|
2838
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
2839
|
+
if (!match) {
|
|
2840
|
+
return {};
|
|
2841
|
+
}
|
|
2842
|
+
const fields = {};
|
|
2843
|
+
for (const line of match[1].split("\n")) {
|
|
2844
|
+
const sep = line.indexOf(":");
|
|
2845
|
+
if (sep > 0) {
|
|
2846
|
+
fields[line.slice(0, sep).trim()] = line.slice(sep + 1).trim();
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
return fields;
|
|
2850
|
+
}
|
|
2851
|
+
function loadCatalog() {
|
|
2852
|
+
let files;
|
|
2853
|
+
try {
|
|
2854
|
+
files = fs11.readdirSync(SKILLS_DIR).filter((f) => f.endsWith(".md"));
|
|
2855
|
+
} catch {
|
|
2856
|
+
return [];
|
|
2857
|
+
}
|
|
2858
|
+
const skills = [];
|
|
2859
|
+
for (const file of files.sort()) {
|
|
2860
|
+
const full = path5.join(SKILLS_DIR, file);
|
|
2861
|
+
const id = file.replace(/\.md$/, "");
|
|
2862
|
+
const fields = parseFrontmatter(fs11.readFileSync(full, "utf-8"));
|
|
2863
|
+
if (!fields.name || !fields.what || !fields.when) {
|
|
2864
|
+
continue;
|
|
2865
|
+
}
|
|
2866
|
+
skills.push({
|
|
2867
|
+
id,
|
|
2868
|
+
name: fields.name,
|
|
2869
|
+
what: fields.what,
|
|
2870
|
+
when: fields.when,
|
|
2871
|
+
path: full
|
|
2872
|
+
});
|
|
2873
|
+
}
|
|
2874
|
+
return skills;
|
|
2875
|
+
}
|
|
2876
|
+
function getSkill(id) {
|
|
2877
|
+
return SKILLS.find((s) => s.id === id);
|
|
2878
|
+
}
|
|
2879
|
+
function readSkillBody(skill) {
|
|
2880
|
+
return fs11.readFileSync(skill.path, "utf-8").replace(/^---[\s\S]*?---\s*/, "").trim();
|
|
2881
|
+
}
|
|
2882
|
+
function loadSkillsCatalog() {
|
|
2883
|
+
if (SKILLS.length === 0) {
|
|
2884
|
+
return "";
|
|
2885
|
+
}
|
|
2886
|
+
const entries = SKILLS.map(
|
|
2887
|
+
(s) => [
|
|
2888
|
+
`### ${s.name} (\`${s.id}\`)`,
|
|
2889
|
+
s.what,
|
|
2890
|
+
"",
|
|
2891
|
+
`When to load: ${s.when}`,
|
|
2892
|
+
`Reference: ${s.path}`
|
|
2893
|
+
].join("\n")
|
|
2894
|
+
);
|
|
2895
|
+
return `<available_skills>
|
|
2896
|
+
Platform capabilities most apps don't use, so their references are kept out of this prompt rather than competing for your attention on every task \u2014 not because they're marginal.
|
|
2897
|
+
|
|
2898
|
+
Read what follows as part of what the platform can do, not as a lookup table. Recognising that one of these fits a feature is your job, and proposing one is fair game \u2014 several of them are the difference between an app that works and an app worth showing off. When a trigger fires, load the reference with loadSkill before writing the code rather than after. Loading is cheap and expected; guessing at one of these APIs is not.
|
|
2899
|
+
|
|
2900
|
+
A loaded reference drops out of the conversation once it ages out. Re-read it at the path listed with readFile whenever you need it again.
|
|
2901
|
+
|
|
2902
|
+
${entries.join("\n\n")}
|
|
2903
|
+
</available_skills>`;
|
|
2904
|
+
}
|
|
2905
|
+
var SKILLS_DIR, SKILLS, SKILL_IDS;
|
|
2906
|
+
var init_catalog = __esm({
|
|
2907
|
+
"src/prompt/skills/catalog.ts"() {
|
|
2908
|
+
"use strict";
|
|
2909
|
+
init_assets();
|
|
2910
|
+
SKILLS_DIR = assetPath("prompt", "skills");
|
|
2911
|
+
SKILLS = loadCatalog();
|
|
2912
|
+
SKILL_IDS = SKILLS.map((s) => s.id);
|
|
2913
|
+
}
|
|
2914
|
+
});
|
|
2915
|
+
|
|
2916
|
+
// src/tools/common/loadSkill.ts
|
|
2917
|
+
var loadSkillTool;
|
|
2918
|
+
var init_loadSkill = __esm({
|
|
2919
|
+
"src/tools/common/loadSkill.ts"() {
|
|
2920
|
+
"use strict";
|
|
2921
|
+
init_catalog();
|
|
2922
|
+
loadSkillTool = {
|
|
2923
|
+
definition: {
|
|
2924
|
+
clearable: true,
|
|
2925
|
+
name: "loadSkill",
|
|
2926
|
+
description: "Load the full reference for a platform capability that isn't in your system prompt \u2014 task agents, agent interfaces, MCP interfaces, data sources. The available skills and the trigger for each are listed in <available_skills>. Load one before writing code in its area, not after: these are APIs where a plausible-looking guess is usually wrong. Calling this is cheap and expected \u2014 if you're unsure whether you need it, load it. Only covers the capabilities listed in the catalog; for backend SDK actions and model IDs use askMindStudioSdk.",
|
|
2927
|
+
inputSchema: {
|
|
2928
|
+
type: "object",
|
|
2929
|
+
properties: {
|
|
2930
|
+
skill: {
|
|
2931
|
+
type: "string",
|
|
2932
|
+
// Omitted when the catalog is empty: an empty enum is a schema no
|
|
2933
|
+
// provider accepts, and failing one tool call beats failing every
|
|
2934
|
+
// request if the docs ever go missing from a build.
|
|
2935
|
+
...SKILL_IDS.length > 0 ? { enum: SKILL_IDS } : {},
|
|
2936
|
+
description: "The skill id, as listed in <available_skills>."
|
|
2937
|
+
}
|
|
2938
|
+
},
|
|
2939
|
+
required: ["skill"]
|
|
2940
|
+
}
|
|
2941
|
+
},
|
|
2942
|
+
async execute(input) {
|
|
2943
|
+
const id = String(input.skill ?? "");
|
|
2944
|
+
const skill = getSkill(id);
|
|
2945
|
+
if (!skill) {
|
|
2946
|
+
return `Error: unknown skill "${id}". Available: ${SKILL_IDS.join(", ") || "(none)"}`;
|
|
2947
|
+
}
|
|
2948
|
+
try {
|
|
2949
|
+
const body = readSkillBody(skill);
|
|
2950
|
+
return `${body}
|
|
2951
|
+
|
|
2952
|
+
---
|
|
2953
|
+
This reference lives at ${skill.path}. Re-read it with readFile if you need it again later \u2014 it won't stay in the conversation.`;
|
|
2954
|
+
} catch (err) {
|
|
2955
|
+
return `Error loading skill "${id}": ${err.message}`;
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
};
|
|
2959
|
+
}
|
|
2960
|
+
});
|
|
2961
|
+
|
|
2847
2962
|
// src/tools/code/readFile.ts
|
|
2848
|
-
import
|
|
2963
|
+
import fs12 from "fs/promises";
|
|
2849
2964
|
function isBinary(buffer) {
|
|
2850
2965
|
const sample = buffer.subarray(0, 8192);
|
|
2851
2966
|
for (let i = 0; i < sample.length; i++) {
|
|
@@ -2862,8 +2977,8 @@ var init_readFile = __esm({
|
|
|
2862
2977
|
DEFAULT_WINDOW = 500;
|
|
2863
2978
|
MAX_BYTES = 64 * 1024;
|
|
2864
2979
|
readFileTool = {
|
|
2865
|
-
clearable: true,
|
|
2866
2980
|
definition: {
|
|
2981
|
+
clearable: true,
|
|
2867
2982
|
name: "readFile",
|
|
2868
2983
|
description: "Read a file's contents with line numbers. Always read a file before editing it \u2014 never guess at contents. By default returns the first 500 lines, and at most 64KB \u2014 a file with very wide lines (a CSV, a minified bundle) comes back short of 500 lines, so read a narrower range or grep rather than paging through it. To read a specific range, pass startLine and endLine (1-indexed, inclusive) \u2014 e.g. to read lines 253\u2013343, pass startLine: 253, endLine: 343. To read the end of a file or log, pass tail (the number of lines from the end). Line numbers in the output correspond to what editFile expects. For a large file, locate the relevant section first (symbols or grep), then read just that range.",
|
|
2869
2984
|
inputSchema: {
|
|
@@ -2871,7 +2986,7 @@ var init_readFile = __esm({
|
|
|
2871
2986
|
properties: {
|
|
2872
2987
|
path: {
|
|
2873
2988
|
type: "string",
|
|
2874
|
-
description: "The file path to read
|
|
2989
|
+
description: "The file path to read. Relative paths resolve from the project root; an absolute path is also accepted, which is how you re-read a reference doc from the path a loadSkill result gave you."
|
|
2875
2990
|
},
|
|
2876
2991
|
startLine: {
|
|
2877
2992
|
type: "number",
|
|
@@ -2891,7 +3006,7 @@ var init_readFile = __esm({
|
|
|
2891
3006
|
},
|
|
2892
3007
|
async execute(input) {
|
|
2893
3008
|
try {
|
|
2894
|
-
const buffer = await
|
|
3009
|
+
const buffer = await fs12.readFile(input.path);
|
|
2895
3010
|
if (isBinary(buffer)) {
|
|
2896
3011
|
const size = buffer.length;
|
|
2897
3012
|
const unit = size > 1024 * 1024 ? `${(size / (1024 * 1024)).toFixed(1)}MB` : `${(size / 1024).toFixed(1)}KB`;
|
|
@@ -2965,8 +3080,8 @@ var init_readFile = __esm({
|
|
|
2965
3080
|
});
|
|
2966
3081
|
|
|
2967
3082
|
// src/tools/code/writeFile.ts
|
|
2968
|
-
import
|
|
2969
|
-
import
|
|
3083
|
+
import fs13 from "fs/promises";
|
|
3084
|
+
import path6 from "path";
|
|
2970
3085
|
var writeFileTool;
|
|
2971
3086
|
var init_writeFile = __esm({
|
|
2972
3087
|
"src/tools/code/writeFile.ts"() {
|
|
@@ -2974,8 +3089,8 @@ var init_writeFile = __esm({
|
|
|
2974
3089
|
init_diff();
|
|
2975
3090
|
init_fileLock();
|
|
2976
3091
|
writeFileTool = {
|
|
2977
|
-
clearable: true,
|
|
2978
3092
|
definition: {
|
|
3093
|
+
clearable: true,
|
|
2979
3094
|
name: "writeFile",
|
|
2980
3095
|
description: "Create a new file or completely overwrite an existing one. Parent directories are created automatically. Use this for new files or full rewrites. For targeted changes to existing files, use editFile instead \u2014 it preserves the parts you don't want to change and avoids errors from forgetting to include unchanged code.",
|
|
2981
3096
|
inputSchema: {
|
|
@@ -3009,7 +3124,7 @@ var init_writeFile = __esm({
|
|
|
3009
3124
|
lastNewlineCount = newlineCount;
|
|
3010
3125
|
const lastNewline = partial.content.lastIndexOf("\n");
|
|
3011
3126
|
const completeContent = partial.content.substring(0, lastNewline + 1);
|
|
3012
|
-
const oldContent = await
|
|
3127
|
+
const oldContent = await fs13.readFile(partial.path, "utf-8").catch(() => "");
|
|
3013
3128
|
return `Writing ${partial.path} (${newlineCount} lines)
|
|
3014
3129
|
${unifiedDiff(partial.path, oldContent, completeContent)}`;
|
|
3015
3130
|
}
|
|
@@ -3018,13 +3133,13 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
|
|
|
3018
3133
|
async execute(input) {
|
|
3019
3134
|
const release = await acquireFileLock(input.path);
|
|
3020
3135
|
try {
|
|
3021
|
-
await
|
|
3136
|
+
await fs13.mkdir(path6.dirname(input.path), { recursive: true });
|
|
3022
3137
|
let oldContent = null;
|
|
3023
3138
|
try {
|
|
3024
|
-
oldContent = await
|
|
3139
|
+
oldContent = await fs13.readFile(input.path, "utf-8");
|
|
3025
3140
|
} catch {
|
|
3026
3141
|
}
|
|
3027
|
-
await
|
|
3142
|
+
await fs13.writeFile(input.path, input.content, "utf-8");
|
|
3028
3143
|
const lineCount = input.content.split("\n").length;
|
|
3029
3144
|
const label = oldContent !== null ? "Wrote" : "Created";
|
|
3030
3145
|
return `${label} ${input.path} (${lineCount} lines)
|
|
@@ -3040,7 +3155,7 @@ ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
|
|
|
3040
3155
|
});
|
|
3041
3156
|
|
|
3042
3157
|
// src/tools/code/editFile/index.ts
|
|
3043
|
-
import
|
|
3158
|
+
import fs14 from "fs/promises";
|
|
3044
3159
|
var editFileTool;
|
|
3045
3160
|
var init_editFile = __esm({
|
|
3046
3161
|
"src/tools/code/editFile/index.ts"() {
|
|
@@ -3049,8 +3164,8 @@ var init_editFile = __esm({
|
|
|
3049
3164
|
init_fileLock();
|
|
3050
3165
|
init_helpers2();
|
|
3051
3166
|
editFileTool = {
|
|
3052
|
-
clearable: true,
|
|
3053
3167
|
definition: {
|
|
3168
|
+
clearable: true,
|
|
3054
3169
|
name: "editFile",
|
|
3055
3170
|
description: "Replace a string in a file. old_string must appear exactly once (minor indentation differences are handled automatically). Set replace_all to true to replace every occurrence at once. For bulk mechanical substitutions (renaming a variable, swapping colors), prefer replace_all. Always read the file first so you know the exact text to match. When editing nested structures (objects, function bodies, arrays, template literals), always include the full enclosing structure in old_string rather than just an inner fragment. Replacing a partial slice from the middle of nested code is the most common source of syntax errors.",
|
|
3056
3171
|
inputSchema: {
|
|
@@ -3079,7 +3194,7 @@ var init_editFile = __esm({
|
|
|
3079
3194
|
async execute(input) {
|
|
3080
3195
|
const release = await acquireFileLock(input.path);
|
|
3081
3196
|
try {
|
|
3082
|
-
const content = await
|
|
3197
|
+
const content = await fs14.readFile(input.path, "utf-8");
|
|
3083
3198
|
const { old_string, new_string, replace_all } = input;
|
|
3084
3199
|
const occurrences = findOccurrences(content, old_string);
|
|
3085
3200
|
if (replace_all) {
|
|
@@ -3095,7 +3210,7 @@ var init_editFile = __esm({
|
|
|
3095
3210
|
new_string
|
|
3096
3211
|
);
|
|
3097
3212
|
}
|
|
3098
|
-
await
|
|
3213
|
+
await fs14.writeFile(input.path, updated, "utf-8");
|
|
3099
3214
|
return `Replaced ${occurrences.length} occurrence${occurrences.length > 1 ? "s" : ""} in ${input.path}
|
|
3100
3215
|
${unifiedDiff(input.path, content, updated)}`;
|
|
3101
3216
|
}
|
|
@@ -3106,7 +3221,7 @@ ${unifiedDiff(input.path, content, updated)}`;
|
|
|
3106
3221
|
old_string.length,
|
|
3107
3222
|
new_string
|
|
3108
3223
|
);
|
|
3109
|
-
await
|
|
3224
|
+
await fs14.writeFile(input.path, updated, "utf-8");
|
|
3110
3225
|
return `Updated ${input.path}
|
|
3111
3226
|
${unifiedDiff(input.path, content, updated)}`;
|
|
3112
3227
|
}
|
|
@@ -3122,7 +3237,7 @@ ${unifiedDiff(input.path, content, updated)}`;
|
|
|
3122
3237
|
flex.matchedText.length,
|
|
3123
3238
|
new_string
|
|
3124
3239
|
);
|
|
3125
|
-
await
|
|
3240
|
+
await fs14.writeFile(input.path, updated, "utf-8");
|
|
3126
3241
|
return `Updated ${input.path} (matched with flexible whitespace at line ${flex.line})
|
|
3127
3242
|
${unifiedDiff(input.path, content, updated)}`;
|
|
3128
3243
|
}
|
|
@@ -3148,7 +3263,7 @@ var init_projectRoot = __esm({
|
|
|
3148
3263
|
|
|
3149
3264
|
// src/tools/code/bash.ts
|
|
3150
3265
|
import { spawn as spawn2 } from "child_process";
|
|
3151
|
-
import
|
|
3266
|
+
import path7 from "path";
|
|
3152
3267
|
var DEFAULT_TIMEOUT_MS, DEFAULT_MAX_LINES2, MAX_OUTPUT_BYTES, bashTool;
|
|
3153
3268
|
var init_bash = __esm({
|
|
3154
3269
|
"src/tools/code/bash.ts"() {
|
|
@@ -3158,8 +3273,8 @@ var init_bash = __esm({
|
|
|
3158
3273
|
DEFAULT_MAX_LINES2 = 500;
|
|
3159
3274
|
MAX_OUTPUT_BYTES = 3e4;
|
|
3160
3275
|
bashTool = {
|
|
3161
|
-
clearable: true,
|
|
3162
3276
|
definition: {
|
|
3277
|
+
clearable: true,
|
|
3163
3278
|
name: "bash",
|
|
3164
3279
|
description: "Run a shell command and return stdout + stderr. 120-second timeout by default (configurable). Use for: npm install/build/test, git operations, tsc --noEmit, or any CLI tool. Prefer dedicated tools over bash when available (use grep instead of bash + rg, readFile instead of bash + cat). Output is truncated to 500 lines or 30KB, whichever comes first. If a command would emit a lot of data, narrow it down (grep, head/tail, --short flags) rather than reading everything.",
|
|
3165
3280
|
inputSchema: {
|
|
@@ -3188,11 +3303,11 @@ var init_bash = __esm({
|
|
|
3188
3303
|
async execute(input, context) {
|
|
3189
3304
|
const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES2;
|
|
3190
3305
|
const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
|
|
3191
|
-
return new Promise((
|
|
3306
|
+
return new Promise((resolve3) => {
|
|
3192
3307
|
const child = spawn2("sh", ["-c", input.command], {
|
|
3193
3308
|
// Pinned rather than inherited. `undefined` here means "wherever the
|
|
3194
3309
|
// process happens to be", which is the project root only by luck.
|
|
3195
|
-
cwd: input.cwd ?
|
|
3310
|
+
cwd: input.cwd ? path7.resolve(PROJECT_ROOT, input.cwd) : PROJECT_ROOT,
|
|
3196
3311
|
env: { ...process.env, FORCE_COLOR: "1" }
|
|
3197
3312
|
});
|
|
3198
3313
|
let output = "";
|
|
@@ -3213,9 +3328,9 @@ var init_bash = __esm({
|
|
|
3213
3328
|
clearTimeout(timer);
|
|
3214
3329
|
if (!output) {
|
|
3215
3330
|
if (code && code !== 0) {
|
|
3216
|
-
|
|
3331
|
+
resolve3(`Error: process exited with code ${code}`);
|
|
3217
3332
|
} else {
|
|
3218
|
-
|
|
3333
|
+
resolve3("(no output)");
|
|
3219
3334
|
}
|
|
3220
3335
|
return;
|
|
3221
3336
|
}
|
|
@@ -3241,18 +3356,18 @@ var init_bash = __esm({
|
|
|
3241
3356
|
`${(MAX_OUTPUT_BYTES / 1024).toFixed(0)}KB of ${(totalBytes / 1024).toFixed(0)}KB`
|
|
3242
3357
|
);
|
|
3243
3358
|
}
|
|
3244
|
-
|
|
3359
|
+
resolve3(
|
|
3245
3360
|
truncated + `
|
|
3246
3361
|
|
|
3247
3362
|
(truncated at ${reasons.join(" / ")} \u2014 narrow the command (grep, head/tail, smaller paths) instead of increasing limits)`
|
|
3248
3363
|
);
|
|
3249
3364
|
} else {
|
|
3250
|
-
|
|
3365
|
+
resolve3(output);
|
|
3251
3366
|
}
|
|
3252
3367
|
});
|
|
3253
3368
|
child.on("error", (err) => {
|
|
3254
3369
|
clearTimeout(timer);
|
|
3255
|
-
|
|
3370
|
+
resolve3(`Error: ${err.message}`);
|
|
3256
3371
|
});
|
|
3257
3372
|
});
|
|
3258
3373
|
}
|
|
@@ -3286,8 +3401,8 @@ var init_grep = __esm({
|
|
|
3286
3401
|
"use strict";
|
|
3287
3402
|
DEFAULT_MAX = 50;
|
|
3288
3403
|
grepTool = {
|
|
3289
|
-
clearable: true,
|
|
3290
3404
|
definition: {
|
|
3405
|
+
clearable: true,
|
|
3291
3406
|
name: "grep",
|
|
3292
3407
|
description: "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers (default 50 results). Use this to find where something is used, locate function definitions, or search for patterns across the codebase. Set outputMode to 'count' for per-file match counts (like grep -c) or 'filesWithMatches' for just the file paths (like grep -l). Add context (like grep -C), or contextBefore/contextAfter (like grep -B/-A), to include surrounding lines. Set caseInsensitive (like grep -i) for a case-insensitive search. For finding a symbol's definition precisely, prefer the definition tool if LSP is available. Automatically excludes node_modules and .git.",
|
|
3293
3408
|
inputSchema: {
|
|
@@ -3376,17 +3491,17 @@ var init_grep = __esm({
|
|
|
3376
3491
|
}
|
|
3377
3492
|
const rgCmd = `rg ${rgFlags}${globFlag} '${escaped}' ${searchPath}`;
|
|
3378
3493
|
const grepCmd = `grep ${grepFlags} '${escaped}' ${searchPath} --include='*.ts' --include='*.tsx' --include='*.js' --include='*.json' --include='*.md'`;
|
|
3379
|
-
return new Promise((
|
|
3494
|
+
return new Promise((resolve3) => {
|
|
3380
3495
|
exec(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
|
|
3381
3496
|
if (stdout?.trim()) {
|
|
3382
|
-
|
|
3497
|
+
resolve3(formatResults(stdout, max, mode));
|
|
3383
3498
|
return;
|
|
3384
3499
|
}
|
|
3385
3500
|
exec(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
|
|
3386
3501
|
if (grepStdout?.trim()) {
|
|
3387
|
-
|
|
3502
|
+
resolve3(formatResults(grepStdout, max, mode));
|
|
3388
3503
|
} else {
|
|
3389
|
-
|
|
3504
|
+
resolve3("No matches found.");
|
|
3390
3505
|
}
|
|
3391
3506
|
});
|
|
3392
3507
|
});
|
|
@@ -3404,8 +3519,8 @@ var init_glob = __esm({
|
|
|
3404
3519
|
"use strict";
|
|
3405
3520
|
DEFAULT_MAX2 = 200;
|
|
3406
3521
|
globTool = {
|
|
3407
|
-
clearable: true,
|
|
3408
3522
|
definition: {
|
|
3523
|
+
clearable: true,
|
|
3409
3524
|
name: "glob",
|
|
3410
3525
|
description: 'Find files matching a glob pattern. Returns matching file paths sorted alphabetically (default 200 results). Use this to discover project structure, find files by name or extension, or check if a file exists. Common patterns: "**/*.ts" (all TypeScript files), "src/**/*.tsx" (React components in src), "*.json" (root-level JSON files). Automatically excludes node_modules and .git.',
|
|
3411
3526
|
inputSchema: {
|
|
@@ -3451,10 +3566,10 @@ var init_glob = __esm({
|
|
|
3451
3566
|
});
|
|
3452
3567
|
|
|
3453
3568
|
// src/tools/code/listDir.ts
|
|
3454
|
-
import
|
|
3455
|
-
import
|
|
3569
|
+
import fs15 from "fs/promises";
|
|
3570
|
+
import path8 from "path";
|
|
3456
3571
|
async function readAndSort(dirPath) {
|
|
3457
|
-
const entries = await
|
|
3572
|
+
const entries = await fs15.readdir(dirPath, { withFileTypes: true });
|
|
3458
3573
|
return entries.filter((e) => !EXCLUDE.has(e.name)).sort((a, b) => {
|
|
3459
3574
|
if (a.isDirectory() && !b.isDirectory()) {
|
|
3460
3575
|
return -1;
|
|
@@ -3467,7 +3582,7 @@ async function readAndSort(dirPath) {
|
|
|
3467
3582
|
}
|
|
3468
3583
|
async function collapsePath(basePath, name) {
|
|
3469
3584
|
let display = name;
|
|
3470
|
-
let current =
|
|
3585
|
+
let current = path8.join(basePath, name);
|
|
3471
3586
|
for (; ; ) {
|
|
3472
3587
|
let children;
|
|
3473
3588
|
try {
|
|
@@ -3477,7 +3592,7 @@ async function collapsePath(basePath, name) {
|
|
|
3477
3592
|
}
|
|
3478
3593
|
if (children.length === 1 && children[0].isDirectory()) {
|
|
3479
3594
|
display += "/" + children[0].name;
|
|
3480
|
-
current =
|
|
3595
|
+
current = path8.join(current, children[0].name);
|
|
3481
3596
|
} else {
|
|
3482
3597
|
break;
|
|
3483
3598
|
}
|
|
@@ -3495,8 +3610,8 @@ function formatSize(bytes) {
|
|
|
3495
3610
|
}
|
|
3496
3611
|
async function formatFile(dirPath, name, indent) {
|
|
3497
3612
|
try {
|
|
3498
|
-
const
|
|
3499
|
-
return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(
|
|
3613
|
+
const stat2 = await fs15.stat(path8.join(dirPath, name));
|
|
3614
|
+
return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat2.size)}`;
|
|
3500
3615
|
} catch {
|
|
3501
3616
|
return `${indent}${name}`;
|
|
3502
3617
|
}
|
|
@@ -3508,8 +3623,8 @@ var init_listDir = __esm({
|
|
|
3508
3623
|
EXCLUDE = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
3509
3624
|
MAX_CHILDREN = 15;
|
|
3510
3625
|
listDirTool = {
|
|
3511
|
-
clearable: true,
|
|
3512
3626
|
definition: {
|
|
3627
|
+
clearable: true,
|
|
3513
3628
|
name: "listDir",
|
|
3514
3629
|
description: "List the contents of a directory with one level of subdirectory expansion. Shows file sizes and collapses single-child directory chains (a/b/c/ shown as one entry). Use this for a quick overview of a directory's structure. For finding files across the whole project, use glob instead.",
|
|
3515
3630
|
inputSchema: {
|
|
@@ -3591,8 +3706,8 @@ var init_editsFinished = __esm({
|
|
|
3591
3706
|
"src/tools/code/editsFinished.ts"() {
|
|
3592
3707
|
"use strict";
|
|
3593
3708
|
editsFinishedTool = {
|
|
3594
|
-
clearable: false,
|
|
3595
3709
|
definition: {
|
|
3710
|
+
clearable: false,
|
|
3596
3711
|
name: "editsFinished",
|
|
3597
3712
|
description: "Signal that file edits are complete. Call this after you finish writing/editing files so the live preview updates cleanly. The preview is paused while you edit to avoid showing broken intermediate states \u2014 this unpauses it. If you forget to call this, the preview updates when your turn ends.",
|
|
3598
3713
|
inputSchema: {
|
|
@@ -3679,8 +3794,8 @@ var init_lspDiagnostics = __esm({
|
|
|
3679
3794
|
"use strict";
|
|
3680
3795
|
init_lsp();
|
|
3681
3796
|
lspDiagnosticsTool = {
|
|
3682
|
-
clearable: true,
|
|
3683
3797
|
definition: {
|
|
3798
|
+
clearable: true,
|
|
3684
3799
|
name: "lspDiagnostics",
|
|
3685
3800
|
description: "Get TypeScript diagnostics (type errors, warnings) for a file, with suggested fixes when available. Use this after editing a file to check for errors.",
|
|
3686
3801
|
inputSchema: {
|
|
@@ -3735,8 +3850,8 @@ var init_restartProcess = __esm({
|
|
|
3735
3850
|
"use strict";
|
|
3736
3851
|
init_lsp();
|
|
3737
3852
|
restartProcessTool = {
|
|
3738
|
-
clearable: false,
|
|
3739
3853
|
definition: {
|
|
3854
|
+
clearable: false,
|
|
3740
3855
|
name: "restartProcess",
|
|
3741
3856
|
description: "Restart a managed sandbox process. Use this after running npm install or changing package.json to restart the dev server so it picks up new dependencies.",
|
|
3742
3857
|
inputSchema: {
|
|
@@ -3753,7 +3868,7 @@ var init_restartProcess = __esm({
|
|
|
3753
3868
|
async execute(input) {
|
|
3754
3869
|
const data = await lspRequest("/restart-process", { name: input.name });
|
|
3755
3870
|
if (data.ok) {
|
|
3756
|
-
await new Promise((
|
|
3871
|
+
await new Promise((resolve3) => setTimeout(resolve3, 5e3));
|
|
3757
3872
|
return `Restarted ${input.name}.`;
|
|
3758
3873
|
}
|
|
3759
3874
|
return `Error: unexpected response: ${JSON.stringify(data)}`;
|
|
@@ -3768,8 +3883,8 @@ var init_runScenario = __esm({
|
|
|
3768
3883
|
"src/tools/code/runScenario.ts"() {
|
|
3769
3884
|
"use strict";
|
|
3770
3885
|
runScenarioTool = {
|
|
3771
|
-
clearable: true,
|
|
3772
3886
|
definition: {
|
|
3887
|
+
clearable: true,
|
|
3773
3888
|
name: "runScenario",
|
|
3774
3889
|
description: "Run a scenario to seed the dev database with test data. By default truncates all tables first, then executes the seed function and impersonates the scenario roles. Use skipTruncate to run the seed function against existing data without resetting. Blocks until complete. Scenario IDs are defined in mindstudio.json. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for details. Return synchronously - no need to sleep before checking results.",
|
|
3775
3890
|
inputSchema: {
|
|
@@ -3800,8 +3915,8 @@ var init_runMethod = __esm({
|
|
|
3800
3915
|
"src/tools/code/runMethod.ts"() {
|
|
3801
3916
|
"use strict";
|
|
3802
3917
|
runMethodTool = {
|
|
3803
|
-
clearable: true,
|
|
3804
3918
|
definition: {
|
|
3919
|
+
clearable: true,
|
|
3805
3920
|
name: "runMethod",
|
|
3806
3921
|
description: 'Run a method in the dev environment and return the result. Use for testing methods after writing or modifying them. Returns output, captured console output, errors with stack traces, and duration. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for more details. Returns synchronously \u2014 no need to sleep before checking results.\n\nBy default methods run unauthenticated. If the method is auth-gated (calls `auth.requireRole()`, filters on `auth.userId`, etc.), pass `userId: "testUser"` to run as the default test user \u2014 no scenario setup required, no userId lookup.',
|
|
3807
3922
|
inputSchema: {
|
|
@@ -3841,8 +3956,8 @@ var init_queryDatabase = __esm({
|
|
|
3841
3956
|
"src/tools/code/queryDatabase.ts"() {
|
|
3842
3957
|
"use strict";
|
|
3843
3958
|
queryDatabaseTool = {
|
|
3844
|
-
clearable: true,
|
|
3845
3959
|
definition: {
|
|
3960
|
+
clearable: true,
|
|
3846
3961
|
name: "queryDatabase",
|
|
3847
3962
|
description: "Execute a raw SQL query against the dev database and return the results. Use for inspecting data and debugging issues.",
|
|
3848
3963
|
inputSchema: {
|
|
@@ -3863,26 +3978,136 @@ var init_queryDatabase = __esm({
|
|
|
3863
3978
|
}
|
|
3864
3979
|
});
|
|
3865
3980
|
|
|
3981
|
+
// src/tools/_helpers/uploadImage.ts
|
|
3982
|
+
import { readFile, stat } from "fs/promises";
|
|
3983
|
+
import { basename, extname, resolve } from "path";
|
|
3984
|
+
function isFetchableUrl(ref) {
|
|
3985
|
+
return /^(?:https?:|data:)/i.test(ref);
|
|
3986
|
+
}
|
|
3987
|
+
async function resolveImageRef(ref, apiConfig) {
|
|
3988
|
+
const trimmed = ref.trim();
|
|
3989
|
+
return isFetchableUrl(trimmed) ? trimmed : uploadLocalImage(trimmed, apiConfig);
|
|
3990
|
+
}
|
|
3991
|
+
function resolveImageRefs(refs, apiConfig) {
|
|
3992
|
+
return Promise.all(refs.map((ref) => resolveImageRef(ref, apiConfig)));
|
|
3993
|
+
}
|
|
3994
|
+
async function uploadLocalImage(localPath, apiConfig) {
|
|
3995
|
+
const absolute = resolve(PROJECT_ROOT, localPath);
|
|
3996
|
+
const ext = extname(absolute).toLowerCase();
|
|
3997
|
+
const contentType = CONTENT_TYPES[ext];
|
|
3998
|
+
if (!contentType) {
|
|
3999
|
+
throw new Error(
|
|
4000
|
+
`Cannot use "${localPath}" as an image \u2014 vision models read ${Object.keys(
|
|
4001
|
+
CONTENT_TYPES
|
|
4002
|
+
).join(", ")}. Convert it first, or pass an image URL.`
|
|
4003
|
+
);
|
|
4004
|
+
}
|
|
4005
|
+
let stats;
|
|
4006
|
+
try {
|
|
4007
|
+
stats = await stat(absolute);
|
|
4008
|
+
} catch {
|
|
4009
|
+
throw new Error(
|
|
4010
|
+
`No file at "${localPath}". Paths resolve from the project root; list the directory to check the name, or pass an image URL.`
|
|
4011
|
+
);
|
|
4012
|
+
}
|
|
4013
|
+
const cacheKey = `${absolute}:${stats.mtimeMs}:${stats.size}`;
|
|
4014
|
+
const cached3 = hosted.get(cacheKey);
|
|
4015
|
+
if (cached3) {
|
|
4016
|
+
return cached3;
|
|
4017
|
+
}
|
|
4018
|
+
if (!apiConfig?.appId) {
|
|
4019
|
+
throw new Error(
|
|
4020
|
+
`Cannot host "${localPath}" for analysis \u2014 this session has no app id. Pass an image URL instead.`
|
|
4021
|
+
);
|
|
4022
|
+
}
|
|
4023
|
+
const bytes = await readFile(absolute);
|
|
4024
|
+
const target = await requestUploadTarget(apiConfig, ext.slice(1));
|
|
4025
|
+
const form = new FormData();
|
|
4026
|
+
for (const [field, value] of Object.entries(target.uploadFields)) {
|
|
4027
|
+
form.append(field, value);
|
|
4028
|
+
}
|
|
4029
|
+
form.append(
|
|
4030
|
+
"file",
|
|
4031
|
+
new Blob([bytes], { type: contentType }),
|
|
4032
|
+
basename(absolute)
|
|
4033
|
+
);
|
|
4034
|
+
const res = await fetch(target.uploadUrl, {
|
|
4035
|
+
method: "POST",
|
|
4036
|
+
body: form,
|
|
4037
|
+
signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS)
|
|
4038
|
+
});
|
|
4039
|
+
if (!res.ok) {
|
|
4040
|
+
throw new Error(`Upload of "${localPath}" failed: HTTP ${res.status}`);
|
|
4041
|
+
}
|
|
4042
|
+
log6.info("Local image hosted", {
|
|
4043
|
+
path: localPath,
|
|
4044
|
+
bytes: stats.size,
|
|
4045
|
+
url: target.publicUrl
|
|
4046
|
+
});
|
|
4047
|
+
hosted.set(cacheKey, target.publicUrl);
|
|
4048
|
+
return target.publicUrl;
|
|
4049
|
+
}
|
|
4050
|
+
async function requestUploadTarget(apiConfig, extension) {
|
|
4051
|
+
const url = `${apiConfig.baseUrl}/_internal/v2/apps/${apiConfig.appId}/dev/manage/upload`;
|
|
4052
|
+
const res = await fetch(url, {
|
|
4053
|
+
method: "POST",
|
|
4054
|
+
headers: {
|
|
4055
|
+
"Content-Type": "application/json",
|
|
4056
|
+
Authorization: `Bearer ${apiConfig.apiKey}`
|
|
4057
|
+
},
|
|
4058
|
+
body: JSON.stringify({ extension }),
|
|
4059
|
+
signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS)
|
|
4060
|
+
});
|
|
4061
|
+
if (!res.ok) {
|
|
4062
|
+
throw new Error(`Could not get an upload URL: HTTP ${res.status}`);
|
|
4063
|
+
}
|
|
4064
|
+
return await res.json();
|
|
4065
|
+
}
|
|
4066
|
+
var log6, UPLOAD_TIMEOUT_MS, CONTENT_TYPES, hosted;
|
|
4067
|
+
var init_uploadImage = __esm({
|
|
4068
|
+
"src/tools/_helpers/uploadImage.ts"() {
|
|
4069
|
+
"use strict";
|
|
4070
|
+
init_projectRoot();
|
|
4071
|
+
init_logger();
|
|
4072
|
+
log6 = createLogger("uploadImage");
|
|
4073
|
+
UPLOAD_TIMEOUT_MS = 6e4;
|
|
4074
|
+
CONTENT_TYPES = {
|
|
4075
|
+
".png": "image/png",
|
|
4076
|
+
".jpg": "image/jpeg",
|
|
4077
|
+
".jpeg": "image/jpeg",
|
|
4078
|
+
".gif": "image/gif",
|
|
4079
|
+
".webp": "image/webp"
|
|
4080
|
+
};
|
|
4081
|
+
hosted = /* @__PURE__ */ new Map();
|
|
4082
|
+
}
|
|
4083
|
+
});
|
|
4084
|
+
|
|
3866
4085
|
// src/subagents/common/analyzeImage.ts
|
|
3867
4086
|
async function analyzeImage(params) {
|
|
3868
|
-
const { prompt,
|
|
3869
|
-
|
|
4087
|
+
const { prompt, image, apiConfig, model, timeout = 2e5, onLog } = params;
|
|
4088
|
+
const url = await resolveImageRef(image, apiConfig);
|
|
4089
|
+
const result = await runMindstudioCliResult(
|
|
3870
4090
|
[
|
|
3871
4091
|
"analyze-image",
|
|
3872
4092
|
"--prompt",
|
|
3873
4093
|
prompt,
|
|
3874
4094
|
"--image-url",
|
|
3875
|
-
|
|
4095
|
+
url,
|
|
3876
4096
|
"--vision-model-override",
|
|
3877
4097
|
JSON.stringify({ model })
|
|
3878
4098
|
],
|
|
3879
4099
|
{ outputKey: "analysis", timeout, onLog }
|
|
3880
4100
|
);
|
|
4101
|
+
if (!result.ok) {
|
|
4102
|
+
throw new Error(`Could not analyze ${url}: ${result.value}`);
|
|
4103
|
+
}
|
|
4104
|
+
return { url, analysis: result.value };
|
|
3881
4105
|
}
|
|
3882
4106
|
var init_analyzeImage = __esm({
|
|
3883
4107
|
"src/subagents/common/analyzeImage.ts"() {
|
|
3884
4108
|
"use strict";
|
|
3885
4109
|
init_runMindstudioCli();
|
|
4110
|
+
init_uploadImage();
|
|
3886
4111
|
}
|
|
3887
4112
|
});
|
|
3888
4113
|
|
|
@@ -3904,13 +4129,14 @@ ${ANALYSIS_RESPONSE_FORMAT}`;
|
|
|
3904
4129
|
return p;
|
|
3905
4130
|
}
|
|
3906
4131
|
async function streamScreenshotAnalysis(opts) {
|
|
3907
|
-
const {
|
|
4132
|
+
const { image, prompt, styleMap, onLog, model, apiConfig } = opts;
|
|
4133
|
+
const url = await resolveImageRef(image, apiConfig);
|
|
3908
4134
|
onLog?.(JSON.stringify({ url, analysis: null }));
|
|
3909
4135
|
const analysisPrompt = buildScreenshotAnalysisPrompt({ prompt, styleMap });
|
|
3910
4136
|
let accumulated = "";
|
|
3911
|
-
const analysis = await analyzeImage({
|
|
4137
|
+
const { analysis } = await analyzeImage({
|
|
3912
4138
|
prompt: analysisPrompt,
|
|
3913
|
-
|
|
4139
|
+
image: url,
|
|
3914
4140
|
model,
|
|
3915
4141
|
onLog: (chunk) => {
|
|
3916
4142
|
accumulated += chunk;
|
|
@@ -3921,18 +4147,19 @@ async function streamScreenshotAnalysis(opts) {
|
|
|
3921
4147
|
}
|
|
3922
4148
|
async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
3923
4149
|
let prompt;
|
|
3924
|
-
let
|
|
4150
|
+
let existingImage;
|
|
3925
4151
|
let onLog;
|
|
3926
4152
|
let model;
|
|
3927
|
-
let
|
|
4153
|
+
let apiConfig;
|
|
4154
|
+
let path15;
|
|
3928
4155
|
let fullPage = true;
|
|
3929
4156
|
let width;
|
|
3930
4157
|
let height;
|
|
3931
4158
|
let format;
|
|
3932
4159
|
if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
|
|
3933
4160
|
prompt = promptOrOptions.prompt;
|
|
3934
|
-
|
|
3935
|
-
|
|
4161
|
+
existingImage = promptOrOptions.image;
|
|
4162
|
+
path15 = promptOrOptions.path;
|
|
3936
4163
|
if (promptOrOptions.fullPage !== void 0) {
|
|
3937
4164
|
fullPage = promptOrOptions.fullPage;
|
|
3938
4165
|
}
|
|
@@ -3941,6 +4168,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
3941
4168
|
format = promptOrOptions.format;
|
|
3942
4169
|
onLog = promptOrOptions.onLog;
|
|
3943
4170
|
model = promptOrOptions.model;
|
|
4171
|
+
apiConfig = promptOrOptions.apiConfig;
|
|
3944
4172
|
} else {
|
|
3945
4173
|
prompt = promptOrOptions;
|
|
3946
4174
|
}
|
|
@@ -3949,13 +4177,13 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
3949
4177
|
}
|
|
3950
4178
|
let url;
|
|
3951
4179
|
let styleMap;
|
|
3952
|
-
if (
|
|
3953
|
-
url =
|
|
4180
|
+
if (existingImage) {
|
|
4181
|
+
url = existingImage;
|
|
3954
4182
|
} else {
|
|
3955
4183
|
const ssResult = await sidecarRequest(
|
|
3956
4184
|
fullPage ? "/screenshot-full-page" : "/screenshot-viewport",
|
|
3957
4185
|
{
|
|
3958
|
-
...
|
|
4186
|
+
...path15 ? { path: path15 } : {},
|
|
3959
4187
|
...width != null ? { width } : {},
|
|
3960
4188
|
...height != null ? { height } : {},
|
|
3961
4189
|
...format ? { format } : {}
|
|
@@ -3973,7 +4201,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
3973
4201
|
styleMap = ssResult?.styleMap;
|
|
3974
4202
|
}
|
|
3975
4203
|
if (prompt === false) {
|
|
3976
|
-
return url;
|
|
4204
|
+
return resolveImageRef(url, apiConfig);
|
|
3977
4205
|
}
|
|
3978
4206
|
if (!model) {
|
|
3979
4207
|
throw new Error(
|
|
@@ -3981,7 +4209,8 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
3981
4209
|
);
|
|
3982
4210
|
}
|
|
3983
4211
|
return streamScreenshotAnalysis({
|
|
3984
|
-
url,
|
|
4212
|
+
image: url,
|
|
4213
|
+
apiConfig,
|
|
3985
4214
|
prompt: prompt || void 0,
|
|
3986
4215
|
styleMap,
|
|
3987
4216
|
onLog,
|
|
@@ -3994,6 +4223,7 @@ var init_screenshot = __esm({
|
|
|
3994
4223
|
"use strict";
|
|
3995
4224
|
init_sidecar();
|
|
3996
4225
|
init_analyzeImage();
|
|
4226
|
+
init_uploadImage();
|
|
3997
4227
|
VIEWPORT_CAPTURE_TIMEOUT_MS = 45e3;
|
|
3998
4228
|
FULLPAGE_CAPTURE_TIMEOUT_MS = 135e3;
|
|
3999
4229
|
SCREENSHOT_ANALYSIS_PROMPT = `Describe everything visible on screen from top to bottom \u2014 every element, its position, its size relative to the viewport, its colors, its content. Be comprehensive, thorough, and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components).`;
|
|
@@ -4020,12 +4250,12 @@ var init_browserLock = __esm({
|
|
|
4020
4250
|
});
|
|
4021
4251
|
|
|
4022
4252
|
// src/toolRegistry.ts
|
|
4023
|
-
var
|
|
4253
|
+
var log7, USER_CANCELLED_RESULT, ToolRegistry;
|
|
4024
4254
|
var init_toolRegistry = __esm({
|
|
4025
4255
|
"src/toolRegistry.ts"() {
|
|
4026
4256
|
"use strict";
|
|
4027
4257
|
init_logger();
|
|
4028
|
-
|
|
4258
|
+
log7 = createLogger("tool-registry");
|
|
4029
4259
|
USER_CANCELLED_RESULT = "[USER CANCELLED] The user manually cancelled this tool. Do not retry it automatically \u2014 wait for the user\u2019s next message for direction.";
|
|
4030
4260
|
ToolRegistry = class {
|
|
4031
4261
|
entries = /* @__PURE__ */ new Map();
|
|
@@ -4052,7 +4282,7 @@ var init_toolRegistry = __esm({
|
|
|
4052
4282
|
if (!entry) {
|
|
4053
4283
|
return false;
|
|
4054
4284
|
}
|
|
4055
|
-
|
|
4285
|
+
log7.info("Tool stopped", { toolCallId: id, name: entry.name, mode });
|
|
4056
4286
|
entry.abortController.abort(mode);
|
|
4057
4287
|
if (mode === "graceful") {
|
|
4058
4288
|
const partial = entry.getPartialResult?.() ?? "";
|
|
@@ -4085,7 +4315,7 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
|
4085
4315
|
if (!entry) {
|
|
4086
4316
|
return false;
|
|
4087
4317
|
}
|
|
4088
|
-
|
|
4318
|
+
log7.info("Tool restarted", { toolCallId: id, name: entry.name });
|
|
4089
4319
|
entry.abortController.abort("restart");
|
|
4090
4320
|
const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
|
|
4091
4321
|
this.onEvent?.({
|
|
@@ -4194,7 +4424,7 @@ async function runSubAgent(config) {
|
|
|
4194
4424
|
const signal = background ? bgAbort.signal : parentSignal;
|
|
4195
4425
|
const agentName = subAgentId || "sub-agent";
|
|
4196
4426
|
const runStart = Date.now();
|
|
4197
|
-
|
|
4427
|
+
log8.info("Sub-agent started", { requestId, parentToolId, agentName });
|
|
4198
4428
|
const emit = (e) => {
|
|
4199
4429
|
onEvent({ ...e, parentToolId });
|
|
4200
4430
|
};
|
|
@@ -4206,7 +4436,6 @@ async function runSubAgent(config) {
|
|
|
4206
4436
|
const fullSystem = `${system}
|
|
4207
4437
|
|
|
4208
4438
|
Current date: ${dateStr}`;
|
|
4209
|
-
const excludeToolsFromClearing = tools2.filter((t) => t.clearable === false).map((t) => t.name);
|
|
4210
4439
|
let turns = 0;
|
|
4211
4440
|
const run = async () => {
|
|
4212
4441
|
const historyLen = (history ?? []).length;
|
|
@@ -4278,7 +4507,6 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4278
4507
|
system: fullSystem,
|
|
4279
4508
|
messages: cleanMessagesForApi(messages),
|
|
4280
4509
|
tools: tools2,
|
|
4281
|
-
excludeToolsFromClearing,
|
|
4282
4510
|
signal
|
|
4283
4511
|
},
|
|
4284
4512
|
{
|
|
@@ -4414,7 +4642,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4414
4642
|
...hasArtifacts ? { artifacts } : {}
|
|
4415
4643
|
};
|
|
4416
4644
|
}
|
|
4417
|
-
|
|
4645
|
+
log8.info("Tools executing", {
|
|
4418
4646
|
requestId,
|
|
4419
4647
|
parentToolId,
|
|
4420
4648
|
count: toolCalls.length,
|
|
@@ -4491,7 +4719,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4491
4719
|
run2(tc.input);
|
|
4492
4720
|
const r = await resultPromise;
|
|
4493
4721
|
toolRegistry?.unregister(tc.id);
|
|
4494
|
-
|
|
4722
|
+
log8.info("Tool completed", {
|
|
4495
4723
|
requestId,
|
|
4496
4724
|
parentToolId,
|
|
4497
4725
|
toolCallId: tc.id,
|
|
@@ -4542,7 +4770,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4542
4770
|
const wrapRun = async () => {
|
|
4543
4771
|
try {
|
|
4544
4772
|
const result = await run();
|
|
4545
|
-
|
|
4773
|
+
log8.info("Sub-agent complete", {
|
|
4546
4774
|
requestId,
|
|
4547
4775
|
parentToolId,
|
|
4548
4776
|
agentName,
|
|
@@ -4551,7 +4779,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4551
4779
|
});
|
|
4552
4780
|
return result;
|
|
4553
4781
|
} catch (err) {
|
|
4554
|
-
|
|
4782
|
+
log8.warn("Sub-agent error", {
|
|
4555
4783
|
requestId,
|
|
4556
4784
|
parentToolId,
|
|
4557
4785
|
agentName,
|
|
@@ -4563,7 +4791,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4563
4791
|
if (!background) {
|
|
4564
4792
|
return wrapRun();
|
|
4565
4793
|
}
|
|
4566
|
-
|
|
4794
|
+
log8.info("Sub-agent backgrounded", { requestId, parentToolId, agentName });
|
|
4567
4795
|
toolRegistry?.register({
|
|
4568
4796
|
id: parentToolId,
|
|
4569
4797
|
name: agentName,
|
|
@@ -4598,7 +4826,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4598
4826
|
});
|
|
4599
4827
|
return { text: ack, messages: [], backgrounded: true };
|
|
4600
4828
|
}
|
|
4601
|
-
var
|
|
4829
|
+
var log8;
|
|
4602
4830
|
var init_runner = __esm({
|
|
4603
4831
|
"src/subagents/runner.ts"() {
|
|
4604
4832
|
"use strict";
|
|
@@ -4609,7 +4837,7 @@ var init_runner = __esm({
|
|
|
4609
4837
|
init_toolResultCap();
|
|
4610
4838
|
init_statusWatcher();
|
|
4611
4839
|
init_cleanMessages();
|
|
4612
|
-
|
|
4840
|
+
log8 = createLogger("sub-agent");
|
|
4613
4841
|
}
|
|
4614
4842
|
});
|
|
4615
4843
|
|
|
@@ -4786,13 +5014,13 @@ var init_tools2 = __esm({
|
|
|
4786
5014
|
});
|
|
4787
5015
|
|
|
4788
5016
|
// src/subagents/common/context.ts
|
|
4789
|
-
import
|
|
4790
|
-
import
|
|
5017
|
+
import fs16 from "fs";
|
|
5018
|
+
import path9 from "path";
|
|
4791
5019
|
function walkMdFiles(dir, skip) {
|
|
4792
5020
|
const files = [];
|
|
4793
5021
|
try {
|
|
4794
|
-
for (const entry of
|
|
4795
|
-
const full =
|
|
5022
|
+
for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
|
|
5023
|
+
const full = path9.join(dir, entry.name);
|
|
4796
5024
|
if (entry.isDirectory()) {
|
|
4797
5025
|
if (!skip?.has(entry.name)) {
|
|
4798
5026
|
files.push(...walkMdFiles(full, skip));
|
|
@@ -4805,9 +5033,9 @@ function walkMdFiles(dir, skip) {
|
|
|
4805
5033
|
}
|
|
4806
5034
|
return files.sort();
|
|
4807
5035
|
}
|
|
4808
|
-
function
|
|
5036
|
+
function parseFrontmatter2(filePath) {
|
|
4809
5037
|
try {
|
|
4810
|
-
const content =
|
|
5038
|
+
const content = fs16.readFileSync(filePath, "utf-8");
|
|
4811
5039
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
4812
5040
|
if (!match) {
|
|
4813
5041
|
return {};
|
|
@@ -4832,7 +5060,7 @@ function loadSpecIndex() {
|
|
|
4832
5060
|
return "";
|
|
4833
5061
|
}
|
|
4834
5062
|
const lines = files.map((f) => {
|
|
4835
|
-
const fm =
|
|
5063
|
+
const fm = parseFrontmatter2(f);
|
|
4836
5064
|
let line = `- ${f}`;
|
|
4837
5065
|
if (fm.name) {
|
|
4838
5066
|
line += ` \u2014 "${fm.name}"`;
|
|
@@ -4853,7 +5081,7 @@ function loadRoadmapIndex() {
|
|
|
4853
5081
|
const parts = [];
|
|
4854
5082
|
try {
|
|
4855
5083
|
const indexJson = JSON.parse(
|
|
4856
|
-
|
|
5084
|
+
fs16.readFileSync("src/roadmap/index.json", "utf-8")
|
|
4857
5085
|
);
|
|
4858
5086
|
if (indexJson.lanes?.length > 0) {
|
|
4859
5087
|
const laneLines = indexJson.lanes.map(
|
|
@@ -4873,7 +5101,7 @@ ${indexJson.standalone.map((s) => `- ${s}`).join("\n")}`
|
|
|
4873
5101
|
const files = walkMdFiles("src/roadmap");
|
|
4874
5102
|
if (files.length > 0) {
|
|
4875
5103
|
const lines = files.map((f) => {
|
|
4876
|
-
const fm =
|
|
5104
|
+
const fm = parseFrontmatter2(f);
|
|
4877
5105
|
let line = `- ${f}`;
|
|
4878
5106
|
if (fm.name) {
|
|
4879
5107
|
line += ` \u2014 "${fm.name}"`;
|
|
@@ -5076,7 +5304,7 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
5076
5304
|
step.result.analysis = analyses[i]?.output?.analysis || analyses[i]?.output || "";
|
|
5077
5305
|
});
|
|
5078
5306
|
} catch {
|
|
5079
|
-
|
|
5307
|
+
log9.debug("Failed to parse batch analysis result", {
|
|
5080
5308
|
batchResult
|
|
5081
5309
|
});
|
|
5082
5310
|
}
|
|
@@ -5099,7 +5327,7 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
5099
5327
|
release();
|
|
5100
5328
|
}
|
|
5101
5329
|
}
|
|
5102
|
-
var
|
|
5330
|
+
var log9, CAPTURE_COMMANDS, browserAutomationTool;
|
|
5103
5331
|
var init_browserAutomation = __esm({
|
|
5104
5332
|
"src/subagents/browserAutomation/index.ts"() {
|
|
5105
5333
|
"use strict";
|
|
@@ -5115,11 +5343,11 @@ var init_browserAutomation = __esm({
|
|
|
5115
5343
|
init_runMindstudioCli();
|
|
5116
5344
|
init_surfaces();
|
|
5117
5345
|
init_logger();
|
|
5118
|
-
|
|
5346
|
+
log9 = createLogger("browser-automation");
|
|
5119
5347
|
CAPTURE_COMMANDS = /* @__PURE__ */ new Set(["screenshotViewport", "screenshotFullPage"]);
|
|
5120
5348
|
browserAutomationTool = {
|
|
5121
|
-
clearable: true,
|
|
5122
5349
|
definition: {
|
|
5350
|
+
clearable: true,
|
|
5123
5351
|
name: "runAutomatedBrowserTest",
|
|
5124
5352
|
description: "Run an automated browser test against the live preview. Describe what to test \u2014 the agent figures out how. Use after meaningful changes to frontend code, to reproduce user-reported issues, or to test end-to-end flows. Never give it explicit values to use when filling out forms or creating accounts \u2014 it will use its own judgement (often it needs specific values to trigger dev-mode bypasses of things like login verification codes).",
|
|
5125
5353
|
inputSchema: {
|
|
@@ -5157,9 +5385,10 @@ async function executeScreenshot(input, onLog, context) {
|
|
|
5157
5385
|
if (input.imageUrl) {
|
|
5158
5386
|
return await captureAndAnalyzeScreenshot({
|
|
5159
5387
|
prompt: input.prompt,
|
|
5160
|
-
|
|
5388
|
+
image: input.imageUrl,
|
|
5161
5389
|
onLog,
|
|
5162
|
-
model
|
|
5390
|
+
model,
|
|
5391
|
+
apiConfig: context?.apiConfig
|
|
5163
5392
|
});
|
|
5164
5393
|
}
|
|
5165
5394
|
if (input.instructions && context) {
|
|
@@ -5172,11 +5401,12 @@ async function executeScreenshot(input, onLog, context) {
|
|
|
5172
5401
|
return result.text;
|
|
5173
5402
|
}
|
|
5174
5403
|
return await streamScreenshotAnalysis({
|
|
5175
|
-
|
|
5404
|
+
image: result.screenshot.url,
|
|
5176
5405
|
prompt: input.prompt,
|
|
5177
5406
|
styleMap: result.screenshot.styleMap,
|
|
5178
5407
|
onLog,
|
|
5179
|
-
model
|
|
5408
|
+
model,
|
|
5409
|
+
apiConfig: context?.apiConfig
|
|
5180
5410
|
});
|
|
5181
5411
|
}
|
|
5182
5412
|
const release = await acquireBrowserLock();
|
|
@@ -5189,7 +5419,8 @@ async function executeScreenshot(input, onLog, context) {
|
|
|
5189
5419
|
height: input.height,
|
|
5190
5420
|
format: input.format,
|
|
5191
5421
|
onLog,
|
|
5192
|
-
model
|
|
5422
|
+
model,
|
|
5423
|
+
apiConfig: context?.apiConfig
|
|
5193
5424
|
});
|
|
5194
5425
|
} finally {
|
|
5195
5426
|
release();
|
|
@@ -5209,7 +5440,7 @@ var init_screenshot2 = __esm({
|
|
|
5209
5440
|
screenshotDefinition = {
|
|
5210
5441
|
clearable: true,
|
|
5211
5442
|
name: "screenshot",
|
|
5212
|
-
description: "Capture a screenshot of the app preview and get a description of what's on screen. Choose `fullPage`: `false` captures just the visible viewport (fast \u2014 for a specific section the page is scrolled to), `true` captures the entire page top-to-bottom (slower \u2014 for overall composition or content past the fold). Captures the settled page state \u2014 it cannot catch animations, transitions, or transient state. The analysis is not precise about every detail \u2014 for example it cannot reliably identify specific fonts by name, only describe what the letterforms look like. Optionally provide specific questions about what you're looking for. Use a bulleted list to ask many questions at once. To ask additional questions about a screenshot you have already captured, pass its URL as imageUrl to skip recapture. If the screenshot requires interaction first (logging in, clicking a tab, dismissing a modal, scrolling to a section), use the instructions param to describe the steps. To render a fixed-size image such as an Open Graph share card, set `width` and `height` (e.g. 1200 \xD7 630) and `format: 'png'`: the tool navigates to `path`, clips to exactly those pixel dimensions, and returns the image URL.",
|
|
5443
|
+
description: "Capture a screenshot of the app preview and get a description of what's on screen. Choose `fullPage`: `false` captures just the visible viewport (fast \u2014 for a specific section the page is scrolled to), `true` captures the entire page top-to-bottom (slower \u2014 for overall composition or content past the fold). Captures the settled page state \u2014 it cannot catch animations, transitions, or transient state. The analysis is not precise about every detail \u2014 for example it cannot reliably identify specific fonts by name, only describe what the letterforms look like. Optionally provide specific questions about what you're looking for. Use a bulleted list to ask many questions at once. To ask additional questions about a screenshot you have already captured, pass its URL as `imageUrl` to skip recapture; `imageUrl` also accepts the disk path of an image file (a user upload, a saved asset) to analyze that instead of the preview. If the screenshot requires interaction first (logging in, clicking a tab, dismissing a modal, scrolling to a section), use the instructions param to describe the steps. To render a fixed-size image such as an Open Graph share card, set `width` and `height` (e.g. 1200 \xD7 630) and `format: 'png'`: the tool navigates to `path`, clips to exactly those pixel dimensions, and returns the image URL.",
|
|
5213
5444
|
inputSchema: {
|
|
5214
5445
|
type: "object",
|
|
5215
5446
|
properties: {
|
|
@@ -5223,7 +5454,7 @@ var init_screenshot2 = __esm({
|
|
|
5223
5454
|
},
|
|
5224
5455
|
imageUrl: {
|
|
5225
5456
|
type: "string",
|
|
5226
|
-
description: "
|
|
5457
|
+
description: "An existing image to analyze instead of capturing a new one \u2014 the URL of a previous screenshot (for follow-up questions about it), or the path of an image file on disk such as a user upload. Local files are hosted automatically and the returned URL can be reused."
|
|
5227
5458
|
},
|
|
5228
5459
|
path: {
|
|
5229
5460
|
type: "string",
|
|
@@ -5251,7 +5482,6 @@ var init_screenshot2 = __esm({
|
|
|
5251
5482
|
}
|
|
5252
5483
|
};
|
|
5253
5484
|
screenshotTool = {
|
|
5254
|
-
clearable: true,
|
|
5255
5485
|
definition: screenshotDefinition,
|
|
5256
5486
|
execute: (input, context) => executeScreenshot(input, context?.onLog, context)
|
|
5257
5487
|
};
|
|
@@ -5351,9 +5581,9 @@ __export(analyzeDesign_exports, {
|
|
|
5351
5581
|
async function execute3(input, onLog, context) {
|
|
5352
5582
|
const url = input.url;
|
|
5353
5583
|
const analysisPrompt = input.prompt || DESIGN_REFERENCE_PROMPT;
|
|
5354
|
-
const
|
|
5355
|
-
let
|
|
5356
|
-
if (!
|
|
5584
|
+
const isImage = /\.(png|jpe?g|webp|gif|svg|avif)(\?|$)/i.test(url);
|
|
5585
|
+
let image = url;
|
|
5586
|
+
if (!isImage) {
|
|
5357
5587
|
const ss = await runMindstudioCliResult(
|
|
5358
5588
|
[
|
|
5359
5589
|
"screenshot-url",
|
|
@@ -5376,15 +5606,16 @@ async function execute3(input, onLog, context) {
|
|
|
5376
5606
|
if (!ss.ok) {
|
|
5377
5607
|
return `Could not screenshot ${url}: ${ss.value}`;
|
|
5378
5608
|
}
|
|
5379
|
-
|
|
5609
|
+
image = ss.value;
|
|
5380
5610
|
}
|
|
5381
|
-
const
|
|
5611
|
+
const analyzed = await analyzeImage({
|
|
5382
5612
|
prompt: analysisPrompt,
|
|
5383
|
-
|
|
5613
|
+
image,
|
|
5614
|
+
apiConfig: context?.apiConfig,
|
|
5384
5615
|
onLog,
|
|
5385
5616
|
model: resolveModel("imageAnalysis", context?.models, context?.model)
|
|
5386
5617
|
});
|
|
5387
|
-
return JSON.stringify({ url:
|
|
5618
|
+
return JSON.stringify({ url: analyzed.url, analysis: analyzed.analysis });
|
|
5388
5619
|
}
|
|
5389
5620
|
var DESIGN_REFERENCE_PROMPT, definition3;
|
|
5390
5621
|
var init_analyzeDesign = __esm({
|
|
@@ -5419,13 +5650,13 @@ Respond only with your analysis as Markdown and absolutely no other text. Do not
|
|
|
5419
5650
|
definition3 = {
|
|
5420
5651
|
clearable: false,
|
|
5421
5652
|
name: "analyzeDesign",
|
|
5422
|
-
description: "Analyze the visual design of a website or image
|
|
5653
|
+
description: "Analyze the visual design of a website, an image URL, or an image file on disk. Websites are automatically screenshotted first. Provides static image analysis only, will not capture animations or video. If no prompt is provided, performs a full design reference analysis (mood, color, typography, layout, distinctiveness). Provide a custom prompt to ask a specific design question instead. Use a bulleted list to ask many questions at once.",
|
|
5423
5654
|
inputSchema: {
|
|
5424
5655
|
type: "object",
|
|
5425
5656
|
properties: {
|
|
5426
5657
|
url: {
|
|
5427
5658
|
type: "string",
|
|
5428
|
-
description: "
|
|
5659
|
+
description: "What to analyze: a website URL (will be screenshotted), an image URL, or the path of an image file on disk (e.g. a reference the user uploaded, under src/.user-uploads/). Local files are hosted automatically and their URL comes back in the result."
|
|
5429
5660
|
},
|
|
5430
5661
|
prompt: {
|
|
5431
5662
|
type: "string",
|
|
@@ -5445,17 +5676,17 @@ __export(analyzeImage_exports, {
|
|
|
5445
5676
|
execute: () => execute4
|
|
5446
5677
|
});
|
|
5447
5678
|
async function execute4(input, onLog, context) {
|
|
5448
|
-
const imageUrl = input.imageUrl;
|
|
5449
5679
|
const prompt = buildScreenshotAnalysisPrompt({
|
|
5450
5680
|
prompt: input.prompt
|
|
5451
5681
|
});
|
|
5452
|
-
const analysis = await analyzeImage({
|
|
5682
|
+
const { url, analysis } = await analyzeImage({
|
|
5453
5683
|
prompt,
|
|
5454
|
-
imageUrl,
|
|
5684
|
+
image: input.imageUrl,
|
|
5685
|
+
apiConfig: context?.apiConfig,
|
|
5455
5686
|
onLog,
|
|
5456
5687
|
model: resolveModel("imageAnalysis", context?.models, context?.model)
|
|
5457
5688
|
});
|
|
5458
|
-
return JSON.stringify({ url
|
|
5689
|
+
return JSON.stringify({ url, analysis });
|
|
5459
5690
|
}
|
|
5460
5691
|
var definition4;
|
|
5461
5692
|
var init_analyzeImage2 = __esm({
|
|
@@ -5467,13 +5698,16 @@ var init_analyzeImage2 = __esm({
|
|
|
5467
5698
|
definition4 = {
|
|
5468
5699
|
clearable: true,
|
|
5469
5700
|
name: "analyzeImage",
|
|
5470
|
-
description: "Analyze an image
|
|
5701
|
+
description: "Analyze an image using a vision model. Provides static image analysis only, will not capture animations or video. Returns an objective description of what is visible \u2014 shapes, colors, layout, text, artifacts. Use for factual inventory of image contents, not for subjective design judgment - the vision model providing the analysis has no sense of design. You are the design expert - use the analysis tool for factual inventory, then apply your own expertise for quality and suitability assessments. Optionally provide specific questions about what you're looking for. Use a bulleted list to ask many questions at once. If you are analyzing a screenshot of the app preview, you can reuse the same screenshot URL multiple times to ask multiple questions.",
|
|
5471
5702
|
inputSchema: {
|
|
5472
5703
|
type: "object",
|
|
5473
5704
|
properties: {
|
|
5705
|
+
// Still `imageUrl` even though a path works: the builder renders this
|
|
5706
|
+
// tool's thumbnail from `input.imageUrl` and deploys separately, so
|
|
5707
|
+
// renaming the property would blank the view under version skew.
|
|
5474
5708
|
imageUrl: {
|
|
5475
5709
|
type: "string",
|
|
5476
|
-
description: "The image URL
|
|
5710
|
+
description: "The image to analyze: either a URL, or the path of an image file on disk (e.g. a reference screenshot the user uploaded, under src/.user-uploads/). Local files are hosted automatically; the URL comes back in the result and can be reused for follow-up questions or embedded in a spec."
|
|
5477
5711
|
},
|
|
5478
5712
|
prompt: {
|
|
5479
5713
|
type: "string",
|
|
@@ -5552,14 +5786,15 @@ var init_enhancePrompt = __esm({
|
|
|
5552
5786
|
async function generateImageAssets(opts) {
|
|
5553
5787
|
const {
|
|
5554
5788
|
prompts,
|
|
5555
|
-
sourceImages,
|
|
5556
5789
|
transparentBackground,
|
|
5557
5790
|
enhancePrompts,
|
|
5558
5791
|
onLog,
|
|
5792
|
+
apiConfig,
|
|
5559
5793
|
imageGenerationModel: genModel,
|
|
5560
5794
|
imageAnalysisModel,
|
|
5561
5795
|
imagePromptEnhancerModel
|
|
5562
5796
|
} = opts;
|
|
5797
|
+
const sourceImages = opts.sourceImages?.length ? await resolveImageRefs(opts.sourceImages, apiConfig) : void 0;
|
|
5563
5798
|
const width = opts.width || 2048;
|
|
5564
5799
|
const height = opts.height || 2048;
|
|
5565
5800
|
const config = { width, height };
|
|
@@ -5665,10 +5900,10 @@ async function generateImageAssets(opts) {
|
|
|
5665
5900
|
}
|
|
5666
5901
|
const analysis = await analyzeImage({
|
|
5667
5902
|
prompt: ANALYZE_PROMPT,
|
|
5668
|
-
|
|
5903
|
+
image: url,
|
|
5669
5904
|
onLog,
|
|
5670
5905
|
model: imageAnalysisModel
|
|
5671
|
-
});
|
|
5906
|
+
}).then((r) => r.analysis).catch((err) => `Could not review this image: ${err.message}`);
|
|
5672
5907
|
return {
|
|
5673
5908
|
url,
|
|
5674
5909
|
prompt: prompts[i],
|
|
@@ -5687,6 +5922,7 @@ var init_imageGenerator = __esm({
|
|
|
5687
5922
|
"use strict";
|
|
5688
5923
|
init_runMindstudioCli();
|
|
5689
5924
|
init_analyzeImage();
|
|
5925
|
+
init_uploadImage();
|
|
5690
5926
|
init_enhancePrompt();
|
|
5691
5927
|
ANALYZE_PROMPT = 'You are reviewing this image for a visual designer sourcing assets for a project. Describe: what the image depicts, the mood and color palette, how the lighting and composition work, any text present in the image, whether there are any issues (artifacts, distortions), and how it could be used in a layout for an app or website. Be concise and practical. Respond only with your analysis as Markdown (starting with the title "Asset Review") and absolutely no other text. Do not use emojis - use unicode if you need symbols.';
|
|
5692
5928
|
}
|
|
@@ -5707,6 +5943,7 @@ async function execute5(input, onLog, context) {
|
|
|
5707
5943
|
sourceImages: input.referenceImage ? [input.referenceImage] : void 0,
|
|
5708
5944
|
enhancePrompts: true,
|
|
5709
5945
|
onLog,
|
|
5946
|
+
apiConfig: context?.apiConfig,
|
|
5710
5947
|
imageGenerationModel: resolveModel(
|
|
5711
5948
|
"imageGeneration",
|
|
5712
5949
|
context?.models,
|
|
@@ -5746,7 +5983,7 @@ var init_generateImages = __esm({
|
|
|
5746
5983
|
},
|
|
5747
5984
|
referenceImage: {
|
|
5748
5985
|
type: "string",
|
|
5749
|
-
description: "Optional
|
|
5986
|
+
description: "Optional single reference image to guide the generation \u2014 for style, subject, character consistency, or composition. Either a URL or the path of an image file on disk (e.g. a user upload under src/.user-uploads/). Your prompt still describes the desired result; the reference conditions it. Applies to every prompt in the batch."
|
|
5750
5987
|
},
|
|
5751
5988
|
width: {
|
|
5752
5989
|
type: "number",
|
|
@@ -5782,6 +6019,7 @@ async function execute6(input, onLog, context) {
|
|
|
5782
6019
|
transparentBackground: input.transparentBackground,
|
|
5783
6020
|
enhancePrompts: false,
|
|
5784
6021
|
onLog,
|
|
6022
|
+
apiConfig: context?.apiConfig,
|
|
5785
6023
|
imageGenerationModel: resolveModel(
|
|
5786
6024
|
"imageGeneration",
|
|
5787
6025
|
context?.models,
|
|
@@ -5824,7 +6062,7 @@ var init_editImages = __esm({
|
|
|
5824
6062
|
items: {
|
|
5825
6063
|
type: "string"
|
|
5826
6064
|
},
|
|
5827
|
-
description: "One or more source/reference image
|
|
6065
|
+
description: "One or more source/reference images, each a URL or the path of an image file on disk (e.g. a user upload under src/.user-uploads/). These are used as the basis for the edit \u2014 the AI will use them as reference for style, subject, or composition."
|
|
5828
6066
|
},
|
|
5829
6067
|
width: {
|
|
5830
6068
|
type: "number",
|
|
@@ -5868,8 +6106,8 @@ var init_copyEditor = __esm({
|
|
|
5868
6106
|
init_surfaces();
|
|
5869
6107
|
BASE_PROMPT2 = readAsset("subagents/copyEditor", "prompt.md");
|
|
5870
6108
|
copyEditorTool = {
|
|
5871
|
-
clearable: false,
|
|
5872
6109
|
definition: {
|
|
6110
|
+
clearable: false,
|
|
5873
6111
|
name: "copyEditor",
|
|
5874
6112
|
description: "Hand it user-facing copy and it hands back a sharper version \u2014 better structured for its audience and free of the overused words, telltale constructions, and rhythms that make text read as AI-generated. Think of it as a design expert for words: it elevates how the copy communicates and strips the AI fingerprints, but it never invents facts or claims you didn't give it. Use it on anything users will read: in-app strings, empty states, errors, the Build Overview, deck copy, launch posts, Slack announcements. Readonly.",
|
|
5875
6113
|
inputSchema: {
|
|
@@ -5998,7 +6236,7 @@ async function initOrgContext(config) {
|
|
|
5998
6236
|
cached = await fetchRemyContext(config);
|
|
5999
6237
|
const orgDefaultModels2 = filterModelPicks(cached?.defaultModels);
|
|
6000
6238
|
setOrgDefaultModels(orgDefaultModels2);
|
|
6001
|
-
|
|
6239
|
+
log10.debug("org context loaded", {
|
|
6002
6240
|
delegatedAvailable: cached?.auth?.delegatedAvailable ?? false,
|
|
6003
6241
|
requireDelegatedOnly: cached?.auth?.requireDelegatedOnly ?? false,
|
|
6004
6242
|
hasOrgName: !!cached?.org?.name,
|
|
@@ -6007,7 +6245,7 @@ async function initOrgContext(config) {
|
|
|
6007
6245
|
} catch (err) {
|
|
6008
6246
|
cached = null;
|
|
6009
6247
|
setOrgDefaultModels({});
|
|
6010
|
-
|
|
6248
|
+
log10.debug("org context init failed", { error: err.message });
|
|
6011
6249
|
}
|
|
6012
6250
|
}
|
|
6013
6251
|
function getOrgContext() {
|
|
@@ -6043,20 +6281,20 @@ function renderOrgContextBlock() {
|
|
|
6043
6281
|
lines.push("</org_context>");
|
|
6044
6282
|
return lines.join("\n");
|
|
6045
6283
|
}
|
|
6046
|
-
var
|
|
6284
|
+
var log10, cached;
|
|
6047
6285
|
var init_orgContext = __esm({
|
|
6048
6286
|
"src/orgContext.ts"() {
|
|
6049
6287
|
"use strict";
|
|
6050
6288
|
init_api();
|
|
6051
6289
|
init_surfaces();
|
|
6052
6290
|
init_logger();
|
|
6053
|
-
|
|
6291
|
+
log10 = createLogger("orgContext");
|
|
6054
6292
|
cached = null;
|
|
6055
6293
|
}
|
|
6056
6294
|
});
|
|
6057
6295
|
|
|
6058
6296
|
// src/subagents/designExpert/data/sampleCache.ts
|
|
6059
|
-
import
|
|
6297
|
+
import fs17 from "fs";
|
|
6060
6298
|
function generateIndices(poolSize, sampleSize) {
|
|
6061
6299
|
const n = Math.min(sampleSize, poolSize);
|
|
6062
6300
|
const indices = Array.from({ length: poolSize }, (_, i) => i);
|
|
@@ -6068,14 +6306,14 @@ function generateIndices(poolSize, sampleSize) {
|
|
|
6068
6306
|
}
|
|
6069
6307
|
function load() {
|
|
6070
6308
|
try {
|
|
6071
|
-
return JSON.parse(
|
|
6309
|
+
return JSON.parse(fs17.readFileSync(SAMPLE_FILE, "utf-8"));
|
|
6072
6310
|
} catch {
|
|
6073
6311
|
return null;
|
|
6074
6312
|
}
|
|
6075
6313
|
}
|
|
6076
6314
|
function save(indices) {
|
|
6077
6315
|
try {
|
|
6078
|
-
|
|
6316
|
+
fs17.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
|
|
6079
6317
|
} catch {
|
|
6080
6318
|
}
|
|
6081
6319
|
}
|
|
@@ -6425,8 +6663,8 @@ Visual design expert. Describe the situation and what you need \u2014 the agent
|
|
|
6425
6663
|
editFileTool.definition
|
|
6426
6664
|
];
|
|
6427
6665
|
designExpertTool = {
|
|
6428
|
-
clearable: false,
|
|
6429
6666
|
definition: {
|
|
6667
|
+
clearable: false,
|
|
6430
6668
|
name: "visualDesignExpert",
|
|
6431
6669
|
description: DESCRIPTION,
|
|
6432
6670
|
inputSchema: {
|
|
@@ -6521,23 +6759,23 @@ var init_tools5 = __esm({
|
|
|
6521
6759
|
});
|
|
6522
6760
|
|
|
6523
6761
|
// src/subagents/productVision/executor.ts
|
|
6524
|
-
import
|
|
6525
|
-
import
|
|
6526
|
-
function
|
|
6527
|
-
return
|
|
6762
|
+
import fs18 from "fs";
|
|
6763
|
+
import path10 from "path";
|
|
6764
|
+
function resolve2(filePath) {
|
|
6765
|
+
return path10.join(ROADMAP_DIR, filePath);
|
|
6528
6766
|
}
|
|
6529
6767
|
async function executeVisionTool(name, input, context) {
|
|
6530
6768
|
switch (name) {
|
|
6531
6769
|
case "writeFile": {
|
|
6532
|
-
const filePath =
|
|
6770
|
+
const filePath = resolve2(input.path);
|
|
6533
6771
|
try {
|
|
6534
|
-
|
|
6772
|
+
fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
6535
6773
|
let oldContent = null;
|
|
6536
6774
|
try {
|
|
6537
|
-
oldContent =
|
|
6775
|
+
oldContent = fs18.readFileSync(filePath, "utf-8");
|
|
6538
6776
|
} catch {
|
|
6539
6777
|
}
|
|
6540
|
-
|
|
6778
|
+
fs18.writeFileSync(filePath, input.content, "utf-8");
|
|
6541
6779
|
const lineCount = input.content.split("\n").length;
|
|
6542
6780
|
const label = oldContent !== null ? "Wrote" : "Created";
|
|
6543
6781
|
return `${label} ${filePath} (${lineCount} lines)
|
|
@@ -6547,13 +6785,13 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
|
|
|
6547
6785
|
}
|
|
6548
6786
|
}
|
|
6549
6787
|
case "deleteFile": {
|
|
6550
|
-
const filePath =
|
|
6788
|
+
const filePath = resolve2(input.path);
|
|
6551
6789
|
try {
|
|
6552
|
-
if (!
|
|
6790
|
+
if (!fs18.existsSync(filePath)) {
|
|
6553
6791
|
return `Error: ${filePath} does not exist`;
|
|
6554
6792
|
}
|
|
6555
|
-
const oldContent =
|
|
6556
|
-
|
|
6793
|
+
const oldContent = fs18.readFileSync(filePath, "utf-8");
|
|
6794
|
+
fs18.unlinkSync(filePath);
|
|
6557
6795
|
return `Deleted ${filePath}
|
|
6558
6796
|
${unifiedDiff(filePath, oldContent, "")}`;
|
|
6559
6797
|
} catch (err) {
|
|
@@ -6564,11 +6802,11 @@ ${unifiedDiff(filePath, oldContent, "")}`;
|
|
|
6564
6802
|
if (!context) {
|
|
6565
6803
|
return "Error: writePitchDeck requires execution context for design expert delegation";
|
|
6566
6804
|
}
|
|
6567
|
-
const filePath =
|
|
6805
|
+
const filePath = resolve2("pitch.html");
|
|
6568
6806
|
try {
|
|
6569
|
-
|
|
6570
|
-
const exists =
|
|
6571
|
-
const before = exists ?
|
|
6807
|
+
fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
6808
|
+
const exists = fs18.existsSync(filePath);
|
|
6809
|
+
const before = exists ? fs18.statSync(filePath).mtimeMs : null;
|
|
6572
6810
|
const delivery = exists ? `### Your deliverable
|
|
6573
6811
|
The pitch deck already exists at \`${filePath}\`. Read it, then update it for the new <pitch_content>, keeping the presentation scaffolding intact \u2014 change only what needs to change.
|
|
6574
6812
|
|
|
@@ -6598,11 +6836,11 @@ Maintain the bones of the presentation scaffolding. Always keep the progress bar
|
|
|
6598
6836
|
${delivery}`;
|
|
6599
6837
|
const result = await runDesignExpertRender({ task }, context);
|
|
6600
6838
|
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
6601
|
-
if (!
|
|
6839
|
+
if (!fs18.existsSync(filePath)) {
|
|
6602
6840
|
return `Error: the design expert did not write ${filePath}. Its reply was:
|
|
6603
6841
|
${result.text}`;
|
|
6604
6842
|
}
|
|
6605
|
-
if (before !== null &&
|
|
6843
|
+
if (before !== null && fs18.statSync(filePath).mtimeMs === before) {
|
|
6606
6844
|
return `Error: the pitch deck at ${filePath} was not modified. The design expert's reply was:
|
|
6607
6845
|
${result.text}`;
|
|
6608
6846
|
}
|
|
@@ -6668,8 +6906,8 @@ var init_productVision = __esm({
|
|
|
6668
6906
|
init_history();
|
|
6669
6907
|
init_surfaces();
|
|
6670
6908
|
productVisionTool = {
|
|
6671
|
-
clearable: false,
|
|
6672
6909
|
definition: {
|
|
6910
|
+
clearable: false,
|
|
6673
6911
|
name: "productVision",
|
|
6674
6912
|
description: "Owns the product roadmap. Reads spec and roadmap files automatically. Creates, updates, and deletes roadmap items in src/roadmap/. Describe the situation and what needs to happen.",
|
|
6675
6913
|
inputSchema: {
|
|
@@ -6800,8 +7038,8 @@ var init_codeSanityCheck = __esm({
|
|
|
6800
7038
|
init_surfaces();
|
|
6801
7039
|
BASE_PROMPT4 = readAsset("subagents/codeSanityCheck", "prompt.md");
|
|
6802
7040
|
codeSanityCheckTool = {
|
|
6803
|
-
clearable: false,
|
|
6804
7041
|
definition: {
|
|
7042
|
+
clearable: false,
|
|
6805
7043
|
name: "codeSanityCheck",
|
|
6806
7044
|
description: 'Quick sanity check on an approach before building. Reviews architecture, package choices, and flags potential issues. Usually responds with "looks good." Occasionally catches something important. Readonly \u2014 can search the web and read code but cannot modify anything.',
|
|
6807
7045
|
inputSchema: {
|
|
@@ -6905,9 +7143,9 @@ ${readAsset(
|
|
|
6905
7143
|
)}
|
|
6906
7144
|
</mindstudio_flavored_markdown_spec_docs>`;
|
|
6907
7145
|
specSyncTool = {
|
|
6908
|
-
clearable: false,
|
|
6909
7146
|
backgroundOnly: true,
|
|
6910
7147
|
definition: {
|
|
7148
|
+
clearable: false,
|
|
6911
7149
|
name: "specSync",
|
|
6912
7150
|
description: "Reconcile the spec to bring it in line with code changes you have made. Provide a brief, bulleted list of what changed and why; it finds the affected spec sections and updates them to match. Always runs in the background \u2014 it returns immediately and reports back when done.",
|
|
6913
7151
|
inputSchema: {
|
|
@@ -6982,8 +7220,8 @@ var init_scrapeWebUrl2 = __esm({
|
|
|
6982
7220
|
init_runMindstudioCli();
|
|
6983
7221
|
init_runCli();
|
|
6984
7222
|
scrapeWebUrlTool = {
|
|
6985
|
-
clearable: false,
|
|
6986
7223
|
definition: {
|
|
7224
|
+
clearable: false,
|
|
6987
7225
|
name: "scrapeWebUrl",
|
|
6988
7226
|
description: "Scrape the content of a web page. Returns the HTML of the page as markdown text. Optionally capture a screenshot if you need see the visual design. Use this when you need to fetch or analyze content from a website",
|
|
6989
7227
|
inputSchema: {
|
|
@@ -7028,7 +7266,7 @@ var init_scrapeWebUrl2 = __esm({
|
|
|
7028
7266
|
});
|
|
7029
7267
|
|
|
7030
7268
|
// src/tools/spec/writeBuildOverview.ts
|
|
7031
|
-
import
|
|
7269
|
+
import fs19 from "fs";
|
|
7032
7270
|
function initialDelivery() {
|
|
7033
7271
|
return `### Your deliverable
|
|
7034
7272
|
Write the complete Build Overview to \`${OVERVIEW_FILE}\`. The file does not exist yet \u2014 start from this scaffold, which carries the technical hygiene for the iframed render context (keep the viewport meta and the reset). Layout, sections, type, and styling are yours to compose.
|
|
@@ -7090,8 +7328,8 @@ The copy in <overview_copy> is final \u2014 it was authored and edited before it
|
|
|
7090
7328
|
</body>
|
|
7091
7329
|
</html>`;
|
|
7092
7330
|
buildOverviewTool = {
|
|
7093
|
-
clearable: false,
|
|
7094
7331
|
definition: {
|
|
7332
|
+
clearable: false,
|
|
7095
7333
|
name: "writeBuildOverview",
|
|
7096
7334
|
description: "Generate or refresh the Build Overview \u2014 the project's home page in the Spec tab: a single-page, plain-language reference of everything the app actually contains, including the parts the user can't see (data stores, backend operations, access and roles, background jobs, seeded scenarios, the design system). You author the full copy: read the manifest and spec and state, plainly and exactly, what genuinely exists \u2014 real names and accurate counts \u2014 in calm, declarative, present-tense outcome language, with no persuasion or hype. Describe only what exists. Pass the complete copy as `content`; the design expert lays it out and skins it to the app's brand using your copy verbatim \u2014 it typesets your words, it does not rewrite them, so polish the copy before you pass it. Generate it at the end of a build and refresh it after meaningful work.",
|
|
7097
7335
|
inputSchema: {
|
|
@@ -7113,7 +7351,7 @@ The copy in <overview_copy> is final \u2014 it was authored and edited before it
|
|
|
7113
7351
|
if (!content) {
|
|
7114
7352
|
return "Error: writeBuildOverview requires non-empty `content` (the overview copy).";
|
|
7115
7353
|
}
|
|
7116
|
-
const exists =
|
|
7354
|
+
const exists = fs19.existsSync(OVERVIEW_FILE);
|
|
7117
7355
|
const task = `<overview_copy>${content}</overview_copy>
|
|
7118
7356
|
|
|
7119
7357
|
${DESIGN_BRIEF}
|
|
@@ -7131,7 +7369,7 @@ ${exists ? refreshDelivery() : initialDelivery()}`;
|
|
|
7131
7369
|
}
|
|
7132
7370
|
const result = await runDesignExpertRender({ task }, context);
|
|
7133
7371
|
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
7134
|
-
if (!
|
|
7372
|
+
if (!fs19.existsSync(OVERVIEW_FILE)) {
|
|
7135
7373
|
return `Error: the design expert did not write ${OVERVIEW_FILE}. Its reply was:
|
|
7136
7374
|
${result.text}`;
|
|
7137
7375
|
}
|
|
@@ -7161,7 +7399,7 @@ function executeTool(name, input, context) {
|
|
|
7161
7399
|
}
|
|
7162
7400
|
return tool.execute(input, context);
|
|
7163
7401
|
}
|
|
7164
|
-
var ALL_TOOLS,
|
|
7402
|
+
var ALL_TOOLS, SUBAGENT_TOOL_NAMES;
|
|
7165
7403
|
var init_tools8 = __esm({
|
|
7166
7404
|
"src/tools/index.ts"() {
|
|
7167
7405
|
"use strict";
|
|
@@ -7179,6 +7417,7 @@ var init_tools8 = __esm({
|
|
|
7179
7417
|
init_searchGoogle();
|
|
7180
7418
|
init_setProjectMetadata();
|
|
7181
7419
|
init_compactConversation();
|
|
7420
|
+
init_loadSkill();
|
|
7182
7421
|
init_readFile();
|
|
7183
7422
|
init_writeFile();
|
|
7184
7423
|
init_editFile();
|
|
@@ -7243,11 +7482,11 @@ var init_tools8 = __esm({
|
|
|
7243
7482
|
browserAutomationTool,
|
|
7244
7483
|
// LSP
|
|
7245
7484
|
lspDiagnosticsTool,
|
|
7246
|
-
restartProcessTool
|
|
7485
|
+
restartProcessTool,
|
|
7486
|
+
// Appended rather than grouped: position is part of the cache prefix, so a
|
|
7487
|
+
// new tool goes at the end to leave every existing session's prefix intact.
|
|
7488
|
+
loadSkillTool
|
|
7247
7489
|
];
|
|
7248
|
-
CLEARABLE_TOOLS = new Set(
|
|
7249
|
-
ALL_TOOLS.filter((t) => t.clearable).map((t) => t.definition.name)
|
|
7250
|
-
);
|
|
7251
7490
|
SUBAGENT_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
7252
7491
|
"visualDesignExpert",
|
|
7253
7492
|
"productVision",
|
|
@@ -7470,35 +7709,35 @@ var init_errors = __esm({
|
|
|
7470
7709
|
});
|
|
7471
7710
|
|
|
7472
7711
|
// src/brandExtraction/index.ts
|
|
7473
|
-
import
|
|
7474
|
-
import
|
|
7712
|
+
import fs20 from "fs";
|
|
7713
|
+
import path11 from "path";
|
|
7475
7714
|
import { createHash } from "crypto";
|
|
7476
7715
|
async function runExtraction(apiConfig, model) {
|
|
7477
7716
|
const inputHash = computeInputHash();
|
|
7478
7717
|
const cached3 = readCache();
|
|
7479
7718
|
if (cached3 && cached3.inputHash === inputHash) {
|
|
7480
|
-
|
|
7719
|
+
log11.debug("Brand inputs unchanged \u2014 skipping extraction", { inputHash });
|
|
7481
7720
|
return null;
|
|
7482
7721
|
}
|
|
7483
|
-
|
|
7722
|
+
log11.info("Extracting brand", { inputHash });
|
|
7484
7723
|
const brand = await extractBrand(apiConfig, model);
|
|
7485
7724
|
if (!brand) {
|
|
7486
|
-
|
|
7725
|
+
log11.warn("Brand extraction failed \u2014 leaving cache untouched");
|
|
7487
7726
|
return null;
|
|
7488
7727
|
}
|
|
7489
7728
|
persistBrand(brand, inputHash);
|
|
7490
|
-
|
|
7729
|
+
log11.info("Brand persisted", { inputHash });
|
|
7491
7730
|
return brand;
|
|
7492
7731
|
}
|
|
7493
7732
|
function isDedicatedBrandFile(filePath) {
|
|
7494
|
-
if (filePath.split(
|
|
7733
|
+
if (filePath.split(path11.sep).includes("@brand")) {
|
|
7495
7734
|
return true;
|
|
7496
7735
|
}
|
|
7497
|
-
const { type } =
|
|
7736
|
+
const { type } = parseFrontmatter3(filePath);
|
|
7498
7737
|
return type.startsWith("design/color") || type.startsWith("design/typography");
|
|
7499
7738
|
}
|
|
7500
7739
|
function isBrandRelevant(filePath) {
|
|
7501
|
-
return filePath ===
|
|
7740
|
+
return filePath === path11.join("src", "app.md") || isDedicatedBrandFile(filePath);
|
|
7502
7741
|
}
|
|
7503
7742
|
function computeInputHash() {
|
|
7504
7743
|
const entries = [];
|
|
@@ -7520,7 +7759,7 @@ function sha256(input) {
|
|
|
7520
7759
|
}
|
|
7521
7760
|
function readSafe(filePath) {
|
|
7522
7761
|
try {
|
|
7523
|
-
return
|
|
7762
|
+
return fs20.readFileSync(filePath, "utf-8");
|
|
7524
7763
|
} catch {
|
|
7525
7764
|
return "";
|
|
7526
7765
|
}
|
|
@@ -7546,9 +7785,9 @@ function readBrandManifest() {
|
|
|
7546
7785
|
function walkMdFiles2(dir) {
|
|
7547
7786
|
const results = [];
|
|
7548
7787
|
try {
|
|
7549
|
-
const entries =
|
|
7788
|
+
const entries = fs20.readdirSync(dir, { withFileTypes: true });
|
|
7550
7789
|
for (const entry of entries) {
|
|
7551
|
-
const full =
|
|
7790
|
+
const full = path11.join(dir, entry.name);
|
|
7552
7791
|
if (entry.isDirectory()) {
|
|
7553
7792
|
results.push(...walkMdFiles2(full));
|
|
7554
7793
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -7559,9 +7798,9 @@ function walkMdFiles2(dir) {
|
|
|
7559
7798
|
}
|
|
7560
7799
|
return results.sort();
|
|
7561
7800
|
}
|
|
7562
|
-
function
|
|
7801
|
+
function parseFrontmatter3(filePath) {
|
|
7563
7802
|
try {
|
|
7564
|
-
const content =
|
|
7803
|
+
const content = fs20.readFileSync(filePath, "utf-8");
|
|
7565
7804
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
7566
7805
|
if (!match) {
|
|
7567
7806
|
return { type: "" };
|
|
@@ -7576,7 +7815,7 @@ function parseFrontmatter2(filePath) {
|
|
|
7576
7815
|
async function extractBrand(apiConfig, model) {
|
|
7577
7816
|
const corpus = buildCorpus();
|
|
7578
7817
|
if (!corpus.trim()) {
|
|
7579
|
-
|
|
7818
|
+
log11.debug("No spec corpus \u2014 emitting empty brand");
|
|
7580
7819
|
return { version: 1 };
|
|
7581
7820
|
}
|
|
7582
7821
|
let responseText = "";
|
|
@@ -7607,17 +7846,17 @@ async function extractBrand(apiConfig, model) {
|
|
|
7607
7846
|
toolNames: []
|
|
7608
7847
|
});
|
|
7609
7848
|
} else if (event.type === "error") {
|
|
7610
|
-
|
|
7849
|
+
log11.error("Brand extraction stream error", { error: event.error });
|
|
7611
7850
|
return null;
|
|
7612
7851
|
}
|
|
7613
7852
|
}
|
|
7614
7853
|
} catch (err) {
|
|
7615
|
-
|
|
7854
|
+
log11.error("Brand extraction threw", { error: err?.message });
|
|
7616
7855
|
return null;
|
|
7617
7856
|
}
|
|
7618
7857
|
const parsed = parseJsonResponse(responseText);
|
|
7619
7858
|
if (!parsed) {
|
|
7620
|
-
|
|
7859
|
+
log11.warn("Brand extraction returned unparseable JSON", {
|
|
7621
7860
|
preview: responseText.slice(0, 200)
|
|
7622
7861
|
});
|
|
7623
7862
|
return null;
|
|
@@ -7769,14 +8008,14 @@ function pickFont(raw) {
|
|
|
7769
8008
|
}
|
|
7770
8009
|
function persistBrand(brand, inputHash) {
|
|
7771
8010
|
const tmp = `${BRAND_FILE}.tmp`;
|
|
7772
|
-
|
|
7773
|
-
|
|
8011
|
+
fs20.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
|
|
8012
|
+
fs20.renameSync(tmp, BRAND_FILE);
|
|
7774
8013
|
const cache = { inputHash, generatedAt: Date.now() };
|
|
7775
|
-
|
|
8014
|
+
fs20.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
|
|
7776
8015
|
}
|
|
7777
8016
|
function readCache() {
|
|
7778
8017
|
try {
|
|
7779
|
-
const raw =
|
|
8018
|
+
const raw = fs20.readFileSync(CACHE_FILE, "utf-8");
|
|
7780
8019
|
const parsed = JSON.parse(raw);
|
|
7781
8020
|
if (parsed && typeof parsed.inputHash === "string" && typeof parsed.generatedAt === "number") {
|
|
7782
8021
|
return parsed;
|
|
@@ -7786,7 +8025,7 @@ function readCache() {
|
|
|
7786
8025
|
return null;
|
|
7787
8026
|
}
|
|
7788
8027
|
}
|
|
7789
|
-
var
|
|
8028
|
+
var log11, EXTRACT_PROMPT, BRAND_FILE, CACHE_FILE, HEAD_SLICE_CHARS, BRAND_CORPUS_CHAR_LIMIT;
|
|
7790
8029
|
var init_brandExtraction = __esm({
|
|
7791
8030
|
"src/brandExtraction/index.ts"() {
|
|
7792
8031
|
"use strict";
|
|
@@ -7794,7 +8033,7 @@ var init_brandExtraction = __esm({
|
|
|
7794
8033
|
init_assets();
|
|
7795
8034
|
init_logger();
|
|
7796
8035
|
init_usageLedger();
|
|
7797
|
-
|
|
8036
|
+
log11 = createLogger("brandExtraction");
|
|
7798
8037
|
EXTRACT_PROMPT = readAsset("brandExtraction", "extract.md");
|
|
7799
8038
|
BRAND_FILE = ".remy-brand.json";
|
|
7800
8039
|
CACHE_FILE = ".remy-brand.cache.json";
|
|
@@ -7811,7 +8050,7 @@ function triggerBrandExtraction(apiConfig, model) {
|
|
|
7811
8050
|
}
|
|
7812
8051
|
inflight = true;
|
|
7813
8052
|
void runExtraction(apiConfig, model).catch((err) => {
|
|
7814
|
-
|
|
8053
|
+
log12.error("Brand extraction failed", { error: err?.message });
|
|
7815
8054
|
}).finally(() => {
|
|
7816
8055
|
inflight = false;
|
|
7817
8056
|
if (dirty) {
|
|
@@ -7820,13 +8059,13 @@ function triggerBrandExtraction(apiConfig, model) {
|
|
|
7820
8059
|
}
|
|
7821
8060
|
});
|
|
7822
8061
|
}
|
|
7823
|
-
var
|
|
8062
|
+
var log12, inflight, dirty;
|
|
7824
8063
|
var init_trigger2 = __esm({
|
|
7825
8064
|
"src/brandExtraction/trigger.ts"() {
|
|
7826
8065
|
"use strict";
|
|
7827
8066
|
init_brandExtraction();
|
|
7828
8067
|
init_logger();
|
|
7829
|
-
|
|
8068
|
+
log12 = createLogger("brandExtraction:trigger");
|
|
7830
8069
|
inflight = false;
|
|
7831
8070
|
dirty = false;
|
|
7832
8071
|
}
|
|
@@ -7869,12 +8108,11 @@ async function runTurn(params) {
|
|
|
7869
8108
|
onBackgroundComplete
|
|
7870
8109
|
} = params;
|
|
7871
8110
|
const tools2 = getToolDefinitions(onboardingState);
|
|
7872
|
-
const excludeToolsFromClearing = tools2.filter((t) => !CLEARABLE_TOOLS.has(t.name)).map((t) => t.name);
|
|
7873
8111
|
const buildModelOverride = buildModel ? filterModelPicks({ parent: buildModel }).parent : void 0;
|
|
7874
8112
|
const baseline = resolveModel("parent", state.models, model);
|
|
7875
8113
|
const parentModel = buildModelOverride ?? baseline;
|
|
7876
8114
|
const modelOverride = buildModelOverride && buildModelOverride !== baseline ? { from: baseline } : void 0;
|
|
7877
|
-
|
|
8115
|
+
log13.info("Turn started", {
|
|
7878
8116
|
requestId,
|
|
7879
8117
|
model,
|
|
7880
8118
|
buildModel: buildModelOverride,
|
|
@@ -8045,7 +8283,6 @@ async function runTurn(params) {
|
|
|
8045
8283
|
system,
|
|
8046
8284
|
messages: cleanMessagesForApi(state.messages),
|
|
8047
8285
|
tools: tools2,
|
|
8048
|
-
excludeToolsFromClearing,
|
|
8049
8286
|
signal
|
|
8050
8287
|
},
|
|
8051
8288
|
{
|
|
@@ -8137,7 +8374,7 @@ async function runTurn(params) {
|
|
|
8137
8374
|
const acc = toolInputAccumulators.get(event.id);
|
|
8138
8375
|
const wasStreamed = acc?.started ?? false;
|
|
8139
8376
|
const isInputStreaming = !!tool?.streaming?.partialInput;
|
|
8140
|
-
|
|
8377
|
+
log13.info("Tool received", {
|
|
8141
8378
|
requestId,
|
|
8142
8379
|
toolCallId: event.id,
|
|
8143
8380
|
name: event.name
|
|
@@ -8260,7 +8497,7 @@ async function runTurn(params) {
|
|
|
8260
8497
|
});
|
|
8261
8498
|
return;
|
|
8262
8499
|
}
|
|
8263
|
-
|
|
8500
|
+
log13.info("Tools executing", {
|
|
8264
8501
|
requestId,
|
|
8265
8502
|
count: toolCalls.length,
|
|
8266
8503
|
tools: toolCalls.map((tc) => tc.name)
|
|
@@ -8307,7 +8544,7 @@ async function runTurn(params) {
|
|
|
8307
8544
|
let result;
|
|
8308
8545
|
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
8309
8546
|
saveSession(state);
|
|
8310
|
-
|
|
8547
|
+
log13.info("Waiting for external tool result", {
|
|
8311
8548
|
requestId,
|
|
8312
8549
|
toolCallId: tc.id,
|
|
8313
8550
|
name: tc.name
|
|
@@ -8375,7 +8612,7 @@ async function runTurn(params) {
|
|
|
8375
8612
|
if (!isBackgroundCall(tc)) {
|
|
8376
8613
|
toolRegistry?.unregister(tc.id);
|
|
8377
8614
|
}
|
|
8378
|
-
|
|
8615
|
+
log13.info("Tool completed", {
|
|
8379
8616
|
requestId,
|
|
8380
8617
|
toolCallId: tc.id,
|
|
8381
8618
|
name: tc.name,
|
|
@@ -8434,7 +8671,7 @@ async function runTurn(params) {
|
|
|
8434
8671
|
}
|
|
8435
8672
|
}
|
|
8436
8673
|
}
|
|
8437
|
-
var
|
|
8674
|
+
var log13, BRAND_TRIGGERING_TOOLS, EXTERNAL_TOOLS, USER_BLOCKING_EXTERNAL_TOOLS;
|
|
8438
8675
|
var init_agent = __esm({
|
|
8439
8676
|
"src/agent.ts"() {
|
|
8440
8677
|
"use strict";
|
|
@@ -8447,13 +8684,12 @@ var init_agent = __esm({
|
|
|
8447
8684
|
init_statusWatcher();
|
|
8448
8685
|
init_errors();
|
|
8449
8686
|
init_cleanMessages();
|
|
8450
|
-
init_tools8();
|
|
8451
8687
|
init_sentinel();
|
|
8452
8688
|
init_trigger2();
|
|
8453
8689
|
init_surfaces();
|
|
8454
8690
|
init_toolRegistry();
|
|
8455
8691
|
init_toolResultCap();
|
|
8456
|
-
|
|
8692
|
+
log13 = createLogger("agent");
|
|
8457
8693
|
BRAND_TRIGGERING_TOOLS = /* @__PURE__ */ new Set(["writeSpec", "editSpec"]);
|
|
8458
8694
|
EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
|
|
8459
8695
|
"promptUser",
|
|
@@ -8475,8 +8711,8 @@ var init_agent = __esm({
|
|
|
8475
8711
|
});
|
|
8476
8712
|
|
|
8477
8713
|
// src/prompt/static/projectContext.ts
|
|
8478
|
-
import
|
|
8479
|
-
import
|
|
8714
|
+
import fs21 from "fs";
|
|
8715
|
+
import path12 from "path";
|
|
8480
8716
|
function loadProjectRoot() {
|
|
8481
8717
|
return `
|
|
8482
8718
|
## Project Root
|
|
@@ -8486,7 +8722,7 @@ File paths are relative to this directory. Every tool operates here and bash com
|
|
|
8486
8722
|
}
|
|
8487
8723
|
function loadProjectManifest() {
|
|
8488
8724
|
try {
|
|
8489
|
-
const manifest =
|
|
8725
|
+
const manifest = fs21.readFileSync("mindstudio.json", "utf-8");
|
|
8490
8726
|
return `
|
|
8491
8727
|
## Project Manifest (mindstudio.json)
|
|
8492
8728
|
\`\`\`json
|
|
@@ -8504,7 +8740,7 @@ function loadSpecFileMetadata() {
|
|
|
8504
8740
|
}
|
|
8505
8741
|
const entries = [];
|
|
8506
8742
|
for (const filePath of files) {
|
|
8507
|
-
const { name, description, type } =
|
|
8743
|
+
const { name, description, type } = parseFrontmatter4(filePath);
|
|
8508
8744
|
let line = `- ${filePath}`;
|
|
8509
8745
|
if (name) {
|
|
8510
8746
|
line += ` \u2014 "${name}"`;
|
|
@@ -8527,9 +8763,9 @@ ${entries.join("\n")}`;
|
|
|
8527
8763
|
function walkMdFiles3(dir) {
|
|
8528
8764
|
const results = [];
|
|
8529
8765
|
try {
|
|
8530
|
-
const entries =
|
|
8766
|
+
const entries = fs21.readdirSync(dir, { withFileTypes: true });
|
|
8531
8767
|
for (const entry of entries) {
|
|
8532
|
-
const full =
|
|
8768
|
+
const full = path12.join(dir, entry.name);
|
|
8533
8769
|
if (entry.isDirectory()) {
|
|
8534
8770
|
results.push(...walkMdFiles3(full));
|
|
8535
8771
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -8540,9 +8776,9 @@ function walkMdFiles3(dir) {
|
|
|
8540
8776
|
}
|
|
8541
8777
|
return results.sort();
|
|
8542
8778
|
}
|
|
8543
|
-
function
|
|
8779
|
+
function parseFrontmatter4(filePath) {
|
|
8544
8780
|
try {
|
|
8545
|
-
const content =
|
|
8781
|
+
const content = fs21.readFileSync(filePath, "utf-8");
|
|
8546
8782
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
8547
8783
|
if (!match) {
|
|
8548
8784
|
return { name: "", description: "", type: "" };
|
|
@@ -8558,7 +8794,7 @@ function parseFrontmatter3(filePath) {
|
|
|
8558
8794
|
}
|
|
8559
8795
|
function loadPlanStatus(onboardingState) {
|
|
8560
8796
|
try {
|
|
8561
|
-
const content =
|
|
8797
|
+
const content = fs21.readFileSync(".remy-plan.md", "utf-8");
|
|
8562
8798
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
8563
8799
|
const status = match?.[1]?.match(/^status:\s*(.+)$/m)?.[1]?.trim();
|
|
8564
8800
|
if (status === "pending" && onboardingState === "intake") {
|
|
@@ -8586,7 +8822,7 @@ The user has approved your implementation plan in .remy-plan.md. You may referen
|
|
|
8586
8822
|
}
|
|
8587
8823
|
function loadProjectFileListing() {
|
|
8588
8824
|
try {
|
|
8589
|
-
const entries =
|
|
8825
|
+
const entries = fs21.readdirSync(".", { withFileTypes: true });
|
|
8590
8826
|
const listing = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules").sort((a, b) => {
|
|
8591
8827
|
if (a.isDirectory() && !b.isDirectory()) {
|
|
8592
8828
|
return -1;
|
|
@@ -8666,18 +8902,6 @@ Current date: ${now}
|
|
|
8666
8902
|
{{compiled/design.md}}
|
|
8667
8903
|
</design>
|
|
8668
8904
|
|
|
8669
|
-
<building_agent_interfaces>
|
|
8670
|
-
{{compiled/agent-interfaces.md}}
|
|
8671
|
-
</building_agent_interfaces>
|
|
8672
|
-
|
|
8673
|
-
<building_mcp_interfaces>
|
|
8674
|
-
{{compiled/mcp-interfaces.md}}
|
|
8675
|
-
</building_mcp_interfaces>
|
|
8676
|
-
|
|
8677
|
-
<media_cdn>
|
|
8678
|
-
{{compiled/media-cdn.md}}
|
|
8679
|
-
</media_cdn>
|
|
8680
|
-
|
|
8681
8905
|
<app_files>
|
|
8682
8906
|
{{compiled/files.md}}
|
|
8683
8907
|
</app_files>
|
|
@@ -8695,10 +8919,10 @@ Current date: ${now}
|
|
|
8695
8919
|
</secrets>
|
|
8696
8920
|
</platform_docs>
|
|
8697
8921
|
|
|
8922
|
+
${loadSkillsCatalog()}
|
|
8923
|
+
|
|
8698
8924
|
<mindstudio_agent_sdk_docs>
|
|
8699
8925
|
{{compiled/sdk-actions.md}}
|
|
8700
|
-
|
|
8701
|
-
{{compiled/task-agents.md}}
|
|
8702
8926
|
</mindstudio_agent_sdk_docs>
|
|
8703
8927
|
|
|
8704
8928
|
<mindstudio_flavored_markdown_spec_docs>
|
|
@@ -8769,20 +8993,21 @@ var init_prompt4 = __esm({
|
|
|
8769
8993
|
init_assets();
|
|
8770
8994
|
init_projectContext();
|
|
8771
8995
|
init_orgContext();
|
|
8996
|
+
init_catalog();
|
|
8772
8997
|
}
|
|
8773
8998
|
});
|
|
8774
8999
|
|
|
8775
9000
|
// src/config.ts
|
|
8776
|
-
import
|
|
8777
|
-
import
|
|
9001
|
+
import fs22 from "fs";
|
|
9002
|
+
import path13 from "path";
|
|
8778
9003
|
import os from "os";
|
|
8779
9004
|
function loadConfigFile() {
|
|
8780
9005
|
try {
|
|
8781
|
-
const raw =
|
|
8782
|
-
|
|
9006
|
+
const raw = fs22.readFileSync(CONFIG_PATH, "utf-8");
|
|
9007
|
+
log14.debug("Loaded config file", { path: CONFIG_PATH });
|
|
8783
9008
|
return JSON.parse(raw);
|
|
8784
9009
|
} catch (err) {
|
|
8785
|
-
|
|
9010
|
+
log14.debug("No config file found", {
|
|
8786
9011
|
path: CONFIG_PATH,
|
|
8787
9012
|
error: err.message
|
|
8788
9013
|
});
|
|
@@ -8797,13 +9022,13 @@ function resolveConfig(flags2) {
|
|
|
8797
9022
|
const baseUrl2 = flags2?.baseUrl || process.env.MINDSTUDIO_BASE_URL || env?.apiBaseUrl || DEFAULT_BASE_URL;
|
|
8798
9023
|
const appId = process.env.MINDSTUDIO_APP_ID || void 0;
|
|
8799
9024
|
if (!apiKey) {
|
|
8800
|
-
|
|
9025
|
+
log14.error("No API key found");
|
|
8801
9026
|
throw new Error(
|
|
8802
9027
|
"No API key found. Set MINDSTUDIO_API_KEY or configure ~/.mindstudio-local-tunnel/config.json."
|
|
8803
9028
|
);
|
|
8804
9029
|
}
|
|
8805
9030
|
const keySource = flags2?.apiKey ? "cli flag" : process.env.MINDSTUDIO_API_KEY ? "env var" : "config file";
|
|
8806
|
-
|
|
9031
|
+
log14.info("Config resolved", {
|
|
8807
9032
|
baseUrl: baseUrl2,
|
|
8808
9033
|
keySource,
|
|
8809
9034
|
environment: activeEnv,
|
|
@@ -8811,13 +9036,13 @@ function resolveConfig(flags2) {
|
|
|
8811
9036
|
});
|
|
8812
9037
|
return { apiKey, baseUrl: baseUrl2, appId };
|
|
8813
9038
|
}
|
|
8814
|
-
var
|
|
9039
|
+
var log14, CONFIG_PATH, DEFAULT_BASE_URL;
|
|
8815
9040
|
var init_config = __esm({
|
|
8816
9041
|
"src/config.ts"() {
|
|
8817
9042
|
"use strict";
|
|
8818
9043
|
init_logger();
|
|
8819
|
-
|
|
8820
|
-
CONFIG_PATH =
|
|
9044
|
+
log14 = createLogger("config");
|
|
9045
|
+
CONFIG_PATH = path13.join(
|
|
8821
9046
|
os.homedir(),
|
|
8822
9047
|
".mindstudio-local-tunnel",
|
|
8823
9048
|
"config.json"
|
|
@@ -8829,11 +9054,11 @@ var init_config = __esm({
|
|
|
8829
9054
|
// src/headless/attachments.ts
|
|
8830
9055
|
import { mkdirSync, existsSync } from "fs";
|
|
8831
9056
|
import { writeFile } from "fs/promises";
|
|
8832
|
-
import { basename, join, extname } from "path";
|
|
9057
|
+
import { basename as basename2, join, extname as extname2 } from "path";
|
|
8833
9058
|
function filenameFromUrl(url) {
|
|
8834
9059
|
try {
|
|
8835
9060
|
const pathname = new URL(url).pathname;
|
|
8836
|
-
const name =
|
|
9061
|
+
const name = basename2(pathname);
|
|
8837
9062
|
return name && name !== "/" ? decodeURIComponent(name) : `upload-${Date.now()}`;
|
|
8838
9063
|
} catch {
|
|
8839
9064
|
return `upload-${Date.now()}`;
|
|
@@ -8844,7 +9069,7 @@ function resolveUniqueFilename(name, claimed) {
|
|
|
8844
9069
|
if (isFree(name)) {
|
|
8845
9070
|
return name;
|
|
8846
9071
|
}
|
|
8847
|
-
const ext =
|
|
9072
|
+
const ext = extname2(name);
|
|
8848
9073
|
const base = name.slice(0, name.length - ext.length);
|
|
8849
9074
|
let counter = 1;
|
|
8850
9075
|
while (!isFree(`${base}-${counter}${ext}`)) {
|
|
@@ -8854,7 +9079,7 @@ function resolveUniqueFilename(name, claimed) {
|
|
|
8854
9079
|
}
|
|
8855
9080
|
function isImageAttachment(att) {
|
|
8856
9081
|
const name = att.filename || filenameFromUrl(att.url);
|
|
8857
|
-
return IMAGE_EXTENSIONS.has(
|
|
9082
|
+
return IMAGE_EXTENSIONS.has(extname2(name).toLowerCase());
|
|
8858
9083
|
}
|
|
8859
9084
|
async function persistAttachments(attachments) {
|
|
8860
9085
|
const nonVoice = attachments.filter((a) => !a.isVoice);
|
|
@@ -8883,7 +9108,7 @@ async function persistAttachments(attachments) {
|
|
|
8883
9108
|
}
|
|
8884
9109
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
8885
9110
|
await writeFile(localPath, buffer);
|
|
8886
|
-
|
|
9111
|
+
log15.info("Attachment saved", {
|
|
8887
9112
|
filename: name,
|
|
8888
9113
|
path: localPath,
|
|
8889
9114
|
bytes: buffer.length
|
|
@@ -8897,7 +9122,7 @@ async function persistAttachments(attachments) {
|
|
|
8897
9122
|
if (textRes.ok) {
|
|
8898
9123
|
extractedTextPath = `${localPath}.txt`;
|
|
8899
9124
|
await writeFile(extractedTextPath, await textRes.text(), "utf-8");
|
|
8900
|
-
|
|
9125
|
+
log15.info("Extracted text saved", { path: extractedTextPath });
|
|
8901
9126
|
}
|
|
8902
9127
|
} catch {
|
|
8903
9128
|
}
|
|
@@ -8942,12 +9167,12 @@ function buildUploadHeader(results) {
|
|
|
8942
9167
|
return `[Uploaded files]
|
|
8943
9168
|
${lines.join("\n")}`;
|
|
8944
9169
|
}
|
|
8945
|
-
var
|
|
9170
|
+
var log15, UPLOADS_DIR, IMAGE_EXTENSIONS;
|
|
8946
9171
|
var init_attachments = __esm({
|
|
8947
9172
|
"src/headless/attachments.ts"() {
|
|
8948
9173
|
"use strict";
|
|
8949
9174
|
init_logger();
|
|
8950
|
-
|
|
9175
|
+
log15 = createLogger("headless:attachments");
|
|
8951
9176
|
UPLOADS_DIR = "src/.user-uploads";
|
|
8952
9177
|
IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
|
|
8953
9178
|
}
|
|
@@ -9161,7 +9386,7 @@ var headless_exports = {};
|
|
|
9161
9386
|
__export(headless_exports, {
|
|
9162
9387
|
HeadlessSession: () => HeadlessSession
|
|
9163
9388
|
});
|
|
9164
|
-
var
|
|
9389
|
+
var log16, EXTERNAL_TOOL_TIMEOUT_MS, USER_FACING_TOOLS, FORCED_COMPACTION_THRESHOLD_TOKENS, HeadlessSession;
|
|
9165
9390
|
var init_headless = __esm({
|
|
9166
9391
|
"src/headless/index.ts"() {
|
|
9167
9392
|
"use strict";
|
|
@@ -9182,7 +9407,7 @@ var init_headless = __esm({
|
|
|
9182
9407
|
init_messageQueue();
|
|
9183
9408
|
init_resolve();
|
|
9184
9409
|
init_sentinel();
|
|
9185
|
-
|
|
9410
|
+
log16 = createLogger("headless");
|
|
9186
9411
|
EXTERNAL_TOOL_TIMEOUT_MS = 3e5;
|
|
9187
9412
|
USER_FACING_TOOLS = /* @__PURE__ */ new Set([
|
|
9188
9413
|
"promptUser",
|
|
@@ -9327,7 +9552,7 @@ var init_headless = __esm({
|
|
|
9327
9552
|
}
|
|
9328
9553
|
const line = JSON.stringify(payload) + "\n";
|
|
9329
9554
|
if (event === "history") {
|
|
9330
|
-
|
|
9555
|
+
log16.info("Wrote history event to stdout", {
|
|
9331
9556
|
requestId,
|
|
9332
9557
|
bytes: line.length
|
|
9333
9558
|
});
|
|
@@ -9404,7 +9629,7 @@ var init_headless = __esm({
|
|
|
9404
9629
|
if (this.sessionStats.lastContextSize <= FORCED_COMPACTION_THRESHOLD_TOKENS) {
|
|
9405
9630
|
return;
|
|
9406
9631
|
}
|
|
9407
|
-
|
|
9632
|
+
log16.info("Forced compaction gate triggered", {
|
|
9408
9633
|
contextSize: this.sessionStats.lastContextSize,
|
|
9409
9634
|
threshold: FORCED_COMPACTION_THRESHOLD_TOKENS,
|
|
9410
9635
|
requestId
|
|
@@ -9421,7 +9646,7 @@ var init_headless = __esm({
|
|
|
9421
9646
|
}
|
|
9422
9647
|
onBackgroundComplete = (toolCallId, name, result, subAgentMessages) => {
|
|
9423
9648
|
this.pendingBlockUpdates.push({ toolCallId, result, subAgentMessages });
|
|
9424
|
-
|
|
9649
|
+
log16.info("Background complete", {
|
|
9425
9650
|
toolCallId,
|
|
9426
9651
|
name,
|
|
9427
9652
|
requestId: this.currentRequestId
|
|
@@ -9458,17 +9683,17 @@ var init_headless = __esm({
|
|
|
9458
9683
|
return Promise.resolve(early);
|
|
9459
9684
|
}
|
|
9460
9685
|
const shouldTimeout = !USER_FACING_TOOLS.has(name);
|
|
9461
|
-
return new Promise((
|
|
9686
|
+
return new Promise((resolve3) => {
|
|
9462
9687
|
const timeout = shouldTimeout ? setTimeout(() => {
|
|
9463
9688
|
this.pendingTools.delete(id);
|
|
9464
|
-
|
|
9689
|
+
resolve3(
|
|
9465
9690
|
"Error: Tool timed out \u2014 no response from the app environment after 5 minutes."
|
|
9466
9691
|
);
|
|
9467
9692
|
}, EXTERNAL_TOOL_TIMEOUT_MS) : void 0;
|
|
9468
9693
|
this.pendingTools.set(id, {
|
|
9469
9694
|
resolve: (result) => {
|
|
9470
9695
|
clearTimeout(timeout);
|
|
9471
|
-
|
|
9696
|
+
resolve3(result);
|
|
9472
9697
|
},
|
|
9473
9698
|
timeout
|
|
9474
9699
|
});
|
|
@@ -9659,7 +9884,7 @@ var init_headless = __esm({
|
|
|
9659
9884
|
await this.runForcedCompactionIfNeeded(requestId);
|
|
9660
9885
|
const attachments = parsed.attachments;
|
|
9661
9886
|
if (attachments?.length) {
|
|
9662
|
-
|
|
9887
|
+
log16.info("Message has attachments", {
|
|
9663
9888
|
count: attachments.length,
|
|
9664
9889
|
urls: attachments.map((a) => a.url)
|
|
9665
9890
|
});
|
|
@@ -9675,7 +9900,7 @@ var init_headless = __esm({
|
|
|
9675
9900
|
attachmentHeader = header;
|
|
9676
9901
|
}
|
|
9677
9902
|
} catch (err) {
|
|
9678
|
-
|
|
9903
|
+
log16.warn("Attachment persistence failed", { error: err.message });
|
|
9679
9904
|
}
|
|
9680
9905
|
}
|
|
9681
9906
|
let resolved = null;
|
|
@@ -9739,7 +9964,7 @@ var init_headless = __esm({
|
|
|
9739
9964
|
error: "Turn ended unexpectedly"
|
|
9740
9965
|
});
|
|
9741
9966
|
}
|
|
9742
|
-
|
|
9967
|
+
log16.info("Turn complete", {
|
|
9743
9968
|
requestId,
|
|
9744
9969
|
durationMs: Date.now() - this.turnStart
|
|
9745
9970
|
});
|
|
@@ -9751,7 +9976,7 @@ var init_headless = __esm({
|
|
|
9751
9976
|
error: err.message
|
|
9752
9977
|
});
|
|
9753
9978
|
}
|
|
9754
|
-
|
|
9979
|
+
log16.warn("Command failed", {
|
|
9755
9980
|
action: "message",
|
|
9756
9981
|
requestId,
|
|
9757
9982
|
error: err.message
|
|
@@ -9908,7 +10133,7 @@ var init_headless = __esm({
|
|
|
9908
10133
|
try {
|
|
9909
10134
|
parsed = JSON.parse(line);
|
|
9910
10135
|
} catch (err) {
|
|
9911
|
-
|
|
10136
|
+
log16.warn("Invalid JSON on stdin", {
|
|
9912
10137
|
error: err.message,
|
|
9913
10138
|
lineLength: line.length,
|
|
9914
10139
|
preview: line.slice(0, 200)
|
|
@@ -9917,7 +10142,7 @@ var init_headless = __esm({
|
|
|
9917
10142
|
return;
|
|
9918
10143
|
}
|
|
9919
10144
|
const { action, requestId } = parsed;
|
|
9920
|
-
|
|
10145
|
+
log16.info("Command received", { action, requestId });
|
|
9921
10146
|
if (action === "tool_result" && parsed.id) {
|
|
9922
10147
|
const id = parsed.id;
|
|
9923
10148
|
const result = parsed.result ?? "";
|
|
@@ -9926,7 +10151,7 @@ var init_headless = __esm({
|
|
|
9926
10151
|
this.pendingTools.delete(id);
|
|
9927
10152
|
pending2.resolve(result);
|
|
9928
10153
|
} else if (!this.running) {
|
|
9929
|
-
|
|
10154
|
+
log16.info("Late tool_result while idle, dismissing", { id });
|
|
9930
10155
|
this.emit("completed", { success: true }, requestId);
|
|
9931
10156
|
} else {
|
|
9932
10157
|
this.earlyResults.set(id, result);
|
|
@@ -9939,7 +10164,7 @@ var init_headless = __esm({
|
|
|
9939
10164
|
...typeof parsed.before === "number" ? { before: parsed.before } : {},
|
|
9940
10165
|
...typeof parsed.limit === "number" ? { limit: parsed.limit } : {}
|
|
9941
10166
|
});
|
|
9942
|
-
|
|
10167
|
+
log16.info("History response", {
|
|
9943
10168
|
requestId,
|
|
9944
10169
|
startIndex: page.startIndex,
|
|
9945
10170
|
endIndex: page.endIndex,
|
|
@@ -10099,8 +10324,8 @@ var init_headless = __esm({
|
|
|
10099
10324
|
// src/index.tsx
|
|
10100
10325
|
import { render } from "ink";
|
|
10101
10326
|
import os2 from "os";
|
|
10102
|
-
import
|
|
10103
|
-
import
|
|
10327
|
+
import fs23 from "fs";
|
|
10328
|
+
import path14 from "path";
|
|
10104
10329
|
|
|
10105
10330
|
// src/tui/App.tsx
|
|
10106
10331
|
import { useState as useState2, useCallback, useRef } from "react";
|
|
@@ -10155,6 +10380,8 @@ function summarizeInput(name, input) {
|
|
|
10155
10380
|
return input.pattern || "";
|
|
10156
10381
|
case "listDir":
|
|
10157
10382
|
return input.path || ".";
|
|
10383
|
+
case "loadSkill":
|
|
10384
|
+
return input.skill || "";
|
|
10158
10385
|
default:
|
|
10159
10386
|
return JSON.stringify(input).slice(0, 60);
|
|
10160
10387
|
}
|
|
@@ -10179,6 +10406,8 @@ function summarizeResult(name, result) {
|
|
|
10179
10406
|
return `${lines} files`;
|
|
10180
10407
|
case "listDir":
|
|
10181
10408
|
return `${lines} entries`;
|
|
10409
|
+
case "loadSkill":
|
|
10410
|
+
return "reference loaded";
|
|
10182
10411
|
default:
|
|
10183
10412
|
return `${lines} lines`;
|
|
10184
10413
|
}
|
|
@@ -10421,8 +10650,8 @@ for (let i = 0; i < args.length; i++) {
|
|
|
10421
10650
|
var startupLog = createLogger("startup");
|
|
10422
10651
|
function printDebugInfo(config) {
|
|
10423
10652
|
const pkg = JSON.parse(
|
|
10424
|
-
|
|
10425
|
-
|
|
10653
|
+
fs23.readFileSync(
|
|
10654
|
+
path14.join(import.meta.dirname, "..", "package.json"),
|
|
10426
10655
|
"utf-8"
|
|
10427
10656
|
)
|
|
10428
10657
|
);
|