@aragon-agent/cli 0.6.1 → 0.6.3

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.
Files changed (127) hide show
  1. package/CHANGELOG.md +216 -0
  2. package/README.md +241 -18
  3. package/dist/agent/agent-mode.d.ts +39 -0
  4. package/dist/agent/agent-mode.js +36 -0
  5. package/dist/agent/controller.d.ts +74 -1
  6. package/dist/agent/controller.js +200 -1
  7. package/dist/agent/headless.d.ts +8 -0
  8. package/dist/agent/headless.js +47 -0
  9. package/dist/agent/reducer.d.ts +120 -0
  10. package/dist/agent/reducer.js +201 -2
  11. package/dist/cli.js +297 -6
  12. package/dist/commands/builtins.js +73 -23
  13. package/dist/commands/registry.d.ts +27 -0
  14. package/dist/compaction/archive.d.ts +175 -0
  15. package/dist/compaction/archive.js +267 -0
  16. package/dist/compaction/child.d.ts +97 -0
  17. package/dist/compaction/child.js +113 -0
  18. package/dist/compaction/command.d.ts +41 -0
  19. package/dist/compaction/command.js +342 -0
  20. package/dist/compaction/compactor.d.ts +268 -0
  21. package/dist/compaction/compactor.js +689 -0
  22. package/dist/compaction/digest.d.ts +62 -0
  23. package/dist/compaction/digest.js +164 -0
  24. package/dist/compaction/limits.d.ts +165 -0
  25. package/dist/compaction/limits.js +172 -0
  26. package/dist/compaction/pressure.d.ts +135 -0
  27. package/dist/compaction/pressure.js +167 -0
  28. package/dist/compaction/summarize-call.d.ts +70 -0
  29. package/dist/compaction/summarize-call.js +86 -0
  30. package/dist/compaction/summary-prompt.d.ts +85 -0
  31. package/dist/compaction/summary-prompt.js +197 -0
  32. package/dist/compaction/tail-budget.d.ts +52 -0
  33. package/dist/compaction/tail-budget.js +67 -0
  34. package/dist/compaction/types.d.ts +179 -0
  35. package/dist/compaction/types.js +22 -0
  36. package/dist/compaction/wiring.d.ts +255 -0
  37. package/dist/compaction/wiring.js +556 -0
  38. package/dist/config/app-paths.d.ts +14 -0
  39. package/dist/config/app-paths.js +16 -0
  40. package/dist/config/cli-commands.d.ts +41 -0
  41. package/dist/config/cli-commands.js +77 -1
  42. package/dist/config/env.d.ts +4 -1
  43. package/dist/config/env.js +92 -2
  44. package/dist/config/load.d.ts +31 -0
  45. package/dist/config/load.js +63 -1
  46. package/dist/config/schema.d.ts +159 -0
  47. package/dist/config/schema.js +137 -0
  48. package/dist/config/store.js +17 -1
  49. package/dist/config/ui-state.d.ts +44 -6
  50. package/dist/config/ui-state.js +21 -12
  51. package/dist/diagnostics/doctor.js +41 -1
  52. package/dist/exec/events.d.ts +50 -1
  53. package/dist/exec/index.js +8 -4
  54. package/dist/exec/runner.js +44 -2
  55. package/dist/input/mouse-events.d.ts +31 -5
  56. package/dist/input/mouse-events.js +86 -27
  57. package/dist/input/stdin-mouse-filter.d.ts +8 -3
  58. package/dist/logging/install.d.ts +51 -1
  59. package/dist/logging/install.js +28 -0
  60. package/dist/logging/logger.d.ts +1 -1
  61. package/dist/session/cli-commands.js +23 -1
  62. package/dist/session/persist.js +19 -0
  63. package/dist/session/store.d.ts +18 -0
  64. package/dist/session/store.js +59 -2
  65. package/dist/team/report.js +7 -1
  66. package/dist/team/runtime.d.ts +10 -0
  67. package/dist/team/runtime.js +7 -0
  68. package/dist/team/subagent.d.ts +31 -1
  69. package/dist/team/subagent.js +42 -15
  70. package/dist/team/types.d.ts +8 -0
  71. package/dist/ui/ActivityLine.d.ts +19 -1
  72. package/dist/ui/ActivityLine.js +10 -2
  73. package/dist/ui/App.d.ts +48 -1
  74. package/dist/ui/App.js +317 -17
  75. package/dist/ui/Composer.d.ts +26 -1
  76. package/dist/ui/Composer.js +6 -6
  77. package/dist/ui/PromptInput.d.ts +34 -1
  78. package/dist/ui/PromptInput.js +54 -3
  79. package/dist/ui/StatusBar.d.ts +40 -0
  80. package/dist/ui/StatusBar.js +13 -4
  81. package/dist/ui/TeamPanel.js +7 -1
  82. package/dist/ui/Transcript.d.ts +21 -1
  83. package/dist/ui/Transcript.js +32 -2
  84. package/dist/ui/clipboard.d.ts +52 -0
  85. package/dist/ui/clipboard.js +91 -0
  86. package/dist/ui/entries/CompactionCard.d.ts +75 -0
  87. package/dist/ui/entries/CompactionCard.js +158 -0
  88. package/dist/ui/frame-differ.d.ts +46 -0
  89. package/dist/ui/frame-differ.js +94 -10
  90. package/dist/ui/gauge.d.ts +16 -1
  91. package/dist/ui/gauge.js +25 -6
  92. package/dist/ui/glyphs.d.ts +11 -0
  93. package/dist/ui/glyphs.js +2 -0
  94. package/dist/ui/layout/ScrollViewport.d.ts +43 -6
  95. package/dist/ui/layout/ScrollViewport.js +128 -21
  96. package/dist/ui/layout/budget.d.ts +10 -3
  97. package/dist/ui/layout/budget.js +10 -3
  98. package/dist/ui/layout/follow-state.d.ts +137 -0
  99. package/dist/ui/layout/follow-state.js +150 -0
  100. package/dist/ui/layout/virtual-window.d.ts +11 -0
  101. package/dist/ui/layout/virtual-window.js +34 -0
  102. package/dist/ui/overlays/HelpOverlay.js +18 -2
  103. package/dist/ui/overlays/SettingsScreen.d.ts +47 -1
  104. package/dist/ui/overlays/SettingsScreen.js +60 -1
  105. package/dist/ui/palettes.d.ts +16 -0
  106. package/dist/ui/palettes.js +6 -0
  107. package/dist/ui/screen.d.ts +22 -0
  108. package/dist/ui/screen.js +70 -8
  109. package/dist/ui/selection/highlight.d.ts +39 -0
  110. package/dist/ui/selection/highlight.js +64 -0
  111. package/dist/ui/selection/screen-mirror.d.ts +24 -0
  112. package/dist/ui/selection/screen-mirror.js +35 -0
  113. package/dist/ui/selection/selection-controller.d.ts +88 -0
  114. package/dist/ui/selection/selection-controller.js +282 -0
  115. package/dist/ui/selection/selection.d.ts +90 -0
  116. package/dist/ui/selection/selection.js +223 -0
  117. package/dist/ui/stdout-frame-writer.d.ts +33 -0
  118. package/dist/ui/stdout-frame-writer.js +15 -0
  119. package/dist/ui/theme.d.ts +11 -0
  120. package/dist/ui/theme.js +2 -0
  121. package/dist/ui/transcript-text.js +23 -0
  122. package/dist/ui/use-startup-notices.d.ts +62 -5
  123. package/dist/ui/use-startup-notices.js +81 -17
  124. package/dist/ui/use-wheel-routing.js +8 -0
  125. package/dist/ui/win-vt-force.d.ts +136 -0
  126. package/dist/ui/win-vt-force.js +237 -0
  127. package/package.json +4 -2
