@aragon-agent/cli 0.6.38 → 0.6.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/CHANGELOG.md +2523 -2326
  2. package/README.md +3614 -3173
  3. package/dist/agent/controller.d.ts +142 -1
  4. package/dist/agent/controller.js +171 -3
  5. package/dist/agent/reducer.d.ts +41 -1
  6. package/dist/agent/reducer.js +25 -0
  7. package/dist/agent/system-prompt.d.ts +14 -0
  8. package/dist/agent/system-prompt.js +5 -0
  9. package/dist/cli.js +85 -27
  10. package/dist/commands/builtins.js +13 -1
  11. package/dist/commands/keys-command.d.ts +23 -0
  12. package/dist/commands/keys-command.js +38 -0
  13. package/dist/commands/registry.d.ts +39 -0
  14. package/dist/commands/task-command.d.ts +39 -0
  15. package/dist/commands/task-command.js +102 -0
  16. package/dist/config/cli-commands.d.ts +23 -0
  17. package/dist/config/cli-commands.js +36 -0
  18. package/dist/config/env.js +17 -4
  19. package/dist/config/load.js +6 -1
  20. package/dist/config/schema.d.ts +43 -4
  21. package/dist/config/schema.js +31 -5
  22. package/dist/config/store.js +17 -2
  23. package/dist/diagnostics/doctor.d.ts +9 -0
  24. package/dist/diagnostics/doctor.js +34 -0
  25. package/dist/diagnostics/info.d.ts +71 -0
  26. package/dist/diagnostics/info.js +91 -5
  27. package/dist/exec/bridge/caller-tools.d.ts +96 -0
  28. package/dist/exec/bridge/caller-tools.js +213 -0
  29. package/dist/exec/bridge/decisions.d.ts +78 -0
  30. package/dist/exec/bridge/decisions.js +116 -0
  31. package/dist/exec/bridge/human-answers.d.ts +38 -0
  32. package/dist/exec/bridge/human-answers.js +164 -0
  33. package/dist/exec/bridge/limits.d.ts +68 -0
  34. package/dist/exec/bridge/limits.js +68 -0
  35. package/dist/exec/bridge/ndjson.d.ts +17 -0
  36. package/dist/exec/bridge/ndjson.js +83 -0
  37. package/dist/exec/bridge/pending.d.ts +70 -0
  38. package/dist/exec/bridge/pending.js +132 -0
  39. package/dist/exec/bridge/protocol.d.ts +346 -0
  40. package/dist/exec/bridge/protocol.js +256 -0
  41. package/dist/exec/bridge/server.d.ts +142 -0
  42. package/dist/exec/bridge/server.js +502 -0
  43. package/dist/exec/bridge/source.d.ts +58 -0
  44. package/dist/exec/bridge/source.js +145 -0
  45. package/dist/exec/bridge/writer.d.ts +26 -0
  46. package/dist/exec/bridge/writer.js +156 -0
  47. package/dist/exec/cli-commands.js +30 -2
  48. package/dist/exec/emitter.d.ts +16 -2
  49. package/dist/exec/emitter.js +26 -6
  50. package/dist/exec/events.d.ts +465 -10
  51. package/dist/exec/events.js +47 -1
  52. package/dist/exec/hooks/dispatch.d.ts +76 -0
  53. package/dist/exec/hooks/dispatch.js +169 -0
  54. package/dist/exec/hooks/index.d.ts +83 -0
  55. package/dist/exec/hooks/index.js +205 -0
  56. package/dist/exec/hooks/limits.d.ts +56 -0
  57. package/dist/exec/hooks/limits.js +49 -0
  58. package/dist/exec/hooks/registry.d.ts +49 -0
  59. package/dist/exec/hooks/registry.js +91 -0
  60. package/dist/exec/hooks/tool-wrapper.d.ts +94 -0
  61. package/dist/exec/hooks/tool-wrapper.js +154 -0
  62. package/dist/exec/hooks/turn.d.ts +61 -0
  63. package/dist/exec/hooks/turn.js +59 -0
  64. package/dist/exec/hooks/validate.d.ts +85 -0
  65. package/dist/exec/hooks/validate.js +310 -0
  66. package/dist/exec/index.d.ts +130 -4
  67. package/dist/exec/index.js +501 -10
  68. package/dist/exec/options.d.ts +124 -2
  69. package/dist/exec/options.js +413 -6
  70. package/dist/exec/permission.d.ts +9 -1
  71. package/dist/exec/permission.js +7 -1
  72. package/dist/exec/request/limits.d.ts +51 -0
  73. package/dist/exec/request/limits.js +49 -0
  74. package/dist/exec/request/scope.d.ts +106 -0
  75. package/dist/exec/request/scope.js +149 -0
  76. package/dist/exec/result/limits.d.ts +74 -0
  77. package/dist/exec/result/limits.js +72 -0
  78. package/dist/exec/result/prompt.d.ts +28 -0
  79. package/dist/exec/result/prompt.js +74 -0
  80. package/dist/exec/result/schema.d.ts +99 -0
  81. package/dist/exec/result/schema.js +464 -0
  82. package/dist/exec/result/sink.d.ts +56 -0
  83. package/dist/exec/result/sink.js +50 -0
  84. package/dist/exec/result/tool.d.ts +37 -0
  85. package/dist/exec/result/tool.js +118 -0
  86. package/dist/exec/runner.d.ts +395 -1
  87. package/dist/exec/runner.js +860 -8
  88. package/dist/exec/threads/aggregate.d.ts +34 -0
  89. package/dist/exec/threads/aggregate.js +30 -0
  90. package/dist/exec/threads/capacity.d.ts +23 -0
  91. package/dist/exec/threads/capacity.js +38 -0
  92. package/dist/exec/threads/dispatch-source.d.ts +60 -0
  93. package/dist/exec/threads/dispatch-source.js +106 -0
  94. package/dist/exec/threads/execute-threads.d.ts +53 -0
  95. package/dist/exec/threads/execute-threads.js +234 -0
  96. package/dist/exec/threads/gate.d.ts +42 -0
  97. package/dist/exec/threads/gate.js +81 -0
  98. package/dist/exec/threads/identity.d.ts +36 -0
  99. package/dist/exec/threads/identity.js +67 -0
  100. package/dist/exec/threads/interrupt.d.ts +29 -0
  101. package/dist/exec/threads/interrupt.js +79 -0
  102. package/dist/exec/threads/limits.d.ts +58 -0
  103. package/dist/exec/threads/limits.js +35 -0
  104. package/dist/exec/threads/outcome.d.ts +47 -0
  105. package/dist/exec/threads/outcome.js +26 -0
  106. package/dist/exec/threads/registry.d.ts +81 -0
  107. package/dist/exec/threads/registry.js +427 -0
  108. package/dist/exec/threads/runtime.d.ts +107 -0
  109. package/dist/exec/threads/runtime.js +523 -0
  110. package/dist/exec/threads/thread-source.d.ts +20 -0
  111. package/dist/exec/threads/thread-source.js +134 -0
  112. package/dist/fast/limits.d.ts +54 -0
  113. package/dist/fast/limits.js +54 -0
  114. package/dist/fast/summary-call.d.ts +72 -0
  115. package/dist/fast/summary-call.js +133 -0
  116. package/dist/fast/summary-prompt.d.ts +106 -0
  117. package/dist/fast/summary-prompt.js +320 -0
  118. package/dist/fast/task-summary-types.d.ts +66 -0
  119. package/dist/fast/task-summary-types.js +17 -0
  120. package/dist/logging/logger.d.ts +1 -1
  121. package/dist/proc/limits.d.ts +7 -0
  122. package/dist/proc/limits.js +7 -0
  123. package/dist/proc/output-streams.d.ts +50 -0
  124. package/dist/proc/output-streams.js +54 -0
  125. package/dist/team/runtime.d.ts +9 -0
  126. package/dist/team/runtime.js +8 -0
  127. package/dist/team/subagent.d.ts +15 -0
  128. package/dist/team/subagent.js +45 -1
  129. package/dist/tools/bash-tool.d.ts +10 -0
  130. package/dist/tools/bash-tool.js +71 -6
  131. package/dist/tools/index.d.ts +93 -1
  132. package/dist/tools/index.js +112 -3
  133. package/dist/tools/tool-gate.d.ts +100 -0
  134. package/dist/tools/tool-gate.js +87 -0
  135. package/dist/ui/App.js +224 -11
  136. package/dist/ui/Header.d.ts +28 -16
  137. package/dist/ui/Header.js +21 -19
  138. package/dist/ui/interaction-hints.js +6 -0
  139. package/dist/ui/layout/OverlayFrame.d.ts +18 -0
  140. package/dist/ui/layout/OverlayFrame.js +19 -4
  141. package/dist/ui/layout/header-hints.d.ts +55 -62
  142. package/dist/ui/layout/header-hints.js +59 -166
  143. package/dist/ui/layout/header-left.d.ts +115 -0
  144. package/dist/ui/layout/header-left.js +206 -0
  145. package/dist/ui/layout/shortcut-panel-layout.d.ts +191 -0
  146. package/dist/ui/layout/shortcut-panel-layout.js +321 -0
  147. package/dist/ui/layout/status-detail-layout.js +5 -1
  148. package/dist/ui/layout/task-summary-cell.d.ts +61 -0
  149. package/dist/ui/layout/task-summary-cell.js +65 -0
  150. package/dist/ui/overlays/HelpOverlay.d.ts +12 -0
  151. package/dist/ui/overlays/HelpOverlay.js +29 -2
  152. package/dist/ui/overlays/ShortcutPanel.d.ts +39 -0
  153. package/dist/ui/overlays/ShortcutPanel.js +106 -0
  154. package/dist/ui/shortcuts.d.ts +81 -0
  155. package/dist/ui/shortcuts.js +221 -0
  156. package/package.json +2 -2
