@mindstudio-ai/remy 0.1.252 → 0.1.254

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
  }
@@ -6094,6 +6101,7 @@ var SUBAGENT_SUMMARY_PROMPT = readAsset("compaction", "subagent.md");
6094
6101
  var SUMMARIZABLE_SUBAGENTS = ["visualDesignExpert", "productVision"];
6095
6102
  async function compactConversation(messages, apiConfig, model) {
6096
6103
  const endIndex = findSafeInsertionPoint(messages);
6104
+ const boundary = endIndex > 0 ? messages[endIndex - 1] : null;
6097
6105
  const summaries = [];
6098
6106
  const tasks = [];
6099
6107
  const conversationMessages = getConversationMessagesForSummary(
@@ -6150,6 +6158,7 @@ async function compactConversation(messages, apiConfig, model) {
6150
6158
  "Could not summarize the conversation \u2014 the model did not return a usable summary. History left intact."
6151
6159
  );
6152
6160
  }
6161
+ const recent = collectRecentNarrative(conversationMessages);
6153
6162
  const checkpointMessages = summaries.map((s) => ({
6154
6163
  role: "user",
6155
6164
  hidden: true,
@@ -6158,12 +6167,16 @@ async function compactConversation(messages, apiConfig, model) {
6158
6167
  type: "summary",
6159
6168
  name: s.name,
6160
6169
  text: s.text,
6170
+ ...s.name === "conversation" && recent ? { recent } : {},
6161
6171
  startedAt: Date.now()
6162
6172
  }
6163
6173
  ]
6164
6174
  }));
6165
- log8.info("Compaction complete", { summaries: summaries.length });
6166
- return checkpointMessages;
6175
+ log8.info("Compaction complete", {
6176
+ summaries: summaries.length,
6177
+ recentNarrativeChars: recent.length
6178
+ });
6179
+ return { checkpoints: checkpointMessages, boundary };
6167
6180
  }
6168
6181
  function findSafeInsertionPoint(messages, fromIndex = messages.length) {
6169
6182
  let idx = fromIndex;
@@ -6239,6 +6252,58 @@ function getSubAgentMessagesForSummary(messages, subAgentName, endIndex) {
6239
6252
  }
6240
6253
  return collected;
6241
6254
  }
