@mindstudio-ai/remy 0.1.143 → 0.1.145

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
@@ -685,13 +685,13 @@ var log4 = createLogger("compaction");
685
685
  var CONVERSATION_SUMMARY_PROMPT = readAsset("compaction", "conversation.md");
686
686
  var SUBAGENT_SUMMARY_PROMPT = readAsset("compaction", "subagent.md");
687
687
  var SUMMARIZABLE_SUBAGENTS = ["visualDesignExpert", "productVision"];
688
- async function compactConversation(state, apiConfig, system, tools2) {
689
- const insertionIndex = findSafeInsertionPoint(state.messages);
688
+ async function compactConversation(messages, apiConfig, system, tools2) {
689
+ const endIndex = findSafeInsertionPoint(messages);
690
690
  const summaries = [];
691
691
  const tasks = [];
692
692
  const conversationMessages = getConversationMessagesForSummary(
693
- state.messages,
694
- insertionIndex
693
+ messages,
694
+ endIndex
695
695
  );
696
696
  if (conversationMessages.length > 0) {
697
697
  tasks.push(
@@ -711,9 +711,9 @@ async function compactConversation(state, apiConfig, system, tools2) {
711
711
  }
712
712
  for (const name of SUMMARIZABLE_SUBAGENTS) {
713
713
  const subagentMessages = getSubAgentMessagesForSummary(
714
- state.messages,
714
+ messages,
715
715
  name,
716
- insertionIndex
716
+ endIndex
717
717
  );
718
718
  if (subagentMessages.length > 0) {
719
719
  tasks.push(
@@ -745,14 +745,8 @@ async function compactConversation(state, apiConfig, system, tools2) {
745
745
  }
746
746
  ]
747
747
  }));
748
- if (checkpointMessages.length > 0) {
749
- state.messages.splice(insertionIndex, 0, ...checkpointMessages);
750
- }
751
- log4.info("Compaction complete", {
752
- summaries: summaries.length,
753
- insertionIndex,
754
- messagesAfter: state.messages.length - insertionIndex - checkpointMessages.length
755
- });
748
+ log4.info("Compaction complete", { summaries: summaries.length });
749
+ return checkpointMessages;
756
750
  }
757
751
  function findSafeInsertionPoint(messages) {
758
752
  let idx = messages.length;
@@ -4165,7 +4159,7 @@ var definition6 = {
4165
4159
  },
4166
4160
  transparentBackground: {
4167
4161
  type: "boolean",
4168
- description: "Remove the background from generated images, producing transparent PNGs. Useful for icons, logos, product shots, and assets that need to be composited onto other backgrounds."
4162
+ description: "Remove the background from generated images, producing transparent PNGs trimmed to the subject bounds. Useful for icons, logos, product shots, and assets that need to be composited onto other backgrounds."
4169
4163
  }
4170
4164
  },
4171
4165
  required: ["prompts"]
@@ -5140,9 +5134,31 @@ function executeTool(name, input, context) {
5140
5134
  return tool.execute(input, context);
5141
5135
  }
5142
5136
 
5137
+ // src/compaction/trigger.ts
5138
+ var log7 = createLogger("compaction:trigger");
5139
+ var pendingSummaries = [];
5140
+ function getPendingSummaries() {
5141
+ return pendingSummaries.splice(0);
5142
+ }
5143
+ function triggerCompaction(state, apiConfig, callbacks) {
5144
+ callbacks?.onStart?.();
5145
+ const system = buildSystemPrompt("onboardingFinished");
5146
+ const tools2 = getToolDefinitions("onboardingFinished");
5147
+ compactConversation(state.messages, apiConfig, system, tools2).then((summaries) => {
5148
+ pendingSummaries.push(...summaries);
5149
+ callbacks?.onSummariesReady?.();
5150
+ log7.info("Compaction complete");
5151
+ }).catch((err) => {
5152
+ callbacks?.onError?.(err.message || "Compaction failed");
5153
+ log7.error("Compaction failed", { error: err.message });
5154
+ }).finally(() => {
5155
+ callbacks?.onFinally?.();
5156
+ });
5157
+ }
5158
+
5143
5159
  // src/session.ts
5144
5160
  import fs19 from "fs";
5145
- var log7 = createLogger("session");
5161
+ var log8 = createLogger("session");
5146
5162
  var SESSION_FILE = ".remy-session.json";
5147
5163
  function loadSession(state) {
5148
5164
  try {
@@ -5150,7 +5166,7 @@ function loadSession(state) {
5150
5166
  const data = JSON.parse(raw);
5151
5167
  if (Array.isArray(data.messages) && data.messages.length > 0) {
5152
5168
  state.messages = sanitizeMessages(data.messages);
5153
- log7.info("Session loaded", { messageCount: state.messages.length });
5169
+ log8.info("Session loaded", { messageCount: state.messages.length });
5154
5170
  return true;
5155
5171
  }
5156
5172
  } catch {
@@ -5200,9 +5216,9 @@ function saveSession(state) {
5200
5216
  JSON.stringify({ messages: state.messages }, null, 2),
5201
5217
  "utf-8"
5202
5218
  );
5203
- log7.info("Session saved", { messageCount: state.messages.length });
5219
+ log8.info("Session saved", { messageCount: state.messages.length });
5204
5220
  } catch (err) {
5205
- log7.warn("Session save failed", { error: err.message });
5221
+ log8.warn("Session save failed", { error: err.message });
5206
5222
  }
5207
5223
  }
5208
5224
  function clearSession(state) {
@@ -5213,24 +5229,6 @@ function clearSession(state) {
5213
5229
  }
5214
5230
  }
5215
5231
 
5216
- // src/compaction/trigger.ts
5217
- var log8 = createLogger("compaction:trigger");
5218
- function triggerCompaction(state, apiConfig, callbacks) {
5219
- callbacks?.onStart?.();
5220
- const system = buildSystemPrompt("onboardingFinished");
5221
- const tools2 = getToolDefinitions("onboardingFinished");
5222
- compactConversation(state, apiConfig, system, tools2).then(() => {
5223
- saveSession(state);
5224
- callbacks?.onComplete?.();
5225
- log8.info("Compaction complete");
5226
- }).catch((err) => {
5227
- callbacks?.onError?.(err.message || "Compaction failed");
5228
- log8.error("Compaction failed", { error: err.message });
5229
- }).finally(() => {
5230
- callbacks?.onFinally?.();
5231
- });
5232
- }
5233
-
5234
5232
  // src/parsePartialJson.ts
5235
5233
  var PartialJSON = class extends Error {
5236
5234
  };
@@ -6157,6 +6155,15 @@ ${xmlParts}
6157
6155
  }
6158
6156
  }
6159
6157
  }
