nolo-cli 0.33.0-alpha.17 → 0.33.0-alpha.18

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.
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-FV63ICZC.js";
5
5
  import {
6
6
  runAgentCreateCommand
7
- } from "./chunk-SIMPRWQ7.js";
7
+ } from "./chunk-WAFMSVUX.js";
8
8
  import {
9
9
  positionalArgs,
10
10
  readFlagValue
@@ -16,7 +16,7 @@ import {
16
16
  fetchWithTransportFallback,
17
17
  resolveAgentRecordFromHybridStore
18
18
  } from "./chunk-YDTYDA5W.js";
19
- import "./chunk-7QJGS6K6.js";
19
+ import "./chunk-JALRRIVE.js";
20
20
  import "./chunk-IEHZHWN7.js";
21
21
  import "./chunk-X2DAEZ3X.js";
22
22
  import {
@@ -83,7 +83,7 @@ async function defaultExecuteCli(provider, prompt, options) {
83
83
  return executeCli(provider, prompt, options);
84
84
  }
85
85
  async function forwardConnectorRunMessage(machine, message, pushMessage, executeCli, env, fetchImpl) {
86
- const { handleConnectorRunMessage } = await import("./machineWsRunDispatch-R7MHM75C.js");
86
+ const { handleConnectorRunMessage } = await import("./machineWsRunDispatch-3R7MOVIQ.js");
87
87
  return handleConnectorRunMessage(message, pushMessage, executeCli, env, fetchImpl);
88
88
  }
89
89
  async function detectLaunchableMachineInfo() {
@@ -2,10 +2,10 @@ import {
2
2
  runAgentCreateCommand,
3
3
  runAgentReadCommand,
4
4
  runAgentUpdateCommand
5
- } from "./chunk-SIMPRWQ7.js";
5
+ } from "./chunk-WAFMSVUX.js";
6
6
  import "./chunk-UNHKVD2N.js";
7
7
  import "./chunk-YDTYDA5W.js";
8
- import "./chunk-7QJGS6K6.js";
8
+ import "./chunk-JALRRIVE.js";
9
9
  import "./chunk-IEHZHWN7.js";
10
10
  import "./chunk-X2DAEZ3X.js";
11
11
  import "./chunk-EKLRRZIN.js";
@@ -7,13 +7,13 @@ import {
7
7
  prependSubjectDialogMarker,
8
8
  resolveWorkflowReference,
9
9
  runAgentRunCommand
10
- } from "./chunk-HZKZP7QH.js";
10
+ } from "./chunk-5ZOD4E7E.js";
11
11
  import {
12
12
  parseAgentRunArgs
13
13
  } from "./chunk-ZHYK3N56.js";
14
14
  import "./chunk-UNHKVD2N.js";
15
15
  import "./chunk-YDTYDA5W.js";
16
- import "./chunk-7QJGS6K6.js";
16
+ import "./chunk-JALRRIVE.js";
17
17
  import "./chunk-IEHZHWN7.js";
18
18
  import "./chunk-X2DAEZ3X.js";
19
19
  import "./chunk-EKLRRZIN.js";
@@ -25,7 +25,7 @@ import {
25
25
  parseAgentRunEvent,
26
26
  t,
27
27
  toolLabel
28
- } from "./chunk-7QJGS6K6.js";
28
+ } from "./chunk-JALRRIVE.js";
29
29
  import {
30
30
  NOLO_PROJECT_MANAGER_AGENT_KEY,
31
31
  isLocalCliAgentKey
@@ -423,8 +423,16 @@ var STRINGS = {
423
423
  zh: "\u5F53\u524D\u73AF\u5883\u6CA1\u6709\u53EF\u7528\u7684\u526A\u8D34\u677F\u3002\u6700\u540E\u4E00\u6761\u56DE\u590D\u5DF2\u6253\u5370\u5728\u4E0A\u65B9\uFF0C\u53EF\u624B\u52A8\u590D\u5236\u3002"
424
424
  },
425
425
  copyUsage: {
426
- en: "Usage: /copy",
427
- zh: "\u7528\u6CD5\uFF1A/copy"
426
+ en: "Usage: /copy or /copy view",
427
+ zh: "\u7528\u6CD5\uFF1A/copy \u6216 /copy view"
428
+ },
429
+ copyViewTitle: {
430
+ en: "Copy view \u2014 conversation history",
431
+ zh: "\u590D\u5236\u89C6\u56FE \u2014 \u5BF9\u8BDD\u5386\u53F2"
432
+ },
433
+ copyViewHint: {
434
+ en: "Drag to select. Use PageUp/PageDown, Home/End, or the mouse wheel to scroll. Press Esc or Enter to return.",
435
+ zh: "\u76F4\u63A5\u62D6\u9009\u3002\u53EF\u7528 PageUp/PageDown\u3001Home/End \u6216\u9F20\u6807\u6EDA\u8F6E\u6EDA\u52A8\u3002\u6309 Esc \u6216 Enter \u8FD4\u56DE\u3002"
428
436
  },
429
437
  historyNoToken: {
430
438
  en: "History requires an auth token. Run `nolo login` or set AUTH_TOKEN.",
@@ -706,8 +714,10 @@ var STRINGS = {
706
714
  " /resume <dialogId> Resume a dialog directly by id",
707
715
  " /lang <zh|en> Switch interface language",
708
716
  " /copy Copy the last reply to the clipboard",
717
+ " /copy view Open native scrollback for partial selection (Ctrl+O)",
709
718
  " /mouse <on|off> Toggle mouse mode (off = drag to select text)",
710
719
  " /altscreen <on|off> Toggle the terminal alternate screen (default on; off shares shell scrollback)",
720
+ " Shift+drag Select text natively even while mouse mode is on (terminal bypass)",
711
721
  " /doc List attached docs",
712
722
  " /doc attach <doc> Attach a doc to this workspace",
713
723
  " /skill List attached skills",
@@ -741,8 +751,10 @@ var STRINGS = {
741
751
  " /resume <dialogId> \u6309 id \u76F4\u63A5\u6062\u590D\u5BF9\u8BDD",
742
752
  " /lang <zh|en> \u5207\u6362\u754C\u9762\u8BED\u8A00",
743
753
  " /copy \u590D\u5236\u6700\u540E\u4E00\u6761\u56DE\u590D\u5230\u526A\u8D34\u677F",
754
+ " /copy view \u6253\u5F00\u539F\u751F\u56DE\u6EDA\u8FDB\u884C\u8DE8\u5C4F\u62D6\u9009\uFF08Ctrl+O\uFF09",
744
755
  " /mouse <on|off> \u5207\u6362\u9F20\u6807\u6A21\u5F0F\uFF08off \u540E\u53EF\u76F4\u63A5\u62D6\u9009\u6587\u672C\uFF09",
745
756
  " /altscreen <on|off> \u5207\u6362\u7EC8\u7AEF\u5907\u7528\u5C4F\uFF08\u9ED8\u8BA4 on\uFF1Boff \u6539\u4E3A\u4E0E shell \u5171\u7528\u56DE\u6EDA\uFF09",
757
+ " Shift+\u62D6\u62FD \u9F20\u6807\u6A21\u5F0F\u5F00\u542F\u65F6\u4E5F\u53EF\u539F\u751F\u9009\u4E2D\u6587\u672C\uFF08\u7EC8\u7AEF\u7ED5\u8FC7\u4FEE\u9970\u952E\uFF09",
746
758
  " /doc \u5217\u51FA\u5DF2\u6302\u8F7D\u7684\u6587\u6863",
747
759
  " /doc attach <doc> \u6302\u8F7D\u6587\u6863\u5230\u5F53\u524D\u5DE5\u4F5C\u533A",
748
760
  " /skill \u5217\u51FA\u5DF2\u6302\u8F7D\u7684\u6280\u80FD",
@@ -11231,12 +11243,16 @@ function createCliLocalRuntimeAdapter(deps) {
11231
11243
  }
11232
11244
  } : void 0;
11233
11245
  if (isAntigravityOAuthAgent(agentConfig)) {
11234
- const accessToken = await apiKeyRefResolver("antigravity");
11235
- if (!accessToken) {
11236
- throw new Error(
11237
- 'OAuth credential for "antigravity" not found locally. Run `nolo auth antigravity`.'
11238
- );
11239
- }
11246
+ const resolveAccessToken = async () => {
11247
+ const accessToken = await apiKeyRefResolver("antigravity");
11248
+ if (!accessToken) {
11249
+ throw new Error(
11250
+ 'OAuth credential for "antigravity" not found locally. Run `nolo auth antigravity`.'
11251
+ );
11252
+ }
11253
+ return accessToken;
11254
+ };
11255
+ await resolveAccessToken();
11240
11256
  const credential = readOAuthCredential("antigravity");
11241
11257
  const { requestedToolNames: requestedToolNames2, tools: tools2 } = resolveProviderOpenAiToolBundle(
11242
11258
  agentConfig,
@@ -11272,9 +11288,10 @@ function createCliLocalRuntimeAdapter(deps) {
11272
11288
  requestedToolNames: requestedToolNames2,
11273
11289
  openAiToolNames: summarizeOpenAiToolNames(tools2)
11274
11290
  });
11275
- const result = await fetchAntigravityCloudCodeCompletion({
11291
+ const accessToken = await resolveAccessToken();
11292
+ const sendCcaRequest = (token) => fetchAntigravityCloudCodeCompletion({
11276
11293
  agentConfig,
11277
- accessToken,
11294
+ accessToken: token,
11278
11295
  metadata: credential?.metadata ?? null,
11279
11296
  openAiBody,
11280
11297
  signal: options?.signal,
@@ -11285,6 +11302,27 @@ function createCliLocalRuntimeAdapter(deps) {
11285
11302
  loopbackRequest
11286
11303
  })
11287
11304
  });
11305
+ let result = await sendCcaRequest(accessToken);
11306
+ if (result.status === 401) {
11307
+ try {
11308
+ const refreshed = await apiKeyRefResolver("antigravity", {
11309
+ force: true
11310
+ });
11311
+ if (refreshed && refreshed !== accessToken) {
11312
+ logLocalRuntimeDiagnostic("provider.request.auth_retry", {
11313
+ agentKey: agentConfig.key,
11314
+ transport: "antigravity-cloud-code"
11315
+ });
11316
+ result = await sendCcaRequest(refreshed);
11317
+ }
11318
+ } catch (error) {
11319
+ logLocalRuntimeDiagnostic("provider.request.auth_retry_failed", {
11320
+ agentKey: agentConfig.key,
11321
+ transport: "antigravity-cloud-code",
11322
+ error: toErrorMessage(error)
11323
+ });
11324
+ }
11325
+ }
11288
11326
  await recordLocalAvailability(result.status, result.body);
11289
11327
  if (result.status < 200 || result.status >= 300) {
11290
11328
  const errMsg = result.body && typeof result.body === "object" && result.body.error && typeof result.body.error.message === "string" ? result.body.error.message : JSON.stringify(result.body);
@@ -12,7 +12,7 @@ import {
12
12
  } from "./chunk-YDTYDA5W.js";
13
13
  import {
14
14
  clearCliLocalRuntimePreparedAgentCache
15
- } from "./chunk-7QJGS6K6.js";
15
+ } from "./chunk-JALRRIVE.js";
16
16
  import {
17
17
  resolveCliAgentKeyInput
18
18
  } from "./chunk-EKLRRZIN.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createCliLocalRuntimeAdapter,
3
3
  summarizeEndpoint
4
- } from "./chunk-7QJGS6K6.js";
4
+ } from "./chunk-JALRRIVE.js";
5
5
  import {
6
6
  clipMultilineText
7
7
  } from "./chunk-AQBJUKBP.js";
package/index.js CHANGED
@@ -126,7 +126,7 @@ function createDaemonShortcutCommand(path, description, command2) {
126
126
  // packages/cli/agentInternalCommandEntries.ts
127
127
  function getAgentInternalCommandEntries() {
128
128
  const createAgentRunCommand = (path, description) => createContextCommand(path, description, async (args2, ctx) => {
129
- const { runAgentRunCommand } = await import("./agentRunCommand-VUUNH3YL.js");
129
+ const { runAgentRunCommand } = await import("./agentRunCommand-KUXEU2C3.js");
130
130
  return runAgentRunCommand(args2, {
131
131
  env: ctx.env,
132
132
  scriptDir: ctx.scriptDir,
@@ -142,7 +142,7 @@ function getAgentInternalCommandEntries() {
142
142
  return runAgentListCommand(args2, deps);
143
143
  }),
144
144
  createEnvCommand(["agent", "create"], "Create a new agent", async (args2, deps) => {
145
- const { runAgentCreateCommand } = await import("./agentRecordCommands-D2WA5AUO.js");
145
+ const { runAgentCreateCommand } = await import("./agentRecordCommands-5QROGD2I.js");
146
146
  return runAgentCreateCommand(args2, deps);
147
147
  }),
148
148
  createEnvCommand(["agent", "pull"], "Cache an agent for local runs", async (args2, deps) => {
@@ -150,7 +150,7 @@ function getAgentInternalCommandEntries() {
150
150
  return runAgentPullCommand(args2, deps);
151
151
  }),
152
152
  createEnvCommand(["agent", "read"], "Read a single agent", async (args2, deps) => {
153
- const { runAgentReadCommand } = await import("./agentRecordCommands-D2WA5AUO.js");
153
+ const { runAgentReadCommand } = await import("./agentRecordCommands-5QROGD2I.js");
154
154
  return runAgentReadCommand(args2, deps);
155
155
  }),
156
156
  createAgentRunCommand(["agent", "run"], "Run an agent"),
@@ -179,26 +179,26 @@ function getAgentInternalCommandEntries() {
179
179
  return runSetupOfflineMarxistsAgentCommand(args2, deps);
180
180
  }),
181
181
  createEnvCommand(["agent", "update"], "Update agent fields", async (args2, deps) => {
182
- const { runAgentUpdateCommand } = await import("./agentRecordCommands-D2WA5AUO.js");
182
+ const { runAgentUpdateCommand } = await import("./agentRecordCommands-5QROGD2I.js");
183
183
  return runAgentUpdateCommand(args2, deps);
184
184
  }),
185
185
  createEnvCommand(["agent", "email", "provision"], "Provision a controlled inbox for an agent", async (args2, deps) => {
186
- const { runAgentEmailProvisionCommand } = await import("./agentEmailCommands-VAZMNKEB.js");
186
+ const { runAgentEmailProvisionCommand } = await import("./agentEmailCommands-W4RJ323L.js");
187
187
  return runAgentEmailProvisionCommand(args2, deps);
188
188
  }),
189
189
  createEnvCommand(["agent", "email", "bind"], "Bind an existing email address to an agent", async (args2, deps) => {
190
- const { runAgentEmailBindCommand } = await import("./agentEmailCommands-VAZMNKEB.js");
190
+ const { runAgentEmailBindCommand } = await import("./agentEmailCommands-W4RJ323L.js");
191
191
  return runAgentEmailBindCommand(args2, deps);
192
192
  }),
193
193
  createEnvCommand(["agent", "email", "transfer"], "Transfer an email identity from one agent to another (cross-account)", async (args2, deps) => {
194
- const { runAgentEmailTransferCommand } = await import("./agentEmailCommands-VAZMNKEB.js");
194
+ const { runAgentEmailTransferCommand } = await import("./agentEmailCommands-W4RJ323L.js");
195
195
  return runAgentEmailTransferCommand(args2, deps);
196
196
  }),
197
197
  createEnvCommand(
198
198
  ["agent", "email", "create-and-provision"],
199
199
  "Create an agent and provision its controlled inbox",
200
200
  async (args2, deps) => {
201
- const { runAgentEmailCreateAndProvisionCommand } = await import("./agentEmailCommands-VAZMNKEB.js");
201
+ const { runAgentEmailCreateAndProvisionCommand } = await import("./agentEmailCommands-W4RJ323L.js");
202
202
  return runAgentEmailCreateAndProvisionCommand(args2, deps);
203
203
  }
204
204
  ),
@@ -219,15 +219,15 @@ function getAgentInternalCommandEntries() {
219
219
  return runAgentRevokeGrantCommand(args2, deps);
220
220
  }),
221
221
  createEnvCommand(["agent", "bind-current"], "Bind an agent to this machine", async (args2, deps) => {
222
- const { runAgentBindCurrentCommand } = await import("./agentMachineCommands-EJBEFNZL.js");
222
+ const { runAgentBindCurrentCommand } = await import("./agentMachineCommands-PZZH5H5U.js");
223
223
  return runAgentBindCurrentCommand(args2, deps);
224
224
  }),
225
225
  createEnvCommand(["agent", "smoke-current"], "Smoke test a bound agent through this machine", async (args2, deps) => {
226
- const { runAgentSmokeCurrentCommand } = await import("./agentMachineCommands-EJBEFNZL.js");
226
+ const { runAgentSmokeCurrentCommand } = await import("./agentMachineCommands-PZZH5H5U.js");
227
227
  return runAgentSmokeCurrentCommand(args2, deps);
228
228
  }),
229
229
  createEnvCommand(["agent", "runtime-doctor"], "Diagnose current machine runtime compatibility", async (args2, deps) => {
230
- const { runAgentRuntimeDoctorCommand } = await import("./agentMachineCommands-EJBEFNZL.js");
230
+ const { runAgentRuntimeDoctorCommand } = await import("./agentMachineCommands-PZZH5H5U.js");
231
231
  return runAgentRuntimeDoctorCommand(args2, deps);
232
232
  }),
233
233
  createAgentRunCommand(["agent", "chat"], "Chat with an agent")
@@ -408,15 +408,15 @@ function getSystemInternalCommandEntries(renderHelpText2) {
408
408
  return runWhoamiCommand({ args: args2, env: ctx.env });
409
409
  }),
410
410
  createEntrypointEnvCommand(["connect"], "Send machine heartbeats or hold a connector websocket", async (args2, deps) => {
411
- const { runMachineConnectCommand } = await import("./machineCommands-NCNUR7QK.js");
411
+ const { runMachineConnectCommand } = await import("./machineCommands-HKV467DU.js");
412
412
  return runMachineConnectCommand(args2, deps);
413
413
  }),
414
414
  createDaemonShortcutCommand(["daemon"], "Run the connector daemon shortcut", async (args2, deps) => {
415
- const { runMachineConnectCommand } = await import("./machineCommands-NCNUR7QK.js");
415
+ const { runMachineConnectCommand } = await import("./machineCommands-HKV467DU.js");
416
416
  return runMachineConnectCommand(args2, deps);
417
417
  }),
418
418
  createEnvCommand(["machine", "status"], "List machines registered to the current profile", async (args2, deps) => {
419
- const { runMachineStatusCommand } = await import("./machineCommands-NCNUR7QK.js");
419
+ const { runMachineStatusCommand } = await import("./machineCommands-HKV467DU.js");
420
420
  return runMachineStatusCommand(args2, deps);
421
421
  }),
422
422
  createEnvCommand(["doctor", "runtime"], "Diagnose local-first agent runtime selection", async (args2, deps) => {
@@ -796,7 +796,7 @@ async function runScript(script, forwardedArgs, env) {
796
796
  return proc.exited;
797
797
  }
798
798
  async function launchTuiWorkspace(args2) {
799
- const { startTuiWorkspace } = await import("./readlineWorkspace-ZA2HSCJI.js");
799
+ const { startTuiWorkspace } = await import("./readlineWorkspace-ZBBCDHJG.js");
800
800
  const { createTuiSummaryLlmCaller } = await import("./tuiSummaryLlmCaller-WUVAOIYU.js");
801
801
  return startTuiWorkspace({
802
802
  ...args2,
@@ -7,8 +7,8 @@ import {
7
7
  buildMachinePermissionPromptBlock,
8
8
  handleConnectorRunMessage,
9
9
  resolveMachineRunPermissionPolicy
10
- } from "./chunk-QSGZ2QGY.js";
11
- import "./chunk-7QJGS6K6.js";
10
+ } from "./chunk-ZOQTWQOA.js";
11
+ import "./chunk-JALRRIVE.js";
12
12
  import "./chunk-IEHZHWN7.js";
13
13
  import "./chunk-X2DAEZ3X.js";
14
14
  import "./chunk-EKLRRZIN.js";
@@ -11,8 +11,8 @@ import {
11
11
  runtimeWorkspacePermissionPolicy,
12
12
  scopePermissionPolicyToRuntimeWorkspace,
13
13
  summarizeAgentConfigForLogs
14
- } from "./chunk-QSGZ2QGY.js";
15
- import "./chunk-7QJGS6K6.js";
14
+ } from "./chunk-ZOQTWQOA.js";
15
+ import "./chunk-JALRRIVE.js";
16
16
  import "./chunk-IEHZHWN7.js";
17
17
  import "./chunk-X2DAEZ3X.js";
18
18
  import "./chunk-EKLRRZIN.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nolo-cli",
3
- "version": "0.33.0-alpha.17",
3
+ "version": "0.33.0-alpha.18",
4
4
  "type": "module",
5
5
  "description": "Agent-first terminal workspace for Nolo",
6
6
  "bin": {
@@ -70,7 +70,7 @@ import {
70
70
  visibleWidth,
71
71
  wrapTextToLines,
72
72
  wrapTranscriptLine
73
- } from "./chunk-HZKZP7QH.js";
73
+ } from "./chunk-5ZOD4E7E.js";
74
74
  import "./chunk-ZHYK3N56.js";
75
75
  import {
76
76
  getReadableCliDb
@@ -87,7 +87,7 @@ import {
87
87
  readTimestamp,
88
88
  setCliLocale,
89
89
  t
90
- } from "./chunk-7QJGS6K6.js";
90
+ } from "./chunk-JALRRIVE.js";
91
91
  import "./chunk-IEHZHWN7.js";
92
92
  import "./chunk-X2DAEZ3X.js";
93
93
  import {
@@ -726,6 +726,24 @@ function parseScrollAction(sequence) {
726
726
  return null;
727
727
  }
728
728
  }
729
+ var WHEEL_SCROLL_LINES = 3;
730
+ function renderScrollbarRow(rowIndex, visibleHeight, totalLines, scrollTop) {
731
+ if (totalLines <= visibleHeight) return " ";
732
+ const trackHeight = visibleHeight;
733
+ const thumbSize = Math.max(
734
+ 1,
735
+ Math.floor(visibleHeight * visibleHeight / totalLines)
736
+ );
737
+ const maxScrollTop = totalLines - visibleHeight;
738
+ const thumbTop = Math.floor(
739
+ scrollTop / maxScrollTop * (trackHeight - thumbSize)
740
+ );
741
+ const thumbBottom = thumbTop + thumbSize;
742
+ if (rowIndex >= thumbTop && rowIndex < thumbBottom) {
743
+ return "\u2588";
744
+ }
745
+ return "\u2502";
746
+ }
729
747
 
730
748
  // packages/cli/tui/selectDialog.ts
731
749
  var CSI_ARROW_UP = "\x1B[A";
@@ -1971,6 +1989,26 @@ function buildTurnOffsets(history, contentWidth) {
1971
1989
  function currentTurnSeparator(history, density) {
1972
1990
  return density === "spacious" && (history.turns.length > 0 || history.currentRole === "user") ? 1 : 0;
1973
1991
  }
1992
+ function buildCopyViewLines(history) {
1993
+ const turns = [...history.turns];
1994
+ if (history.currentRole !== null) {
1995
+ turns.push({ role: history.currentRole, content: history.currentContent });
1996
+ }
1997
+ const lines = [];
1998
+ for (const [index, turn] of turns.entries()) {
1999
+ if (index > 0) lines.push("");
2000
+ const normalized = stripAnsi(turn.content).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
2001
+ if (turn.role === "local") {
2002
+ if (turn.command) {
2003
+ lines.push(`\u203A ${stripAnsi(turn.command)}`);
2004
+ }
2005
+ lines.push(...normalized.split("\n"));
2006
+ } else {
2007
+ lines.push(...normalized.split("\n"));
2008
+ }
2009
+ }
2010
+ return lines;
2011
+ }
1974
2012
  function renderHistory(output, history, inputLines) {
1975
2013
  const tty = output;
1976
2014
  if (!tty.isTTY) return;
@@ -2070,8 +2108,11 @@ function renderHistory(output, history, inputLines) {
2070
2108
  let frame = "";
2071
2109
  for (let i = 0; i < visibleHeight; i++) {
2072
2110
  const line = visibleLines[i] ?? "";
2073
- const padded = padOrTruncateToWidth(line, columns);
2074
- const rowContent = padded;
2111
+ const padded = padOrTruncateToWidth(line, contentWidth);
2112
+ const thumb = renderScrollbarRow(i, visibleHeight, totalLines, history.scrollTop);
2113
+ const scrollbarPrefix = colorEnabled ? themeColorSequence("chrome") : "";
2114
+ const scrollbarSuffix = colorEnabled ? "\x1B[39m" : "";
2115
+ const rowContent = `${padded}\x1B[${columns}G${scrollbarPrefix}${thumb}${scrollbarSuffix}`;
2075
2116
  nextLines[i] = rowContent;
2076
2117
  if (!prevLines || prevLines[i] !== rowContent) {
2077
2118
  frame += `\x1B[${i + 1};1H\x1B[2K${rowContent}`;
@@ -2106,35 +2147,50 @@ function createHistoryOutputStream(history, onUpdate) {
2106
2147
  }
2107
2148
  };
2108
2149
  }
2109
- function createNativeOutputStream(history, terminalOutput) {
2110
- return {
2111
- isTTY: true,
2112
- write(chunk) {
2113
- const text = typeof chunk === "string" ? chunk : chunk.toString();
2114
- applyOutputChunkToCurrentTurn(history, text);
2115
- terminalOutput.write(text);
2116
- return true;
2117
- }
2118
- };
2119
- }
2120
- function formatTurnLines(turn, contentWidth, colorEnabled = resolveCliColorEnabled()) {
2121
- return renderTurnBlock(
2122
- turn.role,
2123
- turn.content,
2124
- contentWidth,
2125
- colorEnabled,
2126
- turn.command
2127
- );
2128
- }
2129
- function commitTurnToTerminal(output, turn, options) {
2150
+ function applyScrollAction(history, action, output, inputLines) {
2130
2151
  const tty = output;
2131
- const contentWidth = options?.contentWidth ?? Math.max(1, (tty.columns ?? 80) - 1);
2132
- const colorEnabled = options?.colorEnabled ?? resolveCliColorEnabled();
2133
- const lines = formatTurnLines(turn, contentWidth, colorEnabled);
2134
- if (lines.length === 0) return;
2135
- const prefix = options?.addBlankSeparator ? "\n" : "";
2136
- output.write(`${prefix}${lines.join("\n")}
2137
- `);
2152
+ const rows = tty.rows ?? 24;
2153
+ const columns = tty.columns ?? 80;
2154
+ const visibleHeight = Math.max(1, rows - inputLines);
2155
+ const contentWidth = Math.max(1, columns - 1);
2156
+ const { totalLines: finalizedLines } = buildTurnOffsets(history, contentWidth);
2157
+ let totalLines = finalizedLines;
2158
+ if (history.currentRole !== null && history.currentContent) {
2159
+ totalLines += currentTurnSeparator(history, getActiveDensity()) + countTurnLines(history.currentRole, history.currentContent, contentWidth);
2160
+ }
2161
+ const maxScrollTop = Math.max(0, totalLines - visibleHeight);
2162
+ history.followBottom = false;
2163
+ switch (action) {
2164
+ case "page-up":
2165
+ history.scrollTop = Math.max(0, history.scrollTop - visibleHeight);
2166
+ break;
2167
+ case "page-down":
2168
+ history.scrollTop = Math.min(maxScrollTop, history.scrollTop + visibleHeight);
2169
+ break;
2170
+ case "half-page-up":
2171
+ history.scrollTop = Math.max(0, history.scrollTop - Math.floor(visibleHeight / 2));
2172
+ break;
2173
+ case "half-page-down":
2174
+ history.scrollTop = Math.min(
2175
+ maxScrollTop,
2176
+ history.scrollTop + Math.floor(visibleHeight / 2)
2177
+ );
2178
+ break;
2179
+ case "wheel-up":
2180
+ history.scrollTop = Math.max(0, history.scrollTop - WHEEL_SCROLL_LINES);
2181
+ break;
2182
+ case "wheel-down":
2183
+ history.scrollTop = Math.min(maxScrollTop, history.scrollTop + WHEEL_SCROLL_LINES);
2184
+ if (history.scrollTop >= maxScrollTop) history.followBottom = true;
2185
+ break;
2186
+ case "top":
2187
+ history.scrollTop = 0;
2188
+ break;
2189
+ case "bottom":
2190
+ history.scrollTop = maxScrollTop;
2191
+ history.followBottom = true;
2192
+ break;
2193
+ }
2138
2194
  }
2139
2195
 
2140
2196
  // packages/cli/tui/dialogHost.ts
@@ -3889,6 +3945,9 @@ function applyTuiInputKey(buffer, sequence, key = {}, cursorPos, options) {
3889
3945
  if (seq === "" || key.ctrl && key.name === "c") {
3890
3946
  return { buffer, cursorPos: curPos, abort: true };
3891
3947
  }
3948
+ if (seq === "" || key.ctrl && key.name === "o") {
3949
+ return { buffer, cursorPos: curPos, copyView: true };
3950
+ }
3892
3951
  if (seq === "\f" || key.ctrl && key.name === "l") {
3893
3952
  return { buffer, cursorPos: curPos, redraw: true };
3894
3953
  }
@@ -3973,7 +4032,7 @@ function applyTuiInputKey(buffer, sequence, key = {}, cursorPos, options) {
3973
4032
  const completed = completeSlashPrefix(buffer) ?? buffer;
3974
4033
  return { buffer: completed, cursorPos: completed.length };
3975
4034
  }
3976
- if (!seq || key.ctrl || key.meta || seq.startsWith("\x1B") || seq.charCodeAt(0) < 32) {
4035
+ if (!seq || key.ctrl || key.meta || seq.startsWith("\x1B")) {
3977
4036
  return { buffer, cursorPos: curPos };
3978
4037
  }
3979
4038
  const insertAt = snapCursorOutsidePasteChip(buffer, curPos);
@@ -4538,6 +4597,13 @@ function handleTuiInput(input, state) {
4538
4597
  action: { type: "pick-dialog" }
4539
4598
  };
4540
4599
  case "/copy":
4600
+ if (argText === "view") {
4601
+ return {
4602
+ nextState: state,
4603
+ output: "",
4604
+ action: { type: "copy-view" }
4605
+ };
4606
+ }
4541
4607
  if (argText) {
4542
4608
  return { nextState: state, output: t("copyUsage") };
4543
4609
  }
@@ -5078,7 +5144,6 @@ function createFixedInput(output, config) {
5078
5144
  };
5079
5145
  let lastScrollBottom = -1;
5080
5146
  const setScrollRegion = (lines, force = false) => {
5081
- if (!isAltScreenOn(output)) return;
5082
5147
  const bottom = Math.max(1, getRows() - lines);
5083
5148
  if (force || lastScrollBottom !== bottom) {
5084
5149
  write(`\x1B[1;${bottom}r`);
@@ -5087,11 +5152,10 @@ function createFixedInput(output, config) {
5087
5152
  };
5088
5153
  const saveCursor = () => write("\x1B7");
5089
5154
  const resetScrollRegion = () => {
5090
- if (!isAltScreenOn(output)) return;
5091
5155
  lastScrollBottom = -1;
5092
5156
  write("\x1B[r");
5093
5157
  };
5094
- let mouseEnabled = (process.env.NOLO_TUI_MOUSE ?? "").trim() === "1";
5158
+ let mouseEnabled = true;
5095
5159
  const enableMouse = () => {
5096
5160
  if (mouseEnabled) write("\x1B[?1006h\x1B[?1000h");
5097
5161
  };
@@ -5558,12 +5622,7 @@ var SIGNAL_EXIT_CODE = {
5558
5622
  };
5559
5623
  var restoreAltScreen = () => {
5560
5624
  try {
5561
- if (altScreenRestoreOutput) {
5562
- leaveAltScreen(altScreenRestoreOutput);
5563
- if (altScreenRestoreOutput.isTTY) {
5564
- altScreenRestoreOutput.write("\x1B[r\x1B[?2004l\x1B[?25h");
5565
- }
5566
- }
5625
+ if (altScreenRestoreOutput) leaveAltScreen(altScreenRestoreOutput);
5567
5626
  } catch {
5568
5627
  }
5569
5628
  };
@@ -5986,14 +6045,10 @@ async function startTuiWorkspace(options) {
5986
6045
  const fetchImpl = options.fetchImpl ?? fetch;
5987
6046
  const spawnRunner = options.spawnRunner ?? spawnProcess;
5988
6047
  const selfUpdater = options.selfUpdater ?? ((target) => runSelfUpdate({ output: target }));
5989
- const env = options.env ?? process.env;
5990
- const startInAltScreen = (env.NOLO_TUI_ALTSCREEN ?? "").trim() === "1";
5991
6048
  if (output.isTTY) {
5992
- if (startInAltScreen) {
5993
- enterAltScreen(output);
5994
- output.write("\x1B[2J\x1B[H");
5995
- }
6049
+ enterAltScreen(output);
5996
6050
  installAltScreenRestoreHandlers(output);
6051
+ output.write("\x1B[2J\x1B[H");
5997
6052
  }
5998
6053
  const detected = await detectTerminalBackground({
5999
6054
  stdin: input,
@@ -6019,8 +6074,8 @@ async function startTuiWorkspace(options) {
6019
6074
  let lastSentTitle = null;
6020
6075
  const syncWindowTitle = () => {
6021
6076
  if (!output.isTTY) return;
6022
- const env2 = options.env ?? process.env;
6023
- const rawSetting = (env2.NOLO_TUI_TITLE ?? "").trim().toLowerCase();
6077
+ const env = options.env ?? process.env;
6078
+ const rawSetting = (env.NOLO_TUI_TITLE ?? "").trim().toLowerCase();
6024
6079
  if (rawSetting === "0" || rawSetting === "false") return;
6025
6080
  const currentTitle = state.dialogLabel?.trim() ?? "";
6026
6081
  if (currentTitle === lastSentTitle) return;
@@ -6068,6 +6123,9 @@ async function startTuiWorkspace(options) {
6068
6123
  reconcile: (runId) => checkStaleRun(runId, { env: process.env }),
6069
6124
  onRecordsPolled: (records) => runCompletionWatcher.observe(records)
6070
6125
  });
6126
+ let copyViewExitResolver = null;
6127
+ let copyViewRender = null;
6128
+ let copyViewScrollTop = 0;
6071
6129
  const history = createTurnHistory();
6072
6130
  const dialogHost = createDialogHost({
6073
6131
  composer: {
@@ -6100,9 +6158,7 @@ async function startTuiWorkspace(options) {
6100
6158
  if (fixedInput.isPaused()) return;
6101
6159
  output.write("\x1B[?2026h\x1B[?25l");
6102
6160
  try {
6103
- if (isAltScreenOn(output)) {
6104
- renderHistory(output, history, fixedInput.getInputLines());
6105
- }
6161
+ renderHistory(output, history, fixedInput.getInputLines());
6106
6162
  if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
6107
6163
  } finally {
6108
6164
  output.write("\x1B[?25h\x1B[?2026l");
@@ -6164,7 +6220,6 @@ async function startTuiWorkspace(options) {
6164
6220
  const renderHistoryToOutput = () => {
6165
6221
  if (fixedInput.isPaused()) return;
6166
6222
  if (syncingLayout) return;
6167
- if (!isAltScreenOn(output)) return;
6168
6223
  syncingLayout = true;
6169
6224
  try {
6170
6225
  renderHistory(output, history, fixedInput.getInputLines());
@@ -6177,6 +6232,53 @@ async function startTuiWorkspace(options) {
6177
6232
  const text = lastReply ? stripAnsi(lastReply).trim() : "";
6178
6233
  return text || null;
6179
6234
  };
6235
+ const openCopyView = async () => {
6236
+ const lines = buildCopyViewLines(history);
6237
+ if (lines.length === 0) return false;
6238
+ if (!isInteractiveInput(input)) {
6239
+ output.write(`${lines.join("\n")}
6240
+ `);
6241
+ return true;
6242
+ }
6243
+ const tty = output;
6244
+ const renderCopyView = () => {
6245
+ const rows = Math.max(1, tty.rows ?? 24);
6246
+ const columns = Math.max(1, tty.columns ?? 80);
6247
+ const copyLines = buildCopyViewLines(history);
6248
+ const visibleHeight = Math.max(1, rows - 3);
6249
+ const maxScrollTop = Math.max(0, copyLines.length - visibleHeight);
6250
+ copyViewScrollTop = Math.max(0, Math.min(copyViewScrollTop, maxScrollTop));
6251
+ let frame = "\x1B[2J\x1B[H";
6252
+ frame += `${t("copyViewTitle")}
6253
+ `;
6254
+ for (let index = 0; index < visibleHeight; index++) {
6255
+ frame += `${fitAnsiLine(copyLines[copyViewScrollTop + index] ?? "", columns)}
6256
+ `;
6257
+ }
6258
+ frame += `${t("copyViewHint")}
6259
+ `;
6260
+ output.write(frame);
6261
+ };
6262
+ fixedInput.pause();
6263
+ copyViewScrollTop = 0;
6264
+ copyViewRender = renderCopyView;
6265
+ try {
6266
+ renderCopyView();
6267
+ await new Promise((resolve2) => {
6268
+ copyViewExitResolver = resolve2;
6269
+ });
6270
+ } finally {
6271
+ copyViewExitResolver = null;
6272
+ copyViewRender = null;
6273
+ output.write("\x1B[2J\x1B[H");
6274
+ resetHistoryFrameDiffCache(output);
6275
+ fixedInput.resumeFromDialog();
6276
+ flushPendingRender();
6277
+ renderHistoryToOutput();
6278
+ fixedInput.repaint(buffer, cursorPos);
6279
+ }
6280
+ return true;
6281
+ };
6180
6282
  let modalOwnsKeyboard = false;
6181
6283
  let rawActionGateTokenHandler = null;
6182
6284
  const AUTO_THEME_POLL_MS = 3e4;
@@ -6247,27 +6349,18 @@ async function startTuiWorkspace(options) {
6247
6349
  history.followBottom = true;
6248
6350
  const isInternalEvent = req.event.kind !== "user";
6249
6351
  const transcriptText = req.event.kind === "child-run-completed" ? req.event.displayText ?? req.event.text : message;
6250
- const userTurnContent = isInternalEvent ? dimCliText(transcriptText, resolveCliColorEnabled()) : transcriptText;
6251
6352
  startTurn(history, isInternalEvent ? "assistant" : "user");
6252
- appendToCurrentTurn(history, userTurnContent);
6353
+ appendToCurrentTurn(
6354
+ history,
6355
+ isInternalEvent ? dimCliText(transcriptText, resolveCliColorEnabled()) : transcriptText
6356
+ );
6253
6357
  finalizeCurrentTurn(history);
6254
- if (!isAltScreenOn(output)) {
6255
- commitTurnToTerminal(
6256
- output,
6257
- {
6258
- role: isInternalEvent ? "assistant" : "user",
6259
- content: userTurnContent
6260
- },
6261
- { addBlankSeparator: true }
6262
- );
6263
- } else {
6264
- renderHistoryToOutput();
6265
- }
6358
+ renderHistoryToOutput();
6266
6359
  if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
6267
6360
  startTurn(history, "assistant");
6268
- const agentOutput = isInteractiveInput(input) ? isAltScreenOn(output) ? createHistoryOutputStream(history, () => {
6361
+ const agentOutput = isInteractiveInput(input) ? createHistoryOutputStream(history, () => {
6269
6362
  scheduleRender();
6270
- }) : createNativeOutputStream(history, output) : output;
6363
+ }) : output;
6271
6364
  const requestUserChoice = isInteractiveInput(input) && dialogHost ? async (req2) => {
6272
6365
  modalOwnsKeyboard = true;
6273
6366
  try {
@@ -6345,11 +6438,7 @@ async function startTuiWorkspace(options) {
6345
6438
  if (isInteractiveInput(input)) {
6346
6439
  finalizeCurrentTurn(history);
6347
6440
  flushPendingRender();
6348
- if (isAltScreenOn(output)) {
6349
- renderHistoryToOutput();
6350
- } else {
6351
- output.write("\n");
6352
- }
6441
+ renderHistoryToOutput();
6353
6442
  if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
6354
6443
  }
6355
6444
  if (wasAborted) {
@@ -6418,19 +6507,7 @@ async function startTuiWorkspace(options) {
6418
6507
  }
6419
6508
  history.followBottom = true;
6420
6509
  appendLocalTurn(history, command, text);
6421
- if (!isAltScreenOn(output)) {
6422
- commitTurnToTerminal(
6423
- output,
6424
- {
6425
- role: "local",
6426
- content: text,
6427
- command
6428
- },
6429
- { addBlankSeparator: true }
6430
- );
6431
- } else {
6432
- renderHistoryToOutput();
6433
- }
6510
+ renderHistoryToOutput();
6434
6511
  if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
6435
6512
  };
6436
6513
  const persistExplicitAgentSwitch = (previousAgentKey) => {
@@ -6673,6 +6750,10 @@ async function startTuiWorkspace(options) {
6673
6750
  renderHistoryToOutput();
6674
6751
  fixedInput.repaint(buffer, cursorPos);
6675
6752
  }
6753
+ if (result.action?.type === "copy-view") {
6754
+ const opened = await openCopyView();
6755
+ if (!opened) emitCommandOutput(t("copyNothing"));
6756
+ }
6676
6757
  if (result.action?.type === "copy-last") {
6677
6758
  const text = readLatestAssistantReply() ?? "";
6678
6759
  if (!text) {
@@ -6719,11 +6800,6 @@ async function startTuiWorkspace(options) {
6719
6800
  history.currentContent = "";
6720
6801
  history.scrollTop = 0;
6721
6802
  history.followBottom = true;
6722
- if (!isAltScreenOn(output)) {
6723
- for (const turn of restored) {
6724
- commitTurnToTerminal(output, turn, { addBlankSeparator: true });
6725
- }
6726
- }
6727
6803
  state = {
6728
6804
  ...state,
6729
6805
  dialogId: pickResult.dialog.id,
@@ -6897,10 +6973,12 @@ ${err.message}` : ""}`
6897
6973
  };
6898
6974
  const onResize = () => {
6899
6975
  if (done) return;
6976
+ if (copyViewExitResolver) {
6977
+ copyViewRender?.();
6978
+ return;
6979
+ }
6900
6980
  if (fixedInput.isPaused()) {
6901
- if (isAltScreenOn(output)) {
6902
- renderHistory(output, history, fixedInput.getInputLines());
6903
- }
6981
+ renderHistory(output, history, fixedInput.getInputLines());
6904
6982
  fixedInput.repaint(buffer, cursorPos);
6905
6983
  return;
6906
6984
  }
@@ -6991,7 +7069,7 @@ ${err.message}` : ""}`
6991
7069
  };
6992
7070
  runWakeHandler = (event) => {
6993
7071
  if (done) return;
6994
- if (busy || fixedInput.isPaused()) {
7072
+ if (busy || fixedInput.isPaused() || copyViewExitResolver) {
6995
7073
  const { actionGateHandler, confirmDestructiveAction } = buildInteractiveTurnHandlers();
6996
7074
  ensureChatQueueBinding(actionGateHandler, confirmDestructiveAction).enqueue(event);
6997
7075
  if (fixedInput.active && !fixedInput.isPaused()) {
@@ -7008,6 +7086,49 @@ ${err.message}` : ""}`
7008
7086
  };
7009
7087
  const handleInputToken = async (sequence) => {
7010
7088
  if (done) return;
7089
+ if (copyViewExitResolver) {
7090
+ const tty = output;
7091
+ const visibleHeight = Math.max(1, (tty.rows ?? 24) - 3);
7092
+ const lines = buildCopyViewLines(history);
7093
+ const maxScrollTop = Math.max(0, lines.length - visibleHeight);
7094
+ const scrollAction2 = parseScrollAction(sequence);
7095
+ if (scrollAction2) {
7096
+ switch (scrollAction2) {
7097
+ case "page-up":
7098
+ copyViewScrollTop = Math.max(0, copyViewScrollTop - visibleHeight);
7099
+ break;
7100
+ case "page-down":
7101
+ copyViewScrollTop = Math.min(maxScrollTop, copyViewScrollTop + visibleHeight);
7102
+ break;
7103
+ case "half-page-up":
7104
+ copyViewScrollTop = Math.max(0, copyViewScrollTop - Math.max(1, Math.floor(visibleHeight / 2)));
7105
+ break;
7106
+ case "wheel-up":
7107
+ copyViewScrollTop = Math.max(0, copyViewScrollTop - WHEEL_SCROLL_LINES);
7108
+ break;
7109
+ case "half-page-down":
7110
+ copyViewScrollTop = Math.min(maxScrollTop, copyViewScrollTop + Math.max(1, Math.floor(visibleHeight / 2)));
7111
+ break;
7112
+ case "wheel-down":
7113
+ copyViewScrollTop = Math.min(maxScrollTop, copyViewScrollTop + WHEEL_SCROLL_LINES);
7114
+ break;
7115
+ case "top":
7116
+ copyViewScrollTop = 0;
7117
+ break;
7118
+ case "bottom":
7119
+ copyViewScrollTop = maxScrollTop;
7120
+ break;
7121
+ }
7122
+ copyViewRender?.();
7123
+ return;
7124
+ }
7125
+ if (sequence === "\x1B" || sequence === "\r" || sequence === "\n" || sequence === "") {
7126
+ const exitCopyView = copyViewExitResolver;
7127
+ copyViewExitResolver = null;
7128
+ exitCopyView();
7129
+ }
7130
+ return;
7131
+ }
7011
7132
  if (modalOwnsKeyboard) {
7012
7133
  rawActionGateTokenHandler?.(sequence);
7013
7134
  return;
@@ -7037,6 +7158,13 @@ ${merged}` : merged;
7037
7158
  return handleInputToken("\r");
7038
7159
  }
7039
7160
  const busyLock = busy;
7161
+ const scrollAction = parseScrollAction(sequence);
7162
+ if (scrollAction) {
7163
+ if (fixedInput.isPaused()) return;
7164
+ applyScrollAction(history, scrollAction, output, fixedInput.getInputLines());
7165
+ paintFrame(buffer);
7166
+ return;
7167
+ }
7040
7168
  if (busyLock && sequence === "\x1B" && activeTurnAbort) {
7041
7169
  if (activityIndicator.isStopping()) {
7042
7170
  forcedStop = true;
@@ -7063,27 +7191,23 @@ ${merged}` : merged;
7063
7191
  const result = applyTuiInputKey(buffer, sequence, {}, cursorPos, {
7064
7192
  pasteStore
7065
7193
  });
7066
- if (result.redraw) {
7067
- if (!isAltScreenOn(output)) {
7068
- output.write("\x1B[2J\x1B[3J\x1B[H");
7069
- for (const turn of history.turns) {
7070
- commitTurnToTerminal(output, turn, { addBlankSeparator: true });
7071
- }
7072
- if (history.currentRole !== null && history.currentContent) {
7073
- commitTurnToTerminal(
7074
- output,
7075
- { role: history.currentRole, content: history.currentContent },
7076
- { addBlankSeparator: true }
7077
- );
7078
- }
7079
- if (fixedInput.active) fixedInput.repaint(buffer, cursorPos, true);
7080
- } else {
7081
- resetHistoryFrameDiffCache(output);
7082
- renderHistoryToOutput();
7083
- if (fixedInput.active) fixedInput.repaint(buffer, cursorPos, true);
7194
+ if (result.copyView) {
7195
+ const opened = await openCopyView();
7196
+ if (!opened) {
7197
+ history.followBottom = true;
7198
+ startTurn(history, "assistant");
7199
+ appendToCurrentTurn(history, t("copyNothing"));
7200
+ finalizeCurrentTurn(history);
7201
+ paintFrame(buffer);
7084
7202
  }
7085
7203
  return;
7086
7204
  }
7205
+ if (result.redraw) {
7206
+ resetHistoryFrameDiffCache(output);
7207
+ renderHistoryToOutput();
7208
+ if (fixedInput.active) fixedInput.repaint(buffer, cursorPos, true);
7209
+ return;
7210
+ }
7087
7211
  if (result.abort) {
7088
7212
  fixedInput.disable();
7089
7213
  finish();
@@ -7266,6 +7390,7 @@ export {
7266
7390
  appendLocalTurn,
7267
7391
  appendToCurrentTurn,
7268
7392
  applyOutputChunkToCurrentTurn,
7393
+ applyScrollAction,
7269
7394
  applyTerminalOutputToText,
7270
7395
  buildWindowTitle,
7271
7396
  countPhysicalLines,
@@ -7282,6 +7407,7 @@ export {
7282
7407
  isAltScreenOn,
7283
7408
  leaveAltScreen,
7284
7409
  padOrTruncateToWidth,
7410
+ parseScrollAction,
7285
7411
  renderHistory,
7286
7412
  renderPinnedAgentNotice,
7287
7413
  resetHistoryFrameDiffCache,