@@ -1,25 +1,21 @@
1
1
  /**
2
- * The right-hand hint budget for the header row (tui-branch-terminal-shortcut 3.8).
2
+ * The right-hand hint budget for the header row
3
+ * (tui-shortcut-drawer-and-task-summary §4.2).
3
4
  *
4
5
  * ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`.
5
6
  *
6
- * WHY THIS IS A PLANNER AND NOT ANOTHER TERNARY IN THE JSX (R-0-4): the header's
7
- * right side used to be one hand-computed width constant (`detailCells = indexHint
8
- * ? 32 : 18`). Adding a third affordance to that arithmetic produces either a
9
- * clipped hint or a pushed-out word mark on a 72-column terminal, and NO TEST
10
- * WOULD SEE IT - the only thing lost is trailing characters. `ui/layout/session-chip.ts`
11
- * already set the precedent for the same problem: the chip has to participate in
12
- * one width budget rather than be appended and hoped for.
7
+ * THIS FILE USED TO BE A SIX-RUNG LADDER. `^L branch`, `^P prompts`, `^I Index`,
8
+ * `^K switch kit`, `^G more` and the expanded variants competed for a width
9
+ * budget, so what a user saw was not "my shortcuts" but "which clauses happen to
10
+ * fit at this width" - and on an 80-column terminal that was three of six. The
11
+ * requirement replaces the whole idea with ONE entry point (`ctrl+G more`) whose
12
+ * panel lists every shortcut, so the ladder is gone and what remains is a
13
+ * three-state single clause.
13
14
  *
14
- * THE RULE IS ONE COMPARISON. `budget = columns - minLeftCells`, then take the
15
- * first ladder rung whose `cells` fits. There is no second magic constant, and the
16
- * threshold table in the spec is what that comparison WORKS OUT TO, not an extra
17
- * branch.
18
- *
19
- * WHY `minLeftCells` IS 24: the word mark is 13 cells and `provider:model` + cwd +
20
- * the key symbol need at least 11 more to stay readable. It is lower than
21
- * `status-layout`'s 34 because the top-left half has a brand to lose while the
22
- * bottom-left half has a phase that must not disappear.
15
+ * WHY A PLANNER AND NOT A TERNARY IN THE JSX (R-0-4, unchanged): the header's
16
+ * right side was once one hand-computed width constant, and every later addition
17
+ * to it produced either a clipped hint or a pushed-out word mark with no test
18
+ * able to see it - the only thing lost is trailing characters.
23
19
  */
