@mindstudio-ai/remy 0.1.276 → 0.1.278

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/headless.js CHANGED
@@ -681,10 +681,9 @@ The user has approved your implementation plan in .remy-plan.md. You may referen
681
681
  }
682
682
  }
683
683
 
684
- // src/prompt/skills/catalog.ts
684
+ // src/skillCatalog.ts
685
685
  import fs5 from "fs";
686
686
  import path3 from "path";
687
- var SKILLS_DIR = assetPath("prompt", "skills");
688
687
  function parseFrontmatter(content) {
689
688
  const match = content.match(/^---\n([\s\S]*?)\n---/);
690
689
  if (!match) {
@@ -699,16 +698,16 @@ function parseFrontmatter(content) {
699
698
  }
700
699
  return fields;
701
700
  }
702
- function loadCatalog() {
701
+ function buildSkillCatalog(opts) {
703
702
  let files;
704
703
  try {
705
- files = fs5.readdirSync(SKILLS_DIR).filter((f) => f.endsWith(".md"));
704
+ files = fs5.readdirSync(opts.dir).filter((f) => f.endsWith(".md"));
706
705
  } catch {
707
- return [];
706
+ files = [];
708
707
  }
709
708
  const skills = [];
710
709
  for (const file of files.sort()) {
711
- const full = path3.join(SKILLS_DIR, file);
710
+ const full = path3.join(opts.dir, file);
712
711
  const id = file.replace(/\.md$/, "");
713
712
  const fields = parseFrontmatter(fs5.readFileSync(full, "utf-8"));
714
713
  if (!fields.name || !fields.what || !fields.when) {
@@ -722,38 +721,58 @@ function loadCatalog() {
722
721
  path: full
723
722
  });
724
723
  }
725
- return skills;
724
+ return {
725
+ skills,
726
+ ids: skills.map((s) => s.id),
727
+ get(id) {
728
+ return skills.find((s) => s.id === id);
729
+ },
730
+ readBody(skill) {
731
+ return fs5.readFileSync(skill.path, "utf-8").replace(/^---[\s\S]*?---\s*/, "").trim();
732
+ },
733
+ renderCatalogBlock() {
734
+ if (skills.length === 0) {
735
+ return "";
736
+ }
737
+ const entries = skills.map(
738
+ (s) => [
739
+ `### ${s.name} (\`${s.id}\`)`,
740
+ s.what,
741
+ "",
742
+ `When to load: ${s.when}`,
743
+ `Reference: ${s.path}`
744
+ ].join("\n")
745
+ );
746
+ return `<${opts.tag}>
747
+ ${opts.intro}
748
+
749
+ ${entries.join("\n\n")}
750
+ </${opts.tag}>`;
751
+ }
752
+ };
726
753
  }
727
- var SKILLS = loadCatalog();
728
- var SKILL_IDS = SKILLS.map((s) => s.id);
754
+
755
+ // src/prompt/skills/_catalog.ts
756
+ var INTRO = `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.
757
+
758
+ 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.
759
+
760
+ 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.`;
761
+ var catalog = buildSkillCatalog({
762
+ dir: assetPath("prompt", "skills"),
763
+ tag: "available_skills",
764
+ intro: INTRO
765
+ });
766
+ var SKILLS = catalog.skills;
767
+ var SKILL_IDS = catalog.ids;
729
768
  function getSkill(id) {
730
- return SKILLS.find((s) => s.id === id);
769
+ return catalog.get(id);
731
770
  }
732
771
  function readSkillBody(skill) {
733
- return fs5.readFileSync(skill.path, "utf-8").replace(/^---[\s\S]*?---\s*/, "").trim();
772
+ return catalog.readBody(skill);
734
773
  }
735
774
  function loadSkillsCatalog() {
736
- if (SKILLS.length === 0) {
737
- return "";
738
- }
739
- const entries = SKILLS.map(
740
- (s) => [
741
- `### ${s.name} (\`${s.id}\`)`,
742
- s.what,
743
- "",
744
- `When to load: ${s.when}`,
745
- `Reference: ${s.path}`
746
- ].join("\n")
747
- );
748
- return `<available_skills>
749
- 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.
750
-
751
- 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.
752
-
753
- 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.
754
-
755
- ${entries.join("\n\n")}
756
- </available_skills>`;
775
+ return catalog.renderCatalogBlock();
757
776
  }
758
777
 
759
778
  // src/prompt/index.ts
@@ -5452,6 +5471,59 @@ async function execute8(input, _onLog, context) {
5452
5471
  return copyEditorTool.execute(input, context);
5453
5472
  }
5454
5473
 
5474
+ // src/subagents/designExpert/tools/loadSkill.ts
5475
+ var loadSkill_exports = {};
5476
+ __export(loadSkill_exports, {
5477
+ definition: () => definition9,
5478
+ execute: () => execute9
5479
+ });
5480
+
5481
+ // src/subagents/designExpert/skills/_catalog.ts
5482
+ var INTRO2 = `Deep craft references for specific surfaces, kept out of this prompt so they can go far beyond what a resident section could \u2014 full technique recipes, worked examples, quality bars. Recognising that one of these fits the brief is part of your job as much as the design work itself: when a trigger fires, load the reference with loadSkill before designing in that area, not after. The craft in these docs is the difference between a defaulted artifact and a designed one.
5483
+
5484
+ 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.`;
5485
+ var designSkillCatalog = buildSkillCatalog({
5486
+ dir: assetPath("subagents/designExpert", "skills"),
5487
+ tag: "available_skills",
5488
+ intro: INTRO2
5489
+ });
5490
+
5491
+ // src/subagents/designExpert/tools/loadSkill.ts
5492
+ var definition9 = {
5493
+ name: "loadSkill",
5494
+ description: "Load the full craft reference for a design surface that isn't in your prompt. The available skills and the trigger for each are listed in <available_skills>. Load one before designing in its area, not after \u2014 these are hard-won technique recipes, and the defaulted version of these surfaces is exactly what they exist to prevent. Calling this is cheap and expected \u2014 if you're unsure whether you need it, load it.",
5495
+ inputSchema: {
5496
+ type: "object",
5497
+ properties: {
5498
+ skill: {
5499
+ type: "string",
5500
+ // Omitted when the catalog is empty: an empty enum is a schema no
5501
+ // provider accepts, and failing one tool call beats failing every
5502
+ // request if the docs ever go missing from a build.
5503
+ ...designSkillCatalog.ids.length > 0 ? { enum: designSkillCatalog.ids } : {},
5504
+ description: "The skill id, as listed in <available_skills>."
5505
+ }
5506
+ },
5507
+ required: ["skill"]
5508
+ }
5509
+ };
5510
+ async function execute9(input) {
5511
+ const id = String(input.skill ?? "");
5512
+ const skill = designSkillCatalog.get(id);
5513
+ if (!skill) {
5514
+ return `Error: unknown skill "${id}". Available: ${designSkillCatalog.ids.join(", ") || "(none)"}`;
5515
+ }
5516
+ try {
5517
+ const body = designSkillCatalog.readBody(skill);
5518
+ return `${body}
5519
+
5520
+ ---
5521
+ 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.`;
5522
+ } catch (err) {
5523
+ return `Error loading skill "${id}": ${err.message}`;
5524
+ }
5525
+ }
5526
+
5455
5527
  // src/subagents/designExpert/tools/index.ts
5456
5528
  var tools = {
5457
5529
  searchGoogle: searchGoogle_exports,
@@ -5465,7 +5537,8 @@ var tools = {
5465
5537
  generateImages: generateImages_exports,
5466
5538
  editImages: editImages_exports,
5467
5539
  renderImage: renderImage_exports,
5468
- polishCopy: polishCopy_exports
5540
+ polishCopy: polishCopy_exports,
5541
+ loadSkill: loadSkill_exports
5469
5542
  };
5470
5543
  var DESIGN_EXPERT_TOOLS = [
5471
5544
  ...COMMON_READ_TOOLS,
@@ -5661,6 +5734,12 @@ function getDesignExpertPrompt(onboardingState, opts) {
5661
5734
  "{{ui_case_studies}}",
5662
5735
  getUiInspirationSample(indices.uiInspiration)
5663
5736
  );
5737
+ const skillsBlock = designSkillCatalog.renderCatalogBlock();
5738
+ if (skillsBlock) {
5739
+ prompt += `
5740
+
5741
+ ${skillsBlock}`;
5742
+ }
5664
5743
  prompt += "\n\n<!-- cache_breakpoint -->";
5665
5744
  if (specContext) {
5666
5745
  prompt += `
package/dist/index.js CHANGED
@@ -3015,7 +3015,7 @@ var init_compactConversation = __esm({
3015
3015
  }
3016
3016
  });
3017
3017
 
3018
- // src/prompt/skills/catalog.ts
3018
+ // src/skillCatalog.ts
3019
3019
  import fs11 from "fs";
3020
3020
  import path5 from "path";
3021
3021
  function parseFrontmatter(content) {
@@ -3032,16 +3032,16 @@ function parseFrontmatter(content) {
3032
3032
  }
3033
3033
  return fields;
3034
3034
  }
3035
- function loadCatalog() {
3035
+ function buildSkillCatalog(opts) {
3036
3036
  let files;
3037
3037
  try {
3038
- files = fs11.readdirSync(SKILLS_DIR).filter((f) => f.endsWith(".md"));
3038
+ files = fs11.readdirSync(opts.dir).filter((f) => f.endsWith(".md"));
3039
3039
  } catch {
3040
- return [];
3040
+ files = [];
3041
3041
  }
3042
3042
  const skills = [];
3043
3043
  for (const file of files.sort()) {
3044
- const full = path5.join(SKILLS_DIR, file);
3044
+ const full = path5.join(opts.dir, file);
3045
3045
  const id = file.replace(/\.md$/, "");
3046
3046
  const fields = parseFrontmatter(fs11.readFileSync(full, "utf-8"));
3047
3047
  if (!fields.name || !fields.what || !fields.when) {
@@ -3055,45 +3055,70 @@ function loadCatalog() {
3055
3055
  path: full
3056
3056
  });
3057
3057
  }
3058
- return skills;
3058
+ return {
3059
+ skills,
3060
+ ids: skills.map((s) => s.id),
3061
+ get(id) {
3062
+ return skills.find((s) => s.id === id);
3063
+ },
3064
+ readBody(skill) {
3065
+ return fs11.readFileSync(skill.path, "utf-8").replace(/^---[\s\S]*?---\s*/, "").trim();
3066
+ },
3067
+ renderCatalogBlock() {
3068
+ if (skills.length === 0) {
3069
+ return "";
3070
+ }
3071
+ const entries = skills.map(
3072
+ (s) => [
3073
+ `### ${s.name} (\`${s.id}\`)`,
3074
+ s.what,
3075
+ "",
3076
+ `When to load: ${s.when}`,
3077
+ `Reference: ${s.path}`
3078
+ ].join("\n")
3079
+ );
3080
+ return `<${opts.tag}>
3081
+ ${opts.intro}
3082
+
3083
+ ${entries.join("\n\n")}
3084
+ </${opts.tag}>`;
3085
+ }
3086
+ };
3059
3087
  }
