@mindstudio-ai/remy 0.1.268 → 0.1.269

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
@@ -618,7 +618,6 @@ function readJsonAsset(fallback, ...segments) {
618
618
 
619
619
  // src/prompt/static/projectContext.ts
620
620
  import fs4 from "fs";
621
- import path3 from "path";
622
621
 
623
622
  // src/projectRoot.ts
624
623
  var PROJECT_ROOT = process.cwd();
@@ -631,78 +630,23 @@ function loadProjectRoot() {
631
630
 
632
631
  File paths are relative to this directory. Every tool operates here and bash commands run it it.`;
633
632
  }
634
- function loadProjectManifest() {
635
- try {
636
- const manifest = fs4.readFileSync("mindstudio.json", "utf-8");
637
- return `
638
- ## Project Manifest (mindstudio.json)
639
- \`\`\`json
640
- ${manifest}
641
- \`\`\``;
642
- } catch {
643
- return "";
644
- }
645
- }
646
- function loadSpecFileMetadata() {
633
+ function loadAppIdentity() {
647
634
  try {
648
- const files = walkMdFiles("src");
649
- if (files.length === 0) {
635
+ const manifest = JSON.parse(fs4.readFileSync("mindstudio.json", "utf-8"));
636
+ const name = typeof manifest.name === "string" ? manifest.name.trim() : "";
637
+ if (!name) {
650
638
  return "";
651
639
  }
652
- const entries = [];
653
- for (const filePath of files) {
654
- const { name, description, type } = parseFrontmatter(filePath);
655
- let line = `- ${filePath}`;
656
- if (name) {
657
- line += ` \u2014 "${name}"`;
658
- }
659
- if (type) {
660
- line += ` (${type})`;
661
- }
662
- if (description) {
663
- line += ` \u2014 ${description}`;
664
- }
665
- entries.push(line);
666
- }
640
+ const description = typeof manifest.description === "string" ? manifest.description.trim() : "";
667
641
  return `
668
- ## Spec Files
669
- ${entries.join("\n")}`;
642
+ ## App
643
+ "${name}"${description ? ` \u2014 ${description}` : ""}
644
+
645
+ Full manifest: \`mindstudio.json\` (read it when you need the app's structure, tables, methods, roles, auth settings, interfaces, and everything else).`;
670
646
  } catch {
671
647
  return "";
672
648
  }
673
649
  }
674
- function walkMdFiles(dir) {
675
- const results = [];
676
- try {
677
- const entries = fs4.readdirSync(dir, { withFileTypes: true });
678
- for (const entry of entries) {
679
- const full = path3.join(dir, entry.name);
680
- if (entry.isDirectory()) {
681
- results.push(...walkMdFiles(full));
682
- } else if (entry.name.endsWith(".md")) {
683
- results.push(full);
684
- }
685
- }
686
- } catch {
687
- }
688
- return results.sort();
689
- }
690
- function parseFrontmatter(filePath) {
691
- try {
692
- const content = fs4.readFileSync(filePath, "utf-8");
693
- const match = content.match(/^---\n([\s\S]*?)\n---/);
694
- if (!match) {
695
- return { name: "", description: "", type: "" };
696
- }
697
- const fm = match[1];
698
- const name = fm.match(/^name:\s*(.+)$/m)?.[1]?.trim() ?? "";
699
- const description = fm.match(/^description:\s*(.+)$/m)?.[1]?.trim() ?? "";
700
- const type = fm.match(/^type:\s*(.+)$/m)?.[1]?.trim() ?? "";
701
- return { name, description, type };
702
- } catch {
703
- return { name: "", description: "", type: "" };
704
- }
705
- }
706
650
  function loadPlanStatus(onboardingState) {
707
651
  try {
708
652
  const content = fs4.readFileSync(".remy-plan.md", "utf-8");
@@ -731,33 +675,12 @@ The user has approved your implementation plan in .remy-plan.md. You may referen
731
675
  return "";
732
676
  }
733
677
  }
734
- function loadProjectFileListing() {
735
- try {
736
- const entries = fs4.readdirSync(".", { withFileTypes: true });
737
- const listing = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules").sort((a, b) => {
738
- if (a.isDirectory() && !b.isDirectory()) {
739
- return -1;
740
- }
741
- if (!a.isDirectory() && b.isDirectory()) {
742
- return 1;
743
- }
744
- return a.name.localeCompare(b.name);
745
- }).map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
746
- return `
747
- ## Project Files
748
- \`\`\`
749
- ${listing}
750
- \`\`\``;
751
- } catch {
752
- return "";
753
- }
754
- }
755
678
 
756
679
  // src/prompt/skills/catalog.ts
757
680
  import fs5 from "fs";
758
- import path4 from "path";
681
+ import path3 from "path";
759
682
  var SKILLS_DIR = assetPath("prompt", "skills");
