@mindstudio-ai/remy 0.1.164 → 0.1.165

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.
@@ -74,6 +74,20 @@ declare class HeadlessSession {
74
74
  private persistStats;
75
75
  /** Apply queued tool block updates to state.messages. Safe to call any time. */
76
76
  private applyPendingBlockUpdates;
77
+ /**
78
+ * Forced compaction gate. If lastContextSize exceeds the threshold, compact
79
+ * before letting the upcoming turn run. Coalesces with any in-flight
80
+ * compaction (e.g., one already started by /compact or a tool call). No
81
+ * timeout — compaction takes as long as it takes.
82
+ *
83
+ * Lifecycle events (`compaction_started` / `compaction_complete`) and
84
+ * stats updates are handled by the listener registered in start(); this
85
+ * method only awaits the promise and applies the resulting summaries.
86
+ *
87
+ * On compaction failure we don't bail — the turn proceeds and surfaces any
88
+ * downstream overflow through the existing "prompt is too long" path.
89
+ */
90
+ private runForcedCompactionIfNeeded;
77
91
  /** Drain pending compaction summaries and insert at a safe point. */
78
92
  private applyPendingSummaries;
79
93
  private onBackgroundComplete;
package/dist/headless.js CHANGED
@@ -1757,8 +1757,10 @@ var compactConversationTool = {
1757
1757
  }
1758
1758
  triggerCompaction(
1759
1759
  { messages: context.conversationMessages },
1760
- context.apiConfig
1761
- );
1760
+ context.apiConfig,
1761
+ { blocking: false, requestId: context.requestId }
1762
+ ).catch(() => {
1763
+ });
1762
1764
  return "Compaction started in the background.";
1763
1765
  }
1764
1766
  };