@@ -111,6 +111,29 @@ function renderEntry(entry, glyphs) {
111
111
  const detail = entry.detail ? `: ${entry.detail}` : '';
112
112
  return [`${head} ${entry.status}${detail}`];
113
113
  }
114
+ case 'compaction': {
115
+ // A compaction is real spend AND a real, permanent loss of history, and an
116
+ // exported transcript that omitted it would be unaccountable for both —
117
+ // the argument the `team`, `retry` and `fast` cases above each make in
118
+ // their own words. It matters most here: this is the one entry kind whose
119
+ // presence explains why the messages the reader is looking for are gone.
120
+ const glyph = entry.applied ? glyphs.compaction : glyphs.toolError;
121
+ const duration = entry.durationMs === undefined ? '' : ` (${entry.durationMs}ms)`;
122
+ if (!entry.applied) {
123
+ const why = entry.reason ? `: ${entry.reason}` : '';
124
+ return [`${TOOL_INDENT}${glyph} context not compacted #${entry.index}${why}`];
125
+ }
126
+ const head = `${TOOL_INDENT}${glyph} context compacted #${entry.index} ` +
127
+ `${entry.messagesBefore} ${glyphs.arrowRight} ${entry.messagesAfter} messages, ` +
128
+ `${entry.tokensBefore} ${glyphs.arrowRight} ${entry.tokensAfter} tokens ` +
129
+ `[${entry.mode}] ${entry.model}${duration}`;
130
+ if (!entry.summary)
131
+ return [head];
132
+ return [
133
+ head,
134
+ ...entry.summary.split('\n').map((l) => `${TOOL_INDENT}${CONTINUATION_INDENT}${l}`),
135
+ ];
136
+ }
114
137
  case 'notice': {
115
138
  const glyph = entry.level === 'error'
116
139
  ? glyphs.error
@@ -12,11 +12,39 @@
12
12
  * not otherwise see — rather than belonging to the view.
13
13
  */
14
14
  import type { ViewAction } from '../agent/reducer.js';
15
+ /**
16
+ * The revision of the mouse notice a user must have seen for it to stay quiet
17
+ * (`state.json::mouseNoticeVersion`, `0` when absent).
18
+ *
19
+ * BUMP IT WHENEVER THE ADVICE CHANGES, never for a rewording that leaves the
20
+ * recommended actions alone — the same rule `VT_INPUT_NOTICE_VERSION` below
21
+ * carries, and for the same reason.
22
+ *
23
+ * `1` = the first text that names DRAG-SELECT and `/mouse`. `0` = the 0.6.2 text
24
+ * that taught the Shift bypass, recorded back when this was a boolean.
25
+ */
26
+ export declare const MOUSE_NOTICE_VERSION = 1;
27
+ /**
28
+ * The revision of the text below that a user must have seen for this notice to
29
+ * stay quiet (`state.json::vtInputNoticeVersion`, `0` when absent).
30
+ *
31
+ * BUMP IT WHENEVER THE ADVICE CHANGES, and never for a rewording that leaves
32
+ * the recommended actions alone. The two failure modes sit on either side of
33
+ * this constant: leaving it alone after changing what the user is told to press
34
+ * makes the change inert for everyone who already ran the old build — which is
35
+ * the entire affected population, since the notice only appears on machines
36
+ * where the key has been broken all along — while dropping the gate altogether
37
+ * turns a one-shot into a banner on every launch.
38
+ *
39
+ * `1` = the first text naming `MODE_TOGGLE_KEYS.fallback`. `0` = the 0.6.1 text
40
+ * that offered only `/plan`, recorded back when this was a boolean.
41
+ */
42
+ export declare const VT_INPUT_NOTICE_VERSION = 1;
15
43
  /**
16
44
  * The `shift-tab-and-mouse-wheel-dead-on-some-terminals` notice (F1'). Shown at
17
- * most once, ever, and only on a Windows console that cannot deliver either
18
- * input — see `ui/win-vt-input.ts` for why that is a property of the Node
19
- * version rather than of the terminal.
45
+ * most once per `VT_INPUT_NOTICE_VERSION`, and only on a Windows console that
46
+ * cannot deliver either input — see `ui/win-vt-input.ts` for why that is a
47
+ * property of the Node version rather than of the terminal.
20
48
  *
21
49
  * IT NAMES WHAT THE KEY DOES NOW, not merely that it is unavailable, and that
22
50
  * is the load-bearing half of the text. `Shift+Tab` on these machines is not
@@ -25,6 +53,16 @@ import type { ViewAction } from '../agent/reducer.js';
25
53
  * draft. A user told only "this key does not work" keeps pressing it and keeps
26
54
  * losing text without ever connecting the two.
27
55
  *
56
+ * THE FALLBACK KEY COMES BEFORE `/plan` IN BOTH BRANCHES, and it is named from
57
+ * `MODE_TOGGLE_KEYS` rather than spelled out, so the four consumers of that
58
+ * constant cannot drift apart. This notice is the last one of them to be wired
59
+ * up, and it is also the only channel that reaches the affected user without
60
+ * being asked to: the composer hint does not exist at all in inline mode and is
61
+ * switched off by a short terminal or `hints: false` in full-screen, and
62
+ * `/help` has to be typed by someone whose current belief is "the key does
63
+ * nothing" rather than "I should read the help". Ordering follows cost — one
64
+ * keystroke before four and a return.
65
+ *
28
66
  * `Shift+Up` / `Shift+Down` is the scroll workaround named here because it is
29
67
  * the only one PROVEN to survive: both Node versions emit `\x1b[1;2A` for it,
30
68
  * byte for byte, in the probe data. `PgUp` / `PgDn` is expected to work too but
@@ -74,7 +112,21 @@ export interface MouseNoticeOptions {
74
112
  * re-deriving either one here would put them back out of step.
75
113
  */
76
114
  enabled: boolean;
77
- /** Record that it was shown (`state.json`). Called once, after dispatch. */
115
+ /**
116
+ * Whether DRAG-SELECT is running too, i.e. the resolved `mouseSelect`.
117
+ *
118
+ * A SECOND FLAG RATHER THAN AN INFERENCE, for the reason the field above
119
+ * records about its own two halves: "the mouse is captured" and "dragging
120
+ * selects text" are not the same claim, and a session that turned drag-select
121
+ * off must not be handed advice about a mode that is not running.
122
+ */
123
+ selectEnabled: boolean;
124
+ /**
125
+ * Record that `MOUSE_NOTICE_VERSION` was shown (`state.json`). Called once,
126
+ * after dispatch — and it must write that constant, not `true`: the gate
127
+ * compares against it, so a caller storing anything else either replays the
128
+ * notice forever or silences a future revision of the text.
129
+ */
78
130
  onSeen: () => void;
79
131
  }