24
20
  /**
25
21
  * Bounds for the hint budget.
@@ -27,71 +23,68 @@
27
23
  * WHY THEY LIVE IN THIS FILE (R-21, accepted): `CLAUDE.md`'s general rule is that
28
24
  * structural bounds live in the subsystem's `limits.ts`, and `ui/layout/` has none
29
25
  * - `budget.ts` keeps `HINT_MIN_ROWS` beside its own module for the same reason.
30
- * This module has exactly one consumer. If `ui/layout/` ever grows a real
31
- * `limits.ts`, these three constants should move there together; this comment is
32
- * that future migration's note.
33
26
  */
34
27
  export declare const HEADER_HINT_LIMITS: {
35
- /** Cells the LEFT half of the header must keep (`AragonAgent` is 13 of them). */
28
+ /**
29
+ * Cells the LEFT half of the header must keep.
30
+ *
31
+ * 24 = the 13-cell word mark plus at least 11 for `provider:model`, the
32
+ * directory and the key symbol. Lower than `status-layout`'s 34 because the top
33
+ * row has a brand to lose while the bottom row has a phase that must not
34
+ * disappear.
35
+ */
36
36
  readonly minLeftCells: 24;
37
- /** Fixed spacing between two hint clauses. */
38
- readonly gapCells: 2;
39
37
  /**
40
38
  * Blank cells between the header's left half and this hint column (defect 12).
41
39
  *
42
- * A SEPARATE CONSTANT FROM `gapCells`. That one spaces two hint clauses INSIDE
43
- * the right column (`buildPlan` below); this one separates the session
44
- * description from that column. One constant carrying two meanings is how a
45
- * later change to clause spacing silently re-lays-out the whole row.
40
+ * A SEPARATE CONCEPT FROM CLAUSE SPACING: this one separates the two HALVES of
41
+ * the row, and it is spent out of the same `columns` budget so the row still
42
+ * spans exactly the terminal width.
46
43
  */
47
44
  readonly separatorCells: 2;
48
45
  };
49
- export type HeaderHintId = 'branch' | 'prompts' | 'index' | 'kit' | 'detail';
50
- export interface HeaderHintClause {
51
- id: HeaderHintId;
52
- text: string;
53
- }
46
+ /**
47
+ * What the right-hand column says. THREE STATES, NO LADDER.
48
+ *
49
+ * `none` - no overlay: `^G more`, the only shortcut hint the header teaches.
50
+ * `shortcuts` - this panel is open: `^G close`, the key that closes it.
51
+ * `other` - some other overlay owns the keyboard: `Esc close`, which is true
52
+ * for every overlay and is the only key that works in all of them.
53
+ */
54
+ export type HeaderPanelState = 'none' | 'shortcuts' | 'other';
54
55
  export interface HeaderHintPlan {
55
- /** Clauses left to right (including the overlay's `Details on/off` replacement). */
56
- clauses: HeaderHintClause[];
57
56
  /** The whole right-hand string, scroll chip included with its trailing spacing. */
58
57
  text: string;
59
- /** Total cells of the right column; `Header` uses it to size the left one. */
58
+ /** Total cells of the right column; `Header` sizes the left one from it. */
60
59
  cells: number;
61
60
  /** Cells taken by the scroll chip (0 when there is none). */
62
61
  scrollCells: number;
63
- /** True while an overlay owns the keyboard: only the detail word remains. */
64
- detailsOnly: boolean;
65
62
  }
