@mindstudio-ai/remy 0.1.224 → 0.1.225

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.
Files changed (3) hide show
  1. package/dist/headless.js +221 -129
  2. package/dist/index.js +235 -136
  3. package/package.json +1 -1
package/dist/headless.js CHANGED
@@ -377,19 +377,168 @@ async function fetchRemyContext(config) {
377
377
  }
378
378
  }
379
379
 
380
+ // src/models/surfaces.ts
381
+ var MODEL_SURFACES = {
382
+ parent: {
383
+ default: "claude-4-8-opus",
384
+ label: "Remy",
385
+ description: "The main Remy agent you chat with about your product. Writes code and manages delegation to other agents.",
386
+ modelType: "text",
387
+ userPickable: true
388
+ },
389
+ visualDesignExpert: {
390
+ default: "claude-4-8-opus",
391
+ label: "Design Agent",
392
+ description: "Designs your product's interfaces, including components, layouts, typography, color, and visual identity.",
393
+ modelType: "text",
394
+ userPickable: true
395
+ },
396
+ productVision: {
397
+ default: "claude-5-sonnet",
398
+ label: "Roadmap Agent",
399
+ description: "Owns your product's roadmap and pitch deck. Helps decide what to build next and how to frame the big picture.",
400
+ modelType: "text",
401
+ userPickable: true
402
+ },
403
+ browserAutomation: {
404
+ default: "claude-5-sonnet",
405
+ label: "QA Agent",
406
+ description: "Tests features and UI flows in an automated browser to verify they work end to end.",
407
+ modelType: "text",
408
+ userPickable: true
409
+ },
410
+ codeSanityCheck: {
411
+ default: "claude-5-sonnet",
412
+ label: "Architecture Agent",
413
+ description: "Reviews the architecture and structure of code changes to avoid technical debt.",
414
+ modelType: "text",
415
+ userPickable: true
416
+ },
417
+ copyEditor: {
418
+ default: "claude-5-sonnet",
419
+ label: "Copy Agent",
420
+ description: "Tightens prose and copy across your app and its launch materials so it reads sharp and human, never machine-made.",
421
+ modelType: "text",
422
+ userPickable: true
423
+ },
424
+ imageGeneration: {
425
+ default: "seedream-4.5",
426
+ label: "Image Generation",
427
+ description: "Creates images for your product \u2014 icons, illustrations, photos, and any other visual assets.",
428
+ modelType: "image_generation",
429
+ userPickable: true
430
+ },
431
+ imageAnalysis: {
432
+ default: "claude-5-sonnet",
433
+ label: "Image Analysis",
434
+ description: "Reads screenshots taken by the QA agent during automated browser tests. Other agents use their own built-in image analysis when they need to read images.",
435
+ modelType: "vision",
436
+ userPickable: true
437
+ },
438
+ conversationSummarizer: {
439
+ default: "claude-5-sonnet",
440
+ label: "Compaction Utility",
441
+ description: "Compresses long conversations into summaries to keep things responsive.",
442
+ modelType: "text",
443
+ userPickable: true
444
+ },
445
+ brandExtractor: {
446
+ default: "claude-5-sonnet",
447
+ label: "Brand Utility",
448
+ description: "Extracts your product's name, colors, and fonts from your spec for use in branded documents.",
449
+ modelType: "text",
450
+ userPickable: true
451
+ },
452
+ // Internal surface — not user-pickable. Remy uses this to rewrite design
453
+ // briefs into model-optimized image prompts before image generation.
454
+ imagePromptEnhancer: {
455
+ default: "claude-5-sonnet",
456
+ label: "Image Prompt Enhancer",
457
+ description: "Rewrites image briefs into model-optimized prompts before image generation.",
458
+ modelType: "text",
459
+ userPickable: false
460
+ }
461
+ };
462
+ var ALLOWED_MODELS_BY_TYPE = {
463
+ text: [
464
+ "claude-4-8-opus",
465
+ "claude-4-7-opus",
466
+ "claude-4-6-opus",
467
+ "claude-4-6-sonnet",
468
+ "claude-fable-5",
469
+ "claude-5-sonnet",
470
+ "gpt-5.5",
471
+ "gemini-3-pro",
472
+ "gemini-3.1-pro",
473
+ "gemini-3-flash",
474
+ "gemini-3.5-flash",
475
+ "grok-build-0.1",
476
+ "grok-4.5",
477
+ "glm-5.2",
478
+ "muse-spark-1.1",
479
+ "kimi-k2-7-code",
480
+ "kimi-k3"
481
+ ]
482
+ // vision: undefined — unconstrained
483
+ // image_generation: undefined — unconstrained
484
+ };
485
+ var orgDefaultModels = {};
486
+ function setOrgDefaultModels(models) {
487
+ orgDefaultModels = models;
488
+ }
489
+ function filterModelPicks(picks) {
490
+ const out = {};
491
+ if (!picks || typeof picks !== "object") {
492
+ return out;
493
+ }
494
+ for (const [key, value] of Object.entries(picks)) {
495
+ if (!(key in MODEL_SURFACES)) {
496
+ continue;
497
+ }
498
+ const surface = MODEL_SURFACES[key];
499
+ if (!surface.userPickable) {
500
+ continue;
501
+ }
502
+ if (typeof value !== "string" || value.length === 0) {
503
+ continue;
504
+ }
505
+ const allow = ALLOWED_MODELS_BY_TYPE[surface.modelType];
506
+ if (allow && !allow.includes(value)) {
507
+ continue;
508
+ }
509
+ out[key] = value;
510
+ }
511
+ return out;
512
+ }
513
+ function getEffectiveModelSurfaces() {
514
+ const out = {};
515
+ for (const [id, surface] of Object.entries(MODEL_SURFACES)) {
516
+ const orgDefault = orgDefaultModels[id];
517
+ out[id] = orgDefault ? { ...surface, default: orgDefault } : { ...surface };
518
+ }
519
+ return out;
520
+ }
521
+ function resolveModel(surfaceId, models, fallback) {
522
+ return models?.[surfaceId] ?? fallback ?? orgDefaultModels[surfaceId] ?? MODEL_SURFACES[surfaceId].default;
523
+ }
524
+
380
525
  // src/orgContext.ts
