@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.
- package/CHANGELOG.md +216 -0
- package/README.md +241 -18
- package/dist/agent/agent-mode.d.ts +39 -0
- package/dist/agent/agent-mode.js +36 -0
- package/dist/agent/controller.d.ts +74 -1
- package/dist/agent/controller.js +200 -1
- package/dist/agent/headless.d.ts +8 -0
- package/dist/agent/headless.js +47 -0
- package/dist/agent/reducer.d.ts +120 -0
- package/dist/agent/reducer.js +201 -2
- package/dist/cli.js +297 -6
- package/dist/commands/builtins.js +73 -23
- package/dist/commands/registry.d.ts +27 -0
- package/dist/compaction/archive.d.ts +175 -0
- package/dist/compaction/archive.js +267 -0
- package/dist/compaction/child.d.ts +97 -0
- package/dist/compaction/child.js +113 -0
- package/dist/compaction/command.d.ts +41 -0
- package/dist/compaction/command.js +342 -0
- package/dist/compaction/compactor.d.ts +268 -0
- package/dist/compaction/compactor.js +689 -0
- package/dist/compaction/digest.d.ts +62 -0
- package/dist/compaction/digest.js +164 -0
- package/dist/compaction/limits.d.ts +165 -0
- package/dist/compaction/limits.js +172 -0
- package/dist/compaction/pressure.d.ts +135 -0
- package/dist/compaction/pressure.js +167 -0
- package/dist/compaction/summarize-call.d.ts +70 -0
- package/dist/compaction/summarize-call.js +86 -0
- package/dist/compaction/summary-prompt.d.ts +85 -0
- package/dist/compaction/summary-prompt.js +197 -0
- package/dist/compaction/tail-budget.d.ts +52 -0
- package/dist/compaction/tail-budget.js +67 -0
- package/dist/compaction/types.d.ts +179 -0
- package/dist/compaction/types.js +22 -0
- package/dist/compaction/wiring.d.ts +255 -0
- package/dist/compaction/wiring.js +556 -0
- package/dist/config/app-paths.d.ts +14 -0
- package/dist/config/app-paths.js +16 -0
- package/dist/config/cli-commands.d.ts +41 -0
- package/dist/config/cli-commands.js +77 -1
- package/dist/config/env.d.ts +4 -1
- package/dist/config/env.js +92 -2
- package/dist/config/load.d.ts +31 -0
- package/dist/config/load.js +63 -1
- package/dist/config/schema.d.ts +159 -0
- package/dist/config/schema.js +137 -0
- package/dist/config/store.js +17 -1
- package/dist/config/ui-state.d.ts +44 -6
- package/dist/config/ui-state.js +21 -12
- package/dist/diagnostics/doctor.js +41 -1
- package/dist/exec/events.d.ts +50 -1
- package/dist/exec/index.js +8 -4
- package/dist/exec/runner.js +44 -2
- package/dist/input/mouse-events.d.ts +31 -5
- package/dist/input/mouse-events.js +86 -27
- package/dist/input/stdin-mouse-filter.d.ts +8 -3
- package/dist/logging/install.d.ts +51 -1
- package/dist/logging/install.js +28 -0
- package/dist/logging/logger.d.ts +1 -1
- package/dist/session/cli-commands.js +23 -1
- package/dist/session/persist.js +19 -0
- package/dist/session/store.d.ts +18 -0
- package/dist/session/store.js +59 -2
- package/dist/team/report.js +7 -1
- package/dist/team/runtime.d.ts +10 -0
- package/dist/team/runtime.js +7 -0
- package/dist/team/subagent.d.ts +31 -1
- package/dist/team/subagent.js +42 -15
- package/dist/team/types.d.ts +8 -0
- package/dist/ui/ActivityLine.d.ts +19 -1
- package/dist/ui/ActivityLine.js +10 -2
- package/dist/ui/App.d.ts +48 -1
- package/dist/ui/App.js +317 -17
- package/dist/ui/Composer.d.ts +26 -1
- package/dist/ui/Composer.js +6 -6
- package/dist/ui/PromptInput.d.ts +34 -1
- package/dist/ui/PromptInput.js +54 -3
- package/dist/ui/StatusBar.d.ts +40 -0
- package/dist/ui/StatusBar.js +13 -4
- package/dist/ui/TeamPanel.js +7 -1
- package/dist/ui/Transcript.d.ts +21 -1
- package/dist/ui/Transcript.js +32 -2
- package/dist/ui/clipboard.d.ts +52 -0
- package/dist/ui/clipboard.js +91 -0
- package/dist/ui/entries/CompactionCard.d.ts +75 -0
- package/dist/ui/entries/CompactionCard.js +158 -0
- package/dist/ui/frame-differ.d.ts +46 -0
- package/dist/ui/frame-differ.js +94 -10
- package/dist/ui/gauge.d.ts +16 -1
- package/dist/ui/gauge.js +25 -6
- package/dist/ui/glyphs.d.ts +11 -0
- package/dist/ui/glyphs.js +2 -0
- package/dist/ui/layout/ScrollViewport.d.ts +43 -6
- package/dist/ui/layout/ScrollViewport.js +128 -21
- package/dist/ui/layout/budget.d.ts +10 -3
- package/dist/ui/layout/budget.js +10 -3
- package/dist/ui/layout/follow-state.d.ts +137 -0
- package/dist/ui/layout/follow-state.js +150 -0
- package/dist/ui/layout/virtual-window.d.ts +11 -0
- package/dist/ui/layout/virtual-window.js +34 -0
- package/dist/ui/overlays/HelpOverlay.js +18 -2
- package/dist/ui/overlays/SettingsScreen.d.ts +47 -1
- package/dist/ui/overlays/SettingsScreen.js +60 -1
- package/dist/ui/palettes.d.ts +16 -0
- package/dist/ui/palettes.js +6 -0
- package/dist/ui/screen.d.ts +22 -0
- package/dist/ui/screen.js +70 -8
- package/dist/ui/selection/highlight.d.ts +39 -0
- package/dist/ui/selection/highlight.js +64 -0
- package/dist/ui/selection/screen-mirror.d.ts +24 -0
- package/dist/ui/selection/screen-mirror.js +35 -0
- package/dist/ui/selection/selection-controller.d.ts +88 -0
- package/dist/ui/selection/selection-controller.js +282 -0
- package/dist/ui/selection/selection.d.ts +90 -0
- package/dist/ui/selection/selection.js +223 -0
- package/dist/ui/stdout-frame-writer.d.ts +33 -0
- package/dist/ui/stdout-frame-writer.js +15 -0
- package/dist/ui/theme.d.ts +11 -0
- package/dist/ui/theme.js +2 -0
- package/dist/ui/transcript-text.js +23 -0
- package/dist/ui/use-startup-notices.d.ts +62 -5
- package/dist/ui/use-startup-notices.js +81 -17
- package/dist/ui/use-wheel-routing.js +8 -0
- package/dist/ui/win-vt-force.d.ts +136 -0
- package/dist/ui/win-vt-force.js +237 -0
- 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
|
|
18
|
-
* input — see `ui/win-vt-input.ts` for why that is a
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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 {
|
|
17
|
+
import { getMouseNoticeVersion, getVtInputNoticeVersion } from '../config/ui-state.js';
|
|
17
18
|
import { getLogger } from '../logging/logger.js';
|
|
18
19
|
/**
|
|
19
|
-
* The
|
|
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
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
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
|
|
27
|
-
|
|
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
|
|
45
|
-
* input — see `ui/win-vt-input.ts` for why that is a
|
|
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
|
-
|
|
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}
|
|
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
|
-
|
|
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:
|
|
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 (
|
|
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;
|