@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/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((resolve2) => setTimeout(resolve2, ms));
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((resolve2) => {
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
- resolve2(result);
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 ROOT, ASSETS_BASE;
1581
+ var ASSETS_BASE;
1592
1582
  var init_assets = __esm({
1593
1583
  "src/assets.ts"() {
1594
1584
  "use strict";
1595
- ROOT = findRoot(
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 fs11 from "fs/promises";
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, relative to the project root."
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 fs11.readFile(input.path);
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 fs12 from "fs/promises";
2969
- import path5 from "path";
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 fs12.readFile(partial.path, "utf-8").catch(() => "");
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 fs12.mkdir(path5.dirname(input.path), { recursive: true });
3136
+ await fs13.mkdir(path6.dirname(input.path), { recursive: true });
3022
3137
  let oldContent = null;
3023
3138
  try {
3024
- oldContent = await fs12.readFile(input.path, "utf-8");
3139
+ oldContent = await fs13.readFile(input.path, "utf-8");
3025
3140
  } catch {
3026
3141
  }
3027
- await fs12.writeFile(input.path, input.content, "utf-8");
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 fs13 from "fs/promises";
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 fs13.readFile(input.path, "utf-8");
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 fs13.writeFile(input.path, updated, "utf-8");
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 fs13.writeFile(input.path, updated, "utf-8");
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 fs13.writeFile(input.path, updated, "utf-8");
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 path6 from "path";
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((resolve2) => {
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 ? path6.resolve(PROJECT_ROOT, input.cwd) : PROJECT_ROOT,
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
- resolve2(`Error: process exited with code ${code}`);
3331
+ resolve3(`Error: process exited with code ${code}`);
3217
3332
  } else {
3218
- resolve2("(no output)");
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
- resolve2(
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
- resolve2(output);
3365
+ resolve3(output);
3251
3366
  }
3252
3367
  });
3253
3368
  child.on("error", (err) => {
3254
3369
  clearTimeout(timer);
3255
- resolve2(`Error: ${err.message}`);
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((resolve2) => {
3494
+ return new Promise((resolve3) => {
3380
3495
  exec(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
3381
3496
  if (stdout?.trim()) {
3382
- resolve2(formatResults(stdout, max, mode));
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
- resolve2(formatResults(grepStdout, max, mode));
3502
+ resolve3(formatResults(grepStdout, max, mode));
3388
3503
  } else {
3389
- resolve2("No matches found.");
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 fs14 from "fs/promises";
3455
- import path7 from "path";
3569
+ import fs15 from "fs/promises";
3570
+ import path8 from "path";
3456
3571
  async function readAndSort(dirPath) {
3457
- const entries = await fs14.readdir(dirPath, { withFileTypes: true });
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 = path7.join(basePath, name);
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 = path7.join(current, children[0].name);
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 stat = await fs14.stat(path7.join(dirPath, name));
3499
- return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat.size)}`;
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((resolve2) => setTimeout(resolve2, 5e3));
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, imageUrl, model, timeout = 2e5, onLog } = params;
3869
- return runMindstudioCli(
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
- imageUrl,
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 { url, prompt, styleMap, onLog, model } = opts;
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
- imageUrl: url,
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 existingUrl;
4150
+ let existingImage;
3925
4151
  let onLog;
3926
4152
  let model;
3927
- let path14;
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
- existingUrl = promptOrOptions.imageUrl;
3935
- path14 = promptOrOptions.path;
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 (existingUrl) {
3953
- url = existingUrl;
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
- ...path14 ? { path: path14 } : {},
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 log6, USER_CANCELLED_RESULT, ToolRegistry;
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
- log6 = createLogger("tool-registry");
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
- log6.info("Tool stopped", { toolCallId: id, name: entry.name, mode });
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
- log6.info("Tool restarted", { toolCallId: id, name: entry.name });
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
- log7.info("Sub-agent started", { requestId, parentToolId, agentName });
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
- log7.info("Tools executing", {
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
- log7.info("Tool completed", {
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
- log7.info("Sub-agent complete", {
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
- log7.warn("Sub-agent error", {
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
- log7.info("Sub-agent backgrounded", { requestId, parentToolId, agentName });
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 log7;
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
- log7 = createLogger("sub-agent");
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 fs15 from "fs";
4790
- import path8 from "path";
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 fs15.readdirSync(dir, { withFileTypes: true })) {
4795
- const full = path8.join(dir, entry.name);
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 parseFrontmatter(filePath) {
5036
+ function parseFrontmatter2(filePath) {
4809
5037
  try {
4810
- const content = fs15.readFileSync(filePath, "utf-8");
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 = parseFrontmatter(f);
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
- fs15.readFileSync("src/roadmap/index.json", "utf-8")
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 = parseFrontmatter(f);
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
- log8.debug("Failed to parse batch analysis result", {
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 log8, CAPTURE_COMMANDS, browserAutomationTool;
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
- log8 = createLogger("browser-automation");
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
- imageUrl: input.imageUrl,
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
- url: result.screenshot.url,
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: "URL of an existing screenshot to analyze instead of capturing a new one. Use this for additional questions about a previous screenshot."
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 isImageUrl = /\.(png|jpe?g|webp|gif|svg|avif)(\?|$)/i.test(url);
5355
- let imageUrl = url;
5356
- if (!isImageUrl) {
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
- imageUrl = ss.value;
5609
+ image = ss.value;
5380
5610
  }
5381
- const analysis = await analyzeImage({
5611
+ const analyzed = await analyzeImage({
5382
5612
  prompt: analysisPrompt,
5383
- imageUrl,
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: imageUrl, analysis });
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 URL. 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.",
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: "URL to analyze. Can be an image URL or a website URL (will be screenshotted)."
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: imageUrl, analysis });
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 by URL 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.",
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 to analyze."
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
- imageUrl: url,
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 URL of a single reference image to guide the generation \u2014 for style, subject, character consistency, or composition. Your prompt still describes the desired result; the reference conditions it. Applies to every prompt in the batch."
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 URLs. These are used as the basis for the edit \u2014 the AI will use them as reference for style, subject, or composition."
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
- log9.debug("org context loaded", {
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
- log9.debug("org context init failed", { error: err.message });
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 log9, cached;
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
- log9 = createLogger("orgContext");
6291
+ log10 = createLogger("orgContext");
6054
6292
  cached = null;
6055
6293
  }
6056
6294
  });
6057
6295
 
6058
6296
  // src/subagents/designExpert/data/sampleCache.ts
6059
- import fs16 from "fs";
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(fs16.readFileSync(SAMPLE_FILE, "utf-8"));
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
- fs16.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
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 fs17 from "fs";
6525
- import path9 from "path";
6526
- function resolve(filePath) {
6527
- return path9.join(ROADMAP_DIR, filePath);
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 = resolve(input.path);
6770
+ const filePath = resolve2(input.path);
6533
6771
  try {
6534
- fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
6772
+ fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
6535
6773
  let oldContent = null;
6536
6774
  try {
6537
- oldContent = fs17.readFileSync(filePath, "utf-8");
6775
+ oldContent = fs18.readFileSync(filePath, "utf-8");
6538
6776
  } catch {
6539
6777
  }
6540
- fs17.writeFileSync(filePath, input.content, "utf-8");
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 = resolve(input.path);
6788
+ const filePath = resolve2(input.path);
6551
6789
  try {
6552
- if (!fs17.existsSync(filePath)) {
6790
+ if (!fs18.existsSync(filePath)) {
6553
6791
  return `Error: ${filePath} does not exist`;
6554
6792
  }
6555
- const oldContent = fs17.readFileSync(filePath, "utf-8");
6556
- fs17.unlinkSync(filePath);
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 = resolve("pitch.html");
6805
+ const filePath = resolve2("pitch.html");
6568
6806
  try {
6569
- fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
6570
- const exists = fs17.existsSync(filePath);
6571
- const before = exists ? fs17.statSync(filePath).mtimeMs : null;
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 (!fs17.existsSync(filePath)) {
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 && fs17.statSync(filePath).mtimeMs === before) {
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 fs18 from "fs";
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 = fs18.existsSync(OVERVIEW_FILE);
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 (!fs18.existsSync(OVERVIEW_FILE)) {
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, CLEARABLE_TOOLS, SUBAGENT_TOOL_NAMES;
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 fs19 from "fs";
7474
- import path10 from "path";
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
- log10.debug("Brand inputs unchanged \u2014 skipping extraction", { inputHash });
7719
+ log11.debug("Brand inputs unchanged \u2014 skipping extraction", { inputHash });
7481
7720
  return null;
7482
7721
  }
7483
- log10.info("Extracting brand", { inputHash });
7722
+ log11.info("Extracting brand", { inputHash });
7484
7723
  const brand = await extractBrand(apiConfig, model);
7485
7724
  if (!brand) {
7486
- log10.warn("Brand extraction failed \u2014 leaving cache untouched");
7725
+ log11.warn("Brand extraction failed \u2014 leaving cache untouched");
7487
7726
  return null;
7488
7727
  }
7489
7728
  persistBrand(brand, inputHash);
7490
- log10.info("Brand persisted", { inputHash });
7729
+ log11.info("Brand persisted", { inputHash });
7491
7730
  return brand;
7492
7731
  }
7493
7732
  function isDedicatedBrandFile(filePath) {
7494
- if (filePath.split(path10.sep).includes("@brand")) {
7733
+ if (filePath.split(path11.sep).includes("@brand")) {
7495
7734
  return true;
7496
7735
  }
7497
- const { type } = parseFrontmatter2(filePath);
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 === path10.join("src", "app.md") || isDedicatedBrandFile(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 fs19.readFileSync(filePath, "utf-8");
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 = fs19.readdirSync(dir, { withFileTypes: true });
7788
+ const entries = fs20.readdirSync(dir, { withFileTypes: true });
7550
7789
  for (const entry of entries) {
7551
- const full = path10.join(dir, entry.name);
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 parseFrontmatter2(filePath) {
7801
+ function parseFrontmatter3(filePath) {
7563
7802
  try {
7564
- const content = fs19.readFileSync(filePath, "utf-8");
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
- log10.debug("No spec corpus \u2014 emitting empty brand");
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
- log10.error("Brand extraction stream error", { error: event.error });
7849
+ log11.error("Brand extraction stream error", { error: event.error });
7611
7850
  return null;
7612
7851
  }
7613
7852
  }
7614
7853
  } catch (err) {
7615
- log10.error("Brand extraction threw", { error: err?.message });
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
- log10.warn("Brand extraction returned unparseable JSON", {
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
- fs19.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
7773
- fs19.renameSync(tmp, BRAND_FILE);
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
- fs19.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
8014
+ fs20.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
7776
8015
  }
7777
8016
  function readCache() {
7778
8017
  try {
7779
- const raw = fs19.readFileSync(CACHE_FILE, "utf-8");
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 log10, EXTRACT_PROMPT, BRAND_FILE, CACHE_FILE, HEAD_SLICE_CHARS, BRAND_CORPUS_CHAR_LIMIT;
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
- log10 = createLogger("brandExtraction");
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
- log11.error("Brand extraction failed", { error: err?.message });
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 log11, inflight, dirty;
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
- log11 = createLogger("brandExtraction:trigger");
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
- log12.info("Turn started", {
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
- log12.info("Tool received", {
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
- log12.info("Tools executing", {
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
- log12.info("Waiting for external tool result", {
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
- log12.info("Tool completed", {
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 log12, BRAND_TRIGGERING_TOOLS, EXTERNAL_TOOLS, USER_BLOCKING_EXTERNAL_TOOLS;
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
- log12 = createLogger("agent");
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 fs20 from "fs";
8479
- import path11 from "path";
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 = fs20.readFileSync("mindstudio.json", "utf-8");
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 } = parseFrontmatter3(filePath);
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 = fs20.readdirSync(dir, { withFileTypes: true });
8766
+ const entries = fs21.readdirSync(dir, { withFileTypes: true });
8531
8767
  for (const entry of entries) {
8532
- const full = path11.join(dir, entry.name);
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 parseFrontmatter3(filePath) {
8779
+ function parseFrontmatter4(filePath) {
8544
8780
  try {
8545
- const content = fs20.readFileSync(filePath, "utf-8");
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 = fs20.readFileSync(".remy-plan.md", "utf-8");
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 = fs20.readdirSync(".", { withFileTypes: true });
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 fs21 from "fs";
8777
- import path12 from "path";
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 = fs21.readFileSync(CONFIG_PATH, "utf-8");
8782
- log13.debug("Loaded config file", { path: CONFIG_PATH });
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
- log13.debug("No config file found", {
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
- log13.error("No API key found");
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
- log13.info("Config resolved", {
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 log13, CONFIG_PATH, DEFAULT_BASE_URL;
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
- log13 = createLogger("config");
8820
- CONFIG_PATH = path12.join(
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 = basename(pathname);
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 = extname(name);
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(extname(name).toLowerCase());
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
- log14.info("Attachment saved", {
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
- log14.info("Extracted text saved", { path: extractedTextPath });
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 log14, UPLOADS_DIR, IMAGE_EXTENSIONS;
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
- log14 = createLogger("headless:attachments");
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 log15, EXTERNAL_TOOL_TIMEOUT_MS, USER_FACING_TOOLS, FORCED_COMPACTION_THRESHOLD_TOKENS, HeadlessSession;
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
- log15 = createLogger("headless");
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
- log15.info("Wrote history event to stdout", {
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
- log15.info("Forced compaction gate triggered", {
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
- log15.info("Background complete", {
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((resolve2) => {
9686
+ return new Promise((resolve3) => {
9462
9687
  const timeout = shouldTimeout ? setTimeout(() => {
9463
9688
  this.pendingTools.delete(id);
9464
- resolve2(
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
- resolve2(result);
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
- log15.info("Message has attachments", {
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
- log15.warn("Attachment persistence failed", { error: err.message });
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
- log15.info("Turn complete", {
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
- log15.warn("Command failed", {
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
- log15.warn("Invalid JSON on stdin", {
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
- log15.info("Command received", { action, requestId });
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
- log15.info("Late tool_result while idle, dismissing", { id });
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
- log15.info("History response", {
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 fs22 from "fs";
10103
- import path13 from "path";
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
- fs22.readFileSync(
10425
- path13.join(import.meta.dirname, "..", "package.json"),
10653
+ fs23.readFileSync(
10654
+ path14.join(import.meta.dirname, "..", "package.json"),
10426
10655
  "utf-8"
10427
10656
  )
10428
10657
  );