381
526
  var log3 = createLogger("orgContext");
382
527
  var cached = null;
383
528
  async function initOrgContext(config) {
384
529
  try {
385
530
  cached = await fetchRemyContext(config);
531
+ const orgDefaultModels2 = filterModelPicks(cached?.defaultModels);
532
+ setOrgDefaultModels(orgDefaultModels2);
386
533
  log3.debug("org context loaded", {
387
534
  delegatedAvailable: cached?.auth?.delegatedAvailable ?? false,
388
535
  requireDelegatedOnly: cached?.auth?.requireDelegatedOnly ?? false,
389
- hasOrgName: !!cached?.org?.name
536
+ hasOrgName: !!cached?.org?.name,
537
+ orgDefaultModels: orgDefaultModels2
390
538
  });
391
539
  } catch (err) {
392
540
  cached = null;
541
+ setOrgDefaultModels({});
393
542
  log3.debug("org context init failed", { error: err.message });
394
543
  }
395
544
  }
@@ -3536,115 +3685,6 @@ ${appSpec}
3536
3685
  }
3537
3686
  }
3538
3687
 
3539
- // src/models/surfaces.ts
3540
- var MODEL_SURFACES = {
3541
- parent: {
3542
- default: "claude-4-8-opus",
3543
- label: "Remy",
3544
- description: "The main Remy agent you chat with about your product. Writes code and manages delegation to other agents.",
3545
- modelType: "text",
3546
- userPickable: true
3547
- },
3548
- visualDesignExpert: {
3549
- default: "claude-4-8-opus",
3550
- label: "Design Agent",
3551
- description: "Designs your product's interfaces, including components, layouts, typography, color, and visual identity.",
3552
- modelType: "text",
3553
- userPickable: true
3554
- },
3555
- productVision: {
3556
- default: "claude-5-sonnet",
3557
- label: "Roadmap Agent",
3558
- description: "Owns your product's roadmap and pitch deck. Helps decide what to build next and how to frame the big picture.",
3559
- modelType: "text",
3560
- userPickable: true
3561
- },
3562
- browserAutomation: {
3563
- default: "claude-5-sonnet",
3564
- label: "QA Agent",
3565
- description: "Tests features and UI flows in an automated browser to verify they work end to end.",
3566
- modelType: "text",
3567
- userPickable: true
3568
- },
3569
- codeSanityCheck: {
3570
- default: "claude-5-sonnet",
3571
- label: "Architecture Agent",
3572
- description: "Reviews the architecture and structure of code changes to avoid technical debt.",
3573
- modelType: "text",
3574
- userPickable: true
3575
- },
3576
- copyEditor: {
3577
- default: "claude-5-sonnet",
3578
- label: "Copy Agent",
3579
- description: "Tightens prose and copy across your app and its launch materials so it reads sharp and human, never machine-made.",
3580
- modelType: "text",
3581
- userPickable: true
3582
- },
3583
- imageGeneration: {
3584
- default: "seedream-4.5",
3585
- label: "Image Generation",
3586
- description: "Creates images for your product \u2014 icons, illustrations, photos, and any other visual assets.",
3587
- modelType: "image_generation",
3588
- userPickable: true
3589
- },
3590
- imageAnalysis: {
3591
- default: "claude-5-sonnet",
3592
- label: "Image Analysis",
3593
- description: "Reads screenshots taken by the QA agent during automated browser tests. Other agents use their own built-in image analysis when they need to read images.",
3594
- modelType: "vision",
3595
- userPickable: true
3596
- },
3597
- conversationSummarizer: {
3598
- default: "claude-5-sonnet",
3599
- label: "Compaction Utility",
3600
- description: "Compresses long conversations into summaries to keep things responsive.",
3601
- modelType: "text",
3602
- userPickable: true
3603
- },
3604
- brandExtractor: {
3605
- default: "claude-5-sonnet",
3606
- label: "Brand Utility",
3607
- description: "Extracts your product's name, colors, and fonts from your spec for use in branded documents.",
3608
- modelType: "text",
3609
- userPickable: true
3610
- },
3611
- // Internal surface — not user-pickable. Remy uses this to rewrite design
3612
- // briefs into model-optimized image prompts before image generation.
3613
- imagePromptEnhancer: {
3614
- default: "claude-5-sonnet",
3615
- label: "Image Prompt Enhancer",
3616
- description: "Rewrites image briefs into model-optimized prompts before image generation.",
3617
- modelType: "text",
3618
- userPickable: false
3619
- }
3620
- };
3621
- var ALLOWED_MODELS_BY_TYPE = {
3622
- text: [
3623
- "claude-4-8-opus",
3624
- "claude-4-7-opus",
3625
- "claude-4-6-opus",
3626
- "claude-4-6-sonnet",
3627
- "claude-fable-5",
3628
- "claude-5-sonnet",
3629
- "gpt-5.5",
3630
- "gemini-3-pro",
3631
- "gemini-3.1-pro",
3632
- "gemini-3-flash",
3633
- "gemini-3.5-flash",
3634
- "grok-build-0.1",
3635
- "grok-4.5",
3636
- "glm-5.2",
3637
- "muse-spark-1.1",
3638
- "kimi-k2-7-code",
3639
- "kimi-k3"
3640
- ]
3641
- // vision: undefined — unconstrained
3642
- // image_generation: undefined — unconstrained
3643
- };
3644
- function resolveModel(surfaceId, models, fallback) {
3645
- return models?.[surfaceId] ?? fallback ?? MODEL_SURFACES[surfaceId].default;
3646
- }
3647
-
3648
3688
  // src/subagents/browserAutomation/index.ts