63
+ /** The verbatim text of each state. Exported so a test can name it instead of guessing. */
64
+ export declare const HEADER_HINTS: Readonly<Record<HeaderPanelState, string>>;
66
65
  /**
67
- * Plan the right-hand hints.
66
+ * Plan the right-hand hint.
68
67
  *
69
- * The inputs are the props `Header` receives plus the two switches that decide which
70
- * clauses exist at all - the call site does not change, only the arithmetic behind it
71
- * (spec 3.11 step 5).
68
+ * NO WIDTH BRANCHING: the clause is always rendered. When the row cannot hold it
69
+ * the LEFT half is what shrinks (to zero) and `overflow="hidden"` keeps that from
70
+ * spilling - the pre-feature behaviour, which is what makes the header safe at 20
71
+ * columns without a ladder. `headerHintFits` below exists only so the threshold
72
+ * is documented and testable, not so the renderer can branch on it.
72
73
  */
73
74
  export declare function planHeaderHints(input: {
74
75
  columns: number;
75
- statusExpanded: boolean;
76
- overlayOpen: boolean;
76
+ panel: HeaderPanelState;
77
77
  scrolledLines: number;
78
- /**
79
- * Whether the `^K switch kit` clause exists at all (default `true`).
80
- *
81
- * `App` ALWAYS PASSES TRUE, including for a user with no kits: the whole point
82
- * of the requirement is discoverability of `ctrl+K`, and an empty library is
83
- * exactly the state that needs it (the picker's own `No kits yet` row tells
84
- * them where to create one). `false` exists for tests and for the future
85
- * switch, and it degrades the six expanded rungs back into today's four.
86
- */
87
- kitHint?: boolean;
88
- /**
89
- * Whether the `^P prompts` clause exists at all (default `true`).
90
- *
91
- * `false` is the `prompts.hint` config key and the only reason the ladder is
92
- * parameterised: a user who turned the header hint off must get exactly the row
93
- * they had before this feature, which is what the unfiltered ladder below
94
- * degenerates into.
95
- */
96
- promptsHint?: boolean;
97
78
  }): HeaderHintPlan;
79
+ /**
80
+ * Whether the row can hold the hint column without squeezing the left half below
81
+ * `minLeftCells`.
82
+ *
83
+ * A FORMULA, NOT A TABLE OF MAGIC NUMBERS: `columns - minLeftCells -
84
+ * separatorCells >= scrollCells + bodyCells`. Substituting the three states gives
85
+ * 32 / 34 / 35 columns with no chip, plus up to 8 more for a saturated `^9999+ `.
86
+ * v1 claimed "any width >= 35", which forgot the chip entirely.
87
+ */
88
+ export declare function headerHintFits(columns: number, panel: HeaderPanelState): boolean;
89
+ /** The narrowest width at which each state fits with no scroll chip. */
90
+ export declare function headerHintThreshold(panel: HeaderPanelState): number;
@@ -1,25 +1,21 @@
1
1
  /**
2
- * The right-hand hint budget for the header row (tui-branch-terminal-shortcut 3.8).
2
+ * The right-hand hint budget for the header row
3
+ * (tui-shortcut-drawer-and-task-summary §4.2).
3
4
  *
4
5
  * ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`.
5
6
  *
6
- * WHY THIS IS A PLANNER AND NOT ANOTHER TERNARY IN THE JSX (R-0-4): the header's
7
- * right side used to be one hand-computed width constant (`detailCells = indexHint
8
- * ? 32 : 18`). Adding a third affordance to that arithmetic produces either a
9
- * clipped hint or a pushed-out word mark on a 72-column terminal, and NO TEST
10
- * WOULD SEE IT - the only thing lost is trailing characters. `ui/layout/session-chip.ts`
11
- * already set the precedent for the same problem: the chip has to participate in
12
- * one width budget rather than be appended and hoped for.
7
+ * THIS FILE USED TO BE A SIX-RUNG LADDER. `^L branch`, `^P prompts`, `^I Index`,
8
+ * `^K switch kit`, `^G more` and the expanded variants competed for a width
9
+ * budget, so what a user saw was not "my shortcuts" but "which clauses happen to
10
+ * fit at this width" - and on an 80-column terminal that was three of six. The
11
+ * requirement replaces the whole idea with ONE entry point (`ctrl+G more`) whose
12
+ * panel lists every shortcut, so the ladder is gone and what remains is a
13
+ * three-state single clause.
13
14
  *
14
- * THE RULE IS ONE COMPARISON. `budget = columns - minLeftCells`, then take the
15
- * first ladder rung whose `cells` fits. There is no second magic constant, and the
16
- * threshold table in the spec is what that comparison WORKS OUT TO, not an extra
17
- * branch.
18
- *
19
- * WHY `minLeftCells` IS 24: the word mark is 13 cells and `provider:model` + cwd +
20
- * the key symbol need at least 11 more to stay readable. It is lower than
21
- * `status-layout`'s 34 because the top-left half has a brand to lose while the
22
- * bottom-left half has a phase that must not disappear.
15
+ * WHY A PLANNER AND NOT A TERNARY IN THE JSX (R-0-4, unchanged): the header's
16
+ * right side was once one hand-computed width constant, and every later addition
17
+ * to it produced either a clipped hint or a pushed-out word mark with no test
18
+ * able to see it - the only thing lost is trailing characters.
23
19
  */
