@mindstudio-ai/remy 0.1.253 → 0.1.255

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
@@ -914,6 +914,54 @@ ${loadPlanStatus(onboardingState)}
914
914
  return resolveIncludes(template);
915
915
  }
916
916
 
917
+ // src/automatedActions/sentinel.ts
918
+ function sentinel(name) {
919
+ return `@@automated::${name}@@`;
920
+ }
921
+ function automatedMessage(name, body) {
922
+ return body ? `${sentinel(name)}
923
+ ${body}` : sentinel(name);
924
+ }
925
+ function hasSentinel(text, name) {
926
+ return text.startsWith(sentinel(name));
927
+ }
928
+ function isAutomatedMessage(text) {
929
+ return text.startsWith("@@automated::");
930
+ }
931
+ function parseSentinel(text) {
932
+ const match = text.match(/^@@automated::(\w+)@@(.*)/s);
933
+ if (!match) {
934
+ return null;
935
+ }
936
+ return { name: match[1], remainder: match[2] };
937
+ }
938
+ function stripSentinelLine(text) {
939
+ return text.replace(/^@@automated::[^@]*@@[^\n]*\n?/, "");
940
+ }
941
+ function buildBackgroundResultsMessage(results) {
942
+ const xml = results.map(
943
+ (r) => `<tool_result id="${r.toolCallId}" name="${r.name}">
944
+ ${r.result}
945
+ </tool_result>`
946
+ ).join("\n\n");
947
+ const plural = results.length > 1 ? "s" : "";
948
+ const body = `This is an automated message containing the result${plural} of ${results.length > 1 ? "tool calls" : "a tool call"} that ${results.length > 1 ? "have" : "has"} been working in the background. This is not a direct message from the user.
949
+ <background_results>
950
+ ${xml}
951
+ </background_results>`;
952
+ return automatedMessage("background_results", body);
953
+ }
954
+ function mergeBackgroundResultsMessages(messages) {
955
+ const results = [];
956
+ const toolRe = /<tool_result id="([^"]+)" name="([^"]+)">\n([\s\S]*?)\n<\/tool_result>/g;
957
+ for (const msg of messages) {
958
+ for (const m of msg.matchAll(toolRe)) {
959
+ results.push({ toolCallId: m[1], name: m[2], result: m[3] });
960
+ }
961
+ }
962
+ return buildBackgroundResultsMessage(results);
963
+ }
964
+
917
965
  // src/tools/spec/readSpec.ts
918
966
  import fs5 from "fs/promises";
919
967
 
@@ -3187,54 +3235,6 @@ function startStatusWatcher(config) {
3187
3235
  };
3188
3236
  }
3189
3237
 
