mindweave 2.4.6 → 2.4.8

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/README.md CHANGED
@@ -25,7 +25,6 @@ It is built lean on purpose. Most of a coding agent's context budget goes on sca
25
25
  the model never needed. Mindweave keeps prompts thin and leaves the room for the model to
26
26
  reason about your code.
27
27
 
28
- Questions are welcome at zallinimann@gmail.com.
29
28
 
30
29
  ## Mindweave 1
31
30
 
@@ -137,6 +136,8 @@ running it on real projects and fixing what breaks, and nearly everything in the
137
136
  changelog started as a failure someone watched happen. [What to include, and what is
138
137
  especially worth reporting.](CONTRIBUTING.md#reporting-a-bug)
139
138
 
139
+ Questions are welcome at zallinimann@gmail.com
140
+
140
141
  ## License
141
142
 
142
143
  [Apache License 2.0](LICENSE).
package/dist/cli/App.js CHANGED
@@ -25,7 +25,8 @@ import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "r
25
25
  import { isAbsolute, resolve } from "node:path";
26
26
  import { Box, Static, Text, measureElement, useApp, useInput, useStdout } from "ink";
27
27
  import { compactNow, contextUsed, respond } from "../dynamo/engine.js";
28
- import { contextPressure, sharpContextWindow } from "../dynamo/contextWindow.js";
28
+ import { contextPressure, sharpContextWindow, autoCompactThreshold } from "../dynamo/contextWindow.js";
29
+ import { contextBudget, formatBudget } from "../dynamo/contextBudget.js";
29
30
  import { createSession, resumeSession, reloadProjectMemory } from "../memory/session.js";
30
31
  import { saveSession, listSessions } from "../memory/store.js";
31
32
  import { stopChassis } from "../alternator/lane.js";
@@ -52,6 +53,7 @@ import { accessRefusal } from "../drivers/providerError.js";
52
53
  import { resolveAttachments, stripAttachments } from "./attachments.js";
53
54
  import { collapsePastes, wrapPastedText } from "../memory/pastedText.js";
54
55
  import { createDropHandles, expandHandles } from "./dropHandles.js";
56
+ import { shouldReactToBackground } from "./backgroundWake.js";
55
57
  import { TIPS, TipLine, nextTip, randomTipIndex } from "./components/TipLine.js";
56
58
  import { completePath } from "./pathComplete.js";
57
59
  import { formatHelp } from "./help.js";
@@ -77,7 +79,7 @@ import { applySelection, ctrlCShouldCopy, isEmpty, selectionText } from "./selec
77
79
  import { latestScreen, repaintOverlay, setFrameOverlay } from "./framebuffer/overlay.js";
78
80
  import { requestFullRepaint } from "./framebuffer/writer.js";
79
81
  import { copyToClipboard } from "./clipboard.js";
80
- import { chatLayout, reflowScroll } from "./chatAnchor.js";
82
+ import { chatLayout, reflowScroll, growScroll } from "./chatAnchor.js";
81
83
  import { growFill, INLINE_LIVE_RESERVE, NO_FILL } from "./startupFill.js";
82
84
  import { setRowsBelowCaret } from "./exitCursor.js";
83
85
  import { caretCell } from "./caretPark.js";
@@ -252,6 +254,9 @@ export function App({ resumeSessionId, initialScreen }) {
252
254
  /** Reading position captured at a width change, pending the first measurement at the
253
255
  * new width. Null except across that one frame. See the width-change block below. */
254
256
  const reflowFrom = useRef(null);
257
+ /** `contentHeight` as of the last measurement, so growth between renders is a delta
258
+ * the scroll position can be compensated by. See the growth-compensation block below. */
259
+ const prevContentHeight = useRef(null);
255
260
  // Each block's real rendered height, so blocks off screen can be replaced by a
256
261
  // spacer of the exact same size instead of being laid out in full — see
257
262
  // `virtualWindow.ts` for why that is the whole performance story, and why exact
@@ -848,13 +853,22 @@ export function App({ resumeSessionId, initialScreen }) {
848
853
  });
849
854
  }, []);