24
20
  /**
25
21
  * Bounds for the hint budget.
@@ -27,183 +23,80 @@
27
23
  * WHY THEY LIVE IN THIS FILE (R-21, accepted): `CLAUDE.md`'s general rule is that
28
24
  * structural bounds live in the subsystem's `limits.ts`, and `ui/layout/` has none
29
25
  * - `budget.ts` keeps `HINT_MIN_ROWS` beside its own module for the same reason.
30
- * This module has exactly one consumer. If `ui/layout/` ever grows a real
31
- * `limits.ts`, these three constants should move there together; this comment is
32
- * that future migration's note.
33
26
  */
34
27
  export const HEADER_HINT_LIMITS = {
35
- /** Cells the LEFT half of the header must keep (`AragonAgent` is 13 of them). */
28
+ /**
29
+ * Cells the LEFT half of the header must keep.
30
+ *
31
+ * 24 = the 13-cell word mark plus at least 11 for `provider:model`, the
32
+ * directory and the key symbol. Lower than `status-layout`'s 34 because the top
33
+ * row has a brand to lose while the bottom row has a phase that must not
34
+ * disappear.
35
+ */
36
36
  minLeftCells: 24,
37
- /** Fixed spacing between two hint clauses. */
38
- gapCells: 2,
39
37
  /**
40
38
  * Blank cells between the header's left half and this hint column (defect 12).
41
39
  *
42
- * A SEPARATE CONSTANT FROM `gapCells`. That one spaces two hint clauses INSIDE
43
- * the right column (`buildPlan` below); this one separates the session
44
- * description from that column. One constant carrying two meanings is how a
45
- * later change to clause spacing silently re-lays-out the whole row.
40
+ * A SEPARATE CONCEPT FROM CLAUSE SPACING: this one separates the two HALVES of
41
+ * the row, and it is spent out of the same `columns` budget so the row still
42
+ * spans exactly the terminal width.
46
43
  */
47
44
  separatorCells: 2,
48
45
  };
49
- /** The verbatim clause text (spec 3.8, appendix A). */
50
- const BRANCH_CLAUSE = { collapsed: '^L branch', expanded: '^L new branch' };
51
- const INDEX_CLAUSE = { collapsed: '^I Index', expanded: '^I build index' };
52
- /**
53
- * THE KIT CLAUSE HAS NO COLLAPSED FORM (agent-model-kits D-11).
54
- *
55
- * The requirement says the `ctrl+k` hint appears AFTER `ctrl+G`; the collapsed
56
- * row is the narrowest budget in the header, and `^K` is a low-frequency action.
57
- * Spending 13 columns of the everyday row on it would push `provider:model`
58
- * leftward on every launch for a key pressed twice a session.
59
- */
60
- const KIT_CLAUSE = { expanded: '^K switch kit' };
61
- /**
62
- * THE PROMPT LIBRARY KEY HAS EXACTLY ONE FORM, AND IT IS THE SHORT ONE
63
- * (quick-prompt-library D-15).
64
- *
65
- * `^P` is a REQUIREMENT (the user must be able to see that `ctrl+P` exists), and
66
- * the collapsed row is where that requirement is met. The expanded row's job is to
67
- * reveal MORE keys than the collapsed one, so inserting a 17-cell
68
- * `^P prompt library` before `^I` there would trade `^I` and `^K` away on an
69
- * 80-column terminal - a user who pressed `^G` asking for more would be shown less.
70
- *
71
- * The second thing this constant's position encodes is an explicit REVISION of the
72
- * `RV-P1-2` ordering constraint for the COLLAPSED ladder: `^P` sits before `^I`
73
- * there, so `^I Index` now appears from 66 columns instead of 54. That is a
74
- * deliberate, documented loss: `^I` is a once-per-project action and `^P` is the
75
- * entry point this feature is about. The expanded ladder keeps its full existing
76
- * order, so `RV-P1-2` still holds there and `^I` never disappears from a row where
77
- * it used to fit.
78
- */
79
- const PROMPTS_CLAUSE = '^P prompts';
80
- const DETAIL_CLAUSE = { collapsed: '^G more', expanded: '^G less' };
81
- /** L3 gives the key name and nothing else: discoverability at the last width. */
82
- const BRANCH_KEY_ONLY = '^L';
83
- /** Overlay wording, kept exactly as `Header.tsx` had it. */
84
- const DETAILS_ON = 'Details on';
85
- const DETAILS_OFF = 'Details off';
46
+ /** The verbatim text of each state. Exported so a test can name it instead of guessing. */
47
+ export const HEADER_HINTS = {
48
+ none: '^G more',
49
+ shortcuts: '^G close',
50
+ other: 'Esc close',
51
+ };
86
52
  /** Above this, the chip stops counting and saturates (`^9999+`), as it always has. */