3190
- // src/automatedActions/sentinel.ts
3191
- function sentinel(name) {
3192
- return `@@automated::${name}@@`;
3193
- }
3194
- function automatedMessage(name, body) {
3195
- return body ? `${sentinel(name)}
3196
- ${body}` : sentinel(name);
3197
- }
3198
- function hasSentinel(text, name) {
3199
- return text.startsWith(sentinel(name));
3200
- }
3201
- function isAutomatedMessage(text) {
3202
- return text.startsWith("@@automated::");
3203
- }
3204
- function parseSentinel(text) {
3205
- const match = text.match(/^@@automated::(\w+)@@(.*)/s);
3206
- if (!match) {
3207
- return null;
3208
- }
3209
- return { name: match[1], remainder: match[2] };
3210
- }
3211
- function stripSentinelLine(text) {
3212
- return text.replace(/^@@automated::[^@]*@@[^\n]*\n?/, "");
3213
- }
3214
- function buildBackgroundResultsMessage(results) {
3215
- const xml = results.map(
3216
- (r) => `<tool_result id="${r.toolCallId}" name="${r.name}">
3217
- ${r.result}
3218
- </tool_result>`
3219
- ).join("\n\n");
3220
- const plural = results.length > 1 ? "s" : "";
3221
- const body = `This is an automated message containing the result${plural} of ${results.length > 1 ? "tool calls" : "a tool call"} that ${results.length > 1 ? "have" : "has"} been working in the background. This is not a direct message from the user.
3222
- <background_results>
3223
- ${xml}
3224
- </background_results>`;
3225
- return automatedMessage("background_results", body);
3226
- }
3227
- function mergeBackgroundResultsMessages(messages) {
3228
- const results = [];
3229
- const toolRe = /<tool_result id="([^"]+)" name="([^"]+)">\n([\s\S]*?)\n<\/tool_result>/g;
3230
- for (const msg of messages) {
3231
- for (const m of msg.matchAll(toolRe)) {
3232
- results.push({ toolCallId: m[1], name: m[2], result: m[3] });
3233
- }
3234
- }
3235
- return buildBackgroundResultsMessage(results);
3236
- }
3237
-
3238
3238
  // src/subagents/common/cleanMessages.ts
3239
3239
  function findLastSummaryCheckpoint(messages, name) {
3240
3240
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -3290,11 +3290,18 @@ function cleanMessagesForApi(messages) {
3290
3290
  (b) => b.type === "summary" && b.name === "conversation"
3291
3291
  );
3292
3292
  if (summaryBlock && summaryBlock.type === "summary") {
3293
+ const recent = summaryBlock.recent ? `
3294
+
3295
+ <recent_messages>
3296
+ The last few turns of the summarized conversation, quoted exactly as they were said.
3297
+
3298
+ ${summaryBlock.recent}
3299
+ </recent_messages>` : "";
3293
3300
  prefix.push({
3294
3301
  role: "user",
3295
3302
  content: `<conversation_summary>
3296
3303
  ${summaryBlock.text}
3297
- </conversation_summary>`,
3304
+ </conversation_summary>${recent}`,
3298
3305
  hidden: true
3299
3306
  });
3300
3307
  }
@@ -5242,6 +5249,11 @@ var PROMPT_TEMPLATE = readAsset(SUBAGENT, "prompt.md").replace(/\{\{([^}]+)\}\}/
5242
5249
  const k = key.trim();
5243
5250
  return RUNTIME_PLACEHOLDERS.has(k) ? match : readAsset(SUBAGENT, k);
5244
5251
  }).replace(/\n{3,}/g, "\n\n");
5252
+ var RENDER_TASK_BLOCK = `<render_task>
5253
+ This task is a render: you are the author of the artifact, not an advisor on it. Everything above describes your usual work \u2014 proposing a design to a developer who then builds it. Here there is no developer downstream. You write the file the brief names, using writeFile (or editFile when it already exists), and that file is the entire deliverable.
5254
+
5255
+ The guidance about specifying layouts in prose, writing implementation notes, and keeping wireframes small doesn't apply here: it exists to hand a design to someone else to build, and you are the one building it. Your reply is a receipt \u2014 one line naming what you wrote.
5256
+ </render_task>`;
5245
5257
  function renderDesignSystemBlock(designSystem) {
5246
5258
  if (!designSystem) {
5247
5259
  return "";
@@ -5252,7 +5264,7 @@ function renderDesignSystemBlock(designSystem) {
5252
5264
  "</design_system>"
5253
5265
  ].join("\n");
5254
5266
  }
5255
- function getDesignExpertPrompt(onboardingState) {
5267
+ function getDesignExpertPrompt(onboardingState, opts) {
5256
5268
  const specContext = loadSpecIndex();
5257
5269
  const indices = getSampleIndices(
5258
5270
  {
@@ -5297,6 +5309,11 @@ ${designSystemBlock}`;
5297
5309
  <project_phase>
5298
5310
  This project is in the "${state}" phase. The codebase is a placeholder scaffold or is being generated for the first time.
5299
5311
  </project_phase>`;
5312
+ }
5313
+ if (opts?.render) {
5314
+ prompt += `
5315
+
5316
+ ${RENDER_TASK_BLOCK}`;
5300
5317
  }
5301
5318
  return prompt;
5302
5319
  }
@@ -5358,17 +5375,19 @@ var DESIGN_EXPERT_RENDER_TOOLS = [
5358
5375
  async function runDesignExpert(opts, context) {
5359
5376
  const history = context.conversationMessages ? getSubAgentHistory(context.conversationMessages, "visualDesignExpert") : [];
5360
5377
  return runSubAgent({
5361
- system: getDesignExpertPrompt(context.onboardingState),
5378
+ system: getDesignExpertPrompt(context.onboardingState, {
5379
+ render: opts.render
5380
+ }),
5362
5381
  task: opts.task,
5363
5382
  history: history.length > 0 ? history : void 0,
5364
- tools: opts.enableWrite ? DESIGN_EXPERT_RENDER_TOOLS : DESIGN_EXPERT_TOOLS,
5383
+ tools: opts.render ? DESIGN_EXPERT_RENDER_TOOLS : DESIGN_EXPERT_TOOLS,
5365
5384
  externalTools: /* @__PURE__ */ new Set(),
5366
5385
  executeTool: (name, input, toolCallId, onLog, sams) => {
5367
5386
  const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
5368
5387
  if (COMMON_READ_TOOL_NAMES.has(name)) {
5369
5388
  return executeTool(name, input, childCtx);
5370
5389
  }
5371
- if (opts.enableWrite && RENDER_WRITE_TOOL_NAMES.has(name)) {
5390
+ if (opts.render && RENDER_WRITE_TOOL_NAMES.has(name)) {
5372
5391
  return executeTool(name, input, childCtx);
5373
5392
  }
5374
5393
  return executeDesignExpertTool(name, input, childCtx, toolCallId, onLog);
@@ -5429,7 +5448,7 @@ var designExpertTool = {
5429
5448
  }
5430
5449
  };
5431
5450
  async function runDesignExpertRender(opts, context) {
5432
- return runDesignExpert({ ...opts, enableWrite: true }, context);
5451
+ return runDesignExpert({ ...opts, render: true }, context);
5433
5452
  }
5434
5453
 
5435
5454
  // src/subagents/productVision/tools.ts
@@ -5540,14 +5559,14 @@ ${unifiedDiff(filePath, oldContent, "")}`;
5540
5559
  const delivery = exists ? `### Your deliverable
5541
5560
  The pitch deck already exists at \`${filePath}\`. Read it, then update it for the new <pitch_content>, keeping the presentation scaffolding intact \u2014 change only what needs to change.
5542
5561
 
5543
- Reply with a one-line summary of what you changed \u2014 not the HTML.` : `### Your deliverable
5562
+ Then reply with a one-line summary of what you changed.` : `### Your deliverable
5544
5563
  Write the complete pitch deck to \`${filePath}\`. It does not exist yet \u2014 start from this scaffold, keeping its progress bar, chevron navigation, keyboard navigation, and transition mechanics intact:
5545
5564
 
5546
5565
  <pitch_deck_shell>
5547
5566
  ${PITCH_DECK_SHELL}
5548
5567
  </pitch_deck_shell>
5549
5568
 
5550
- Reply with a one-line summary of what you wrote \u2014 not the HTML.`;
5569
+ Then reply with a one-line summary of what you wrote.`;
5551
5570
  const task = `
5552
5571
  <pitch_content>${input.task}</pitch_content>
5553
5572
 
@@ -5952,13 +5971,13 @@ Write the complete Build Overview to \`${OVERVIEW_FILE}\`. The file does not exi
5952
5971
  ${OVERVIEW_SHELL}
5953
5972
  </overview_shell>
5954
5973
 
5955
- Reply with a one-line summary of what you wrote \u2014 not the HTML.`;
5974
+ Then reply with a one-line summary of what you wrote.`;
5956
5975
  }
5957
5976
  function refreshDelivery() {
5958
5977
  return `### Your deliverable
5959
5978
  The Build Overview already exists at \`${OVERVIEW_FILE}\`. Read it, then update it to reflect <overview_copy>, preserving its established skin \u2014 change only what the copy changed.
5960
5979
 
5961
- Reply with a one-line summary of what you changed \u2014 not the HTML.`;
5980
+ Then reply with a one-line summary of what you changed.`;
5962
5981
  }
5963
5982
  var buildOverviewTool = {
5964
5983
  clearable: false,
@@ -6094,6 +6113,7 @@ var SUBAGENT_SUMMARY_PROMPT = readAsset("compaction", "subagent.md");
6094
6113
  var SUMMARIZABLE_SUBAGENTS = ["visualDesignExpert", "productVision"];
6095
6114
  async function compactConversation(messages, apiConfig, model) {
6096
6115
  const endIndex = findSafeInsertionPoint(messages);
6116
+ const boundary = endIndex > 0 ? messages[endIndex - 1] : null;
6097
6117
  const summaries = [];
6098
6118
  const tasks = [];
6099
6119
  const conversationMessages = getConversationMessagesForSummary(
@@ -6150,6 +6170,7 @@ async function compactConversation(messages, apiConfig, model) {
6150
6170
  "Could not summarize the conversation \u2014 the model did not return a usable summary. History left intact."
6151
6171
  );
6152
6172
  }
6173
+ const recent = collectRecentNarrative(conversationMessages);
6153
6174
  const checkpointMessages = summaries.map((s) => ({
6154
6175
  role: "user",
6155
6176
  hidden: true,
@@ -6158,12 +6179,16 @@ async function compactConversation(messages, apiConfig, model) {
6158
6179
  type: "summary",
6159
6180
  name: s.name,
6160
6181
  text: s.text,
6182
+ ...s.name === "conversation" && recent ? { recent } : {},
6161
6183
  startedAt: Date.now()
6162
6184
  }
6163
6185
  ]
6164
6186
  }));
6165
- log8.info("Compaction complete", { summaries: summaries.length });
6166
- return checkpointMessages;
6187
+ log8.info("Compaction complete", {
6188
+ summaries: summaries.length,
6189
+ recentNarrativeChars: recent.length
6190
+ });
6191
+ return { checkpoints: checkpointMessages, boundary };
6167
6192
  }
6168
6193
  function findSafeInsertionPoint(messages, fromIndex = messages.length) {
6169
6194
  let idx = fromIndex;
@@ -6239,6 +6264,58 @@ function getSubAgentMessagesForSummary(messages, subAgentName, endIndex) {
6239
6264
  }
6240
6265
  return collected;
6241
6266
  }
6267
+ var RECENT_NARRATIVE_REPLIES = 3;
6268
+ var RECENT_NARRATIVE_MAX_CHARS = 2e4;
6269
+ function narrativeText(msg) {
6270
+ if (msg.role === "assistant") {
6271
+ if (!Array.isArray(msg.content)) {
6272
+ return null;
6273
+ }
6274
+ const blocks = msg.content;
6275
+ if (blocks.some((b) => b.type === "tool")) {
6276
+ return null;
6277
+ }
6278
+ const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
6279
+ return text || null;
6280
+ }
6281
+ if (msg.role !== "user" || msg.toolCallId || msg.hidden) {
6282
+ return null;
6283
+ }
6284
+ if (typeof msg.content !== "string" || isAutomatedMessage(msg.content)) {
6285
+ return null;
6286
+ }
6287
+ const body = msg.content.trim();
6288
+ if (body) {
6289
+ return body;
6290
+ }
6291
+ return msg.attachments?.find((a) => a.transcript)?.transcript?.trim() || null;
6292
+ }
6293
+ function collectRecentNarrative(messages) {
6294
+ const quoted = [];
6295
+ let replies = 0;
6296
+ let chars = 0;
6297
+ for (let i = messages.length - 1; i >= 0; i--) {
6298
+ const msg = messages[i];
6299
+ const text = narrativeText(msg);
6300
+ if (!text) {
6301
+ continue;
6302
+ }
6303
+ if (quoted.length > 0) {
6304
+ if (replies >= RECENT_NARRATIVE_REPLIES) {
6305
+ break;
6306
+ }
6307
+ if (chars + text.length > RECENT_NARRATIVE_MAX_CHARS) {
6308
+ break;
6309
+ }
6310
+ }
6311
+ quoted.push(`[${msg.role}]: ${text}`);
6312
+ chars += text.length;
6313
+ if (msg.role === "assistant") {
6314
+ replies++;
6315
+ }
6316
+ }
6317
+ return quoted.reverse().join("\n\n");
6318
+ }
6242
6319
  function serializeForSummary(messages) {
6243
6320
  const toolNameById = /* @__PURE__ */ new Map();
6244
6321
  for (const msg of messages) {
@@ -6763,15 +6840,26 @@ function clearSession(state) {
6763
6840
 
6764
6841
  // src/compaction/trigger.ts
6765
6842
  var log10 = createLogger("compaction:trigger");
6766
- var pendingSummaries = [];
6843
+ var pending = null;
6767
6844
  var inflightCompaction = null;
6768
6845
  function applyPendingSummaries(state) {
6769
- const summaries = pendingSummaries.splice(0);
6770
- if (summaries.length === 0) {
6846
+ const drained = pending;
6847
+ pending = null;
6848
+ if (!drained || drained.checkpoints.length === 0) {
6771
6849
  return;
6772
6850
  }
6773
- const idx = findSafeInsertionPoint(state.messages);
6774
- state.messages.splice(idx, 0, ...summaries);
6851
+ let idx;
6852
+ if (!drained.boundary) {
6853
+ idx = 0;
6854
+ } else {
6855
+ const at = state.messages.indexOf(drained.boundary);
6856
+ idx = at === -1 ? 0 : at + 1;
6857
+ }
6858
+ state.messages.splice(idx, 0, ...drained.checkpoints);
6859
+ log10.info("Checkpoint applied", {
6860
+ index: idx,
6861
+ messageCount: state.messages.length
6862
+ });
6775
6863
  saveSession(state);
6776
6864
  }
6777
6865
  var listener = null;
@@ -6782,14 +6870,18 @@ function triggerCompaction(state, apiConfig, opts = {}) {
6782
6870
  if (inflightCompaction) {
6783
6871
  return inflightCompaction;
6784
6872
  }
6873
+ if (pending) {
6874
+ log10.info("Compaction skipped \u2014 a checkpoint is already waiting to apply");
6875
+ return Promise.resolve();
6876
+ }
6785
6877
  const { blocking = false, requestId, model } = opts;
6786
6878
  listener?.({ type: "started", blocking, requestId });
6787
6879
  inflightCompaction = compactConversation(
6788
6880
  state.messages,
6789
6881
  apiConfig,
6790
6882
  resolveModel("conversationSummarizer", state.models, model)
6791
- ).then((summaries) => {
6792
- pendingSummaries.push(...summaries);
6883
+ ).then((result) => {
6884
+ pending = result;
6793
6885
  listener?.({ type: "complete", requestId });
6794
6886
  log10.info("Compaction complete");
6795
6887
  }).catch((err) => {
@@ -8963,9 +9055,9 @@ var HeadlessSession = class {
8963
9055
  if (this.currentAbort) {
8964
9056
  this.currentAbort.abort();
8965
9057
  }
8966
- for (const [id, pending] of this.pendingTools) {
8967
- clearTimeout(pending.timeout);
8968
- pending.resolve(USER_CANCELLED_RESULT);
9058
+ for (const [id, pending2] of this.pendingTools) {
9059
+ clearTimeout(pending2.timeout);
9060
+ pending2.resolve(USER_CANCELLED_RESULT);
8969
9061
  this.pendingTools.delete(id);
8970
9062
  }
8971
9063
  return this.queue.removeWhere((item) => item.source !== "user");
@@ -9002,10 +9094,10 @@ var HeadlessSession = class {
9002
9094
  if (action === "tool_result" && parsed.id) {
9003
9095
  const id = parsed.id;
9004
9096
  const result = parsed.result ?? "";
9005
- const pending = this.pendingTools.get(id);
9006
- if (pending) {
9097
+ const pending2 = this.pendingTools.get(id);
9098
+ if (pending2) {
9007
9099
  this.pendingTools.delete(id);
9008
- pending.resolve(result);
9100
+ pending2.resolve(result);
9009
9101
  } else if (!this.running) {
9010
9102
  log15.info("Late tool_result while idle, dismissing", { id });
9011
9103
  this.emit("completed", { success: true }, requestId);
package/dist/index.js CHANGED
@@ -1599,9 +1599,63 @@ var init_assets = __esm({
1599
1599
  }
1600
1600
  });
1601
1601
 
1602
+ // src/automatedActions/sentinel.ts
1603
+ function sentinel(name) {
1604
+ return `@@automated::${name}@@`;
1605
+ }
1606
+ function automatedMessage(name, body) {
1607
+ return body ? `${sentinel(name)}
1608
+ ${body}` : sentinel(name);
1609
+ }
1610
+ function hasSentinel(text, name) {
1611
+ return text.startsWith(sentinel(name));
1612
+ }
1613
+ function isAutomatedMessage(text) {
1614
+ return text.startsWith("@@automated::");
1615
+ }
1616
+ function parseSentinel(text) {
1617
+ const match = text.match(/^@@automated::(\w+)@@(.*)/s);
1618
+ if (!match) {
1619
+ return null;
1620
+ }
1621
+ return { name: match[1], remainder: match[2] };
1622
+ }
1623
+ function stripSentinelLine(text) {
1624
+ return text.replace(/^@@automated::[^@]*@@[^\n]*\n?/, "");
1625
+ }
1626
+ function buildBackgroundResultsMessage(results) {
1627
+ const xml = results.map(
1628
+ (r) => `<tool_result id="${r.toolCallId}" name="${r.name}">
1629
+ ${r.result}
1630
+ </tool_result>`
1631
+ ).join("\n\n");
1632
+ const plural = results.length > 1 ? "s" : "";
1633
+ const body = `This is an automated message containing the result${plural} of ${results.length > 1 ? "tool calls" : "a tool call"} that ${results.length > 1 ? "have" : "has"} been working in the background. This is not a direct message from the user.
1634
+ <background_results>
1635
+ ${xml}
1636
+ </background_results>`;
1637
+ return automatedMessage("background_results", body);
1638
+ }
1639
+ function mergeBackgroundResultsMessages(messages) {
1640
+ const results = [];
1641
+ const toolRe = /<tool_result id="([^"]+)" name="([^"]+)">\n([\s\S]*?)\n<\/tool_result>/g;
1642
+ for (const msg of messages) {
1643
+ for (const m of msg.matchAll(toolRe)) {
1644
+ results.push({ toolCallId: m[1], name: m[2], result: m[3] });
1645
+ }
1646
+ }
1647
+ return buildBackgroundResultsMessage(results);
1648
+ }
1649
+ var init_sentinel = __esm({
1650
+ "src/automatedActions/sentinel.ts"() {
1651
+ "use strict";
1652
+ }
1653
+ });
1654
+
1602
1655
  // src/compaction/index.ts
1603
1656
  async function compactConversation(messages, apiConfig, model) {
1604
1657
  const endIndex = findSafeInsertionPoint(messages);
1658
+ const boundary = endIndex > 0 ? messages[endIndex - 1] : null;
1605
1659
  const summaries = [];
1606
1660
  const tasks = [];
1607
1661
  const conversationMessages = getConversationMessagesForSummary(
@@ -1658,6 +1712,7 @@ async function compactConversation(messages, apiConfig, model) {
1658
1712
  "Could not summarize the conversation \u2014 the model did not return a usable summary. History left intact."
1659
1713
  );
1660
1714
  }
1715
+ const recent = collectRecentNarrative(conversationMessages);
1661
1716
  const checkpointMessages = summaries.map((s) => ({
1662
1717
  role: "user",
1663
1718
  hidden: true,
@@ -1666,12 +1721,16 @@ async function compactConversation(messages, apiConfig, model) {
1666
1721
  type: "summary",
1667
1722
  name: s.name,
1668
1723
  text: s.text,
1724
+ ...s.name === "conversation" && recent ? { recent } : {},
1669
1725
  startedAt: Date.now()
1670
1726
  }
1671
1727
  ]
1672
1728
  }));
1673
- log2.info("Compaction complete", { summaries: summaries.length });
1674
- return checkpointMessages;
1729
+ log2.info("Compaction complete", {
1730
+ summaries: summaries.length,
1731
+ recentNarrativeChars: recent.length
1732
+ });
1733
+ return { checkpoints: checkpointMessages, boundary };
1675
1734
  }
1676
1735
  function findSafeInsertionPoint(messages, fromIndex = messages.length) {
1677
1736
  let idx = fromIndex;
@@ -1747,6 +1806,56 @@ function getSubAgentMessagesForSummary(messages, subAgentName, endIndex) {
1747
1806
  }
1748
1807
  return collected;
1749
1808
  }
1809
+ function narrativeText(msg) {
1810
+ if (msg.role === "assistant") {
1811
+ if (!Array.isArray(msg.content)) {
1812
+ return null;
1813
+ }
1814
+ const blocks = msg.content;
1815
+ if (blocks.some((b) => b.type === "tool")) {
1816
+ return null;
1817
+ }
1818
+ const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
1819
+ return text || null;
1820
+ }
1821
+ if (msg.role !== "user" || msg.toolCallId || msg.hidden) {
1822
+ return null;
1823
+ }
1824
+ if (typeof msg.content !== "string" || isAutomatedMessage(msg.content)) {
1825
+ return null;
1826
+ }
1827
+ const body = msg.content.trim();
1828
+ if (body) {
1829
+ return body;
1830
+ }
1831
+ return msg.attachments?.find((a) => a.transcript)?.transcript?.trim() || null;
1832
+ }
1833
+ function collectRecentNarrative(messages) {
1834
+ const quoted = [];
1835
+ let replies = 0;
1836
+ let chars = 0;
1837
+ for (let i = messages.length - 1; i >= 0; i--) {
1838
+ const msg = messages[i];
1839
+ const text = narrativeText(msg);
1840
+ if (!text) {
1841
+ continue;
1842
+ }
1843
+ if (quoted.length > 0) {
1844
+ if (replies >= RECENT_NARRATIVE_REPLIES) {
1845
+ break;
1846
+ }
1847
+ if (chars + text.length > RECENT_NARRATIVE_MAX_CHARS) {
1848
+ break;
1849
+ }
1850
+ }
1851
+ quoted.push(`[${msg.role}]: ${text}`);
1852
+ chars += text.length;
1853
+ if (msg.role === "assistant") {
1854
+ replies++;
1855
+ }
1856
+ }
1857
+ return quoted.reverse().join("\n\n");
1858
+ }
1750
1859
  function serializeForSummary(messages) {
1751
1860
  const toolNameById = /* @__PURE__ */ new Map();
1752
1861
  for (const msg of messages) {
@@ -1930,12 +2039,13 @@ Write the summary of the conversation above, following your instructions.`;
1930
2039
  }
1931
2040
  return summaryText.trim();
1932
2041
  }
1933
- var log2, CONVERSATION_SUMMARY_PROMPT, SUBAGENT_SUMMARY_PROMPT, SUMMARIZABLE_SUBAGENTS, CHUNK_CHAR_LIMIT, MIN_SUMMARY_CHARS;
2042
+ var log2, CONVERSATION_SUMMARY_PROMPT, SUBAGENT_SUMMARY_PROMPT, SUMMARIZABLE_SUBAGENTS, RECENT_NARRATIVE_REPLIES, RECENT_NARRATIVE_MAX_CHARS, CHUNK_CHAR_LIMIT, MIN_SUMMARY_CHARS;
1934
2043
  var init_compaction = __esm({
1935
2044
  "src/compaction/index.ts"() {
1936
2045
  "use strict";
1937
2046
  init_api();
1938
2047
  init_assets();
2048
+ init_sentinel();
1939
2049
  init_tools8();
1940
2050
  init_logger();
1941
2051
  init_usageLedger();
@@ -1943,6 +2053,8 @@ var init_compaction = __esm({
1943
2053
  CONVERSATION_SUMMARY_PROMPT = readAsset("compaction", "conversation.md");
1944
2054
  SUBAGENT_SUMMARY_PROMPT = readAsset("compaction", "subagent.md");
1945
2055
  SUMMARIZABLE_SUBAGENTS = ["visualDesignExpert", "productVision"];
2056
+ RECENT_NARRATIVE_REPLIES = 3;
2057
+ RECENT_NARRATIVE_MAX_CHARS = 2e4;
1946
2058
  CHUNK_CHAR_LIMIT = 2e5;
1947
2059
  MIN_SUMMARY_CHARS = 400;
1948
2060
  }
@@ -2111,59 +2223,6 @@ var init_surfaces = __esm({
2111
2223
  }
2112
2224
  });
2113
2225
 
2114
- // src/automatedActions/sentinel.ts
2115
- function sentinel(name) {
2116
- return `@@automated::${name}@@`;
2117
- }
2118
- function automatedMessage(name, body) {
2119
- return body ? `${sentinel(name)}
2120
- ${body}` : sentinel(name);
2121
- }
2122
- function hasSentinel(text, name) {
2123
- return text.startsWith(sentinel(name));
2124
- }
2125
- function isAutomatedMessage(text) {
2126
- return text.startsWith("@@automated::");
2127
- }
2128
- function parseSentinel(text) {
2129
- const match = text.match(/^@@automated::(\w+)@@(.*)/s);
2130
- if (!match) {
2131
- return null;
2132
- }
2133
- return { name: match[1], remainder: match[2] };
2134
- }
2135
- function stripSentinelLine(text) {
2136
- return text.replace(/^@@automated::[^@]*@@[^\n]*\n?/, "");
2137
- }
2138
- function buildBackgroundResultsMessage(results) {
2139
- const xml = results.map(
2140
- (r) => `<tool_result id="${r.toolCallId}" name="${r.name}">
2141
- ${r.result}
2142
- </tool_result>`
2143
- ).join("\n\n");
2144
- const plural = results.length > 1 ? "s" : "";
2145
- const body = `This is an automated message containing the result${plural} of ${results.length > 1 ? "tool calls" : "a tool call"} that ${results.length > 1 ? "have" : "has"} been working in the background. This is not a direct message from the user.
2146
- <background_results>
2147
- ${xml}
2148
- </background_results>`;
2149
- return automatedMessage("background_results", body);
2150
- }
2151
- function mergeBackgroundResultsMessages(messages) {
2152
- const results = [];
2153
- const toolRe = /<tool_result id="([^"]+)" name="([^"]+)">\n([\s\S]*?)\n<\/tool_result>/g;
2154
- for (const msg of messages) {
2155
- for (const m of msg.matchAll(toolRe)) {
2156
- results.push({ toolCallId: m[1], name: m[2], result: m[3] });
2157
- }
2158
- }
2159
- return buildBackgroundResultsMessage(results);
2160
- }
2161
- var init_sentinel = __esm({
2162
- "src/automatedActions/sentinel.ts"() {
2163
- "use strict";
2164
- }
2165
- });
2166
-
2167
2226
  // src/subagents/common/cleanMessages.ts
2168
2227
  function findLastSummaryCheckpoint(messages, name) {
2169
2228
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -2219,11 +2278,18 @@ function cleanMessagesForApi(messages) {
2219
2278
  (b) => b.type === "summary" && b.name === "conversation"
2220
2279
  );
2221
2280
  if (summaryBlock && summaryBlock.type === "summary") {
2281
+ const recent = summaryBlock.recent ? `
2282
+
2283
+ <recent_messages>
2284
+ The last few turns of the summarized conversation, quoted exactly as they were said.
2285
+
2286
+ ${summaryBlock.recent}
2287
+ </recent_messages>` : "";
2222
2288
  prefix.push({
2223
2289
  role: "user",
2224
2290
  content: `<conversation_summary>
2225
2291
  ${summaryBlock.text}
2226
- </conversation_summary>`,
2292
+ </conversation_summary>${recent}`,
2227
2293
  hidden: true
2228
2294
  });
2229
2295
  }
@@ -2662,12 +2728,23 @@ var init_session = __esm({
2662
2728
 
2663
2729
  // src/compaction/trigger.ts
2664
2730
  function applyPendingSummaries(state) {
2665
- const summaries = pendingSummaries.splice(0);
2666
- if (summaries.length === 0) {
2731
+ const drained = pending;
2732
+ pending = null;
2733
+ if (!drained || drained.checkpoints.length === 0) {
2667
2734
  return;
2668
2735
  }
2669
- const idx = findSafeInsertionPoint(state.messages);
2670
- state.messages.splice(idx, 0, ...summaries);
2736
+ let idx;
2737
+ if (!drained.boundary) {
2738
+ idx = 0;
2739
+ } else {
2740
+ const at = state.messages.indexOf(drained.boundary);
2741
+ idx = at === -1 ? 0 : at + 1;
2742
+ }
2743
+ state.messages.splice(idx, 0, ...drained.checkpoints);
2744
+ log4.info("Checkpoint applied", {
2745
+ index: idx,
2746
+ messageCount: state.messages.length
2747
+ });
2671
2748
  saveSession(state);
2672
2749
  }
2673
2750
  function setCompactionListener(l) {
@@ -2677,14 +2754,18 @@ function triggerCompaction(state, apiConfig, opts = {}) {
2677
2754
  if (inflightCompaction) {
2678
2755
  return inflightCompaction;
2679
2756
  }
2757
+ if (pending) {
2758
+ log4.info("Compaction skipped \u2014 a checkpoint is already waiting to apply");
2759
+ return Promise.resolve();
2760
+ }
2680
2761
  const { blocking = false, requestId, model } = opts;
2681
2762
  listener?.({ type: "started", blocking, requestId });
2682
2763
  inflightCompaction = compactConversation(
2683
2764
  state.messages,
2684
2765
  apiConfig,
2685
2766
  resolveModel("conversationSummarizer", state.models, model)
2686
- ).then((summaries) => {
2687
- pendingSummaries.push(...summaries);
2767
+ ).then((result) => {
2768
+ pending = result;
2688
2769
  listener?.({ type: "complete", requestId });
2689
2770
  log4.info("Compaction complete");
2690
2771
  }).catch((err) => {
@@ -2697,7 +2778,7 @@ function triggerCompaction(state, apiConfig, opts = {}) {
2697
2778
  });
2698
2779
  return inflightCompaction;
2699
2780
  }
2700
- var log4, pendingSummaries, inflightCompaction, listener;
2781
+ var log4, pending, inflightCompaction, listener;
2701
2782
  var init_trigger = __esm({
2702
2783
  "src/compaction/trigger.ts"() {
2703
2784
  "use strict";
@@ -2706,7 +2787,7 @@ var init_trigger = __esm({
2706
2787
  init_surfaces();
2707
2788
  init_session();
2708
2789
  log4 = createLogger("compaction:trigger");
2709
- pendingSummaries = [];
2790
+ pending = null;
2710
2791
  inflightCompaction = null;
2711
2792
  listener = null;
2712
2793
  }
@@ -6218,7 +6299,7 @@ function renderDesignSystemBlock(designSystem) {
6218
6299
  "</design_system>"
6219
6300
  ].join("\n");
6220
6301
  }
6221
- function getDesignExpertPrompt(onboardingState) {
6302
+ function getDesignExpertPrompt(onboardingState, opts) {
6222
6303
  const specContext = loadSpecIndex();
6223
6304
  const indices = getSampleIndices(
6224
6305
  {
@@ -6263,10 +6344,15 @@ ${designSystemBlock}`;
6263
6344
  <project_phase>
6264
6345
  This project is in the "${state}" phase. The codebase is a placeholder scaffold or is being generated for the first time.
6265
6346
  </project_phase>`;
6347
+ }
6348
+ if (opts?.render) {
6349
+ prompt += `
6350
+
6351
+ ${RENDER_TASK_BLOCK}`;
6266
6352
  }
6267
6353
  return prompt;
6268
6354
  }
6269
- var SUBAGENT, RUNTIME_PLACEHOLDERS, PROMPT_TEMPLATE;
6355
+ var SUBAGENT, RUNTIME_PLACEHOLDERS, PROMPT_TEMPLATE, RENDER_TASK_BLOCK;
6270
6356
  var init_prompt2 = __esm({
6271
6357
  "src/subagents/designExpert/prompt.ts"() {
6272
6358
  "use strict";
@@ -6287,6 +6373,11 @@ var init_prompt2 = __esm({
6287
6373
  const k = key.trim();
6288
6374
  return RUNTIME_PLACEHOLDERS.has(k) ? match : readAsset(SUBAGENT, k);
6289
6375
  }).replace(/\n{3,}/g, "\n\n");
6376
+ RENDER_TASK_BLOCK = `<render_task>
6377
+ This task is a render: you are the author of the artifact, not an advisor on it. Everything above describes your usual work \u2014 proposing a design to a developer who then builds it. Here there is no developer downstream. You write the file the brief names, using writeFile (or editFile when it already exists), and that file is the entire deliverable.
6378
+
6379
+ The guidance about specifying layouts in prose, writing implementation notes, and keeping wireframes small doesn't apply here: it exists to hand a design to someone else to build, and you are the one building it. Your reply is a receipt \u2014 one line naming what you wrote.
6380
+ </render_task>`;
6290
6381
  }
6291
6382
  });
6292
6383
 
@@ -6343,17 +6434,19 @@ var init_history = __esm({
6343
6434
  async function runDesignExpert(opts, context) {
6344
6435
  const history = context.conversationMessages ? getSubAgentHistory(context.conversationMessages, "visualDesignExpert") : [];
6345
6436
  return runSubAgent({
6346
- system: getDesignExpertPrompt(context.onboardingState),
6437
+ system: getDesignExpertPrompt(context.onboardingState, {
6438
+ render: opts.render
6439
+ }),
6347
6440
  task: opts.task,
6348
6441
  history: history.length > 0 ? history : void 0,
6349
- tools: opts.enableWrite ? DESIGN_EXPERT_RENDER_TOOLS : DESIGN_EXPERT_TOOLS,
6442
+ tools: opts.render ? DESIGN_EXPERT_RENDER_TOOLS : DESIGN_EXPERT_TOOLS,
6350
6443
  externalTools: /* @__PURE__ */ new Set(),
6351
6444
  executeTool: (name, input, toolCallId, onLog, sams) => {
6352
6445
  const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
6353
6446
  if (COMMON_READ_TOOL_NAMES.has(name)) {
6354
6447
  return executeTool(name, input, childCtx);
6355
6448
  }
6356
- if (opts.enableWrite && RENDER_WRITE_TOOL_NAMES.has(name)) {
6449
+ if (opts.render && RENDER_WRITE_TOOL_NAMES.has(name)) {
6357
6450
  return executeTool(name, input, childCtx);
6358
6451
  }
6359
6452
  return executeDesignExpertTool(name, input, childCtx, toolCallId, onLog);
@@ -6379,7 +6472,7 @@ async function runDesignExpert(opts, context) {
6379
6472
  });
6380
6473
  }
6381
6474
  async function runDesignExpertRender(opts, context) {
6382
- return runDesignExpert({ ...opts, enableWrite: true }, context);
6475
+ return runDesignExpert({ ...opts, render: true }, context);
6383
6476
  }
6384
6477
  var DESCRIPTION, RENDER_WRITE_TOOL_NAMES, DESIGN_EXPERT_RENDER_TOOLS, designExpertTool;
6385
6478
  var init_designExpert = __esm({
@@ -6551,14 +6644,14 @@ ${unifiedDiff(filePath, oldContent, "")}`;
6551
6644
  const delivery = exists ? `### Your deliverable
6552
6645
  The pitch deck already exists at \`${filePath}\`. Read it, then update it for the new <pitch_content>, keeping the presentation scaffolding intact \u2014 change only what needs to change.
6553
6646
 
6554
- Reply with a one-line summary of what you changed \u2014 not the HTML.` : `### Your deliverable
6647
+ Then reply with a one-line summary of what you changed.` : `### Your deliverable
6555
6648
  Write the complete pitch deck to \`${filePath}\`. It does not exist yet \u2014 start from this scaffold, keeping its progress bar, chevron navigation, keyboard navigation, and transition mechanics intact:
6556
6649
 
6557
6650
  <pitch_deck_shell>
6558
6651
  ${PITCH_DECK_SHELL}
6559
6652
  </pitch_deck_shell>
6560
6653
 
6561
- Reply with a one-line summary of what you wrote \u2014 not the HTML.`;
6654
+ Then reply with a one-line summary of what you wrote.`;
6562
6655
  const task = `
6563
6656
  <pitch_content>${input.task}</pitch_content>
6564
6657
 
@@ -7016,13 +7109,13 @@ Write the complete Build Overview to \`${OVERVIEW_FILE}\`. The file does not exi
7016
7109
  ${OVERVIEW_SHELL}
7017
7110
  </overview_shell>
7018
7111
 
7019
- Reply with a one-line summary of what you wrote \u2014 not the HTML.`;
7112
+ Then reply with a one-line summary of what you wrote.`;
7020
7113
  }
7021
7114
  function refreshDelivery() {
7022
7115
  return `### Your deliverable
7023
7116
  The Build Overview already exists at \`${OVERVIEW_FILE}\`. Read it, then update it to reflect <overview_copy>, preserving its established skin \u2014 change only what the copy changed.
7024
7117
 
7025
- Reply with a one-line summary of what you changed \u2014 not the HTML.`;
7118
+ Then reply with a one-line summary of what you changed.`;
7026
7119
  }
7027
7120
  var OVERVIEW_FILE, DESIGN_BRIEF, OVERVIEW_SHELL, buildOverviewTool;
7028
7121
  var init_writeBuildOverview = __esm({
@@ -9861,9 +9954,9 @@ var init_headless = __esm({
9861
9954
  if (this.currentAbort) {
9862
9955
  this.currentAbort.abort();
9863
9956
  }
9864
- for (const [id, pending] of this.pendingTools) {
9865
- clearTimeout(pending.timeout);
9866
- pending.resolve(USER_CANCELLED_RESULT);
9957
+ for (const [id, pending2] of this.pendingTools) {
9958
+ clearTimeout(pending2.timeout);
9959
+ pending2.resolve(USER_CANCELLED_RESULT);
9867
9960
  this.pendingTools.delete(id);
9868
9961
  }
9869
9962
  return this.queue.removeWhere((item) => item.source !== "user");
@@ -9900,10 +9993,10 @@ var init_headless = __esm({
9900
9993
  if (action === "tool_result" && parsed.id) {
9901
9994
  const id = parsed.id;
9902
9995
  const result = parsed.result ?? "";
9903
- const pending = this.pendingTools.get(id);
9904
- if (pending) {
9996
+ const pending2 = this.pendingTools.get(id);
9997
+ if (pending2) {
9905
9998
  this.pendingTools.delete(id);
9906
- pending.resolve(result);
9999
+ pending2.resolve(result);
9907
10000
  } else if (!this.running) {
9908
10001
  log15.info("Late tool_result while idle, dismissing", { id });
9909
10002
  this.emit("completed", { success: true }, requestId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.253",
3
+ "version": "0.1.255",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",