@aragon-agent/cli 0.6.34 → 0.6.36
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 +21 -0
- package/README.md +173 -7
- package/dist/agent/agent-mode.d.ts +20 -4
- package/dist/agent/agent-mode.js +20 -4
- package/dist/agent/controller.d.ts +67 -3
- package/dist/agent/controller.js +106 -5
- package/dist/agent/headless.js +31 -0
- package/dist/agent/kit-apply.d.ts +56 -0
- package/dist/agent/kit-apply.js +209 -0
- package/dist/agent/model-profile-settings.d.ts +28 -0
- package/dist/agent/model-profile-settings.js +142 -29
- package/dist/agent/reducer.d.ts +13 -2
- package/dist/agent/reducer.js +1 -0
- package/dist/cli.js +12 -0
- package/dist/commands/agents-command.d.ts +15 -0
- package/dist/commands/agents-command.js +27 -0
- package/dist/commands/builtins.js +135 -4
- package/dist/commands/kit-command.d.ts +20 -0
- package/dist/commands/kit-command.js +131 -0
- package/dist/commands/perf.d.ts +11 -0
- package/dist/commands/perf.js +2 -1
- package/dist/commands/registry.d.ts +14 -0
- package/dist/config/app-paths.d.ts +15 -0
- package/dist/config/app-paths.js +33 -2
- package/dist/config/env.d.ts +4 -1
- package/dist/config/env.js +28 -2
- package/dist/config/kit-resolution.d.ts +93 -0
- package/dist/config/kit-resolution.js +226 -0
- package/dist/config/load.d.ts +10 -0
- package/dist/config/load.js +8 -1
- package/dist/config/model-kits.d.ts +224 -0
- package/dist/config/model-kits.js +302 -0
- package/dist/config/model-profile-store.d.ts +25 -0
- package/dist/config/model-profiles.d.ts +41 -3
- package/dist/config/model-profiles.js +46 -5
- package/dist/config/schema.d.ts +84 -0
- package/dist/config/schema.js +39 -0
- package/dist/config/store.js +12 -1
- package/dist/config/ui-state.d.ts +18 -0
- package/dist/config/ui-state.js +9 -0
- package/dist/exec/events.d.ts +25 -0
- package/dist/exec/events.js +4 -0
- package/dist/exec/index.js +16 -1
- package/dist/exec/runner.js +10 -0
- package/dist/input/keymap.js +18 -4
- package/dist/proc/limits.d.ts +16 -0
- package/dist/proc/limits.js +16 -0
- package/dist/proc/supervisor.d.ts +20 -1
- package/dist/proc/supervisor.js +42 -14
- package/dist/prompts/command.d.ts +34 -0
- package/dist/prompts/command.js +237 -0
- package/dist/prompts/copy.d.ts +89 -0
- package/dist/prompts/copy.js +131 -0
- package/dist/prompts/fill.d.ts +51 -0
- package/dist/prompts/fill.js +46 -0
- package/dist/prompts/limits.d.ts +53 -0
- package/dist/prompts/limits.js +53 -0
- package/dist/prompts/normalize.d.ts +53 -0
- package/dist/prompts/normalize.js +190 -0
- package/dist/prompts/resolve.d.ts +31 -0
- package/dist/prompts/resolve.js +60 -0
- package/dist/prompts/search.d.ts +58 -0
- package/dist/prompts/search.js +146 -0
- package/dist/prompts/store.d.ts +86 -0
- package/dist/prompts/store.js +453 -0
- package/dist/prompts/types.d.ts +204 -0
- package/dist/prompts/types.js +13 -0
- package/dist/session/checkpoint.d.ts +8 -1
- package/dist/session/checkpoint.js +6 -0
- package/dist/session/persist.d.ts +19 -1
- package/dist/session/persist.js +13 -1
- package/dist/session/startup-session.d.ts +7 -2
- package/dist/session/startup-session.js +5 -2
- package/dist/session/store.js +3 -0
- package/dist/session/validate-session.js +64 -1
- package/dist/team/limits.d.ts +25 -0
- package/dist/team/limits.js +26 -0
- package/dist/team/runtime.js +27 -2
- package/dist/team/settled.d.ts +57 -0
- package/dist/team/settled.js +87 -0
- package/dist/team/subagent.js +48 -2
- package/dist/team/timeline.d.ts +74 -0
- package/dist/team/timeline.js +101 -0
- package/dist/team/types.d.ts +24 -1
- package/dist/todo/acceptance.d.ts +120 -0
- package/dist/todo/acceptance.js +399 -0
- package/dist/todo/follow-through.d.ts +48 -23
- package/dist/todo/follow-through.js +110 -44
- package/dist/todo/limits.d.ts +62 -1
- package/dist/todo/limits.js +62 -1
- package/dist/todo/panel-rows.d.ts +29 -39
- package/dist/todo/panel-rows.js +37 -83
- package/dist/todo/plan-sections.d.ts +60 -0
- package/dist/todo/plan-sections.js +108 -0
- package/dist/todo/prompt.d.ts +17 -12
- package/dist/todo/prompt.js +59 -40
- package/dist/todo/row-window.d.ts +51 -0
- package/dist/todo/row-window.js +107 -0
- package/dist/todo/store.d.ts +139 -31
- package/dist/todo/store.js +271 -73
- package/dist/todo/todo-tool.d.ts +33 -8
- package/dist/todo/todo-tool.js +186 -30
- package/dist/todo/types.d.ts +58 -0
- package/dist/ui/App.js +414 -54
- package/dist/ui/Composer.d.ts +6 -2
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/Header.d.ts +17 -1
- package/dist/ui/Header.js +8 -2
- package/dist/ui/PromptInput.d.ts +21 -1
- package/dist/ui/PromptInput.js +22 -2
- package/dist/ui/ScrollbackShell.d.ts +22 -7
- package/dist/ui/ScrollbackShell.js +8 -1
- package/dist/ui/StatusBar.d.ts +18 -0
- package/dist/ui/StatusBar.js +11 -3
- package/dist/ui/TeamPanel.d.ts +35 -41
- package/dist/ui/TeamPanel.js +53 -163
- package/dist/ui/TodoPanel.d.ts +17 -14
- package/dist/ui/TodoPanel.js +146 -38
- package/dist/ui/Transcript.js +23 -3
- package/dist/ui/composer-input.d.ts +10 -0
- package/dist/ui/composer-input.js +57 -24
- package/dist/ui/entries/TeamCard.d.ts +18 -2
- package/dist/ui/entries/TeamCard.js +47 -13
- package/dist/ui/entries/TodoCard.d.ts +26 -11
- package/dist/ui/entries/TodoCard.js +56 -15
- package/dist/ui/glyphs.d.ts +19 -0
- package/dist/ui/glyphs.js +4 -0
- package/dist/ui/layout/agents-board.d.ts +102 -0
- package/dist/ui/layout/agents-board.js +389 -0
- package/dist/ui/layout/cells.d.ts +61 -0
- package/dist/ui/layout/cells.js +156 -0
- package/dist/ui/layout/header-hints.d.ts +23 -3
- package/dist/ui/layout/header-hints.js +73 -7
- package/dist/ui/layout/kit-chip.d.ts +50 -0
- package/dist/ui/layout/kit-chip.js +81 -0
- package/dist/ui/layout/status-detail-layout.js +9 -0
- package/dist/ui/layout/status-layout.d.ts +13 -0
- package/dist/ui/layout/team-row.d.ts +139 -0
- package/dist/ui/layout/team-row.js +246 -0
- package/dist/ui/layout/todo-layout.d.ts +38 -0
- package/dist/ui/layout/todo-layout.js +75 -0
- package/dist/ui/layout/virtual-window.js +32 -3
- package/dist/ui/overlays/AgentsBoardOverlay.d.ts +57 -0
- package/dist/ui/overlays/AgentsBoardOverlay.js +183 -0
- package/dist/ui/overlays/HelpOverlay.js +24 -2
- package/dist/ui/overlays/KitEditor.d.ts +23 -0
- package/dist/ui/overlays/KitEditor.js +101 -0
- package/dist/ui/overlays/KitManager.d.ts +23 -0
- package/dist/ui/overlays/KitManager.js +104 -0
- package/dist/ui/overlays/KitPicker.d.ts +49 -0
- package/dist/ui/overlays/KitPicker.js +58 -0
- package/dist/ui/overlays/ModelProfilePicker.js +16 -6
- package/dist/ui/overlays/PromptEditorPage.d.ts +48 -0
- package/dist/ui/overlays/PromptEditorPage.js +227 -0
- package/dist/ui/overlays/PromptLibraryOverlay.d.ts +37 -0
- package/dist/ui/overlays/PromptLibraryOverlay.js +191 -0
- package/dist/ui/overlays/PromptPicker.d.ts +36 -0
- package/dist/ui/overlays/PromptPicker.js +129 -0
- package/dist/ui/overlays/PromptTextArea.d.ts +86 -0
- package/dist/ui/overlays/PromptTextArea.js +141 -0
- package/dist/ui/overlays/SettingsScreen.js +139 -16
- package/dist/ui/overlays/kit-picker-state.d.ts +87 -0
- package/dist/ui/overlays/kit-picker-state.js +136 -0
- package/dist/ui/overlays/kit-settings-state.d.ts +129 -0
- package/dist/ui/overlays/kit-settings-state.js +267 -0
- package/dist/ui/overlays/model-profile-state.d.ts +26 -3
- package/dist/ui/overlays/model-profile-state.js +59 -6
- package/dist/ui/overlays/prompt-library-state.d.ts +162 -0
- package/dist/ui/overlays/prompt-library-state.js +335 -0
- package/dist/ui/overlays/prompt-picker-state.d.ts +71 -0
- package/dist/ui/overlays/prompt-picker-state.js +98 -0
- package/dist/ui/scrollback/LiveTail.d.ts +2 -2
- package/dist/ui/scrollback/LiveTail.js +7 -2
- package/dist/ui/scrollback/band.js +14 -1
- package/dist/ui/scrollback/live-preview.js +10 -3
- package/dist/ui/scrollback/tail-window.d.ts +12 -0
- package/dist/ui/scrollback/tail-window.js +17 -2
- package/dist/ui/scrollback/use-scrollback-view.js +7 -0
- package/dist/ui/team-status.d.ts +166 -0
- package/dist/ui/team-status.js +252 -0
- package/dist/ui/transcript-text.js +13 -4
- package/dist/ui/use-startup-notices.d.ts +35 -1
- package/dist/ui/use-startup-notices.js +29 -2
- package/dist/ui/use-wheel-routing.js +10 -2
- package/package.json +2 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TodoSnapshot } from '../../todo/types.js';
|
|
1
2
|
export interface TodoRailLayoutInput {
|
|
2
3
|
cols: number;
|
|
3
4
|
panelEnabled: boolean;
|
|
@@ -22,3 +23,40 @@ export interface TodoRailLayout {
|
|
|
22
23
|
* never depends on menu occupancy or rail visibility, preventing feedback loops.
|
|
23
24
|
*/
|
|
24
25
|
export declare function buildTodoRailLayout(input: TodoRailLayoutInput): TodoRailLayout;
|
|
26
|
+
export interface AcceptanceChipInput {
|
|
27
|
+
snapshot: TodoSnapshot | null;
|
|
28
|
+
/** Rows the rail (in-band or full-screen) actually drew; 0 means it drew none. */
|
|
29
|
+
railRows: number;
|
|
30
|
+
width: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Does the acceptance count still need the status-bar chip
|
|
34
|
+
* (todo-acceptance-criteria §6.11)?
|
|
35
|
+
*
|
|
36
|
+
* HERE, NEXT TO `buildTodoRailLayout`, because it answers the same question from
|
|
37
|
+
* the same inputs: "what did the rail actually draw". Putting it in `App` - a
|
|
38
|
+
* 3,215-line registered oversize file - would give one fact a second owner, which
|
|
39
|
+
* is precisely what `scrollback/band.ts`'s "one number, one owner" rule exists to
|
|
40
|
+
* prevent (P1-8 / C-9).
|
|
41
|
+
*
|
|
42
|
+
* THE RULE IS ABOUT THE RAIL, NOT ABOUT THE SECTION: while the section is `visible`
|
|
43
|
+
* or `summary` the rail is already reporting the count, so a chip would repeat it in
|
|
44
|
+
* the same glance (RV-P2-4). Only two situations leave the count unreported - no rail
|
|
45
|
+
* at all, and the single `rows === 3 && footer` corner where the panel is up but the
|
|
46
|
+
* section could not be fitted.
|
|
47
|
+
*/
|
|
48
|
+
export declare function acceptanceChip(input: AcceptanceChipInput): {
|
|
49
|
+
checked: number;
|
|
50
|
+
total: number;
|
|
51
|
+
failed: number;
|
|
52
|
+
} | null;
|
|
53
|
+
/**
|
|
54
|
+
* Which rung the acceptance section reached in the rail, for `/perf`'s band line
|
|
55
|
+
* (§6.9.6).
|
|
56
|
+
*
|
|
57
|
+
* `n/a` means "this plan has no criteria", which is a different fact from `absent`
|
|
58
|
+
* ("the plan has criteria and the rail could not show them"). Both come from the
|
|
59
|
+
* same `buildPlanSections` call the rail rendered with, so the readout cannot
|
|
60
|
+
* disagree with the screen.
|
|
61
|
+
*/
|
|
62
|
+
export declare function acceptanceRung(input: AcceptanceChipInput): 'visible' | 'summary' | 'absent' | 'n/a';
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { TODO_LIMITS } from '../../todo/limits.js';
|
|
2
|
+
import { derivePlanPhase } from '../../todo/acceptance.js';
|
|
3
|
+
import { buildPlanSections } from '../../todo/plan-sections.js';
|
|
2
4
|
import { todoRailWidth } from './rail.js';
|
|
3
5
|
/** Allocate columns and rows from the same team/menu projections as the renderers.
|
|
4
6
|
* Invalid geometry becomes zero, excess team/menu rows are clamped. Menu capacity
|
|
@@ -25,3 +27,76 @@ export function buildTodoRailLayout(input) {
|
|
|
25
27
|
function nonNegativeInt(value) {
|
|
26
28
|
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
27
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Does the acceptance count still need the status-bar chip
|
|
32
|
+
* (todo-acceptance-criteria §6.11)?
|
|
33
|
+
*
|
|
34
|
+
* HERE, NEXT TO `buildTodoRailLayout`, because it answers the same question from
|
|
35
|
+
* the same inputs: "what did the rail actually draw". Putting it in `App` - a
|
|
36
|
+
* 3,215-line registered oversize file - would give one fact a second owner, which
|
|
37
|
+
* is precisely what `scrollback/band.ts`'s "one number, one owner" rule exists to
|
|
38
|
+
* prevent (P1-8 / C-9).
|
|
39
|
+
*
|
|
40
|
+
* THE RULE IS ABOUT THE RAIL, NOT ABOUT THE SECTION: while the section is `visible`
|
|
41
|
+
* or `summary` the rail is already reporting the count, so a chip would repeat it in
|
|
42
|
+
* the same glance (RV-P2-4). Only two situations leave the count unreported - no rail
|
|
43
|
+
* at all, and the single `rows === 3 && footer` corner where the panel is up but the
|
|
44
|
+
* section could not be fitted.
|
|
45
|
+
*/
|
|
46
|
+
export function acceptanceChip(input) {
|
|
47
|
+
const acceptance = input.snapshot?.acceptance;
|
|
48
|
+
if (!acceptance || acceptance.total === 0)
|
|
49
|
+
return null;
|
|
50
|
+
const chip = {
|
|
51
|
+
checked: acceptance.checkedCount,
|
|
52
|
+
total: acceptance.total,
|
|
53
|
+
failed: acceptance.failedCount,
|
|
54
|
+
};
|
|
55
|
+
if (input.railRows <= 0)
|
|
56
|
+
return chip;
|
|
57
|
+
return acceptanceSectionOf(input) === 'absent' ? chip : null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Which rung the acceptance section reached in the rail, for `/perf`'s band line
|
|
61
|
+
* (§6.9.6).
|
|
62
|
+
*
|
|
63
|
+
* `n/a` means "this plan has no criteria", which is a different fact from `absent`
|
|
64
|
+
* ("the plan has criteria and the rail could not show them"). Both come from the
|
|
65
|
+
* same `buildPlanSections` call the rail rendered with, so the readout cannot
|
|
66
|
+
* disagree with the screen.
|
|
67
|
+
*/
|
|
68
|
+
export function acceptanceRung(input) {
|
|
69
|
+
const acceptance = input.snapshot?.acceptance;
|
|
70
|
+
if (!acceptance || acceptance.total === 0)
|
|
71
|
+
return 'n/a';
|
|
72
|
+
// No rail drawn at all: the section is not on screen, and saying `absent` is the
|
|
73
|
+
// one honest answer for that.
|
|
74
|
+
if (input.railRows <= 0)
|
|
75
|
+
return 'absent';
|
|
76
|
+
return acceptanceSectionOf(input) ?? 'absent';
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The section's reason for a given rail, or `null` when the plan has no criteria.
|
|
80
|
+
*
|
|
81
|
+
* `showGauge` is assumed TRUE. That is the conservative direction for the ROW BUDGET -
|
|
82
|
+
* it can only make the rail look shorter - and it cannot change the REASON at all: the
|
|
83
|
+
* compact rung (rows < 6, where the real gauge is always off) ignores `available`
|
|
84
|
+
* entirely, and the full rung spends the difference on item rows. So this readout can
|
|
85
|
+
* never disagree with the rail about `visible` / `summary` / `absent`, and the chip
|
|
86
|
+
* therefore never duplicates a count the rail is already showing (RV-P2-4).
|
|
87
|
+
*/
|
|
88
|
+
function acceptanceSectionOf(input) {
|
|
89
|
+
const snapshot = input.snapshot;
|
|
90
|
+
const acceptance = snapshot?.acceptance;
|
|
91
|
+
if (!snapshot || !acceptance || acceptance.total === 0)
|
|
92
|
+
return null;
|
|
93
|
+
const sections = buildPlanSections({
|
|
94
|
+
rows: input.railRows,
|
|
95
|
+
width: input.width,
|
|
96
|
+
phase: derivePlanPhase(snapshot.items, acceptance.items),
|
|
97
|
+
todoCount: snapshot.items.length,
|
|
98
|
+
acceptanceCount: acceptance.total,
|
|
99
|
+
showGauge: true,
|
|
100
|
+
});
|
|
101
|
+
return sections.acceptance?.reason ?? null;
|
|
102
|
+
}
|
|
@@ -38,6 +38,7 @@ import { COMPACTION_LIMITS } from '../../compaction/limits.js';
|
|
|
38
38
|
import { GUTTER_WIDTH } from './Gutter.js';
|
|
39
39
|
import { expandTabs } from '../display-tab.js';
|
|
40
40
|
import { USER_ENTRY_MAX_ROWS } from '../composer-limits.js';
|
|
41
|
+
import { isLiveTeamCard } from '../team-status.js';
|
|
41
42
|
export const VIRTUAL_LIMITS = {
|
|
42
43
|
/**
|
|
43
44
|
* Entries kept mounted on each side of the visible band.
|
|
@@ -95,7 +96,17 @@ export function entryRevision(entry) {
|
|
|
95
96
|
.map((r) => r.phase[0])
|
|
96
97
|
.join('')}.${entry.runs.reduce((acc, r) => acc + r.toolCalls, 0)}`;
|
|
97
98
|
case 'todo':
|
|
98
|
-
|
|
99
|
+
// REVISION MUST COVER EVERY FIELD THAT CAN CHANGE THE HEIGHT (§6.10.1). A
|
|
100
|
+
// criterion going `pending` -> `failed` changes NONE of `items.length`,
|
|
101
|
+
// `doneCount` or `total`, yet it adds an evidence row; without
|
|
102
|
+
// `checkedCount` and `failedCount` here the height cache would keep the old
|
|
103
|
+
// measurement and the cache would be silently wrong - textbook form of "add a
|
|
104
|
+
// field, forget the revision".
|
|
105
|
+
return `d${entry.live ? 1 : 0}.${entry.doneCount}.${entry.total}.${entry.items.length}`
|
|
106
|
+
+ `.a${entry.acceptance
|
|
107
|
+
? `${entry.acceptance.items.length}.${entry.acceptance.checkedCount}` +
|
|
108
|
+
`.${entry.acceptance.failedCount}`
|
|
109
|
+
: '-'}`;
|
|
99
110
|
/**
|
|
100
111
|
* The retry card is ONE ROW IN EVERY PHASE, so its revision carries the phase
|
|
101
112
|
* and the counter but deliberately NOT the countdown: `resumeAt` changes the
|
|
@@ -315,9 +326,27 @@ export function estimateEntryRows(entry, cols, density, expanded, thinkingVisibl
|
|
|
315
326
|
return separation + 1 + shown + 1;
|
|
316
327
|
}
|
|
317
328
|
case 'team':
|
|
329
|
+
// A LIVE DISPATCH CARD IS NOT DRAWN AT ALL (tui-subagent-double-roster A-2):
|
|
330
|
+
// the roster is the only live subagent surface, so this entry owes the window
|
|
331
|
+
// ZERO rows. Estimating it at its real height while `EntryView` renders nothing
|
|
332
|
+
// would make `selectWindow` reserve four rows for a card that is not on screen -
|
|
333
|
+
// blank space at the bottom of every dispatch frame, and a window boundary that
|
|
334
|
+
// disagrees with what the printer measures.
|
|
335
|
+
if (isLiveTeamCard(entry))
|
|
336
|
+
return 0;
|
|
318
337
|
return separation + 1 + entry.runs.length + (expanded ? entry.runs.length * 3 + 1 : 1);
|
|
319
|
-
case 'todo':
|
|
320
|
-
|
|
338
|
+
case 'todo': {
|
|
339
|
+
// THE ACCEPTANCE BLOCK'S ROWS, EXACTLY (§6.10.1): one title row plus one row
|
|
340
|
+
// per criterion plus one row per FAILED criterion's evidence. Exact because
|
|
341
|
+
// every one of those lines is `wrap="truncate"`; the moment one of them wraps,
|
|
342
|
+
// this stops being an equality and becomes an upper bound - and the failure
|
|
343
|
+
// mode is scroll drift, which nothing reports.
|
|
344
|
+
const acceptance = entry.acceptance;
|
|
345
|
+
const acceptanceRows = acceptance && acceptance.total > 0
|
|
346
|
+
? 1 + acceptance.items.length + acceptance.failedCount
|
|
347
|
+
: 0;
|
|
348
|
+
return separation + 1 + entry.items.length + acceptanceRows;
|
|
349
|
+
}
|
|
321
350
|
// ONE ROW, ALWAYS. The card is `wrap="truncate"` in every phase, so this is an
|
|
322
351
|
// exact height rather than an upper bound.
|
|
323
352
|
case 'retry':
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentsBoardOverlay — the read-only detail board behind `Ctrl+B` / `/agents`
|
|
3
|
+
* (tui-subagent-panel §4.4 / §6.3).
|
|
4
|
+
*
|
|
5
|
+
* READ-ONLY IS THE SAFETY BOUNDARY (§2.2). Nothing here nudges, kills or re-dispatches
|
|
6
|
+
* a child, and no new `TeamEvent` member was introduced to feed it: a board that can
|
|
7
|
+
* only read can never kill a healthy child, which is the whole reason this is a
|
|
8
|
+
* second surface rather than a control panel.
|
|
9
|
+
*
|
|
10
|
+
* ONE SCREEN, CONTROLLED MODE. `OverlayFrame`'s controlled form already owns paging,
|
|
11
|
+
* the position indicator and the narrow-terminal degradation, and `helpRows` /
|
|
12
|
+
* `projectQueueRows` are the precedent. A second pane would mean a second horizontal
|
|
13
|
+
* budget inside Ink for a benefit the board's 2-up field rows already deliver.
|
|
14
|
+
*
|
|
15
|
+
* IT DOES NOT GO BLANK WHEN THE DISPATCH ENDS (D-9). `state.team` becomes `null` the
|
|
16
|
+
* frame the dispatch settles, and a reader would lose the child they were reading
|
|
17
|
+
* about at the exact moment they wanted the post-mortem. The last live snapshot is
|
|
18
|
+
* therefore FROZEN (a copy, held in a ref) and keeps rendering with a
|
|
19
|
+
* `dispatch ended` row; a board opened AFTER the dispatch has ended builds from the
|
|
20
|
+
* transcript's settled `team` entry instead (`resolveBoardSource`), which is where the
|
|
21
|
+
* `result` section earns its place - the error is the one fact that is never stripped.
|
|
22
|
+
*
|
|
23
|
+
* NO `ink-spinner` HERE (D-13): `spinner-census.test.ts` pins the exact list of files
|
|
24
|
+
* that mount an animated spinner, and this is a reading surface, not an animation.
|
|
25
|
+
* The panel is where motion belongs.
|
|
26
|
+
*
|
|
27
|
+
* AND NO MOTION PROP (v2 implementation note, appended to the spec's
|
|
28
|
+
* "implementation findings"). Because nothing here animates, a motion flag would be a
|
|
29
|
+
* prop with no consumer - and worse, the same census counts how many times `App.tsx`
|
|
30
|
+
* hands the RAW motion flag to a child and asserts the count is EXACTLY ONE, so passing
|
|
31
|
+
* it from the board's branch would break a shipped guard rail for no gain.
|
|
32
|
+
*
|
|
33
|
+
* ASCII ONLY: every symbol comes through `glyphs`.
|
|
34
|
+
*/
|
|
35
|
+
import React from 'react';
|
|
36
|
+
import type { Entry } from '../../agent/reducer.js';
|
|
37
|
+
import type { Theme } from '../theme.js';
|
|
38
|
+
import type { TermCapabilities } from '../capabilities.js';
|
|
39
|
+
import type { TeamSnapshot } from '../../team/types.js';
|
|
40
|
+
export interface AgentsBoardOverlayProps {
|
|
41
|
+
/** The live snapshot; `null` once the dispatch has settled. */
|
|
42
|
+
team: TeamSnapshot | null;
|
|
43
|
+
/** The transcript, for the settled fallback (D-9 path 4). */
|
|
44
|
+
entries: readonly Entry[];
|
|
45
|
+
/** Overlay content width (`App`'s `viewCols`). */
|
|
46
|
+
cols: number;
|
|
47
|
+
maxRows: number;
|
|
48
|
+
scrollOffset: number;
|
|
49
|
+
onScrollClamp: (offset: number) => void;
|
|
50
|
+
isActive: boolean;
|
|
51
|
+
native: boolean;
|
|
52
|
+
theme: Theme;
|
|
53
|
+
caps: TermCapabilities;
|
|
54
|
+
/** Injectable clock, so the board's tests do not depend on wall time. */
|
|
55
|
+
now?: number;
|
|
56
|
+
}
|
|
57
|
+
export declare function AgentsBoardOverlay({ team, entries, cols, maxRows, scrollOffset, onScrollClamp, isActive, native, theme, caps, now, }: AgentsBoardOverlayProps): React.ReactElement;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* AgentsBoardOverlay — the read-only detail board behind `Ctrl+B` / `/agents`
|
|
4
|
+
* (tui-subagent-panel §4.4 / §6.3).
|
|
5
|
+
*
|
|
6
|
+
* READ-ONLY IS THE SAFETY BOUNDARY (§2.2). Nothing here nudges, kills or re-dispatches
|
|
7
|
+
* a child, and no new `TeamEvent` member was introduced to feed it: a board that can
|
|
8
|
+
* only read can never kill a healthy child, which is the whole reason this is a
|
|
9
|
+
* second surface rather than a control panel.
|
|
10
|
+
*
|
|
11
|
+
* ONE SCREEN, CONTROLLED MODE. `OverlayFrame`'s controlled form already owns paging,
|
|
12
|
+
* the position indicator and the narrow-terminal degradation, and `helpRows` /
|
|
13
|
+
* `projectQueueRows` are the precedent. A second pane would mean a second horizontal
|
|
14
|
+
* budget inside Ink for a benefit the board's 2-up field rows already deliver.
|
|
15
|
+
*
|
|
16
|
+
* IT DOES NOT GO BLANK WHEN THE DISPATCH ENDS (D-9). `state.team` becomes `null` the
|
|
17
|
+
* frame the dispatch settles, and a reader would lose the child they were reading
|
|
18
|
+
* about at the exact moment they wanted the post-mortem. The last live snapshot is
|
|
19
|
+
* therefore FROZEN (a copy, held in a ref) and keeps rendering with a
|
|
20
|
+
* `dispatch ended` row; a board opened AFTER the dispatch has ended builds from the
|
|
21
|
+
* transcript's settled `team` entry instead (`resolveBoardSource`), which is where the
|
|
22
|
+
* `result` section earns its place - the error is the one fact that is never stripped.
|
|
23
|
+
*
|
|
24
|
+
* NO `ink-spinner` HERE (D-13): `spinner-census.test.ts` pins the exact list of files
|
|
25
|
+
* that mount an animated spinner, and this is a reading surface, not an animation.
|
|
26
|
+
* The panel is where motion belongs.
|
|
27
|
+
*
|
|
28
|
+
* AND NO MOTION PROP (v2 implementation note, appended to the spec's
|
|
29
|
+
* "implementation findings"). Because nothing here animates, a motion flag would be a
|
|
30
|
+
* prop with no consumer - and worse, the same census counts how many times `App.tsx`
|
|
31
|
+
* hands the RAW motion flag to a child and asserts the count is EXACTLY ONE, so passing
|
|
32
|
+
* it from the board's branch would break a shipped guard rail for no gain.
|
|
33
|
+
*
|
|
34
|
+
* ASCII ONLY: every symbol comes through `glyphs`.
|
|
35
|
+
*/
|
|
36
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
37
|
+
import { Text, useInput } from 'ink';
|
|
38
|
+
import { pickGlyphs } from '../glyphs.js';
|
|
39
|
+
import { OverlayFrame } from '../layout/OverlayFrame.js';
|
|
40
|
+
import { buildAgentBoardRows, resolveBoardSource, resolveSelection, } from '../layout/agents-board.js';
|
|
41
|
+
/** `OverlayFrame`'s own breakpoint, mirrored so the chrome estimate cannot drift. */
|
|
42
|
+
const FRAME_BORDERLESS_COLS = 48;
|
|
43
|
+
/**
|
|
44
|
+
* Close a frozen live snapshot: the dispatch it described has ended.
|
|
45
|
+
*
|
|
46
|
+
* `durationMs` is derived from the LAST frame the snapshot was live, so the board's
|
|
47
|
+
* summary shows the dispatch's real total instead of a clock that keeps running against
|
|
48
|
+
* a dispatch nobody can see any more. When the snapshot never had a `startedAt` there is
|
|
49
|
+
* nothing to derive and the field stays absent - the LIVE-ONLY rule turns that into an
|
|
50
|
+
* empty clock cell rather than an invented number (§4.4.2).
|
|
51
|
+
*/
|
|
52
|
+
function settleFrozen(frozen) {
|
|
53
|
+
const { source, at } = frozen;
|
|
54
|
+
const total = source.startedAt === undefined ? undefined : Math.max(0, at - source.startedAt);
|
|
55
|
+
return {
|
|
56
|
+
...source,
|
|
57
|
+
active: false,
|
|
58
|
+
...(total !== undefined ? { durationMs: total } : {}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Palette slot -> theme colour. One place, so a tone can never be spelled twice. */
|
|
62
|
+
function toneColor(tone, theme) {
|
|
63
|
+
switch (tone) {
|
|
64
|
+
case 'accent':
|
|
65
|
+
return theme.accent;
|
|
66
|
+
case 'primary':
|
|
67
|
+
return theme.primary;
|
|
68
|
+
case 'muted':
|
|
69
|
+
return theme.muted;
|
|
70
|
+
case 'warn':
|
|
71
|
+
return theme.noticeWarn;
|
|
72
|
+
case 'error':
|
|
73
|
+
return theme.noticeError;
|
|
74
|
+
case 'done':
|
|
75
|
+
return theme.toolDone;
|
|
76
|
+
case 'hint':
|
|
77
|
+
return theme.hintFg;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export function AgentsBoardOverlay({ team, entries, cols, maxRows, scrollOffset, onScrollClamp, isActive, native, theme, caps, now, }) {
|
|
81
|
+
const glyphs = pickGlyphs(caps);
|
|
82
|
+
const at = now ?? Date.now();
|
|
83
|
+
const [selectedLabel, setSelectedLabel] = useState(null);
|
|
84
|
+
/**
|
|
85
|
+
* THE FROZEN SNAPSHOT (D-9), plus the clock reading it was frozen at.
|
|
86
|
+
*
|
|
87
|
+
* A ref rather than state on purpose: it is written from an effect that must not
|
|
88
|
+
* trigger a render of its own, and reading it during the render that FIRST sees
|
|
89
|
+
* `team === null` is what keeps the board from blinking empty for one frame. The
|
|
90
|
+
* timestamp is kept WITH the snapshot because a frozen live source still says
|
|
91
|
+
* `active: true` - it was live when it was copied - and a board that kept believing
|
|
92
|
+
* that would never show its `dispatch ended` row nor stop its clock.
|
|
93
|
+
*/
|
|
94
|
+
const frozen = useRef(null);
|
|
95
|
+
const live = team === null ? null : resolveBoardSource({ team, entries });
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (live !== null)
|
|
98
|
+
frozen.current = { source: live, at };
|
|
99
|
+
}, [live, at]);
|
|
100
|
+
// PRIORITY: live > frozen > the most recent settled entry (§4.4.5). The frozen copy
|
|
101
|
+
// only ever takes over on the frame the live snapshot disappears, and a NEW dispatch
|
|
102
|
+
// overwrites it on its own first frame, so the two paths cannot both be active.
|
|
103
|
+
//
|
|
104
|
+
// A FROZEN SOURCE IS SETTLED, and it is closed here rather than at the moment of
|
|
105
|
+
// copying: `active: false` is what draws `dispatch ended` (task AC-17), and taking
|
|
106
|
+
// `durationMs` from the freeze instant stops the clock at the dispatch's real end
|
|
107
|
+
// instead of letting it count up forever from a snapshot that no longer exists.
|
|
108
|
+
const frozenSource = frozen.current === null ? null : settleFrozen(frozen.current);
|
|
109
|
+
const source = live ?? frozenSource ?? resolveBoardSource({ team: null, entries });
|
|
110
|
+
const selected = source === null ? null : resolveSelection(source.runs, selectedLabel);
|
|
111
|
+
/**
|
|
112
|
+
* UP/DOWN MOVES THE CHILD CURSOR, and a change of child RESETS the scroll.
|
|
113
|
+
*
|
|
114
|
+
* `agents` deliberately does NOT join `App`'s row-scroll set (`help | plan | queue`):
|
|
115
|
+
* the same trade `settings` makes with its field cursor, where Up/Down is worth more
|
|
116
|
+
* as navigation than as a six-row scroll. `PgUp` / `PgDn` / the wheel still page the
|
|
117
|
+
* board through `App`'s `controlled` set, so no reading gesture is lost.
|
|
118
|
+
*/
|
|
119
|
+
const move = useCallback((delta) => {
|
|
120
|
+
const runs = source?.runs ?? [];
|
|
121
|
+
if (runs.length === 0)
|
|
122
|
+
return;
|
|
123
|
+
const current = resolveSelection(runs, selectedLabel);
|
|
124
|
+
const index = current === null ? -1 : runs.findIndex((run) => run.label === current.label);
|
|
125
|
+
const next = runs[Math.min(runs.length - 1, Math.max(0, index + delta))];
|
|
126
|
+
if (next === undefined || next.label === current?.label)
|
|
127
|
+
return;
|
|
128
|
+
setSelectedLabel(next.label);
|
|
129
|
+
// The documented semantic of this port is "the offset should now be 0" (§6.3).
|
|
130
|
+
onScrollClamp(0);
|
|
131
|
+
}, [onScrollClamp, selectedLabel, source]);
|
|
132
|
+
useInput((_input, key) => {
|
|
133
|
+
if (!isActive)
|
|
134
|
+
return;
|
|
135
|
+
if (key.upArrow)
|
|
136
|
+
move(-1);
|
|
137
|
+
else if (key.downArrow)
|
|
138
|
+
move(1);
|
|
139
|
+
}, { isActive });
|
|
140
|
+
// THE FRAME'S CHROME IS NOT FREE (v2 implementation note). `cols` arrives as the
|
|
141
|
+
// overlay's FRAME width (`App`'s `viewCols`), and `OverlayFrame` spends 4 of those
|
|
142
|
+
// columns on its border plus `paddingX={1}` (2 in the narrow rail form, which drops
|
|
143
|
+
// the right border and the right padding). A board laid out against the frame width
|
|
144
|
+
// therefore has its LAST four columns eaten by Ink's truncation - which lands exactly
|
|
145
|
+
// on the right-anchored figures this board exists to align. `helpRows` gets away with
|
|
146
|
+
// it because its rows are prose; these are a table.
|
|
147
|
+
const contentCols = Math.max(1, cols - (cols >= FRAME_BORDERLESS_COLS ? 4 : 2));
|
|
148
|
+
const rows = source === null
|
|
149
|
+
? [{
|
|
150
|
+
key: 'empty',
|
|
151
|
+
kind: 'hint',
|
|
152
|
+
text: 'No subagent dispatch yet.',
|
|
153
|
+
tone: 'muted',
|
|
154
|
+
}]
|
|
155
|
+
: buildAgentBoardRows({
|
|
156
|
+
source,
|
|
157
|
+
cols: contentCols,
|
|
158
|
+
now: at,
|
|
159
|
+
selectedLabel: selected?.label ?? null,
|
|
160
|
+
glyphs,
|
|
161
|
+
native,
|
|
162
|
+
});
|
|
163
|
+
// The `dispatch ended` banner is a ROW rather than a title swap: the title is the
|
|
164
|
+
// frame's own ("Agents"), and replacing it would hide which screen the reader is on.
|
|
165
|
+
const endedRow = source !== null && !source.active
|
|
166
|
+
? [{
|
|
167
|
+
key: 'ended',
|
|
168
|
+
kind: 'hint',
|
|
169
|
+
text: `dispatch ended ${glyphs.midDot} Esc to close`,
|
|
170
|
+
tone: 'warn',
|
|
171
|
+
}]
|
|
172
|
+
: [];
|
|
173
|
+
const elements = [...endedRow, ...rows].map((row) => (_jsx(Text, { wrap: "truncate", color: toneColor(row.tone, theme), ...(row.kind === 'section' ? { bold: true } : {}), children: row.text }, row.key)));
|
|
174
|
+
// THE HINT SHRINKS WITH THE FRAME. `OverlayFrame` prints the hint as one unwrapped
|
|
175
|
+
// row, so a 41-column hint inside a 30-column frame makes the FRAME 41 wide - the
|
|
176
|
+
// board would overflow the terminal and take the overlay's border with it.
|
|
177
|
+
const hint = contentCols >= 60
|
|
178
|
+
? `Esc close ${glyphs.midDot} Up/Down child ${glyphs.midDot} PgUp/PgDn scroll`
|
|
179
|
+
: contentCols >= 30
|
|
180
|
+
? `Esc close ${glyphs.midDot} Up/Down child`
|
|
181
|
+
: 'Esc close';
|
|
182
|
+
return (_jsx(OverlayFrame, { title: "Agents", hint: hint, maxRows: maxRows, cols: cols, rows: elements, scrollOffset: scrollOffset, onScrollClamp: onScrollClamp, theme: theme, caps: caps }));
|
|
183
|
+
}
|
|
@@ -34,7 +34,12 @@ function allKeyRows(times) {
|
|
|
34
34
|
// terminal (`showHint`) and `hints: false`. A key that only exists behind
|
|
35
35
|
// either of them is a key the affected user never learns about, which is the
|
|
36
36
|
// one-shot notice's failure repeated with a different switch.
|
|
37
|
-
|
|
37
|
+
// BOTH KEYS ARE SPELLED OUT, and `Ctrl+N` is written in FULL: `^N` is this
|
|
38
|
+
// product's word for transcript line offset (the header chip and the row below),
|
|
39
|
+
// so abbreviating the mode fallback would put two meanings of `^` on one screen
|
|
40
|
+
// (quick-prompt-library D-1 / P1-7).
|
|
41
|
+
['Shift+Tab / Ctrl+N', 'Toggle plan mode (same as /plan)'],
|
|
42
|
+
['Ctrl+P', 'Quick prompt library: manage, create, reorder, pick'],
|
|
38
43
|
// The ladder clears a draft first (ctrl-c-clear-draft-exit):
|
|
39
44
|
// with a draft x2 clears the input and x3 exits; an empty input
|
|
40
45
|
// keeps the previous x2 exit.
|
|
@@ -46,7 +51,17 @@ function allKeyRows(times) {
|
|
|
46
51
|
['Ctrl+R', 'Redraw the frame'],
|
|
47
52
|
['Ctrl+T', 'Show/hide thinking (off by default)'],
|
|
48
53
|
['Ctrl+I', 'Build the project index (bundled skill; asks first; needs keyboard enhancement)'],
|
|
54
|
+
// THE KIT KEYS (agent-model-kits D-11 / D-15): `Ctrl+K` moved to the picker
|
|
55
|
+
// and `killToEnd` moved to `Alt+K`. BOTH rows are listed, because an existing
|
|
56
|
+
// user will press the old key by muscle memory and needs to find out where it
|
|
57
|
+
// went - and because `^K` is only advertised in the expanded header row.
|
|
58
|
+
['Ctrl+K', 'Agent kits: pick a preset for this session (see /kit)'],
|
|
59
|
+
['Alt+K', 'Delete from the cursor to the end of the line (was Ctrl+K)'],
|
|
49
60
|
['Ctrl+G', 'Expand/collapse status details; disabled while an overlay is open'],
|
|
61
|
+
// The board's key is listed HERE and in the team table below: the roster shows
|
|
62
|
+
// `ctrl+b` only on a terminal wide enough for the hint cell, and a key a user can
|
|
63
|
+
// only learn from a wide terminal is a key the rest never discover (AC-18).
|
|
64
|
+
['Ctrl+B', 'Agents board: every subagent, its facts and its timeline (read-only)'],
|
|
50
65
|
['PgUp / PgDn', 'Scroll a page (transcript, or this overlay)'],
|
|
51
66
|
['Shift+Up / Shift+Down', 'Scroll the transcript a line (full-screen mode)'],
|
|
52
67
|
['Wheel', 'Scroll the transcript (or the open overlay)'],
|
|
@@ -85,6 +100,7 @@ const COMMANDS = [
|
|
|
85
100
|
['/max-tokens [n|auto]', 'Output token cap; no argument reports the effective one'],
|
|
86
101
|
['/team [on|off|max n]', 'Team subagents: status, switch, fan-out width'],
|
|
87
102
|
['/fast [on|off|model|review]', 'Fast model tier: status, switch, model, review cadence'],
|
|
103
|
+
['/kit [list|use <name>]', 'Agent kits: what is in effect, list them, use one for this session'],
|
|
88
104
|
[
|
|
89
105
|
'/todo [on|off|panel|follow|clear|continue]',
|
|
90
106
|
'Todo planning: status, switches, the rail, follow-through',
|
|
@@ -105,6 +121,11 @@ const COMMANDS = [
|
|
|
105
121
|
['/branch', 'Same as Ctrl+L: branch into a new terminal'],
|
|
106
122
|
['/copy', 'Copy last answer; native completion confirmed, OSC 52 request unconfirmed'],
|
|
107
123
|
['/queue', 'Read every pending message in full; no replay or queue mutation'],
|
|
124
|
+
['/prompts', 'Quick prompt library: pick one and fill the draft (Enter fills)'],
|
|
125
|
+
['/agents', 'Open the subagent board: every child, its facts and its timeline'],
|
|
126
|
+
['/prompts list | add | edit | rm', 'Manage the library; `add` opens the editor'],
|
|
127
|
+
['/prompts move <n> <to> | sort',
|
|
128
|
+
'Reorder by hand, or rewrite the order by name/recent/favorite'],
|
|
108
129
|
['/mouse [on|off]', 'Release the mouse to your terminal, or take it back'],
|
|
109
130
|
['/terminal-setup', 'Show newline key bindings without changing terminal settings'],
|
|
110
131
|
['/exit', 'Exit'],
|
|
@@ -126,7 +147,7 @@ const PLAN_ROWS = [
|
|
|
126
147
|
// this one is the reader whose `Shift+Tab` does nothing, and what they need is
|
|
127
148
|
// the reason, not another synonym. Listing the key in both places instead just
|
|
128
149
|
// says it twice and explains it nowhere.
|
|
129
|
-
['Ctrl+
|
|
150
|
+
['Ctrl+N', 'The same toggle, for Windows consoles that deliver Shift+Tab as a plain Tab'],
|
|
130
151
|
['In PLAN mode', 'write_file, edit_file, bash, skill_install and skill_create are refused'],
|
|
131
152
|
['bash', 'Refused in full, including git status - use read_file / glob / grep'],
|
|
132
153
|
['ask_user', 'The agent asks 1-5 multiple-choice questions; Enter takes the recommendation'],
|
|
@@ -140,6 +161,7 @@ const TEAM_ROWS = [
|
|
|
140
161
|
['/team max <n>', 'Fan-out width, 1-10 (5 by default)'],
|
|
141
162
|
['--team / --no-team', 'Start a session with team mode on or off (also ARAGON_TEAM=0)'],
|
|
142
163
|
['team_send / team_wait', 'How subagents message each other; 6 each, one every 15s'],
|
|
164
|
+
['Ctrl+B / /agents', 'The board: every child, its full facts, its 24-event timeline'],
|
|
143
165
|
];
|
|
144
166
|
const FAST_ROWS = [
|
|
145
167
|
['/fast', 'Status: on/off, the model, the cadence, and this session\'s totals'],
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings > Kit pack > editor (agent-model-kits §4.5).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`.
|
|
5
|
+
*
|
|
6
|
+
* FOUR ROWS AND TWO ACTIONS, and every slot is an explicit decision (D-8): the
|
|
7
|
+
* Main slot is required, the Fast slot is a two-state choice (`off` is a
|
|
8
|
+
* decision, not an omission), and the Subagent slot reuses the existing
|
|
9
|
+
* `Same as main` / `Fast tier model` vocabulary so the summary sentence the root
|
|
10
|
+
* page already prints (`Fast tier (off - falls back to main)`) needs no second
|
|
11
|
+
* wording. Nothing is written until the ROOT page's Save.
|
|
12
|
+
*/
|
|
13
|
+
import React from 'react';
|
|
14
|
+
import type { ModelProfileState } from './model-profile-state.js';
|
|
15
|
+
import type { KitAction, KitDraft } from './kit-settings-state.js';
|
|
16
|
+
import { type ProfileFrameProps } from './ModelProfilePicker.js';
|
|
17
|
+
interface KitEditorProps extends ProfileFrameProps {
|
|
18
|
+
draft: KitDraft;
|
|
19
|
+
state: ModelProfileState;
|
|
20
|
+
dispatch: React.Dispatch<KitAction>;
|
|
21
|
+
}
|
|
22
|
+
export declare function KitEditor(props: KitEditorProps): React.ReactElement;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Settings > Kit pack > editor (agent-model-kits §4.5).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`.
|
|
6
|
+
*
|
|
7
|
+
* FOUR ROWS AND TWO ACTIONS, and every slot is an explicit decision (D-8): the
|
|
8
|
+
* Main slot is required, the Fast slot is a two-state choice (`off` is a
|
|
9
|
+
* decision, not an omission), and the Subagent slot reuses the existing
|
|
10
|
+
* `Same as main` / `Fast tier model` vocabulary so the summary sentence the root
|
|
11
|
+
* page already prints (`Fast tier (off - falls back to main)`) needs no second
|
|
12
|
+
* wording. Nothing is written until the ROOT page's Save.
|
|
13
|
+
*/
|
|
14
|
+
import { useState } from 'react';
|
|
15
|
+
import { Text, useInput } from 'ink';
|
|
16
|
+
import { pickGlyphs } from '../glyphs.js';
|
|
17
|
+
import { OverlayFrame } from '../layout/OverlayFrame.js';
|
|
18
|
+
import { stripPasteFrames } from '../paste-frames.js';
|
|
19
|
+
import { stripEnterFrames } from '../enter-frames.js';
|
|
20
|
+
import { clipProfileText, useProfileScroll } from './ModelProfilePicker.js';
|
|
21
|
+
const ROWS = ['name', 'main', 'subagent', 'fast', 'save', 'cancel'];
|
|
22
|
+
function slotLabel(draft, state) {
|
|
23
|
+
const entries = state.draft.profiles?.entries ?? [];
|
|
24
|
+
const name = (id) => {
|
|
25
|
+
const entry = id ? entries.find((item) => item.id === id) : undefined;
|
|
26
|
+
// `Profile missing` rather than a blank: a kit can be pointed at a profile
|
|
27
|
+
// that was removed on disk, and the editor is where that gets fixed.
|
|
28
|
+
return entry ? `${entry.provider}:${entry.model} (${entry.name})` : id ? 'Profile missing'
|
|
29
|
+
: '(not set)';
|
|
30
|
+
};
|
|
31
|
+
const subagent = draft.subagent.model === 'main' ? 'Same as main'
|
|
32
|
+
: draft.subagent.model === 'fast' ? 'Fast tier model'
|
|
33
|
+
: name(draft.subagent.modelProfileId);
|
|
34
|
+
return { main: name(draft.mainProfileId), fast: name(draft.fastProfileId),
|
|
35
|
+
subagent };
|
|
36
|
+
}
|
|
37
|
+
export function KitEditor(props) {
|
|
38
|
+
const glyphs = pickGlyphs(props.caps);
|
|
39
|
+
const { draft, dispatch } = props;
|
|
40
|
+
const [index, setIndex] = useState(0);
|
|
41
|
+
const row = ROWS[index];
|
|
42
|
+
const offset = useProfileScroll(props, index);
|
|
43
|
+
const labels = slotLabel(draft, props.state);
|
|
44
|
+
const filled = [draft.mainProfileId, draft.fastProfileId,
|
|
45
|
+
draft.subagent.model !== 'profile' || draft.subagent.modelProfileId]
|
|
46
|
+
.filter(Boolean).length;
|
|
47
|
+
function activate(target) {
|
|
48
|
+
if (target === 'main')
|
|
49
|
+
dispatch({ type: 'kit-slot', slot: 'main' });
|
|
50
|
+
if (target === 'fast')
|
|
51
|
+
dispatch({ type: 'kit-slot', slot: 'fast' });
|
|
52
|
+
if (target === 'subagent')
|
|
53
|
+
dispatch({ type: 'kit-slot', slot: 'subagent' });
|
|
54
|
+
if (target === 'save')
|
|
55
|
+
dispatch({ type: 'kit-save' });
|
|
56
|
+
if (target === 'cancel')
|
|
57
|
+
dispatch({ type: 'kit-cancel' });
|
|
58
|
+
}
|
|
59
|
+
useInput((input, key) => {
|
|
60
|
+
if (props.isActive === false)
|
|
61
|
+
return;
|
|
62
|
+
if (key.escape) {
|
|
63
|
+
dispatch({ type: 'kit-cancel' });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (key.upArrow || (key.tab && key.shift)) {
|
|
67
|
+
setIndex((index - 1 + ROWS.length) % ROWS.length);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (key.downArrow || key.tab) {
|
|
71
|
+
setIndex((index + 1) % ROWS.length);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (key.return) {
|
|
75
|
+
activate(row);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (row !== 'name' || key.ctrl || key.meta)
|
|
79
|
+
return;
|
|
80
|
+
// THE NAME IS THE ONLY TEXT FIELD, and control characters are stripped by the
|
|
81
|
+
// reducer rather than refused here - a pasted name with a newline should
|
|
82
|
+
// become a name, not an error the user cannot see.
|
|
83
|
+
const text = stripPasteFrames(stripEnterFrames(input));
|
|
84
|
+
if (key.backspace || key.delete) {
|
|
85
|
+
dispatch({ type: 'kit-name', value: [...draft.name].slice(0, -1).join('') });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (text)
|
|
89
|
+
dispatch({ type: 'kit-name', value: `${draft.name}${text}` });
|
|
90
|
+
});
|
|
91
|
+
const lines = [`Name ${draft.name}`,
|
|
92
|
+
`Main profile ${labels.main}`,
|
|
93
|
+
`Subagent ${labels.subagent}`,
|
|
94
|
+
`Fast tier ${draft.fastProfileId ? labels.fast : 'off (this kit turns it off)'}`,
|
|
95
|
+
`Save (${filled} slots set)`, 'Cancel'];
|
|
96
|
+
const rows = lines.map((line, at) => (_jsx(Text, { wrap: "truncate", color: at === index ? props.theme.accent : props.theme.muted, children: `${at === index ? glyphs.caret : ' '} ${clipProfileText(line, props.cols - 8)}` }, at)));
|
|
97
|
+
if (draft.error) {
|
|
98
|
+
rows.push(_jsx(Text, { wrap: "truncate", color: props.theme.noticeError, children: clipProfileText(draft.error, props.cols - 8) }, "error"));
|
|
99
|
+
}
|
|
100
|
+
return _jsx(OverlayFrame, { ...props, title: draft.id ? 'Edit kit' : 'New kit', hint: "Enter choose | type to rename | Esc back", rows: rows, scrollOffset: offset });
|
|
101
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings > Kit pack: the manager page (agent-model-kits §4.5 / D-9).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`.
|
|
5
|
+
*
|
|
6
|
+
* CRUD LIVES HERE AND NOWHERE ELSE: `New kit` and `e` open the editor, `d` asks
|
|
7
|
+
* a confirmation that NAMES the consequences, `s` runs exit A (three slots plus
|
|
8
|
+
* the persisted pointer, because a default that only moved a pointer would not
|
|
9
|
+
* be in effect at the next launch - RV-P0-3), and `Enter` applies a kit to the
|
|
10
|
+
* transaction draft. Nothing on this page writes the config file.
|
|
11
|
+
*/
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { type ProfileFrameProps } from './ModelProfilePicker.js';
|
|
14
|
+
import type { KitAction, KitManagerRow } from './kit-settings-state.js';
|
|
15
|
+
interface KitManagerProps extends ProfileFrameProps {
|
|
16
|
+
rows: KitManagerRow[];
|
|
17
|
+
error?: string;
|
|
18
|
+
dispatch: React.Dispatch<KitAction>;
|
|
19
|
+
/** Leave the kit pages entirely: `Esc` here means "back to Settings". */
|
|
20
|
+
onBack: () => void;
|
|
21
|
+
}
|
|
22
|
+
export declare function KitManager(props: KitManagerProps): React.ReactElement;
|
|
23
|
+
export {};
|