87
53
  const SCROLL_CHIP_MAX = 9999;
88
54
  /**
89
- * Plan the right-hand hints.
55
+ * Plan the right-hand hint.
90
56
  *
91
- * The inputs are the props `Header` receives plus the two switches that decide which
92
- * clauses exist at all - the call site does not change, only the arithmetic behind it
93
- * (spec 3.11 step 5).
57
+ * NO WIDTH BRANCHING: the clause is always rendered. When the row cannot hold it
58
+ * the LEFT half is what shrinks (to zero) and `overflow="hidden"` keeps that from
59
+ * spilling - the pre-feature behaviour, which is what makes the header safe at 20
60
+ * columns without a ladder. `headerHintFits` below exists only so the threshold
61
+ * is documented and testable, not so the renderer can branch on it.
94
62
  */
95
63
  export function planHeaderHints(input) {
96
- // NORMALISED HERE, NOT TRUSTED (R-14). `Header` defaults to 80, but a test or a
97
- // future caller may pass anything; `budget.ts::dimension()` already set the rule
98
- // (non-finite or < 1 falls back, everything else floors) and this follows it.
99
64
  const columns = normalizeDimension(input.columns, 80);
100
65
  const scrolledLines = Math.max(0, Math.floor(normalizeDimension(input.scrolledLines, 0)));
101
- const scrollCells = scrollChip(scrolledLines, input.overlayOpen);
102
- if (input.overlayOpen) {
103
- // THE OVERLAY OWNS THE KEYBOARD, so every key hint disappears (D-14's sibling
104
- // rule, unchanged from the old JSX). The scroll chip stays: it is a position
105
- // indicator, not an affordance.
106
- const detail = input.statusExpanded ? DETAILS_ON : DETAILS_OFF;
107
- return buildPlan([{ id: 'detail', text: detail }], scrollCells, true);
108
- }
109
- const ladder = ladderFor(input.statusExpanded, input.kitHint !== false, input.promptsHint !== false);
110
- // THE SEPARATOR IS SPENT OUT OF THE SAME ROW (defect 12): left half + gap + this
111
- // column have to add up to `columns`, so the gap comes off the budget instead of
112
- // being assumed free.
113
- const budget = columns - HEADER_HINT_LIMITS.minLeftCells
114
- - HEADER_HINT_LIMITS.separatorCells;
115
- for (const rung of ladder) {
116
- const plan = buildPlan(rung, scrollCells, false);
117
- if (plan.cells <= budget)
118
- return plan;
119
- }
120
- // L4 IS UNCONDITIONAL: `^G` is the only entry in this table that may never be
121
- // dropped - it is how a user who hid the status detail fields gets them back.
122
- // A header narrower than the last rung still renders; the LEFT column is what
123
- // shrinks (to zero), and `overflow="hidden"` keeps that from overflowing.
124
- return buildPlan(ladder[ladder.length - 1] ?? [], scrollCells, false);
66
+ const overlayOpen = input.panel !== 'none';
67
+ const chip = scrollChip(scrolledLines, overlayOpen);
68
+ const body = HEADER_HINTS[input.panel] ?? HEADER_HINTS.none;
69
+ const text = chip.length > 0 ? `${chip}${body}` : body;
70
+ return { text, cells: text.length, scrollCells: chip.length };
125
71
  }
126
72
  /**
127
- * The rungs, widest first. Built per call because the widths are text-derived.
73
+ * Whether the row can hold the hint column without squeezing the left half below
74
+ * `minLeftCells`.
128
75
  *
129
- * THE EXPANDED ORDER `branch -> index -> kit -> detail` IS A REGRESSION
130
- * CONSTRAINT, NOT A PREFERENCE (RV-P1-2). With `kit` AFTER `index`, rungs E2,
131
- * E4, E5 and E6 are exactly today's four expanded rungs, so every existing
132
- * width assertion for 48-78 columns keeps its old answer and `^I` never
133
- * disappears from a row where it used to fit. Putting `^K` first would have cost
134
- * `^I` everything from 64 to 78 columns - a permanent loss, paid for a
135
- * low-frequency key.
76
+ * A FORMULA, NOT A TABLE OF MAGIC NUMBERS: `columns - minLeftCells -
77
+ * separatorCells >= scrollCells + bodyCells`. Substituting the three states gives
78
+ * 32 / 34 / 35 columns with no chip, plus up to 8 more for a saturated `^9999+ `.
79
+ * v1 claimed "any width >= 35", which forgot the chip entirely.
136
80
  */