6255
+ var RECENT_NARRATIVE_REPLIES = 3;
6256
+ var RECENT_NARRATIVE_MAX_CHARS = 2e4;
6257
+ function narrativeText(msg) {
6258
+ if (msg.role === "assistant") {
6259
+ if (!Array.isArray(msg.content)) {
6260
+ return null;
6261
+ }
6262
+ const blocks = msg.content;
6263
+ if (blocks.some((b) => b.type === "tool")) {
6264
+ return null;
6265
+ }
6266
+ const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
6267
+ return text || null;
6268
+ }
6269
+ if (msg.role !== "user" || msg.toolCallId || msg.hidden) {
6270
+ return null;
6271
+ }
6272
+ if (typeof msg.content !== "string" || isAutomatedMessage(msg.content)) {
6273
+ return null;
6274
+ }
6275
+ const body = msg.content.trim();
6276
+ if (body) {
6277
+ return body;
6278
+ }
6279
+ return msg.attachments?.find((a) => a.transcript)?.transcript?.trim() || null;
6280
+ }
6281
+ function collectRecentNarrative(messages) {
6282
+ const quoted = [];
6283
+ let replies = 0;
6284
+ let chars = 0;
6285
+ for (let i = messages.length - 1; i >= 0; i--) {
6286
+ const msg = messages[i];
6287
+ const text = narrativeText(msg);
6288
+ if (!text) {
6289
+ continue;
6290
+ }
6291
+ if (quoted.length > 0) {
6292
+ if (replies >= RECENT_NARRATIVE_REPLIES) {
6293
+ break;
6294
+ }
6295
+ if (chars + text.length > RECENT_NARRATIVE_MAX_CHARS) {
6296
+ break;
6297
+ }
6298
+ }
6299
+ quoted.push(`[${msg.role}]: ${text}`);
6300
+ chars += text.length;
6301
+ if (msg.role === "assistant") {
6302
+ replies++;
6303
+ }
6304
+ }
6305
+ return quoted.reverse().join("\n\n");
6306
+ }
6242
6307
  function serializeForSummary(messages) {
6243
6308
  const toolNameById = /* @__PURE__ */ new Map();
6244
6309
  for (const msg of messages) {
@@ -6763,15 +6828,26 @@ function clearSession(state) {
6763
6828
 
6764
6829
  // src/compaction/trigger.ts
6765
6830
  var log10 = createLogger("compaction:trigger");
6766
- var pendingSummaries = [];
6831
+ var pending = null;
6767
6832
  var inflightCompaction = null;
6768
6833
  function applyPendingSummaries(state) {
6769
- const summaries = pendingSummaries.splice(0);
6770
- if (summaries.length === 0) {
6834
+ const drained = pending;
6835
+ pending = null;
6836
+ if (!drained || drained.checkpoints.length === 0) {
6771
6837
  return;
6772
6838
  }
6773
- const idx = findSafeInsertionPoint(state.messages);
6774
- state.messages.splice(idx, 0, ...summaries);
6839
+ let idx;
6840
+ if (!drained.boundary) {
6841
+ idx = 0;
6842
+ } else {
6843
+ const at = state.messages.indexOf(drained.boundary);
6844
+ idx = at === -1 ? 0 : at + 1;
6845
+ }
6846
+ state.messages.splice(idx, 0, ...drained.checkpoints);
6847
+ log10.info("Checkpoint applied", {
6848
+ index: idx,
6849
+ messageCount: state.messages.length
6850
+ });
6775
6851
  saveSession(state);
6776
6852
  }
6777
6853
  var listener = null;
@@ -6782,14 +6858,18 @@ function triggerCompaction(state, apiConfig, opts = {}) {
6782
6858
  if (inflightCompaction) {
6783
6859
  return inflightCompaction;
6784
6860
  }
6861
+ if (pending) {
6862
+ log10.info("Compaction skipped \u2014 a checkpoint is already waiting to apply");
6863
+ return Promise.resolve();
6864
+ }
6785
6865
  const { blocking = false, requestId, model } = opts;
6786
6866
  listener?.({ type: "started", blocking, requestId });
6787
6867
  inflightCompaction = compactConversation(
6788
6868
  state.messages,
6789
6869
  apiConfig,
6790
6870
  resolveModel("conversationSummarizer", state.models, model)
6791
- ).then((summaries) => {
6792
- pendingSummaries.push(...summaries);
6871
+ ).then((result) => {
6872
+ pending = result;
6793
6873
  listener?.({ type: "complete", requestId });
6794
6874
  log10.info("Compaction complete");
6795
6875
  }).catch((err) => {
@@ -8963,9 +9043,9 @@ var HeadlessSession = class {
8963
9043
  if (this.currentAbort) {
8964
9044
  this.currentAbort.abort();
8965
9045
  }
8966
- for (const [id, pending] of this.pendingTools) {
8967
- clearTimeout(pending.timeout);
8968
- pending.resolve(USER_CANCELLED_RESULT);
9046
+ for (const [id, pending2] of this.pendingTools) {
9047
+ clearTimeout(pending2.timeout);
9048
+ pending2.resolve(USER_CANCELLED_RESULT);
8969
9049
  this.pendingTools.delete(id);
8970
9050
  }
8971
9051
  return this.queue.removeWhere((item) => item.source !== "user");
@@ -9002,10 +9082,10 @@ var HeadlessSession = class {
9002
9082
  if (action === "tool_result" && parsed.id) {
9003
9083
  const id = parsed.id;
9004
9084
  const result = parsed.result ?? "";
9005
- const pending = this.pendingTools.get(id);
9006
- if (pending) {
9085
+ const pending2 = this.pendingTools.get(id);
9086
+ if (pending2) {
9007
9087
  this.pendingTools.delete(id);
9008
- pending.resolve(result);
9088
+ pending2.resolve(result);
9009
9089
  } else if (!this.running) {
9010
9090
  log15.info("Late tool_result while idle, dismissing", { id });
9011
9091
  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
  }
@@ -9861,9 +9942,9 @@ var init_headless = __esm({
9861
9942
  if (this.currentAbort) {
9862
9943
  this.currentAbort.abort();
9863
9944
  }
9864
- for (const [id, pending] of this.pendingTools) {
9865
- clearTimeout(pending.timeout);
9866
- pending.resolve(USER_CANCELLED_RESULT);
9945
+ for (const [id, pending2] of this.pendingTools) {
9946
+ clearTimeout(pending2.timeout);
9947
+ pending2.resolve(USER_CANCELLED_RESULT);
9867
9948
  this.pendingTools.delete(id);
9868
9949
  }
9869
9950
  return this.queue.removeWhere((item) => item.source !== "user");
@@ -9900,10 +9981,10 @@ var init_headless = __esm({
9900
9981
  if (action === "tool_result" && parsed.id) {
9901
9982
  const id = parsed.id;
9902
9983
  const result = parsed.result ?? "";
9903
- const pending = this.pendingTools.get(id);
9904
- if (pending) {
9984
+ const pending2 = this.pendingTools.get(id);
9985
+ if (pending2) {
9905
9986
  this.pendingTools.delete(id);
9906
- pending.resolve(result);
9987
+ pending2.resolve(result);
9907
9988
  } else if (!this.running) {
9908
9989
  log15.info("Late tool_result while idle, dismissing", { id });
9909
9990
  this.emit("completed", { success: true }, requestId);
@@ -1,13 +1,31 @@
1
1
  # Files & Storage
2
2
 
3
- Per-app blob storage the twin of `db` (`db` stores rows; `files` stores files: user uploads,
4
- generated documents, images, marketing assets). **Private by default.** Files serve on the app's own
5
- domain.
6
-
7
- **File stores are always live there is no dev copy.** Every `put`/`delete`/overwrite hits
8
- production storage immediately and irreversibly. And unlike the database, **scenarios never reset file
9
- stores** — a scenario truncates DB tables but leaves files untouched, so files are not a "clean slate"
10
- you can re-seed, and orphaned files accumulate across runs. Delete deliberately.
3
+ Per-app blob storage: user uploads, generated documents, images, marketing assets. **Think of a store
4
+ as a CDN-backed bucket the app talks to not app-defined state like the database.** You declare the
5
+ store; what lands in it is arbitrary durable blobs the app doesn't model — no schema, nothing to
6
+ migrate, no dev/prod sync. **Private by default**, served on the app's own domain. (The API is
7
+ *shaped* like `db` `defineStore` at module scope, import the handle, like `defineTable` — but the
8
+ mental model is a bucket, not rows.)
9
+
10
+ ## How a store behaves (read before the API)
11
+
12
+ - **One store, shared across dev and prod — on purpose.** There's no dev copy: a file you upload in
13
+ the dev editor (a marketing image, a corpus to vectorize) is *already there in prod* at the same
14
+ stable URL. That continuity is a feature — don't fork buckets per environment.
15
+ - **Creates are safe by default**, because keys default to unique — `put()` mints a UUID (or a
16
+ content-addressed hash) when you don't pass one, so a dev write and a prod write land at different
17
+ keys and coexist. A collision only happens when you *choose* a fixed key.
18
+ - **Care goes on the destructive / fixed-key operations**, not on writing in general: `delete(key)`
19
+ and overwriting a **stable key** (e.g. `config/latest.json`) reach the one live store, so a dev run
20
+ can clobber what prod serves. A `put()` with a default key can't. (There's intentionally no bulk
21
+ "clear the store".)
22
+ - **Scenarios don't touch files — and that's correct.** A scenario truncates DB tables to seed test
23
+ *rows*; files are durable and left alone. A store isn't a "clean slate" you re-seed each run — upload
24
+ a test file once in dev and it stays. Accumulation is normal for an asset store; don't write
25
+ `clear()`-style reset helpers.
26
+ - **Need dev and prod to *not* share** something (mutable fixed-key state, or sensitive uploads a
27
+ developer shouldn't see)? There's no per-store isolation switch — scope the key yourself (e.g.
28
+ `config/${env}/…`). Rare; the shared default is right almost always.
11
29
 
12
30
  ## Defining a store
13
31
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.252",
3
+ "version": "0.1.254",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",