3088
+ var init_skillCatalog = __esm({
3089
+ "src/skillCatalog.ts"() {
3090
+ "use strict";
3091
+ }
3092
+ });
3093
+
3094
+ // src/prompt/skills/_catalog.ts
3060
3095
  function getSkill(id) {
3061
- return SKILLS.find((s) => s.id === id);
3096
+ return catalog.get(id);
3062
3097
  }
3063
3098
  function readSkillBody(skill) {
3064
- return fs11.readFileSync(skill.path, "utf-8").replace(/^---[\s\S]*?---\s*/, "").trim();
3099
+ return catalog.readBody(skill);
3065
3100
  }
3066
3101
  function loadSkillsCatalog() {
3067
- if (SKILLS.length === 0) {
3068
- return "";
3069
- }
3070
- const entries = SKILLS.map(
3071
- (s) => [
3072
- `### ${s.name} (\`${s.id}\`)`,
3073
- s.what,
3074
- "",
3075
- `When to load: ${s.when}`,
3076
- `Reference: ${s.path}`
3077
- ].join("\n")
3078
- );
3079
- return `<available_skills>
3080
- 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.
3081
-
3082
- 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.
3083
-
3084
- 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.
3085
-
3086
- ${entries.join("\n\n")}
3087
- </available_skills>`;
3102
+ return catalog.renderCatalogBlock();
3088
3103
  }