3649
3689
  var log7 = createLogger("browser-automation");
3650
3690
  async function runBrowserAutomation(task, context, opts) {
@@ -6011,15 +6051,17 @@ async function runExtraction(apiConfig, model) {
6011
6051
  log10.info("Brand persisted", { inputHash });
6012
6052
  return brand;
6013
6053
  }
6054
+ function isBrandRelevant(filePath) {
6055
+ if (filePath === path10.join("src", "app.md")) {
6056
+ return true;
6057
+ }
6058
+ const { type } = parseFrontmatter3(filePath);
6059
+ return type.startsWith("design/color") || type.startsWith("design/typography");
6060
+ }
6014
6061
  function computeInputHash() {
6015
6062
  const entries = [];
6016
6063
  for (const filePath of walkMdFiles3("src")) {
6017
- if (filePath === path10.join("src", "app.md")) {
6018
- entries.push({ path: filePath, content: readSafe(filePath) });
6019
- continue;
6020
- }
6021
- const fm = parseFrontmatter3(filePath);
6022
- if (fm.type.startsWith("design/color") || fm.type.startsWith("design/typography")) {
6064
+ if (isBrandRelevant(filePath)) {
6023
6065
  entries.push({ path: filePath, content: readSafe(filePath) });
6024
6066
  }
6025
6067
  }
@@ -6122,23 +6164,42 @@ async function extractBrand(apiConfig, model) {
6122
6164
  }
6123
6165
  return validateBrand(parsed);
6124
6166
  }
6167
+ var BRAND_CORPUS_CHAR_LIMIT = 24e5;
6125
6168
  function buildCorpus() {
6126
- const sections = [];
6169
+ const all = walkMdFiles3("src");
6170
+ const ordered = [
6171
+ ...all.filter(isBrandRelevant),
6172
+ ...all.filter((f) => !isBrandRelevant(f))
6173
+ ];
6174
+ const files = [];
6127
6175
  const manifest = readSafe("mindstudio.json");
6128
6176
  if (manifest) {
6129
- sections.push(`## File: mindstudio.json
6130
-
6131
- ${manifest}`);
6177
+ files.push({ path: "mindstudio.json", content: manifest });
6132
6178
  }
6133
- for (const filePath of walkMdFiles3("src")) {
6179
+ for (const filePath of ordered) {
6134
6180
  const content = readSafe(filePath);
6135
6181
  if (content) {
6136
- sections.push(`## File: ${filePath}
6182
+ files.push({ path: filePath, content });
6183
+ }
6184
+ }
6185
+ const sep = "\n\n---\n\n";
6186
+ const sections = [];
6187
+ let usedChars = 0;
6188
+ for (const { path: p, content } of files) {
6189
+ const section = `## File: ${p}
6137
6190
 
6138
- ${content}`);
6191
+ ${content}`;
6192
+ const added = section.length + (sections.length > 0 ? sep.length : 0);
6193
+ if (sections.length > 0 && usedChars + added > BRAND_CORPUS_CHAR_LIMIT) {
6194
+ sections.push(
6195
+ `(brand corpus truncated: included ${sections.length} of ${files.length} files, ~${(usedChars / 1024).toFixed(0)}KB; brand-relevant files were prioritized.)`
6196
+ );
6197
+ break;
6139
6198
  }
6199
+ sections.push(section);
6200
+ usedChars += added;
6140
6201
  }
6141
- return sections.join("\n\n---\n\n");
6202
+ return sections.join(sep);
6142
6203
  }
6143
6204
  function parseJsonResponse(text) {
6144
6205
  const trimmed = text.trim();
@@ -6280,6 +6341,21 @@ function triggerBrandExtraction(apiConfig, model) {
6280
6341
  // src/session.ts
6281
6342
  import fs22 from "fs";
6282
6343
  import path11 from "path";
6344
+
6345
+ // src/toolResultCap.ts
6346
+ var MAX_TOOL_RESULT_BYTES = 256 * 1024;
6347
+ function capToolResult(result) {
6348
+ const total = Buffer.byteLength(result, "utf-8");
6349
+ if (total <= MAX_TOOL_RESULT_BYTES) {
6350
+ return result;
6351
+ }
6352
+ const head = Buffer.from(result, "utf-8").subarray(0, MAX_TOOL_RESULT_BYTES).toString("utf-8");
6353
+ return head + `
6354
+
6355
+ (tool result truncated at ${(MAX_TOOL_RESULT_BYTES / 1024).toFixed(0)}KB of ${(total / 1024).toFixed(0)}KB \u2014 too large to keep in context. Narrow the call (select fewer fields, paginate, or query a subset) instead of fetching everything.)`;
6356
+ }
6357
+
6358
+ // src/session.ts
6283
6359
  var log12 = createLogger("session");
6284
6360
  var SESSION_FILE = ".remy-session.json";
6285
6361
  var ARCHIVE_DIR = ".logs/sessions";
@@ -6304,11 +6380,23 @@ function loadSession(state) {
6304
6380
  }
6305
6381
  return false;
6306
6382
  }
6383
+ function capOversizedResults(msg) {
6384
+ if (msg.role === "assistant" && Array.isArray(msg.content)) {
6385
+ for (const block of msg.content) {
6386
+ if (block.type === "tool" && typeof block.result === "string") {
6387
+ block.result = capToolResult(block.result);
6388
+ }
6389
+ }
6390
+ } else if (msg.role === "user" && msg.toolCallId && typeof msg.content === "string") {
6391
+ msg.content = capToolResult(msg.content);
6392
+ }
6393
+ }
6307
6394
  function sanitizeMessages(messages) {
6308
6395
  const result = [];
6309
6396
  for (let i = 0; i < messages.length; i++) {
6310
- result.push(messages[i]);
6311
6397
  const msg = messages[i];
6398
+ capOversizedResults(msg);
6399
+ result.push(msg);
6312
6400
  if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
6313
6401
  continue;
6314
6402
  }
@@ -6597,6 +6685,10 @@ var patterns = [
6597
6685
  "Too many requests. Please wait a moment and try again."
6598
6686
  ],
6599
6687
  [/HTTP 40[13]/i, "Authentication failed. Please check your API key."],
6688
+ [
6689
+ /HTTP 413/i,
6690
+ "This conversation has grown too large to send in a single request. Starting a new conversation will reset the context."
6691
+ ],
6600
6692
  [
6601
6693
  /HTTP 5\d\d/i,
6602
6694
  "The AI service is temporarily unavailable. Please try again."
@@ -7123,7 +7215,7 @@ async function runTurn(params) {
7123
7215
  })
7124
7216
  });
7125
7217
  }
7126
- safeSettle(result, result.startsWith("Error"));
7218
+ safeSettle(capToolResult(result), result.startsWith("Error"));
7127
7219
  } catch (err) {
7128
7220
  safeSettle(`Error: ${err.message}`, true);
7129
7221
  }
@@ -7578,7 +7670,7 @@ var HeadlessSession = class {
7578
7670
  this.emit("session_restored", {
7579
7671
  messageCount: this.state.messages.length,
7580
7672
  ...this.state.models && { models: this.state.models },
7581
- modelSurfaces: MODEL_SURFACES,
7673
+ modelSurfaces: getEffectiveModelSurfaces(),
7582
7674
  allowedModelsByType: ALLOWED_MODELS_BY_TYPE
7583
7675
  });
7584
7676
  }
@@ -8176,7 +8268,7 @@ var HeadlessSession = class {
8176
8268
  saveSession(this.state);
8177
8269
  return {
8178
8270
  ...this.state.models && { models: this.state.models },
8179
- modelSurfaces: MODEL_SURFACES,
8271
+ modelSurfaces: getEffectiveModelSurfaces(),
8180
8272
  allowedModelsByType: ALLOWED_MODELS_BY_TYPE
8181
8273
  };
8182
8274
  }
@@ -8272,7 +8364,7 @@ var HeadlessSession = class {
8272
8364
  running: this.running,
8273
8365
  ...this.running && this.currentRequestId ? { currentRequestId: this.currentRequestId } : {},
8274
8366
  ...this.state.models && { models: this.state.models },
8275
- modelSurfaces: MODEL_SURFACES,
8367
+ modelSurfaces: getEffectiveModelSurfaces(),
8276
8368
  allowedModelsByType: ALLOWED_MODELS_BY_TYPE,
8277
8369
  // Current queue snapshot for connect/reconnect — get_history is the
8278
8370
  // on-demand "current state" query. Always an array (possibly empty),
package/dist/index.js CHANGED
@@ -1942,17 +1942,172 @@ var init_projectContext = __esm({
1942
1942
  }
1943
1943
  });
1944
1944
 
1945
+ // src/models/surfaces.ts
1946
+ function setOrgDefaultModels(models) {
1947
+ orgDefaultModels = models;
1948
+ }
1949
+ function filterModelPicks(picks) {
1950
+ const out = {};
1951
+ if (!picks || typeof picks !== "object") {
1952
+ return out;
1953
+ }
1954
+ for (const [key, value] of Object.entries(picks)) {
1955
+ if (!(key in MODEL_SURFACES)) {
1956
+ continue;
1957
+ }
1958
+ const surface = MODEL_SURFACES[key];
1959
+ if (!surface.userPickable) {
1960
+ continue;
1961
+ }
1962
+ if (typeof value !== "string" || value.length === 0) {
1963
+ continue;
1964
+ }
1965
+ const allow = ALLOWED_MODELS_BY_TYPE[surface.modelType];
1966
+ if (allow && !allow.includes(value)) {
1967
+ continue;
1968
+ }
1969
+ out[key] = value;
1970
+ }
1971
+ return out;
1972
+ }
1973
+ function getEffectiveModelSurfaces() {
1974
+ const out = {};
1975
+ for (const [id, surface] of Object.entries(MODEL_SURFACES)) {
1976
+ const orgDefault = orgDefaultModels[id];
1977
+ out[id] = orgDefault ? { ...surface, default: orgDefault } : { ...surface };
1978
+ }
1979
+ return out;
1980
+ }
1981
+ function resolveModel(surfaceId, models, fallback) {
1982
+ return models?.[surfaceId] ?? fallback ?? orgDefaultModels[surfaceId] ?? MODEL_SURFACES[surfaceId].default;
1983
+ }
1984
+ var MODEL_SURFACES, ALLOWED_MODELS_BY_TYPE, orgDefaultModels;
1985
+ var init_surfaces = __esm({
1986
+ "src/models/surfaces.ts"() {
1987
+ "use strict";
1988
+ MODEL_SURFACES = {
1989
+ parent: {
1990
+ default: "claude-4-8-opus",
1991
+ label: "Remy",
1992
+ description: "The main Remy agent you chat with about your product. Writes code and manages delegation to other agents.",
1993
+ modelType: "text",
1994
+ userPickable: true
1995
+ },
1996
+ visualDesignExpert: {
1997
+ default: "claude-4-8-opus",
1998
+ label: "Design Agent",
1999
+ description: "Designs your product's interfaces, including components, layouts, typography, color, and visual identity.",
2000
+ modelType: "text",
2001
+ userPickable: true
2002
+ },
2003
+ productVision: {
2004
+ default: "claude-5-sonnet",
2005
+ label: "Roadmap Agent",
2006
+ description: "Owns your product's roadmap and pitch deck. Helps decide what to build next and how to frame the big picture.",
2007
+ modelType: "text",
2008
+ userPickable: true
2009
+ },
2010
+ browserAutomation: {
2011
+ default: "claude-5-sonnet",
2012
+ label: "QA Agent",
2013
+ description: "Tests features and UI flows in an automated browser to verify they work end to end.",
2014
+ modelType: "text",
2015
+ userPickable: true
2016
+ },
2017
+ codeSanityCheck: {
2018
+ default: "claude-5-sonnet",
2019
+ label: "Architecture Agent",
2020
+ description: "Reviews the architecture and structure of code changes to avoid technical debt.",
2021
+ modelType: "text",
2022
+ userPickable: true
2023
+ },
2024
+ copyEditor: {
2025
+ default: "claude-5-sonnet",
2026
+ label: "Copy Agent",
2027
+ description: "Tightens prose and copy across your app and its launch materials so it reads sharp and human, never machine-made.",
2028
+ modelType: "text",
2029
+ userPickable: true
2030
+ },
2031
+ imageGeneration: {
2032
+ default: "seedream-4.5",
2033
+ label: "Image Generation",
2034
+ description: "Creates images for your product \u2014 icons, illustrations, photos, and any other visual assets.",
2035
+ modelType: "image_generation",
2036
+ userPickable: true
2037
+ },
2038
+ imageAnalysis: {
2039
+ default: "claude-5-sonnet",
2040
+ label: "Image Analysis",
2041
+ description: "Reads screenshots taken by the QA agent during automated browser tests. Other agents use their own built-in image analysis when they need to read images.",
2042
+ modelType: "vision",
2043
+ userPickable: true
2044
+ },
2045
+ conversationSummarizer: {
2046
+ default: "claude-5-sonnet",
2047
+ label: "Compaction Utility",
2048
+ description: "Compresses long conversations into summaries to keep things responsive.",
2049
+ modelType: "text",
2050
+ userPickable: true
2051
+ },
2052
+ brandExtractor: {
2053
+ default: "claude-5-sonnet",
2054
+ label: "Brand Utility",
2055
+ description: "Extracts your product's name, colors, and fonts from your spec for use in branded documents.",
2056
+ modelType: "text",
2057
+ userPickable: true
2058
+ },
2059
+ // Internal surface — not user-pickable. Remy uses this to rewrite design
2060
+ // briefs into model-optimized image prompts before image generation.
2061
+ imagePromptEnhancer: {
2062
+ default: "claude-5-sonnet",
2063
+ label: "Image Prompt Enhancer",
2064
+ description: "Rewrites image briefs into model-optimized prompts before image generation.",
2065
+ modelType: "text",
2066
+ userPickable: false
2067
+ }
2068
+ };
2069
+ ALLOWED_MODELS_BY_TYPE = {
2070
+ text: [
2071
+ "claude-4-8-opus",
2072
+ "claude-4-7-opus",
2073
+ "claude-4-6-opus",
2074
+ "claude-4-6-sonnet",
2075
+ "claude-fable-5",
2076
+ "claude-5-sonnet",
2077
+ "gpt-5.5",
2078
+ "gemini-3-pro",
2079
+ "gemini-3.1-pro",
2080
+ "gemini-3-flash",
2081
+ "gemini-3.5-flash",
2082
+ "grok-build-0.1",
2083
+ "grok-4.5",
2084
+ "glm-5.2",
2085
+ "muse-spark-1.1",
2086
+ "kimi-k2-7-code",
2087
+ "kimi-k3"
2088
+ ]
2089
+ // vision: undefined — unconstrained
2090
+ // image_generation: undefined — unconstrained
2091
+ };
2092
+ orgDefaultModels = {};
2093
+ }
2094
+ });
2095
+
1945
2096
  // src/orgContext.ts
1946
2097
  async function initOrgContext(config) {
1947
2098
  try {
1948
2099
  cached = await fetchRemyContext(config);
2100
+ const orgDefaultModels2 = filterModelPicks(cached?.defaultModels);
2101
+ setOrgDefaultModels(orgDefaultModels2);
1949
2102
  log3.debug("org context loaded", {
1950
2103
  delegatedAvailable: cached?.auth?.delegatedAvailable ?? false,
1951
2104
  requireDelegatedOnly: cached?.auth?.requireDelegatedOnly ?? false,
1952
- hasOrgName: !!cached?.org?.name
2105
+ hasOrgName: !!cached?.org?.name,
2106
+ orgDefaultModels: orgDefaultModels2
1953
2107
  });
1954
2108
  } catch (err) {
1955
2109
  cached = null;
2110
+ setOrgDefaultModels({});
1956
2111
  log3.debug("org context init failed", { error: err.message });
1957
2112
  }
1958
2113
  }
@@ -1987,6 +2142,7 @@ var init_orgContext = __esm({
1987
2142
  "src/orgContext.ts"() {
1988
2143
  "use strict";
1989
2144
  init_api();
2145
+ init_surfaces();
1990
2146
  init_logger();
1991
2147
  log3 = createLogger("orgContext");
1992
2148
  cached = null;
@@ -2148,121 +2304,6 @@ var init_prompt = __esm({
2148
2304
  }
2149
2305
  });
2150
2306
 
2151
- // src/models/surfaces.ts
2152
- function resolveModel(surfaceId, models, fallback) {
2153
- return models?.[surfaceId] ?? fallback ?? MODEL_SURFACES[surfaceId].default;
2154
- }
2155
- var MODEL_SURFACES, ALLOWED_MODELS_BY_TYPE;
2156
- var init_surfaces = __esm({
2157
- "src/models/surfaces.ts"() {
2158
- "use strict";
2159
- MODEL_SURFACES = {
2160
- parent: {
2161
- default: "claude-4-8-opus",
2162
- label: "Remy",
2163
- description: "The main Remy agent you chat with about your product. Writes code and manages delegation to other agents.",
2164
- modelType: "text",
2165
- userPickable: true
2166
- },
2167
- visualDesignExpert: {
2168
- default: "claude-4-8-opus",
2169
- label: "Design Agent",
2170
- description: "Designs your product's interfaces, including components, layouts, typography, color, and visual identity.",
2171
- modelType: "text",
2172
- userPickable: true
2173
- },
2174
- productVision: {
2175
- default: "claude-5-sonnet",
2176
- label: "Roadmap Agent",
2177
- description: "Owns your product's roadmap and pitch deck. Helps decide what to build next and how to frame the big picture.",
2178
- modelType: "text",
2179
- userPickable: true
2180
- },
2181
- browserAutomation: {
2182
- default: "claude-5-sonnet",
2183
- label: "QA Agent",
2184
- description: "Tests features and UI flows in an automated browser to verify they work end to end.",
2185
- modelType: "text",
2186
- userPickable: true
2187
- },
2188
- codeSanityCheck: {
2189
- default: "claude-5-sonnet",
2190
- label: "Architecture Agent",
2191
- description: "Reviews the architecture and structure of code changes to avoid technical debt.",
2192
- modelType: "text",
2193
- userPickable: true
2194
- },
2195
- copyEditor: {
2196
- default: "claude-5-sonnet",
2197
- label: "Copy Agent",
2198
- description: "Tightens prose and copy across your app and its launch materials so it reads sharp and human, never machine-made.",
2199
- modelType: "text",
2200
- userPickable: true
2201
- },
2202
- imageGeneration: {
2203
- default: "seedream-4.5",
2204
- label: "Image Generation",
2205
- description: "Creates images for your product \u2014 icons, illustrations, photos, and any other visual assets.",
2206
- modelType: "image_generation",
2207
- userPickable: true
2208
- },
2209
- imageAnalysis: {
2210
- default: "claude-5-sonnet",
2211
- label: "Image Analysis",
2212
- description: "Reads screenshots taken by the QA agent during automated browser tests. Other agents use their own built-in image analysis when they need to read images.",
2213
- modelType: "vision",
2214
- userPickable: true
2215
- },
2216
- conversationSummarizer: {
2217
- default: "claude-5-sonnet",
2218
- label: "Compaction Utility",
2219
- description: "Compresses long conversations into summaries to keep things responsive.",
2220
- modelType: "text",
2221
- userPickable: true
2222
- },
2223
- brandExtractor: {
2224
- default: "claude-5-sonnet",
2225
- label: "Brand Utility",
2226
- description: "Extracts your product's name, colors, and fonts from your spec for use in branded documents.",
2227
- modelType: "text",
2228
- userPickable: true
2229
- },
2230
- // Internal surface — not user-pickable. Remy uses this to rewrite design
2231
- // briefs into model-optimized image prompts before image generation.
2232
- imagePromptEnhancer: {
2233
- default: "claude-5-sonnet",
2234
- label: "Image Prompt Enhancer",
2235
- description: "Rewrites image briefs into model-optimized prompts before image generation.",
2236
- modelType: "text",
2237
- userPickable: false
2238
- }
2239
- };
2240
- ALLOWED_MODELS_BY_TYPE = {
2241
- text: [
2242
- "claude-4-8-opus",
2243
- "claude-4-7-opus",
2244
- "claude-4-6-opus",
2245
- "claude-4-6-sonnet",
2246
- "claude-fable-5",
2247
- "claude-5-sonnet",
2248
- "gpt-5.5",
2249
- "gemini-3-pro",
2250
- "gemini-3.1-pro",
2251
- "gemini-3-flash",
2252
- "gemini-3.5-flash",
2253
- "grok-build-0.1",
2254
- "grok-4.5",
2255
- "glm-5.2",
2256
- "muse-spark-1.1",
2257
- "kimi-k2-7-code",
2258
- "kimi-k3"
2259
- ]
2260
- // vision: undefined — unconstrained
2261
- // image_generation: undefined — unconstrained
2262
- };
2263
- }
2264
- });
2265
-
2266
2307
  // src/compaction/trigger.ts
2267
2308
  function getPendingSummaries() {
2268
2309
  return pendingSummaries.splice(0);
@@ -6652,6 +6693,25 @@ var init_tools7 = __esm({
6652
6693
  }
6653
6694
  });
6654
6695
 
6696
+ // src/toolResultCap.ts
6697
+ function capToolResult(result) {
6698
+ const total = Buffer.byteLength(result, "utf-8");
6699
+ if (total <= MAX_TOOL_RESULT_BYTES) {
6700
+ return result;
6701
+ }
6702
+ const head = Buffer.from(result, "utf-8").subarray(0, MAX_TOOL_RESULT_BYTES).toString("utf-8");
6703
+ return head + `
6704
+
6705
+ (tool result truncated at ${(MAX_TOOL_RESULT_BYTES / 1024).toFixed(0)}KB of ${(total / 1024).toFixed(0)}KB \u2014 too large to keep in context. Narrow the call (select fewer fields, paginate, or query a subset) instead of fetching everything.)`;
6706
+ }
6707
+ var MAX_TOOL_RESULT_BYTES;
6708
+ var init_toolResultCap = __esm({
6709
+ "src/toolResultCap.ts"() {
6710
+ "use strict";
6711
+ MAX_TOOL_RESULT_BYTES = 256 * 1024;
6712
+ }
6713
+ });
6714
+
6655
6715
  // src/session.ts
6656
6716
  import fs20 from "fs";
6657
6717
  import path9 from "path";
@@ -6674,11 +6734,23 @@ function loadSession(state) {
6674
6734
  }
6675
6735
  return false;
6676
6736
  }
6737
+ function capOversizedResults(msg) {
6738
+ if (msg.role === "assistant" && Array.isArray(msg.content)) {
6739
+ for (const block of msg.content) {
6740
+ if (block.type === "tool" && typeof block.result === "string") {
6741
+ block.result = capToolResult(block.result);
6742
+ }
6743
+ }
6744
+ } else if (msg.role === "user" && msg.toolCallId && typeof msg.content === "string") {
6745
+ msg.content = capToolResult(msg.content);
6746
+ }
6747
+ }
6677
6748
  function sanitizeMessages(messages) {
6678
6749
  const result = [];
6679
6750
  for (let i = 0; i < messages.length; i++) {
6680
- result.push(messages[i]);
6681
6751
  const msg = messages[i];
6752
+ capOversizedResults(msg);
6753
+ result.push(msg);
6682
6754
  if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
6683
6755
  continue;
6684
6756
  }
@@ -6799,6 +6871,7 @@ var init_session = __esm({
6799
6871
  init_logger();
6800
6872
  init_compaction();
6801
6873
  init_cleanMessages();
6874
+ init_toolResultCap();
6802
6875
  log9 = createLogger("session");
6803
6876
  SESSION_FILE = ".remy-session.json";
6804
6877
  ARCHIVE_DIR = ".logs/sessions";
@@ -6999,6 +7072,10 @@ var init_errors = __esm({
6999
7072
  "Too many requests. Please wait a moment and try again."
7000
7073
  ],
7001
7074
  [/HTTP 40[13]/i, "Authentication failed. Please check your API key."],
7075
+ [
7076
+ /HTTP 413/i,
7077
+ "This conversation has grown too large to send in a single request. Starting a new conversation will reset the context."
7078
+ ],
7002
7079
  [
7003
7080
  /HTTP 5\d\d/i,
7004
7081
  "The AI service is temporarily unavailable. Please try again."
@@ -7033,15 +7110,17 @@ async function runExtraction(apiConfig, model) {
7033
7110
  log10.info("Brand persisted", { inputHash });
7034
7111
  return brand;
7035
7112
  }
7113
+ function isBrandRelevant(filePath) {
7114
+ if (filePath === path10.join("src", "app.md")) {
7115
+ return true;
7116
+ }
7117
+ const { type } = parseFrontmatter3(filePath);
7118
+ return type.startsWith("design/color") || type.startsWith("design/typography");
7119
+ }
7036
7120
  function computeInputHash() {
7037
7121
  const entries = [];
7038
7122
  for (const filePath of walkMdFiles3("src")) {
7039
- if (filePath === path10.join("src", "app.md")) {
7040
- entries.push({ path: filePath, content: readSafe(filePath) });
7041
- continue;
7042
- }
7043
- const fm = parseFrontmatter3(filePath);
7044
- if (fm.type.startsWith("design/color") || fm.type.startsWith("design/typography")) {
7123
+ if (isBrandRelevant(filePath)) {
7045
7124
  entries.push({ path: filePath, content: readSafe(filePath) });
7046
7125
  }
7047
7126
  }
@@ -7145,22 +7224,40 @@ async function extractBrand(apiConfig, model) {
7145
7224
  return validateBrand(parsed);
7146
7225
  }
7147
7226
  function buildCorpus() {
7148
- const sections = [];
7227
+ const all = walkMdFiles3("src");
7228
+ const ordered = [
7229
+ ...all.filter(isBrandRelevant),
7230
+ ...all.filter((f) => !isBrandRelevant(f))
7231
+ ];
7232
+ const files = [];
7149
7233
  const manifest = readSafe("mindstudio.json");
7150
7234
  if (manifest) {
7151
- sections.push(`## File: mindstudio.json
7152
-
7153
- ${manifest}`);
7235
+ files.push({ path: "mindstudio.json", content: manifest });
7154
7236
  }
7155
- for (const filePath of walkMdFiles3("src")) {
7237
+ for (const filePath of ordered) {
7156
7238
  const content = readSafe(filePath);
7157
7239
  if (content) {
7158
- sections.push(`## File: ${filePath}
7240
+ files.push({ path: filePath, content });
7241
+ }
7242
+ }
7243
+ const sep = "\n\n---\n\n";
7244
+ const sections = [];
7245
+ let usedChars = 0;
7246
+ for (const { path: p, content } of files) {
7247
+ const section = `## File: ${p}
7159
7248
 
7160
- ${content}`);
7249
+ ${content}`;
7250
+ const added = section.length + (sections.length > 0 ? sep.length : 0);
7251
+ if (sections.length > 0 && usedChars + added > BRAND_CORPUS_CHAR_LIMIT) {
7252
+ sections.push(
7253
+ `(brand corpus truncated: included ${sections.length} of ${files.length} files, ~${(usedChars / 1024).toFixed(0)}KB; brand-relevant files were prioritized.)`
7254
+ );
7255
+ break;
7161
7256
  }
7257
+ sections.push(section);
7258
+ usedChars += added;
7162
7259
  }
7163
- return sections.join("\n\n---\n\n");
7260
+ return sections.join(sep);
7164
7261
  }
7165
7262
  function parseJsonResponse(text) {
7166
7263
  const trimmed = text.trim();
@@ -7277,7 +7374,7 @@ function readCache() {
7277
7374
  return null;
7278
7375
  }
7279
7376
  }
7280
- var log10, EXTRACT_PROMPT, BRAND_FILE, CACHE_FILE;
7377
+ var log10, EXTRACT_PROMPT, BRAND_FILE, CACHE_FILE, BRAND_CORPUS_CHAR_LIMIT;
7281
7378
  var init_brandExtraction = __esm({
7282
7379
  "src/brandExtraction/index.ts"() {
7283
7380
  "use strict";
@@ -7289,6 +7386,7 @@ var init_brandExtraction = __esm({
7289
7386
  EXTRACT_PROMPT = readAsset("brandExtraction", "extract.md");
7290
7387
  BRAND_FILE = ".remy-brand.json";
7291
7388
  CACHE_FILE = ".remy-brand.cache.json";
7389
+ BRAND_CORPUS_CHAR_LIMIT = 24e5;
7292
7390
  }
7293
7391
  });
7294
7392
 
@@ -7810,7 +7908,7 @@ async function runTurn(params) {
7810
7908
  })
7811
7909
  });
7812
7910
  }
7813
- safeSettle(result, result.startsWith("Error"));
7911
+ safeSettle(capToolResult(result), result.startsWith("Error"));
7814
7912
  } catch (err) {
7815
7913
  safeSettle(`Error: ${err.message}`, true);
7816
7914
  }
@@ -7916,6 +8014,7 @@ var init_agent = __esm({
7916
8014
  init_trigger2();
7917
8015
  init_surfaces();
7918
8016
  init_toolRegistry();
8017
+ init_toolResultCap();
7919
8018
  log12 = createLogger("agent");
7920
8019
  BRAND_TRIGGERING_TOOLS = /* @__PURE__ */ new Set(["writeSpec", "editSpec"]);
7921
8020
  EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
@@ -8417,7 +8516,7 @@ var init_headless = __esm({
8417
8516
  this.emit("session_restored", {
8418
8517
  messageCount: this.state.messages.length,
8419
8518
  ...this.state.models && { models: this.state.models },
8420
- modelSurfaces: MODEL_SURFACES,
8519
+ modelSurfaces: getEffectiveModelSurfaces(),
8421
8520
  allowedModelsByType: ALLOWED_MODELS_BY_TYPE
8422
8521
  });
8423
8522
  }
@@ -9015,7 +9114,7 @@ var init_headless = __esm({
9015
9114
  saveSession(this.state);
9016
9115
  return {
9017
9116
  ...this.state.models && { models: this.state.models },
9018
- modelSurfaces: MODEL_SURFACES,
9117
+ modelSurfaces: getEffectiveModelSurfaces(),
9019
9118
  allowedModelsByType: ALLOWED_MODELS_BY_TYPE
9020
9119
  };
9021
9120
  }
@@ -9111,7 +9210,7 @@ var init_headless = __esm({
9111
9210
  running: this.running,
9112
9211
  ...this.running && this.currentRequestId ? { currentRequestId: this.currentRequestId } : {},
9113
9212
  ...this.state.models && { models: this.state.models },
9114
- modelSurfaces: MODEL_SURFACES,
9213
+ modelSurfaces: getEffectiveModelSurfaces(),
9115
9214
  allowedModelsByType: ALLOWED_MODELS_BY_TYPE,
9116
9215
  // Current queue snapshot for connect/reconnect — get_history is the
9117
9216
  // on-demand "current state" query. Always an array (possibly empty),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.224",
3
+ "version": "0.1.225",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",