@mindstudio-ai/remy 0.1.269 → 0.1.270

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -872,12 +872,44 @@ var init_presentPublishPlan = __esm({
872
872
  }
873
873
  });
874
874
 
875
+ // src/atomicWrite.ts
876
+ import fs6 from "fs";
877
+ import fsp from "fs/promises";
878
+ var writeFileAtomicSync, writeFileAtomic;
879
+ var init_atomicWrite = __esm({
880
+ "src/atomicWrite.ts"() {
881
+ "use strict";
882
+ writeFileAtomicSync = (file, data) => {
883
+ const tmp = `${file}.${process.pid}.tmp`;
884
+ const fd2 = fs6.openSync(tmp, "w");
885
+ try {
886
+ fs6.writeSync(fd2, data);
887
+ fs6.fsyncSync(fd2);
888
+ } finally {
889
+ fs6.closeSync(fd2);
890
+ }
891
+ fs6.renameSync(tmp, file);
892
+ };
893
+ writeFileAtomic = async (file, data) => {
894
+ const tmp = `${file}.${process.pid}.tmp`;
895
+ const handle = await fsp.open(tmp, "w");
896
+ try {
897
+ await handle.writeFile(data);
898
+ await handle.sync();
899
+ } finally {
900
+ await handle.close();
901
+ }
902
+ await fsp.rename(tmp, file);
903
+ };
904
+ }
905
+ });
906
+
875
907
  // src/tools/spec/writePlan.ts
876
- import fs6 from "fs/promises";
877
908
  var PLAN_FILE, writePlanTool;