3089
- var SKILLS_DIR, SKILLS, SKILL_IDS;
3104
+ var INTRO, catalog, SKILLS, SKILL_IDS;
3090
3105
  var init_catalog = __esm({
3091
- "src/prompt/skills/catalog.ts"() {
3106
+ "src/prompt/skills/_catalog.ts"() {
3092
3107
  "use strict";
3108
+ init_skillCatalog();
3093
3109
  init_assets();
3094
- SKILLS_DIR = assetPath("prompt", "skills");
3095
- SKILLS = loadCatalog();
3096
- SKILL_IDS = SKILLS.map((s) => s.id);
3110
+ INTRO = `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.
3111
+
3112
+ 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.
3113
+
3114
+ 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.`;
3115
+ catalog = buildSkillCatalog({
3116
+ dir: assetPath("prompt", "skills"),
3117
+ tag: "available_skills",
3118
+ intro: INTRO
3119
+ });
3120
+ SKILLS = catalog.skills;
3121
+ SKILL_IDS = catalog.ids;
3097
3122
  }
3098
3123
  });
3099
3124
 
@@ -6613,6 +6638,72 @@ var init_polishCopy = __esm({
6613
6638
  }
6614
6639
  });
6615
6640
 
6641
+ // src/subagents/designExpert/skills/_catalog.ts
6642
+ var INTRO2, designSkillCatalog;
6643
+ var init_catalog2 = __esm({
6644
+ "src/subagents/designExpert/skills/_catalog.ts"() {
6645
+ "use strict";
6646
+ init_skillCatalog();
6647
+ init_assets();
6648
+ INTRO2 = `Deep craft references for specific surfaces, kept out of this prompt so they can go far beyond what a resident section could \u2014 full technique recipes, worked examples, quality bars. Recognising that one of these fits the brief is part of your job as much as the design work itself: when a trigger fires, load the reference with loadSkill before designing in that area, not after. The craft in these docs is the difference between a defaulted artifact and a designed one.
6649
+
6650
+ 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.`;
6651
+ designSkillCatalog = buildSkillCatalog({
6652
+ dir: assetPath("subagents/designExpert", "skills"),
6653
+ tag: "available_skills",
6654
+ intro: INTRO2
6655
+ });
6656
+ }
6657
+ });
6658
+
6659
+ // src/subagents/designExpert/tools/loadSkill.ts
6660
+ var loadSkill_exports = {};
6661
+ __export(loadSkill_exports, {
6662
+ definition: () => definition9,
6663
+ execute: () => execute9
6664
+ });
6665
+ async function execute9(input) {
6666
+ const id = String(input.skill ?? "");
6667
+ const skill = designSkillCatalog.get(id);
6668
+ if (!skill) {
6669
+ return `Error: unknown skill "${id}". Available: ${designSkillCatalog.ids.join(", ") || "(none)"}`;
6670
+ }
6671
+ try {
6672
+ const body = designSkillCatalog.readBody(skill);
6673
+ return `${body}
6674
+
6675
+ ---
6676
+ 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.`;
6677
+ } catch (err) {
6678
+ return `Error loading skill "${id}": ${err.message}`;
6679
+ }
6680
+ }
6681
+ var definition9;
6682
+ var init_loadSkill2 = __esm({
6683
+ "src/subagents/designExpert/tools/loadSkill.ts"() {
6684
+ "use strict";
6685
+ init_catalog2();
6686
+ definition9 = {
6687
+ name: "loadSkill",
6688
+ description: "Load the full craft reference for a design surface that isn't in your prompt. The available skills and the trigger for each are listed in <available_skills>. Load one before designing in its area, not after \u2014 these are hard-won technique recipes, and the defaulted version of these surfaces is exactly what they exist to prevent. Calling this is cheap and expected \u2014 if you're unsure whether you need it, load it.",
6689
+ inputSchema: {
6690
+ type: "object",
6691
+ properties: {
6692
+ skill: {
6693
+ type: "string",
6694
+ // Omitted when the catalog is empty: an empty enum is a schema no
6695
+ // provider accepts, and failing one tool call beats failing every
6696
+ // request if the docs ever go missing from a build.
6697
+ ...designSkillCatalog.ids.length > 0 ? { enum: designSkillCatalog.ids } : {},
6698
+ description: "The skill id, as listed in <available_skills>."
6699
+ }
6700
+ },
6701
+ required: ["skill"]
6702
+ }
6703
+ };
6704
+ }
6705
+ });
6706
+
6616
6707
  // src/subagents/designExpert/tools/index.ts
