@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.
- package/CHANGELOG.md +2523 -2326
- package/README.md +3614 -3173
- package/dist/agent/controller.d.ts +142 -1
- package/dist/agent/controller.js +171 -3
- package/dist/agent/reducer.d.ts +41 -1
- package/dist/agent/reducer.js +25 -0
- package/dist/agent/system-prompt.d.ts +14 -0
- package/dist/agent/system-prompt.js +5 -0
- package/dist/cli.js +85 -27
- package/dist/commands/builtins.js +13 -1
- package/dist/commands/keys-command.d.ts +23 -0
- package/dist/commands/keys-command.js +38 -0
- package/dist/commands/registry.d.ts +39 -0
- package/dist/commands/task-command.d.ts +39 -0
- package/dist/commands/task-command.js +102 -0
- package/dist/config/cli-commands.d.ts +23 -0
- package/dist/config/cli-commands.js +36 -0
- package/dist/config/env.js +17 -4
- package/dist/config/load.js +6 -1
- package/dist/config/schema.d.ts +43 -4
- package/dist/config/schema.js +31 -5
- package/dist/config/store.js +17 -2
- package/dist/diagnostics/doctor.d.ts +9 -0
- package/dist/diagnostics/doctor.js +34 -0
- package/dist/diagnostics/info.d.ts +71 -0
- package/dist/diagnostics/info.js +91 -5
- package/dist/exec/bridge/caller-tools.d.ts +96 -0
- package/dist/exec/bridge/caller-tools.js +213 -0
- package/dist/exec/bridge/decisions.d.ts +78 -0
- package/dist/exec/bridge/decisions.js +116 -0
- package/dist/exec/bridge/human-answers.d.ts +38 -0
- package/dist/exec/bridge/human-answers.js +164 -0
- package/dist/exec/bridge/limits.d.ts +68 -0
- package/dist/exec/bridge/limits.js +68 -0
- package/dist/exec/bridge/ndjson.d.ts +17 -0
- package/dist/exec/bridge/ndjson.js +83 -0
- package/dist/exec/bridge/pending.d.ts +70 -0
- package/dist/exec/bridge/pending.js +132 -0
- package/dist/exec/bridge/protocol.d.ts +346 -0
- package/dist/exec/bridge/protocol.js +256 -0
- package/dist/exec/bridge/server.d.ts +142 -0
- package/dist/exec/bridge/server.js +502 -0
- package/dist/exec/bridge/source.d.ts +58 -0
- package/dist/exec/bridge/source.js +145 -0
- package/dist/exec/bridge/writer.d.ts +26 -0
- package/dist/exec/bridge/writer.js +156 -0
- package/dist/exec/cli-commands.js +30 -2
- package/dist/exec/emitter.d.ts +16 -2
- package/dist/exec/emitter.js +26 -6
- package/dist/exec/events.d.ts +465 -10
- package/dist/exec/events.js +47 -1
- package/dist/exec/hooks/dispatch.d.ts +76 -0
- package/dist/exec/hooks/dispatch.js +169 -0
- package/dist/exec/hooks/index.d.ts +83 -0
- package/dist/exec/hooks/index.js +205 -0
- package/dist/exec/hooks/limits.d.ts +56 -0
- package/dist/exec/hooks/limits.js +49 -0
- package/dist/exec/hooks/registry.d.ts +49 -0
- package/dist/exec/hooks/registry.js +91 -0
- package/dist/exec/hooks/tool-wrapper.d.ts +94 -0
- package/dist/exec/hooks/tool-wrapper.js +154 -0
- package/dist/exec/hooks/turn.d.ts +61 -0
- package/dist/exec/hooks/turn.js +59 -0
- package/dist/exec/hooks/validate.d.ts +85 -0
- package/dist/exec/hooks/validate.js +310 -0
- package/dist/exec/index.d.ts +130 -4
- package/dist/exec/index.js +501 -10
- package/dist/exec/options.d.ts +124 -2
- package/dist/exec/options.js +413 -6
- package/dist/exec/permission.d.ts +9 -1
- package/dist/exec/permission.js +7 -1
- package/dist/exec/request/limits.d.ts +51 -0
- package/dist/exec/request/limits.js +49 -0
- package/dist/exec/request/scope.d.ts +106 -0
- package/dist/exec/request/scope.js +149 -0
- package/dist/exec/result/limits.d.ts +74 -0
- package/dist/exec/result/limits.js +72 -0
- package/dist/exec/result/prompt.d.ts +28 -0
- package/dist/exec/result/prompt.js +74 -0
- package/dist/exec/result/schema.d.ts +99 -0
- package/dist/exec/result/schema.js +464 -0
- package/dist/exec/result/sink.d.ts +56 -0
- package/dist/exec/result/sink.js +50 -0
- package/dist/exec/result/tool.d.ts +37 -0
- package/dist/exec/result/tool.js +118 -0
- package/dist/exec/runner.d.ts +395 -1
- package/dist/exec/runner.js +860 -8
- package/dist/exec/threads/aggregate.d.ts +34 -0
- package/dist/exec/threads/aggregate.js +30 -0
- package/dist/exec/threads/capacity.d.ts +23 -0
- package/dist/exec/threads/capacity.js +38 -0
- package/dist/exec/threads/dispatch-source.d.ts +60 -0
- package/dist/exec/threads/dispatch-source.js +106 -0
- package/dist/exec/threads/execute-threads.d.ts +53 -0
- package/dist/exec/threads/execute-threads.js +234 -0
- package/dist/exec/threads/gate.d.ts +42 -0
- package/dist/exec/threads/gate.js +81 -0
- package/dist/exec/threads/identity.d.ts +36 -0
- package/dist/exec/threads/identity.js +67 -0
- package/dist/exec/threads/interrupt.d.ts +29 -0
- package/dist/exec/threads/interrupt.js +79 -0
- package/dist/exec/threads/limits.d.ts +58 -0
- package/dist/exec/threads/limits.js +35 -0
- package/dist/exec/threads/outcome.d.ts +47 -0
- package/dist/exec/threads/outcome.js +26 -0
- package/dist/exec/threads/registry.d.ts +81 -0
- package/dist/exec/threads/registry.js +427 -0
- package/dist/exec/threads/runtime.d.ts +107 -0
- package/dist/exec/threads/runtime.js +523 -0
- package/dist/exec/threads/thread-source.d.ts +20 -0
- package/dist/exec/threads/thread-source.js +134 -0
- package/dist/fast/limits.d.ts +54 -0
- package/dist/fast/limits.js +54 -0
- package/dist/fast/summary-call.d.ts +72 -0
- package/dist/fast/summary-call.js +133 -0
- package/dist/fast/summary-prompt.d.ts +106 -0
- package/dist/fast/summary-prompt.js +320 -0
- package/dist/fast/task-summary-types.d.ts +66 -0
- package/dist/fast/task-summary-types.js +17 -0
- package/dist/logging/logger.d.ts +1 -1
- package/dist/proc/limits.d.ts +7 -0
- package/dist/proc/limits.js +7 -0
- package/dist/proc/output-streams.d.ts +50 -0
- package/dist/proc/output-streams.js +54 -0
- package/dist/team/runtime.d.ts +9 -0
- package/dist/team/runtime.js +8 -0
- package/dist/team/subagent.d.ts +15 -0
- package/dist/team/subagent.js +45 -1
- package/dist/tools/bash-tool.d.ts +10 -0
- package/dist/tools/bash-tool.js +71 -6
- package/dist/tools/index.d.ts +93 -1
- package/dist/tools/index.js +112 -3
- package/dist/tools/tool-gate.d.ts +100 -0
- package/dist/tools/tool-gate.js +87 -0
- package/dist/ui/App.js +224 -11
- package/dist/ui/Header.d.ts +28 -16
- package/dist/ui/Header.js +21 -19
- package/dist/ui/interaction-hints.js +6 -0
- package/dist/ui/layout/OverlayFrame.d.ts +18 -0
- package/dist/ui/layout/OverlayFrame.js +19 -4
- package/dist/ui/layout/header-hints.d.ts +55 -62
- package/dist/ui/layout/header-hints.js +59 -166
- package/dist/ui/layout/header-left.d.ts +115 -0
- package/dist/ui/layout/header-left.js +206 -0
- package/dist/ui/layout/shortcut-panel-layout.d.ts +191 -0
- package/dist/ui/layout/shortcut-panel-layout.js +321 -0
- package/dist/ui/layout/status-detail-layout.js +5 -1
- package/dist/ui/layout/task-summary-cell.d.ts +61 -0
- package/dist/ui/layout/task-summary-cell.js +65 -0
- package/dist/ui/overlays/HelpOverlay.d.ts +12 -0
- package/dist/ui/overlays/HelpOverlay.js +29 -2
- package/dist/ui/overlays/ShortcutPanel.d.ts +39 -0
- package/dist/ui/overlays/ShortcutPanel.js +106 -0
- package/dist/ui/shortcuts.d.ts +81 -0
- package/dist/ui/shortcuts.js +221 -0
- package/package.json +2 -2
|
@@ -0,0 +1,206 @@
|
|
|
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
|
+
import { cellWidth, fitCells } from './cells.js';
|
|
21
|
+
import { TASK_SUMMARY_LIMITS } from '../../fast/limits.js';
|
|
22
|
+
export const HEADER_LEFT_LIMITS = {
|
|
23
|
+
/** The `AragonAgent` word mark, the same 13 `header-hints.ts` documents. */
|
|
24
|
+
brandCells: 13,
|
|
25
|
+
/** Blank cells between two left clauses. */
|
|
26
|
+
gapCells: 2,
|
|
27
|
+
/** The has-key symbol (1 cell) plus its gap, always reserved. */
|
|
28
|
+
keyCells: 3,
|
|
29
|
+
/** Below this the model clause is dropped rather than rendered as `a...`. */
|
|
30
|
+
modelMinCells: 8,
|
|
31
|
+
/** Below this the cwd clause is dropped. */
|
|
32
|
+
cwdMinCells: 8,
|
|
33
|
+
/** Below this the summary is dropped instead of rendered as `...`. */
|
|
34
|
+
summaryMinCells: 8,
|
|
35
|
+
/**
|
|
36
|
+
* The separator that belongs to the SUMMARY rather than to the general gap:
|
|
37
|
+
* two spaces, the mid dot and one space. It is reserved together with the label
|
|
38
|
+
* so a summary can never be drawn without its dot, or the dot without it.
|
|
39
|
+
*/
|
|
40
|
+
summaryGapCells: 4,
|
|
41
|
+
};
|
|
42
|
+
/** `provider:model`, the clause `Header` prints after the word mark. */
|
|
43
|
+
export function modelClause(provider, model) {
|
|
44
|
+
return `${provider}:${model}`;
|
|
45
|
+
}
|
|
46
|
+
/** The directory name, falling back to the whole path for a bare root. */
|
|
47
|
+
export function cwdClause(cwd, basename) {
|
|
48
|
+
return basename(cwd) || cwd;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Allocate the left half.
|
|
52
|
+
*
|
|
53
|
+
* THE ORDER OF THE FOUR STEPS IS THE PRIORITY (D-2):
|
|
54
|
+
* 1. reserve the word mark, its gap and the key symbol - they are never cut;
|
|
55
|
+
* 2. `provider:model`, down to `modelMinCells`, then dropped;
|
|
56
|
+
* 3. cwd, down to `cwdMinCells`, then dropped;
|
|
57
|
+
* 4. the summary, capped at `TASK_SUMMARY_LIMITS.maxCells`, dropped below
|
|
58
|
+
* `summaryMinCells` and reported as `narrow`.
|
|
59
|
+
*
|
|
60
|
+
* THE LADDER IS EXPLICIT RATHER THAN GREEDY, and that is a correction to v2's
|
|
61
|
+
* algorithm, which funded the summary out of the reserved budget and could therefore
|
|
62
|
+
* show a label on a row where the model name had already been dropped. Four
|
|
63
|
+
* presence sets are tried from most to least complete; within a set the LAST clause
|
|
64
|
+
* gives way first, each only down to its own minimum. The first set that fits wins.
|
|
65
|
+
*
|
|
66
|
+
* Every returned text already MEASURES its reported width, so `Header` renders
|
|
67
|
+
* cells rather than hoping for them.
|
|
68
|
+
*/
|
|
69
|
+
export function planHeaderLeft(input) {
|
|
70
|
+
const leftCells = Number.isFinite(input.leftCells) ? Math.max(0, Math.floor(input.leftCells)) : 0;
|
|
71
|
+
const empty = {
|
|
72
|
+
text: { model: '', cwd: '', summary: '' },
|
|
73
|
+
cells: { model: 0, cwd: 0, summary: 0 },
|
|
74
|
+
summaryReason: input.summaryEnabled ? 'empty' : 'disabled',
|
|
75
|
+
};
|
|
76
|
+
const gap = HEADER_LEFT_LIMITS.gapCells;
|
|
77
|
+
// Everything the planner never touches, taken off the top so no later step can
|
|
78
|
+
// borrow from it.
|
|
79
|
+
// `keyCells` already contains the gap before the symbol, so this is the whole fixed
|
|
80
|
+
// cost and every clause below pays its own gap.
|
|
81
|
+
const budget = leftCells - HEADER_LEFT_LIMITS.brandCells - HEADER_LEFT_LIMITS.keyCells;
|
|
82
|
+
if (budget <= 0) {
|
|
83
|
+
return {
|
|
84
|
+
...empty,
|
|
85
|
+
summaryReason: input.summaryEnabled
|
|
86
|
+
? (input.summary === null ? 'empty' : 'narrow')
|
|
87
|
+
: 'disabled',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const model = modelClause(input.provider, input.model);
|
|
91
|
+
const summaryText = input.summary?.text ?? '';
|
|
92
|
+
const wantsSummary = input.summaryEnabled && input.summary !== null;
|
|
93
|
+
/** Desired width and the floor this clause may be cut to. */
|
|
94
|
+
const clause = (text, floor) => ({
|
|
95
|
+
want: cellWidth(text),
|
|
96
|
+
floor: Math.min(cellWidth(text), floor),
|
|
97
|
+
});
|
|
98
|
+
const modelWanted = clause(model, HEADER_LEFT_LIMITS.modelMinCells);
|
|
99
|
+
const cwdWanted = clause(input.cwd, HEADER_LEFT_LIMITS.cwdMinCells);
|
|
100
|
+
const summaryWanted = {
|
|
101
|
+
want: Math.min(cellWidth(summaryText), TASK_SUMMARY_LIMITS.maxCells),
|
|
102
|
+
// A label is never shown below its minimum: a three-cell label is a fragment,
|
|
103
|
+
// not a label, and the cell is dropped instead.
|
|
104
|
+
floor: HEADER_LEFT_LIMITS.summaryMinCells,
|
|
105
|
+
};
|
|
106
|
+
const sets = [
|
|
107
|
+
['model', 'cwd', ...(wantsSummary ? ['summary'] : [])],
|
|
108
|
+
['model', 'cwd'],
|
|
109
|
+
['model'],
|
|
110
|
+
[],
|
|
111
|
+
];
|
|
112
|
+
const wanted = { model: modelWanted, cwd: cwdWanted, summary: summaryWanted };
|
|
113
|
+
for (const set of sets) {
|
|
114
|
+
const assigned = fitClauses(set, wanted, budget, gap);
|
|
115
|
+
if (assigned === null)
|
|
116
|
+
continue;
|
|
117
|
+
const modelCells = assigned.model ?? 0;
|
|
118
|
+
const cwdCells = assigned.cwd ?? 0;
|
|
119
|
+
const summaryCells = assigned.summary ?? 0;
|
|
120
|
+
let summaryReason = 'empty';
|
|
121
|
+
if (!input.summaryEnabled)
|
|
122
|
+
summaryReason = 'disabled';
|
|
123
|
+
else if (input.summary === null)
|
|
124
|
+
summaryReason = 'empty';
|
|
125
|
+
else if (summaryCells === 0)
|
|
126
|
+
summaryReason = 'narrow';
|
|
127
|
+
else
|
|
128
|
+
summaryReason = 'ok';
|
|
129
|
+
return {
|
|
130
|
+
text: {
|
|
131
|
+
model: modelCells > 0 ? fitCells(model, modelCells, input.ellipsis) : '',
|
|
132
|
+
cwd: cwdCells > 0 ? fitCells(input.cwd, cwdCells, input.ellipsis) : '',
|
|
133
|
+
summary: summaryCells > 0 ? fitCells(summaryText, summaryCells, input.ellipsis) : '',
|
|
134
|
+
},
|
|
135
|
+
cells: { model: modelCells, cwd: cwdCells, summary: summaryCells },
|
|
136
|
+
summaryReason,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return empty;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Fit one presence set into `budget`, cutting from the LAST clause inward.
|
|
143
|
+
*
|
|
144
|
+
* Returns `null` when the set cannot fit even at every clause's floor, which is the
|
|
145
|
+
* signal to try the next (smaller) set. `null` rather than a partial result because
|
|
146
|
+
* "the summary was dropped" and "the summary does not fit" must be the same fact to
|
|
147
|
+
* the caller, and only the set composition may express it.
|
|
148
|
+
*/
|
|
149
|
+
function fitClauses(set, wanted, budget, gap) {
|
|
150
|
+
const widths = { model: 0, cwd: 0, summary: 0 };
|
|
151
|
+
let cost = 0;
|
|
152
|
+
for (const key of set) {
|
|
153
|
+
widths[key] = wanted[key].want;
|
|
154
|
+
// EVERY CLAUSE PAYS ITS OWN SEPARATOR, including the first: the row renders
|
|
155
|
+
// `word mark, gap, clause, gap, clause, gap, key`, so a one-clause plan still
|
|
156
|
+
// spends a gap on each side of that clause.
|
|
157
|
+
cost += (key === 'summary' ? HEADER_LEFT_LIMITS.summaryGapCells : gap)
|
|
158
|
+
+ wanted[key].want;
|
|
159
|
+
}
|
|
160
|
+
let excess = cost - budget;
|
|
161
|
+
for (let index = set.length - 1; index >= 0 && excess > 0; index -= 1) {
|
|
162
|
+
const key = set[index];
|
|
163
|
+
const room = widths[key] - wanted[key].floor;
|
|
164
|
+
if (room <= 0)
|
|
165
|
+
continue;
|
|
166
|
+
const cut = Math.min(room, excess);
|
|
167
|
+
widths[key] -= cut;
|
|
168
|
+
excess -= cut;
|
|
169
|
+
}
|
|
170
|
+
return excess > 0 ? null : widths;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Cells the plan actually spends, for the row invariant:
|
|
174
|
+
* `brand + gap + model + gap + cwd + gap + summary + key <= leftCells`.
|
|
175
|
+
*
|
|
176
|
+
* Exported so `header-left.test.ts` can assert the inequality against the SAME
|
|
177
|
+
* arithmetic the renderer uses instead of re-deriving it in the test (which is how
|
|
178
|
+
* the two drift apart).
|
|
179
|
+
*/
|
|
180
|
+
export function planHeaderLeftCells(plan) {
|
|
181
|
+
const { gapCells, brandCells, keyCells, summaryGapCells } = HEADER_LEFT_LIMITS;
|
|
182
|
+
// The word mark and the key symbol are always drawn; every clause pays its own gap.
|
|
183
|
+
// The gap before the key is INSIDE `keyCells`, so it is counted once here and never
|
|
184
|
+
// again after a clause - the double count that made a one-clause plan read two cells
|
|
185
|
+
// wider than it renders.
|
|
186
|
+
let cells = brandCells + keyCells;
|
|
187
|
+
if (plan.cells.model > 0)
|
|
188
|
+
cells += gapCells + plan.cells.model;
|
|
189
|
+
if (plan.cells.cwd > 0)
|
|
190
|
+
cells += gapCells + plan.cells.cwd;
|
|
191
|
+
// The summary's separator carries the dot, so it is wider than the general gap.
|
|
192
|
+
if (plan.cells.summary > 0)
|
|
193
|
+
cells += summaryGapCells + plan.cells.summary;
|
|
194
|
+
return cells;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* The fixed cells a plan needs before any clause can appear: the word mark plus the
|
|
198
|
+
* key symbol and its gap.
|
|
199
|
+
*
|
|
200
|
+
* Below this the row does not "spend too much", it simply cannot show a clause - the
|
|
201
|
+
* renderer truncates the overflow and `Header`'s own `wrap="truncate"` keeps that from
|
|
202
|
+
* spilling. Stating it as a constant keeps the invariant in the tests honest:
|
|
203
|
+
* `planHeaderLeftCells(plan) <= max(leftCells, HEADER_LEFT_BASELINE)`.
|
|
204
|
+
*/
|
|
205
|
+
export const HEADER_LEFT_BASELINE = HEADER_LEFT_LIMITS.brandCells
|
|
206
|
+
+ HEADER_LEFT_LIMITS.keyCells;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shortcut drawer's geometry and row layout
|
|
3
|
+
* (tui-shortcut-drawer-and-task-summary §4.5).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: `src/ui/**` is inside the glyph scanner's scope, so every symbol
|
|
6
|
+
* this module emits arrives as a `glyphs` field and is never spelled here.
|
|
7
|
+
*
|
|
8
|
+
* PURE AND TOTAL. No Ink, no clock, no terminal: the drawer component renders
|
|
9
|
+
* what this returns, `OverlayFrame` slices the window this function DERIVED, and
|
|
10
|
+
* `shortcut-panel-layout.test.ts` pins the arithmetic directly. That split is the
|
|
11
|
+
* whole defence against P0-1, which was not a rendering bug: v1 hand-fixed the key
|
|
12
|
+
* column at 21 cells, which left six cells for the keyword at 80 columns, so every
|
|
13
|
+
* keyword was truncated to `promp...` while a "does not overflow" assertion stayed
|
|
14
|
+
* green. The column is therefore DERIVED from the table, and a keyword that does
|
|
15
|
+
* not fit makes its row STACK rather than be cut.
|
|
16
|
+
*
|
|
17
|
+
* THERE IS EXACTLY ONE WINDOW AUTHORITY: `cursor`. v1 took both `cursor` and
|
|
18
|
+
* `scrollOffset`, which meant paging could leave the selected row - and the detail
|
|
19
|
+
* line that belongs to it - off screen. Paging now moves the CURSOR by a page and
|
|
20
|
+
* the window is the smallest one containing it.
|
|
21
|
+
*/
|
|
22
|
+
import { type OverlayWindow } from './overlay-window.js';
|
|
23
|
+
import { type ShortcutEntry } from '../shortcuts.js';
|
|
24
|
+
export declare const SHORTCUT_PANEL_LIMITS: {
|
|
25
|
+
/** Below this the drawer is full width: a 30-cell bar on a 40-cell screen is noise. */
|
|
26
|
+
readonly minSplitCols: 64;
|
|
27
|
+
/** Fraction of the terminal the drawer takes when it IS a drawer. */
|
|
28
|
+
readonly widthFraction: 0.34;
|
|
29
|
+
readonly minWidth: 30;
|
|
30
|
+
readonly maxWidth: 46;
|
|
31
|
+
/** Rail (1 cell) + `paddingLeft` (1 cell) that `OverlayFrame` spends below 48 columns. */
|
|
32
|
+
readonly railCells: 2;
|
|
33
|
+
/** Border (2 cells) + `paddingX` (2 cells) that `OverlayFrame` spends at or above 48. */
|
|
34
|
+
readonly boxCells: 4;
|
|
35
|
+
/**
|
|
36
|
+
* The key column is DERIVED: `min(longestKeys, ceil(listCells * 0.55))`.
|
|
37
|
+
*
|
|
38
|
+
* A hard 21 is what turned `prompt library` into `promp...` at 80 columns. The
|
|
39
|
+
* share is a ceiling, not a target: a table of short keys gets a narrow key
|
|
40
|
+
* column and the leftover width goes to the keyword.
|
|
41
|
+
*/
|
|
42
|
+
readonly keyColumnShare: 0.55;
|
|
43
|
+
/** The keyword column must be at least this wide, or the row stacks. */
|
|
44
|
+
readonly keywordMinCells: 8;
|
|
45
|
+
/** Hard ceiling for a keyword string; a longer one is a spec bug, not a width problem. */
|
|
46
|
+
readonly keywordMaxCells: 10;
|
|
47
|
+
/** Gap between the key column and the keyword column. */
|
|
48
|
+
readonly keyGapCells: 1;
|
|
49
|
+
/** Indent for the selected row's detail lines, and for a stacked keyword. */
|
|
50
|
+
readonly detailIndent: 2;
|
|
51
|
+
/** Title + footer rows are chrome and are spent out of the row budget. */
|
|
52
|
+
readonly chromeRows: 2;
|
|
53
|
+
/** The task section is capped so the key list always gets the majority of the panel. */
|
|
54
|
+
readonly taskMaxRows: 2;
|
|
55
|
+
/**
|
|
56
|
+
* ABOVE THIS BODY HEIGHT THE GROUP HEADERS GET THEIR OWN ROWS.
|
|
57
|
+
*
|
|
58
|
+
* Below it they are dropped entirely - not compressed into `---- GROUP ----`
|
|
59
|
+
* separators, which was v2's rule and left FOUR entry rows visible on an
|
|
60
|
+
* 80x24 terminal (title + task + rule + five separators = 9 of 13 body rows).
|
|
61
|
+
* See `spec.md`'s `## 实施过程发现的方案缺陷` note 1: the ordering still runs
|
|
62
|
+
* COMPOSE -> RUN -> PANELS -> SESSION -> VIEW, and the requirement ("list every
|
|
63
|
+
* shortcut from top to bottom") is met by the entries themselves.
|
|
64
|
+
*/
|
|
65
|
+
readonly groupHeaderMinRows: 18;
|
|
66
|
+
/** The longest detail line the selected row may grow to. */
|
|
67
|
+
readonly detailMaxRows: 2;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* The drawer's width in columns.
|
|
71
|
+
*
|
|
72
|
+
* `cols < 64` is FULL WIDTH on purpose: at 40 columns a right-aligned 30-cell bar
|
|
73
|
+
* spends 10 of the user's columns on empty space. The clamp mirrors
|
|
74
|
+
* `rail.ts::todoRailWidth` (same shape, same reason).
|
|
75
|
+
*/
|
|
76
|
+
export declare function shortcutPanelWidth(cols: number): number;
|
|
77
|
+
/**
|
|
78
|
+
* Cells the drawer may draw on INSIDE its frame.
|
|
79
|
+
*
|
|
80
|
+
* THE FORM IS DECIDED BY THE TERMINAL, exactly as `OverlayFrame` decides it
|
|
81
|
+
* (`cols >= BORDERLESS_COLS`), so this function takes the same `cols` the frame
|
|
82
|
+
* receives. Passing the panel's own width instead would make a 46-cell drawer on
|
|
83
|
+
* a 200-column terminal fall back to the rail form for ever, because 46 < 48.
|
|
84
|
+
*/
|
|
85
|
+
export declare function shortcutPanelContentCells(cols: number): number;
|
|
86
|
+
/** One rendered row. `text` already measures at most the panel's content width. */
|
|
87
|
+
export interface ShortcutRow {
|
|
88
|
+
/** `entry` = key + keyword on one row; `stacked` = key alone, keyword below. */
|
|
89
|
+
kind: 'task' | 'rule' | 'group' | 'entry' | 'stacked' | 'keyword' | 'detail';
|
|
90
|
+
text: string;
|
|
91
|
+
/** Which entry a `detail` / `keyword` row belongs to; `null` for every other kind. */
|
|
92
|
+
entryId: string | null;
|
|
93
|
+
tone: 'accent' | 'primary' | 'muted' | 'dim';
|
|
94
|
+
}
|
|
95
|
+
/** The task section's inputs, already flattened by the caller. */
|
|
96
|
+
export interface ShortcutTaskSection {
|
|
97
|
+
/** The label, or `null` when there is none yet. */
|
|
98
|
+
text: string | null;
|
|
99
|
+
/** `model` / `fallback`, so the row can be coloured by provenance. */
|
|
100
|
+
tone: 'model' | 'fallback' | null;
|
|
101
|
+
/** Why it is a fallback, or `null`. */
|
|
102
|
+
reason: string | null;
|
|
103
|
+
/** `provider:model`, or `''`. */
|
|
104
|
+
by: string;
|
|
105
|
+
/** Age in ms, or `null` when unknown. */
|
|
106
|
+
ageMs: number | null;
|
|
107
|
+
/** `false` when `taskSummary.enabled` is off. */
|
|
108
|
+
enabled: boolean;
|
|
109
|
+
/** A call is in flight. */
|
|
110
|
+
pending: boolean;
|
|
111
|
+
}
|
|
112
|
+
export interface BuildShortcutRowsInput {
|
|
113
|
+
cols: number;
|
|
114
|
+
/** Body rows available INSIDE the frame (`OverlayFrame` already subtracted its chrome). */
|
|
115
|
+
rows: number;
|
|
116
|
+
/** Index into the FILTERED entry list. The one authority for the window. */
|
|
117
|
+
cursor: number;
|
|
118
|
+
/**
|
|
119
|
+
* THE ENTRIES ARRIVE FILTERED AND WITH THEIR AVAILABILITY, so this module takes
|
|
120
|
+
* neither a `query` nor a `context` for them (review fix RF-2). It used to accept
|
|
121
|
+
* both - and `detailsExpanded` as well - and read none of them, which is the exact
|
|
122
|
+
* "second source of truth that silently does nothing" its own 0-3 rule forbids: a
|
|
123
|
+
* caller could pass `query: 'git'` and get the unfiltered list with nothing
|
|
124
|
+
* failing.
|
|
125
|
+
*/
|
|
126
|
+
entries: readonly ShortcutEntry[];
|
|
127
|
+
task: ShortcutTaskSection;
|
|
128
|
+
/** Per-entry availability, so an unavailable row renders dim and explains itself. */
|
|
129
|
+
entryAvailable?: (entry: ShortcutEntry) => boolean;
|
|
130
|
+
/** The sentence appended to an unavailable row's detail line. */
|
|
131
|
+
unavailableReason?: string;
|
|
132
|
+
glyphs: {
|
|
133
|
+
ellipsis: string;
|
|
134
|
+
cursor: string;
|
|
135
|
+
hrule: string;
|
|
136
|
+
midDot: string;
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
export interface BuildShortcutRowsResult {
|
|
140
|
+
rows: ShortcutRow[];
|
|
141
|
+
window: OverlayWindow;
|
|
142
|
+
/** Row index of the selected entry; equal to `window.start + 0` at the top. */
|
|
143
|
+
cursorRow: number;
|
|
144
|
+
/** Total rows the panel holds, which is what the frame's position indicator counts. */
|
|
145
|
+
total: number;
|
|
146
|
+
/** The footer string; `OverlayFrame`'s `hint`, and chrome rather than a row. */
|
|
147
|
+
hint: string;
|
|
148
|
+
}
|
|
149
|
+
/** Column widths for one geometry. Exported so the tests can assert them directly. */
|
|
150
|
+
export interface ShortcutColumns {
|
|
151
|
+
/** The panel's own width. */
|
|
152
|
+
width: number;
|
|
153
|
+
/** Content cells inside the frame. */
|
|
154
|
+
available: number;
|
|
155
|
+
/** Cells left for a list row after the cursor column is spent. */
|
|
156
|
+
listCells: number;
|
|
157
|
+
keyCells: number;
|
|
158
|
+
keywordCells: number;
|
|
159
|
+
/** True when no entry fits the two-column form and every row stacks. */
|
|
160
|
+
stacked: boolean;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Derive the two columns (P0-1).
|
|
164
|
+
*
|
|
165
|
+
* `stacked` is a GLOBAL flag only when the keyword column is too narrow for any
|
|
166
|
+
* keyword at all; otherwise stacking is decided per row, so one long binding
|
|
167
|
+
* (`Shift+Up / Shift+Down`, 20 cells) does not cost the other thirty rows a line.
|
|
168
|
+
*/
|
|
169
|
+
export declare function shortcutColumns(cols: number, entries: readonly ShortcutEntry[], glyphs: {
|
|
170
|
+
cursor: string;
|
|
171
|
+
}): ShortcutColumns;
|
|
172
|
+
/**
|
|
173
|
+
* The footer, built by DROPPING clauses from the right (never by re-wording).
|
|
174
|
+
*
|
|
175
|
+
* `Esc close` is never dropped: it is the one thing a user must be able to find on
|
|
176
|
+
* a terminal too narrow to explain anything else. The rest go in a fixed order so
|
|
177
|
+
* the footer is stable across resizes instead of shuffling.
|
|
178
|
+
*/
|
|
179
|
+
export declare function shortcutFooter(cols: number, glyphs: {
|
|
180
|
+
midDot: string;
|
|
181
|
+
}, opts?: {
|
|
182
|
+
canRefresh: boolean;
|
|
183
|
+
}): string;
|
|
184
|
+
/**
|
|
185
|
+
* Build every row the drawer can show, and derive the window from the cursor.
|
|
186
|
+
*
|
|
187
|
+
* @param input.cols Terminal columns (the frame receives the same number).
|
|
188
|
+
* @param input.rows Body rows inside the frame - `overlayBodyRows(maxRows, cols)`.
|
|
189
|
+
* @param input.cursor Index into `input.entries`; clamped, never trusted.
|
|
190
|
+
*/
|
|
191
|
+
export declare function buildShortcutRows(input: BuildShortcutRowsInput): BuildShortcutRowsResult;
|