850
855
  // When a background shell finishes and Mindweave is idle, react to it automatically.
856
+ // `modalOpen` is a dependency on purpose: an open menu holds the wake back, and closing
857
+ // it has to re-run this check or the finished command waits for some unrelated render.
858
+ const modalOpen = overlay !== null || mcpOpen || trustOpen || needsKey;
851
859
  useEffect(() => {
852
- if (!ready || busy || needsKey || reactingRef.current)
853
- return;
854
860
  const mgr = session.current?.toolContext.backgroundShells;
855
- if (mgr && mgr.pendingCount() > 0)
861
+ const wake = shouldReactToBackground({
862
+ ready,
863
+ busy,
864
+ needsKey,
865
+ reacting: reactingRef.current,
866
+ modalOpen,
867
+ pending: mgr?.pendingCount() ?? 0,
868
+ });
869
+ if (wake)
856
870
  void reactToBackground();
857
- }, [bgTick, busy, ready, needsKey]);
871
+ }, [bgTick, busy, ready, needsKey, modalOpen]);
858
872
  // When the turn ends, send what's queued — CONSECUTIVE plain messages together, as
859
873
  // one turn (see messageQueue.ts for why), a slash command on its own. Chains: each
860
874
  // send ends, this fires again, until the queue is empty.
@@ -1158,6 +1172,15 @@ export function App({ resumeSessionId, initialScreen }) {
1158
1172
  const next = reflowScroll(from.scrolled, from.maxScroll, Math.max(0, height - chatRows));
1159
1173
  setScrollUp((s) => (s === next ? s : next));
1160
1174
  }
1175
+ else if (prevContentHeight.current !== null) {
1176
+ // Ordinary growth, not a resize — see growScroll for why this has to hold the
1177
+ // reader's absolute position rather than leave `scrollUp` where it was.
1178
+ const prev = prevContentHeight.current;
1179
+ const next = growScroll(scrollUp, prev, height);
1180
+ if (next !== scrollUp)
1181
+ setScrollUp(next);
1182
+ }
1183
+ prevContentHeight.current = height;
1161
1184
  });
1162
1185
  // Measure each block that was rendered without a known height yet, so the next
1163
1186
  // frame can replace it with an exact spacer when it scrolls out of view.
@@ -2441,9 +2464,25 @@ export function App({ resumeSessionId, initialScreen }) {
2441
2464
  return;
2442
2465
  }
2443
2466
  if (name === "/context") {
2444
- const text = s.projectContext || "No project context was captured for this directory.";
2445
- note("project context (what Mindweave sees at startup):");
2446
- say(text);
2467
+ // The old /context printed the startup project blurb, which answers a question
2468
+ // nobody asks twice. The one people ask every time a compaction fires is what is
2469
+ // filling the window — so that is the default, and the blurb moved to an argument.
2470
+ if (arg.trim() === "project") {
2471
+ const text = s.projectContext || "No project context was captured for this directory.";
2472
+ note("project context (what Mindweave sees at startup):");
2473
+ say(text);
2474
+ return;
2475
+ }
2476
+ // Only a measurement taken against the CURRENT model is passed through; a figure
2477
+ // carried over from another provider's tool serialisation would be the largest
2478
+ // single error in the table, and the table is meant to settle arguments.
2479
+ const measured = s.contextOverhead;
2480
+ const overhead = measured && measured.model === s.modelConfig.model ? measured.tokens : undefined;
2481
+ const budget = contextBudget(s.transcript, overhead);
2482
+ note("context breakdown:");
2483
+ say(`${formatBudget(budget, sharpContextWindow(s.modelConfig.model), autoCompactThreshold(s.modelConfig.model))}
2484
+
2485
+ /context project shows what Mindweave read about this directory at startup.`);
2447
2486
  return;
2448
2487
  }
2449
2488
  // /undo — roll back file changes. Bare undoes the last turn; `list` shows what's