80
132
  export interface VtInputNoticeOptions {
@@ -90,7 +142,12 @@ export interface VtInputNoticeOptions {
90
142
  * the renderer disagreeing about which mode is running.
91
143
  */
92
144
  fullscreen: boolean;
93
- /** Record that it was shown (`state.json`). Called once, after dispatch. */
145
+ /**
146
+ * Record that `VT_INPUT_NOTICE_VERSION` was shown (`state.json`). Called once,
147
+ * after dispatch — and it must write that constant, not `true`: the gate
148
+ * below compares against it, so a caller storing anything else either replays
149
+ * the notice forever or silences a future revision of the text.
150
+ */
94
151
  onSeen: () => void;
95
152
  }
96
153
  export declare function useStartupNotices(dispatch: (action: ViewAction) => void, mouseNotice?: MouseNoticeOptions, retainNotice?: RetainNoticeOptions, vtInputNotice?: VtInputNoticeOptions): void;
@@ -12,19 +12,44 @@
12
12
  * not otherwise see — rather than belonging to the view.
13
13
  */
14
14
  import { useEffect, useRef } from 'react';
15
+ import { MODE_TOGGLE_KEYS } from '../agent/agent-mode.js';
15
16
  import { readConfigFile } from '../config/store.js';
16
- import { getMouseNoticeSeen, getVtInputNoticeSeen } from '../config/ui-state.js';
17
+ import { getMouseNoticeVersion, getVtInputNoticeVersion } from '../config/ui-state.js';
17
18
  import { getLogger } from '../logging/logger.js';
18
19
  /**
19
- * The §6.1 notice (mouse-wheel-region-routing). Shown at most once, ever.
20
+ * The revision of the mouse notice a user must have seen for it to stay quiet
21
+ * (`state.json::mouseNoticeVersion`, `0` when absent).
20
22
  *
21
- * R-3 is the only part of wheel region routing that reaches users who never
22
- * scroll, and it REMOVES a gesture — drag to select — that is used more often
23
- * than the one being added. A README is consulted only after the belief
24
- * "aragon broke copy/paste" has already formed.
23
+ * BUMP IT WHENEVER THE ADVICE CHANGES, never for a rewording that leaves the
24
+ * recommended actions alone — the same rule `VT_INPUT_NOTICE_VERSION` below
25
+ * carries, and for the same reason.
26
+ *
27
+ * `1` = the first text that names DRAG-SELECT and `/mouse`. `0` = the 0.6.2 text
28
+ * that taught the Shift bypass, recorded back when this was a boolean.
25
29
  */
26
- const MOUSE_SELECTION_NOTICE = 'Mouse wheel scrolls the transcript. Hold Shift to select text, ' +
27
- 'or turn it off with `aragon config set mouse false`.';
30
+ export const MOUSE_NOTICE_VERSION = 1;
31
+ /**
32
+ * The one-shot mouse notice (tui-selection-and-scroll-follow §5.2 row 29).
33
+ *
34
+ * IT NO LONGER TEACHES THE SHIFT WORKAROUND. That text was a workaround told
35
+ * once, in a line that scrolls away, naming a key that is not portable — Shift
36
+ * on xterm / VTE / Windows Terminal, Fn on macOS Terminal, a preference
37
+ * elsewhere — and it collided with our own Shift+wheel binding. The user report
38
+ * this feature exists for is the evidence that it was not enough (D-1).
39
+ *
40
+ * TWO TEXTS, because a `mouseSelect: false` session must not be told about a
41
+ * gesture that is not running — the same rule the vt-input notice follows below,
42
+ * and the reason `MouseNoticeOptions` carries the flag rather than this module
43
+ * re-deriving it.
44
+ */
45
+ function mouseNoticeText(selectEnabled) {
46
+ if (!selectEnabled) {
47
+ return ('Mouse wheel scrolls the transcript. Drag-select is off (mouseSelect); ' +
48
+ '/mouse off hands the mouse back to your terminal for this session.');
49
+ }
50
+ return ('Mouse wheel scrolls the transcript, and dragging selects text - releasing ' +
51
+ 'copies it. /mouse off hands the mouse back to your terminal for this session.');
52
+ }
28
53
  /**
29
54
  * The §5.1 notice (tui-render-performance). Shown once per run, and only when
30
55
  * the user's own `transcriptRetain` was below `transcriptWindow`.
@@ -39,11 +64,27 @@ function retainRaisedNotice(requested, resolved) {
39
64
  'so the whole scroll horizon stays reachable. Lower transcriptWindow too if you meant ' +
40
65
  'to cap memory.');
41
66
  }
67
+ /**
68
+ * The revision of the text below that a user must have seen for this notice to
69
+ * stay quiet (`state.json::vtInputNoticeVersion`, `0` when absent).
70
+ *
71
+ * BUMP IT WHENEVER THE ADVICE CHANGES, and never for a rewording that leaves
72
+ * the recommended actions alone. The two failure modes sit on either side of
73
+ * this constant: leaving it alone after changing what the user is told to press
74
+ * makes the change inert for everyone who already ran the old build — which is
75
+ * the entire affected population, since the notice only appears on machines
76
+ * where the key has been broken all along — while dropping the gate altogether
77
+ * turns a one-shot into a banner on every launch.
78
+ *
79
+ * `1` = the first text naming `MODE_TOGGLE_KEYS.fallback`. `0` = the 0.6.1 text
80
+ * that offered only `/plan`, recorded back when this was a boolean.
81
+ */
82
+ export const VT_INPUT_NOTICE_VERSION = 1;
42
83
  /**
43
84
  * The `shift-tab-and-mouse-wheel-dead-on-some-terminals` notice (F1'). Shown at
44
- * most once, ever, and only on a Windows console that cannot deliver either
45
- * input — see `ui/win-vt-input.ts` for why that is a property of the Node
46
- * version rather than of the terminal.
85
+ * most once per `VT_INPUT_NOTICE_VERSION`, and only on a Windows console that
86
+ * cannot deliver either input — see `ui/win-vt-input.ts` for why that is a
87
+ * property of the Node version rather than of the terminal.
47
88
  *
48
89
  * IT NAMES WHAT THE KEY DOES NOW, not merely that it is unavailable, and that
49
90
  * is the load-bearing half of the text. `Shift+Tab` on these machines is not
@@ -52,6 +93,16 @@ function retainRaisedNotice(requested, resolved) {
52
93
  * draft. A user told only "this key does not work" keeps pressing it and keeps
53
94
  * losing text without ever connecting the two.
54
95
  *
96
+ * THE FALLBACK KEY COMES BEFORE `/plan` IN BOTH BRANCHES, and it is named from
97
+ * `MODE_TOGGLE_KEYS` rather than spelled out, so the four consumers of that
98
+ * constant cannot drift apart. This notice is the last one of them to be wired
99
+ * up, and it is also the only channel that reaches the affected user without
100
+ * being asked to: the composer hint does not exist at all in inline mode and is
101
+ * switched off by a short terminal or `hints: false` in full-screen, and
102
+ * `/help` has to be typed by someone whose current belief is "the key does
103
+ * nothing" rather than "I should read the help". Ordering follows cost — one
104
+ * keystroke before four and a return.
105
+ *
55
106
  * `Shift+Up` / `Shift+Down` is the scroll workaround named here because it is
56
107
  * the only one PROVEN to survive: both Node versions emit `\x1b[1;2A` for it,
57
108
  * byte for byte, in the probe data. `PgUp` / `PgDn` is expected to work too but
@@ -79,12 +130,13 @@ export function vtInputDeadNotice(nodeVersion, fullscreen) {
79
130
  const cause = `Node ${nodeVersion} on Windows does not turn on the console's VT input mode, so `;
80
131
  const cost = 'it will not switch mode, and with the / palette or an @ popup open it completes that ' +
81
132
  'instead, overwriting your draft. ';
133
+ const toggle = `Press ${MODE_TOGGLE_KEYS.fallback} to switch mode instead (or /plan)`;
82
134
  if (!fullscreen) {
83
135
  return (`${cause}Shift+Tab never arrives: it comes through as a PLAIN TAB - ${cost}` +
84
- 'Use /plan to switch mode instead. Node 22.17.0+ (or 24.2.0+) restores it.');
136
+ `${toggle}. Node 22.17.0+ (or 24.2.0+) restores it.`);
85
137
  }
86
138
  return (`${cause}two keys never arrive: the wheel sends nothing at all, and Shift+Tab arrives ` +
87
- `as a PLAIN TAB - ${cost}Use /plan to switch mode and Shift+Up / Shift+Down to scroll. ` +
139
+ `as a PLAIN TAB - ${cost}${toggle}, and Shift+Up / Shift+Down to scroll. ` +
88
140
  'Node 22.17.0+ (or 24.2.0+) restores both.');
89
141
  }
90
142
  export function useStartupNotices(dispatch, mouseNotice, retainNotice, vtInputNotice) {
@@ -114,16 +166,24 @@ export function useStartupNotices(dispatch, mouseNotice, retainNotice, vtInputNo
114
166
  const noticeRef = useRef(mouseNotice);
115
167
  noticeRef.current = mouseNotice;
116
168
  const mouseEnabled = mouseNotice?.enabled ?? false;
169
+ const mouseSelectEnabled = mouseNotice?.selectEnabled ?? false;
117
170
  const mouseNoticeShown = useRef(false);
118
171
  useEffect(() => {
119
172
  if (!mouseEnabled || mouseNoticeShown.current)
120
173
  return;
121
- if (getMouseNoticeSeen())
174
+ // A VERSION, NOT THE OLD BOOLEAN (P1-8 / AC-16). The audience for the
175
+ // corrected text is exactly the set of users who already have
176
+ // `mouseNoticeSeen: true` on disk from the text it replaces, so reading that
177
+ // flag would make this change inert for everyone it is for.
178
+ //
179
+ // `>=`, not `!==`: a state file written by a NEWER build (a downgrade, or a
180
+ // shared home directory) has already told this user everything below.
181
+ if (getMouseNoticeVersion() >= MOUSE_NOTICE_VERSION)
122
182
  return;
123
183
  mouseNoticeShown.current = true;
124
- dispatch({ type: 'notice', level: 'info', text: MOUSE_SELECTION_NOTICE });
184
+ dispatch({ type: 'notice', level: 'info', text: mouseNoticeText(mouseSelectEnabled) });
125
185
  noticeRef.current?.onSeen();
126
- }, [mouseEnabled, dispatch]);
186
+ }, [mouseEnabled, mouseSelectEnabled, dispatch]);
127
187
  // The R-1 notice. `warn`, not `info`: unlike the one above it reports a
128
188
  // capability the user was promised and does not have, plus a key that is
129
189
  // actively editing their draft.
@@ -132,6 +192,10 @@ export function useStartupNotices(dispatch, mouseNotice, retainNotice, vtInputNo
132
192
  // out on the retain effect below — `App` builds that literal during render.
133
193
  // Its one-shot key is checked only AFTER the presence test, so a machine that
134
194
  // is not affected never touches `state.json` at all.
195
+ //
196
+ // `>=`, not `!==`: a state file written by a NEWER build (a downgrade, or a
197
+ // shared home directory) has already told this user everything the text below
198
+ // says, so re-showing it would be noise rather than news.
135
199
  const vtNoticeRef = useRef(vtInputNotice);
136
200
  vtNoticeRef.current = vtInputNotice;
137
201
  const vtNodeVersion = vtInputNotice?.nodeVersion;
@@ -140,7 +204,7 @@ export function useStartupNotices(dispatch, mouseNotice, retainNotice, vtInputNo
140
204
  useEffect(() => {
141
205
  if (vtNodeVersion === undefined || vtNoticeShown.current)
142
206
  return;
143
- if (getVtInputNoticeSeen())
207
+ if (getVtInputNoticeVersion() >= VT_INPUT_NOTICE_VERSION)
144
208
  return;
145
209
  vtNoticeShown.current = true;
146
210
  dispatch({
@@ -84,6 +84,14 @@ export function useWheelRouting(options) {
84
84
  }
85
85
  }, [flush]);
86
86
  const route = useCallback((event) => {
87
+ // The channel now carries press / drag / release as well
88
+ // (tui-selection-and-scroll-follow §4.4.2). ONE LINE, AT THE TOP, and the
89
+ // rest of this module is untouched: widening the single channel rather
90
+ // than adding a second is what keeps arrival order intact between a wheel
91
+ // notch and a button, and each subscriber ignoring what it does not own is
92
+ // the price of that.
93
+ if (event.kind !== 'wheel')
94
+ return;
87
95
  const current = optionsRef.current;
88
96
  const overlay = current.getOverlay();
89
97
  // THE OVERLAY BRANCH IS EVALUATED FIRST AND RETURNS FOR EVERY OVERLAY IT
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Turn `ENABLE_VIRTUAL_TERMINAL_INPUT` on for THIS console, from outside Node
3
+ * (shift-tab-mode-toggle-still-dead-on-windows, C1-M1).
4
+ *
5
+ * ASCII ONLY - this tree is inside the glyph scanner's scope.
6
+ *
7
+ * `win-vt-input.ts` answers "will this console ever deliver `CSI Z`?". This
8
+ * module answers "can we MAKE it deliver one?", and the measured answer is yes.
9
+ * The bit is a property of the console input buffer, not of Node, so any process
10
+ * attached to the same console can set it - and libuv's pre-22.17 translator
11
+ * forwards whatever characters the console generates, `ESC` `[` `Z` included.
12
+ * On Node 20.19.0, in one run, one injected `Shift+Tab` went from `09` to
13
+ * `1b 5b 5a` the instant an outside process OR-ed `0x0200` in (analysis section
14
+ * 3.2; raw probe data under `docs/diagnoses/.../repro/`).
15
+ *
16
+ * Node ships no FFI, so the outside process is `powershell.exe` calling
17
+ * `SetConsoleMode` through P/Invoke. Measured cost end to end: 722 ms, roughly
18
+ * 280 ms of process start plus 300 ms of `Add-Type` compiling C# at run time
19
+ * (analysis section 3.4). That is the whole price of not adding a native
20
+ * dependency, and it is paid ONCE, only on an affected Windows console.
21
+ *
22
+ * THREE TRAPS ARE ENCODED HERE, all of them measured rather than imagined:
23
+ *
24
+ * 1. `windowsHide: true` maps to `CREATE_NO_WINDOW`, which hands the child its
25
+ * OWN console. The helper then reads, sets and reports success against a
26
+ * console nobody is typing into: exit code 0, `SetConsoleMode` true, log
27
+ * clean, and no effect whatsoever - indistinguishable from "the fix was
28
+ * never written" (analysis section 3.5). Hence `windowsHide: false`.
29
+ * 2. The helper must open `CONIN$` with `CreateFileW`, never
30
+ * `GetStdHandle(STD_INPUT_HANDLE)`: it is spawned with stdin `ignore`, so
31
+ * its standard input is `NUL`. Sharing the real stdin instead is not an
32
+ * option - that handle is being reconfigured underneath it.
33
+ * 3. The `before` mode is the ONLY signal that distinguishes trap 1 from
34
+ * success, so the helper reports it and `decideVtForceOutcome` checks it.
35
+ * The check is a POSITIVE bitmask ("is this console already in raw mode"),
36
+ * not `before !== 0x01F7`: that number is one machine's default and varies
37
+ * with QuickEdit, with the host, and with a user who already turned VT input
38
+ * on. A magic-number comparison passes silently on the first machine whose
39
+ * default happens to differ, which puts trap 1 straight back (review R-3).
40
+ *
41
+ * WHAT UNDOES IT: `setRawMode(false)`. libuv rewrites the whole console mode on
42
+ * every raw-mode transition and its pre-22.17 path does not know about this bit,
43
+ * so one transition clears it (analysis section 3.3). That is why the caller
44
+ * must raise raw mode BEFORE calling this, and it is also the entire restore
45
+ * path - the bit outlives the process otherwise, and neither Windows nor the
46
+ * console clears it (analysis section 3.5b).
47
+ *
48
+ * FAILURE IS ALWAYS GRACEFUL. Constrained Language Mode disables `Add-Type`
49
+ * outright, and enterprise fleets ship it by policy - exactly the "another PC"
50
+ * population this bug is about. Every failure returns `ok: false` with a reason
51
+ * for the log and nothing else happens; the alternate mode-toggle key
52
+ * (`MODE_TOGGLE_KEYS.fallback`) is the rung below that needs none of this.
53
+ */
54
+ /** Console input mode bits (`consoleapi.h`). */
55
+ export declare const ENABLE_PROCESSED_INPUT = 1;
56
+ export declare const ENABLE_LINE_INPUT = 2;
57
+ export declare const ENABLE_ECHO_INPUT = 4;
58
+ export declare const ENABLE_WINDOW_INPUT = 8;
59
+ export declare const ENABLE_VIRTUAL_TERMINAL_INPUT = 512;
60
+ /**
61
+ * The three bits a cooked console has and a raw one does not.
62
+ *
63
+ * This is the fingerprint of "libuv has already put this console in raw mode",
64
+ * and it is what tells the two measured samples apart: `0x0008` (the CLI's own
65
+ * console, raw) versus `0x01F7` (a fresh `CREATE_NO_WINDOW` console, cooked).
66
+ */
67
+ export declare const COOKED_INPUT_BITS: number;
68
+ export declare function isRawConsoleMode(mode: number): boolean;
69
+ export type VtForceFailure = 'not_windows' | 'spawn_failed' | 'helper_failed' | 'no_before' | 'wrong_console' | 'bit_not_set';
70
+ export interface VtForceResult {
71
+ ok: boolean;
72
+ /** Absent on success; a stable machine token on failure, for the log. */
73
+ reason?: VtForceFailure;
74
+ before?: number;
75
+ after?: number;
76
+ /** Free-form context for the log line. Never shown to the user. */
77
+ detail?: string;
78
+ }
79
+ /**
80
+ * The PowerShell side, as a string.
81
+ *
82
+ * A LITERAL HERE-STRING (`@'` ... `'@`) around the C# so PowerShell expands
83
+ * nothing inside it, and `'CONIN$'` in single quotes for the same reason - the
84
+ * `$` is part of the device name, not a variable.
85
+ *
86
+ * Shipped inline rather than as a `.ps1` next to the build output, and that is a
87
+ * capability difference rather than tidiness: ExecutionPolicy blocks script
88
+ * FILES and does not apply to `-EncodedCommand`, so a locked-down machine - the
89
+ * population this bug keeps landing on - can still run it. It also removes the
90
+ * "where did my file go after packaging" class of failure entirely.
91
+ */
92
+ export declare function buildHelperScript(): string;
93
+ /** `-EncodedCommand` takes base64 of UTF-16LE, which needs no shell quoting. */
94
+ export declare function encodePowerShellCommand(script: string): string;
95
+ /**
96
+ * Prefer the absolute path so a sanitized or hijacked `PATH` cannot decide what
97
+ * runs; fall back to the bare name, which is still better than not trying.
98
+ */
99
+ export declare function resolvePowerShellExe(env?: NodeJS.ProcessEnv): string;
100
+ /** Pull `before=` / `after=` back out of the helper's stdout. */
101
+ export declare function parseConsoleModes(stdout: string): {
102
+ before?: number;
103
+ after?: number;
104
+ };
105
+ /**
106
+ * PURE, and the reason this feature is testable without a Windows box: every
107
+ * interesting outcome - including the `0x01F7` wrong-console trap that returns
108
+ * exit code 0 - is decided here from two strings and a number.
109
+ */
110
+ export declare function decideVtForceOutcome(run: {
111
+ status: number | null;
112
+ stdout: string;
113
+ stderr?: string;
114
+ }): VtForceResult;
115
+ export interface VtForceOptions {
116
+ platform?: string;
117
+ timeoutMs?: number;
118
+ /** Injected by the tests; defaults to a real `spawnSync`. */
119
+ run?: (exe: string, args: string[]) => {
120
+ status: number | null;
121
+ stdout: string;
122
+ stderr: string;
123
+ };
124
+ }
125
+ /**
126
+ * Set the bit on the console this process is attached to.
127
+ *
128
+ * SYNCHRONOUS on purpose. The caller has to know the answer before it decides
129
+ * whether the mouse can report and whether to warn the user, and at the one
130
+ * moment this runs - after raw mode is up, before Ink renders - there is nothing
131
+ * else for the event loop to do. An async version would buy nothing and would
132
+ * spread the ordering across two files.
133
+ *
134
+ * NEVER THROWS.
135
+ */
136
+ export declare function forceWindowsVtInput(opts?: VtForceOptions): VtForceResult;