@aragon-agent/cli 0.6.26 → 0.6.27
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 +15 -0
- package/README.md +31 -2
- package/dist/proc/terminal-plan.d.ts +20 -2
- package/dist/proc/terminal-plan.js +37 -4
- package/dist/session/branch-invocation.d.ts +53 -9
- package/dist/session/branch-invocation.js +52 -8
- package/dist/session/branch-launch.d.ts +9 -0
- package/dist/session/branch-launch.js +30 -13
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -77,6 +77,21 @@ were written.
|
|
|
77
77
|
|
|
78
78
|
### Fixed
|
|
79
79
|
|
|
80
|
+
- **`Ctrl+L` opened a window that failed with a PowerShell `ParserError` on
|
|
81
|
+
Windows.** The script handed to `-EncodedCommand` was the argv joined with
|
|
82
|
+
quotes but with no call operator, and PowerShell reads a quoted string in command
|
|
83
|
+
position as an *expression*: with node installed under `C:\Program Files\...`
|
|
84
|
+
the child was never created - `UnexpectedToken` at column 36 - while the parent
|
|
85
|
+
TUI still reported that the window opened. `powerShellScript` now emits
|
|
86
|
+
`& <argv...>`, which is the only form that runs a word of any shape as a
|
|
87
|
+
command. The failure fallback follows: it is now two labelled lines
|
|
88
|
+
(`PowerShell:` / `cmd.exe :`), because one line cannot be correct for both
|
|
89
|
+
parsers - and the `PowerShell:` line is byte-for-byte the script the window
|
|
90
|
+
receives. A leading `@` can no longer be left bare either, since `@name` is
|
|
91
|
+
PowerShell's splat operator and silently swallows an argument. New
|
|
92
|
+
`powershell-script-parse.test.ts` hands the generated scripts to the real
|
|
93
|
+
Windows PowerShell parser (and pins the old shape to its exact error), so this
|
|
94
|
+
class of defect has a guard instead of a green suite.
|
|
80
95
|
- **A conversation saved mid-tool could be resumed into an illegal history.**
|
|
81
96
|
A `tool_call` with no matching `tool_result` makes the next request fail. The
|
|
82
97
|
checkpoint writer closes every dangling call on the copy it writes and re-checks
|
package/README.md
CHANGED
|
@@ -1838,16 +1838,45 @@ The platform ladder, in order:
|
|
|
1838
1838
|
The shell is `pwsh.exe` then `powershell.exe` on Windows, and `$SHELL` then `bash`
|
|
1839
1839
|
then `/bin/sh` elsewhere. Windows receives the command as `-EncodedCommand` (base64
|
|
1840
1840
|
of UTF-16LE), because that alphabet has no spaces, quotes or `cmd` metacharacters -
|
|
1841
|
-
it survives `cmd` -> `start` -> PowerShell without a quoting argument.
|
|
1841
|
+
it survives `cmd` -> `start` -> PowerShell without a quoting argument. The payload
|
|
1842
|
+
that gets encoded is `& <argv...>`, and **the leading `&` is load-bearing**: in
|
|
1843
|
+
command position PowerShell reads a quoted string as an *expression*, so with a node
|
|
1844
|
+
install under `C:\Program Files\...` the script `'C:\Program Files\nodejs\node.exe'
|
|
1845
|
+
<script>` is a `ParserError ... UnexpectedToken` and no child is ever created - while
|
|
1846
|
+
the parent still reports that the window opened. `&` is PowerShell's call operator:
|
|
1847
|
+
it takes the next word as a command name whatever its shape.
|
|
1842
1848
|
|
|
1843
1849
|
When no terminal can be opened - an SSH session, a container, a machine with no
|
|
1844
|
-
graphical session - the command opens nothing and prints the fallback instead
|
|
1850
|
+
graphical session - the command opens nothing and prints the fallback instead. On
|
|
1851
|
+
Windows that fallback is **two labelled lines**, because one line cannot be correct
|
|
1852
|
+
for both parsers (`& 'x'` is right for PowerShell and wrong for `cmd.exe`, which
|
|
1853
|
+
keeps the single quotes; `"x"` is right for `cmd.exe` and an expression to
|
|
1854
|
+
PowerShell):
|
|
1855
|
+
|
|
1856
|
+
```
|
|
1857
|
+
No terminal window could be opened here. Run this in another terminal:
|
|
1858
|
+
PowerShell: & aragon --resume a4k2m9
|
|
1859
|
+
cmd.exe : aragon --resume a4k2m9
|
|
1860
|
+
```
|
|
1861
|
+
|
|
1862
|
+
Copy the line that matches the terminal you opened. macOS and Linux keep the single
|
|
1863
|
+
line they have always printed:
|
|
1845
1864
|
|
|
1846
1865
|
```
|
|
1847
1866
|
No terminal window could be opened here. Run this in another terminal:
|
|
1848
1867
|
aragon --resume a4k2m9
|
|
1849
1868
|
```
|
|
1850
1869
|
|
|
1870
|
+
A `node <dist/cli.js>` build prints the same two lines with the full paths, quoted:
|
|
1871
|
+
|
|
1872
|
+
```
|
|
1873
|
+
PowerShell: & 'C:\Program Files\nodejs\node.exe' C:\Users\me\AppData\Roaming\npm\node_modules\@aragon-agent\cli\dist\launcher.js --resume a4k2m9
|
|
1874
|
+
cmd.exe : "C:\Program Files\nodejs\node.exe" C:\Users\me\AppData\Roaming\npm\node_modules\@aragon-agent\cli\dist\launcher.js --resume a4k2m9
|
|
1875
|
+
```
|
|
1876
|
+
|
|
1877
|
+
The `PowerShell:` line is byte-for-byte the script the new window receives, so the
|
|
1878
|
+
fallback can never drift from what the feature actually runs.
|
|
1879
|
+
|
|
1851
1880
|
Two environment variables exist because neither belongs in a config file:
|
|
1852
1881
|
|
|
1853
1882
|
| Variable | Effect |
|
|
@@ -65,7 +65,10 @@ export interface TerminalPlanInput {
|
|
|
65
65
|
/** New window title, ASCII, already truncated to the cell budget. */
|
|
66
66
|
title: string;
|
|
67
67
|
cwd: string;
|
|
68
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* `renderManualCommand(command, platform)`'s product: two lines on win32
|
|
70
|
+
* (`PowerShell:` / `cmd.exe :`), one line everywhere else.
|
|
71
|
+
*/
|
|
69
72
|
manual: string;
|
|
70
73
|
}
|
|
71
74
|
/** The env var that overrides the shell the new window runs (D-7). */
|
|
@@ -80,7 +83,22 @@ export declare const ARAGON_BRANCH_SHELL_ENV = "ARAGON_BRANCH_SHELL";
|
|
|
80
83
|
* so there is exactly one place that number lives.
|
|
81
84
|
*/
|
|
82
85
|
export declare function planTerminalLaunch(input: TerminalPlanInput): TerminalPlanResult;
|
|
83
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* The PowerShell statement that runs the aragon command (D-1).
|
|
88
|
+
*
|
|
89
|
+
* THE LEADING `&` IS NOT DECORATION. In command position PowerShell reads a quoted
|
|
90
|
+
* string as an EXPRESSION, so `'C:\Program Files\nodejs\node.exe' <script>` is a
|
|
91
|
+
* parse error - "unexpected token ..." at column 36 - and the child is never
|
|
92
|
+
* created, while the parent still believes the window opened (K-1: `wt`/`start`
|
|
93
|
+
* forward and exit). That is exactly the report this branch exists to fix.
|
|
94
|
+
* `&` is the call operator: it takes the following word as a command name whatever
|
|
95
|
+
* its shape, and it does not change name resolution (`& 'npm.cmd'` still finds
|
|
96
|
+
* `npm` through PATH/PATHEXT, appendix B-8).
|
|
97
|
+
*
|
|
98
|
+
* `posixScript` MUST NOT be given the same treatment: in sh `&` means run in the
|
|
99
|
+
* background, so the window would reach the trailing `exec <shell> -i` immediately
|
|
100
|
+
* and the branch would start behind a prompt that looks idle (I-3 / K-1).
|
|
101
|
+
*/
|
|
84
102
|
export declare function powerShellScript(parts: readonly string[]): string;
|
|
85
103
|
/** Bare words stay bare; everything else becomes a single-quoted literal. */
|
|
86
104
|
export declare function powerShellQuote(value: string): string;
|
|
@@ -50,8 +50,21 @@ const POSIX_CANDIDATES = Object.freeze([
|
|
|
50
50
|
]);
|
|
51
51
|
/** The env var that overrides the shell the new window runs (D-7). */
|
|
52
52
|
export const ARAGON_BRANCH_SHELL_ENV = 'ARAGON_BRANCH_SHELL';
|
|
53
|
-
/**
|
|
54
|
-
|
|
53
|
+
/**
|
|
54
|
+
* A bare word is one PowerShell will not read as anything else.
|
|
55
|
+
*
|
|
56
|
+
* THE FIRST CHARACTER IS THE LOAD-BEARING PART (D-3). `@name` in argument position
|
|
57
|
+
* is the SPLAT operator: `& node.exe -p 'process.argv.length' @foo` hands node an
|
|
58
|
+
* EMPTY argument list, exits 0 and prints nothing (measured on Windows PowerShell
|
|
59
|
+
* 5.1, branch-terminal-powershell-fix appendix B-11). As the only word on the line
|
|
60
|
+
* it is not even parseable - `error col=1 id=SplattingNotPermitted`. Nothing after
|
|
61
|
+
* the first character can do that, which is why the class differs in the first
|
|
62
|
+
* position. `-` stays allowed there so that every flag this feature emits
|
|
63
|
+
* (`--resume`) keeps its bare form, and quoting it would change nothing except the
|
|
64
|
+
* look of the fallback line (appendix B-6 A/B, quoting is transparent to a native
|
|
65
|
+
* exe).
|
|
66
|
+
*/
|
|
67
|
+
const BARE_POWER_SHELL_WORD_RE = /^[A-Za-z0-9._\\/:+=\-][A-Za-z0-9._\\/:+=@\-]*$/;
|
|
55
68
|
/**
|
|
56
69
|
* Build the ladder for this platform, most-preferred rung first.
|
|
57
70
|
*
|
|
@@ -201,9 +214,29 @@ function resolveCandidate(candidate, env) {
|
|
|
201
214
|
function posixWindowScript(cwd, shell, command) {
|
|
202
215
|
return `cd ${posixQuote(cwd)} && ${posixScript(command)} ; exec ${posixQuote(shell.path)} -i`;
|
|
203
216
|
}
|
|
204
|
-
/**
|
|
217
|
+
/**
|
|
218
|
+
* The PowerShell statement that runs the aragon command (D-1).
|
|
219
|
+
*
|
|
220
|
+
* THE LEADING `&` IS NOT DECORATION. In command position PowerShell reads a quoted
|
|
221
|
+
* string as an EXPRESSION, so `'C:\Program Files\nodejs\node.exe' <script>` is a
|
|
222
|
+
* parse error - "unexpected token ..." at column 36 - and the child is never
|
|
223
|
+
* created, while the parent still believes the window opened (K-1: `wt`/`start`
|
|
224
|
+
* forward and exit). That is exactly the report this branch exists to fix.
|
|
225
|
+
* `&` is the call operator: it takes the following word as a command name whatever
|
|
226
|
+
* its shape, and it does not change name resolution (`& 'npm.cmd'` still finds
|
|
227
|
+
* `npm` through PATH/PATHEXT, appendix B-8).
|
|
228
|
+
*
|
|
229
|
+
* `posixScript` MUST NOT be given the same treatment: in sh `&` means run in the
|
|
230
|
+
* background, so the window would reach the trailing `exec <shell> -i` immediately
|
|
231
|
+
* and the branch would start behind a prompt that looks idle (I-3 / K-1).
|
|
232
|
+
*/
|
|
205
233
|
export function powerShellScript(parts) {
|
|
206
|
-
|
|
234
|
+
// TOTAL, NOT THROWING (D-4): an empty argv cannot arise from
|
|
235
|
+
// `resolveAragonCommand` (T-3 pins that), and an empty script is a parse-valid
|
|
236
|
+
// no-op rather than a throw inside a key handler.
|
|
237
|
+
if (parts.length === 0)
|
|
238
|
+
return '';
|
|
239
|
+
return ['&', ...parts.map(powerShellQuote)].join(' ');
|
|
207
240
|
}
|
|
208
241
|
/** Bare words stay bare; everything else becomes a single-quoted literal. */
|
|
209
242
|
export function powerShellQuote(value) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* ASCII ONLY - `src/session/**` is inside `glyphs.test.ts::inScope`.
|
|
5
5
|
*
|
|
6
|
-
* THIS MODULE
|
|
6
|
+
* THIS MODULE PRODUCES NO IO ITSELF. It answers two questions and nothing else:
|
|
7
7
|
* "which argv pieces make up the aragon command" and "how does that sentence read
|
|
8
8
|
* when printed to a human". Spawning lives in `session/branch-launch.ts`, and the
|
|
9
9
|
* terminal ladder lives in `proc/terminal-plan.ts`, so the one part of this
|
|
@@ -12,6 +12,14 @@
|
|
|
12
12
|
* `proc/readiness.ts` and `proc/classify.ts` are pure, `proc/supervisor.ts` is the
|
|
13
13
|
* part that touches processes.
|
|
14
14
|
*
|
|
15
|
+
* IT DOES IMPORT `proc/terminal-plan.ts` (D-5), and that is still the right
|
|
16
|
+
* direction: `session -> proc`. The manual line needs both quoting dialects, and
|
|
17
|
+
* the dialect owner is `proc/terminal-plan.ts`; putting it in `proc/` would mean
|
|
18
|
+
* `proc/` importing `session/`, which is the cycle the header above forbids.
|
|
19
|
+
* What that import brings with it is one `statSync` probe used by the PATH lookup -
|
|
20
|
+
* not a process, not a write - so "no IO on purpose" still describes this module's
|
|
21
|
+
* own behaviour.
|
|
22
|
+
*
|
|
15
23
|
* NAMED `branch-invocation`, NOT `branch-command` (R-22): `commands/branch-command.ts`
|
|
16
24
|
* is the command body, and two files with one basename make every import, stack
|
|
17
25
|
* trace and grep ambiguous. What this module exports is an *invocation*.
|
|
@@ -28,7 +36,15 @@ export interface AragonCommandResolution {
|
|
|
28
36
|
export interface BranchInvocation {
|
|
29
37
|
/** `[...aragon.parts, '--resume', id]`. */
|
|
30
38
|
parts: string[];
|
|
31
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* One line, for the toast and the log.
|
|
41
|
+
*
|
|
42
|
+
* NOT THE PASTEABLE FORM ON WIN32 - and the defect that produced this comment is
|
|
43
|
+
* the proof: a double-quoted command word is a string EXPRESSION to PowerShell, so
|
|
44
|
+
* pasting this line into the shell Windows Terminal opens by default yields the
|
|
45
|
+
* same `ParserError` the new window used to. `renderManualCommand(parts,
|
|
46
|
+
* platform)` is the paste-ready form; on win32 it is two labelled lines.
|
|
47
|
+
*/
|
|
32
48
|
command: string;
|
|
33
49
|
}
|
|
34
50
|
export declare const BRANCH_RESUME_FLAG = "--resume";
|
|
@@ -54,21 +70,49 @@ export declare function resolveAragonCommand(input: {
|
|
|
54
70
|
execPath: string;
|
|
55
71
|
env: Record<string, string | undefined>;
|
|
56
72
|
}): AragonCommandResolution;
|
|
57
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* `parts` + `--resume <id>`, plus the ONE-LINE form for the toast and the log.
|
|
75
|
+
*
|
|
76
|
+
* The paste-ready form is NOT this field (see `BranchInvocation.command` and
|
|
77
|
+
* `renderManualCommand`): "one line for every shell" was the claim this branch
|
|
78
|
+
* disproved, and leaving it here would keep the next reader believing it.
|
|
79
|
+
*/
|
|
58
80
|
export declare function buildResumeInvocation(input: {
|
|
59
81
|
id: string;
|
|
60
82
|
aragon: AragonCommandResolution;
|
|
61
83
|
}): BranchInvocation;
|
|
62
84
|
/**
|
|
63
|
-
* Join argv pieces into one line a human can
|
|
85
|
+
* Join argv pieces into one line a human can read at a glance.
|
|
64
86
|
*
|
|
65
|
-
* NOT A SHELL ESCAPER
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
87
|
+
* NOT A SHELL ESCAPER, and no longer claimed to be a universal paste line: it is
|
|
88
|
+
* the RIGHT line for `cmd.exe` and for POSIX shells, and the WRONG one for
|
|
89
|
+
* PowerShell on win32 (a double-quoted command word is an expression there). Its
|
|
90
|
+
* product is what a POSIX/darwin user pastes, and what the `cmd.exe` half of the
|
|
91
|
+
* win32 manual block shows; the PowerShell half comes from `powerShellScript` via
|
|
92
|
+
* `renderManualCommand`. The real command lines are built by
|
|
93
|
+
* `proc/terminal-plan.ts`, which quotes for the exact parser each rung lands in.
|
|
70
94
|
*/
|
|
71
95
|
export declare function joinCommandLine(parts: readonly string[]): string;
|
|
96
|
+
/**
|
|
97
|
+
* The paste-ready form of the command, per platform (D-5 / D-6 / I-4).
|
|
98
|
+
*
|
|
99
|
+
* ONE LINE CANNOT BE CORRECT FOR BOTH WINDOWS PARSERS. A double-quoted command word
|
|
100
|
+
* is a string EXPRESSION to PowerShell (the defect this branch fixes) while it is
|
|
101
|
+
* exactly right for `cmd.exe`; `& 'x'` is right for PowerShell and wrong for
|
|
102
|
+
* `cmd.exe` (which keeps the single quotes and tries to run `'x'`). So Windows gets
|
|
103
|
+
* two labelled lines and the user copies the one matching the terminal they opened;
|
|
104
|
+
* POSIX and macOS keep the single line they have always printed.
|
|
105
|
+
*
|
|
106
|
+
* The first line is `powerShellScript(parts)` VERBATIM, which is what makes I-1
|
|
107
|
+
* ("one command, one artefact") testable: the fallback the user pastes is the same
|
|
108
|
+
* text the new window receives in its `-EncodedCommand` payload.
|
|
109
|
+
*
|
|
110
|
+
* @param parts argv pieces of the aragon invocation, `--resume <id>` included.
|
|
111
|
+
* @param platform the platform whose parsers the two lines have to satisfy.
|
|
112
|
+
* @returns one line off Windows; two ASCII lines labelled `PowerShell:` and
|
|
113
|
+
* `cmd.exe :` on win32. Both labels are 11 columns wide on purpose.
|
|
114
|
+
*/
|
|
115
|
+
export declare function renderManualCommand(parts: readonly string[], platform: NodeJS.Platform): string;
|
|
72
116
|
/**
|
|
73
117
|
* The second gate on D-9: an id is about to be concatenated into a command line.
|
|
74
118
|
*
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* ASCII ONLY - `src/session/**` is inside `glyphs.test.ts::inScope`.
|
|
5
5
|
*
|
|
6
|
-
* THIS MODULE
|
|
6
|
+
* THIS MODULE PRODUCES NO IO ITSELF. It answers two questions and nothing else:
|
|
7
7
|
* "which argv pieces make up the aragon command" and "how does that sentence read
|
|
8
8
|
* when printed to a human". Spawning lives in `session/branch-launch.ts`, and the
|
|
9
9
|
* terminal ladder lives in `proc/terminal-plan.ts`, so the one part of this
|
|
@@ -12,11 +12,20 @@
|
|
|
12
12
|
* `proc/readiness.ts` and `proc/classify.ts` are pure, `proc/supervisor.ts` is the
|
|
13
13
|
* part that touches processes.
|
|
14
14
|
*
|
|
15
|
+
* IT DOES IMPORT `proc/terminal-plan.ts` (D-5), and that is still the right
|
|
16
|
+
* direction: `session -> proc`. The manual line needs both quoting dialects, and
|
|
17
|
+
* the dialect owner is `proc/terminal-plan.ts`; putting it in `proc/` would mean
|
|
18
|
+
* `proc/` importing `session/`, which is the cycle the header above forbids.
|
|
19
|
+
* What that import brings with it is one `statSync` probe used by the PATH lookup -
|
|
20
|
+
* not a process, not a write - so "no IO on purpose" still describes this module's
|
|
21
|
+
* own behaviour.
|
|
22
|
+
*
|
|
15
23
|
* NAMED `branch-invocation`, NOT `branch-command` (R-22): `commands/branch-command.ts`
|
|
16
24
|
* is the command body, and two files with one basename make every import, stack
|
|
17
25
|
* trace and grep ambiguous. What this module exports is an *invocation*.
|
|
18
26
|
*/
|
|
19
27
|
import { isAbsolute } from 'node:path';
|
|
28
|
+
import { powerShellScript } from '../proc/terminal-plan.js';
|
|
20
29
|
import { isValidSessionId } from './store.js';
|
|
21
30
|
export const BRANCH_RESUME_FLAG = '--resume';
|
|
22
31
|
/** The env var that overrides "what executable the new window runs" (D-7 sibling). */
|
|
@@ -65,23 +74,58 @@ export function resolveAragonCommand(input) {
|
|
|
65
74
|
}
|
|
66
75
|
return { parts: ['aragon'], kind: 'bin', origin: 'aragon on PATH' };
|
|
67
76
|
}
|
|
68
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* `parts` + `--resume <id>`, plus the ONE-LINE form for the toast and the log.
|
|
79
|
+
*
|
|
80
|
+
* The paste-ready form is NOT this field (see `BranchInvocation.command` and
|
|
81
|
+
* `renderManualCommand`): "one line for every shell" was the claim this branch
|
|
82
|
+
* disproved, and leaving it here would keep the next reader believing it.
|
|
83
|
+
*/
|
|
69
84
|
export function buildResumeInvocation(input) {
|
|
70
85
|
const parts = [...input.aragon.parts, BRANCH_RESUME_FLAG, input.id];
|
|
71
86
|
return { parts, command: joinCommandLine(parts) };
|
|
72
87
|
}
|
|
73
88
|
/**
|
|
74
|
-
* Join argv pieces into one line a human can
|
|
89
|
+
* Join argv pieces into one line a human can read at a glance.
|
|
75
90
|
*
|
|
76
|
-
* NOT A SHELL ESCAPER
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
91
|
+
* NOT A SHELL ESCAPER, and no longer claimed to be a universal paste line: it is
|
|
92
|
+
* the RIGHT line for `cmd.exe` and for POSIX shells, and the WRONG one for
|
|
93
|
+
* PowerShell on win32 (a double-quoted command word is an expression there). Its
|
|
94
|
+
* product is what a POSIX/darwin user pastes, and what the `cmd.exe` half of the
|
|
95
|
+
* win32 manual block shows; the PowerShell half comes from `powerShellScript` via
|
|
96
|
+
* `renderManualCommand`. The real command lines are built by
|
|
97
|
+
* `proc/terminal-plan.ts`, which quotes for the exact parser each rung lands in.
|
|
81
98
|
*/
|
|
82
99
|
export function joinCommandLine(parts) {
|
|
83
100
|
return parts.map(quoteForDisplay).join(' ');
|
|
84
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* The paste-ready form of the command, per platform (D-5 / D-6 / I-4).
|
|
104
|
+
*
|
|
105
|
+
* ONE LINE CANNOT BE CORRECT FOR BOTH WINDOWS PARSERS. A double-quoted command word
|
|
106
|
+
* is a string EXPRESSION to PowerShell (the defect this branch fixes) while it is
|
|
107
|
+
* exactly right for `cmd.exe`; `& 'x'` is right for PowerShell and wrong for
|
|
108
|
+
* `cmd.exe` (which keeps the single quotes and tries to run `'x'`). So Windows gets
|
|
109
|
+
* two labelled lines and the user copies the one matching the terminal they opened;
|
|
110
|
+
* POSIX and macOS keep the single line they have always printed.
|
|
111
|
+
*
|
|
112
|
+
* The first line is `powerShellScript(parts)` VERBATIM, which is what makes I-1
|
|
113
|
+
* ("one command, one artefact") testable: the fallback the user pastes is the same
|
|
114
|
+
* text the new window receives in its `-EncodedCommand` payload.
|
|
115
|
+
*
|
|
116
|
+
* @param parts argv pieces of the aragon invocation, `--resume <id>` included.
|
|
117
|
+
* @param platform the platform whose parsers the two lines have to satisfy.
|
|
118
|
+
* @returns one line off Windows; two ASCII lines labelled `PowerShell:` and
|
|
119
|
+
* `cmd.exe :` on win32. Both labels are 11 columns wide on purpose.
|
|
120
|
+
*/
|
|
121
|
+
export function renderManualCommand(parts, platform) {
|
|
122
|
+
if (platform !== 'win32')
|
|
123
|
+
return joinCommandLine(parts);
|
|
124
|
+
return [
|
|
125
|
+
`PowerShell: ${powerShellScript(parts)}`,
|
|
126
|
+
`cmd.exe : ${joinCommandLine(parts)}`,
|
|
127
|
+
].join('\n');
|
|
128
|
+
}
|
|
85
129
|
/** Empty and whitespace-or-quote-bearing pieces get double quotes; the rest stay bare. */
|
|
86
130
|
function quoteForDisplay(part) {
|
|
87
131
|
if (part.length === 0)
|
|
@@ -29,6 +29,15 @@ export interface BranchRequest {
|
|
|
29
29
|
}
|
|
30
30
|
export type BranchOpenResult = {
|
|
31
31
|
ok: true;
|
|
32
|
+
/**
|
|
33
|
+
* The paste-ready command block for this platform (D-7).
|
|
34
|
+
*
|
|
35
|
+
* "Always present, so no failure is a dead end" is I-4 for the FAILURE arm;
|
|
36
|
+
* the success arm carries it for the mirror-image reason - "what did the
|
|
37
|
+
* window I just opened actually run" is copyable without retyping it from the
|
|
38
|
+
* toast. Same meaning as the field of the same name below: on win32 it is the
|
|
39
|
+
* two-line PowerShell / `cmd.exe` block, off win32 it is one line.
|
|
40
|
+
*/
|
|
32
41
|
command: string;
|
|
33
42
|
shell: string;
|
|
34
43
|
rung: string;
|
|
@@ -22,7 +22,7 @@ import { spawn } from 'node:child_process';
|
|
|
22
22
|
import { getLogger } from '../logging/logger.js';
|
|
23
23
|
import { defaultIsExecutable, pathDirsOf, planTerminalLaunch, resolveShell, truncateTitle, } from '../proc/terminal-plan.js';
|
|
24
24
|
import { BRANCH_COPY } from '../commands/branch-command.js';
|
|
25
|
-
import { buildResumeInvocation,
|
|
25
|
+
import { buildResumeInvocation, renderManualCommand, resolveAragonCommand, validateBranchId, } from './branch-invocation.js';
|
|
26
26
|
import { BRANCH_TERMINAL_LIMITS } from './limits.js';
|
|
27
27
|
/**
|
|
28
28
|
* Build the port. The only mutable state is this closure's four values, none of
|
|
@@ -41,12 +41,16 @@ export function createBranchLauncher(deps) {
|
|
|
41
41
|
let cachedAragon = null;
|
|
42
42
|
let cachedShell = null;
|
|
43
43
|
/**
|
|
44
|
-
* Resolve WHAT the new window runs, and the command
|
|
44
|
+
* Resolve WHAT the new window runs, and the two command forms built from it.
|
|
45
45
|
*
|
|
46
46
|
* The resolution is cached for the process (R-13 / K-14). It costs no
|
|
47
47
|
* filesystem probe - the three rules are env, `argv[1]` and a literal - so the
|
|
48
48
|
* cache is about identity, not speed: the value that opened the first window is
|
|
49
49
|
* the value the log keeps reporting.
|
|
50
|
+
*
|
|
51
|
+
* `manual` is derived HERE from the same `parts` on purpose (D-7): one source of
|
|
52
|
+
* truth for the command means the paste block and the `-EncodedCommand` payload
|
|
53
|
+
* cannot drift apart (I-1).
|
|
50
54
|
*/
|
|
51
55
|
function planFor(id) {
|
|
52
56
|
cachedAragon ??= resolveAragonCommand({
|
|
@@ -54,9 +58,11 @@ export function createBranchLauncher(deps) {
|
|
|
54
58
|
execPath: deps.execPath,
|
|
55
59
|
env: deps.env,
|
|
56
60
|
});
|
|
61
|
+
const invocation = buildResumeInvocation({ id, aragon: cachedAragon });
|
|
57
62
|
return {
|
|
58
63
|
aragon: cachedAragon,
|
|
59
|
-
invocation
|
|
64
|
+
invocation,
|
|
65
|
+
manual: renderManualCommand(invocation.parts, deps.platform),
|
|
60
66
|
};
|
|
61
67
|
}
|
|
62
68
|
function shellFor() {
|
|
@@ -93,22 +99,25 @@ export function createBranchLauncher(deps) {
|
|
|
93
99
|
return cooldown(command, Math.max(1, windowMs - waited));
|
|
94
100
|
}
|
|
95
101
|
async function openOnce(request) {
|
|
96
|
-
const { aragon, invocation } = planFor(request.id);
|
|
102
|
+
const { aragon, invocation, manual } = planFor(request.id);
|
|
97
103
|
const command = invocation.command;
|
|
98
104
|
// 4.5 BEFORE ANY SPAWN (R-7 / T-46): a command line that `cmd` or AppleScript
|
|
99
105
|
// would truncate is worse than no command line, because the window opens and
|
|
100
|
-
// then fails in a way this process cannot see.
|
|
106
|
+
// then fails in a way this process cannot see. The MEASURED object stays the
|
|
107
|
+
// single unquoted line: it is the lower bound of every dialect (quoting only
|
|
108
|
+
// ever adds characters), so the check is conservative and needs no second
|
|
109
|
+
// source of truth.
|
|
101
110
|
if (command.length > BRANCH_TERMINAL_LIMITS.maxCommandLineCells) {
|
|
102
111
|
// A COMPLETE SENTENCE WITH THE FALLBACK INSIDE IT (I-4 / appendix A): the
|
|
103
112
|
// over-long case is the one failure where the user most needs a command to
|
|
104
113
|
// paste, so it may not report the bare reason "command line too long" - that
|
|
105
114
|
// is an internal code, not an answer, and it would leave a dead end.
|
|
106
|
-
return failure('spawn-failed', BRANCH_COPY.spawnFailed('command line too long',
|
|
115
|
+
return failure('spawn-failed', BRANCH_COPY.spawnFailed('command line too long', manual), manual);
|
|
107
116
|
}
|
|
108
117
|
// D-9's second gate: an id is about to enter a command line.
|
|
109
118
|
const invalid = validateBranchId(request.id);
|
|
110
119
|
if (invalid !== null)
|
|
111
|
-
return failure('spawn-failed', invalid,
|
|
120
|
+
return failure('spawn-failed', invalid, manual);
|
|
112
121
|
// Step 6: flush FIRST, spawn only on `ok` (I-1). `force` skips the fingerprint
|
|
113
122
|
// short circuit and nothing else (D-15 / I-7).
|
|
114
123
|
const flushed = deps.checkpoint({ force: true });
|
|
@@ -118,7 +127,10 @@ export function createBranchLauncher(deps) {
|
|
|
118
127
|
// disk problem, so it must not be printed as "could not checkpoint
|
|
119
128
|
// (no-identity)" - that reads like corruption. It gets `/session`'s sentence.
|
|
120
129
|
const message = reason === 'no-identity' ? BRANCH_COPY.noIdentity : BRANCH_COPY.checkpointFailed(reason);
|
|
121
|
-
|
|
130
|
+
// The THIRD argument changes, never the sentence (parent-R-19): these two
|
|
131
|
+
// failures deliberately do NOT embed the command, because their job is to
|
|
132
|
+
// explain why nothing opened. The command still travels in `result.command`.
|
|
133
|
+
return failure('checkpoint-failed', message, manual);
|
|
122
134
|
}
|
|
123
135
|
const shell = shellFor();
|
|
124
136
|
// Step 7: the TITLE is what gets truncated, never the id in the command line (T-47).
|
|
@@ -132,11 +144,13 @@ export function createBranchLauncher(deps) {
|
|
|
132
144
|
command: invocation.parts,
|
|
133
145
|
title,
|
|
134
146
|
cwd: request.cwd,
|
|
135
|
-
|
|
147
|
+
// The paste block the fallback prints, built by the same dialect owner that
|
|
148
|
+
// encodes the window script (I-1: one command, one artefact).
|
|
149
|
+
manual,
|
|
136
150
|
});
|
|
137
151
|
if (!planned.ok) {
|
|
138
152
|
// Not cached: the next press must be allowed to find a terminal that appeared.
|
|
139
|
-
return failure('no-launcher', BRANCH_COPY.noLauncher(planned.manual),
|
|
153
|
+
return failure('no-launcher', BRANCH_COPY.noLauncher(planned.manual), manual);
|
|
140
154
|
}
|
|
141
155
|
// Step 8: walk the ladder, bounded by maxRungs (T-33).
|
|
142
156
|
const rungs = planned.plans.slice(0, BRANCH_TERMINAL_LIMITS.maxRungs);
|
|
@@ -156,14 +170,14 @@ export function createBranchLauncher(deps) {
|
|
|
156
170
|
aragon: aragon.kind,
|
|
157
171
|
origin: aragon.origin,
|
|
158
172
|
});
|
|
159
|
-
return { ok: true, command, shell: planned.shell.path, rung: plan.rung };
|
|
173
|
+
return { ok: true, command: manual, shell: planned.shell.path, rung: plan.rung };
|
|
160
174
|
}
|
|
161
175
|
logger.warn('rung_failed', { rung: plan.rung, message: outcome.message });
|
|
162
176
|
lastError = outcome;
|
|
163
177
|
}
|
|
164
178
|
// Step 9: every rung failed. `wt`'s own semantic failures land here too only if
|
|
165
179
|
// the error event fired; a forwarded-but-rejected `wt` invocation does not (K-1).
|
|
166
|
-
return failure('spawn-failed', BRANCH_COPY.spawnFailed(lastError?.message ?? 'no rung available',
|
|
180
|
+
return failure('spawn-failed', BRANCH_COPY.spawnFailed(lastError?.message ?? 'no rung available', manual), manual);
|
|
167
181
|
}
|
|
168
182
|
/**
|
|
169
183
|
* One rung, with EXACTLY ONE `'error'` LISTENER THAT IS NEVER REMOVED (R-3 / I-10).
|
|
@@ -230,7 +244,10 @@ export function createBranchLauncher(deps) {
|
|
|
230
244
|
}
|
|
231
245
|
return {
|
|
232
246
|
async open(request) {
|
|
233
|
-
|
|
247
|
+
// The cooldown path carries the same paste block. It never displays it, but the
|
|
248
|
+
// result type has exactly one meaning for `command` and inventing a second one
|
|
249
|
+
// for this arm is how the two forms would drift apart (D-7).
|
|
250
|
+
const blocked = gated(planFor(request.id).manual);
|
|
234
251
|
if (blocked !== null)
|
|
235
252
|
return blocked;
|
|
236
253
|
inFlight = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aragon-agent/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.27",
|
|
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",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"prepublishOnly": "npm run build"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@aragon-agent/core": "^0.2.
|
|
59
|
+
"@aragon-agent/core": "^0.2.39",
|
|
60
60
|
"chalk": "^5.6.2",
|
|
61
61
|
"cli-highlight": "^2.0.0",
|
|
62
62
|
"commander": "^12.1.0",
|