760
- function parseFrontmatter2(content) {
683
+ function parseFrontmatter(content) {
761
684
  const match = content.match(/^---\n([\s\S]*?)\n---/);
762
685
  if (!match) {
763
686
  return {};
@@ -780,9 +703,9 @@ function loadCatalog() {
780
703
  }
781
704
  const skills = [];
782
705
  for (const file of files.sort()) {
783
- const full = path4.join(SKILLS_DIR, file);
706
+ const full = path3.join(SKILLS_DIR, file);
784
707
  const id = file.replace(/\.md$/, "");
785
- const fields = parseFrontmatter2(fs5.readFileSync(full, "utf-8"));
708
+ const fields = parseFrontmatter(fs5.readFileSync(full, "utf-8"));
786
709
  if (!fields.name || !fields.what || !fields.when) {
787
710
  continue;
788
711
  }
@@ -836,13 +759,7 @@ function resolveIncludes(template) {
836
759
  );
837
760
  return result.replace(/\n{3,}/g, "\n\n").trim();
838
761
  }
839
- function buildSystemPrompt(onboardingState, viewContext) {
840
- const projectContext = [
841
- loadProjectRoot(),
842
- loadProjectManifest(),
843
- loadSpecFileMetadata(),
844
- loadProjectFileListing()
845
- ].filter(Boolean).join("\n");
762
+ function buildSystemPrompt(onboardingState) {
846
763
  const now = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
847
764
  month: "long",
848
765
  day: "numeric",
@@ -851,8 +768,6 @@ function buildSystemPrompt(onboardingState, viewContext) {
851
768
  const template = `
852
769
  {{static/identity.md}}
853
770
 
854
- Current date: ${now}
855
-
856
771
  <platform_docs>
857
772
  <platform>
858
773
  {{compiled/platform.md}}
@@ -932,7 +847,7 @@ ${loadSkillsCatalog()}
932
847
  <conversation_summaries>
933
848
  Your conversation history may include <prior_conversation_summary> blocks in the user's messages. These are automated summaries of earlier messages that have been compacted to save context space. The user does not see this summary, they see the full conversation history in their UI. Treat the summary as ground truth for what happened before, but do not reference it directly to the user ("as mentioned in the summary..."). Just continue naturally as if you remember the prior work.
934
849
 
935
- Old tool results are periodically cleared from the conversation to save context space. This is automatic and expected \u2014 you don't need to note down or preserve information from tool results. If you need to reference something from an earlier tool call, just re-read the file or re-run the query.
850
+ Tool results generally persist in the conversation until a compaction summarizes them. In rare cases very old tool results may be trimmed when the conversation nears the context limit. Either way, if you need something from an earlier tool call and can't see it anymore, just re-read the file or re-run the query.
936
851
  </conversation_summaries>
937
852
 
938
853
  <project_onboarding>
@@ -944,24 +859,21 @@ New projects progress through three onboarding states. The user might skip this
944
859
  - **onboardingFinished**: The project is built and ready. Full development mode with all tools available. From here on, keep spec and code in sync as changes are made.
945
860
  </project_onboarding>
946
861
 
862
+ ${loadProjectRoot()}
863
+
864
+ ${renderOrgContextBlock()}
865
+
947
866
  {{static/instructions.md}}
948
867
 
949
868
  <!-- cache_breakpoint -->
950
869
 
870
+ Current date: ${now}
871
+
951
872
  <current_project_onboarding_state>
952
873
  ${onboardingState ?? "onboardingFinished"}
953
874
  </current_project_onboarding_state>
954
875
 
955
- <project_context>
956
- ${projectContext}
957
- </project_context>
958
-
959
- ${renderOrgContextBlock()}
960
-
961
- <view_context>
962
- The user is currently in ${viewContext?.mode ?? "code"} mode.
963
- ${viewContext?.activeFile ? `Active file: ${viewContext.activeFile}` : ""}
964
- </view_context>
876
+ ${loadAppIdentity()}
965
877
 
966
878
  ${loadPlanStatus(onboardingState)}
967
879
  `;
@@ -1100,7 +1012,7 @@ var readSpecTool = {
1100
1012
 
1101
1013
  // src/tools/spec/writeSpec.ts
1102
1014
  import fs7 from "fs/promises";
1103
- import path5 from "path";
1015
+ import path4 from "path";
1104
1016
 
1105
1017
  // src/tools/_helpers/diff.ts
1106
1018
  var CONTEXT_LINES = 3;
@@ -1189,7 +1101,7 @@ ${unifiedDiff(partial.path, oldContent, partial.content)}`;
1189
1101
  }
1190
1102
  const release = await acquireFileLock(input.path);
1191
1103
  try {
1192
- await fs7.mkdir(path5.dirname(input.path), { recursive: true });
1104
+ await fs7.mkdir(path4.dirname(input.path), { recursive: true });
1193
1105
  let oldContent = null;
1194
1106
  try {
1195
1107
  oldContent = await fs7.readFile(input.path, "utf-8");
@@ -1394,7 +1306,7 @@ var editSpecTool = {
1394
1306
 
1395
1307
  // src/tools/spec/listSpecFiles.ts
1396
1308
  import fs9 from "fs/promises";
1397
- import path6 from "path";
1309
+ import path5 from "path";
1398
1310
  var listSpecFilesTool = {
1399
1311
  definition: {
1400
1312
  name: "listSpecFiles",
@@ -1433,7 +1345,7 @@ async function listRecursive(dir) {
1433
1345
  return a.name.localeCompare(b.name);
1434
1346
  });
1435
1347
  for (const entry of entries) {
1436
- const fullPath = path6.join(dir, entry.name);
1348
+ const fullPath = path5.join(dir, entry.name);
1437
1349
  if (entry.isDirectory()) {
1438
1350
  results.push(`${fullPath}/`);
1439
1351
  results.push(...await listRecursive(fullPath));
@@ -2271,7 +2183,7 @@ var readFileTool = {
2271
2183
 
2272
2184
  // src/tools/code/writeFile.ts
2273
2185
  import fs14 from "fs/promises";
2274
- import path7 from "path";
2186
+ import path6 from "path";
2275
2187
  var writeFileTool = {
2276
2188
  definition: {
2277
2189
  name: "writeFile",
@@ -2316,7 +2228,7 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
2316
2228
  async execute(input) {
2317
2229
  const release = await acquireFileLock(input.path);
2318
2230
  try {
2319
- await fs14.mkdir(path7.dirname(input.path), { recursive: true });
2231
+ await fs14.mkdir(path6.dirname(input.path), { recursive: true });
2320
2232
  let oldContent = null;
2321
2233
  try {
2322
2234
  oldContent = await fs14.readFile(input.path, "utf-8");
@@ -2425,7 +2337,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2425
2337
 
2426
2338
  // src/tools/code/bash.ts
2427
2339
  import { spawn as spawn2 } from "child_process";
2428
- import path8 from "path";
2340
+ import path7 from "path";
2429
2341
  var DEFAULT_TIMEOUT_MS = 12e4;
2430
2342
  var DEFAULT_MAX_LINES2 = 500;
2431
2343
  var MAX_OUTPUT_BYTES = 3e4;
@@ -2463,7 +2375,7 @@ var bashTool = {
2463
2375
  const child = spawn2("sh", ["-c", input.command], {
2464
2376
  // Pinned rather than inherited. `undefined` here means "wherever the
2465
2377
  // process happens to be", which is the project root only by luck.
2466
- cwd: input.cwd ? path8.resolve(PROJECT_ROOT, input.cwd) : PROJECT_ROOT,
2378
+ cwd: input.cwd ? path7.resolve(PROJECT_ROOT, input.cwd) : PROJECT_ROOT,
2467
2379
  env: { ...process.env, FORCE_COLOR: "1" }
2468
2380
  });
2469
2381
  let output = "";
@@ -2707,7 +2619,7 @@ var globTool = {
2707
2619
 
2708
2620
  // src/tools/code/listDir.ts
2709
2621
  import fs16 from "fs/promises";
2710
- import path9 from "path";
2622
+ import path8 from "path";
2711
2623
  var EXCLUDE = /* @__PURE__ */ new Set([".git", "node_modules"]);
2712
2624
  var MAX_CHILDREN = 15;
2713
2625
  async function readAndSort(dirPath) {
@@ -2724,7 +2636,7 @@ async function readAndSort(dirPath) {
2724
2636
  }
2725
2637
  async function collapsePath(basePath, name) {
2726
2638
  let display = name;
2727
- let current = path9.join(basePath, name);
2639
+ let current = path8.join(basePath, name);
2728
2640
  for (; ; ) {
2729
2641
  let children;
2730
2642
  try {
@@ -2734,7 +2646,7 @@ async function collapsePath(basePath, name) {
2734
2646
  }
2735
2647
  if (children.length === 1 && children[0].isDirectory()) {
2736
2648
  display += "/" + children[0].name;
2737
- current = path9.join(current, children[0].name);
2649
+ current = path8.join(current, children[0].name);
2738
2650
  } else {
2739
2651
  break;
2740
2652
  }
@@ -2752,7 +2664,7 @@ function formatSize(bytes) {
2752
2664
  }
2753
2665
  async function formatFile(dirPath, name, indent) {
2754
2666
  try {
2755
- const stat2 = await fs16.stat(path9.join(dirPath, name));
2667
+ const stat2 = await fs16.stat(path8.join(dirPath, name));
2756
2668
  return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat2.size)}`;
2757
2669
  } catch {
2758
2670
  return `${indent}${name}`;
@@ -3215,7 +3127,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
3215
3127
  let onLog;
3216
3128
  let model;
3217
3129
  let apiConfig;
3218
- let path14;
3130
+ let path13;
3219
3131
  let fullPage = true;
3220
3132
  let width;
3221
3133
  let height;
@@ -3223,7 +3135,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
3223
3135
  if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
3224
3136
  prompt = promptOrOptions.prompt;
3225
3137
  existingImage = promptOrOptions.image;
3226
- path14 = promptOrOptions.path;
3138
+ path13 = promptOrOptions.path;
3227
3139
  if (promptOrOptions.fullPage !== void 0) {
3228
3140
  fullPage = promptOrOptions.fullPage;
3229
3141
  }
@@ -3247,7 +3159,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
3247
3159
  const ssResult = await sidecarRequest(
3248
3160
  fullPage ? "/screenshot-full-page" : "/screenshot-viewport",
3249
3161
  {
3250
- ...path14 ? { path: path14 } : {},
3162
+ ...path13 ? { path: path13 } : {},
3251
3163
  ...width != null ? { width } : {},
3252
3164
  ...height != null ? { height } : {},
3253
3165
  ...format ? { format } : {}
@@ -4250,15 +4162,15 @@ var BROWSER_EXTERNAL_TOOLS = /* @__PURE__ */ new Set(["browserCommand"]);
4250
4162
 
4251
4163
  // src/subagents/common/context.ts
4252
4164
  import fs17 from "fs";
4253
- import path10 from "path";
4254
- function walkMdFiles2(dir, skip) {
4165
+ import path9 from "path";
4166
+ function walkMdFiles(dir, skip) {
4255
4167
  const files = [];
4256
4168
  try {
4257
4169
  for (const entry of fs17.readdirSync(dir, { withFileTypes: true })) {
4258
- const full = path10.join(dir, entry.name);
4170
+ const full = path9.join(dir, entry.name);
4259
4171
  if (entry.isDirectory()) {
4260
4172
  if (!skip?.has(entry.name)) {
4261
- files.push(...walkMdFiles2(full, skip));
4173
+ files.push(...walkMdFiles(full, skip));
4262
4174
  }
4263
4175
  } else if (entry.name.endsWith(".md")) {
4264
4176
  files.push(full);
@@ -4268,7 +4180,7 @@ function walkMdFiles2(dir, skip) {
4268
4180
  }
4269
4181
  return files.sort();
4270
4182
  }
4271
- function parseFrontmatter3(filePath) {
4183
+ function parseFrontmatter2(filePath) {
4272
4184
  try {
4273
4185
  const content = fs17.readFileSync(filePath, "utf-8");
4274
4186
  const match = content.match(/^---\n([\s\S]*?)\n---/);
@@ -4290,12 +4202,12 @@ function parseFrontmatter3(filePath) {
4290
4202
  }
4291
4203
  }
4292
4204
  function loadSpecIndex() {
4293
- const files = walkMdFiles2("src", /* @__PURE__ */ new Set(["roadmap"]));
4205
+ const files = walkMdFiles("src", /* @__PURE__ */ new Set(["roadmap"]));
4294
4206
  if (files.length === 0) {
4295
4207
  return "";
4296
4208
  }
4297
4209
  const lines = files.map((f) => {
4298
- const fm = parseFrontmatter3(f);
4210
+ const fm = parseFrontmatter2(f);
4299
4211
  let line = `- ${f}`;
4300
4212
  if (fm.name) {
4301
4213
  line += ` \u2014 "${fm.name}"`;
@@ -4333,10 +4245,10 @@ ${indexJson.standalone.map((s) => `- ${s}`).join("\n")}`
4333
4245
  }
4334
4246
  } catch {
4335
4247
  }
4336
- const files = walkMdFiles2("src/roadmap");
4248
+ const files = walkMdFiles("src/roadmap");
4337
4249
  if (files.length > 0) {
4338
4250
  const lines = files.map((f) => {
4339
- const fm = parseFrontmatter3(f);
4251
+ const fm = parseFrontmatter2(f);
4340
4252
  let line = `- ${f}`;
4341
4253
  if (fm.name) {
4342
4254
  line += ` \u2014 "${fm.name}"`;
@@ -5705,14 +5617,14 @@ var VISION_TOOLS = [
5705
5617
 
5706
5618
  // src/subagents/productVision/executor.ts
5707
5619
  import fs19 from "fs";
5708
- import path11 from "path";
5620
+ import path10 from "path";
5709
5621
  var ROADMAP_DIR = "src/roadmap";
5710
5622
  var PITCH_DECK_SHELL = readAsset(
5711
5623
  "subagents/productVision",
5712
5624
  "pitch-deck-shell.html"
5713
5625
  );
5714
5626
  function resolve2(filePath) {
5715
- return path11.join(ROADMAP_DIR, filePath);
5627
+ return path10.join(ROADMAP_DIR, filePath);
5716
5628
  }
5717
5629
  async function executeVisionTool(name, input, context) {
5718
5630
  switch (name) {
@@ -6732,7 +6644,7 @@ Write the summary of the conversation above, following your instructions.`;
6732
6644
 
6733
6645
  // src/session.ts
6734
6646
  import fs21 from "fs";
6735
- import path12 from "path";
6647
+ import path11 from "path";
6736
6648
  var log10 = createLogger("session");
6737
6649
  var SESSION_FILE = ".remy-session.json";
6738
6650
  var ARCHIVE_DIR = ".logs/sessions";
@@ -6831,17 +6743,17 @@ function archiveMessages(messages, label, models) {
6831
6743
  fs21.mkdirSync(ARCHIVE_DIR, { recursive: true });
6832
6744
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
6833
6745
  const count = messages.length;
6834
- let dest = path12.join(ARCHIVE_DIR, `${label}-${ts}.c${count}.json`);
6746
+ let dest = path11.join(ARCHIVE_DIR, `${label}-${ts}.c${count}.json`);
6835
6747
  let n = 1;
6836
6748
  while (fs21.existsSync(dest)) {
6837
- dest = path12.join(ARCHIVE_DIR, `${label}-${ts}-${n++}.c${count}.json`);
6749
+ dest = path11.join(ARCHIVE_DIR, `${label}-${ts}-${n++}.c${count}.json`);
6838
6750
  }
6839
6751
  const payload = { messages };
6840
6752
  if (models && Object.keys(models).length > 0) {
6841
6753
  payload.models = models;
6842
6754
  }
6843
6755
  writeFileAtomicSync(dest, JSON.stringify(payload));
6844
- archiveCountCache.set(path12.basename(dest), count);
6756
+ archiveCountCache.set(path11.basename(dest), count);
6845
6757
  log10.info("Session archived", { label, dest, messageCount: count });
6846
6758
  pruneArchives();
6847
6759
  return dest;
@@ -6854,7 +6766,7 @@ function pruneArchives() {
6854
6766
  }
6855
6767
  const archives = entries.map((name) => ({
6856
6768
  name,
6857
- size: fs21.statSync(path12.join(ARCHIVE_DIR, name)).size
6769
+ size: fs21.statSync(path11.join(ARCHIVE_DIR, name)).size
6858
6770
  })).sort(
6859
6771
  (a, b) => archiveSortKey(b.name).localeCompare(archiveSortKey(a.name))
6860
6772
  );
@@ -6872,7 +6784,7 @@ function pruneArchives() {
6872
6784
  let freed = 0;
6873
6785
  for (let i = cut; i < archives.length; i++) {
6874
6786
  try {
6875
- fs21.unlinkSync(path12.join(ARCHIVE_DIR, archives[i].name));
6787
+ fs21.unlinkSync(path11.join(ARCHIVE_DIR, archives[i].name));
6876
6788
  freed += archives[i].size;
6877
6789
  removed++;
6878
6790
  } catch {
@@ -6896,7 +6808,7 @@ function parseArchive(name) {
6896
6808
  return cached3;
6897
6809
  }
6898
6810
  try {
6899
- const raw = fs21.readFileSync(path12.join(ARCHIVE_DIR, name), "utf-8");
6811
+ const raw = fs21.readFileSync(path11.join(ARCHIVE_DIR, name), "utf-8");
6900
6812
  const data = JSON.parse(raw);
6901
6813
  const messages = Array.isArray(data?.messages) ? data.messages : [];
6902
6814
  archiveCountCache.set(name, messages.length);
@@ -7177,7 +7089,7 @@ function triggerCompaction(state, apiConfig, opts = {}) {
7177
7089
 
7178
7090
  // src/brandExtraction/index.ts
7179
7091
  import fs22 from "fs";
7180
- import path13 from "path";
7092
+ import path12 from "path";
7181
7093
  import { createHash } from "crypto";
7182
7094
  var log12 = createLogger("brandExtraction");
7183
7095
  var EXTRACT_PROMPT = readAsset("brandExtraction", "extract.md");
@@ -7201,18 +7113,18 @@ async function runExtraction(apiConfig, model) {
7201
7113
  return brand;
7202
7114
  }
7203
7115
  function isDedicatedBrandFile(filePath) {
7204
- if (filePath.split(path13.sep).includes("@brand")) {
7116
+ if (filePath.split(path12.sep).includes("@brand")) {
7205
7117
  return true;
7206
7118
  }
7207
- const { type } = parseFrontmatter4(filePath);
7119
+ const { type } = parseFrontmatter3(filePath);
7208
7120
  return type.startsWith("design/color") || type.startsWith("design/typography");
7209
7121
  }
7210
7122
  function isBrandRelevant(filePath) {
7211
- return filePath === path13.join("src", "app.md") || isDedicatedBrandFile(filePath);
7123
+ return filePath === path12.join("src", "app.md") || isDedicatedBrandFile(filePath);
7212
7124
  }
7213
7125
  function computeInputHash() {
7214
7126
  const entries = [];
7215
- for (const filePath of walkMdFiles3("src")) {
7127
+ for (const filePath of walkMdFiles2("src")) {
7216
7128
  if (isBrandRelevant(filePath)) {
7217
7129
  entries.push({ path: filePath, content: readSafe(filePath) });
7218
7130
  }
@@ -7253,14 +7165,14 @@ function readBrandManifest() {
7253
7165
  return "";
7254
7166
  }
7255
7167
  }
7256
- function walkMdFiles3(dir) {
7168
+ function walkMdFiles2(dir) {
7257
7169
  const results = [];
7258
7170
  try {
7259
7171
  const entries = fs22.readdirSync(dir, { withFileTypes: true });
7260
7172
  for (const entry of entries) {
7261
- const full = path13.join(dir, entry.name);
7173
+ const full = path12.join(dir, entry.name);
7262
7174
  if (entry.isDirectory()) {
7263
- results.push(...walkMdFiles3(full));
7175
+ results.push(...walkMdFiles2(full));
7264
7176
  } else if (entry.name.endsWith(".md")) {
7265
7177
  results.push(full);
7266
7178
  }
@@ -7269,7 +7181,7 @@ function walkMdFiles3(dir) {
7269
7181
  }
7270
7182
  return results.sort();
7271
7183
  }
7272
- function parseFrontmatter4(filePath) {
7184
+ function parseFrontmatter3(filePath) {
7273
7185
  try {
7274
7186
  const content = fs22.readFileSync(filePath, "utf-8");
7275
7187
  const match = content.match(/^---\n([\s\S]*?)\n---/);
@@ -7337,7 +7249,7 @@ async function extractBrand(apiConfig, model) {
7337
7249
  var HEAD_SLICE_CHARS = 2e3;
7338
7250
  var BRAND_CORPUS_CHAR_LIMIT = 24e5;
7339
7251
  function buildCorpus() {
7340
- const all = walkMdFiles3("src");
7252
+ const all = walkMdFiles2("src");
7341
7253
  const ordered = [
7342
7254
  ...all.filter(isBrandRelevant),
7343
7255
  ...all.filter((f) => !isBrandRelevant(f))
@@ -9350,10 +9262,7 @@ var HeadlessSession = class {
9350
9262
  const buildModel = hasSentinel(rawText, "approvePlan") ? parsed.buildModel : void 0;
9351
9263
  const onboardingState = parsed.onboardingState ?? "onboardingFinished";
9352
9264
  this.currentOnboardingState = onboardingState;
9353
- const system = buildSystemPrompt(
9354
- onboardingState,
9355
- parsed.viewContext
9356
- );
9265
+ const system = buildSystemPrompt(onboardingState);
9357
9266
  if (resolved?.next && !fromChain) {
9358
9267
  for (const step of getActionChain(resolved.next)) {
9359
9268
  this.queue.push({
@@ -9430,13 +9339,12 @@ var HeadlessSession = class {
9430
9339
  }
9431
9340
  const onboardingState = batch.find((b) => b.command.onboardingState !== void 0)?.command.onboardingState ?? this.currentOnboardingState ?? "onboardingFinished";
9432
9341
  this.currentOnboardingState = onboardingState;
9433
- const viewContext = [...batch].reverse().find((b) => b.command.viewContext !== void 0)?.command.viewContext;
9434
9342
  await this.executeTurn({
9435
9343
  entries,
9436
9344
  requestId: primaryRid,
9437
9345
  absorbedRids,
9438
9346
  onboardingState,
9439
- system: buildSystemPrompt(onboardingState, viewContext)
9347
+ system: buildSystemPrompt(onboardingState)
9440
9348
  });
9441
9349
  }
9442
9350
  /**
package/dist/index.js CHANGED
@@ -4215,7 +4215,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
4215
4215
  let onLog;
4216
4216
  let model;
4217
4217
  let apiConfig;
4218
- let path15;
4218
+ let path14;
4219
4219
  let fullPage = true;
4220
4220
  let width;
4221
4221
  let height;
@@ -4223,7 +4223,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
4223
4223
  if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
4224
4224
  prompt = promptOrOptions.prompt;
4225
4225
  existingImage = promptOrOptions.image;
4226
- path15 = promptOrOptions.path;
4226
+ path14 = promptOrOptions.path;
4227
4227
  if (promptOrOptions.fullPage !== void 0) {
4228
4228
  fullPage = promptOrOptions.fullPage;
4229
4229
  }
@@ -4247,7 +4247,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
4247
4247
  const ssResult = await sidecarRequest(
4248
4248
  fullPage ? "/screenshot-full-page" : "/screenshot-viewport",
4249
4249
  {
4250
- ...path15 ? { path: path15 } : {},
4250
+ ...path14 ? { path: path14 } : {},
4251
4251
  ...width != null ? { width } : {},
4252
4252
  ...height != null ? { height } : {},
4253
4253
  ...format ? { format } : {}
@@ -8977,7 +8977,6 @@ var init_agent = __esm({
8977
8977
 
8978
8978
  // src/prompt/static/projectContext.ts
8979
8979
  import fs21 from "fs";
8980
- import path12 from "path";
8981
8980
  function loadProjectRoot() {
8982
8981
  return `
8983
8982
  ## Project Root
@@ -8985,78 +8984,23 @@ function loadProjectRoot() {
8985
8984
 
8986
8985
  File paths are relative to this directory. Every tool operates here and bash commands run it it.`;
8987
8986
  }
8988
- function loadProjectManifest() {
8989
- try {
8990
- const manifest = fs21.readFileSync("mindstudio.json", "utf-8");
8991
- return `
8992
- ## Project Manifest (mindstudio.json)
8993
- \`\`\`json
8994
- ${manifest}
8995
- \`\`\``;
8996
- } catch {
8997
- return "";
8998
- }
8999
- }
9000
- function loadSpecFileMetadata() {
8987
+ function loadAppIdentity() {
9001
8988
  try {
9002
- const files = walkMdFiles3("src");
9003
- if (files.length === 0) {
8989
+ const manifest = JSON.parse(fs21.readFileSync("mindstudio.json", "utf-8"));
8990
+ const name = typeof manifest.name === "string" ? manifest.name.trim() : "";
8991
+ if (!name) {
9004
8992
  return "";
9005
8993
  }
9006
- const entries = [];
9007
- for (const filePath of files) {
9008
- const { name, description, type } = parseFrontmatter4(filePath);
9009
- let line = `- ${filePath}`;
9010
- if (name) {
9011
- line += ` \u2014 "${name}"`;
9012
- }
9013
- if (type) {
9014
- line += ` (${type})`;
9015
- }
9016
- if (description) {
9017
- line += ` \u2014 ${description}`;
9018
- }
9019
- entries.push(line);
9020
- }
8994
+ const description = typeof manifest.description === "string" ? manifest.description.trim() : "";
9021
8995
  return `
9022
- ## Spec Files
9023
- ${entries.join("\n")}`;
8996
+ ## App
8997
+ "${name}"${description ? ` \u2014 ${description}` : ""}
8998
+
8999
+ Full manifest: \`mindstudio.json\` (read it when you need the app's structure, tables, methods, roles, auth settings, interfaces, and everything else).`;
9024
9000
  } catch {
9025
9001
  return "";
9026
9002
  }
9027
9003
  }
9028
- function walkMdFiles3(dir) {
9029
- const results = [];
9030
- try {
9031
- const entries = fs21.readdirSync(dir, { withFileTypes: true });
9032
- for (const entry of entries) {
9033
- const full = path12.join(dir, entry.name);
9034
- if (entry.isDirectory()) {
9035
- results.push(...walkMdFiles3(full));
9036
- } else if (entry.name.endsWith(".md")) {
9037
- results.push(full);
9038
- }
9039
- }
9040
- } catch {
9041
- }
9042
- return results.sort();
9043
- }
9044
- function parseFrontmatter4(filePath) {
9045
- try {
9046
- const content = fs21.readFileSync(filePath, "utf-8");
9047
- const match = content.match(/^---\n([\s\S]*?)\n---/);
9048
- if (!match) {
9049
- return { name: "", description: "", type: "" };
9050
- }
9051
- const fm = match[1];
9052
- const name = fm.match(/^name:\s*(.+)$/m)?.[1]?.trim() ?? "";
9053
- const description = fm.match(/^description:\s*(.+)$/m)?.[1]?.trim() ?? "";
9054
- const type = fm.match(/^type:\s*(.+)$/m)?.[1]?.trim() ?? "";
9055
- return { name, description, type };
9056
- } catch {
9057
- return { name: "", description: "", type: "" };
9058
- }
9059
- }
9060
9004
  function loadPlanStatus(onboardingState) {
9061
9005
  try {
9062
9006
  const content = fs21.readFileSync(".remy-plan.md", "utf-8");
@@ -9085,27 +9029,6 @@ The user has approved your implementation plan in .remy-plan.md. You may referen
9085
9029
  return "";
9086
9030
  }
9087
9031
  }
9088
- function loadProjectFileListing() {
9089
- try {
9090
- const entries = fs21.readdirSync(".", { withFileTypes: true });
9091
- const listing = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules").sort((a, b) => {
9092
- if (a.isDirectory() && !b.isDirectory()) {
9093
- return -1;
9094
- }
9095
- if (!a.isDirectory() && b.isDirectory()) {
9096
- return 1;
9097
- }
9098
- return a.name.localeCompare(b.name);
9099
- }).map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
9100
- return `
9101
- ## Project Files
9102
- \`\`\`
9103
- ${listing}
9104
- \`\`\``;
9105
- } catch {
9106
- return "";
9107
- }
9108
- }
9109
9032
  var init_projectContext = __esm({
9110
9033
  "src/prompt/static/projectContext.ts"() {
9111
9034
  "use strict";
@@ -9121,13 +9044,7 @@ function resolveIncludes(template) {
9121
9044
  );
9122
9045
  return result.replace(/\n{3,}/g, "\n\n").trim();
9123
9046
  }
9124
- function buildSystemPrompt(onboardingState, viewContext) {
9125
- const projectContext = [
9126
- loadProjectRoot(),
9127
- loadProjectManifest(),
9128
- loadSpecFileMetadata(),
9129
- loadProjectFileListing()
9130
- ].filter(Boolean).join("\n");
9047
+ function buildSystemPrompt(onboardingState) {
9131
9048
  const now = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
9132
9049
  month: "long",
9133
9050
  day: "numeric",
@@ -9136,8 +9053,6 @@ function buildSystemPrompt(onboardingState, viewContext) {
9136
9053
  const template = `
9137
9054
  {{static/identity.md}}
9138
9055
 
9139
- Current date: ${now}
9140
-
9141
9056
  <platform_docs>
9142
9057
  <platform>
9143
9058
  {{compiled/platform.md}}
@@ -9217,7 +9132,7 @@ ${loadSkillsCatalog()}
9217
9132
  <conversation_summaries>
9218
9133
  Your conversation history may include <prior_conversation_summary> blocks in the user's messages. These are automated summaries of earlier messages that have been compacted to save context space. The user does not see this summary, they see the full conversation history in their UI. Treat the summary as ground truth for what happened before, but do not reference it directly to the user ("as mentioned in the summary..."). Just continue naturally as if you remember the prior work.
9219
9134
 
9220
- Old tool results are periodically cleared from the conversation to save context space. This is automatic and expected \u2014 you don't need to note down or preserve information from tool results. If you need to reference something from an earlier tool call, just re-read the file or re-run the query.
9135
+ Tool results generally persist in the conversation until a compaction summarizes them. In rare cases very old tool results may be trimmed when the conversation nears the context limit. Either way, if you need something from an earlier tool call and can't see it anymore, just re-read the file or re-run the query.
9221
9136
  </conversation_summaries>
9222
9137
 
9223
9138
  <project_onboarding>
@@ -9229,24 +9144,21 @@ New projects progress through three onboarding states. The user might skip this
9229
9144
  - **onboardingFinished**: The project is built and ready. Full development mode with all tools available. From here on, keep spec and code in sync as changes are made.
9230
9145
  </project_onboarding>
9231
9146
 
9147
+ ${loadProjectRoot()}
9148
+
9149
+ ${renderOrgContextBlock()}
9150
+
9232
9151
  {{static/instructions.md}}
9233
9152
 
9234
9153
  <!-- cache_breakpoint -->
9235
9154
 
9155
+ Current date: ${now}
9156
+
9236
9157
  <current_project_onboarding_state>
9237
9158
  ${onboardingState ?? "onboardingFinished"}
9238
9159
  </current_project_onboarding_state>
9239
9160
 
9240
- <project_context>
9241
- ${projectContext}
9242
- </project_context>
9243
-
9244
- ${renderOrgContextBlock()}
9245
-
9246
- <view_context>
9247
- The user is currently in ${viewContext?.mode ?? "code"} mode.
9248
- ${viewContext?.activeFile ? `Active file: ${viewContext.activeFile}` : ""}
9249
- </view_context>
9161
+ ${loadAppIdentity()}
9250
9162
 
9251
9163
  ${loadPlanStatus(onboardingState)}
9252
9164
  `;
@@ -9264,7 +9176,7 @@ var init_prompt4 = __esm({
9264
9176
 
9265
9177
  // src/config.ts
9266
9178
  import fs22 from "fs";
9267
- import path13 from "path";
9179
+ import path12 from "path";
9268
9180
  import os from "os";
9269
9181
  function loadConfigFile() {
9270
9182
  try {
@@ -9307,7 +9219,7 @@ var init_config = __esm({
9307
9219
  "use strict";
9308
9220
  init_logger();
9309
9221
  log14 = createLogger("config");
9310
- CONFIG_PATH = path13.join(
9222
+ CONFIG_PATH = path12.join(
9311
9223
  os.homedir(),
9312
9224
  ".mindstudio-local-tunnel",
9313
9225
  "config.json"
@@ -10269,10 +10181,7 @@ var init_headless = __esm({
10269
10181
  const buildModel = hasSentinel(rawText, "approvePlan") ? parsed.buildModel : void 0;
10270
10182
  const onboardingState = parsed.onboardingState ?? "onboardingFinished";
10271
10183
  this.currentOnboardingState = onboardingState;
10272
- const system = buildSystemPrompt(
10273
- onboardingState,
10274
- parsed.viewContext
10275
- );
10184
+ const system = buildSystemPrompt(onboardingState);
10276
10185
  if (resolved?.next && !fromChain) {
10277
10186
  for (const step of getActionChain(resolved.next)) {
10278
10187
  this.queue.push({
@@ -10349,13 +10258,12 @@ var init_headless = __esm({
10349
10258
  }
10350
10259
  const onboardingState = batch.find((b) => b.command.onboardingState !== void 0)?.command.onboardingState ?? this.currentOnboardingState ?? "onboardingFinished";
10351
10260
  this.currentOnboardingState = onboardingState;
10352
- const viewContext = [...batch].reverse().find((b) => b.command.viewContext !== void 0)?.command.viewContext;
10353
10261
  await this.executeTurn({
10354
10262
  entries,
10355
10263
  requestId: primaryRid,
10356
10264
  absorbedRids,
10357
10265
  onboardingState,
10358
- system: buildSystemPrompt(onboardingState, viewContext)
10266
+ system: buildSystemPrompt(onboardingState)
10359
10267
  });
10360
10268
  }
10361
10269
  /**
@@ -10896,7 +10804,7 @@ var init_headless = __esm({
10896
10804
  import { render } from "ink";
10897
10805
  import os2 from "os";
10898
10806
  import fs23 from "fs";
10899
- import path14 from "path";
10807
+ import path13 from "path";
10900
10808
 
10901
10809
  // src/tui/App.tsx
10902
10810
  import { useState as useState2, useCallback, useRef } from "react";
@@ -11222,7 +11130,7 @@ var startupLog = createLogger("startup");
11222
11130
  function printDebugInfo(config) {
11223
11131
  const pkg = JSON.parse(
11224
11132
  fs23.readFileSync(
11225
- path14.join(import.meta.dirname, "..", "package.json"),
11133
+ path13.join(import.meta.dirname, "..", "package.json"),
11226
11134
  "utf-8"
11227
11135
  )
11228
11136
  );
@@ -99,6 +99,12 @@ method itself can read `session.voiceSessionId` / `session.visitorId` from the a
99
99
  (`import { session } from '@mindstudio-ai/agent'`) — the same id the browser holds as
100
100
  `session.sessionId`, guaranteed by the platform rather than echoed by the model.
101
101
 
102
+ Voice sessions also carry `session.medium` (`'web' | 'phone-in' | 'phone-out'`) and, on phone
103
+ calls, `session.sip` (`{ to, fromNumber }`) — use `medium` in tool methods and the session-context
104
+ method to branch web vs phone behavior (what to prefetch, how to phrase context). Treat
105
+ `session.sip.fromNumber` as context only, never identity: caller ID is spoofable, so don't gate
106
+ data or roles on it — in-call verification is the auth rail.
107
+
102
108
  ### Client tools: actions that happen on screen (`target: "client"`)
103
109
 
104
110
  A tool whose effect belongs in the browser — open the verification sheet, navigate to a page,
@@ -136,6 +142,11 @@ session.registerClientTool('showVerification', async ({ reason }) => {
136
142
  becomes an error/ack the agent can speak around.
137
143
  - The progressive-auth pattern above is the canonical use: make the verification sheet a client
138
144
  tool and the agent opens it deliberately instead of the frontend inferring it from tool events.
145
+ - Phone sessions never see client tools — there is no browser on a call, so the platform drops
146
+ them from the toolset and tells the agent it's on a phone call with no screen. Verification
147
+ branches by itself: on a phone the agent gets the platform's in-call verify tools instead of
148
+ the app's sheet. Nothing to author; just don't make a client tool the only path to something
149
+ phone callers need.
139
150
 
140
151
  ### Tool descriptions say results out loud
141
152
 
@@ -338,7 +349,10 @@ export async function callMeAboutMyOrder(input: { phone: string }) {
338
349
  who it's talking to (Current User block) and every tool call carries their roles — regardless
339
350
  of which number was dialed (the user types any number into a field; identity comes from their
340
351
  session, not the phone). Omitted/false → anonymous call; role-gated tools decline.
341
- System/cron invocations have no human identity and always run anonymously.
352
+ System/cron invocations have no human identity and always run anonymously. Anonymous outbound
353
+ calls (deployed) get the same in-call verification flow as inbound — the callee proves
354
+ possession of the number that was dialed, or verifies by email — so an anonymous call can
355
+ still upgrade to a known user mid-conversation.
342
356
  - **Production needs a dedicated phone number.** The app owner attaches one ($1/month) via the
343
357
  dashboard or `mindstudio-prod voice numbers` (see "The voice CLI"
344
358
  below) — it becomes the caller ID for every call, in dev sessions too, so users always see
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.268",
3
+ "version": "0.1.269",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",