@@ -5184,23 +5186,40 @@ function executeTool(name, input, context) {
5184
5186
  // src/compaction/trigger.ts
5185
5187
  var log7 = createLogger("compaction:trigger");
5186
5188
  var pendingSummaries = [];
5189
+ var inflightCompaction = null;
5187
5190
  function getPendingSummaries() {
5188
5191
  return pendingSummaries.splice(0);
5189
5192
  }
5190
- function triggerCompaction(state, apiConfig, callbacks) {
5191
- callbacks?.onStart?.();
5193
+ var listener = null;
5194
+ function setCompactionListener(l) {
5195
+ listener = l;
5196
+ }
5197
+ function triggerCompaction(state, apiConfig, opts = {}) {
5198
+ if (inflightCompaction) {
5199
+ return inflightCompaction;
5200
+ }
5201
+ const { blocking = false, requestId } = opts;
5202
+ listener?.({ type: "started", blocking, requestId });
5192
5203
  const system = buildSystemPrompt("onboardingFinished");
5193
5204
  const tools2 = getToolDefinitions("onboardingFinished");
5194
- compactConversation(state.messages, apiConfig, system, tools2).then((summaries) => {
5205
+ inflightCompaction = compactConversation(
5206
+ state.messages,
5207
+ apiConfig,
5208
+ system,
5209
+ tools2
5210
+ ).then((summaries) => {
5195
5211
  pendingSummaries.push(...summaries);
5196
- callbacks?.onSummariesReady?.();
5212
+ listener?.({ type: "complete", requestId });
5197
5213
  log7.info("Compaction complete");
5198
5214
  }).catch((err) => {
5199
- callbacks?.onError?.(err.message || "Compaction failed");
5200
- log7.error("Compaction failed", { error: err.message });
5215
+ const message = err.message || "Compaction failed";
5216
+ listener?.({ type: "complete", error: message, requestId });
5217
+ log7.error("Compaction failed", { error: message });
5218
+ throw err;
5201
5219
  }).finally(() => {
5202
- callbacks?.onFinally?.();
5220
+ inflightCompaction = null;
5203
5221
  });
5222
+ return inflightCompaction;
5204
5223
  }
5205
5224
 
5206
5225
  // src/brandExtraction/index.ts
@@ -6628,6 +6647,7 @@ var USER_FACING_TOOLS = /* @__PURE__ */ new Set([
6628
6647
  "confirmDestructiveAction",
6629
6648
  "presentPublishPlan"
6630
6649
  ]);
6650
+ var FORCED_COMPACTION_THRESHOLD_TOKENS = 85e4;
6631
6651
  var HeadlessSession = class {
6632
6652
  // Configuration
6633
6653
  opts;
@@ -6695,6 +6715,24 @@ var HeadlessSession = class {
6695
6715
  }
6696
6716
  triggerBrandExtraction(this.config);
6697
6717
  this.toolRegistry.onEvent = this.onEvent;
6718
+ setCompactionListener((event) => {
6719
+ if (event.type === "started") {
6720
+ this.emit(
6721
+ "compaction_started",
6722
+ { blocking: event.blocking },
6723
+ event.requestId
6724
+ );
6725
+ this.sessionStats.compactionInProgress = true;
6726
+ this.persistStats();
6727
+ } else {
6728
+ const data = event.error ? { error: event.error } : {};
6729
+ this.emit("compaction_complete", data, event.requestId);
6730
+ this.sessionStats.compactionInProgress = false;
6731
+ this.sessionStats.lastContextSize = 0;
6732
+ this.sessionStats.messageCount = this.state.messages.length;
6733
+ this.persistStats();
6734
+ }
6735
+ });
6698
6736
  process.stdin.setEncoding("utf-8");
6699
6737
  process.stdin.on("data", (chunk) => {
6700
6738
  this.stdinBuffer += chunk;
@@ -6796,6 +6834,37 @@ var HeadlessSession = class {
6796
6834
  }
6797
6835
  saveSession(this.state);
6798
6836
  }
6837
+ /**
6838
+ * Forced compaction gate. If lastContextSize exceeds the threshold, compact
6839
+ * before letting the upcoming turn run. Coalesces with any in-flight
6840
+ * compaction (e.g., one already started by /compact or a tool call). No
6841
+ * timeout — compaction takes as long as it takes.
6842
+ *
6843
+ * Lifecycle events (`compaction_started` / `compaction_complete`) and
6844
+ * stats updates are handled by the listener registered in start(); this
6845
+ * method only awaits the promise and applies the resulting summaries.
6846
+ *
6847
+ * On compaction failure we don't bail — the turn proceeds and surfaces any
6848
+ * downstream overflow through the existing "prompt is too long" path.
6849
+ */
6850
+ async runForcedCompactionIfNeeded(requestId) {
6851
+ if (this.sessionStats.lastContextSize <= FORCED_COMPACTION_THRESHOLD_TOKENS) {
6852
+ return;
6853
+ }
6854
+ log14.info("Forced compaction gate triggered", {
6855
+ contextSize: this.sessionStats.lastContextSize,
6856
+ threshold: FORCED_COMPACTION_THRESHOLD_TOKENS,
6857
+ requestId
6858
+ });
6859
+ try {
6860
+ await triggerCompaction(this.state, this.config, {
6861
+ blocking: true,
6862
+ requestId
6863
+ });
6864
+ this.applyPendingSummaries();
6865
+ } catch {
6866
+ }
6867
+ }
6799
6868
  /** Drain pending compaction summaries and insert at a safe point. */
6800
6869
  applyPendingSummaries() {
6801
6870
  const summaries = getPendingSummaries();
@@ -7027,6 +7096,7 @@ var HeadlessSession = class {
7027
7096
  this.currentAbort = new AbortController();
7028
7097
  this.completedEmitted = false;
7029
7098
  this.turnStart = Date.now();
7099
+ await this.runForcedCompactionIfNeeded(requestId);
7030
7100
  const attachments = parsed.attachments;
7031
7101
  if (attachments?.length) {
7032
7102
  log14.info("Message has attachments", {
@@ -7333,29 +7403,19 @@ var HeadlessSession = class {
7333
7403
  return;
7334
7404
  }
7335
7405
  if (action === "compact") {
7336
- triggerCompaction(this.state, this.config, {
7337
- onStart: () => {
7338
- this.sessionStats.compactionInProgress = true;
7339
- this.persistStats();
7340
- },
7341
- onSummariesReady: () => {
7342
- if (!this.running) {
7343
- this.applyPendingSummaries();
7344
- }
7345
- this.emit("compaction_complete", {}, requestId);
7346
- this.emit("completed", { success: true }, requestId);
7347
- },
7348
- onError: (error) => {
7349
- this.emit("compaction_complete", { error }, requestId);
7350
- this.emit("completed", { success: false, error }, requestId);
7351
- },
7352
- onFinally: () => {
7353
- this.sessionStats.compactionInProgress = false;
7354
- this.sessionStats.lastContextSize = 0;
7355
- this.sessionStats.messageCount = this.state.messages.length;
7356
- this.persistStats();
7406
+ try {
7407
+ await triggerCompaction(this.state, this.config, {
7408
+ blocking: false,
7409
+ requestId
7410
+ });
7411
+ if (!this.running) {
7412
+ this.applyPendingSummaries();
7357
7413
  }
7358
- });
7414
+ this.emit("completed", { success: true }, requestId);
7415
+ } catch (err) {
7416
+ const error = err.message || "Compaction failed";
7417
+ this.emit("completed", { success: false, error }, requestId);
7418
+ }
7359
7419
  return;
7360
7420
  }
7361
7421
  if (action === "message") {
package/dist/index.js CHANGED
@@ -1870,22 +1870,37 @@ var init_prompt = __esm({
1870
1870
  function getPendingSummaries() {
1871
1871
  return pendingSummaries.splice(0);
1872
1872
  }
1873
- function triggerCompaction(state, apiConfig, callbacks) {
1874
- callbacks?.onStart?.();
1873
+ function setCompactionListener(l) {
1874
+ listener = l;
1875
+ }
1876
+ function triggerCompaction(state, apiConfig, opts = {}) {
1877
+ if (inflightCompaction) {
1878
+ return inflightCompaction;
1879
+ }
1880
+ const { blocking = false, requestId } = opts;
1881
+ listener?.({ type: "started", blocking, requestId });
1875
1882
  const system = buildSystemPrompt("onboardingFinished");
1876
1883
  const tools2 = getToolDefinitions("onboardingFinished");
1877
- compactConversation(state.messages, apiConfig, system, tools2).then((summaries) => {
1884
+ inflightCompaction = compactConversation(
1885
+ state.messages,
1886
+ apiConfig,
1887
+ system,
1888
+ tools2
1889
+ ).then((summaries) => {
1878
1890
  pendingSummaries.push(...summaries);
1879
- callbacks?.onSummariesReady?.();
1891
+ listener?.({ type: "complete", requestId });
1880
1892
  log3.info("Compaction complete");
1881
1893
  }).catch((err) => {
1882
- callbacks?.onError?.(err.message || "Compaction failed");
1883
- log3.error("Compaction failed", { error: err.message });
1894
+ const message = err.message || "Compaction failed";
1895
+ listener?.({ type: "complete", error: message, requestId });
1896
+ log3.error("Compaction failed", { error: message });
1897
+ throw err;
1884
1898
  }).finally(() => {
1885
- callbacks?.onFinally?.();
1899
+ inflightCompaction = null;
1886
1900
  });
1901
+ return inflightCompaction;
1887
1902
  }
1888
- var log3, pendingSummaries;
1903
+ var log3, pendingSummaries, inflightCompaction, listener;
1889
1904
  var init_trigger = __esm({
1890
1905
  "src/compaction/trigger.ts"() {
1891
1906
  "use strict";
@@ -1895,6 +1910,8 @@ var init_trigger = __esm({
1895
1910
  init_logger();
1896
1911
  log3 = createLogger("compaction:trigger");
1897
1912
  pendingSummaries = [];
1913
+ inflightCompaction = null;
1914
+ listener = null;
1898
1915
  }
1899
1916
  });
1900
1917
 
@@ -1920,8 +1937,10 @@ var init_compactConversation = __esm({
1920
1937
  }
1921
1938
  triggerCompaction(
1922
1939
  { messages: context.conversationMessages },
1923
- context.apiConfig
1924
- );
1940
+ context.apiConfig,
1941
+ { blocking: false, requestId: context.requestId }
1942
+ ).catch(() => {
1943
+ });
1925
1944
  return "Compaction started in the background.";
1926
1945
  }
1927
1946
  };
@@ -7355,7 +7374,7 @@ var headless_exports = {};
7355
7374
  __export(headless_exports, {
7356
7375
  HeadlessSession: () => HeadlessSession
7357
7376
  });
7358
- var log14, EXTERNAL_TOOL_TIMEOUT_MS, USER_FACING_TOOLS, HeadlessSession;
7377
+ var log14, EXTERNAL_TOOL_TIMEOUT_MS, USER_FACING_TOOLS, FORCED_COMPACTION_THRESHOLD_TOKENS, HeadlessSession;
7359
7378
  var init_headless = __esm({
7360
7379
  "src/headless/index.ts"() {
7361
7380
  "use strict";
@@ -7382,6 +7401,7 @@ var init_headless = __esm({
7382
7401
  "confirmDestructiveAction",
7383
7402
  "presentPublishPlan"
7384
7403
  ]);
7404
+ FORCED_COMPACTION_THRESHOLD_TOKENS = 85e4;
7385
7405
  HeadlessSession = class {
7386
7406
  // Configuration
7387
7407
  opts;
@@ -7449,6 +7469,24 @@ var init_headless = __esm({
7449
7469
  }
7450
7470
  triggerBrandExtraction(this.config);
7451
7471
  this.toolRegistry.onEvent = this.onEvent;
7472
+ setCompactionListener((event) => {
7473
+ if (event.type === "started") {
7474
+ this.emit(
7475
+ "compaction_started",
7476
+ { blocking: event.blocking },
7477
+ event.requestId
7478
+ );
7479
+ this.sessionStats.compactionInProgress = true;
7480
+ this.persistStats();
7481
+ } else {
7482
+ const data = event.error ? { error: event.error } : {};
7483
+ this.emit("compaction_complete", data, event.requestId);
7484
+ this.sessionStats.compactionInProgress = false;
7485
+ this.sessionStats.lastContextSize = 0;
7486
+ this.sessionStats.messageCount = this.state.messages.length;
7487
+ this.persistStats();
7488
+ }
7489
+ });
7452
7490
  process.stdin.setEncoding("utf-8");
7453
7491
  process.stdin.on("data", (chunk) => {
7454
7492
  this.stdinBuffer += chunk;
@@ -7550,6 +7588,37 @@ var init_headless = __esm({
7550
7588
  }
7551
7589
  saveSession(this.state);
7552
7590
  }
7591
+ /**
7592
+ * Forced compaction gate. If lastContextSize exceeds the threshold, compact
7593
+ * before letting the upcoming turn run. Coalesces with any in-flight
7594
+ * compaction (e.g., one already started by /compact or a tool call). No
7595
+ * timeout — compaction takes as long as it takes.
7596
+ *
7597
+ * Lifecycle events (`compaction_started` / `compaction_complete`) and
7598
+ * stats updates are handled by the listener registered in start(); this
7599
+ * method only awaits the promise and applies the resulting summaries.
7600
+ *
7601
+ * On compaction failure we don't bail — the turn proceeds and surfaces any
7602
+ * downstream overflow through the existing "prompt is too long" path.
7603
+ */
7604
+ async runForcedCompactionIfNeeded(requestId) {
7605
+ if (this.sessionStats.lastContextSize <= FORCED_COMPACTION_THRESHOLD_TOKENS) {
7606
+ return;
7607
+ }
7608
+ log14.info("Forced compaction gate triggered", {
7609
+ contextSize: this.sessionStats.lastContextSize,
7610
+ threshold: FORCED_COMPACTION_THRESHOLD_TOKENS,
7611
+ requestId
7612
+ });
7613
+ try {
7614
+ await triggerCompaction(this.state, this.config, {
7615
+ blocking: true,
7616
+ requestId
7617
+ });
7618
+ this.applyPendingSummaries();
7619
+ } catch {
7620
+ }
7621
+ }
7553
7622
  /** Drain pending compaction summaries and insert at a safe point. */
7554
7623
  applyPendingSummaries() {
7555
7624
  const summaries = getPendingSummaries();
@@ -7781,6 +7850,7 @@ var init_headless = __esm({
7781
7850
  this.currentAbort = new AbortController();
7782
7851
  this.completedEmitted = false;
7783
7852
  this.turnStart = Date.now();
7853
+ await this.runForcedCompactionIfNeeded(requestId);
7784
7854
  const attachments = parsed.attachments;
7785
7855
  if (attachments?.length) {
7786
7856
  log14.info("Message has attachments", {
@@ -8087,29 +8157,19 @@ var init_headless = __esm({
8087
8157
  return;
8088
8158
  }
8089
8159
  if (action === "compact") {
8090
- triggerCompaction(this.state, this.config, {
8091
- onStart: () => {
8092
- this.sessionStats.compactionInProgress = true;
8093
- this.persistStats();
8094
- },
8095
- onSummariesReady: () => {
8096
- if (!this.running) {
8097
- this.applyPendingSummaries();
8098
- }
8099
- this.emit("compaction_complete", {}, requestId);
8100
- this.emit("completed", { success: true }, requestId);
8101
- },
8102
- onError: (error) => {
8103
- this.emit("compaction_complete", { error }, requestId);
8104
- this.emit("completed", { success: false, error }, requestId);
8105
- },
8106
- onFinally: () => {
8107
- this.sessionStats.compactionInProgress = false;
8108
- this.sessionStats.lastContextSize = 0;
8109
- this.sessionStats.messageCount = this.state.messages.length;
8110
- this.persistStats();
8160
+ try {
8161
+ await triggerCompaction(this.state, this.config, {
8162
+ blocking: false,
8163
+ requestId
8164
+ });
8165
+ if (!this.running) {
8166
+ this.applyPendingSummaries();
8111
8167
  }
8112
- });
8168
+ this.emit("completed", { success: true }, requestId);
8169
+ } catch (err) {
8170
+ const error = err.message || "Compaction failed";
8171
+ this.emit("completed", { success: false, error }, requestId);
8172
+ }
8113
8173
  return;
8114
8174
  }
8115
8175
  if (action === "message") {
@@ -13,7 +13,7 @@
13
13
  - Work with what you already know. If you've read a file in this session, use what you learned rather than reading it again. If a subagent already researched something, use its findings. Every tool call costs time; prefer acting on information you have over re-gathering it.
14
14
  - When multiple tool calls are independent, make them all in a single turn. Reading three files, writing two methods, or running a scenario while taking a screenshot: batch them instead of doing one per turn.
15
15
  - After two failed attempts at the same approach, tell the user what's going wrong.
16
- - Never estimate how long something will take or how much it will cost. Just do it. If the user asks, you must politely refuse and let them know that due to the way AI models work, any answer would just be a guess. You can, however, help them understand the scope and scale of the work, or how long it might take/how much it might cost a traditional engineering or product team (e.g., weeks/months, $100k USD+ to a consulting shop, etc.) - but you can not estimate token usage or costs of work within the system.
16
+ - Never estimate how long something will take or how much it will cost. Just do it. If the user asks, politely refuse any number would be a guess. Never quote concrete time units for work you're about to do. You can describe scope qualitatively (small change, large refactor, etc.), but never estimate the time it will take you to do work.
17
17
  - Pushing to main branch will trigger a deploy. The user presses the publish button in the interface to request publishing.
18
18
 
19
19
  ### Build Notes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.164",
3
+ "version": "0.1.165",
4
4
  "description": "MindStudio coding agent",
5
5
  "repository": {
6
6
  "type": "git",