@aragon-agent/cli 0.6.44 → 0.6.45
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 +44 -0
- package/README.md +69 -2
- package/dist/agent/controller.d.ts +69 -0
- package/dist/agent/controller.js +224 -11
- package/dist/agent/governance-view.d.ts +40 -0
- package/dist/agent/governance-view.js +40 -0
- package/dist/agent/governance-wiring.d.ts +162 -0
- package/dist/agent/governance-wiring.js +470 -0
- package/dist/agent/plan-prompt.d.ts +7 -0
- package/dist/agent/plan-prompt.js +2 -1
- package/dist/agent/reducer.d.ts +33 -1
- package/dist/agent/reducer.js +45 -9
- package/dist/agent/system-prompt.d.ts +19 -0
- package/dist/agent/system-prompt.js +9 -1
- package/dist/cli.js +45 -1
- package/dist/commands/builtins.js +10 -4
- package/dist/commands/governance-command.d.ts +35 -0
- package/dist/commands/governance-command.js +292 -0
- package/dist/commands/governance-runs-table.d.ts +40 -0
- package/dist/commands/governance-runs-table.js +130 -0
- package/dist/commands/registry.d.ts +12 -0
- package/dist/config/app-paths.d.ts +10 -0
- package/dist/config/app-paths.js +12 -0
- package/dist/config/cli-commands.d.ts +18 -0
- package/dist/config/cli-commands.js +47 -1
- package/dist/config/env.d.ts +2 -1
- package/dist/config/env.js +10 -1
- package/dist/config/governance-config.d.ts +125 -0
- package/dist/config/governance-config.js +164 -0
- package/dist/config/governance-launch.d.ts +71 -0
- package/dist/config/governance-launch.js +58 -0
- package/dist/config/load.d.ts +15 -0
- package/dist/config/load.js +26 -1
- package/dist/config/model-profile-store.d.ts +4 -2
- package/dist/config/schema.d.ts +22 -0
- package/dist/config/schema.js +6 -0
- package/dist/config/store.js +14 -1
- package/dist/config/ui-state.d.ts +11 -0
- package/dist/config/ui-state.js +11 -0
- package/dist/diagnostics/doctor.js +20 -0
- package/dist/governance/card-normalize.d.ts +24 -0
- package/dist/governance/card-normalize.js +76 -0
- package/dist/governance/copy.d.ts +109 -0
- package/dist/governance/copy.js +223 -0
- package/dist/governance/engine/aftermath.d.ts +49 -0
- package/dist/governance/engine/aftermath.js +134 -0
- package/dist/governance/engine/artifact-persist.d.ts +35 -0
- package/dist/governance/engine/artifact-persist.js +86 -0
- package/dist/governance/engine/budget.d.ts +52 -0
- package/dist/governance/engine/budget.js +112 -0
- package/dist/governance/engine/cards.d.ts +18 -0
- package/dist/governance/engine/cards.js +186 -0
- package/dist/governance/engine/digest.d.ts +32 -0
- package/dist/governance/engine/digest.js +63 -0
- package/dist/governance/engine/engine-core.d.ts +219 -0
- package/dist/governance/engine/engine-core.js +562 -0
- package/dist/governance/engine/engine.d.ts +68 -0
- package/dist/governance/engine/engine.js +314 -0
- package/dist/governance/engine/estimate.d.ts +38 -0
- package/dist/governance/engine/estimate.js +46 -0
- package/dist/governance/engine/inspector.d.ts +16 -0
- package/dist/governance/engine/inspector.js +396 -0
- package/dist/governance/engine/integration.d.ts +35 -0
- package/dist/governance/engine/integration.js +220 -0
- package/dist/governance/engine/lifecycle.d.ts +29 -0
- package/dist/governance/engine/lifecycle.js +656 -0
- package/dist/governance/engine/pause.d.ts +56 -0
- package/dist/governance/engine/pause.js +107 -0
- package/dist/governance/engine/phase-members.d.ts +15 -0
- package/dist/governance/engine/phase-members.js +34 -0
- package/dist/governance/engine/phase-planner.d.ts +53 -0
- package/dist/governance/engine/phase-planner.js +282 -0
- package/dist/governance/engine/rebuild.d.ts +27 -0
- package/dist/governance/engine/rebuild.js +57 -0
- package/dist/governance/engine/report.d.ts +66 -0
- package/dist/governance/engine/report.js +220 -0
- package/dist/governance/engine/run-digest.d.ts +20 -0
- package/dist/governance/engine/run-digest.js +42 -0
- package/dist/governance/engine/spec-builder.d.ts +117 -0
- package/dist/governance/engine/spec-builder.js +321 -0
- package/dist/governance/engine/stage-runner.d.ts +22 -0
- package/dist/governance/engine/stage-runner.js +664 -0
- package/dist/governance/engine/start-input.d.ts +19 -0
- package/dist/governance/engine/start-input.js +111 -0
- package/dist/governance/engine/state-machine.d.ts +101 -0
- package/dist/governance/engine/state-machine.js +586 -0
- package/dist/governance/engine/submission-queries.d.ts +42 -0
- package/dist/governance/engine/submission-queries.js +97 -0
- package/dist/governance/engine/submission-render.d.ts +12 -0
- package/dist/governance/engine/submission-render.js +92 -0
- package/dist/governance/engine/submissions.d.ts +27 -0
- package/dist/governance/engine/submissions.js +455 -0
- package/dist/governance/engine/visibility.d.ts +104 -0
- package/dist/governance/engine/visibility.js +223 -0
- package/dist/governance/governed-dispatch.d.ts +19 -0
- package/dist/governance/governed-dispatch.js +17 -0
- package/dist/governance/limits.d.ts +107 -0
- package/dist/governance/limits.js +131 -0
- package/dist/governance/posture-transition.d.ts +38 -0
- package/dist/governance/posture-transition.js +44 -0
- package/dist/governance/posture.d.ts +35 -0
- package/dist/governance/posture.js +59 -0
- package/dist/governance/profiles.d.ts +48 -0
- package/dist/governance/profiles.js +140 -0
- package/dist/governance/prompts/orchestrator-block.d.ts +40 -0
- package/dist/governance/prompts/orchestrator-block.js +128 -0
- package/dist/governance/prompts/plan-addendum.d.ts +22 -0
- package/dist/governance/prompts/plan-addendum.js +60 -0
- package/dist/governance/prompts/refusals.d.ts +48 -0
- package/dist/governance/prompts/refusals.js +82 -0
- package/dist/governance/prompts/role-blocks.d.ts +40 -0
- package/dist/governance/prompts/role-blocks.js +255 -0
- package/dist/governance/report-reader.d.ts +31 -0
- package/dist/governance/report-reader.js +57 -0
- package/dist/governance/roles.d.ts +83 -0
- package/dist/governance/roles.js +142 -0
- package/dist/governance/rules/claims.d.ts +83 -0
- package/dist/governance/rules/claims.js +250 -0
- package/dist/governance/rules/elimination.d.ts +66 -0
- package/dist/governance/rules/elimination.js +105 -0
- package/dist/governance/rules/events-table.d.ts +46 -0
- package/dist/governance/rules/events-table.js +94 -0
- package/dist/governance/rules/failure-analysis.d.ts +28 -0
- package/dist/governance/rules/failure-analysis.js +93 -0
- package/dist/governance/rules/ledger-replay.d.ts +53 -0
- package/dist/governance/rules/ledger-replay.js +53 -0
- package/dist/governance/rules/ledger-state.d.ts +85 -0
- package/dist/governance/rules/ledger-state.js +175 -0
- package/dist/governance/rules/ledger.d.ts +228 -0
- package/dist/governance/rules/ledger.js +560 -0
- package/dist/governance/rules/rca.d.ts +68 -0
- package/dist/governance/rules/rca.js +220 -0
- package/dist/governance/rules/settlement.d.ts +129 -0
- package/dist/governance/rules/settlement.js +363 -0
- package/dist/governance/rules/suspicion.d.ts +66 -0
- package/dist/governance/rules/suspicion.js +169 -0
- package/dist/governance/store/artifacts.d.ts +81 -0
- package/dist/governance/store/artifacts.js +173 -0
- package/dist/governance/store/atomic.d.ts +79 -0
- package/dist/governance/store/atomic.js +119 -0
- package/dist/governance/store/events-log.d.ts +35 -0
- package/dist/governance/store/events-log.js +86 -0
- package/dist/governance/store/run-lock.d.ts +51 -0
- package/dist/governance/store/run-lock.js +122 -0
- package/dist/governance/store/run-store.d.ts +229 -0
- package/dist/governance/store/run-store.js +644 -0
- package/dist/governance/store/scoreboard-export.d.ts +53 -0
- package/dist/governance/store/scoreboard-export.js +65 -0
- package/dist/governance/tools/child-tools.d.ts +31 -0
- package/dist/governance/tools/child-tools.js +65 -0
- package/dist/governance/tools/guard.d.ts +71 -0
- package/dist/governance/tools/guard.js +194 -0
- package/dist/governance/tools/inspector-tools.d.ts +63 -0
- package/dist/governance/tools/inspector-tools.js +424 -0
- package/dist/governance/tools/lead-tools.d.ts +42 -0
- package/dist/governance/tools/lead-tools.js +226 -0
- package/dist/governance/tools/normalize-util.d.ts +101 -0
- package/dist/governance/tools/normalize-util.js +230 -0
- package/dist/governance/tools/scan-incomplete.d.ts +79 -0
- package/dist/governance/tools/scan-incomplete.js +486 -0
- package/dist/governance/tools/shared-tools.d.ts +44 -0
- package/dist/governance/tools/shared-tools.js +125 -0
- package/dist/governance/tools/submit-normalizers.d.ts +28 -0
- package/dist/governance/tools/submit-normalizers.js +474 -0
- package/dist/governance/tools/submit-tools.d.ts +26 -0
- package/dist/governance/tools/submit-tools.js +171 -0
- package/dist/governance/types.d.ts +661 -0
- package/dist/governance/types.js +27 -0
- package/dist/session/validate-session.js +32 -1
- package/dist/team/runtime.d.ts +8 -0
- package/dist/team/runtime.js +9 -0
- package/dist/team/subagent.d.ts +23 -0
- package/dist/team/subagent.js +14 -3
- package/dist/team/types.d.ts +6 -0
- package/dist/tools/index.d.ts +30 -7
- package/dist/tools/index.js +14 -0
- package/dist/tools/plan-tools.d.ts +14 -2
- package/dist/tools/plan-tools.js +5 -2
- package/dist/ui/App.js +145 -25
- package/dist/ui/BottomStatusRow.js +1 -1
- package/dist/ui/Composer.d.ts +7 -1
- package/dist/ui/Composer.js +9 -6
- package/dist/ui/GovernancePanel.d.ts +50 -0
- package/dist/ui/GovernancePanel.js +77 -0
- package/dist/ui/GovernanceRun.d.ts +18 -0
- package/dist/ui/GovernanceRun.js +11 -0
- package/dist/ui/StatusBar.d.ts +15 -2
- package/dist/ui/StatusBar.js +10 -4
- package/dist/ui/Transcript.js +3 -0
- package/dist/ui/entries/GovernanceCard.d.ts +30 -0
- package/dist/ui/entries/GovernanceCard.js +32 -0
- package/dist/ui/governance-copy.d.ts +69 -0
- package/dist/ui/governance-copy.js +104 -0
- package/dist/ui/governance-status.d.ts +145 -0
- package/dist/ui/governance-status.js +326 -0
- package/dist/ui/interaction-copy.d.ts +4 -0
- package/dist/ui/interaction-copy.js +5 -0
- package/dist/ui/interaction-hints.d.ts +17 -0
- package/dist/ui/interaction-hints.js +14 -2
- package/dist/ui/layout/governance-board.d.ts +121 -0
- package/dist/ui/layout/governance-board.js +382 -0
- package/dist/ui/layout/governance-card.d.ts +53 -0
- package/dist/ui/layout/governance-card.js +134 -0
- package/dist/ui/layout/governance-chip.d.ts +38 -0
- package/dist/ui/layout/governance-chip.js +64 -0
- package/dist/ui/layout/governance-panel.d.ts +85 -0
- package/dist/ui/layout/governance-panel.js +358 -0
- package/dist/ui/layout/governance-roster.d.ts +65 -0
- package/dist/ui/layout/governance-roster.js +275 -0
- package/dist/ui/layout/status-detail-layout.js +3 -3
- package/dist/ui/layout/status-layout.d.ts +5 -2
- package/dist/ui/layout/status-layout.js +10 -6
- package/dist/ui/layout/virtual-window.js +11 -0
- package/dist/ui/overlays/GovernanceBoardOverlay.d.ts +41 -0
- package/dist/ui/overlays/GovernanceBoardOverlay.js +76 -0
- package/dist/ui/overlays/GovernanceSettingsPage.d.ts +48 -0
- package/dist/ui/overlays/GovernanceSettingsPage.js +93 -0
- package/dist/ui/overlays/HelpOverlay.js +4 -3
- package/dist/ui/overlays/QuestionOverlay.d.ts +9 -1
- package/dist/ui/overlays/QuestionOverlay.js +4 -2
- package/dist/ui/overlays/SettingsScreen.d.ts +7 -0
- package/dist/ui/overlays/SettingsScreen.js +21 -1
- package/dist/ui/overlays/governance-settings.d.ts +79 -0
- package/dist/ui/overlays/governance-settings.js +140 -0
- package/dist/ui/overlays/model-profile-state.d.ts +6 -1
- package/dist/ui/overlays/model-profile-state.js +10 -0
- package/dist/ui/palettes.d.ts +17 -0
- package/dist/ui/palettes.js +9 -0
- package/dist/ui/scrollback/live-preview.js +2 -0
- package/dist/ui/scrollback/settled-boundary.js +3 -0
- package/dist/ui/shortcuts.js +2 -2
- package/dist/ui/theme.d.ts +7 -0
- package/dist/ui/theme.js +1 -0
- package/dist/ui/transcript-text.js +9 -0
- package/dist/ui/use-wheel-routing.js +3 -1
- package/package.json +2 -2
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Roster geometry and cell text for the legion panel (governance-mode section 10.3).
|
|
3
|
+
*
|
|
4
|
+
* Pure and total: no Ink, no clock, no terminal. ASCII ONLY.
|
|
5
|
+
*
|
|
6
|
+
* WHY COMPUTED FIXED-WIDTH COLUMNS. Same lesson as `team-row.ts`, whose header records
|
|
7
|
+
* it at length: a fixed-height parent makes Yoga size a flex column to its widest CHILD,
|
|
8
|
+
* so a row placed by `flexGrow` stops short of the terminal and the cells to its right
|
|
9
|
+
* lose their absolute offsets. Every column below therefore has a COMPUTED integer width
|
|
10
|
+
* and exactly ONE column is elastic; it sits between the fixed cluster on its left and
|
|
11
|
+
* the fixed `delta` cell on its right, so the score change always lands in the row's
|
|
12
|
+
* last column whatever the activity text says.
|
|
13
|
+
*
|
|
14
|
+
* `columnPlan` is TOTAL: for any width (including 0 and 400) the widths never sum past
|
|
15
|
+
* the width it was given. `governance-panel-layout.test.ts` runs it for every width.
|
|
16
|
+
*/
|
|
17
|
+
import { statusMarkerCell, statusMarkerWidth } from '../team-status.js';
|
|
18
|
+
import { GAUGE_CELLS, deltaCell, gaugeFilledCells, govChildStatus, labelTag, scoreText, scoreToneKey, segment, statusTone, } from '../governance-status.js';
|
|
19
|
+
import { NOT_SCORED, ROLE_LABEL, STATE_WORD, WORKING_VERB, replacedByText, } from '../governance-copy.js';
|
|
20
|
+
import { cellWidth, fitCells } from './cells.js';
|
|
21
|
+
/** The elastic column keeps at least this many columns, else it is dropped (activity) or kept (wp). */
|
|
22
|
+
const ELASTIC_MIN_CELLS = { activity: 12, wp: 6 };
|
|
23
|
+
/** One leading margin column, drawn by the component. */
|
|
24
|
+
export const PANEL_MARGIN_CELLS = 1;
|
|
25
|
+
function columnSpecs(tier, markerCells) {
|
|
26
|
+
const marker = { key: 'marker', width: markerCells };
|
|
27
|
+
const delta = { key: 'delta', width: 7, align: 'right', drop: 2 };
|
|
28
|
+
switch (tier) {
|
|
29
|
+
case 'full':
|
|
30
|
+
return [
|
|
31
|
+
marker,
|
|
32
|
+
{ key: 'id', width: 8 },
|
|
33
|
+
{ key: 'role', width: 6, drop: 3 },
|
|
34
|
+
{ key: 'state', width: 12 },
|
|
35
|
+
{ key: 'gauge', width: GAUGE_CELLS + 1, drop: 5 },
|
|
36
|
+
{ key: 'score', width: 5, align: 'right' },
|
|
37
|
+
{ key: 'prov', width: 7, drop: 4 },
|
|
38
|
+
{ key: 'tag', width: 8, drop: 6 },
|
|
39
|
+
{ key: 'wp', width: 11 },
|
|
40
|
+
{ key: 'activity', width: 0, elastic: true },
|
|
41
|
+
delta,
|
|
42
|
+
];
|
|
43
|
+
case 'standard':
|
|
44
|
+
return [
|
|
45
|
+
marker,
|
|
46
|
+
{ key: 'id', width: 8 },
|
|
47
|
+
{ key: 'role', width: 6, drop: 3 },
|
|
48
|
+
{ key: 'state', width: 12 },
|
|
49
|
+
{ key: 'gauge', width: GAUGE_CELLS + 1, drop: 5 },
|
|
50
|
+
{ key: 'score', width: 5, align: 'right' },
|
|
51
|
+
{ key: 'prov', width: 7, drop: 4 },
|
|
52
|
+
{ key: 'tag', width: 8, drop: 6 },
|
|
53
|
+
{ key: 'wp', width: 0, elastic: true },
|
|
54
|
+
delta,
|
|
55
|
+
];
|
|
56
|
+
default:
|
|
57
|
+
return [
|
|
58
|
+
marker,
|
|
59
|
+
{ key: 'id', width: 8 },
|
|
60
|
+
{ key: 'state', width: 12, drop: 3 },
|
|
61
|
+
{ key: 'score', width: 5, align: 'right' },
|
|
62
|
+
{ key: 'wp', width: 0, elastic: true },
|
|
63
|
+
delta,
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function fixedSum(specs) {
|
|
68
|
+
return specs.reduce((sum, spec) => sum + (spec.elastic ? 0 : spec.width), 0);
|
|
69
|
+
}
|
|
70
|
+
/** The column that gives way first: the highest `drop` rank, or -1 when none can. */
|
|
71
|
+
function dropVictim(specs) {
|
|
72
|
+
let victim = -1;
|
|
73
|
+
let rank = 0;
|
|
74
|
+
specs.forEach((spec, index) => {
|
|
75
|
+
if (spec.drop !== undefined && spec.drop > rank) {
|
|
76
|
+
rank = spec.drop;
|
|
77
|
+
victim = index;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return victim;
|
|
81
|
+
}
|
|
82
|
+
/** Last-resort squeeze so the sum NEVER exceeds `inner`, from the right edge inward. */
|
|
83
|
+
function squeeze(columns, inner) {
|
|
84
|
+
let over = columns.reduce((sum, column) => sum + column.width, 0) - inner;
|
|
85
|
+
for (let index = columns.length - 1; index >= 0 && over > 0; index -= 1) {
|
|
86
|
+
const column = columns[index];
|
|
87
|
+
const cut = Math.min(column.width, over);
|
|
88
|
+
column.width -= cut;
|
|
89
|
+
over -= cut;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Give the activity column up and make the work-package cell the elastic one, which turns
|
|
94
|
+
* a `full` row into a `standard` one without losing the single-elastic-column invariant.
|
|
95
|
+
*/
|
|
96
|
+
function promoteWorkPackage(specs) {
|
|
97
|
+
return specs
|
|
98
|
+
.filter((spec) => spec.key !== 'activity')
|
|
99
|
+
.map((spec) => (spec.key === 'wp' ? { key: 'wp', width: 0, elastic: true } : spec));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The row's columns for a tier at `inner` columns (the terminal width minus the margin).
|
|
103
|
+
*
|
|
104
|
+
* THE DROP LADDER IS THE SPECIFICATION, in the order the cells give way: the activity
|
|
105
|
+
* text first (the only column whose loss costs nothing structural - the work-package cell
|
|
106
|
+
* becomes the elastic one), then the tag, gauge, provisional, role and delta cells. The
|
|
107
|
+
* elastic column keeps at least its floor; when even the fixed cluster does not fit,
|
|
108
|
+
* `squeeze` trims from the right so the sum stays inside `inner` for any input.
|
|
109
|
+
*/
|
|
110
|
+
export function columnPlan(tier, inner, markerCells) {
|
|
111
|
+
const width = Number.isFinite(inner) ? Math.max(0, Math.floor(inner)) : 0;
|
|
112
|
+
let specs = columnSpecs(tier, markerCells);
|
|
113
|
+
const floorOf = (spec) => spec?.key === 'activity' ? ELASTIC_MIN_CELLS.activity : ELASTIC_MIN_CELLS.wp;
|
|
114
|
+
for (;;) {
|
|
115
|
+
const elastic = specs.find((spec) => spec.elastic === true);
|
|
116
|
+
if (fixedSum(specs) + floorOf(elastic) <= width)
|
|
117
|
+
break;
|
|
118
|
+
if (elastic?.key === 'activity') {
|
|
119
|
+
specs = promoteWorkPackage(specs);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const victim = dropVictim(specs);
|
|
123
|
+
if (victim < 0)
|
|
124
|
+
break;
|
|
125
|
+
specs = specs.filter((_, index) => index !== victim);
|
|
126
|
+
}
|
|
127
|
+
const remaining = Math.max(0, width - fixedSum(specs));
|
|
128
|
+
const columns = specs.map((spec) => ({
|
|
129
|
+
key: spec.key,
|
|
130
|
+
width: spec.elastic ? remaining : spec.width,
|
|
131
|
+
elastic: spec.elastic === true,
|
|
132
|
+
align: spec.align ?? 'left',
|
|
133
|
+
}));
|
|
134
|
+
squeeze(columns, width);
|
|
135
|
+
return columns;
|
|
136
|
+
}
|
|
137
|
+
export function columnsWidth(columns) {
|
|
138
|
+
return columns.reduce((sum, column) => sum + column.width, 0);
|
|
139
|
+
}
|
|
140
|
+
const BLANK_TONE = 'muted';
|
|
141
|
+
function textCell(column, text, tone, ellipsis, bold = false) {
|
|
142
|
+
const clipped = column.width === 0 ? '' : fitCells(text, column.width, ellipsis);
|
|
143
|
+
return {
|
|
144
|
+
key: column.key,
|
|
145
|
+
width: column.width,
|
|
146
|
+
elastic: column.elastic,
|
|
147
|
+
align: column.align,
|
|
148
|
+
segments: clipped === '' ? [] : [segment(clipped, tone, bold)],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function leading(text) {
|
|
152
|
+
return text === '' ? '' : ` ${text}`;
|
|
153
|
+
}
|
|
154
|
+
/** `not scored` replaces the gauge for the inspector (spec 5.1: never scored). */
|
|
155
|
+
function gaugeCell(column, row, glyphs) {
|
|
156
|
+
if (row.confirmed === null)
|
|
157
|
+
return textCell(column, NOT_SCORED, 'muted', glyphs.ellipsis);
|
|
158
|
+
const filled = gaugeFilledCells(row.confirmed);
|
|
159
|
+
const bar = [
|
|
160
|
+
segment(glyphs.gaugeFull.repeat(filled), scoreToneKey(row.label)),
|
|
161
|
+
segment(glyphs.gaugeEmpty.repeat(GAUGE_CELLS - filled), 'border'),
|
|
162
|
+
].filter((part) => part.text !== '');
|
|
163
|
+
return {
|
|
164
|
+
key: column.key,
|
|
165
|
+
width: column.width,
|
|
166
|
+
elastic: false,
|
|
167
|
+
align: 'left',
|
|
168
|
+
segments: bar,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function stateText(row) {
|
|
172
|
+
return row.state === 'working' ? WORKING_VERB[row.role] : STATE_WORD[row.state];
|
|
173
|
+
}
|
|
174
|
+
function rightAligned(column, text) {
|
|
175
|
+
return ' '.repeat(Math.max(0, column.width - cellWidth(text))) + text;
|
|
176
|
+
}
|
|
177
|
+
function scoreCell(column, row, ctx) {
|
|
178
|
+
const ell = ctx.glyphs.ellipsis;
|
|
179
|
+
if (row.confirmed === null)
|
|
180
|
+
return textCell(column, '', BLANK_TONE, ell);
|
|
181
|
+
const text = rightAligned(column, scoreText(row.confirmed));
|
|
182
|
+
return textCell(column, text, scoreToneKey(row.label), ell);
|
|
183
|
+
}
|
|
184
|
+
function deltaColumnCell(column, row, ctx) {
|
|
185
|
+
const delta = deltaCell(row, ctx.now, ctx.reducedMotion);
|
|
186
|
+
const text = delta.text === '' ? '' : rightAligned(column, delta.text);
|
|
187
|
+
return textCell(column, text, delta.tone, ctx.glyphs.ellipsis, delta.bold);
|
|
188
|
+
}
|
|
189
|
+
/** One builder per column: a table, so adding a column cannot raise a function's complexity. */
|
|
190
|
+
const CELL_BUILDERS = {
|
|
191
|
+
marker: (column, row, ctx) => {
|
|
192
|
+
const status = govChildStatus(row.state);
|
|
193
|
+
const text = ` ${statusMarkerCell(status, ctx.glyphs)} `;
|
|
194
|
+
return textCell(column, text, statusTone(status), ctx.glyphs.ellipsis);
|
|
195
|
+
},
|
|
196
|
+
id: (column, row, ctx) => textCell(column, row.id, 'primary', ctx.glyphs.ellipsis),
|
|
197
|
+
role: (column, row, ctx) => textCell(column, ROLE_LABEL[row.role], 'muted', ctx.glyphs.ellipsis),
|
|
198
|
+
state: (column, row, ctx) => {
|
|
199
|
+
const tone = statusTone(govChildStatus(row.state));
|
|
200
|
+
return textCell(column, stateText(row), tone, ctx.glyphs.ellipsis);
|
|
201
|
+
},
|
|
202
|
+
gauge: (column, row, ctx) => gaugeCell(column, row, ctx.glyphs),
|
|
203
|
+
score: scoreCell,
|
|
204
|
+
prov: (column, row, ctx) => {
|
|
205
|
+
const text = row.provisional > 0 ? leading(`+${row.provisional}P`) : '';
|
|
206
|
+
return textCell(column, text, 'muted', ctx.glyphs.ellipsis);
|
|
207
|
+
},
|
|
208
|
+
tag: (column, row, ctx) => {
|
|
209
|
+
const text = leading(labelTag(row.label));
|
|
210
|
+
return textCell(column, text, scoreToneKey(row.label), ctx.glyphs.ellipsis, true);
|
|
211
|
+
},
|
|
212
|
+
wp: (column, row, ctx) => textCell(column, leading(row.wpId ?? '-'), 'text', ctx.glyphs.ellipsis),
|
|
213
|
+
activity: (column, row, ctx) => {
|
|
214
|
+
const text = leading(row.activity.replace(/\s+/g, ' ').trim());
|
|
215
|
+
return textCell(column, text, 'muted', ctx.glyphs.ellipsis);
|
|
216
|
+
},
|
|
217
|
+
delta: deltaColumnCell,
|
|
218
|
+
note: (column, _row, ctx) => textCell(column, '', BLANK_TONE, ctx.glyphs.ellipsis),
|
|
219
|
+
};
|
|
220
|
+
function cellFor(column, row, ctx) {
|
|
221
|
+
return CELL_BUILDERS[column.key](column, row, ctx);
|
|
222
|
+
}
|
|
223
|
+
/** Columns a replaced member's one-line note spans. */
|
|
224
|
+
const NOTE_SPAN = new Set(['state', 'gauge', 'score', 'prov', 'tag']);
|
|
225
|
+
/**
|
|
226
|
+
* A replaced member: marker, id and role stay (so the row is recognisable), and one
|
|
227
|
+
* muted note spans the state-through-tag cells. The note NAMES the successor, which is the
|
|
228
|
+
* information the member's vanishing from the panel next phase would otherwise erase.
|
|
229
|
+
*/
|
|
230
|
+
function eliminatedCells(columns, row, ctx) {
|
|
231
|
+
const cells = [];
|
|
232
|
+
let spanned = 0;
|
|
233
|
+
let spanColumn = null;
|
|
234
|
+
const flush = () => {
|
|
235
|
+
if (spanColumn === null)
|
|
236
|
+
return;
|
|
237
|
+
const merged = { ...spanColumn, key: 'note', width: spanned, align: 'left' };
|
|
238
|
+
const text = ` ${replacedByText(row.replacedBy)}`;
|
|
239
|
+
cells.push(textCell(merged, text, 'muted', ctx.glyphs.ellipsis));
|
|
240
|
+
spanColumn = null;
|
|
241
|
+
spanned = 0;
|
|
242
|
+
};
|
|
243
|
+
for (const column of columns) {
|
|
244
|
+
if (NOTE_SPAN.has(column.key)) {
|
|
245
|
+
spanColumn ??= column;
|
|
246
|
+
spanned += column.width;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
flush();
|
|
250
|
+
const cell = cellFor(column, row, ctx);
|
|
251
|
+
const quiet = column.key === 'id' || column.key === 'role' || column.key === 'marker';
|
|
252
|
+
cells.push(quiet ? mute(cell) : blank(cell));
|
|
253
|
+
}
|
|
254
|
+
flush();
|
|
255
|
+
return cells;
|
|
256
|
+
}
|
|
257
|
+
function mute(cell) {
|
|
258
|
+
return { ...cell, segments: cell.segments.map((part) => ({ ...part, tone: 'muted' })) };
|
|
259
|
+
}
|
|
260
|
+
function blank(cell) {
|
|
261
|
+
return { ...cell, segments: [] };
|
|
262
|
+
}
|
|
263
|
+
/** One roster row's cells, text already clamped to its cell. */
|
|
264
|
+
export function buildRosterLine(input) {
|
|
265
|
+
const { columns, row, glyphs, now, reducedMotion } = input;
|
|
266
|
+
const ctx = { glyphs, now, reducedMotion };
|
|
267
|
+
if (row.state === 'eliminated') {
|
|
268
|
+
return { key: row.id, kind: 'eliminated', cells: eliminatedCells(columns, row, ctx) };
|
|
269
|
+
}
|
|
270
|
+
return { key: row.id, kind: 'agent', cells: columns.map((column) => cellFor(column, row, ctx)) };
|
|
271
|
+
}
|
|
272
|
+
/** The marker cell's width in the current glyph tier: 1 + widest marker + 1. */
|
|
273
|
+
export function markerCells(glyphs) {
|
|
274
|
+
return 1 + statusMarkerWidth(glyphs) + 1;
|
|
275
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { formatCost, formatTokens, promptTokensOf } from '../../agent/usage.js';
|
|
2
2
|
import { actionCandidates, clipStatusText, escapeClause } from '../interaction-hints.js';
|
|
3
|
-
import {
|
|
3
|
+
import { isGovernedModeValue, modeWord } from '../governance-status.js';
|
|
4
4
|
import { statusField, statusLinePlan, validContext } from './status-layout.js';
|
|
5
5
|
/**
|
|
6
6
|
* Uppercase mode word for the details row, mirroring the primary row's field:
|
|
7
7
|
* same vocabulary (`MODE_LABEL`), same `CUR>PENDING` deferred-switch shape.
|
|
8
8
|
*/
|
|
9
9
|
function detailModeText(mode, pendingMode) {
|
|
10
|
-
const word = (value) =>
|
|
10
|
+
const word = (value) => modeWord(value, true) ?? value.toUpperCase();
|
|
11
11
|
if (pendingMode)
|
|
12
12
|
return `${word(mode || 'build')}>${word(pendingMode)}`;
|
|
13
13
|
return word(mode || 'build');
|
|
@@ -41,7 +41,7 @@ export function planStatusDetail(input) {
|
|
|
41
41
|
if (status.servicesActive?.live)
|
|
42
42
|
add(statusField('services', 'Svc' + status.servicesActive.live));
|
|
43
43
|
if (status.mode || status.pendingMode)
|
|
44
|
-
add(statusField('mode', detailModeText(status.mode, status.pendingMode), 'accent'));
|
|
44
|
+
add(statusField('mode', detailModeText(status.mode, status.pendingMode), isGovernedModeValue(status.mode) || isGovernedModeValue(status.pendingMode) ? 'govern' : 'accent'));
|
|
45
45
|
if (validContext(status.context)) {
|
|
46
46
|
const approximate = status.context.source === 'estimate' || status.context.deltaTokens > 0 ? '~' : '';
|
|
47
47
|
add(statusField('context', 'Context ' + approximate + formatTokens(status.context.occupied)
|
|
@@ -117,8 +117,11 @@ export interface StatusField {
|
|
|
117
117
|
id: string;
|
|
118
118
|
text: string;
|
|
119
119
|
cells: number;
|
|
120
|
-
/**
|
|
121
|
-
|
|
120
|
+
/**
|
|
121
|
+
* 'accent' carries the mode word - the row's one always-on emphasis. 'govern' is the
|
|
122
|
+
* same slot while the posture is GOVERN / PLAN+GOVERN (`governAccent`, its own hue).
|
|
123
|
+
*/
|
|
124
|
+
tone: 'normal' | 'muted' | 'warning' | 'error' | 'accent' | 'govern';
|
|
122
125
|
}
|
|
123
126
|
export interface StatusLinePlan {
|
|
124
127
|
fields: readonly StatusField[];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import stringWidth from 'string-width';
|
|
2
2
|
import stripAnsi from 'strip-ansi';
|
|
3
3
|
import { formatDuration } from '../../agent/usage.js';
|
|
4
|
-
import {
|
|
4
|
+
import { isGovernedModeValue, modeWord } from '../governance-status.js';
|
|
5
5
|
import { interactionCopy as copy } from '../interaction-copy.js';
|
|
6
6
|
export function cleanStatusText(text) {
|
|
7
7
|
// eslint-disable-next-line no-control-regex
|
|
@@ -170,10 +170,12 @@ function modeFieldText(mode, pendingMode, full) {
|
|
|
170
170
|
if (!mode && !pendingMode)
|
|
171
171
|
return null;
|
|
172
172
|
const word = (value) => {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
// `build` / `plan` answer exactly as before; the two governed postures are the only
|
|
174
|
+
// other values `modeWord` knows (`PG` is the short form of PLAN+GOVERN, since `P` is PLAN).
|
|
175
|
+
const known = modeWord(value, full);
|
|
176
|
+
if (known !== undefined)
|
|
177
|
+
return known;
|
|
178
|
+
return full ? value.toUpperCase() : (value[0] ?? '?').toUpperCase();
|
|
177
179
|
};
|
|
178
180
|
const current = word(mode || 'build');
|
|
179
181
|
return pendingMode ? `${current}>${word(pendingMode)}` : current;
|
|
@@ -231,7 +233,9 @@ export function planPrimaryStatusFields(input) {
|
|
|
231
233
|
return statusLinePlan([
|
|
232
234
|
statusField('phase', glyph + (rung >= 5 ? '' : ' ') + label, phaseTone),
|
|
233
235
|
...(notice ? [statusField('feedback', notice, feedbackTone)] : []),
|
|
234
|
-
...(modeText
|
|
236
|
+
...(modeText
|
|
237
|
+
? [statusField('mode', modeText, isGovernedModeValue(input.mode) || isGovernedModeValue(input.pendingMode) ? 'govern' : 'accent')]
|
|
238
|
+
: []),
|
|
235
239
|
...(forcedText ? [statusField('forcedTeam', forcedText, 'accent')] : []),
|
|
236
240
|
statusField('context', (full ? 'Context ' : 'C') + pct
|
|
237
241
|
+ (input.compactionPaused === true ? pausedSuffix(full) : '')),
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
* through the mounted set and the measured heights back to the offset.
|
|
34
34
|
*/
|
|
35
35
|
import stringWidth from 'string-width';
|
|
36
|
+
import { governanceCardRows } from './governance-card.js';
|
|
36
37
|
import { FAST_LIMITS } from '../../fast/limits.js';
|
|
37
38
|
import { COMPACTION_LIMITS } from '../../compaction/limits.js';
|
|
38
39
|
import { GUTTER_WIDTH } from './Gutter.js';
|
|
@@ -153,6 +154,11 @@ export function entryRevision(entry) {
|
|
|
153
154
|
*/
|
|
154
155
|
case 'service':
|
|
155
156
|
return `v${entry.status}.${entry.rowsSeen}.${entry.url ? 1 : 0}.${entry.exitCode ?? -1}.${entry.terminal ? 1 : 0}`;
|
|
157
|
+
// A governance card is IMMUTABLE once appended (it is a settled record), so its
|
|
158
|
+
// revision only has to tell two cards apart: the shape terms below are what decide
|
|
159
|
+
// its height (`governanceCardRows`), and they cannot change without a new entry.
|
|
160
|
+
case 'governance':
|
|
161
|
+
return `g${entry.card.rows.length}.${entry.card.notes.length}.${entry.card.headline.length}`;
|
|
156
162
|
default:
|
|
157
163
|
return 'x';
|
|
158
164
|
}
|
|
@@ -402,6 +408,11 @@ export function estimateEntryRows(entry, cols, density, expanded, thinkingVisibl
|
|
|
402
408
|
const progress = entry.progress || entry.stalled ? SERVICE_PROGRESS_ROWS : 0;
|
|
403
409
|
return separation + 1 + progress + url + tail;
|
|
404
410
|
}
|
|
411
|
+
// EXACT, not an upper bound: every line of the card is `wrap="truncate"`, so its
|
|
412
|
+
// height is a function of its data alone (`layout/governance-card.ts`). It has to
|
|
413
|
+
// be added to BOTH of this file's switches - one alone yields scroll drift.
|
|
414
|
+
case 'governance':
|
|
415
|
+
return separation + governanceCardRows(entry.card);
|
|
405
416
|
default:
|
|
406
417
|
return separation + 1;
|
|
407
418
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GovernanceBoardOverlay - the read-only `/gov board` overview
|
|
3
|
+
* (governance-mode section 10.5).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: every symbol comes through `glyphs`.
|
|
6
|
+
*
|
|
7
|
+
* READ-ONLY IS THE SAFETY BOUNDARY, as for the Agents board: nothing here scores, rules
|
|
8
|
+
* or resolves anything. The only side effect is `e`, which asks the owner to copy the
|
|
9
|
+
* `board.md` path.
|
|
10
|
+
*
|
|
11
|
+
* `OverlayFrame` SELF-MANAGED MODE, with this file owning the window. The tab strip must
|
|
12
|
+
* stay put while the rows scroll, which the frame's controlled mode (it windows EVERY row
|
|
13
|
+
* it is given) cannot do; so the body is "one strip row + a window of rows" and the
|
|
14
|
+
* windowing is `sliceWindow` through `boardWindow`, the arithmetic the frame itself uses.
|
|
15
|
+
* `PgUp` / `PgDn` / the wheel arrive through `App`'s `controlled` overlay list as
|
|
16
|
+
* `scrollOffset`; Up/Down and Left/Right are this component's own keys, the same split the
|
|
17
|
+
* Agents board makes.
|
|
18
|
+
*
|
|
19
|
+
* NO `ink-spinner` and no motion prop (spinner-census): this is a reading surface.
|
|
20
|
+
*/
|
|
21
|
+
import React from 'react';
|
|
22
|
+
import type { Theme } from '../theme.js';
|
|
23
|
+
import type { TermCapabilities } from '../capabilities.js';
|
|
24
|
+
import { type BoardTab, type GovernanceBoardData } from '../layout/governance-board.js';
|
|
25
|
+
export interface GovernanceBoardOverlayProps {
|
|
26
|
+
/** The board's data, or null when there is no governed run to show. */
|
|
27
|
+
data: GovernanceBoardData | null;
|
|
28
|
+
/** The overlay's FRAME width (`App`'s `viewCols`). */
|
|
29
|
+
cols: number;
|
|
30
|
+
maxRows: number;
|
|
31
|
+
scrollOffset: number;
|
|
32
|
+
onScrollClamp: (offset: number) => void;
|
|
33
|
+
isActive: boolean;
|
|
34
|
+
theme: Theme;
|
|
35
|
+
caps: TermCapabilities;
|
|
36
|
+
/** `e`: copy the board file path. Absent = the key reports the path as unavailable. */
|
|
37
|
+
onCopyPath?: (path: string) => void;
|
|
38
|
+
onNotice?: (level: 'info' | 'warn', text: string) => void;
|
|
39
|
+
initialTab?: BoardTab;
|
|
40
|
+
}
|
|
41
|
+
export declare function GovernanceBoardOverlay({ data, cols, maxRows, scrollOffset, onScrollClamp, isActive, theme, caps, onCopyPath, onNotice, initialTab, }: GovernanceBoardOverlayProps): React.ReactElement;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* GovernanceBoardOverlay - the read-only `/gov board` overview
|
|
4
|
+
* (governance-mode section 10.5).
|
|
5
|
+
*
|
|
6
|
+
* ASCII ONLY: every symbol comes through `glyphs`.
|
|
7
|
+
*
|
|
8
|
+
* READ-ONLY IS THE SAFETY BOUNDARY, as for the Agents board: nothing here scores, rules
|
|
9
|
+
* or resolves anything. The only side effect is `e`, which asks the owner to copy the
|
|
10
|
+
* `board.md` path.
|
|
11
|
+
*
|
|
12
|
+
* `OverlayFrame` SELF-MANAGED MODE, with this file owning the window. The tab strip must
|
|
13
|
+
* stay put while the rows scroll, which the frame's controlled mode (it windows EVERY row
|
|
14
|
+
* it is given) cannot do; so the body is "one strip row + a window of rows" and the
|
|
15
|
+
* windowing is `sliceWindow` through `boardWindow`, the arithmetic the frame itself uses.
|
|
16
|
+
* `PgUp` / `PgDn` / the wheel arrive through `App`'s `controlled` overlay list as
|
|
17
|
+
* `scrollOffset`; Up/Down and Left/Right are this component's own keys, the same split the
|
|
18
|
+
* Agents board makes.
|
|
19
|
+
*
|
|
20
|
+
* NO `ink-spinner` and no motion prop (spinner-census): this is a reading surface.
|
|
21
|
+
*/
|
|
22
|
+
import { useEffect, useRef, useState } from 'react';
|
|
23
|
+
import { Box, Text, useInput } from 'ink';
|
|
24
|
+
import { pickGlyphs } from '../glyphs.js';
|
|
25
|
+
import { OverlayFrame, overlayBodyRows } from '../layout/OverlayFrame.js';
|
|
26
|
+
import { boardBodyRows, boardHint, boardPosition, boardWindow, buildGovernanceBoardLayout, stepTab, } from '../layout/governance-board.js';
|
|
27
|
+
import { cellWidth } from '../layout/cells.js';
|
|
28
|
+
import { GovernanceLine } from '../GovernanceRun.js';
|
|
29
|
+
import { BOARD_COPIED, BOARD_NO_PATH, BOARD_TITLE } from '../governance-copy.js';
|
|
30
|
+
/** `OverlayFrame`'s own breakpoint, mirrored so the chrome estimate cannot drift. */
|
|
31
|
+
const FRAME_BORDERLESS_COLS = 48;
|
|
32
|
+
export function GovernanceBoardOverlay({ data, cols, maxRows, scrollOffset, onScrollClamp, isActive, theme, caps, onCopyPath, onNotice, initialTab = 'roster', }) {
|
|
33
|
+
const glyphs = pickGlyphs(caps);
|
|
34
|
+
const [tab, setTab] = useState(initialTab);
|
|
35
|
+
// The frame spends 4 of `cols` on its border + padding (2 in the narrow rail form), so a
|
|
36
|
+
// board planned against the frame width would lose its last four columns to truncation.
|
|
37
|
+
const contentCols = Math.max(1, cols - (cols >= FRAME_BORDERLESS_COLS ? 4 : 2));
|
|
38
|
+
const layout = buildGovernanceBoardLayout({ cols: contentCols, tab, data, glyphs });
|
|
39
|
+
const height = boardBodyRows(overlayBodyRows(maxRows, cols));
|
|
40
|
+
const window = boardWindow(layout.lines.length, scrollOffset, height);
|
|
41
|
+
const position = boardPosition(window, glyphs);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (window.clamped !== scrollOffset)
|
|
44
|
+
onScrollClamp(window.clamped);
|
|
45
|
+
}, [window.clamped, scrollOffset, onScrollClamp]);
|
|
46
|
+
// The offset the NEXT key press starts from. A held Down key delivers several presses
|
|
47
|
+
// before React re-renders; stepping from the rendered value would turn three presses
|
|
48
|
+
// into one row of movement.
|
|
49
|
+
const offsetRef = useRef(window.clamped);
|
|
50
|
+
offsetRef.current = window.clamped;
|
|
51
|
+
useInput((input, key) => {
|
|
52
|
+
if (!isActive)
|
|
53
|
+
return;
|
|
54
|
+
if (key.leftArrow || key.rightArrow) {
|
|
55
|
+
setTab((current) => stepTab(current, key.rightArrow ? 1 : -1));
|
|
56
|
+
offsetRef.current = 0;
|
|
57
|
+
onScrollClamp(0);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (key.upArrow || key.downArrow) {
|
|
61
|
+
offsetRef.current = Math.max(0, offsetRef.current + (key.upArrow ? -1 : 1));
|
|
62
|
+
onScrollClamp(offsetRef.current);
|
|
63
|
+
}
|
|
64
|
+
else if (input === 'e' || input === 'E') {
|
|
65
|
+
const path = data?.boardPath ?? null;
|
|
66
|
+
if (path === null || onCopyPath === undefined) {
|
|
67
|
+
onNotice?.('warn', BOARD_NO_PATH);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
onCopyPath(path);
|
|
71
|
+
onNotice?.('info', BOARD_COPIED);
|
|
72
|
+
}
|
|
73
|
+
}, { isActive });
|
|
74
|
+
const visible = layout.lines.slice(window.start, window.start + window.visible);
|
|
75
|
+
return (_jsxs(OverlayFrame, { title: BOARD_TITLE, hint: boardHint(contentCols, glyphs), maxRows: maxRows, cols: cols, theme: theme, caps: caps, children: [_jsxs(Box, { flexDirection: "row", height: 1, flexShrink: 0, children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(GovernanceLine, { segments: layout.tabStrip.segments, theme: theme }) }), position !== '' && (_jsx(Box, { width: cellWidth(position) + 1, flexShrink: 0, justifyContent: "flex-end", children: _jsx(Text, { color: theme.muted, children: position }) }))] }), visible.map((line) => (_jsx(GovernanceLine, { segments: line.segments, theme: theme }, line.key)))] }));
|
|
76
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings > Governance: eleven arrows-adjustable rows (governance-mode-v1-1 5, closing
|
|
3
|
+
* governance-mode IF-13).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`.
|
|
6
|
+
*
|
|
7
|
+
* THIS IS `TeamFanoutPage` WITH A DIFFERENT TABLE, ON PURPOSE. Every defence that page
|
|
8
|
+
* carries was paid for by a real defect, so each one is inherited verbatim:
|
|
9
|
+
*
|
|
10
|
+
* - THE PAGE OWNS ONE PIECE OF STATE, its cursor. Every value comes from `props.view.rows`,
|
|
11
|
+
* which `SettingsScreen` derives from the reducer; a second copy of the draft here could
|
|
12
|
+
* disagree with the root page about what Ctrl+S is about to save.
|
|
13
|
+
* - `Enter` TOGGLES THE SWITCH ROWS AND DOES NOTHING ON A NUMBER (R-P1-12). A stray Return
|
|
14
|
+
* that silently changed a number is indistinguishable from a bug, and the arrow keys are
|
|
15
|
+
* already the obvious control there.
|
|
16
|
+
* - IT DOES NOT LISTEN FOR Ctrl+S, AND THE FOOTER SAYS SO (R-P1-13). `useRootSettingsInput`
|
|
17
|
+
* gates its `useInput` on `state.page === 'root'`, so a subpage has no save listener at
|
|
18
|
+
* all; a footer promising "Ctrl+S saves here" would be a lie the user acts on.
|
|
19
|
+
*
|
|
20
|
+
* THE RESTART NOTE IS THE ONE THING THIS PAGE ADDS. `enabled` is a REGISTRATION key: the tool
|
|
21
|
+
* array is fixed when the session is built, so flipping it here changes the file and the
|
|
22
|
+
* next launch, not this session. The note sits under that row so the consequence is read
|
|
23
|
+
* where the choice is made.
|
|
24
|
+
*/
|
|
25
|
+
import React from 'react';
|
|
26
|
+
import { type ProfileFrameProps } from './ModelProfilePicker.js';
|
|
27
|
+
import type { GovernanceSettingKey, GovernanceSettingRow } from './governance-settings.js';
|
|
28
|
+
/** What the page may ask the screen to change. */
|
|
29
|
+
export interface GovernanceSettingsAction {
|
|
30
|
+
type: 'governance-step';
|
|
31
|
+
key: GovernanceSettingKey;
|
|
32
|
+
delta: -1 | 1;
|
|
33
|
+
}
|
|
34
|
+
export interface GovernanceSettingsView {
|
|
35
|
+
/** Exactly eleven, in the fixed order the cursor indexes. */
|
|
36
|
+
rows: GovernanceSettingRow[];
|
|
37
|
+
error?: string;
|
|
38
|
+
/** Shown under the first row; `null` when the draft agrees with this session. */
|
|
39
|
+
restartNote: string | null;
|
|
40
|
+
}
|
|
41
|
+
interface GovernanceSettingsPageProps extends ProfileFrameProps {
|
|
42
|
+
view: GovernanceSettingsView;
|
|
43
|
+
dispatch: (action: GovernanceSettingsAction) => void;
|
|
44
|
+
/** Esc: back to the settings root. The draft is NOT discarded here. */
|
|
45
|
+
onBack: () => void;
|
|
46
|
+
}
|
|
47
|
+
export declare function GovernanceSettingsPage(props: GovernanceSettingsPageProps): React.ReactElement;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Settings > Governance: eleven arrows-adjustable rows (governance-mode-v1-1 5, closing
|
|
4
|
+
* governance-mode IF-13).
|
|
5
|
+
*
|
|
6
|
+
* ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`.
|
|
7
|
+
*
|
|
8
|
+
* THIS IS `TeamFanoutPage` WITH A DIFFERENT TABLE, ON PURPOSE. Every defence that page
|
|
9
|
+
* carries was paid for by a real defect, so each one is inherited verbatim:
|
|
10
|
+
*
|
|
11
|
+
* - THE PAGE OWNS ONE PIECE OF STATE, its cursor. Every value comes from `props.view.rows`,
|
|
12
|
+
* which `SettingsScreen` derives from the reducer; a second copy of the draft here could
|
|
13
|
+
* disagree with the root page about what Ctrl+S is about to save.
|
|
14
|
+
* - `Enter` TOGGLES THE SWITCH ROWS AND DOES NOTHING ON A NUMBER (R-P1-12). A stray Return
|
|
15
|
+
* that silently changed a number is indistinguishable from a bug, and the arrow keys are
|
|
16
|
+
* already the obvious control there.
|
|
17
|
+
* - IT DOES NOT LISTEN FOR Ctrl+S, AND THE FOOTER SAYS SO (R-P1-13). `useRootSettingsInput`
|
|
18
|
+
* gates its `useInput` on `state.page === 'root'`, so a subpage has no save listener at
|
|
19
|
+
* all; a footer promising "Ctrl+S saves here" would be a lie the user acts on.
|
|
20
|
+
*
|
|
21
|
+
* THE RESTART NOTE IS THE ONE THING THIS PAGE ADDS. `enabled` is a REGISTRATION key: the tool
|
|
22
|
+
* array is fixed when the session is built, so flipping it here changes the file and the
|
|
23
|
+
* next launch, not this session. The note sits under that row so the consequence is read
|
|
24
|
+
* where the choice is made.
|
|
25
|
+
*/
|
|
26
|
+
import { useState } from 'react';
|
|
27
|
+
import { Text, useInput } from 'ink';
|
|
28
|
+
import { pickGlyphs } from '../glyphs.js';
|
|
29
|
+
import { OverlayFrame } from '../layout/OverlayFrame.js';
|
|
30
|
+
import { clipProfileText, useProfileScroll } from './ModelProfilePicker.js';
|
|
31
|
+
/** Matches the root page's `padEnd(18)` rows so the value column lines up (measured). */
|
|
32
|
+
const LABEL_PAD = 18;
|
|
33
|
+
/** The rows `Enter` flips, and nothing else (R-P1-12). */
|
|
34
|
+
const TOGGLE_KEYS = new Set([
|
|
35
|
+
'enabled',
|
|
36
|
+
'shiftTab',
|
|
37
|
+
'panel',
|
|
38
|
+
'confirmCharter',
|
|
39
|
+
]);
|
|
40
|
+
export function GovernanceSettingsPage(props) {
|
|
41
|
+
const [index, setIndex] = useState(0);
|
|
42
|
+
const rows = props.view.rows;
|
|
43
|
+
const count = Math.max(1, rows.length);
|
|
44
|
+
const cursor = Math.min(index, count - 1);
|
|
45
|
+
const focused = rows[cursor];
|
|
46
|
+
const note = props.view.restartNote;
|
|
47
|
+
// The note is one extra LINE after row 0, so every later row sits one line lower.
|
|
48
|
+
const offset = useProfileScroll(props, cursor + (note !== null && cursor > 0 ? 1 : 0));
|
|
49
|
+
const nudge = (delta) => {
|
|
50
|
+
if (focused)
|
|
51
|
+
props.dispatch({ type: 'governance-step', key: focused.key, delta });
|
|
52
|
+
};
|
|
53
|
+
const handleKey = (key) => {
|
|
54
|
+
if (key.escape) {
|
|
55
|
+
props.onBack();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (key.upArrow || (key.tab && key.shift)) {
|
|
59
|
+
setIndex((cursor - 1 + count) % count);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (key.downArrow || key.tab) {
|
|
63
|
+
setIndex((cursor + 1) % count);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (key.leftArrow || key.rightArrow) {
|
|
67
|
+
nudge(key.leftArrow ? -1 : 1);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// `Enter` toggles the switch rows and does NOTHING on a numeric or choice row.
|
|
71
|
+
if (key.return && focused && TOGGLE_KEYS.has(focused.key))
|
|
72
|
+
nudge(1);
|
|
73
|
+
};
|
|
74
|
+
useInput((_input, key) => {
|
|
75
|
+
if (props.isActive === false)
|
|
76
|
+
return;
|
|
77
|
+
handleKey(key);
|
|
78
|
+
});
|
|
79
|
+
const glyphs = pickGlyphs(props.caps);
|
|
80
|
+
const body = [];
|
|
81
|
+
rows.forEach((row, at) => {
|
|
82
|
+
const line = `${row.label.padEnd(LABEL_PAD)}${row.value}`;
|
|
83
|
+
body.push(_jsx(Text, { wrap: "truncate", color: at === cursor ? props.theme.accent : props.theme.muted, children: `${at === cursor ? glyphs.caret : ' '} ${clipProfileText(line, props.cols - 8)}` }, row.key));
|
|
84
|
+
if (at === 0 && note !== null) {
|
|
85
|
+
body.push(_jsx(Text, { wrap: "truncate", color: props.theme.muted, children: ` ${clipProfileText(note, props.cols - 8)}` }, "restart-note"));
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
// The hint explains the FOCUSED row: the range and the reason are the point of the page.
|
|
89
|
+
if (focused) {
|
|
90
|
+
body.push(_jsx(Text, { wrap: "truncate", color: props.theme.muted, children: clipProfileText(focused.hint, props.cols - 8) }, "hint"));
|
|
91
|
+
}
|
|
92
|
+
return _jsx(OverlayFrame, { ...props, rows: body, scrollOffset: offset, title: props.view.error ?? 'Governance', hint: "Arrows adjust. Esc goes back; Ctrl+S on the main page saves." });
|
|
93
|
+
}
|