@mindstudio-ai/remy 0.1.235 → 0.1.237

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
@@ -421,6 +421,13 @@ var MODEL_SURFACES = {
421
421
  modelType: "text",
422
422
  userPickable: true
423
423
  },
424
+ specSync: {
425
+ default: "claude-5-sonnet",
426
+ label: "Spec Sync Agent",
427
+ description: "Keeps your spec in sync with the code as you build, updating the affected sections in the background after changes.",
428
+ modelType: "text",
429
+ userPickable: true
430
+ },
424
431
  imageGeneration: {
425
432
  default: "seedream-4.5",
426
433
  label: "Image Generation",
@@ -3231,6 +3238,7 @@ async function runSubAgent(config) {
3231
3238
  requestId,
3232
3239
  history,
3233
3240
  background,
3241
+ acquireLock,
3234
3242
  onBackgroundComplete,
3235
3243
  captureArtifacts
3236
3244
  } = config;
@@ -3623,7 +3631,15 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
3623
3631
  agentName: subAgentId || "agent",
3624
3632
  task
3625
3633
  });
3626
- wrapRun().then((finalResult) => {
3634
+ const runDetached = async () => {
3635
+ const release = acquireLock ? await acquireLock() : null;
3636
+ try {
3637
+ return await wrapRun();
3638
+ } finally {
3639
+ release?.();
3640
+ }
3641
+ };
3642
+ runDetached().then((finalResult) => {
3627
3643
  toolRegistry?.unregister(parentToolId);
3628
3644
  onBackgroundComplete?.(finalResult);
3629
3645
  }).catch((err) => {
@@ -5571,6 +5587,102 @@ var codeSanityCheckTool = {
5571
5587
  }
5572
5588
  };
5573
5589
 
5590
+ // src/subagents/specSync/tools.ts
5591
+ var SPEC_SYNC_TOOLS = [
5592
+ ...COMMON_READ_TOOLS,
5593
+ readSpecTool.definition,
5594
+ editSpecTool.definition,
5595
+ writeSpecTool.definition
5596
+ ];
5597
+
5598
+ // src/subagents/specSync/lock.ts
5599
+ var lockQueue2 = Promise.resolve();
5600
+ function acquireSpecSyncLock() {
5601
+ let release;
5602
+ const next = new Promise((res) => {
5603
+ release = res;
5604
+ });
5605
+ const wait = lockQueue2;
5606
+ lockQueue2 = next;
5607
+ return wait.then(() => release);
5608
+ }
5609
+
5610
+ // src/subagents/specSync/index.ts
5611
+ var BASE_PROMPT5 = readAsset("subagents/specSync", "prompt.md");
5612
+ var MSFM_DOCS = `<mindstudio_flavored_markdown_spec_docs>
5613
+ ${readAsset(
5614
+ "prompt",
5615
+ "compiled/msfm.md"
5616
+ )}
5617
+ </mindstudio_flavored_markdown_spec_docs>`;
5618
+ var specSyncTool = {
5619
+ clearable: false,
5620
+ backgroundOnly: true,
5621
+ definition: {
5622
+ name: "specSync",
5623
+ description: "Reconcile the spec to bring it in line with code changes you have made. Provide a brief, bulleted list of what changed and why; it finds the affected spec sections and updates them to match. Always runs in the background \u2014 it returns immediately and reports back when done.",
5624
+ inputSchema: {
5625
+ type: "object",
5626
+ properties: {
5627
+ task: {
5628
+ type: "string",
5629
+ description: "What you changed in the code and why, in plain language \u2014 the way you would explain it to a teammate. The specialist reads the spec itself and decides which sections to update; you do not need to name files or spec locations."
5630
+ }
5631
+ },
5632
+ required: ["task"]
5633
+ }
5634
+ },
5635
+ async execute(input, context) {
5636
+ if (!context) {
5637
+ return "Error: spec sync requires execution context";
5638
+ }
5639
+ if (context.onboardingState !== "onboardingFinished") {
5640
+ return "Spec sync runs only after the build is finished (onboardingFinished). During intake and the initial build you author the spec directly, so there is nothing to reconcile yet.";
5641
+ }
5642
+ const specIndex = loadSpecIndex();
5643
+ const parts = [
5644
+ BASE_PROMPT5,
5645
+ loadPlatformBrief(),
5646
+ MSFM_DOCS,
5647
+ "<!-- cache_breakpoint -->"
5648
+ ];
5649
+ if (specIndex) {
5650
+ parts.push(specIndex);
5651
+ }
5652
+ const system = parts.join("\n\n");
5653
+ const result = await runSubAgent({
5654
+ system,
5655
+ task: input.task,
5656
+ tools: SPEC_SYNC_TOOLS,
5657
+ externalTools: /* @__PURE__ */ new Set(),
5658
+ executeTool: (name, toolInput) => executeTool(name, toolInput, context),
5659
+ apiConfig: context.apiConfig,
5660
+ model: resolveModel("specSync", context.models, context.model),
5661
+ subAgentId: "specSync",
5662
+ signal: context.signal,
5663
+ parentToolId: context.toolCallId,
5664
+ requestId: context.requestId,
5665
+ onEvent: context.onEvent,
5666
+ resolveExternalTool: context.resolveExternalTool,
5667
+ toolRegistry: context.toolRegistry,
5668
+ // Always background + serialized: never blocks Remy's turn, and two
5669
+ // reconciliations queue (FIFO) instead of running at once.
5670
+ background: true,
5671
+ acquireLock: acquireSpecSyncLock,
5672
+ onBackgroundComplete: (bgResult) => {
5673
+ context.onBackgroundComplete?.(
5674
+ context.toolCallId,
5675
+ "specSync",
5676
+ bgResult.text,
5677
+ bgResult.messages
5678
+ );
5679
+ }
5680
+ });
5681
+ context.subAgentMessages?.set(context.toolCallId, result.messages);
5682
+ return result.text;
5683
+ }
5684
+ };
5685
+
5574
5686
  // src/tools/common/scrapeWebUrl.ts
5575
5687
  var scrapeWebUrlTool = {
5576
5688
  clearable: false,
@@ -5739,6 +5851,8 @@ var ALL_TOOLS = [
5739
5851
  productVisionTool,
5740
5852
  codeSanityCheckTool,
5741
5853
  copyEditorTool,
5854
+ specSyncTool,
5855
+ // no-ops until onboardingFinished; kept here so the tool list stays cache-stable
5742
5856
  buildOverviewTool,
5743
5857
  compactConversationTool,
5744
5858
  // Post-onboarding
@@ -5776,6 +5890,7 @@ var SUBAGENT_TOOL_NAMES = /* @__PURE__ */ new Set([
5776
5890
  "productVision",
5777
5891
  "codeSanityCheck",
5778
5892
  "copyEditor",
5893
+ "specSync",
5779
5894
  "runAutomatedBrowserTest",
5780
5895
  "askMindStudioSdk"
5781
5896
  ]);
@@ -6860,6 +6975,11 @@ var USER_BLOCKING_EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
6860
6975
  "presentPublishPlan",
6861
6976
  "confirmDestructiveAction"
6862
6977
  ]);
6978
+ function isBackgroundCall(tc) {
6979
+ return Boolean(
6980
+ tc.input?.background || getToolByName(tc.name)?.backgroundOnly
6981
+ );
6982
+ }
6863
6983
  function createAgentState() {
6864
6984
  return { messages: [] };
6865
6985
  }
@@ -6912,7 +7032,10 @@ async function runTurn(params) {
6912
7032
  onEvent({
6913
7033
  type: "user_message",
6914
7034
  text: userMessage,
6915
- hidden: hidden || void 0
7035
+ hidden: hidden || void 0,
7036
+ // Include attachments so the live event can render a queued voice/image/file
7037
+ // bubble; a voice message has empty text and the transcript lives here.
7038
+ ...hasAttachments && { attachments }
6916
7039
  });
6917
7040
  const isFirstMessage = state.messages.filter((m) => m.role === "user").length === 1;
6918
7041
  const STATUS_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
@@ -7124,16 +7247,18 @@ async function runTurn(params) {
7124
7247
  break;
7125
7248
  }
7126
7249
  case "tool_use": {
7250
+ const tool = getToolByName(event.name);
7127
7251
  contentBlocks.push({
7128
7252
  type: "tool",
7129
7253
  id: event.id,
7130
7254
  name: event.name,
7131
7255
  input: event.input,
7132
7256
  startedAt: event.ts,
7133
- ...event.input.background && { background: true }
7257
+ ...(event.input.background || tool?.backgroundOnly) && {
7258
+ background: true
7259
+ }
7134
7260
  });
7135
7261
  const acc = toolInputAccumulators.get(event.id);
7136
- const tool = getToolByName(event.name);
7137
7262
  const wasStreamed = acc?.started ?? false;
7138
7263
  const isInputStreaming = !!tool?.streaming?.partialInput;
7139
7264
  log13.info("Tool received", {
@@ -7366,7 +7491,7 @@ async function runTurn(params) {
7366
7491
  toolRegistry?.register(entry);
7367
7492
  run(tc.input);
7368
7493
  const r = await resultPromise;
7369
- if (!tc.input.background) {
7494
+ if (!isBackgroundCall(tc)) {
7370
7495
  toolRegistry?.unregister(tc.id);
7371
7496
  }
7372
7497
  log13.info("Tool completed", {
@@ -8030,7 +8155,16 @@ var HeadlessSession = class {
8030
8155
  this.emit("turn_started", {}, rid);
8031
8156
  return;
8032
8157
  case "user_message":
8033
- this.emit("user_message", { text: e.text }, rid);
8158
+ this.emit(
8159
+ "user_message",
8160
+ {
8161
+ text: e.text,
8162
+ // Forward attachments so queued voice/image/file sends render live;
8163
+ // otherwise the bubble is blank until a get_history refresh.
8164
+ ...e.attachments && { attachments: e.attachments }
8165
+ },
8166
+ rid
8167
+ );
8034
8168
  return;
8035
8169
  // Terminal events — translate to `completed`.
8036
8170
  // Post-turn queue drain happens in handleMessage AFTER runTurn returns,
package/dist/index.js CHANGED
@@ -1810,7 +1810,7 @@ var init_compaction = __esm({
1810
1810
  "use strict";
1811
1811
  init_api();
1812
1812
  init_assets();
1813
- init_tools7();
1813
+ init_tools8();
1814
1814
  init_logger();
1815
1815
  init_usageLedger();
1816
1816
  log2 = createLogger("compaction");
@@ -2037,6 +2037,13 @@ var init_surfaces = __esm({
2037
2037
  modelType: "text",
2038
2038
  userPickable: true
2039
2039
  },
2040
+ specSync: {
2041
+ default: "claude-5-sonnet",
2042
+ label: "Spec Sync Agent",
2043
+ description: "Keeps your spec in sync with the code as you build, updating the affected sections in the background after changes.",
2044
+ modelType: "text",
2045
+ userPickable: true
2046
+ },
2040
2047
  imageGeneration: {
2041
2048
  default: "seedream-4.5",
2042
2049
  label: "Image Generation",
@@ -2356,7 +2363,7 @@ var init_trigger = __esm({
2356
2363
  "use strict";
2357
2364
  init_compaction();
2358
2365
  init_prompt();
2359
- init_tools7();
2366
+ init_tools8();
2360
2367
  init_logger();
2361
2368
  init_surfaces();
2362
2369
  log4 = createLogger("compaction:trigger");
@@ -3883,6 +3890,7 @@ async function runSubAgent(config) {
3883
3890
  requestId,
3884
3891
  history,
3885
3892
  background,
3893
+ acquireLock,
3886
3894
  onBackgroundComplete,
3887
3895
  captureArtifacts
3888
3896
  } = config;
@@ -4275,7 +4283,15 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
4275
4283
  agentName: subAgentId || "agent",
4276
4284
  task
4277
4285
  });
4278
- wrapRun().then((finalResult) => {
4286
+ const runDetached = async () => {
4287
+ const release = acquireLock ? await acquireLock() : null;
4288
+ try {
4289
+ return await wrapRun();
4290
+ } finally {
4291
+ release?.();
4292
+ }
4293
+ };
4294
+ runDetached().then((finalResult) => {
4279
4295
  toolRegistry?.unregister(parentToolId);
4280
4296
  onBackgroundComplete?.(finalResult);
4281
4297
  }).catch((err) => {
@@ -5615,7 +5631,7 @@ var init_copyEditor = __esm({
5615
5631
  init_assets();
5616
5632
  init_runner();
5617
5633
  init_context();
5618
- init_tools7();
5634
+ init_tools8();
5619
5635
  init_tools3();
5620
5636
  init_surfaces();
5621
5637
  BASE_PROMPT2 = readAsset("subagents/copyEditor", "prompt.md");
@@ -5714,7 +5730,7 @@ var tools, DESIGN_EXPERT_TOOLS;
5714
5730
  var init_tools4 = __esm({
5715
5731
  "src/subagents/designExpert/tools/index.ts"() {
5716
5732
  "use strict";
5717
- init_tools7();
5733
+ init_tools8();
5718
5734
  init_tools2();
5719
5735
  init_searchGoogle2();
5720
5736
  init_scrapeWebUrl();
@@ -6080,7 +6096,7 @@ var DESCRIPTION, RENDER_WRITE_TOOL_NAMES, DESIGN_EXPERT_RENDER_TOOLS, designExpe
6080
6096
  var init_designExpert = __esm({
6081
6097
  "src/subagents/designExpert/index.ts"() {
6082
6098
  "use strict";
6083
- init_tools7();
6099
+ init_tools8();
6084
6100
  init_runner();
6085
6101
  init_tools4();
6086
6102
  init_tools2();
@@ -6335,7 +6351,7 @@ var productVisionTool;
6335
6351
  var init_productVision = __esm({
6336
6352
  "src/subagents/productVision/index.ts"() {
6337
6353
  "use strict";
6338
- init_tools7();
6354
+ init_tools8();
6339
6355
  init_runner();
6340
6356
  init_tools5();
6341
6357
  init_tools2();
@@ -6471,7 +6487,7 @@ var init_codeSanityCheck = __esm({
6471
6487
  init_assets();
6472
6488
  init_runner();
6473
6489
  init_context();
6474
- init_tools7();
6490
+ init_tools8();
6475
6491
  init_tools6();
6476
6492
  init_surfaces();
6477
6493
  BASE_PROMPT4 = readAsset("subagents/codeSanityCheck", "prompt.md");
@@ -6525,6 +6541,131 @@ var init_codeSanityCheck = __esm({
6525
6541
  }
6526
6542
  });
6527
6543
 
6544
+ // src/subagents/specSync/tools.ts
6545
+ var SPEC_SYNC_TOOLS;
6546
+ var init_tools7 = __esm({
6547
+ "src/subagents/specSync/tools.ts"() {
6548
+ "use strict";
6549
+ init_tools2();
6550
+ init_readSpec();
6551
+ init_editSpec();
6552
+ init_writeSpec();
6553
+ SPEC_SYNC_TOOLS = [
6554
+ ...COMMON_READ_TOOLS,
6555
+ readSpecTool.definition,
6556
+ editSpecTool.definition,
6557
+ writeSpecTool.definition
6558
+ ];
6559
+ }
6560
+ });
6561
+
6562
+ // src/subagents/specSync/lock.ts
6563
+ function acquireSpecSyncLock() {
6564
+ let release;
6565
+ const next = new Promise((res) => {
6566
+ release = res;
6567
+ });
6568
+ const wait = lockQueue2;
6569
+ lockQueue2 = next;
6570
+ return wait.then(() => release);
6571
+ }
6572
+ var lockQueue2;
6573
+ var init_lock = __esm({
6574
+ "src/subagents/specSync/lock.ts"() {
6575
+ "use strict";
6576
+ lockQueue2 = Promise.resolve();
6577
+ }
6578
+ });
6579
+
6580
+ // src/subagents/specSync/index.ts
6581
+ var BASE_PROMPT5, MSFM_DOCS, specSyncTool;
6582
+ var init_specSync = __esm({
6583
+ "src/subagents/specSync/index.ts"() {
6584
+ "use strict";
6585
+ init_assets();
6586
+ init_runner();
6587
+ init_context();
6588
+ init_tools8();
6589
+ init_tools7();
6590
+ init_lock();
6591
+ init_surfaces();
6592
+ BASE_PROMPT5 = readAsset("subagents/specSync", "prompt.md");
6593
+ MSFM_DOCS = `<mindstudio_flavored_markdown_spec_docs>
6594
+ ${readAsset(
6595
+ "prompt",
6596
+ "compiled/msfm.md"
6597
+ )}
6598
+ </mindstudio_flavored_markdown_spec_docs>`;
6599
+ specSyncTool = {
6600
+ clearable: false,
6601
+ backgroundOnly: true,
6602
+ definition: {
6603
+ name: "specSync",
6604
+ description: "Reconcile the spec to bring it in line with code changes you have made. Provide a brief, bulleted list of what changed and why; it finds the affected spec sections and updates them to match. Always runs in the background \u2014 it returns immediately and reports back when done.",
6605
+ inputSchema: {
6606
+ type: "object",
6607
+ properties: {
6608
+ task: {
6609
+ type: "string",
6610
+ description: "What you changed in the code and why, in plain language \u2014 the way you would explain it to a teammate. The specialist reads the spec itself and decides which sections to update; you do not need to name files or spec locations."
6611
+ }
6612
+ },
6613
+ required: ["task"]
6614
+ }
6615
+ },
6616
+ async execute(input, context) {
6617
+ if (!context) {
6618
+ return "Error: spec sync requires execution context";
6619
+ }
6620
+ if (context.onboardingState !== "onboardingFinished") {
6621
+ return "Spec sync runs only after the build is finished (onboardingFinished). During intake and the initial build you author the spec directly, so there is nothing to reconcile yet.";
6622
+ }
6623
+ const specIndex = loadSpecIndex();
6624
+ const parts = [
6625
+ BASE_PROMPT5,
6626
+ loadPlatformBrief(),
6627
+ MSFM_DOCS,
6628
+ "<!-- cache_breakpoint -->"
6629
+ ];
6630
+ if (specIndex) {
6631
+ parts.push(specIndex);
6632
+ }
6633
+ const system = parts.join("\n\n");
6634
+ const result = await runSubAgent({
6635
+ system,
6636
+ task: input.task,
6637
+ tools: SPEC_SYNC_TOOLS,
6638
+ externalTools: /* @__PURE__ */ new Set(),
6639
+ executeTool: (name, toolInput) => executeTool(name, toolInput, context),
6640
+ apiConfig: context.apiConfig,
6641
+ model: resolveModel("specSync", context.models, context.model),
6642
+ subAgentId: "specSync",
6643
+ signal: context.signal,
6644
+ parentToolId: context.toolCallId,
6645
+ requestId: context.requestId,
6646
+ onEvent: context.onEvent,
6647
+ resolveExternalTool: context.resolveExternalTool,
6648
+ toolRegistry: context.toolRegistry,
6649
+ // Always background + serialized: never blocks Remy's turn, and two
6650
+ // reconciliations queue (FIFO) instead of running at once.
6651
+ background: true,
6652
+ acquireLock: acquireSpecSyncLock,
6653
+ onBackgroundComplete: (bgResult) => {
6654
+ context.onBackgroundComplete?.(
6655
+ context.toolCallId,
6656
+ "specSync",
6657
+ bgResult.text,
6658
+ bgResult.messages
6659
+ );
6660
+ }
6661
+ });
6662
+ context.subAgentMessages?.set(context.toolCallId, result.messages);
6663
+ return result.text;
6664
+ }
6665
+ };
6666
+ }
6667
+ });
6668
+
6528
6669
  // src/tools/common/scrapeWebUrl.ts
6529
6670
  var scrapeWebUrlTool;
6530
6671
  var init_scrapeWebUrl2 = __esm({
@@ -6708,7 +6849,7 @@ function executeTool(name, input, context) {
6708
6849
  return tool.execute(input, context);
6709
6850
  }
6710
6851
  var ALL_TOOLS, CLEARABLE_TOOLS, SUBAGENT_TOOL_NAMES;
6711
- var init_tools7 = __esm({
6852
+ var init_tools8 = __esm({
6712
6853
  "src/tools/index.ts"() {
6713
6854
  "use strict";
6714
6855
  init_readSpec();
@@ -6744,6 +6885,7 @@ var init_tools7 = __esm({
6744
6885
  init_productVision();
6745
6886
  init_codeSanityCheck();
6746
6887
  init_copyEditor();
6888
+ init_specSync();
6747
6889
  init_scrapeWebUrl2();
6748
6890
  init_writeBuildOverview();
6749
6891
  ALL_TOOLS = [
@@ -6759,6 +6901,8 @@ var init_tools7 = __esm({
6759
6901
  productVisionTool,
6760
6902
  codeSanityCheckTool,
6761
6903
  copyEditorTool,
6904
+ specSyncTool,
6905
+ // no-ops until onboardingFinished; kept here so the tool list stays cache-stable
6762
6906
  buildOverviewTool,
6763
6907
  compactConversationTool,
6764
6908
  // Post-onboarding
@@ -6796,6 +6940,7 @@ var init_tools7 = __esm({
6796
6940
  "productVision",
6797
6941
  "codeSanityCheck",
6798
6942
  "copyEditor",
6943
+ "specSync",
6799
6944
  "runAutomatedBrowserTest",
6800
6945
  "askMindStudioSdk"
6801
6946
  ]);
@@ -7562,6 +7707,11 @@ function getToolCalls(blocks) {
7562
7707
  (b) => b.type === "tool"
7563
7708
  );
7564
7709
  }
7710
+ function isBackgroundCall(tc) {
7711
+ return Boolean(
7712
+ tc.input?.background || getToolByName(tc.name)?.backgroundOnly
7713
+ );
7714
+ }
7565
7715
  function createAgentState() {
7566
7716
  return { messages: [] };
7567
7717
  }
@@ -7614,7 +7764,10 @@ async function runTurn(params) {
7614
7764
  onEvent({
7615
7765
  type: "user_message",
7616
7766
  text: userMessage,
7617
- hidden: hidden || void 0
7767
+ hidden: hidden || void 0,
7768
+ // Include attachments so the live event can render a queued voice/image/file
7769
+ // bubble; a voice message has empty text and the transcript lives here.
7770
+ ...hasAttachments && { attachments }
7618
7771
  });
7619
7772
  const isFirstMessage = state.messages.filter((m) => m.role === "user").length === 1;
7620
7773
  const STATUS_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
@@ -7826,16 +7979,18 @@ async function runTurn(params) {
7826
7979
  break;
7827
7980
  }
7828
7981
  case "tool_use": {
7982
+ const tool = getToolByName(event.name);
7829
7983
  contentBlocks.push({
7830
7984
  type: "tool",
7831
7985
  id: event.id,
7832
7986
  name: event.name,
7833
7987
  input: event.input,
7834
7988
  startedAt: event.ts,
7835
- ...event.input.background && { background: true }
7989
+ ...(event.input.background || tool?.backgroundOnly) && {
7990
+ background: true
7991
+ }
7836
7992
  });
7837
7993
  const acc = toolInputAccumulators.get(event.id);
7838
- const tool = getToolByName(event.name);
7839
7994
  const wasStreamed = acc?.started ?? false;
7840
7995
  const isInputStreaming = !!tool?.streaming?.partialInput;
7841
7996
  log12.info("Tool received", {
@@ -8068,7 +8223,7 @@ async function runTurn(params) {
8068
8223
  toolRegistry?.register(entry);
8069
8224
  run(tc.input);
8070
8225
  const r = await resultPromise;
8071
- if (!tc.input.background) {
8226
+ if (!isBackgroundCall(tc)) {
8072
8227
  toolRegistry?.unregister(tc.id);
8073
8228
  }
8074
8229
  log12.info("Tool completed", {
@@ -8135,7 +8290,7 @@ var init_agent = __esm({
8135
8290
  "src/agent.ts"() {
8136
8291
  "use strict";
8137
8292
  init_api();
8138
- init_tools7();
8293
+ init_tools8();
8139
8294
  init_session();
8140
8295
  init_logger();
8141
8296
  init_usageLedger();
@@ -8143,7 +8298,7 @@ var init_agent = __esm({
8143
8298
  init_statusWatcher();
8144
8299
  init_errors();
8145
8300
  init_cleanMessages();
8146
- init_tools7();
8301
+ init_tools8();
8147
8302
  init_sentinel();
8148
8303
  init_trigger2();
8149
8304
  init_surfaces();
@@ -8885,7 +9040,16 @@ var init_headless = __esm({
8885
9040
  this.emit("turn_started", {}, rid);
8886
9041
  return;
8887
9042
  case "user_message":
8888
- this.emit("user_message", { text: e.text }, rid);
9043
+ this.emit(
9044
+ "user_message",
9045
+ {
9046
+ text: e.text,
9047
+ // Forward attachments so queued voice/image/file sends render live;
9048
+ // otherwise the bubble is blank until a get_history refresh.
9049
+ ...e.attachments && { attachments: e.attachments }
9050
+ },
9051
+ rid
9052
+ );
8889
9053
  return;
8890
9054
  // Terminal events — translate to `completed`.
8891
9055
  // Post-turn queue drain happens in handleMessage AFTER runTurn returns,
@@ -89,6 +89,6 @@ When generated code exists in `dist/`, you have both spec tools and code tools.
89
89
 
90
90
  **Key principle: spec and code stay in sync.**
91
91
  - When editing the spec, also update the affected code in the same turn.
92
- - When the user asks for a code change that represents a behavioral change, also update the spec.
92
+ - When you make a code change that alters what the app does, the spec has to catch up too - but this must happen infrequently and in the background so it doesn't block your work and iteration with the user. Use the specSync tool to periodically update the spec after big/meaningful chunks of work, avoid editing the spec files directly. Remember, it is completely fine and normal for there to be some drift between the code and spec - you do not need to sync after every conversation turn. You can batch many changes together - you can trust that the specSync tool will investigate the changes and reconcile on its own.
93
93
  - Spec tools work on `src/` files: `readSpec` to read (do this before editing), `editSpec` for targeted find/replace edits (same `old_string`/`new_string` model as `editFile`), `writeSpec` for a full-file write, `listSpecFiles` to see what exists.
94
94
  - Code tools (`readFile`, `writeFile`, `editFile`, etc.) work on `dist/` and other project files.
@@ -5,8 +5,7 @@
5
5
  4. **Iterate.** If something fails, read the error, diagnose the root cause, and try a different approach.
6
6
 
7
7
  ## Principles
8
- - The spec in `src/` is the source of truth. When in doubt, consult the spec before making code changes. When behavior changes, update the spec first.
9
- - Always keep the spec up to date after making changes to the code, especially when adding features or building things from the roadmap.
8
+ - The spec in `src/` is the source of truth for what the app does: consult it before making behavioral code changes, and keep it in sync as the app evolves by dispatching periodic requests to the specSync tool after meaningful changes. Some amount of drift is fine and inevitable - your priority is working efficiently with the user, let the specSync tool handle things in the background.
10
9
  - Keep `src/overview.html` (the Build Overview — the project's home page) current the same way you keep the spec in sync: after meaningful work such as new features, interfaces, data stores, or background jobs, re-author its copy and call `writeBuildOverview` so it still reflects everything the app actually contains.
11
10
  - Change only what the task requires. Match existing styles. Keep solutions simple.
12
11
  - Read files before editing them. Understand the context before making changes.
@@ -40,6 +40,12 @@ Always consult the code sanity check before writing code in initialCodegen with
40
40
 
41
41
  Your editor — a design expert for words. Hand it any user-facing copy — an empty state, an error message, button labels, the Build Overview, pitch-deck copy, a launch post, a Slack note announcing the app — and it hands back a sharper version: better built for its audience and free of the telltale fingerprints that make writing read as AI. You're good at deciding *what* to say; it's great at making it land. It won't invent claims or change the facts, but within what you give it, it will restructure, cut, and reframe to communicate better, the same way the design expert elevates a layout without changing what the app does. Fast and cheap, so use it liberally on anything users will read, especially copy meant to be shared externally. Give it the text plus what it's for (the medium, the audience).
42
42
 
43
+ ### Spec Sync Agent (`specSync`)
44
+
45
+ Your spec keeper. Once the app is built and you're iterating on it, whenever you make code changes that alter what the app does, hand it a brief, plain-language description of what you changed and why (prefer bullet points, batch multiple changes into one invocation). It finds the affected sections of the spec in `src/` and updates them to match what has been built. It reads the spec itself and decides what to touch, so you don't need to name files or locations.
46
+
47
+ It always runs in the background: it returns immediately and you keep working while it reconciles, and you'll get notified when the updated spec lands later. So don't hunt through spec files to sync them yourself and don't wait on it — hand off and move on. You decide when the spec has drifted enough to be worth a hand-off (after a meaningful change, or a batch of them - you do not need to invoke this after every change or conversation turn - some amount of drift between code and spec is completely normal and acceptable).
48
+
43
49
  ### QA (`runAutomatedBrowserTest`)
44
50
 
45
51
  For verifying complex stateful interactions: multi-step form submissions, auth flows, real-time updates, flows that require specific data/role setup. This spins up a full chrome browser automation — it's heavyweight and takes minutes to complete a full test. Do not use it for basic rendering or navigation checks. If you can verify something with a screenshot or by reading the code, do that instead. Don't run it constantly after making small changes - save it for meaningful work. Run a scenario first to seed test data and set user roles. The user is able to watch QA work on their screen via a live browser preview - the cursor will move, type, etc - so you can also use this to demo functionality to the user and help them understand how to use their app.
@@ -72,4 +78,4 @@ You can only background the following two tasks, unless the user specifically as
72
78
  - `productVision` seeding the intiial roadmap after writing the spec for the first time or updating the roadmap after large work sessions. This task takes a while and we can allow the user to continue building while it happens in the background.
73
79
  - After writing the spec, once you have finalized the shape of the app, ask `visualDesignExpert` to create an icon and an open graph shring image for the app, then set them with `setProjectMetadata`, alongside the app's name and short description.
74
80
 
75
- Do not background any other tasks.
81
+ Do not background any other tasks. Be aware that sometimes tools like specSync will background on their own - this is not something that is within your control.
@@ -0,0 +1,16 @@
1
+ You keep the app's spec in `src/` aligned with the code. The developer has just made code changes and is handing you a description of what changed and why. Your one job is to find the spec sections that describe that behavior and update them so the spec matches the code again.
2
+
3
+ Work fast. This is reconciliation, not review.
4
+
5
+ ## How to work
6
+
7
+ 1. **Trust the developer's brief.** The developer knows what it changed and why. Take the brief as ground truth for the change. Do not re-derive it, second-guess whether the change was correct, or audit the rest of the app.
8
+ 2. **Locate the affected spec.** The project's spec files are already listed for you below, each with a one-line description. Use `grep`/`readSpec` to find where the changed behavior is documented, and read only the sections you are about to touch. If the brief mentions something not yet in any spec (a new feature, table, role, interface, background job), add it to the spec file where it belongs.
9
+ 3. **Update it.** Use `editSpec` for targeted changes (read the section first so your `old_string` matches), or `writeSpec` for a full rewrite of a file. Read a spec file with `readSpec` before editing it.
10
+ 4. **Finish.** Once the spec reflects the change, you are done. Do not keep looking for more to fix, do not polish unrelated sections, do not verify the code beyond a quick read if and only if the brief is genuinely ambiguous.
11
+
12
+ ## What to write
13
+
14
+ The spec is written in MindStudio-Flavored Markdown (MSFM); the format reference is included below. Follow it, and match the structure and voice already in the spec files. Write prose in plain, human language, the way you would describe the app to a colleague. Keep exact technical values (field names, routes, roles, limits, config) in annotations, not in the prose. Do not invent detail the brief and code do not support: describe what actually exists now, nothing more.
15
+
16
+ You edit the spec only. You never edit code.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.235",
3
+ "version": "0.1.237",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",