6158
+ function applyPendingSummaries() {
6159
+ const summaries = getPendingSummaries();
6160
+ if (summaries.length === 0) {
6161
+ return;
6162
+ }
6163
+ const idx = findSafeInsertionPoint(state.messages);
6164
+ state.messages.splice(idx, 0, ...summaries);
6165
+ saveSession(state);
6166
+ }
6160
6167
  function onBackgroundComplete(toolCallId, name, result, subAgentMessages) {
6161
6168
  pendingBlockUpdates.push({ toolCallId, result, subAgentMessages });
6162
6169
  log11.info("Background complete", {
@@ -6238,6 +6245,7 @@ ${xmlParts}
6238
6245
  rid
6239
6246
  );
6240
6247
  setTimeout(() => {
6248
+ applyPendingSummaries();
6241
6249
  applyPendingBlockUpdates();
6242
6250
  flushBackgroundQueue();
6243
6251
  }, 0);
@@ -6549,7 +6557,10 @@ ${xmlParts}
6549
6557
  } catch {
6550
6558
  }
6551
6559
  },
6552
- onComplete: () => {
6560
+ onSummariesReady: () => {
6561
+ if (!running) {
6562
+ applyPendingSummaries();
6563
+ }
6553
6564
  emit("compaction_complete", {}, requestId);
6554
6565
  emit("completed", { success: true }, requestId);
6555
6566
  },
package/dist/index.js CHANGED
@@ -1326,13 +1326,13 @@ var init_assets = __esm({
1326
1326
  });
1327
1327
 
1328
1328
  // src/compaction/index.ts
1329
- async function compactConversation(state, apiConfig, system, tools2) {
1330
- const insertionIndex = findSafeInsertionPoint(state.messages);
1329
+ async function compactConversation(messages, apiConfig, system, tools2) {
1330
+ const endIndex = findSafeInsertionPoint(messages);
1331
1331
  const summaries = [];
1332
1332
  const tasks = [];
1333
1333
  const conversationMessages = getConversationMessagesForSummary(
1334
- state.messages,
1335
- insertionIndex
1334
+ messages,
1335
+ endIndex
1336
1336
  );
1337
1337
  if (conversationMessages.length > 0) {
1338
1338
  tasks.push(
@@ -1352,9 +1352,9 @@ async function compactConversation(state, apiConfig, system, tools2) {
1352
1352
  }
1353
1353
  for (const name of SUMMARIZABLE_SUBAGENTS) {
1354
1354
  const subagentMessages = getSubAgentMessagesForSummary(
1355
- state.messages,
1355
+ messages,
1356
1356
  name,
1357
- insertionIndex
1357
+ endIndex
1358
1358
  );
1359
1359
  if (subagentMessages.length > 0) {
1360
1360
  tasks.push(
@@ -1386,14 +1386,8 @@ async function compactConversation(state, apiConfig, system, tools2) {
1386
1386
  }
1387
1387
  ]
1388
1388
  }));
1389
- if (checkpointMessages.length > 0) {
1390
- state.messages.splice(insertionIndex, 0, ...checkpointMessages);
1391
- }
1392
- log2.info("Compaction complete", {
1393
- summaries: summaries.length,
1394
- insertionIndex,
1395
- messagesAfter: state.messages.length - insertionIndex - checkpointMessages.length
1396
- });
1389
+ log2.info("Compaction complete", { summaries: summaries.length });
1390
+ return checkpointMessages;
1397
1391
  }
1398
1392
  function findSafeInsertionPoint(messages) {
1399
1393
  let idx = messages.length;
@@ -1909,112 +1903,35 @@ var init_prompt = __esm({
1909
1903
  }
1910
1904
  });
1911
1905
 
1912
- // src/session.ts
1913
- import fs10 from "fs";
1914
- function loadSession(state) {
1915
- try {
1916
- const raw = fs10.readFileSync(SESSION_FILE, "utf-8");
1917
- const data = JSON.parse(raw);
1918
- if (Array.isArray(data.messages) && data.messages.length > 0) {
1919
- state.messages = sanitizeMessages(data.messages);
1920
- log4.info("Session loaded", { messageCount: state.messages.length });
1921
- return true;
1922
- }
1923
- } catch {
1924
- }
1925
- return false;
1926
- }
1927
- function sanitizeMessages(messages) {
1928
- const result = [];
1929
- for (let i = 0; i < messages.length; i++) {
1930
- result.push(messages[i]);
1931
- const msg = messages[i];
1932
- if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
1933
- continue;
1934
- }
1935
- const toolBlocks = msg.content.filter(
1936
- (b) => b.type === "tool"
1937
- );
1938
- if (toolBlocks.length === 0) {
1939
- continue;
1940
- }
1941
- const resultIds = /* @__PURE__ */ new Set();
1942
- for (let j = i + 1; j < messages.length; j++) {
1943
- const next = messages[j];
1944
- if (next.role === "user" && next.toolCallId) {
1945
- resultIds.add(next.toolCallId);
1946
- } else {
1947
- break;
1948
- }
1949
- }
1950
- for (const tc of toolBlocks) {
1951
- if (!resultIds.has(tc.id)) {
1952
- result.push({
1953
- role: "user",
1954
- content: "Error: tool result lost (session recovered)",
1955
- toolCallId: tc.id,
1956
- isToolError: true
1957
- });
1958
- }
1959
- }
1960
- }
1961
- return result;
1962
- }
1963
- function saveSession(state) {
1964
- try {
1965
- fs10.writeFileSync(
1966
- SESSION_FILE,
1967
- JSON.stringify({ messages: state.messages }, null, 2),
1968
- "utf-8"
1969
- );
1970
- log4.info("Session saved", { messageCount: state.messages.length });
1971
- } catch (err) {
1972
- log4.warn("Session save failed", { error: err.message });
1973
- }
1974
- }
1975
- function clearSession(state) {
1976
- state.messages = [];
1977
- try {
1978
- fs10.unlinkSync(SESSION_FILE);
1979
- } catch {
1980
- }
1981
- }
1982
- var log4, SESSION_FILE;
1983
- var init_session = __esm({
1984
- "src/session.ts"() {
1985
- "use strict";
1986
- init_logger();
1987
- log4 = createLogger("session");
1988
- SESSION_FILE = ".remy-session.json";
1989
- }
1990
- });
1991
-
1992
1906
  // src/compaction/trigger.ts
1907
+ function getPendingSummaries() {
1908
+ return pendingSummaries.splice(0);
1909
+ }
1993
1910
  function triggerCompaction(state, apiConfig, callbacks) {
1994
1911
  callbacks?.onStart?.();
1995
1912
  const system = buildSystemPrompt("onboardingFinished");
1996
1913
  const tools2 = getToolDefinitions("onboardingFinished");
1997
- compactConversation(state, apiConfig, system, tools2).then(() => {
1998
- saveSession(state);
1999
- callbacks?.onComplete?.();
2000
- log5.info("Compaction complete");
1914
+ compactConversation(state.messages, apiConfig, system, tools2).then((summaries) => {
1915
+ pendingSummaries.push(...summaries);
1916
+ callbacks?.onSummariesReady?.();
1917
+ log4.info("Compaction complete");
2001
1918
  }).catch((err) => {
2002
1919
  callbacks?.onError?.(err.message || "Compaction failed");
2003
- log5.error("Compaction failed", { error: err.message });
1920
+ log4.error("Compaction failed", { error: err.message });
2004
1921
  }).finally(() => {
2005
1922
  callbacks?.onFinally?.();
2006
1923
  });
2007
1924
  }
2008
- var log5;
1925
+ var log4, pendingSummaries;
2009
1926
  var init_trigger = __esm({
2010
1927
  "src/compaction/trigger.ts"() {
2011
1928
  "use strict";
2012
1929
  init_compaction();
2013
1930
  init_prompt();
2014
1931
  init_tools6();
2015
- init_session();
2016
1932
  init_logger();
2017
- log5 = createLogger("compaction:trigger");
1933
+ log4 = createLogger("compaction:trigger");
1934
+ pendingSummaries = [];
2018
1935
  }
2019
1936
  });
2020
1937
 
@@ -2049,7 +1966,7 @@ var init_compactConversation = __esm({
2049
1966
  });
2050
1967
 
2051
1968
  // src/tools/code/readFile.ts
2052
- import fs11 from "fs/promises";
1969
+ import fs10 from "fs/promises";
2053
1970
  function isBinary(buffer) {
2054
1971
  const sample = buffer.subarray(0, 8192);
2055
1972
  for (let i = 0; i < sample.length; i++) {
@@ -2090,7 +2007,7 @@ var init_readFile = __esm({
2090
2007
  },
2091
2008
  async execute(input) {
2092
2009
  try {
2093
- const buffer = await fs11.readFile(input.path);
2010
+ const buffer = await fs10.readFile(input.path);
2094
2011
  if (isBinary(buffer)) {
2095
2012
  const size = buffer.length;
2096
2013
  const unit = size > 1024 * 1024 ? `${(size / (1024 * 1024)).toFixed(1)}MB` : `${(size / 1024).toFixed(1)}KB`;
@@ -2126,7 +2043,7 @@ var init_readFile = __esm({
2126
2043
  });
2127
2044
 
2128
2045
  // src/tools/code/writeFile.ts
2129
- import fs12 from "fs/promises";
2046
+ import fs11 from "fs/promises";
2130
2047
  import path5 from "path";
2131
2048
  var writeFileTool;
2132
2049
  var init_writeFile = __esm({
@@ -2170,7 +2087,7 @@ var init_writeFile = __esm({
2170
2087
  lastNewlineCount = newlineCount;
2171
2088
  const lastNewline = partial.content.lastIndexOf("\n");
2172
2089
  const completeContent = partial.content.substring(0, lastNewline + 1);
2173
- const oldContent = await fs12.readFile(partial.path, "utf-8").catch(() => "");
2090
+ const oldContent = await fs11.readFile(partial.path, "utf-8").catch(() => "");
2174
2091
  return `Writing ${partial.path} (${newlineCount} lines)
2175
2092
  ${unifiedDiff(partial.path, oldContent, completeContent)}`;
2176
2093
  }
@@ -2179,13 +2096,13 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
2179
2096
  async execute(input) {
2180
2097
  const release = await acquireFileLock(input.path);
2181
2098
  try {
2182
- await fs12.mkdir(path5.dirname(input.path), { recursive: true });
2099
+ await fs11.mkdir(path5.dirname(input.path), { recursive: true });
2183
2100
  let oldContent = null;
2184
2101
  try {
2185
- oldContent = await fs12.readFile(input.path, "utf-8");
2102
+ oldContent = await fs11.readFile(input.path, "utf-8");
2186
2103
  } catch {
2187
2104
  }
2188
- await fs12.writeFile(input.path, input.content, "utf-8");
2105
+ await fs11.writeFile(input.path, input.content, "utf-8");
2189
2106
  const lineCount = input.content.split("\n").length;
2190
2107
  const label = oldContent !== null ? "Wrote" : "Created";
2191
2108
  return `${label} ${input.path} (${lineCount} lines)
@@ -2285,7 +2202,7 @@ var init_helpers2 = __esm({
2285
2202
  });
2286
2203
 
2287
2204
  // src/tools/code/editFile/index.ts
2288
- import fs13 from "fs/promises";
2205
+ import fs12 from "fs/promises";
2289
2206
  var editFileTool;
2290
2207
  var init_editFile = __esm({
2291
2208
  "src/tools/code/editFile/index.ts"() {
@@ -2324,7 +2241,7 @@ var init_editFile = __esm({
2324
2241
  async execute(input) {
2325
2242
  const release = await acquireFileLock(input.path);
2326
2243
  try {
2327
- const content = await fs13.readFile(input.path, "utf-8");
2244
+ const content = await fs12.readFile(input.path, "utf-8");
2328
2245
  const { old_string, new_string, replace_all } = input;
2329
2246
  const occurrences = findOccurrences(content, old_string);
2330
2247
  if (replace_all) {
@@ -2340,7 +2257,7 @@ var init_editFile = __esm({
2340
2257
  new_string
2341
2258
  );
2342
2259
  }
2343
- await fs13.writeFile(input.path, updated, "utf-8");
2260
+ await fs12.writeFile(input.path, updated, "utf-8");
2344
2261
  return `Replaced ${occurrences.length} occurrence${occurrences.length > 1 ? "s" : ""} in ${input.path}
2345
2262
  ${unifiedDiff(input.path, content, updated)}`;
2346
2263
  }
@@ -2351,7 +2268,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2351
2268
  old_string.length,
2352
2269
  new_string
2353
2270
  );
2354
- await fs13.writeFile(input.path, updated, "utf-8");
2271
+ await fs12.writeFile(input.path, updated, "utf-8");
2355
2272
  return `Updated ${input.path}
2356
2273
  ${unifiedDiff(input.path, content, updated)}`;
2357
2274
  }
@@ -2367,7 +2284,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2367
2284
  flex.matchedText.length,
2368
2285
  new_string
2369
2286
  );
2370
- await fs13.writeFile(input.path, updated, "utf-8");
2287
+ await fs12.writeFile(input.path, updated, "utf-8");
2371
2288
  return `Updated ${input.path} (matched with flexible whitespace at line ${flex.line})
2372
2289
  ${unifiedDiff(input.path, content, updated)}`;
2373
2290
  }
@@ -2598,10 +2515,10 @@ var init_glob = __esm({
2598
2515
  });
2599
2516
 
2600
2517
  // src/tools/code/listDir.ts
2601
- import fs14 from "fs/promises";
2518
+ import fs13 from "fs/promises";
2602
2519
  import path6 from "path";
2603
2520
  async function readAndSort(dirPath) {
2604
- const entries = await fs14.readdir(dirPath, { withFileTypes: true });
2521
+ const entries = await fs13.readdir(dirPath, { withFileTypes: true });
2605
2522
  return entries.filter((e) => !EXCLUDE.has(e.name)).sort((a, b) => {
2606
2523
  if (a.isDirectory() && !b.isDirectory()) {
2607
2524
  return -1;
@@ -2642,7 +2559,7 @@ function formatSize(bytes) {
2642
2559
  }
2643
2560
  async function formatFile(dirPath, name, indent) {
2644
2561
  try {
2645
- const stat = await fs14.stat(path6.join(dirPath, name));
2562
+ const stat = await fs13.stat(path6.join(dirPath, name));
2646
2563
  return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat.size)}`;
2647
2564
  } catch {
2648
2565
  return `${indent}${name}`;
@@ -3301,7 +3218,7 @@ async function runSubAgent(config) {
3301
3218
  const signal = background ? bgAbort.signal : parentSignal;
3302
3219
  const agentName = subAgentId || "sub-agent";
3303
3220
  const runStart = Date.now();
3304
- log6.info("Sub-agent started", { requestId, parentToolId, agentName });
3221
+ log5.info("Sub-agent started", { requestId, parentToolId, agentName });
3305
3222
  const emit2 = (e) => {
3306
3223
  onEvent({ ...e, parentToolId });
3307
3224
  };
@@ -3475,7 +3392,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
3475
3392
  ...hasArtifacts ? { artifacts } : {}
3476
3393
  };
3477
3394
  }
3478
- log6.info("Tools executing", {
3395
+ log5.info("Tools executing", {
3479
3396
  requestId,
3480
3397
  parentToolId,
3481
3398
  count: toolCalls.length,
@@ -3552,7 +3469,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
3552
3469
  run2(tc.input);
3553
3470
  const r = await resultPromise;
3554
3471
  toolRegistry?.unregister(tc.id);
3555
- log6.info("Tool completed", {
3472
+ log5.info("Tool completed", {
3556
3473
  requestId,
3557
3474
  parentToolId,
3558
3475
  toolCallId: tc.id,
@@ -3603,7 +3520,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
3603
3520
  const wrapRun = async () => {
3604
3521
  try {
3605
3522
  const result = await run();
3606
- log6.info("Sub-agent complete", {
3523
+ log5.info("Sub-agent complete", {
3607
3524
  requestId,
3608
3525
  parentToolId,
3609
3526
  agentName,
@@ -3612,7 +3529,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
3612
3529
  });
3613
3530
  return result;
3614
3531
  } catch (err) {
3615
- log6.warn("Sub-agent error", {
3532
+ log5.warn("Sub-agent error", {
3616
3533
  requestId,
3617
3534
  parentToolId,
3618
3535
  agentName,
@@ -3624,7 +3541,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
3624
3541
  if (!background) {
3625
3542
  return wrapRun();
3626
3543
  }
3627
- log6.info("Sub-agent backgrounded", { requestId, parentToolId, agentName });
3544
+ log5.info("Sub-agent backgrounded", { requestId, parentToolId, agentName });
3628
3545
  toolRegistry?.register({
3629
3546
  id: parentToolId,
3630
3547
  name: agentName,
@@ -3651,7 +3568,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
3651
3568
  });
3652
3569
  return { text: ack, messages: [], backgrounded: true };
3653
3570
  }
3654
- var log6;
3571
+ var log5;
3655
3572
  var init_runner = __esm({
3656
3573
  "src/subagents/runner.ts"() {
3657
3574
  "use strict";
@@ -3659,7 +3576,7 @@ var init_runner = __esm({
3659
3576
  init_logger();
3660
3577
  init_statusWatcher();
3661
3578
  init_cleanMessages();
3662
- log6 = createLogger("sub-agent");
3579
+ log5 = createLogger("sub-agent");
3663
3580
  }
3664
3581
  });
3665
3582
 
@@ -3803,10 +3720,10 @@ var init_tools = __esm({
3803
3720
  });
3804
3721
 
3805
3722
  // src/subagents/browserAutomation/prompt.ts
3806
- import fs15 from "fs";
3723
+ import fs14 from "fs";
3807
3724
  function getBrowserAutomationPrompt() {
3808
3725
  try {
3809
- const appSpec = fs15.readFileSync("src/app.md", "utf-8").trim();
3726
+ const appSpec = fs14.readFileSync("src/app.md", "utf-8").trim();
3810
3727
  return `${BASE_PROMPT}
3811
3728
 
3812
3729
  <!-- cache_breakpoint -->
@@ -3828,7 +3745,7 @@ var init_prompt2 = __esm({
3828
3745
  });
3829
3746
 
3830
3747
  // src/subagents/browserAutomation/index.ts
3831
- var log7, browserAutomationTool;
3748
+ var log6, browserAutomationTool;
3832
3749
  var init_browserAutomation = __esm({
3833
3750
  "src/subagents/browserAutomation/index.ts"() {
3834
3751
  "use strict";
@@ -3840,7 +3757,7 @@ var init_browserAutomation = __esm({
3840
3757
  init_screenshot();
3841
3758
  init_runCli();
3842
3759
  init_logger();
3843
- log7 = createLogger("browser-automation");
3760
+ log6 = createLogger("browser-automation");
3844
3761
  browserAutomationTool = {
3845
3762
  clearable: true,
3846
3763
  definition: {
@@ -3946,7 +3863,7 @@ var init_browserAutomation = __esm({
3946
3863
  }
3947
3864
  }
3948
3865
  } catch {
3949
- log7.debug("Failed to parse batch analysis result", {
3866
+ log6.debug("Failed to parse batch analysis result", {
3950
3867
  batchResult
3951
3868
  });
3952
3869
  }
@@ -4661,7 +4578,7 @@ var init_generateImages = __esm({
4661
4578
  },
4662
4579
  transparentBackground: {
4663
4580
  type: "boolean",
4664
- description: "Remove the background from generated images, producing transparent PNGs. Useful for icons, logos, product shots, and assets that need to be composited onto other backgrounds."
4581
+ description: "Remove the background from generated images, producing transparent PNGs trimmed to the subject bounds. Useful for icons, logos, product shots, and assets that need to be composited onto other backgrounds."
4665
4582
  }
4666
4583
  },
4667
4584
  required: ["prompts"]
@@ -4770,12 +4687,12 @@ var init_tools3 = __esm({
4770
4687
  });
4771
4688
 
4772
4689
  // src/subagents/common/context.ts
4773
- import fs16 from "fs";
4690
+ import fs15 from "fs";
4774
4691
  import path7 from "path";
4775
4692
  function walkMdFiles2(dir, skip) {
4776
4693
  const files = [];
4777
4694
  try {
4778
- for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
4695
+ for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
4779
4696
  const full = path7.join(dir, entry.name);
4780
4697
  if (entry.isDirectory()) {
4781
4698
  if (!skip?.has(entry.name)) {
@@ -4791,7 +4708,7 @@ function walkMdFiles2(dir, skip) {
4791
4708
  }
4792
4709
  function parseFrontmatter2(filePath) {
4793
4710
  try {
4794
- const content = fs16.readFileSync(filePath, "utf-8");
4711
+ const content = fs15.readFileSync(filePath, "utf-8");
4795
4712
  const match = content.match(/^---\n([\s\S]*?)\n---/);
4796
4713
  if (!match) {
4797
4714
  return {};
@@ -4837,7 +4754,7 @@ function loadRoadmapIndex() {
4837
4754
  const parts = [];
4838
4755
  try {
4839
4756
  const indexJson = JSON.parse(
4840
- fs16.readFileSync("src/roadmap/index.json", "utf-8")
4757
+ fs15.readFileSync("src/roadmap/index.json", "utf-8")
4841
4758
  );
4842
4759
  if (indexJson.lanes?.length > 0) {
4843
4760
  const laneLines = indexJson.lanes.map(
@@ -4945,7 +4862,7 @@ var init_context = __esm({
4945
4862
  });
4946
4863
 
4947
4864
  // src/subagents/designExpert/data/sampleCache.ts
4948
- import fs17 from "fs";
4865
+ import fs16 from "fs";
4949
4866
  function generateIndices(poolSize, sampleSize) {
4950
4867
  const n = Math.min(sampleSize, poolSize);
4951
4868
  const indices = Array.from({ length: poolSize }, (_, i) => i);
@@ -4957,14 +4874,14 @@ function generateIndices(poolSize, sampleSize) {
4957
4874
  }
4958
4875
  function load() {
4959
4876
  try {
4960
- return JSON.parse(fs17.readFileSync(SAMPLE_FILE, "utf-8"));
4877
+ return JSON.parse(fs16.readFileSync(SAMPLE_FILE, "utf-8"));
4961
4878
  } catch {
4962
4879
  return null;
4963
4880
  }
4964
4881
  }
4965
4882
  function save(indices) {
4966
4883
  try {
4967
- fs17.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
4884
+ fs16.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
4968
4885
  } catch {
4969
4886
  }
4970
4887
  }
@@ -5361,7 +5278,7 @@ var init_tools4 = __esm({
5361
5278
  });
5362
5279
 
5363
5280
  // src/subagents/productVision/executor.ts
5364
- import fs18 from "fs";
5281
+ import fs17 from "fs";
5365
5282
  import path8 from "path";
5366
5283
  function resolve(filePath) {
5367
5284
  return path8.join(ROADMAP_DIR, filePath);
@@ -5371,13 +5288,13 @@ async function executeVisionTool(name, input, context) {
5371
5288
  case "writeFile": {
5372
5289
  const filePath = resolve(input.path);
5373
5290
  try {
5374
- fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
5291
+ fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
5375
5292
  let oldContent = null;
5376
5293
  try {
5377
- oldContent = fs18.readFileSync(filePath, "utf-8");
5294
+ oldContent = fs17.readFileSync(filePath, "utf-8");
5378
5295
  } catch {
5379
5296
  }
5380
- fs18.writeFileSync(filePath, input.content, "utf-8");
5297
+ fs17.writeFileSync(filePath, input.content, "utf-8");
5381
5298
  const lineCount = input.content.split("\n").length;
5382
5299
  const label = oldContent !== null ? "Wrote" : "Created";
5383
5300
  return `${label} ${filePath} (${lineCount} lines)
@@ -5389,11 +5306,11 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
5389
5306
  case "deleteFile": {
5390
5307
  const filePath = resolve(input.path);
5391
5308
  try {
5392
- if (!fs18.existsSync(filePath)) {
5309
+ if (!fs17.existsSync(filePath)) {
5393
5310
  return `Error: ${filePath} does not exist`;
5394
5311
  }
5395
- const oldContent = fs18.readFileSync(filePath, "utf-8");
5396
- fs18.unlinkSync(filePath);
5312
+ const oldContent = fs17.readFileSync(filePath, "utf-8");
5313
+ fs17.unlinkSync(filePath);
5397
5314
  return `Deleted ${filePath}
5398
5315
  ${unifiedDiff(filePath, oldContent, "")}`;
5399
5316
  } catch (err) {
@@ -5406,8 +5323,8 @@ ${unifiedDiff(filePath, oldContent, "")}`;
5406
5323
  }
5407
5324
  const filePath = resolve("pitch.html");
5408
5325
  try {
5409
- fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
5410
- const existing = fs18.existsSync(filePath) ? fs18.readFileSync(filePath, "utf-8").trim() : "";
5326
+ fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
5327
+ const existing = fs17.existsSync(filePath) ? fs17.readFileSync(filePath, "utf-8").trim() : "";
5411
5328
  const currentDeck = existing || PITCH_DECK_SHELL;
5412
5329
  const task = `
5413
5330
  <pitch_content>${input.task}</pitch_content>
@@ -5432,7 +5349,7 @@ Respond only with the complete HTML file and absolutely no other text. Your resp
5432
5349
  /```(?:html|wireframe)\n([\s\S]*?)```/
5433
5350
  );
5434
5351
  const html = htmlMatch ? htmlMatch[1].trim() : result;
5435
- fs18.writeFileSync(filePath, html, "utf-8");
5352
+ fs17.writeFileSync(filePath, html, "utf-8");
5436
5353
  return `Pitch deck written successfully.`;
5437
5354
  } catch (err) {
5438
5355
  return `Error generating pitch deck: ${err.message}`;
@@ -5818,6 +5735,86 @@ var init_tools6 = __esm({
5818
5735
  }
5819
5736
  });
5820
5737
 
5738
+ // src/session.ts
5739
+ import fs18 from "fs";
5740
+ function loadSession(state) {
5741
+ try {
5742
+ const raw = fs18.readFileSync(SESSION_FILE, "utf-8");
5743
+ const data = JSON.parse(raw);
5744
+ if (Array.isArray(data.messages) && data.messages.length > 0) {
5745
+ state.messages = sanitizeMessages(data.messages);
5746
+ log7.info("Session loaded", { messageCount: state.messages.length });
5747
+ return true;
5748
+ }
5749
+ } catch {
5750
+ }
5751
+ return false;
5752
+ }
5753
+ function sanitizeMessages(messages) {
5754
+ const result = [];
5755
+ for (let i = 0; i < messages.length; i++) {
5756
+ result.push(messages[i]);
5757
+ const msg = messages[i];
5758
+ if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
5759
+ continue;
5760
+ }
5761
+ const toolBlocks = msg.content.filter(
5762
+ (b) => b.type === "tool"
5763
+ );
5764
+ if (toolBlocks.length === 0) {
5765
+ continue;
5766
+ }
5767
+ const resultIds = /* @__PURE__ */ new Set();
5768
+ for (let j = i + 1; j < messages.length; j++) {
5769
+ const next = messages[j];
5770
+ if (next.role === "user" && next.toolCallId) {
5771
+ resultIds.add(next.toolCallId);
5772
+ } else {
5773
+ break;
5774
+ }
5775
+ }
5776
+ for (const tc of toolBlocks) {
5777
+ if (!resultIds.has(tc.id)) {
5778
+ result.push({
5779
+ role: "user",
5780
+ content: "Error: tool result lost (session recovered)",
5781
+ toolCallId: tc.id,
5782
+ isToolError: true
5783
+ });
5784
+ }
5785
+ }
5786
+ }
5787
+ return result;
5788
+ }
5789
+ function saveSession(state) {
5790
+ try {
5791
+ fs18.writeFileSync(
5792
+ SESSION_FILE,
5793
+ JSON.stringify({ messages: state.messages }, null, 2),
5794
+ "utf-8"
5795
+ );
5796
+ log7.info("Session saved", { messageCount: state.messages.length });
5797
+ } catch (err) {
5798
+ log7.warn("Session save failed", { error: err.message });
5799
+ }
5800
+ }
5801
+ function clearSession(state) {
5802
+ state.messages = [];
5803
+ try {
5804
+ fs18.unlinkSync(SESSION_FILE);
5805
+ } catch {
5806
+ }
5807
+ }
5808
+ var log7, SESSION_FILE;
5809
+ var init_session = __esm({
5810
+ "src/session.ts"() {
5811
+ "use strict";
5812
+ init_logger();
5813
+ log7 = createLogger("session");
5814
+ SESSION_FILE = ".remy-session.json";
5815
+ }
5816
+ });
5817
+
5821
5818
  // src/parsePartialJson.ts
5822
5819
  function parsePartialJson(jsonString) {
5823
5820
  const length = jsonString.length;
@@ -6842,6 +6839,15 @@ ${xmlParts}
6842
6839
  }
6843
6840
  }
6844
6841
  }
6842
+ function applyPendingSummaries() {
6843
+ const summaries = getPendingSummaries();
6844
+ if (summaries.length === 0) {
6845
+ return;
6846
+ }
6847
+ const idx = findSafeInsertionPoint(state.messages);
6848
+ state.messages.splice(idx, 0, ...summaries);
6849
+ saveSession(state);
6850
+ }
6845
6851
  function onBackgroundComplete(toolCallId, name, result, subAgentMessages) {
6846
6852
  pendingBlockUpdates.push({ toolCallId, result, subAgentMessages });
6847
6853
  log11.info("Background complete", {
@@ -6923,6 +6929,7 @@ ${xmlParts}
6923
6929
  rid
6924
6930
  );
6925
6931
  setTimeout(() => {
6932
+ applyPendingSummaries();
6926
6933
  applyPendingBlockUpdates();
6927
6934
  flushBackgroundQueue();
6928
6935
  }, 0);
@@ -7234,7 +7241,10 @@ ${xmlParts}
7234
7241
  } catch {
7235
7242
  }
7236
7243
  },
7237
- onComplete: () => {
7244
+ onSummariesReady: () => {
7245
+ if (!running) {
7246
+ applyPendingSummaries();
7247
+ }
7238
7248
  emit("compaction_complete", {}, requestId);
7239
7249
  emit("completed", { success: true }, requestId);
7240
7250
  },
@@ -7287,6 +7297,7 @@ var init_headless = __esm({
7287
7297
  init_config();
7288
7298
  init_prompt();
7289
7299
  init_trigger();
7300
+ init_compaction();
7290
7301
  init_lsp();
7291
7302
  init_agent();
7292
7303
  init_session();
@@ -98,16 +98,18 @@ Even if the app is intuitive and easy to use, users showing up for the first tim
98
98
 
99
99
  ## What to Actively Avoid At All Costs
100
100
 
101
- - **Avoid generic fonts.** Overused defaults that strip away all personality. Instead: pick a distinctive Google Font that fits the app's character.
101
+ Always rely on the details provided by the design expert - their work is the source of truth for the design of the app. Be mindful of the following things to avoid as you work:
102
+
103
+ - **Avoid generic fonts.** Avoid overused defaults that strip away all personality.
102
104
  - **Avoid purple or indigo anything.** Purple gradients, purple buttons, purple accents are overused. The user will be dismissive of our designs if they come out looking purple or indigo. Avoid terracotta for similar reasons.
103
- - **Avoid colored left-border callout boxes.** Rounded divs with a thick colored `border-left` — the generic "info card" pattern. Instead: use typography, spacing, and background tints to create hierarchy. If you need to call something out, use a full subtle background or a top border.
104
- - **Avoid three equal boxes with icons.** The default AI landing page layout. Instead: use asymmetric layouts, varied column widths, or a single focused content area.
105
- - **Avoid timid color palettes.** Evenly distributed, non-committal colors. Instead: one or two dominant colors with sharp accents. Commit to a direction.
106
- - **Avoid card-heavy nested layouts.** Cards inside cards, everything boxed. Instead: use space, typography, and dividers to create hierarchy without extra containers.
107
- - **Avoid inconsistent spacing.** 12px here, 20px there, 8px somewhere else. Instead: define a spacing scale (4/8/12/16/24/32/48/64) and use it everywhere.
108
- - **Avoid components from different visual languages.** Rounded buttons next to square inputs, shadows mixed with flat design. Instead: pick one system and apply it consistently.
105
+ - **Avoid colored border callout boxes.** Avoid rounded divs with a thick colored `border-left|top` — the generic "info card" pattern. Instead: use typography, spacing, and background tints to create hierarchy. If you need to call something out, use a full subtle background or a top border.
106
+ - **Avoid three equal boxes with icons.** The default AI landing page layout. Instead: use asymmetric layouts, varied column widths, or a single focused content area. Avoid cards labelled things like: 1, 2, 3 - this feels generic and cheap.
107
+ - **Avoid timid color palettes.** Avoid evenly distributed, non-committal colors. Commit to a direction and be bold.
108
+ - **Avoid card-heavy nested layouts.** Avoid cards inside cards, everything boxed. Instead: use space, typography, and dividers to create hierarchy without extra containers.
109
+ - **Avoid inconsistent spacing.** Avoid 12px here, 20px there, 8px somewhere else. Instead: define a spacing scale (4/8/12/16/24/32/48/64) and use it everywhere.
110
+ - **Avoid components from different visual languages.** Avoid, e.g., rounded buttons next to square inputs, shadows mixed with flat design. Instead: pick one system and apply it consistently.
109
111
  - **Avoid long scrolling forms with no visual grouping.** Instead: group fields into sections with clear headings, cards, or stepped flows.
110
- - **Avoid cramped layouts.** Text pressed against edges, no room to breathe. Instead: generous padding, comfortable margins, let the content float.
112
+ - **Avoid cramped layouts.** Avoid text pressed against edges, no room to breathe. Instead: generous padding, comfortable margins, let the content float.
111
113
  - **Avoid loading states that are just a centered spinner on a blank page.** Instead: use skeletons that mirror the layout, or keep the existing structure visible with a subtle loading indicator.
112
114
 
113
115
  Most importantly: **Avoid any interface where the first reaction is "this looks like a demo" or "this looks like it was made with a website builder."**
@@ -70,7 +70,11 @@ Once you have written the draft and set the project onboarding state to "initial
70
70
  - When the user asks "is this ready?" — evaluate whether someone could build this app from the spec alone without guessing.
71
71
 
72
72
  **Building from the spec:**
73
- When the user clicks "Build," you will receive a build command. Follow the instructions in the build comment plan, build, polish, verify). Build everything in one turn: methods, tables, interfaces, manifest updates, and scenarios, using the spec as the master plan. Build only what's in the core spec files (app.md, interfaces, brand). Ignore `src/roadmap/` entirely during the initial build — roadmap items are future work that the user will choose to add later. The onboarding state transitions are handled automatically as part of the build command.
73
+ When the user clicks "Build," you will receive a build command. Follow the instructions in the build comment plan, build, polish, verify). Build everything in one turn: methods, tables, interfaces, manifest updates, and scenarios, using the spec as the master plan. Build only what's in the core spec files (app.md, interfaces, brand). Ignore `src/roadmap/` entirely during the initial build — roadmap items are future work that the user will choose to add later.
74
+
75
+ When you have finished building, verify your work, then be sure to do a thorough pass to polish. Re-read the spec files and the design expert's guidance, then walk through each frontend file looking for design details that got skipped in the initial build: animations, transitions, hover states, micro-interactions, spring physics, entrance reveals, gesture handling, layout issues, and anything else.
76
+
77
+ The initial build prioritizes getting everything connected and functional, but this polish pass closes the gap between "it works" and "it feels great." In many ways this is *the* most important part of the initial build, as the user's first experience of the deliverable will set their expectations for every iteration that follows. Don't mess this up.
74
78
 
75
79
  **Scenarios are required.** Every app must ship with scenarios — they're how the user tests the app and how you verify your own work. Write at minimum:
76
80
  - A **realistic data scenario** with enough sample records to make the app feel populated and alive (5-20 rows depending on the app). Use plausible names, dates, amounts — not "test 1", "test 2".
@@ -27,6 +27,9 @@ The user can already see your tool calls, so most of your work is visible withou
27
27
 
28
28
  Skip the rest: narrating what you're about to do, restating what the user asked, explaining tool calls they can already see.
29
29
 
30
+ ### User attachments
31
+ User messages may include uploaded documents (PDFs, Word docs, etc.) as XML blocks prepended to the message content (e.g., `<user_uploaded_document_1>`). These are inline in the conversation history, not files in the project directory. When a user says "here is the document" or "use this document," the document content is in that same message. Do not ask the user to re-share a document that is already in the conversation.
32
+
30
33
  ### Automated messages
31
34
  You will occasionally receive automated messages prefixed with `@@automated_message@@` - these are triggered by things like background agents returning their work, or by the user clicking a button in the UI (e.g., the user might click a "Build Feature" button in the product roadmap UI, and you will receive a message detailing what they want to build). You will be able to see these messages in your chat history but the user will not see them, so acknowledge them appropriately and then perform the requested work.
32
35
 
@@ -33,7 +33,7 @@ These are non-negotiable. Violating them produces bad output.
33
33
  You'll receive context about the generation parameters. Use them:
34
34
 
35
35
  - **Dimensions**: If the image is wide (landscape), compose horizontally. If tall (portrait), compose vertically. If square, center the subject.
36
- - **Transparent background**: The background will be removed after generation. Don't describe elaborate backgrounds — focus on the subject. Describe it as an isolated element.
36
+ - **Transparent background**: The background will be removed after generation and the image will be trimmed to the subject bounds (no extra padding). Don't describe elaborate backgrounds — focus on the subject. Describe it as an isolated element.
37
37
 
38
38
  ## Photography prompts
39
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.143",
3
+ "version": "0.1.145",
4
4
  "description": "MindStudio coding agent",
5
5
  "repository": {
6
6
  "type": "git",