@aragon-agent/cli 0.5.12 → 0.6.2
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 +100 -0
- package/README.md +322 -6
- package/dist/agent/agent-mode.d.ts +39 -0
- package/dist/agent/agent-mode.js +36 -0
- package/dist/agent/controller.d.ts +32 -0
- package/dist/agent/controller.js +25 -0
- package/dist/agent/system-prompt.d.ts +16 -0
- package/dist/agent/system-prompt.js +5 -0
- package/dist/cli.js +143 -2
- package/dist/config/ui-state.d.ts +29 -0
- package/dist/config/ui-state.js +14 -4
- package/dist/diagnostics/cli-commands.d.ts +10 -0
- package/dist/diagnostics/cli-commands.js +61 -0
- package/dist/diagnostics/doctor.d.ts +29 -0
- package/dist/diagnostics/doctor.js +207 -0
- package/dist/diagnostics/info.d.ts +30 -0
- package/dist/diagnostics/info.js +99 -0
- package/dist/exec/cli-commands.d.ts +31 -0
- package/dist/exec/cli-commands.js +63 -0
- package/dist/exec/emitter.d.ts +80 -0
- package/dist/exec/emitter.js +127 -0
- package/dist/exec/events.d.ts +226 -0
- package/dist/exec/events.js +43 -0
- package/dist/exec/index.d.ts +56 -0
- package/dist/exec/index.js +439 -0
- package/dist/exec/options.d.ts +76 -0
- package/dist/exec/options.js +221 -0
- package/dist/exec/permission.d.ts +66 -0
- package/dist/exec/permission.js +96 -0
- package/dist/exec/runner.d.ts +204 -0
- package/dist/exec/runner.js +559 -0
- package/dist/exec/stdin-stream.d.ts +46 -0
- package/dist/exec/stdin-stream.js +144 -0
- package/dist/logging/install.d.ts +22 -0
- package/dist/logging/install.js +22 -0
- package/dist/session/cli-commands.d.ts +30 -0
- package/dist/session/cli-commands.js +184 -0
- package/dist/session/persist.d.ts +46 -0
- package/dist/session/persist.js +8 -0
- package/dist/session/store.d.ts +147 -0
- package/dist/session/store.js +442 -0
- package/dist/team/runtime.d.ts +12 -0
- package/dist/team/runtime.js +6 -0
- package/dist/team/subagent.d.ts +12 -0
- package/dist/team/subagent.js +4 -0
- package/dist/tools/index.d.ts +20 -0
- package/dist/tools/index.js +26 -0
- package/dist/ui/ActivityLine.d.ts +16 -0
- package/dist/ui/ActivityLine.js +23 -1
- package/dist/ui/App.d.ts +14 -1
- package/dist/ui/App.js +61 -7
- package/dist/ui/BottomStatusRow.d.ts +33 -1
- package/dist/ui/BottomStatusRow.js +20 -6
- package/dist/ui/Composer.d.ts +14 -1
- package/dist/ui/Composer.js +5 -5
- package/dist/ui/overlays/HelpOverlay.js +12 -1
- package/dist/ui/use-startup-notices.d.ts +98 -4
- package/dist/ui/use-startup-notices.js +108 -2
- package/dist/ui/win-vt-force.d.ts +136 -0
- package/dist/ui/win-vt-force.js +237 -0
- package/dist/ui/win-vt-input.d.ts +57 -0
- package/dist/ui/win-vt-input.js +62 -0
- package/package.json +7 -3
|
@@ -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;
|
|
@@ -0,0 +1,237 @@
|
|
|
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
|
+
import { spawnSync } from 'node:child_process';
|
|
55
|
+
import { existsSync } from 'node:fs';
|
|
56
|
+
import { join } from 'node:path';
|
|
57
|
+
import process from 'node:process';
|
|
58
|
+
/** Console input mode bits (`consoleapi.h`). */
|
|
59
|
+
export const ENABLE_PROCESSED_INPUT = 0x0001;
|
|
60
|
+
export const ENABLE_LINE_INPUT = 0x0002;
|
|
61
|
+
export const ENABLE_ECHO_INPUT = 0x0004;
|
|
62
|
+
export const ENABLE_WINDOW_INPUT = 0x0008;
|
|
63
|
+
export const ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200;
|
|
64
|
+
/**
|
|
65
|
+
* The three bits a cooked console has and a raw one does not.
|
|
66
|
+
*
|
|
67
|
+
* This is the fingerprint of "libuv has already put this console in raw mode",
|
|
68
|
+
* and it is what tells the two measured samples apart: `0x0008` (the CLI's own
|
|
69
|
+
* console, raw) versus `0x01F7` (a fresh `CREATE_NO_WINDOW` console, cooked).
|
|
70
|
+
*/
|
|
71
|
+
export const COOKED_INPUT_BITS = ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT;
|
|
72
|
+
export function isRawConsoleMode(mode) {
|
|
73
|
+
return (mode & COOKED_INPUT_BITS) === 0;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The PowerShell side, as a string.
|
|
77
|
+
*
|
|
78
|
+
* A LITERAL HERE-STRING (`@'` ... `'@`) around the C# so PowerShell expands
|
|
79
|
+
* nothing inside it, and `'CONIN$'` in single quotes for the same reason - the
|
|
80
|
+
* `$` is part of the device name, not a variable.
|
|
81
|
+
*
|
|
82
|
+
* Shipped inline rather than as a `.ps1` next to the build output, and that is a
|
|
83
|
+
* capability difference rather than tidiness: ExecutionPolicy blocks script
|
|
84
|
+
* FILES and does not apply to `-EncodedCommand`, so a locked-down machine - the
|
|
85
|
+
* population this bug keeps landing on - can still run it. It also removes the
|
|
86
|
+
* "where did my file go after packaging" class of failure entirely.
|
|
87
|
+
*/
|
|
88
|
+
export function buildHelperScript() {
|
|
89
|
+
return [
|
|
90
|
+
"$ErrorActionPreference = 'Stop'",
|
|
91
|
+
'try {',
|
|
92
|
+
' Add-Type -TypeDefinition @' + "'",
|
|
93
|
+
'using System;',
|
|
94
|
+
'using System.Runtime.InteropServices;',
|
|
95
|
+
'public static class AragonVtInput {',
|
|
96
|
+
' [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]',
|
|
97
|
+
' public static extern IntPtr CreateFileW(string name, uint access, uint share,',
|
|
98
|
+
' IntPtr sec, uint disp, uint flags, IntPtr tmpl);',
|
|
99
|
+
' [DllImport("kernel32.dll", SetLastError=true)]',
|
|
100
|
+
' public static extern bool GetConsoleMode(IntPtr h, out uint mode);',
|
|
101
|
+
' [DllImport("kernel32.dll", SetLastError=true)]',
|
|
102
|
+
' public static extern bool SetConsoleMode(IntPtr h, uint mode);',
|
|
103
|
+
'}',
|
|
104
|
+
"'" + '@',
|
|
105
|
+
'} catch { Write-Output "error=addtype"; exit 3 }',
|
|
106
|
+
// GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING.
|
|
107
|
+
//
|
|
108
|
+
// DECIMAL, NOT `0xC0000000`. Windows PowerShell 5.1 - which is what
|
|
109
|
+
// `resolvePowerShellExe` finds, on every machine - parses a hex literal with
|
|
110
|
+
// the high bit set as a SIGNED Int32, so `[uint32]0xC0000000` throws
|
|
111
|
+
// "Cannot convert value -1073741824" before `CreateFileW` is ever reached.
|
|
112
|
+
// With `$ErrorActionPreference = 'Stop'` that is a terminating error outside
|
|
113
|
+
// the `try`, so the helper dies with no `before=` line and the whole feature
|
|
114
|
+
// degrades to `no_before` on 100% of consoles - working code, never once
|
|
115
|
+
// executed. Round 2's own probe helper carries the same comment.
|
|
116
|
+
"$h = [AragonVtInput]::CreateFileW('CONIN$', ([uint32]2147483648 -bor [uint32]1073741824)," +
|
|
117
|
+
' [uint32]3, [IntPtr]::Zero, [uint32]3, [uint32]0, [IntPtr]::Zero)',
|
|
118
|
+
'if ($h -eq [IntPtr]::Zero -or $h.ToInt64() -eq -1) { Write-Output "error=conin"; exit 4 }',
|
|
119
|
+
'$before = [uint32]0',
|
|
120
|
+
'if (-not [AragonVtInput]::GetConsoleMode($h, [ref]$before)) {',
|
|
121
|
+
' Write-Output "error=getmode"; exit 5',
|
|
122
|
+
'}',
|
|
123
|
+
'Write-Output ("before=0x{0:X4}" -f $before)',
|
|
124
|
+
'if (-not [AragonVtInput]::SetConsoleMode($h, ($before -bor 0x0200))) {',
|
|
125
|
+
' Write-Output "error=setmode"; exit 6',
|
|
126
|
+
'}',
|
|
127
|
+
'$after = [uint32]0',
|
|
128
|
+
'if (-not [AragonVtInput]::GetConsoleMode($h, [ref]$after)) {',
|
|
129
|
+
' Write-Output "error=recheck"; exit 7',
|
|
130
|
+
'}',
|
|
131
|
+
'Write-Output ("after=0x{0:X4}" -f $after)',
|
|
132
|
+
'exit 0',
|
|
133
|
+
].join('\n');
|
|
134
|
+
}
|
|
135
|
+
/** `-EncodedCommand` takes base64 of UTF-16LE, which needs no shell quoting. */
|
|
136
|
+
export function encodePowerShellCommand(script) {
|
|
137
|
+
return Buffer.from(script, 'utf16le').toString('base64');
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Prefer the absolute path so a sanitized or hijacked `PATH` cannot decide what
|
|
141
|
+
* runs; fall back to the bare name, which is still better than not trying.
|
|
142
|
+
*/
|
|
143
|
+
export function resolvePowerShellExe(env = process.env) {
|
|
144
|
+
const root = env.SystemRoot ?? env.windir;
|
|
145
|
+
if (root) {
|
|
146
|
+
const abs = join(root, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
147
|
+
if (existsSync(abs))
|
|
148
|
+
return abs;
|
|
149
|
+
}
|
|
150
|
+
return 'powershell.exe';
|
|
151
|
+
}
|
|
152
|
+
/** Pull `before=` / `after=` back out of the helper's stdout. */
|
|
153
|
+
export function parseConsoleModes(stdout) {
|
|
154
|
+
const read = (label) => {
|
|
155
|
+
const m = new RegExp(`^${label}=0x([0-9a-fA-F]+)\\s*$`, 'm').exec(stdout);
|
|
156
|
+
if (!m)
|
|
157
|
+
return undefined;
|
|
158
|
+
const n = Number.parseInt(m[1] ?? '', 16);
|
|
159
|
+
return Number.isFinite(n) ? n : undefined;
|
|
160
|
+
};
|
|
161
|
+
const before = read('before');
|
|
162
|
+
const after = read('after');
|
|
163
|
+
return { ...(before !== undefined ? { before } : {}), ...(after !== undefined ? { after } : {}) };
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* PURE, and the reason this feature is testable without a Windows box: every
|
|
167
|
+
* interesting outcome - including the `0x01F7` wrong-console trap that returns
|
|
168
|
+
* exit code 0 - is decided here from two strings and a number.
|
|
169
|
+
*/
|
|
170
|
+
export function decideVtForceOutcome(run) {
|
|
171
|
+
const { before, after } = parseConsoleModes(run.stdout);
|
|
172
|
+
const carry = before !== undefined ? { before } : {};
|
|
173
|
+
if (run.status !== 0) {
|
|
174
|
+
const note = (run.stdout.trim() || run.stderr?.trim() || '').split('\n')[0] ?? '';
|
|
175
|
+
return { ok: false, reason: 'helper_failed', detail: `exit=${run.status} ${note}`, ...carry };
|
|
176
|
+
}
|
|
177
|
+
if (before === undefined)
|
|
178
|
+
return { ok: false, reason: 'no_before' };
|
|
179
|
+
// Trap 1. Exit code and API return value both said success; only this tells us
|
|
180
|
+
// the helper was talking to a different console than the one being typed into.
|
|
181
|
+
if (!isRawConsoleMode(before))
|
|
182
|
+
return { ok: false, reason: 'wrong_console', before };
|
|
183
|
+
if (after === undefined || (after & ENABLE_VIRTUAL_TERMINAL_INPUT) === 0) {
|
|
184
|
+
// Old conhosts accept `SetConsoleMode` and drop bits they do not implement,
|
|
185
|
+
// so the re-read is not paranoia - it is the only proof (analysis 3.9).
|
|
186
|
+
return { ok: false, reason: 'bit_not_set', before, ...(after !== undefined ? { after } : {}) };
|
|
187
|
+
}
|
|
188
|
+
return { ok: true, before, after };
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Set the bit on the console this process is attached to.
|
|
192
|
+
*
|
|
193
|
+
* SYNCHRONOUS on purpose. The caller has to know the answer before it decides
|
|
194
|
+
* whether the mouse can report and whether to warn the user, and at the one
|
|
195
|
+
* moment this runs - after raw mode is up, before Ink renders - there is nothing
|
|
196
|
+
* else for the event loop to do. An async version would buy nothing and would
|
|
197
|
+
* spread the ordering across two files.
|
|
198
|
+
*
|
|
199
|
+
* NEVER THROWS.
|
|
200
|
+
*/
|
|
201
|
+
export function forceWindowsVtInput(opts = {}) {
|
|
202
|
+
const platform = opts.platform ?? process.platform;
|
|
203
|
+
if (platform !== 'win32')
|
|
204
|
+
return { ok: false, reason: 'not_windows' };
|
|
205
|
+
const exe = resolvePowerShellExe();
|
|
206
|
+
const args = [
|
|
207
|
+
'-NoProfile',
|
|
208
|
+
'-NonInteractive',
|
|
209
|
+
'-NoLogo',
|
|
210
|
+
'-EncodedCommand',
|
|
211
|
+
encodePowerShellCommand(buildHelperScript()),
|
|
212
|
+
];
|
|
213
|
+
try {
|
|
214
|
+
const run = opts.run ?? defaultRun(opts.timeoutMs ?? 5000);
|
|
215
|
+
return decideVtForceOutcome(run(exe, args));
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
return { ok: false, reason: 'spawn_failed', detail: err.message };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function defaultRun(timeoutMs) {
|
|
222
|
+
return (exe, args) => {
|
|
223
|
+
const r = spawnSync(exe, args, {
|
|
224
|
+
// stdin `ignore` is trap 2's premise: the helper must not share the handle
|
|
225
|
+
// whose mode is being rewritten. stdout/stderr are pipes because `before`
|
|
226
|
+
// has to come back - a log file would be a second failure surface.
|
|
227
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
228
|
+
// TRAP 1. Do not "tidy" this to `true`; see the module header.
|
|
229
|
+
windowsHide: false,
|
|
230
|
+
timeout: timeoutMs,
|
|
231
|
+
encoding: 'utf-8',
|
|
232
|
+
});
|
|
233
|
+
if (r.error)
|
|
234
|
+
throw r.error;
|
|
235
|
+
return { status: r.status, stdout: r.stdout ?? '', stderr: r.stderr ?? '' };
|
|
236
|
+
};
|
|
237
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Will this console ever deliver `CSI Z` and SGR mouse reports?
|
|
3
|
+
* (shift-tab-and-mouse-wheel-dead-on-some-terminals, F1')
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY - this tree is inside the glyph scanner's scope.
|
|
6
|
+
*
|
|
7
|
+
* On Windows the answer is decided by NODE, not by the terminal emulator and not
|
|
8
|
+
* by anything this package writes. `Shift+Tab` and SGR mouse reporting both
|
|
9
|
+
* exist only while the console input handle has `ENABLE_VIRTUAL_TERMINAL_INPUT`
|
|
10
|
+
* set, and Node only sets it from v22.17.0 / v24.2.0 onward, where
|
|
11
|
+
* `SetRawMode` switched from `UV_TTY_MODE_RAW` to `UV_TTY_MODE_RAW_VT`
|
|
12
|
+
* (`nodejs/node` `src/tty_wrap.cc`). Before that libuv translates console input
|
|
13
|
+
* records into ANSI itself, and that translation
|
|
14
|
+
*
|
|
15
|
+
* - has no `VK_TAB` entry, and takes the character branch for it because
|
|
16
|
+
* `uChar.UnicodeChar` is 0x09, where SHIFT_PRESSED is never read. So
|
|
17
|
+
* `Shift+Tab` arrives as a PLAIN TAB, byte-identical to `Tab`;
|
|
18
|
+
* - `continue`s past every record that is not a `KEY_EVENT`, so a mouse
|
|
19
|
+
* report cannot reach the process at all.
|
|
20
|
+
*
|
|
21
|
+
* Measured rather than reasoned: injecting the same keystrokes into the same
|
|
22
|
+
* console, Node 20.19.0 yields `09` for `Shift+Tab` and Node 22.18.0 yields
|
|
23
|
+
* `1b 5b 5a`; console mode samples `0x0008` and `0x0208` respectively. The full
|
|
24
|
+
* probe data is in `docs/diagnoses/shift-tab-and-mouse-wheel-dead-on-some-terminals/`.
|
|
25
|
+
*
|
|
26
|
+
* PURE: the platform and the version are arguments, so the whole table is
|
|
27
|
+
* testable without a Windows box and without a second Node install.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* The versions whose `setRawMode(true)` reaches `UV_TTY_MODE_RAW_VT`.
|
|
31
|
+
*
|
|
32
|
+
* `<23` IS LOAD-BEARING: 23.x branched before the change landed and never got
|
|
33
|
+
* it, and 24.0/24.1 did not have it either - the fix reached the 24 line only in
|
|
34
|
+
* 24.2.0. A range of `>=22.17.0` alone would call both of those supported.
|
|
35
|
+
*
|
|
36
|
+
* A TYPO HERE DISABLES THE WHOLE GATE SILENTLY, because `satisfiesNodeRange`
|
|
37
|
+
* fails open: a clause it cannot parse makes the range unknown and the answer
|
|
38
|
+
* `true`. `win-vt-input.test.ts` pins concrete version strings against concrete
|
|
39
|
+
* booleans for exactly that reason - it is the only guard this constant has.
|
|
40
|
+
*/
|
|
41
|
+
export declare const WINDOWS_VT_INPUT_NODE_RANGE = ">=22.17.0 <23 || >=24.2.0";
|
|
42
|
+
/**
|
|
43
|
+
* `false` only for the combination that is known to swallow both inputs:
|
|
44
|
+
* `win32` running a Node outside `WINDOWS_VT_INPUT_NODE_RANGE`.
|
|
45
|
+
*
|
|
46
|
+
* FAILS OPEN, deliberately, in both directions (inherited from
|
|
47
|
+
* `satisfiesNodeRange`, whose own reasoning is written down at its definition):
|
|
48
|
+
* a version string that cannot be parsed is assumed supported. Guessing wrong
|
|
49
|
+
* that way costs nothing beyond today's behaviour; guessing wrong the other way
|
|
50
|
+
* would turn the mouse off on a machine where it works, which is a regression
|
|
51
|
+
* this bug does not justify risking.
|
|
52
|
+
*
|
|
53
|
+
* Every non-Windows platform is `true` unconditionally. macOS and Linux go
|
|
54
|
+
* through a real pty, where the terminal - not Node - encodes the keys, so no
|
|
55
|
+
* version of Node has ever been able to affect this.
|
|
56
|
+
*/
|
|
57
|
+
export declare function supportsWindowsVtInput(platform: string, nodeVersion: string): boolean;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Will this console ever deliver `CSI Z` and SGR mouse reports?
|
|
3
|
+
* (shift-tab-and-mouse-wheel-dead-on-some-terminals, F1')
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY - this tree is inside the glyph scanner's scope.
|
|
6
|
+
*
|
|
7
|
+
* On Windows the answer is decided by NODE, not by the terminal emulator and not
|
|
8
|
+
* by anything this package writes. `Shift+Tab` and SGR mouse reporting both
|
|
9
|
+
* exist only while the console input handle has `ENABLE_VIRTUAL_TERMINAL_INPUT`
|
|
10
|
+
* set, and Node only sets it from v22.17.0 / v24.2.0 onward, where
|
|
11
|
+
* `SetRawMode` switched from `UV_TTY_MODE_RAW` to `UV_TTY_MODE_RAW_VT`
|
|
12
|
+
* (`nodejs/node` `src/tty_wrap.cc`). Before that libuv translates console input
|
|
13
|
+
* records into ANSI itself, and that translation
|
|
14
|
+
*
|
|
15
|
+
* - has no `VK_TAB` entry, and takes the character branch for it because
|
|
16
|
+
* `uChar.UnicodeChar` is 0x09, where SHIFT_PRESSED is never read. So
|
|
17
|
+
* `Shift+Tab` arrives as a PLAIN TAB, byte-identical to `Tab`;
|
|
18
|
+
* - `continue`s past every record that is not a `KEY_EVENT`, so a mouse
|
|
19
|
+
* report cannot reach the process at all.
|
|
20
|
+
*
|
|
21
|
+
* Measured rather than reasoned: injecting the same keystrokes into the same
|
|
22
|
+
* console, Node 20.19.0 yields `09` for `Shift+Tab` and Node 22.18.0 yields
|
|
23
|
+
* `1b 5b 5a`; console mode samples `0x0008` and `0x0208` respectively. The full
|
|
24
|
+
* probe data is in `docs/diagnoses/shift-tab-and-mouse-wheel-dead-on-some-terminals/`.
|
|
25
|
+
*
|
|
26
|
+
* PURE: the platform and the version are arguments, so the whole table is
|
|
27
|
+
* testable without a Windows box and without a second Node install.
|
|
28
|
+
*/
|
|
29
|
+
import { satisfiesNodeRange } from '../update/semver.js';
|
|
30
|
+
/**
|
|
31
|
+
* The versions whose `setRawMode(true)` reaches `UV_TTY_MODE_RAW_VT`.
|
|
32
|
+
*
|
|
33
|
+
* `<23` IS LOAD-BEARING: 23.x branched before the change landed and never got
|
|
34
|
+
* it, and 24.0/24.1 did not have it either - the fix reached the 24 line only in
|
|
35
|
+
* 24.2.0. A range of `>=22.17.0` alone would call both of those supported.
|
|
36
|
+
*
|
|
37
|
+
* A TYPO HERE DISABLES THE WHOLE GATE SILENTLY, because `satisfiesNodeRange`
|
|
38
|
+
* fails open: a clause it cannot parse makes the range unknown and the answer
|
|
39
|
+
* `true`. `win-vt-input.test.ts` pins concrete version strings against concrete
|
|
40
|
+
* booleans for exactly that reason - it is the only guard this constant has.
|
|
41
|
+
*/
|
|
42
|
+
export const WINDOWS_VT_INPUT_NODE_RANGE = '>=22.17.0 <23 || >=24.2.0';
|
|
43
|
+
/**
|
|
44
|
+
* `false` only for the combination that is known to swallow both inputs:
|
|
45
|
+
* `win32` running a Node outside `WINDOWS_VT_INPUT_NODE_RANGE`.
|
|
46
|
+
*
|
|
47
|
+
* FAILS OPEN, deliberately, in both directions (inherited from
|
|
48
|
+
* `satisfiesNodeRange`, whose own reasoning is written down at its definition):
|
|
49
|
+
* a version string that cannot be parsed is assumed supported. Guessing wrong
|
|
50
|
+
* that way costs nothing beyond today's behaviour; guessing wrong the other way
|
|
51
|
+
* would turn the mouse off on a machine where it works, which is a regression
|
|
52
|
+
* this bug does not justify risking.
|
|
53
|
+
*
|
|
54
|
+
* Every non-Windows platform is `true` unconditionally. macOS and Linux go
|
|
55
|
+
* through a real pty, where the terminal - not Node - encodes the keys, so no
|
|
56
|
+
* version of Node has ever been able to affect this.
|
|
57
|
+
*/
|
|
58
|
+
export function supportsWindowsVtInput(platform, nodeVersion) {
|
|
59
|
+
if (platform !== 'win32')
|
|
60
|
+
return true;
|
|
61
|
+
return satisfiesNodeRange(WINDOWS_VT_INPUT_NODE_RANGE, nodeVersion);
|
|
62
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aragon-agent/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "AragonAgent CLI — a Claude-Code / Codex-style interactive terminal UI (TUI) on top of the @aragon-agent/core engine. Installable and runnable via npm / npx.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "AragonAgent contributors",
|
|
@@ -15,7 +15,11 @@
|
|
|
15
15
|
"anthropic",
|
|
16
16
|
"openai",
|
|
17
17
|
"google",
|
|
18
|
-
"aragonagent"
|
|
18
|
+
"aragonagent",
|
|
19
|
+
"exec",
|
|
20
|
+
"headless",
|
|
21
|
+
"automation",
|
|
22
|
+
"ci"
|
|
19
23
|
],
|
|
20
24
|
"repository": {
|
|
21
25
|
"type": "git",
|
|
@@ -52,7 +56,7 @@
|
|
|
52
56
|
"prepublishOnly": "npm run build"
|
|
53
57
|
},
|
|
54
58
|
"dependencies": {
|
|
55
|
-
"@aragon-agent/core": "^0.2.
|
|
59
|
+
"@aragon-agent/core": "^0.2.14",
|
|
56
60
|
"chalk": "^5.6.2",
|
|
57
61
|
"cli-highlight": "^2.0.0",
|
|
58
62
|
"commander": "^12.1.0",
|