6617
6708
  async function executeDesignExpertTool(name, input, context, toolCallId, onLog) {
6618
6709
  const tool = tools[name];
@@ -6636,6 +6727,7 @@ var init_tools4 = __esm({
6636
6727
  init_editImages();
6637
6728
  init_renderImage();
6638
6729
  init_polishCopy();
6730
+ init_loadSkill2();
6639
6731
  init_screenshot2();
6640
6732
  tools = {
6641
6733
  searchGoogle: searchGoogle_exports,
@@ -6649,7 +6741,8 @@ var init_tools4 = __esm({
6649
6741
  generateImages: generateImages_exports,
6650
6742
  editImages: editImages_exports,
6651
6743
  renderImage: renderImage_exports,
6652
- polishCopy: polishCopy_exports
6744
+ polishCopy: polishCopy_exports,
6745
+ loadSkill: loadSkill_exports
6653
6746
  };
6654
6747
  DESIGN_EXPERT_TOOLS = [
6655
6748
  ...COMMON_READ_TOOLS,
@@ -6918,6 +7011,12 @@ function getDesignExpertPrompt(onboardingState, opts) {
6918
7011
  "{{ui_case_studies}}",
6919
7012
  getUiInspirationSample(indices.uiInspiration)
6920
7013
  );
7014
+ const skillsBlock = designSkillCatalog.renderCatalogBlock();
7015
+ if (skillsBlock) {
7016
+ prompt += `
7017
+
7018
+ ${skillsBlock}`;
7019
+ }
6921
7020
  prompt += "\n\n<!-- cache_breakpoint -->";
6922
7021
  if (specContext) {
6923
7022
  prompt += `
@@ -6952,6 +7051,7 @@ var init_prompt2 = __esm({
6952
7051
  "src/subagents/designExpert/prompt.ts"() {
6953
7052
  "use strict";
6954
7053
  init_assets();
7054
+ init_catalog2();
6955
7055
  init_context();
6956
7056
  init_orgContext();
6957
7057
  init_sampleCache();
@@ -202,25 +202,23 @@ export async function createPurchaseOrder(input: {
202
202
 
203
203
  A method can return immediately while kicking off slow work (like `runTask()`) that continues in the background. Don't await the slow call — use `.then()` / `.catch()` to update the record when it completes, and return an early result to the caller. The frontend polls the record's status to track progress. Wrap background chains other than `runTask()` in `mindstudio.waitUntil(...)` so the platform keeps the sandbox alive for them and records an interruption if they're cut short — `runTask()` registers itself automatically.
204
204
 
205
- The example below shows the fire-and-forget shape, not a complete `runTask()` call. Load the `taskAgents` skill before writing one — configuring its tools, validating the output, and handling failures are all there, and none of them are visible here.
205
+ The example below shows the fire-and-forget shape, not a complete `runTask()` call. Load the `taskAgents` skill before writing one — configuring its tools, the `outputSchema` output contract, and handling failures are all there, and none of them are visible here.
206
206
 
207
207
  ```typescript
208
208
  export async function enrichRestaurant(input: { id: string; name: string }) {
209
209
  await Restaurants.update(input.id, { status: 'enriching' });
210
210
 
211
211
  // Fire — don't await
212
- mindstudio.runTask<RestaurantData>({
212
+ mindstudio.runTask({
213
213
  prompt: '...',
214
214
  input: { name: input.name },
215
215
  tools: ['searchGoogle', 'fetchUrl', 'generateImage'],
216
- structuredOutputExample: { /* ... */ },
216
+ outputSchema: { type: 'object', properties: { /* ... */ }, required: [/* ... */] },
217
217
  model: 'claude-5-sonnet',
218
218
  }).then(async (result) => {
219
- if (result.parsedSuccessfully) {
220
- await Restaurants.update(input.id, { ...result.output, status: 'complete' });
221
- } else {
222
- await Restaurants.update(input.id, { status: 'failed' });
223
- }
219
+ // outputSchema means result.output is validated and typed — a task that
220
+ // can't produce conforming output rejects into the .catch instead.
221
+ await Restaurants.update(input.id, { ...result.output, status: 'complete' });
224
222
  }).catch(async () => {
225
223
  await Restaurants.update(input.id, { status: 'failed' });
226
224
  });
@@ -112,13 +112,7 @@ await chat.deleteThread(thread.id);
112
112
  await chat.claimThread(thread.id);
113
113
  ```
114
114
 
115
- **Client tools** — a tool whose effect happens in the browser (open a sheet, navigate, highlight)
116
- is declared with `target: "client"` and a `name` + inline `inputSchema` instead of a `method`
117
- (names must not collide with method ids; the schema is authored — there's no method contract to
118
- derive it from). The agent's invocation arrives as the `client_tool_call` stream event / the
119
- `onClientToolCall` callback on `sendMessage`; run the action there. Fire-and-forget on this
120
- surface: the agent is told the action was displayed and keeps going — the user's next message
121
- closes the loop.
115
+ **Client tools** — a tool whose effect happens in the browser (open a sheet, navigate, highlight) is declared with `target: "client"` and a `name` + inline `inputSchema` instead of a `method` (names must not collide with method ids; the schema is authored — there's no method contract to derive it from). The agent's invocation arrives as the `client_tool_call` stream event / the `onClientToolCall` callback on `sendMessage`; run the action there. Fire-and-forget on this surface: the agent is told the action was displayed and keeps going — the user's next message closes the loop.
122
116
 
123
117
  ```js
124
118
  await chat.sendMessage(thread.id, text, {
@@ -183,7 +177,7 @@ Images are sent as vision input; documents have their text extracted server-side
183
177
 
184
178
  ### Layout
185
179
 
186
- Ask `visualDesignExpert` for ideas about how to design the chat UI in a way that is appropriate and unique to the app.
180
+ The chat UI is a first-class design deliverable, not a bolted-on widget. Bring in `visualDesignExpert` for the chat experience as a whole — message design, streaming behavior, tool-activity presentation, the composer, and the empty state, composed as one surface (it has a dedicated craft reference for exactly this) — and implement what it prescribes. The rules below are the floor, not the direction.
187
181
 
188
182
  User messages visually distinct from assistant messages (right-aligned, different background, or both). Keep it clean — no avatars unless they add meaning. Generous vertical spacing between messages so the conversation breathes. Use clean, beautiful animation where it is additive.
189
183
 
@@ -236,8 +230,7 @@ The chat UI uses the app's design system — colors, typography, voice from `@br
236
230
 
237
231
  ## Spec: `src/interfaces/agent.md`
238
232
 
239
- The human-readable spec. Frontmatter contains structured fields; the prose body is the behavioral spec —
240
- voice, personality, capabilities, rules — written in MSFM.
233
+ The human-readable spec. Frontmatter contains structured fields; the prose body is the behavioral spec — voice, personality, capabilities, rules — written in MSFM.
241
234
 
242
235
  ```yaml
243
236
  ---
@@ -250,15 +243,10 @@ description: Conversational agent that helps users manage their to-do list.
250
243
  Frontmatter fields:
251
244
 
252
245
  - `name` — agent display name
253
- - `model` — JSON string with `model` (MindStudio model ID), `temperature`, `maxResponseTokens`, and
254
- optional `config` (model-specific settings like `reasoning`, `tools`, etc.). Ask `askMindStudioSdk`
255
- for available model IDs and their config options — MindStudio's ids don't match vendor ids, so treat
256
- any id in this document's examples as illustrative rather than current. The user's UI has a visual picker for changing it later, so only validate
257
- the model when you're setting it; if the value changes afterwards, assume it's correct.
246
+ - `model` — JSON string with `model` (MindStudio model ID), `temperature`, `maxResponseTokens`, and optional `config` (model-specific settings like `reasoning`, `tools`, etc.). Ask `askMindStudioSdk` for available model IDs and their config options — MindStudio's ids don't match vendor ids, so treat any id in this document's examples as illustrative rather than current. The user's UI has a visual picker for changing it later, so only validate the model when you're setting it; if the value changes afterwards, assume it's correct.
258
247
  - `description` — one-liner for agent card/listing
259
248
 
260
- The prose body contains sections like Voice & Personality, Capabilities, Behavior — whatever structure
261
- serves the agent's character. This is compiled into the system prompt and tool descriptions.
249
+ The prose body contains sections like Voice & Personality, Capabilities, Behavior — whatever structure serves the agent's character. This is compiled into the system prompt and tool descriptions.
262
250
 
263
251
  ## Compiled Output: `dist/interfaces/agent/`
264
252
 
@@ -291,9 +279,7 @@ dist/interfaces/agent/
291
279
  }
292
280
  ```
293
281
 
294
- **The token-limit field is renamed during compilation.** The spec frontmatter calls it
295
- `maxResponseTokens`; the compiled `agent.json` calls it `maxTokens`. Same value, two names — carry it
296
- across rather than copying the key.
282
+ **The token-limit field is renamed during compilation.** The spec frontmatter calls it `maxResponseTokens`; the compiled `agent.json` calls it `maxTokens`. Same value, two names — carry it across rather than copying the key.
297
283
 
298
284
  | Field | Description |
299
285
  |-------|-------------|
@@ -313,29 +299,17 @@ Declare it in `mindstudio.json`:
313
299
 
314
300
  ## Auth
315
301
 
316
- **Every agent config declares an `auth` block.** Agent chat spends the owner's money on every
317
- message without necessarily touching a backend method, so the platform gates the lobby itself —
318
- enforced at thread creation and message send:
302
+ **Every agent config declares an `auth` block.** Agent chat spends the owner's money on every message without necessarily touching a backend method, so the platform gates the lobby itself — enforced at thread creation and message send:
319
303
 
320
304
  ```json
321
305
  "auth": { "requireUser": true, "requireRole": ["support-agent", "admin"] }
322
306
  ```
323
307
 
324
- - `requireUser: true` — only authenticated app users may chat; `false` — anyone, including
325
- anonymous visitors. Most apps want `true`; choose `false` deliberately (a public concierge).
326
- - `requireRole` (optional) the user must hold **at least one** of the listed manifest role ids
327
- (OR semantics, same as the backend `auth.requireRole(...)`). Omit or leave empty for no role
328
- gate. Requires `requireUser: true`. Unknown role ids fail the build.
329
- - Denials surface to the frontend SDK as `MindStudioInterfaceError` with code `auth_required`
330
- (401) or `role_required` (403).
308
+ - `requireUser: true` — only authenticated app users may chat; `false` — anyone, including anonymous visitors. Most apps want `true`; choose `false` deliberately (a public concierge).
309
+ - `requireRole` (optional) — the user must hold **at least one** of the listed manifest role ids (OR semantics, same as the backend `auth.requireRole(...)`). Omit or leave empty for no role gate. Requires `requireUser: true`. Unknown role ids fail the build.
310
+ - Denials surface to the frontend SDK as `MindStudioInterfaceError` with code `auth_required` (401) or `role_required` (403).
331
311
  - Dev preview is exempt — the builder is never locked out while testing.
332
- - Older compiled apps without the block fall back to the manifest's `auth.enabled` (auth-enabled →
333
- users only; no auth → public). New configs always declare it explicitly.
334
-
335
- Once inside, agent chat runs as the **authenticated user**, not as a system role — tool calls
336
- carry that user's roles, so a method gated with `auth.requireRole` behaves exactly as it would if
337
- the user had called it from the web frontend. That's what makes exposing real methods safe; it's
338
- also why role restrictions belong in the tool descriptions, so the agent can decline gracefully
339
- instead of surfacing a rejection. Anonymous visitors (when allowed) are scoped by a per-browser
340
- visitor identity: their threads are private to their browser, and gated methods still reject.
312
+ - Older compiled apps without the block fall back to the manifest's `auth.enabled` (auth-enabled → users only; no auth → public). New configs always declare it explicitly.
313
+
314
+ Once inside, agent chat runs as the **authenticated user**, not as a system role — tool calls carry that user's roles, so a method gated with `auth.requireRole` behaves exactly as it would if the user had called it from the web frontend. That's what makes exposing real methods safe; it's also why role restrictions belong in the tool descriptions, so the agent can decline gracefully instead of surfacing a rejection. Anonymous visitors (when allowed) are scoped by a per-browser visitor identity: their threads are private to their browser, and gated methods still reject.
341
315
 
@@ -390,13 +390,7 @@ Roles are declared in the manifest, stored as an array column on the user table,
390
390
 
391
391
  ## Interface-Level Auth (Agent + Voice)
392
392
 
393
- Agent and voice interfaces additionally declare auth **in their config** (a required `auth` key:
394
- `{ "requireUser": boolean, "requireRole"?: string[] }`) because those sessions spend money without
395
- necessarily calling a backend method — the platform gates the lobby itself, before any model or
396
- media spend. `requireRole` uses the same manifest role ids with OR semantics. Denials reach the
397
- frontend SDK as `MindStudioInterfaceError` codes `auth_required` (401) and `role_required` (403) —
398
- route them to the app's login flow. See the `agentInterfaces` / `voiceInterfaces` skills for the
399
- full contract. Method-level `auth.requireRole(...)` checks still apply to every tool call inside.
393
+ Agent and voice interfaces additionally declare auth **in their config** (a required `auth` key: `{ "requireUser": boolean, "requireRole"?: string[] }`) because those sessions spend money without necessarily calling a backend method — the platform gates the lobby itself, before any model or media spend. `requireRole` uses the same manifest role ids with OR semantics. Denials reach the frontend SDK as `MindStudioInterfaceError` codes `auth_required` (401) and `role_required` (403) — route them to the app's login flow. See the `agentInterfaces` / `voiceInterfaces` skills for the full contract. Method-level `auth.requireRole(...)` checks still apply to every tool call inside.
400
394
 
401
395
  ## Apps Without Auth
402
396