flavor-code 1.3.15 → 1.3.17

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.
@@ -1,5 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module"; const require = createRequire(import.meta.url);
3
+ import {
4
+ MEMORY_RESTART_EXIT_CODE,
5
+ memoryRestartMarkerPath,
6
+ nextMemoryRestartMarker,
7
+ parseMemoryRestartMarker
8
+ } from "./chunk-CIVPKHF4.js";
3
9
  import {
4
10
  PERMISSION_MODES,
5
11
  PermissionModeSchema,
@@ -12,7 +18,7 @@ import {
12
18
  redactConfig,
13
19
  setProjectMcpServerDisabled,
14
20
  updateProtectedFile
15
- } from "./chunk-JFKRX2KG.js";
21
+ } from "./chunk-HJOCNKPN.js";
16
22
  import {
17
23
  AuditLogger,
18
24
  appendUsageLog,
@@ -1308,6 +1314,8 @@ function appendThinkingText(previous, delta) {
1308
1314
  }
1309
1315
 
1310
1316
  // src/ui/transcript.ts
1317
+ var MAX_TRANSCRIPT_TURNS = 200;
1318
+ var MAX_TRANSCRIPT_CHARS = 2e6;
1311
1319
  function createTranscriptState() {
1312
1320
  return { completed: [], nextId: 1 };
1313
1321
  }
@@ -1604,7 +1612,7 @@ function hydrateHistory(messages, compact) {
1604
1612
  }
1605
1613
  }
1606
1614
  if (turn !== void 0) completed.push(turn);
1607
- return { completed, nextId: completed.length + 1 };
1615
+ return { completed: boundedCompleted(completed), nextId: completed.length + 1 };
1608
1616
  }