878
909
  var init_writePlan = __esm({
879
910
  "src/tools/spec/writePlan.ts"() {
880
911
  "use strict";
912
+ init_atomicWrite();
881
913
  PLAN_FILE = ".remy-plan.md";
882
914
  writePlanTool = {
883
915
  definition: {
@@ -901,7 +933,7 @@ status: pending
901
933
  ---
902
934
 
903
935
  ${content}`;
904
- await fs6.writeFile(PLAN_FILE, file, "utf-8");
936
+ await writeFileAtomic(PLAN_FILE, file);
905
937
  return "Plan written to .remy-plan.md. Waiting for user approval.";
906
938
  }
907
939
  };
@@ -914,6 +946,7 @@ var PLAN_FILE2, updatePlanStatusTool;
914
946
  var init_updatePlanStatus = __esm({
915
947
  "src/tools/spec/updatePlanStatus.ts"() {
916
948
  "use strict";
949
+ init_atomicWrite();
917
950
  PLAN_FILE2 = ".remy-plan.md";
918
951
  updatePlanStatusTool = {
919
952
  definition: {
@@ -943,13 +976,13 @@ var init_updatePlanStatus = __esm({
943
976
  return "No plan file found.";
944
977
  }
945
978
  if (status === "rejected") {
946
- await fs7.unlink(PLAN_FILE2);
979
+ await fs7.unlink(PLAN_FILE2).catch(() => {
980
+ });
947
981
  return "Plan rejected and removed.";
948
982
  }
949
- await fs7.writeFile(
983
+ await writeFileAtomic(
950
984
  PLAN_FILE2,
951
- content.replace(/^status:\s*\w+/m, `status: ${status}`),
952
- "utf-8"
985
+ content.replace(/^status:\s*\w+/m, `status: ${status}`)
953
986
  );
954
987
  return "Plan approved. Proceeding with implementation.";
955
988
  }
@@ -1988,7 +2021,11 @@ Write the summary of the conversation above, following your instructions.`;
1988
2021
  messages: [{ role: "user", content: userContent }],
1989
2022
  // Always empty. With a toolset available the model picks `tool_use` over
1990
2023
  // producing a summary, leaving summaryText empty.
1991
- tools: []
2024
+ tools: [],
2025
+ // Each summary call carries a unique 100-200KB chunk that is never
2026
+ // re-read (parallel siblings can't read each other's in-flight writes
2027
+ // either) — a cache write here is pure waste.
2028
+ cachePolicy: "oneshot"
1992
2029
  })) {
1993
2030
  if (event.type === "text") {
1994
2031
  summaryText += event.text;
@@ -2389,11 +2426,6 @@ var init_toolResultCap = __esm({
2389
2426
  // src/session.ts
2390
2427
  import fs10 from "fs";
2391
2428
  import path4 from "path";
2392
- function writeFileAtomicSync(file, data) {
2393
- const tmp = `${file}.tmp`;
2394
- fs10.writeFileSync(tmp, data, "utf-8");
2395
- fs10.renameSync(tmp, file);
2396
- }
2397
2429
  function loadSession(state) {
2398
2430
  pruneArchives();
2399
2431
  try {
@@ -2411,6 +2443,12 @@ function loadSession(state) {
2411
2443
  return true;
2412
2444
  }
2413
2445
  } catch {
2446
+ try {
2447
+ const quarantine = `${SESSION_FILE}.corrupt-${Date.now()}`;
2448
+ fs10.renameSync(SESSION_FILE, quarantine);
2449
+ log3.warn(`Session file unreadable \u2014 quarantined to ${quarantine}`);
2450
+ } catch {
2451
+ }
2414
2452
  }
2415
2453
  return false;
2416
2454
  }
@@ -2716,6 +2754,7 @@ var log3, SESSION_FILE, ARCHIVE_DIR, ROTATE_THRESHOLD_BYTES, RETAIN_TAIL_BYTES,
2716
2754
  var init_session = __esm({
2717
2755
  "src/session.ts"() {
2718
2756
  "use strict";
2757
+ init_atomicWrite();
2719
2758
  init_logger();
2720
2759
  init_compaction();
2721
2760
  init_cleanMessages();
@@ -4140,7 +4179,8 @@ var init_uploadImage = __esm({
4140
4179
  ".jpg": "image/jpeg",
4141
4180
  ".jpeg": "image/jpeg",
4142
4181
  ".gif": "image/gif",
4143
- ".webp": "image/webp"
4182
+ ".webp": "image/webp",
4183
+ ".svg": "image/svg+xml"
4144
4184
  };
4145
4185
  hosted = /* @__PURE__ */ new Map();
4146
4186
  }
@@ -4532,7 +4572,8 @@ async function runSubAgent(config) {
4532
4572
  background,
4533
4573
  acquireLock,
4534
4574
  onBackgroundComplete,
4535
- captureArtifacts
4575
+ captureArtifacts,
4576
+ cachePolicy = "run"
4536
4577
  } = config;
4537
4578
  const artifacts = {};
4538
4579
  const bgAbort = background ? new AbortController() : null;
@@ -4627,6 +4668,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
4627
4668
  system: fullSystem,
4628
4669
  messages: cleanMessagesForApi(messages),
4629
4670
  tools: tools2,
4671
+ cachePolicy,
4630
4672
  signal
4631
4673
  },
4632
4674
  {
@@ -6433,7 +6475,7 @@ function load() {
6433
6475
  }
6434
6476
  function save(indices) {
6435
6477
  try {
6436
- fs17.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
6478
+ writeFileAtomicSync(SAMPLE_FILE, JSON.stringify(indices));
6437
6479
  } catch {
6438
6480
  }
6439
6481
  }
@@ -6475,6 +6517,7 @@ var SAMPLE_FILE, cached2;
6475
6517
  var init_sampleCache = __esm({
6476
6518
  "src/subagents/designExpert/data/sampleCache.ts"() {
6477
6519
  "use strict";
6520
+ init_atomicWrite();
6478
6521
  SAMPLE_FILE = ".remy-design-sample.json";
6479
6522
  cached2 = null;
6480
6523
  }
@@ -6725,6 +6768,8 @@ async function runDesignExpert(opts, context) {
6725
6768
  }),
6726
6769
  task: opts.task,
6727
6770
  history: history.length > 0 ? history : void 0,
6771
+ // History-carrying thread re-sent across parent turns — 1h-TTL profile.
6772
+ cachePolicy: "conversation",
6728
6773
  tools: opts.render ? DESIGN_EXPERT_RENDER_TOOLS : DESIGN_EXPERT_TOOLS,
6729
6774
  externalTools: /* @__PURE__ */ new Set(),
6730
6775
  executeTool: (name, input, toolCallId, onLog, sams) => {
@@ -7052,6 +7097,8 @@ var init_productVision = __esm({
7052
7097
  system: getProductVisionPrompt(),
7053
7098
  task: input.task,
7054
7099
  history: history.length > 0 ? history : void 0,
7100
+ // History-carrying thread re-sent across parent turns — 1h-TTL profile.
7101
+ cachePolicy: "conversation",
7055
7102
  tools: VISION_TOOLS,
7056
7103
  externalTools: /* @__PURE__ */ new Set(),
7057
7104
  executeTool: (name, input2, toolCallId, _onLog, sams) => {
@@ -7529,7 +7576,7 @@ var init_scrapeWebUrl2 = __esm({
7529
7576
  function deriveContext(parent, toolCallId) {
7530
7577
  return { ...parent, toolCallId };
7531
7578
  }
7532
- function getToolDefinitions(_onboardingState) {
7579
+ function getToolDefinitions() {
7533
7580
  return ALL_TOOLS.map((t) => t.definition);
7534
7581
  }
7535
7582
  function getToolByName(name) {
@@ -8034,7 +8081,10 @@ async function extractBrand(apiConfig, model) {
8034
8081
  subAgentId: "brandExtractor",
8035
8082
  system: EXTRACT_PROMPT,
8036
8083
  messages: [{ role: "user", content: corpus }],
8037
- tools: []
8084
+ tools: [],
8085
+ // One call per extraction over a corpus that changes with every spec
8086
+ // edit — never re-read, so a cache write is pure waste.
8087
+ cachePolicy: "oneshot"
8038
8088
  })) {
8039
8089
  if (event.type === "text") {
8040
8090
  responseText += event.text;
@@ -8214,11 +8264,9 @@ function pickFont(raw) {
8214
8264
  return out;
8215
8265
  }
8216
8266
  function persistBrand(brand, inputHash) {
8217
- const tmp = `${BRAND_FILE}.tmp`;
8218
- fs20.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
8219
- fs20.renameSync(tmp, BRAND_FILE);
8267
+ writeFileAtomicSync(BRAND_FILE, JSON.stringify(brand, null, 2));
8220
8268
  const cache = { inputHash, generatedAt: Date.now() };
8221
- fs20.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
8269
+ writeFileAtomicSync(CACHE_FILE, JSON.stringify(cache, null, 2));
8222
8270
  }
8223
8271
  function readCache() {
8224
8272
  try {
@@ -8238,6 +8286,7 @@ var init_brandExtraction = __esm({
8238
8286
  "use strict";
8239
8287
  init_api();
8240
8288
  init_assets();
8289
+ init_atomicWrite();
8241
8290
  init_logger();
8242
8291
  init_usageLedger();
8243
8292
  log11 = createLogger("brandExtraction");
@@ -8312,7 +8361,7 @@ async function runTurn(params) {
8312
8361
  toolRegistry,
8313
8362
  onBackgroundComplete
8314
8363
  } = params;
8315
- const tools2 = getToolDefinitions(onboardingState);
8364
+ const tools2 = getToolDefinitions();
8316
8365
  const buildModelOverride = buildModel ? filterModelPicks({ parent: buildModel }).parent : void 0;
8317
8366
  const baseline = resolveModel("parent", state.models, model);
8318
8367
  const parentModel = buildModelOverride ?? baseline;
@@ -8520,6 +8569,9 @@ async function runTurn(params) {
8520
8569
  system,
8521
8570
  messages: cleanMessagesForApi(state.messages),
8522
8571
  tools: tools2,
8572
+ // The parent is one long-lived conversation re-read across turns
8573
+ // with multi-minute gaps — the 1h-TTL profile.
8574
+ cachePolicy: "conversation",
8523
8575
  signal
8524
8576
  },
8525
8577
  {
@@ -8982,7 +9034,7 @@ function loadProjectRoot() {
8982
9034
  ## Project Root
8983
9035
  \`${PROJECT_ROOT}\`
8984
9036
 
8985
- File paths are relative to this directory. Every tool operates here and bash commands run it it.`;
9037
+ File paths are relative to this directory. Every tool operates here and bash commands run in it.`;
8986
9038
  }
8987
9039
  function loadAppIdentity() {
8988
9040
  try {
@@ -9082,21 +9134,9 @@ function buildSystemPrompt(onboardingState) {
9082
9134
  {{compiled/design.md}}
9083
9135
  </design>
9084
9136
 
9085
- <app_files>
9086
- {{compiled/files.md}}
9087
- </app_files>
9088
-
9089
9137
  <interfaces>
9090
9138
  {{compiled/interfaces.md}}
9091
9139
  </interfaces>
9092
-
9093
- <scenarios>
9094
- {{compiled/scenarios.md}}
9095
- </scenarios>
9096
-
9097
- <secrets>
9098
- {{compiled/secrets.md}}
9099
- </secrets>
9100
9140
  </platform_docs>
9101
9141
 
9102
9142
  ${loadSkillsCatalog()}
@@ -9109,12 +9149,12 @@ ${loadSkillsCatalog()}
9109
9149
  {{compiled/msfm.md}}
9110
9150
  </mindstudio_flavored_markdown_spec_docs>
9111
9151
 
9112
- <intake_mode_instructions>
9152
+ ${onboardingState === "intake" ? `<intake_mode_instructions>
9113
9153
  {{static/intake.md}}
9114
- </intake_mode_instructions>
9154
+ </intake_mode_instructions>` : ""}
9115
9155
 
9116
9156
  <spec_authoring_instructions>
9117
- {{static/authoring.md}}
9157
+ ${onboardingState !== void 0 && onboardingState !== "onboardingFinished" ? "{{static/authoring.md}}" : "{{static/spec-maintenance.md}}"}
9118
9158
  </spec_authoring_instructions>
9119
9159
 
9120
9160
  <team>
@@ -9136,7 +9176,7 @@ Tool results generally persist in the conversation until a compaction summarizes
9136
9176
  </conversation_summaries>
9137
9177
 
9138
9178
  <project_onboarding>
9139
- New projects progress through three onboarding states. The user might skip this entirely and jump straight into working on the existing scaffold (which defaults to onboardingFinished), but ideally new projects move through each phase:
9179
+ New projects progress through four onboarding states. The user might skip this entirely and jump straight into working on the existing scaffold (which defaults to onboardingFinished), but ideally new projects move through each phase:
9140
9180
 
9141
9181
  - **intake**: Gathering requirements. The project has scaffold code (a "hello world" starter) but it's not the user's app yet. Focus on understanding what they want to build, not on the existing code. Intake ends with a plan proposal via writePlan.
9142
9182
  - **building**: The user approved the initial plan. The agent is writing the spec and building the app. This can take a while and involves heavy tool use (spec authoring, design expert consultation, code generation, verification, polishing).
@@ -9355,15 +9395,14 @@ var init_attachments = __esm({
9355
9395
  });
9356
9396
 
9357
9397
  // src/headless/planFile.ts
9358
- import { readFileSync, writeFileSync, unlinkSync } from "fs";
9398
+ import { readFileSync, unlinkSync } from "fs";
9359
9399
  function applyPlanFileSideEffect(rawText) {
9360
9400
  if (hasSentinel(rawText, "approvePlan") || hasSentinel(rawText, "approveInitialPlan")) {
9361
9401
  try {
9362
9402
  const plan = readFileSync(PLAN_FILE3, "utf-8");
9363
- writeFileSync(
9403
+ writeFileAtomicSync(
9364
9404
  PLAN_FILE3,
9365
- plan.replace(/^status:\s*pending/m, "status: approved"),
9366
- "utf-8"
9405
+ plan.replace(/^status:\s*pending/m, "status: approved")
9367
9406
  );
9368
9407
  } catch {
9369
9408
  }
@@ -9378,13 +9417,14 @@ var PLAN_FILE3;
9378
9417
  var init_planFile = __esm({
9379
9418
  "src/headless/planFile.ts"() {
9380
9419
  "use strict";
9420
+ init_atomicWrite();
9381
9421
  init_sentinel();
9382
9422
  PLAN_FILE3 = ".remy-plan.md";
9383
9423
  }
9384
9424
  });
9385
9425
 
9386
9426
  // src/headless/stats.ts
9387
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
9427
+ import { readFileSync as readFileSync2 } from "fs";
9388
9428
  function createSessionStats() {
9389
9429
  return {
9390
9430
  messageCount: 0,
@@ -9420,7 +9460,7 @@ function loadPassiveResults() {
9420
9460
  }
9421
9461
  function writeStats(stats, queue, passiveResults) {
9422
9462
  try {
9423
- writeFileSync2(
9463
+ writeFileAtomicSync(
9424
9464
  STATS_FILE,
9425
9465
  JSON.stringify({
9426
9466
  ...stats,
@@ -9435,6 +9475,7 @@ var STATS_FILE;
9435
9475
  var init_stats = __esm({
9436
9476
  "src/headless/stats.ts"() {
9437
9477
  "use strict";
9478
+ init_atomicWrite();
9438
9479
  STATS_FILE = ".remy-stats.json";
9439
9480
  }
9440
9481
  });
@@ -9688,9 +9729,7 @@ var init_headless = __esm({
9688
9729
  name: "compactConversation",
9689
9730
  input: {},
9690
9731
  startedAt: Date.now(),
9691
- background: true,
9692
- uiOnly: true,
9693
- result: "Compaction started in the background."
9732
+ uiOnly: true
9694
9733
  }
9695
9734
  ]
9696
9735
  });
@@ -9699,15 +9738,7 @@ var init_headless = __esm({
9699
9738
  type: "tool_start",
9700
9739
  id,
9701
9740
  name: "compactConversation",
9702
- input: {},
9703
- background: true
9704
- });
9705
- this.onEvent({
9706
- type: "tool_done",
9707
- id,
9708
- name: "compactConversation",
9709
- result: "Compaction started in the background.",
9710
- isError: false
9741
+ input: {}
9711
9742
  });
9712
9743
  }
9713
9744
  } else {
@@ -9716,11 +9747,31 @@ var init_headless = __esm({
9716
9747
  if (this.syntheticCompactionId) {
9717
9748
  const id = this.syntheticCompactionId;
9718
9749
  this.syntheticCompactionId = null;
9719
- this.onBackgroundComplete(
9750
+ const result = event.error ? `Error: ${event.error}` : formatSummariesResult(event.summaries ?? []);
9751
+ const isError = !!event.error;
9752
+ for (let i = this.state.messages.length - 1; i >= 0; i--) {
9753
+ const msg = this.state.messages[i];
9754
+ if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
9755
+ continue;
9756
+ }
9757
+ const block = msg.content.find(
9758
+ (b) => b.type === "tool" && b.id === id
9759
+ );
9760
+ if (block && block.type === "tool") {
9761
+ block.result = result;
9762
+ block.isError = isError;
9763
+ block.completedAt = Date.now();
9764
+ saveSession(this.state);
9765
+ break;
9766
+ }
9767
+ }
9768
+ this.onEvent({
9769
+ type: "tool_done",
9720
9770
  id,
9721
- "compactConversation",
9722
- event.error ? `Error: ${event.error}` : formatSummariesResult(event.summaries ?? [])
9723
- );
9771
+ name: "compactConversation",
9772
+ result,
9773
+ isError
9774
+ });
9724
9775
  }
9725
9776
  this.sessionStats.compactionInProgress = false;
9726
9777
  if (!event.error) {
@@ -10958,7 +11009,7 @@ function App({ apiConfig, model }) {
10958
11009
  return s;
10959
11010
  });
10960
11011
  const [sessionRestored] = useState2(() => agentState.messages.length > 0);
10961
- const [system] = useState2(() => buildSystemPrompt());
11012
+ const [system] = useState2(() => buildSystemPrompt("onboardingFinished"));
10962
11013
  useInput((input, key) => {
10963
11014
  if (key.escape && isRunning && abortRef.current) {
10964
11015
  abortRef.current.abort();