nolo-cli 0.64.2 → 0.65.0

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/index.js CHANGED
@@ -815,7 +815,7 @@ async function runScript(script, forwardedArgs, env) {
815
815
  return proc.exited;
816
816
  }
817
817
  async function launchTuiWorkspace(args2) {
818
- const { startTuiWorkspace } = await import("./readlineWorkspace-PKOMYDRS.js");
818
+ const { startTuiWorkspace } = await import("./readlineWorkspace-7FNJDYLD.js");
819
819
  const { createTuiSummaryLlmCaller } = await import("./tuiSummaryLlmCaller-F3DPFFZM.js");
820
820
  return startTuiWorkspace({
821
821
  ...args2,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nolo-cli",
3
- "version": "0.64.2",
3
+ "version": "0.65.0",
4
4
  "type": "module",
5
5
  "description": "Agent-first terminal workspace for Nolo",
6
6
  "bin": {
@@ -2215,7 +2215,7 @@ function createHistoryOutputStream(history, onUpdate) {
2215
2215
  };
2216
2216
  return stream;
2217
2217
  }
2218
- function applyScrollAction(history, action, output, inputLines, reservedRows = 0) {
2218
+ function computeScrollMetrics(history, output, inputLines, reservedRows = 0) {
2219
2219
  const tty = output;
2220
2220
  const rows = tty.rows ?? 24;
2221
2221
  const columns = tty.columns ?? 80;
@@ -2230,6 +2230,15 @@ function applyScrollAction(history, action, output, inputLines, reservedRows = 0
2230
2230
  totalLines += currentTurnSeparator(history) + countTurnLines(history.currentRole, history.currentContent, contentWidth);
2231
2231
  }
2232
2232
  const maxScrollTop = Math.max(0, totalLines - visibleHeight);
2233
+ return { visibleHeight, contentWidth, totalLines, maxScrollTop };
2234
+ }
2235
+ function applyScrollAction(history, action, output, inputLines, reservedRows = 0) {
2236
+ const { visibleHeight, maxScrollTop } = computeScrollMetrics(
2237
+ history,
2238
+ output,
2239
+ inputLines,
2240
+ reservedRows
2241
+ );
2233
2242
  history.followBottom = false;
2234
2243
  switch (action) {
2235
2244
  case "page-up":
@@ -8214,7 +8223,118 @@ function createTuiRender(host) {
8214
8223
  renderHistoryToOutput,
8215
8224
  renderHistoryUnderDialog,
8216
8225
  scheduleRender,
8217
- flushPendingRender
8226
+ flushPendingRender,
8227
+ // 立即绘制一帧同步帧(BSU/ESU + 光标隐藏)。流式路径走 33ms 节流的
8228
+ // scheduleRender;平滑滚动推进器按 16ms 自己的节奏调用这里,避免被节流
8229
+ // 抹平成"每 33ms 一跳"。
8230
+ paintImmediate: paintSyncedFrame
8231
+ };
8232
+ }
8233
+
8234
+ // packages/cli/tui/tuiScrollAnimation.ts
8235
+ var SCROLL_ANIMATION_TICK_MS = 16;
8236
+ var DEFAULT_SCROLL_MAX_LINES_PER_TICK = 6;
8237
+ function readPositiveInt(raw, fallback, max) {
8238
+ if (raw === void 0 || raw.trim() === "") return fallback;
8239
+ const parsed = Number.parseInt(raw, 10);
8240
+ if (!Number.isFinite(parsed) || parsed < 1) return fallback;
8241
+ return Math.min(parsed, max);
8242
+ }
8243
+ function resolveScrollTuning(env = process.env) {
8244
+ return {
8245
+ wheelLines: readPositiveInt(env.NOLO_TUI_WHEEL_LINES, WHEEL_SCROLL_LINES, 50),
8246
+ maxLinesPerTick: readPositiveInt(
8247
+ env.NOLO_TUI_SCROLL_MAX_STEP,
8248
+ DEFAULT_SCROLL_MAX_LINES_PER_TICK,
8249
+ 100
8250
+ )
8251
+ };
8252
+ }
8253
+ var defaultTickTimer = {
8254
+ every(ms, fn) {
8255
+ const handle = setInterval(fn, ms);
8256
+ handle.unref?.();
8257
+ return () => clearInterval(handle);
8258
+ }
8259
+ };
8260
+ function createScrollAnimator(deps) {
8261
+ const { wheelLines, maxLinesPerTick } = resolveScrollTuning(
8262
+ deps.env ?? process.env
8263
+ );
8264
+ const timer = deps.tickTimer ?? defaultTickTimer;
8265
+ const history = deps.history;
8266
+ let goal = null;
8267
+ let intent = null;
8268
+ let stopTicker = null;
8269
+ const metrics = () => computeScrollMetrics(
8270
+ history,
8271
+ deps.output,
8272
+ deps.getInputLines(),
8273
+ deps.getReservedRows?.() ?? 0
8274
+ );
8275
+ const stop = () => {
8276
+ if (stopTicker) {
8277
+ stopTicker();
8278
+ stopTicker = null;
8279
+ }
8280
+ goal = null;
8281
+ intent = null;
8282
+ };
8283
+ const settle = (maxScrollTop) => {
8284
+ if (intent === "down" && history.scrollTop >= maxScrollTop) {
8285
+ history.followBottom = true;
8286
+ }
8287
+ stop();
8288
+ };
8289
+ const tick = () => {
8290
+ if (goal === null) {
8291
+ stop();
8292
+ return;
8293
+ }
8294
+ const { maxScrollTop } = metrics();
8295
+ if (goal > maxScrollTop) goal = maxScrollTop;
8296
+ const delta = goal - history.scrollTop;
8297
+ if (delta === 0) {
8298
+ settle(maxScrollTop);
8299
+ return;
8300
+ }
8301
+ const step = Math.sign(delta) * Math.min(Math.abs(delta), maxLinesPerTick);
8302
+ history.scrollTop += step;
8303
+ deps.onPaint();
8304
+ if (history.scrollTop === goal) {
8305
+ settle(maxScrollTop);
8306
+ }
8307
+ };
8308
+ const ensureTicker = () => {
8309
+ if (!stopTicker) {
8310
+ stopTicker = timer.every(SCROLL_ANIMATION_TICK_MS, tick);
8311
+ }
8312
+ };
8313
+ return {
8314
+ /**
8315
+ * 一条滚轮报告:折算为目标位置并保证推进器在跑。
8316
+ * followBottom 先退出(与 applyScrollAction 的 wheel 分支一致)。
8317
+ */
8318
+ wheel(direction) {
8319
+ const isOpposing = intent !== null && intent !== direction;
8320
+ const base = isOpposing ? history.scrollTop : goal ?? history.scrollTop;
8321
+ const next = direction === "up" ? base - wheelLines : base + wheelLines;
8322
+ goal = Math.max(0, next);
8323
+ intent = direction;
8324
+ if (goal === history.scrollTop) {
8325
+ stop();
8326
+ return;
8327
+ }
8328
+ history.followBottom = false;
8329
+ ensureTicker();
8330
+ },
8331
+ /** 外部直接改了 scrollTop(键盘滚动、选区拖拽自动滚动)前调用,避免两套写入打架。 */
8332
+ cancel() {
8333
+ stop();
8334
+ },
8335
+ isAnimating() {
8336
+ return goal !== null;
8337
+ }
8218
8338
  };
8219
8339
  }
8220
8340
 
@@ -10053,6 +10173,15 @@ async function runTuiWorkspace(options) {
10053
10173
  onRecordsPolled: (records) => runCompletionWatcher.observe(records)
10054
10174
  });
10055
10175
  const history = createTurnHistory();
10176
+ let handleTranscriptScroll = (action) => {
10177
+ applyScrollAction(
10178
+ history,
10179
+ action,
10180
+ output,
10181
+ fixedInput.getInputLines(),
10182
+ dialogHost.getReservedRows()
10183
+ );
10184
+ };
10056
10185
  const dialogHost = createDialogHost({
10057
10186
  composer: {
10058
10187
  pause: () => fixedInput.pause(),
@@ -10073,13 +10202,7 @@ async function runTuiWorkspace(options) {
10073
10202
  fixedInput.repaint(buffer, cursorPos);
10074
10203
  },
10075
10204
  onTranscriptScroll: (action) => {
10076
- applyScrollAction(
10077
- history,
10078
- action,
10079
- output,
10080
- fixedInput.getInputLines(),
10081
- dialogHost.getReservedRows()
10082
- );
10205
+ handleTranscriptScroll(action);
10083
10206
  },
10084
10207
  output,
10085
10208
  // The decoder-drain hook is only bound once the interactive raw-mode
@@ -10096,7 +10219,8 @@ async function runTuiWorkspace(options) {
10096
10219
  renderHistoryToOutput,
10097
10220
  renderHistoryUnderDialog,
10098
10221
  scheduleRender,
10099
- flushPendingRender
10222
+ flushPendingRender,
10223
+ paintImmediate
10100
10224
  } = createTuiRender({
10101
10225
  output,
10102
10226
  history,
@@ -10112,6 +10236,28 @@ async function runTuiWorkspace(options) {
10112
10236
  },
10113
10237
  getReservedRows: () => dialogHost.getReservedRows()
10114
10238
  });
10239
+ const scrollAnimator = createScrollAnimator({
10240
+ history,
10241
+ output,
10242
+ getInputLines: () => fixedInput.getInputLines(),
10243
+ getReservedRows: () => dialogHost.getReservedRows(),
10244
+ onPaint: () => paintImmediate(),
10245
+ env: effectiveEnv
10246
+ });
10247
+ handleTranscriptScroll = (action) => {
10248
+ if (action === "wheel-up" || action === "wheel-down") {
10249
+ scrollAnimator.wheel(action === "wheel-up" ? "up" : "down");
10250
+ return;
10251
+ }
10252
+ scrollAnimator.cancel();
10253
+ applyScrollAction(
10254
+ history,
10255
+ action,
10256
+ output,
10257
+ fixedInput.getInputLines(),
10258
+ dialogHost.getReservedRows()
10259
+ );
10260
+ };
10115
10261
  const refreshGitStatus = () => {
10116
10262
  if ((options.env?.NOLO_CLI_GIT_STATUS ?? process.env.NOLO_CLI_GIT_STATUS) === "0") return;
10117
10263
  void detectGitStatusAsync(state.cwd).then((gitStatus) => {
@@ -10433,6 +10579,7 @@ async function runTuiWorkspace(options) {
10433
10579
  if (done) return;
10434
10580
  done = true;
10435
10581
  sessionEnded = true;
10582
+ scrollAnimator.cancel();
10436
10583
  if (resizeTimer !== null) {
10437
10584
  clearTimeout(resizeTimer);
10438
10585
  resizeTimer = null;
@@ -10561,6 +10708,7 @@ ${err.message}` : ""}`
10561
10708
  const startAutoScroll = (direction, mouseX) => {
10562
10709
  autoScrollDirection = direction;
10563
10710
  lastDragMouseX = mouseX;
10711
+ scrollAnimator.cancel();
10564
10712
  if (autoScrollTimer) return;
10565
10713
  autoScrollTimer = setInterval(() => {
10566
10714
  if (!selectionState.dragging || !selectionState.anchor || fixedInput.isPaused()) {
@@ -10771,9 +10919,9 @@ ${merged}` : merged;
10771
10919
  if (mouseEvent) {
10772
10920
  if (fixedInput.isPaused()) return;
10773
10921
  if (mouseEvent.kind === "wheel") {
10774
- const scrollAction2 = mouseEvent.wheelDirection === "down" ? "wheel-down" : "wheel-up";
10775
- applyScrollAction(history, scrollAction2, output, fixedInput.getInputLines());
10776
- scheduleRender();
10922
+ scrollAnimator.wheel(
10923
+ mouseEvent.wheelDirection === "down" ? "down" : "up"
10924
+ );
10777
10925
  return;
10778
10926
  }
10779
10927
  const tty = output;
@@ -10793,6 +10941,7 @@ ${merged}` : merged;
10793
10941
  const screenCol = mouseEvent.x - 1;
10794
10942
  if (mouseEvent.kind === "press" && mouseEvent.button === "left") {
10795
10943
  stopAutoScroll();
10944
+ scrollAnimator.cancel();
10796
10945
  if (screenRow < visibleHeight && screenCol < contentWidth) {
10797
10946
  const hit = hitTestHistory(
10798
10947
  history,
@@ -10872,6 +11021,7 @@ ${merged}` : merged;
10872
11021
  const scrollAction = parseScrollAction(sequence);
10873
11022
  if (scrollAction) {
10874
11023
  if (fixedInput.isPaused()) return;
11024
+ scrollAnimator.cancel();
10875
11025
  applyScrollAction(history, scrollAction, output, fixedInput.getInputLines());
10876
11026
  paintFrame(buffer);
10877
11027
  return;