137
- function ladderFor(statusExpanded, kitHint, promptsHint) {
138
- const branch = {
139
- id: 'branch',
140
- text: statusExpanded ? BRANCH_CLAUSE.expanded : BRANCH_CLAUSE.collapsed,
141
- };
142
- const index = {
143
- id: 'index',
144
- text: statusExpanded ? INDEX_CLAUSE.expanded : INDEX_CLAUSE.collapsed,
145
- };
146
- const detail = {
147
- id: 'detail',
148
- text: statusExpanded ? DETAIL_CLAUSE.expanded : DETAIL_CLAUSE.collapsed,
149
- };
150
- const branchKey = { id: 'branch', text: BRANCH_KEY_ONLY };
151
- const prompts = { id: 'prompts', text: PROMPTS_CLAUSE };
152
- const kit = { id: 'kit', text: KIT_CLAUSE.expanded };
153
- // THE LADDERS ARE WRITTEN WITH EVERY CLAUSE IN PLACE AND THEN FILTERED, rather
154
- // than branched per width. Two independently written tables drift: the day one of
155
- // them gains a rung, the `prompts.hint=false` / `kitHint=false` degradations stop
156
- // being "the same row minus that clause" and nothing would notice. Filtering a
157
- // single table makes that property structural.
158
- const keep = (clause) => (promptsHint || clause.id !== 'prompts') && (kitHint || clause.id !== 'kit');
159
- // COLLAPSED (six rungs). `^P prompts` sits between the branch and the index, and
160
- // the third rung is where `^L` loses its label in favour of `^P` - see the note
161
- // on `PROMPTS_CLAUSE` above for the revision of `RV-P1-2` this encodes.
162
- const collapsed = [
163
- [branch, prompts, index, detail],
164
- [branch, prompts, detail],
165
- [branchKey, prompts, detail],
166
- [branch, detail],
167
- [branchKey, detail],
168
- [detail],
169
- ];
170
- // EXPANDED (seven rungs). `^P` appears in the WIDEST rung only, and AFTER `^K`,
171
- // so E2..E7 - the rungs that decide what a 34..90 column terminal shows - are
172
- // byte-identical to the pre-feature answers.
173
- const expanded = [
174
- [branch, index, kit, prompts, detail],
175
- [branch, index, kit, detail],
176
- [branch, index, detail],
177
- [branch, kit, detail],
178
- [branch, detail],
179
- [branchKey, detail],
180
- [detail],
181
- ];
182
- return (statusExpanded ? expanded : collapsed).map((rung) => rung.filter(keep));
81
+ export function headerHintFits(columns, panel) {
82
+ const cols = normalizeDimension(columns, 80);
83
+ const plan = planHeaderHints({ columns: cols, panel, scrolledLines: 0 });
84
+ return cols - HEADER_HINT_LIMITS.minLeftCells - HEADER_HINT_LIMITS.separatorCells
85
+ >= plan.cells;
183
86
  }
184
- /**
185
- * Compose the final string. The chip carries its OWN trailing spacing and is the
186
- * only source of it (R-8): one space while an overlay is open, two otherwise -
187
- * copied byte for byte from the expression the header already used.
188
- */
189
- function buildPlan(clauses, scrollChipText, detailsOnly) {
190
- const body = clauses.map((clause) => clause.text).join(' '.repeat(HEADER_HINT_LIMITS.gapCells));
191
- const text = scrollChipText.length > 0 ? `${scrollChipText}${body}` : body;
192
- return {
193
- clauses,
194
- text,
195
- cells: text.length,
196
- scrollCells: scrollChipText.length,
197
- detailsOnly,
198
- };
87
+ /** The narrowest width at which each state fits with no scroll chip. */
88
+ export function headerHintThreshold(panel) {
89
+ return HEADER_HINTS[panel].length
90
+ + HEADER_HINT_LIMITS.minLeftCells
91
+ + HEADER_HINT_LIMITS.separatorCells;
199
92
  }
200
93
  /**
201
94
  * `^<n>` plus its trailing spacing, or `''`.
202
95
  *
203
96
  * THE SPACING IS PART OF THE CHIP, not something `Header` adds afterwards: with two
204
97
  * sources of spacing a reviewer has to read two files to answer "how wide is the
205
- * chip", and the overlay's single-space case lived in the JSX as a ternary nobody
206
- * would think to check.
98
+ * chip". One space while an overlay is open, two otherwise - copied byte for byte
99
+ * from the expression the header already used.
207
100
  */
208
101
  function scrollChip(scrolledLines, overlayOpen) {
209
102
  if (scrolledLines <= 0)
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The header's LEFT half budget (tui-shortcut-drawer-and-task-summary §4.3).
3
+ *
4
+ * ASCII ONLY: `src/ui/**` is inside the glyph scanner's scope.
5
+ *
6
+ * WHY A PLANNER (the same argument `header-hints.ts` makes one file over): the
7
+ * left half used to be a `<Text wrap="truncate">` over four clauses with no
8
+ * clause-level arithmetic anywhere, so the only thing that ever decided what got
9
+ * cut was the terminal. Adding a fifth clause - the task summary - to that shape
10
+ * produces a header where the summary silently eats the directory name on an
11
+ * 80-column terminal and NO ASSERTION WOULD SEE IT, because losing trailing
12
+ * characters is invisible to a snapshot test.
13
+ *
14
+ * THE PRIORITY ORDER IS THE DESIGN: brand, then `provider:model`, then cwd, then
15
+ * the summary, with the key symbol reserved up front. The summary is the only
16
+ * clause that is allowed to disappear entirely - it is the newest and the least
17
+ * load-bearing (D-2) - and it is dropped rather than rendered as `...`, because a
18
+ * cell holding three dots spends eight columns to say nothing.
19
+ */
20
+ export declare const HEADER_LEFT_LIMITS: {
21
+ /** The `AragonAgent` word mark, the same 13 `header-hints.ts` documents. */
22
+ readonly brandCells: 13;
23
+ /** Blank cells between two left clauses. */
24
+ readonly gapCells: 2;
25
+ /** The has-key symbol (1 cell) plus its gap, always reserved. */
26
+ readonly keyCells: 3;
27
+ /** Below this the model clause is dropped rather than rendered as `a...`. */
28
+ readonly modelMinCells: 8;
29
+ /** Below this the cwd clause is dropped. */
30
+ readonly cwdMinCells: 8;
31
+ /** Below this the summary is dropped instead of rendered as `...`. */
32
+ readonly summaryMinCells: 8;
33
+ /**
34
+ * The separator that belongs to the SUMMARY rather than to the general gap:
35
+ * two spaces, the mid dot and one space. It is reserved together with the label
36
+ * so a summary can never be drawn without its dot, or the dot without it.
37
+ */
38
+ readonly summaryGapCells: 4;
39
+ };
40
+ export interface HeaderLeftPlan {
41
+ text: {
42
+ model: string;
43
+ cwd: string;
44
+ summary: string;
45
+ };
46
+ cells: {
47
+ model: number;
48
+ cwd: number;
49
+ summary: number;
50
+ };
51
+ /**
52
+ * Why the summary cell is empty. `pending` is not a reason: a pending summary
53
+ * draws nothing at all, so the plan is indistinguishable from `empty` - which
54
+ * is exactly what removed the v1 layout jitter on the first submit.
55
+ */
56
+ summaryReason: 'ok' | 'empty' | 'narrow' | 'disabled';
57
+ }
58
+ export interface HeaderLeftInput {
59
+ /** Cells the header gives the left half (`columns - hintCells - separatorCells`). */
60
+ leftCells: number;
61
+ provider: string;
62
+ model: string;
63
+ cwd: string;
64
+ hasKey: boolean;
65
+ /** `null` while pending, empty or disabled - the planner never sees a placeholder. */
66
+ summary: {
67
+ text: string;
68
+ tone: 'model' | 'fallback';
69
+ } | null;
70
+ summaryEnabled: boolean;
71
+ ellipsis: string;
72
+ }
73
+ /** `provider:model`, the clause `Header` prints after the word mark. */
74
+ export declare function modelClause(provider: string, model: string): string;
75
+ /** The directory name, falling back to the whole path for a bare root. */
76
+ export declare function cwdClause(cwd: string, basename: (path: string) => string): string;
77
+ /**
78
+ * Allocate the left half.
79
+ *
80
+ * THE ORDER OF THE FOUR STEPS IS THE PRIORITY (D-2):
81
+ * 1. reserve the word mark, its gap and the key symbol - they are never cut;
82
+ * 2. `provider:model`, down to `modelMinCells`, then dropped;
83
+ * 3. cwd, down to `cwdMinCells`, then dropped;
84
+ * 4. the summary, capped at `TASK_SUMMARY_LIMITS.maxCells`, dropped below
85
+ * `summaryMinCells` and reported as `narrow`.
86
+ *
87
+ * THE LADDER IS EXPLICIT RATHER THAN GREEDY, and that is a correction to v2's
88
+ * algorithm, which funded the summary out of the reserved budget and could therefore
89
+ * show a label on a row where the model name had already been dropped. Four
90
+ * presence sets are tried from most to least complete; within a set the LAST clause
91
+ * gives way first, each only down to its own minimum. The first set that fits wins.
92
+ *
93
+ * Every returned text already MEASURES its reported width, so `Header` renders
94
+ * cells rather than hoping for them.
95
+ */
96
+ export declare function planHeaderLeft(input: HeaderLeftInput): HeaderLeftPlan;
97
+ /**
98
+ * Cells the plan actually spends, for the row invariant:
99
+ * `brand + gap + model + gap + cwd + gap + summary + key <= leftCells`.
100
+ *
101
+ * Exported so `header-left.test.ts` can assert the inequality against the SAME
102
+ * arithmetic the renderer uses instead of re-deriving it in the test (which is how
103
+ * the two drift apart).
104
+ */
105
+ export declare function planHeaderLeftCells(plan: HeaderLeftPlan): number;
106
+ /**
107
+ * The fixed cells a plan needs before any clause can appear: the word mark plus the
108
+ * key symbol and its gap.
109
+ *
110
+ * Below this the row does not "spend too much", it simply cannot show a clause - the
111
+ * renderer truncates the overflow and `Header`'s own `wrap="truncate"` keeps that from
112
+ * spilling. Stating it as a constant keeps the invariant in the tests honest:
113
+ * `planHeaderLeftCells(plan) <= max(leftCells, HEADER_LEFT_BASELINE)`.
114
+ */
115
+ export declare const HEADER_LEFT_BASELINE: number;