1609
1617
  function transcriptFromLegacyConversation(messages, compact) {
1610
1618
  return hydrateHistory(messages, compact);
@@ -1698,10 +1706,10 @@ function finishActive(state) {
1698
1706
  if (state.active === void 0) return state;
1699
1707
  const active = withoutModelActivity(state.active);
1700
1708
  return {
1701
- completed: [...state.completed, {
1709
+ completed: boundedCompleted([...state.completed, {
1702
1710
  ...active,
1703
1711
  completedAt: active.completedAt ?? (/* @__PURE__ */ new Date()).toISOString()
1704
- }],
1712
+ }]),
1705
1713
  nextId: state.nextId,
1706
1714
  ...state.taskSnapshot === void 0 ? {} : { taskSnapshot: state.taskSnapshot }
1707
1715
  };
@@ -1730,12 +1738,26 @@ function withoutModelActivity(turn, id) {
1730
1738
  function restoreTranscriptState(state) {
1731
1739
  const completed = state.completed.map(cloneTurn);
1732
1740
  if (state.active !== void 0) completed.push(normalizeInterruptedTurn(cloneTurn(state.active)));
1741
+ const nextId = Math.max(state.nextId, ...completed.map((turn) => turn.id + 1), 1);
1733
1742
  return {
1734
- completed,
1735
- nextId: Math.max(state.nextId, ...completed.map((turn) => turn.id + 1), 1),
1743
+ completed: boundedCompleted(completed),
1744
+ nextId,
1736
1745
  ...state.taskSnapshot === void 0 ? {} : { taskSnapshot: state.taskSnapshot }
1737
1746
  };
1738
1747
  }
1748
+ function boundedCompleted(input) {
1749
+ const recent = input.slice(-MAX_TRANSCRIPT_TURNS);
1750
+ let chars = 0;
1751
+ let start = recent.length;
1752
+ for (let index = recent.length - 1; index >= 0; index -= 1) {
1753
+ const turn = recent[index];
1754
+ const size = JSON.stringify(turn).length;
1755
+ if (start < recent.length && chars + size > MAX_TRANSCRIPT_CHARS) break;
1756
+ chars += size;
1757
+ start = index;
1758
+ }
1759
+ return recent.slice(start);
1760
+ }
1739
1761
  function compactionTurn(compact) {
1740
1762
  const text = `Original execution steps before ${compact.compactedAt} are unavailable.`;
1741
1763
  return {
@@ -2026,6 +2048,8 @@ function isCode(error, code) {
2026
2048
  import { randomUUID as randomUUID2 } from "crypto";
2027
2049
  import { mkdir as mkdir3, readFile as readFile3, rename as rename2, writeFile as writeFile3 } from "fs/promises";
2028
2050
  import { dirname as dirname5, join as join4, resolve as resolve3 } from "path";
2051
+ var MAX_SESSION_HISTORY_NODES = 100;
2052
+ var MAX_SESSION_HISTORY_CONTEXT_CHARS = 2e6;
2029
2053
  var SessionHistory = class _SessionHistory {
2030
2054
  #path;
2031
2055
  #checkpoints;
@@ -2037,8 +2061,8 @@ var SessionHistory = class _SessionHistory {
2037
2061
  this.#path = join4(resolve3(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
2038
2062
  this.#checkpoints = new WorkspaceCheckpointStore({ workspace: options.workspace });
2039
2063
  this.#restoreContext = options.restoreContext;
2040
- this.#nodes = document.nodes;
2041
- this.#leafId = document.leafId;
2064
+ this.#nodes = boundedNodes(document.nodes);
2065
+ this.#leafId = document.leafId !== null && this.#nodes.some((node) => node.id === document.leafId) ? document.leafId : this.#nodes.at(-1)?.id ?? null;
2042
2066
  }
2043
2067
  static async open(options) {
2044
2068
  const path = join4(resolve3(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
@@ -2050,7 +2074,9 @@ var SessionHistory = class _SessionHistory {
2050
2074
  } catch (error) {
2051
2075
  if (!isCode2(error, "ENOENT")) throw error;
2052
2076
  }
2053
- return new _SessionHistory(options, document);
2077
+ const history = new _SessionHistory(options, document);
2078
+ if (history.#nodes.length !== document.nodes.length) await history.#persist();
2079
+ return history;
2054
2080
  }
2055
2081
  get leafId() {
2056
2082
  return this.#leafId;
@@ -2070,6 +2096,7 @@ var SessionHistory = class _SessionHistory {
2070
2096
  };
2071
2097
  this.#nodes.push(node);
2072
2098
  this.#leafId = node.id;
2099
+ this.#boundNodes();
2073
2100
  await this.#persist();
2074
2101
  return structuredClone(node);
2075
2102
  }
@@ -2111,6 +2138,13 @@ var SessionHistory = class _SessionHistory {
2111
2138
  if (node === void 0) throw new Error(`Unknown session tree node: ${id}`);
2112
2139
  return node;
2113
2140
  }
2141
+ #boundNodes() {
2142
+ const bounded2 = boundedNodes(this.#nodes);
2143
+ this.#nodes.splice(0, this.#nodes.length, ...bounded2);
2144
+ if (this.#leafId !== null && !this.#nodes.some((node) => node.id === this.#leafId)) {
2145
+ this.#leafId = this.#nodes.at(-1)?.id ?? null;
2146
+ }
2147
+ }
2114
2148
  async #persist() {
2115
2149
  const document = {
2116
2150
  version: 1,
@@ -2125,6 +2159,24 @@ var SessionHistory = class _SessionHistory {
2125
2159
  await rename2(temporary, this.#path);
2126
2160
  }
2127
2161
  };
2162
+ function boundedNodes(input) {
2163
+ const recent = input.slice(-MAX_SESSION_HISTORY_NODES);
2164
+ let contextChars = 0;
2165
+ let start = recent.length;
2166
+ for (let index = recent.length - 1; index >= 0; index -= 1) {
2167
+ const node = recent[index];
2168
+ const size = JSON.stringify(node.context).length;
2169
+ if (start < recent.length && contextChars + size > MAX_SESSION_HISTORY_CONTEXT_CHARS) break;
2170
+ contextChars += size;
2171
+ start = index;
2172
+ }
2173
+ const retained = recent.slice(start).map((node) => structuredClone(node));
2174
+ const ids = new Set(retained.map((node) => node.id));
2175
+ for (const node of retained) {
2176
+ if (node.parentId !== null && !ids.has(node.parentId)) node.parentId = null;
2177
+ }
2178
+ return retained;
2179
+ }
2128
2180
  function validate(value, sessionId) {
2129
2181
  if (value.version !== 1 || value.sessionId !== sessionId || !Array.isArray(value.nodes)) {
2130
2182
  throw new Error("Invalid session tree");
@@ -5326,9 +5378,10 @@ async function ensureOnce(options) {
5326
5378
 
5327
5379
  // src/production.ts
5328
5380
  import { readFile as readFile22 } from "fs/promises";
5381
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync } from "fs";
5329
5382
  import { createHash as createHash18, randomUUID as randomUUID20 } from "crypto";
5330
5383
  import { homedir as homedir2, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
5331
- import { basename as basename6, join as join26, relative as relative20, resolve as resolve28, sep as sep20 } from "path";
5384
+ import { basename as basename6, dirname as dirname19, join as join26, relative as relative20, resolve as resolve28, sep as sep20 } from "path";
5332
5385
 
5333
5386
  // src/agent/subagents.ts
5334
5387
  import { z as z5 } from "zod";
@@ -5877,7 +5930,9 @@ import { createHash as createHash4, randomUUID as randomUUID6 } from "crypto";
5877
5930
  var DEFAULT_COMPACTION_POLICY = {
5878
5931
  windowTokens: 2e5,
5879
5932
  reservedOutputTokens: 2e4,
5880
- autoCompactBufferTokens: 13e3,
5933
+ // 85% of the effective window (180_000 - 27_000 = 153_000): compaction used
5934
+ // to trigger at ~92.8%, which let sessions ride the threshold into heap OOM.
5935
+ autoCompactBufferTokens: 27e3,
5881
5936
  warningBufferTokens: 2e4,
5882
5937
  blockingBufferTokens: 3e3,
5883
5938
  microcompactKeepRecentToolResults: 5,
@@ -6087,6 +6142,13 @@ var ContextManager = class _ContextManager {
6087
6142
  #epoch;
6088
6143
  #visibilityLog = [];
6089
6144
  #activeTransientSystem = /* @__PURE__ */ new Map();
6145
+ /**
6146
+ * The visibility audit log persists every transient system admission and is
6147
+ * serialized in full on every session save. Bound both dimensions or a long
6148
+ * subagent-driven run grows it into hundreds of megabytes (heap OOM).
6149
+ */
6150
+ static VISIBILITY_LOG_MAX_ENTRIES = 1e3;
6151
+ static VISIBILITY_RECORD_CONTENT_LIMIT = 2048;
6090
6152
  constructor(options) {
6091
6153
  if (options.compactAtChars !== void 0 && options.compactAtChars <= 0) throw new Error("compactAtChars must be positive");
6092
6154
  if (options.toolOutputChars <= 0) throw new Error("toolOutputChars must be positive");
@@ -6182,11 +6244,14 @@ var ContextManager = class _ContextManager {
6182
6244
  const record = {
6183
6245
  id,
6184
6246
  role: "system",
6185
- content,
6247
+ content: limitVisibilityContent(content),
6186
6248
  admittedAt: (/* @__PURE__ */ new Date()).toISOString(),
6187
6249
  scope: "run"
6188
6250
  };
6189
6251
  this.#visibilityLog.push(record);
6252
+ if (this.#visibilityLog.length > _ContextManager.VISIBILITY_LOG_MAX_ENTRIES) {
6253
+ this.#visibilityLog = this.#visibilityLog.slice(-_ContextManager.VISIBILITY_LOG_MAX_ENTRIES);
6254
+ }
6190
6255
  this.#activeTransientSystem.set(id, { role: "system", content });
6191
6256
  return id;
6192
6257
  }
@@ -6222,7 +6287,7 @@ var ContextManager = class _ContextManager {
6222
6287
  for (const key of DYNAMIC_SOURCE_ORDER) {
6223
6288
  const value = next[key];
6224
6289
  if (value === void 0 || value === this.#epoch.sources[key]) continue;
6225
- updates.push(contextUpdateMessage(key, displaySourceValue(key, value)));
6290
+ updates.push(contextUpdateMessage(key, deltaSourceValue(this.#epoch.sources[key], displaySourceValue(key, value))));
6226
6291
  this.#epoch.sources[key] = value;
6227
6292
  }
6228
6293
  for (const key of DYNAMIC_SOURCE_ORDER) {
@@ -6232,8 +6297,6 @@ var ContextManager = class _ContextManager {
6232
6297
  }
6233
6298
  if (updates.length === 0) return false;
6234
6299
  this.#messages.push(...updates);
6235
- this.#lastRecordedInputTokens = void 0;
6236
- this.#estimatedTokensAtLastRecordedUsage = void 0;
6237
6300
  return true;
6238
6301
  }
6239
6302
  snapshot() {
@@ -6249,7 +6312,7 @@ var ContextManager = class _ContextManager {
6249
6312
  const legacySummary = snapshot4.summary?.role === "system" && snapshot4.summary.content.startsWith("Conversation summary\n") ? snapshot4.summary.content.slice("Conversation summary\n".length) : void 0;
6250
6313
  this.#compact = snapshot4.compact === void 0 ? legacySummary === void 0 ? void 0 : { summary: legacySummary, compactedAt: (/* @__PURE__ */ new Date(0)).toISOString() } : { ...snapshot4.compact };
6251
6314
  if (snapshot4.epoch !== void 0) this.#epoch = cloneEpoch(snapshot4.epoch);
6252
- this.#visibilityLog = snapshot4.visibilityLog?.map((item) => ({ ...item })) ?? [];
6315
+ this.#visibilityLog = boundVisibilityLog(snapshot4.visibilityLog);
6253
6316
  this.#activeTransientSystem.clear();
6254
6317
  this.#messages = messages.map((message2) => message2.role === "tool" ? { ...message2, content: truncateToolOutput(message2.content, this.#toolOutputChars) } : cloneMessage2(message2));
6255
6318
  this.#lastRecordedInputTokens = void 0;
@@ -6293,6 +6356,7 @@ var ContextManager = class _ContextManager {
6293
6356
  async prepareForModelCall(signal = new AbortController().signal) {
6294
6357
  signal.throwIfAborted();
6295
6358
  this.refreshContextSources();
6359
+ this.#dropStaleContextUpdates();
6296
6360
  if (!this.needsCompaction()) return false;
6297
6361
  const originalMessages = this.#messages;
6298
6362
  const originalRecordedUsage = this.#lastRecordedInputTokens;
@@ -6305,8 +6369,6 @@ var ContextManager = class _ContextManager {
6305
6369
  const microcompact = microcompactMessages(this.#messages, this.#compaction.microcompactKeepRecentToolResults);
6306
6370
  if (microcompact.changed) {
6307
6371
  this.#messages = microcompact.messages;
6308
- this.#lastRecordedInputTokens = void 0;
6309
- this.#estimatedTokensAtLastRecordedUsage = void 0;
6310
6372
  }
6311
6373
  if (!this.needsCompaction()) {
6312
6374
  if (microcompact.changed) {
@@ -6392,6 +6454,26 @@ var ContextManager = class _ContextManager {
6392
6454
  this.#reportCompactProgress(100);
6393
6455
  return true;
6394
6456
  }
6457
+ #dropStaleContextUpdates() {
6458
+ const latestBySource = /* @__PURE__ */ new Map();
6459
+ for (let index = this.#messages.length - 1; index >= 0; index -= 1) {
6460
+ const source = contextUpdateSource(this.#messages[index]);
6461
+ if (source === void 0 || latestBySource.has(source)) continue;
6462
+ latestBySource.set(source, index);
6463
+ }
6464
+ const next = [];
6465
+ let changed = false;
6466
+ this.#messages.forEach((message2, index) => {
6467
+ const source = contextUpdateSource(message2);
6468
+ if (source !== void 0 && latestBySource.get(source) !== index) {
6469
+ changed = true;
6470
+ return;
6471
+ }
6472
+ next.push(message2);
6473
+ });
6474
+ if (changed) this.#messages = next;
6475
+ return changed;
6476
+ }
6395
6477
  #reportCompactProgress(percentage) {
6396
6478
  const normalized = Math.max(0, Math.min(100, Math.floor(percentage / 10) * 10));
6397
6479
  if (normalized === this.#lastCompactProgress) return;
@@ -6503,6 +6585,33 @@ function contextUpdateMessage(source, content) {
6503
6585
  return { role: "system", content: `Context update [${source}]
6504
6586
  ${content}` };
6505
6587
  }
6588
+ var CONTEXT_UPDATE_PREFIX = "Context update [";
6589
+ function contextUpdateSource(message2) {
6590
+ if (message2.role !== "system" || typeof message2.content !== "string") return void 0;
6591
+ if (!message2.content.startsWith(CONTEXT_UPDATE_PREFIX)) return void 0;
6592
+ const end = message2.content.indexOf("]\n", CONTEXT_UPDATE_PREFIX.length);
6593
+ if (end < 0) return void 0;
6594
+ return message2.content.slice(CONTEXT_UPDATE_PREFIX.length, end);
6595
+ }
6596
+ function deltaSourceValue(previous, display) {
6597
+ if (previous === void 0 || !display.startsWith(previous)) return display;
6598
+ return display.slice(previous.length).replace(/^\s+/, "") || display;
6599
+ }
6600
+ function limitVisibilityContent(content) {
6601
+ const limit = ContextManager.VISIBILITY_RECORD_CONTENT_LIMIT;
6602
+ if (content.length <= limit) return content;
6603
+ return `${content.slice(0, limit)}
6604
+ ...[truncated; original length: ${content.length} characters]...`;
6605
+ }
6606
+ function boundVisibilityLog(records) {
6607
+ if (records === void 0) return [];
6608
+ const limit = ContextManager.VISIBILITY_RECORD_CONTENT_LIMIT;
6609
+ const bounded2 = records.map((item) => ({
6610
+ ...item,
6611
+ ...item.content.length <= limit ? {} : { content: limitVisibilityContent(item.content) }
6612
+ }));
6613
+ return bounded2.length > ContextManager.VISIBILITY_LOG_MAX_ENTRIES ? bounded2.slice(-ContextManager.VISIBILITY_LOG_MAX_ENTRIES) : bounded2;
6614
+ }
6506
6615
  function cloneEpoch(epoch) {
6507
6616
  return {
6508
6617
  ...epoch,
@@ -6797,6 +6906,9 @@ function reportProgress(callback, percentage) {
6797
6906
  }
6798
6907
  }
6799
6908
 
6909
+ // src/agent/loop.ts
6910
+ import { getHeapStatistics } from "v8";
6911
+
6800
6912
  // src/models/structured.ts
6801
6913
  import { z as z7 } from "zod";
6802
6914
  var DEFAULT_RETRY_DELAYS = [1e3, 2e3, 4e3];
@@ -7214,6 +7326,18 @@ function envMaxIterations() {
7214
7326
  if (!Number.isInteger(parsed) || parsed <= 0) return void 0;
7215
7327
  return parsed;
7216
7328
  }
7329
+ var MEMORY_PRESSURE_HEAP_RATIO = 0.8;
7330
+ function memoryPressureError(heapUsed, heapLimit) {
7331
+ if (heapLimit <= 0 || heapUsed < heapLimit * MEMORY_PRESSURE_HEAP_RATIO) return void 0;
7332
+ const mb = (bytes) => Math.round(bytes / 1048576);
7333
+ return {
7334
+ code: "memory_pressure",
7335
+ message: `Heap usage reached ${Math.round(heapUsed / heapLimit * 100)}% of the V8 limit (${mb(heapUsed)}MB of ${mb(heapLimit)}MB); stopping the current turn to avoid a crash. The saved session will reopen after restart.`
7336
+ };
7337
+ }
7338
+ function currentMemoryPressure() {
7339
+ return memoryPressureError(process.memoryUsage().heapUsed, getHeapStatistics().heap_size_limit);
7340
+ }
7217
7341
  var AgentLoop = class {
7218
7342
  #options;
7219
7343
  #iterationLimitMode = "standard";
@@ -7284,6 +7408,11 @@ var AgentLoop = class {
7284
7408
  yield { type: "error", error: { code: "cancelled", message: abortMessage(request.signal) } };
7285
7409
  return;
7286
7410
  }
7411
+ const pressure = currentMemoryPressure();
7412
+ if (pressure !== void 0) {
7413
+ yield { type: "error", error: pressure };
7414
+ return;
7415
+ }
7287
7416
  const softLimit = Math.floor(maxIterations * this.#options.softLimitFactor);
7288
7417
  if (!warned && iteration >= softLimit) {
7289
7418
  warned = true;
@@ -7323,6 +7452,11 @@ var AgentLoop = class {
7323
7452
  yield { type: "error", error: normalizeProviderError(error) };
7324
7453
  return;
7325
7454
  }
7455
+ const preparedPressure = currentMemoryPressure();
7456
+ if (preparedPressure !== void 0) {
7457
+ yield { type: "error", error: preparedPressure };
7458
+ return;
7459
+ }
7326
7460
  let assistantText = "";
7327
7461
  const collectedToolCalls = [];
7328
7462
  const collectedThinking = [];
@@ -7346,6 +7480,11 @@ var AgentLoop = class {
7346
7480
  tools: [...this.#options.tools],
7347
7481
  ...request.signal === void 0 ? {} : { signal: request.signal }
7348
7482
  };
7483
+ const requestPressure = currentMemoryPressure();
7484
+ if (requestPressure !== void 0) {
7485
+ yield { type: "error", error: requestPressure };
7486
+ return;
7487
+ }
7349
7488
  let before;
7350
7489
  try {
7351
7490
  before = await this.#options.hooks.emit({
@@ -12679,7 +12818,8 @@ var BoundedOutput = class {
12679
12818
  #headLimit;
12680
12819
  #tailLimit;
12681
12820
  #head = Buffer.alloc(0);
12682
- #tail = Buffer.alloc(0);
12821
+ #tailChunks = [];
12822
+ #tailBytes = 0;
12683
12823
  #complete = Buffer.alloc(0);
12684
12824
  #bytes = 0;
12685
12825
  constructor(limit) {
@@ -12699,15 +12839,24 @@ var BoundedOutput = class {
12699
12839
  const count = Math.min(this.#headLimit - this.#head.length, chunk.length);
12700
12840
  this.#head = Buffer.concat([this.#head, chunk.subarray(0, count)]);
12701
12841
  }
12702
- if (this.#tailLimit > 0) this.#tail = Buffer.concat([this.#tail, chunk]).subarray(-this.#tailLimit);
12842
+ if (this.#tailLimit > 0) {
12843
+ this.#tailChunks.push(chunk);
12844
+ this.#tailBytes += chunk.length;
12845
+ if (this.#tailBytes >= this.#tailLimit * 2) {
12846
+ const merged = Buffer.concat(this.#tailChunks).subarray(-this.#tailLimit);
12847
+ this.#tailChunks = [merged];
12848
+ this.#tailBytes = merged.length;
12849
+ }
12850
+ }
12703
12851
  }
12704
12852
  metadata() {
12705
12853
  return { truncated: this.truncated, originalBytes: this.#bytes, limitBytes: this.#limit };
12706
12854
  }
12707
12855
  text() {
12708
12856
  if (!this.truncated) return decodeOutput(this.#complete);
12857
+ const tail = Buffer.concat(this.#tailChunks).subarray(-this.#tailLimit);
12709
12858
  const encoding = classifyUtf8(this.#complete) === "invalid" && process.platform === "win32" ? "gb18030" : "utf-8";
12710
- return `${decodePrefix(this.#head, encoding)}${ELLIPSIS}${decodeSuffix(this.#tail, encoding)}`;
12859
+ return `${decodePrefix(this.#head, encoding)}${ELLIPSIS}${decodeSuffix(tail, encoding)}`;
12711
12860
  }
12712
12861
  };
12713
12862
  var AdaptiveOutputDecoder = class {
@@ -22634,7 +22783,7 @@ async function createProductionRuntime(options) {
22634
22783
  contextEpoch: harness.main.context.snapshot().epoch?.id ?? "legacy"
22635
22784
  });
22636
22785
  const turnId = harnessJournal.startTurn(turnConfig, { prompt, initialUserMessage: runOptions?.initialUserMessage });
22637
- return durableTurn(persistAndCheckpointAfter(runMain(
22786
+ return monitorMemoryRestart(durableTurn(persistAfter(runMain(
22638
22787
  harness,
22639
22788
  skills,
22640
22789
  prompt,
@@ -22650,11 +22799,7 @@ async function createProductionRuntime(options) {
22650
22799
  runOptions?.initialUserMessage,
22651
22800
  runOptions?.additionalContext,
22652
22801
  ide
22653
- ), persist, () => sessionHistory.append({
22654
- prompt,
22655
- context: harness.main.context.snapshot(),
22656
- label: `turn: ${prompt.slice(0, 80)}`
22657
- })), harnessJournal, turnId, turnConfig);
22802
+ ), persist), harnessJournal, turnId, turnConfig), () => requestMemoryRestart(workspace, sessionId, emitOutput));
22658
22803
  },
22659
22804
  runSkill: (skill, prompt, signal) => persistAfter(
22660
22805
  runExplicitSkill(harness, skills, skill, prompt, signal, selectedModels.mainError),
@@ -23364,14 +23509,6 @@ async function* persistAfter(source, persist) {
23364
23509
  await persist();
23365
23510
  }
23366
23511
  }
23367
- async function* persistAndCheckpointAfter(source, persist, checkpoint) {
23368
- try {
23369
- for await (const item of source) yield item;
23370
- await checkpoint();
23371
- } finally {
23372
- await persist();
23373
- }
23374
- }
23375
23512
  async function* persistEach(source, persist) {
23376
23513
  try {
23377
23514
  for await (const item of source) {
@@ -23403,6 +23540,31 @@ async function* durableTurn(source, journal, turnId, config) {
23403
23540
  }
23404
23541
  }
23405
23542
  }
23543
+ async function* monitorMemoryRestart(source, onRestartNeeded) {
23544
+ let pressure = false;
23545
+ for await (const event of source) {
23546
+ if (event.type === "error" && event.error.code === "memory_pressure") pressure = true;
23547
+ yield event;
23548
+ }
23549
+ if (pressure) onRestartNeeded();
23550
+ }
23551
+ function requestMemoryRestart(workspace, sessionId, emitOutput) {
23552
+ try {
23553
+ const markerPath = memoryRestartMarkerPath(workspace);
23554
+ let previous;
23555
+ try {
23556
+ previous = parseMemoryRestartMarker(readFileSync3(markerPath, "utf8"));
23557
+ } catch {
23558
+ }
23559
+ mkdirSync3(dirname19(markerPath), { recursive: true });
23560
+ writeFileSync(markerPath, `${JSON.stringify(nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date()))}
23561
+ `, "utf8");
23562
+ } catch {
23563
+ }
23564
+ process.exitCode = MEMORY_RESTART_EXIT_CODE;
23565
+ emitOutput({ type: "notice", message: "Memory watermark reached; the session is saved and Flavor is restarting with a fresh heap." });
23566
+ emitOutput({ type: "exit" });
23567
+ }
23406
23568
  async function runGitCommit(deps, hint, signal) {
23407
23569
  if (!await isGitRepository(deps.workspace)) return "Not a git repository \u2014 /commit needs git.";
23408
23570
  let summary2 = await changeSummary(deps.workspace);
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module"; const require = createRequire(import.meta.url);
3
+
4
+ // src/utils/memory-restart.ts
5
+ import { join } from "path";
6
+ var MEMORY_RESTART_EXIT_CODE = 75;
7
+ var MEMORY_RESTART_MARKER_FILE = "memory-restart.json";
8
+ var MEMORY_RESTART_MAX_ATTEMPTS = 3;
9
+ var MEMORY_RESTART_WINDOW_MS = 30 * 60 * 1e3;
10
+ var SESSION_ID_PATTERN = /^session-[A-Za-z0-9_-]+$/u;
11
+ function memoryRestartMarkerPath(workspace) {
12
+ return join(workspace, ".flavor", "tmp", MEMORY_RESTART_MARKER_FILE);
13
+ }
14
+ function nextMemoryRestartMarker(previous, sessionId, now) {
15
+ const reusable = previous !== void 0 && previous.sessionId === sessionId && now.getTime() - Date.parse(previous.requestedAt) < MEMORY_RESTART_WINDOW_MS && Number.isInteger(previous.attempts) && previous.attempts > 0;
16
+ return { sessionId, requestedAt: now.toISOString(), attempts: reusable ? previous.attempts + 1 : 1 };
17
+ }
18
+ function shouldRelaunchForMemoryRestart(marker) {
19
+ return marker !== void 0 && SESSION_ID_PATTERN.test(marker.sessionId) && Number.isInteger(marker.attempts) && marker.attempts >= 1 && marker.attempts <= MEMORY_RESTART_MAX_ATTEMPTS;
20
+ }
21
+ function memoryRestartArgs(argv, marker) {
22
+ if (!shouldRelaunchForMemoryRestart(marker)) return void 0;
23
+ const stripped = [];
24
+ for (let index = 0; index < argv.length; index += 1) {
25
+ const argument = argv[index];
26
+ if (argument === "--resume") {
27
+ if (index + 1 < argv.length && !argv[index + 1].startsWith("-")) index += 1;
28
+ continue;
29
+ }
30
+ if (argument.startsWith("--resume=")) continue;
31
+ stripped.push(argument);
32
+ }
33
+ return [...stripped, "--resume", marker.sessionId];
34
+ }
35
+ function parseMemoryRestartMarker(json) {
36
+ try {
37
+ const value = JSON.parse(json);
38
+ if (typeof value.sessionId !== "string" || typeof value.requestedAt !== "string" || typeof value.attempts !== "number") {
39
+ return void 0;
40
+ }
41
+ return { sessionId: value.sessionId, requestedAt: value.requestedAt, attempts: value.attempts };
42
+ } catch {
43
+ return void 0;
44
+ }
45
+ }
46
+
47
+ export {
48
+ MEMORY_RESTART_EXIT_CODE,
49
+ memoryRestartMarkerPath,
50
+ nextMemoryRestartMarker,
51
+ memoryRestartArgs,
52
+ parseMemoryRestartMarker
53
+ };
@@ -129,7 +129,7 @@ var FlavorConfigSchema = z.object({
129
129
  context: z.object({
130
130
  windowTokens: z.number().int().positive().default(2e5),
131
131
  reservedOutputTokens: z.number().int().nonnegative().default(2e4),
132
- autoCompactBufferTokens: z.number().int().nonnegative().default(13e3),
132
+ autoCompactBufferTokens: z.number().int().nonnegative().default(27e3),
133
133
  warningBufferTokens: z.number().int().nonnegative().default(2e4),
134
134
  blockingBufferTokens: z.number().int().nonnegative().default(3e3),
135
135
  microcompactKeepRecentToolResults: z.number().int().nonnegative().default(5),
@@ -4,7 +4,7 @@ import {
4
4
  message,
5
5
  redactErrorText,
6
6
  redactSecrets
7
- } from "./chunk-WB4IJXFT.js";
7
+ } from "./chunk-BKOOYSGH.js";
8
8
 
9
9
  // src/rpc/server.ts
10
10
  import { createInterface } from "readline";
@@ -10,7 +10,7 @@ import {
10
10
  useStdout,
11
11
  use_app_default,
12
12
  use_input_default
13
- } from "./chunk-F2HMO5Q2.js";
13
+ } from "./chunk-36UKDXPF.js";
14
14
  import "./chunk-RQTYHUMK.js";
15
15
  import "./chunk-S32JRDZK.js";
16
16
  export {
package/dist/cli-main.js CHANGED
@@ -3,7 +3,7 @@ import { createRequire } from "node:module"; const require = createRequire(impor
3
3
  import {
4
4
  FlavorRpcServer,
5
5
  TraceRecorder
6
- } from "./chunk-TQXNF337.js";
6
+ } from "./chunk-RV3PTIQE.js";
7
7
  import {
8
8
  staticTaskLines
9
9
  } from "./chunk-2BF7CVWZ.js";
@@ -23,7 +23,8 @@ import {
23
23
  packageVersion,
24
24
  redactErrorText,
25
25
  runPalBroker
26
- } from "./chunk-WB4IJXFT.js";
26
+ } from "./chunk-BKOOYSGH.js";
27
+ import "./chunk-CIVPKHF4.js";
27
28
  import {
28
29
  McpServerConfigSchema,
29
30
  McpServerNameSchema,
@@ -32,7 +33,7 @@ import {
32
33
  readRecoverableFile,
33
34
  setProjectSkillDisabled,
34
35
  updateProtectedFile
35
- } from "./chunk-JFKRX2KG.js";
36
+ } from "./chunk-HJOCNKPN.js";
36
37
  import "./chunk-RQTYHUMK.js";
37
38
  import "./chunk-S32JRDZK.js";
38
39
 
@@ -750,7 +751,7 @@ async function runEvaluationFile(path, outputPath) {
750
751
  })),
751
752
  ...parsed.maxTokens === void 0 ? {} : { maxTokens: parsed.maxTokens }
752
753
  };
753
- const configured = await import("./load-LTTKVPJZ.js").then(({ loadConfig: loadConfig2 }) => loadConfig2({ cwd: workspace, home: process.env.USERPROFILE ?? process.env.HOME ?? workspace }));
754
+ const configured = await import("./load-DELQW42K.js").then(({ loadConfig: loadConfig2 }) => loadConfig2({ cwd: workspace, home: process.env.USERPROFILE ?? process.env.HOME ?? workspace }));
754
755
  const environment = createExecutionEnvironment(workspace, configured.config.execution) ?? new LocalExecutionEnvironment();
755
756
  let report;
756
757
  try {
@@ -875,9 +876,9 @@ function createProgram(dependencies = {}) {
875
876
  }
876
877
  async function runInteractiveCli(props) {
877
878
  const [{ render, AlternateScreen }, { createElement }, { App }] = await Promise.all([
878
- import("./claude-ink-ORUA7GWG.js"),
879
+ import("./claude-ink-AUZBJ3BE.js"),
879
880
  import("react"),
880
- import("./app-DDYOKGCX.js")
881
+ import("./app-VPS5QIRK.js")
881
882
  ]);
882
883
  const instance = await render(createElement(
883
884
  AlternateScreen,