@aragon-agent/cli 0.6.16 → 0.6.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +44 -47
  2. package/dist/agent/controller.js +39 -47
  3. package/dist/agent/reducer.js +0 -4
  4. package/dist/cli.js +0 -13
  5. package/dist/commands/builtins.js +3 -17
  6. package/dist/compaction/child.d.ts +10 -8
  7. package/dist/compaction/child.js +23 -25
  8. package/dist/compaction/limits.d.ts +0 -2
  9. package/dist/compaction/limits.js +0 -2
  10. package/dist/config/cli-commands.d.ts +1 -1
  11. package/dist/config/cli-commands.js +16 -15
  12. package/dist/config/load.d.ts +0 -17
  13. package/dist/config/load.js +0 -6
  14. package/dist/config/schema.d.ts +17 -5
  15. package/dist/config/schema.js +14 -11
  16. package/dist/exec/events.d.ts +12 -8
  17. package/dist/exec/events.js +2 -2
  18. package/dist/exec/runner.js +4 -1
  19. package/dist/fast/limits.d.ts +2 -2
  20. package/dist/fast/limits.js +2 -2
  21. package/dist/fast/prompt.d.ts +10 -11
  22. package/dist/fast/prompt.js +19 -20
  23. package/dist/fast/reviewer.d.ts +1 -1
  24. package/dist/fast/reviewer.js +1 -1
  25. package/dist/fast/types.d.ts +0 -2
  26. package/dist/fast/wiring.d.ts +18 -34
  27. package/dist/fast/wiring.js +20 -51
  28. package/dist/session/validate-session.js +3 -1
  29. package/dist/team/child-reviewer.d.ts +105 -0
  30. package/dist/team/child-reviewer.js +117 -0
  31. package/dist/team/normalize.d.ts +7 -26
  32. package/dist/team/normalize.js +9 -22
  33. package/dist/team/prompt.d.ts +0 -11
  34. package/dist/team/prompt.js +3 -12
  35. package/dist/team/report.d.ts +4 -19
  36. package/dist/team/report.js +1 -27
  37. package/dist/team/runtime.d.ts +5 -10
  38. package/dist/team/runtime.js +35 -39
  39. package/dist/team/subagent.d.ts +18 -18
  40. package/dist/team/subagent.js +80 -30
  41. package/dist/team/task-tool.d.ts +6 -26
  42. package/dist/team/task-tool.js +13 -47
  43. package/dist/team/types.d.ts +3 -42
  44. package/dist/ui/App.js +3 -4
  45. package/dist/ui/BottomStatusRow.js +2 -1
  46. package/dist/ui/StatusBar.js +2 -1
  47. package/dist/ui/TeamPanel.js +1 -1
  48. package/dist/ui/entries/TeamCard.js +1 -6
  49. package/dist/ui/layout/status-detail-layout.js +12 -2
  50. package/dist/ui/layout/status-layout.d.ts +4 -2
  51. package/dist/ui/layout/status-layout.js +24 -1
  52. package/dist/ui/overlays/HelpOverlay.js +1 -2
  53. package/package.json +2 -2
  54. package/dist/ui/ModeChip.d.ts +0 -18
  55. package/dist/ui/ModeChip.js +0 -24
package/dist/ui/App.js CHANGED
@@ -662,10 +662,9 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
662
662
  // so it closes over the `cfg` object of the render it mounted in, and
663
663
  // `setModel` builds a NEW one.
664
664
  const live = controller.getConfig();
665
- const fastTier = event.tier === 'fast' ? controller.getFastStatus().tier : null;
666
- const ref = fastTier && fastTier.ok
667
- ? fastTier.ref
668
- : { providerId: live.provider, modelId: live.model };
665
+ // Every child runs the lead's own model (main-agent parity), so the
666
+ // lead's live connection IS the tier that billed.
667
+ const ref = { providerId: live.provider, modelId: live.model };
669
668
  // Unknown pricing contributes ZERO rather than a fabricated figure
670
669
  // (C-11 / RV-4); `/fast status` and the dispatch report say `unknown`.
671
670
  const costDelta = controller.isPricedModel(ref)
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React from 'react';
4
4
  import { Box, Text } from 'ink';
5
5
  export function BottomStatusRow({ plan, columns, theme }) {
6
- const tones = { normal: theme.primary, muted: theme.muted, warning: theme.noticeWarn, error: theme.noticeError };
6
+ const tones = { normal: theme.primary, muted: theme.muted, warning: theme.noticeWarn, error: theme.noticeError,
7
+ accent: theme.accent };
7
8
  return _jsxs(Box, { height: 1, width: columns, flexShrink: 0, overflow: "hidden", children: [_jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { children: " " }) }), plan.fields.map((field, index) => _jsxs(React.Fragment, { children: [index > 0 && _jsx(Box, { width: plan.separatorCells, flexShrink: 0, children: _jsx(Text, { color: theme.muted, children: plan.separator }) }), _jsx(Box, { width: field.cells, flexShrink: 0, children: _jsx(Text, { wrap: "truncate", color: tones[field.tone], children: field.text }) })] }, field.id))] });
8
9
  }
@@ -27,7 +27,8 @@ export function StatusBar(props) {
27
27
  ecoRung: props.ecoRung, fastActive: props.fastActive, scrolledLines: props.scrolledLines,
28
28
  });
29
29
  const tones = { normal: props.theme.primary, muted: props.theme.muted,
30
- warning: props.theme.noticeWarn, error: props.theme.noticeError };
30
+ warning: props.theme.noticeWarn, error: props.theme.noticeError,
31
+ accent: props.theme.accent };
31
32
  return _jsxs(Box, { width: cols, height: 1, flexShrink: 0, flexDirection: "row", overflow: "hidden", children: [_jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { children: redrawChar(props.redrawNonce ?? 0) }) }), plan.fields.map((item, index) => _jsxs(React.Fragment, { children: [index > 0 && _jsx(Box, { width: plan.separatorCells, flexShrink: 0, children: _jsx(Text, { color: props.theme.muted, children: plan.separator }) }), _jsx(Box, { width: item.cells, flexShrink: 0, children: _jsx(Text, { color: tones[item.tone], wrap: "truncate", children: item.id === 'phase' && active
32
33
  ? _jsxs(_Fragment, { children: [spinner ?? glyph, item.text.slice(glyph.length)] })
33
34
  : item.text }) })] }, item.id))] });
@@ -130,7 +130,7 @@ export function TeamPanel({ snapshot, rows, cols, reducedMotion, theme, caps, no
130
130
  // `reducedMotion` and a non-Unicode terminal fall back to the same
131
131
  // static marker, exactly as `AssistantEntry` and `ToolCard` already do.
132
132
  const animate = isRunning(run) && !reducedMotion && caps.unicode;
133
- return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: color, children: [' ', animate ? _jsx(Spinner, { type: "dots" }) : marker(run, glyphs), ' '] }), _jsx(Text, { color: theme.primary, children: `${run.label}${run.tier === 'fast' ? '~' : ''}`.padEnd(8).slice(0, 8) }), _jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: theme.muted, children: run.description.replace(/\s+/g, ' ') }) }), _jsx(Box, { flexShrink: 1, overflow: "hidden", children: _jsxs(Text, { wrap: "truncate", color: color, children: [' ', activityLine(run, cols)] }) }), _jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', run.startedAt ? formatDuration(Math.max(0, (run.endedAt ?? at) - run.startedAt)) : ''] })] }, run.label));
133
+ return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: color, children: [' ', animate ? _jsx(Spinner, { type: "dots" }) : marker(run, glyphs), ' '] }), _jsx(Text, { color: theme.primary, children: run.label.padEnd(8).slice(0, 8) }), _jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: theme.muted, children: run.description.replace(/\s+/g, ' ') }) }), _jsx(Box, { flexShrink: 1, overflow: "hidden", children: _jsxs(Text, { wrap: "truncate", color: color, children: [' ', activityLine(run, cols)] }) }), _jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', run.startedAt ? formatDuration(Math.max(0, (run.endedAt ?? at) - run.startedAt)) : ''] })] }, run.label));
134
134
  }), hiddenTotal > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "+", hiddenTotal, " more", hiddenRunning > 0 ? ` (${hiddenRunning} running)` : ''] })), snapshot.lastMessage && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', glyphs.teamMail, " ", snapshot.lastMessage.from, " ", glyphs.arrowRight, " ", snapshot.lastMessage.to, ": ", snapshot.lastMessage.subject.replace(/\s+/g, ' ')] }))] }));
135
135
  }
136
136
  function marker(run, glyphs) {
@@ -48,12 +48,7 @@ function TeamCardImpl(props) {
48
48
  : `${formatDuration(durationMs ?? 0)} (${ok} ok, ${failed} failed)`;
49
49
  const count = `${runs.length} ${runs.length === 1 ? 'subagent' : 'subagents'}`;
50
50
  const dropped = requested > runs.length ? ` (${runs.length} of ${requested} requested)` : '';
51
- // ABSENT WHEN NO FAST CHILD RAN, so an ordinary dispatch's card is unchanged
52
- // (fast-model-tier §6). `n fast` rather than a ratio: the total is already one
53
- // column to the left.
54
- const fastCount = runs.filter((r) => r.tier === 'fast').length;
55
- const fastNote = fastCount > 0 ? ` ${fastCount} fast` : '';
56
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: theme.accent, bold: true, children: "team" }), _jsxs(Text, { color: theme.muted, children: [' ', count, dropped, fastNote, ' '] }), active && !reducedMotion && caps.unicode ? (_jsxs(Text, { color: color, children: [_jsx(Spinner, { type: "dots" }), " ", headline] })) : (_jsxs(Text, { color: color, children: [active ? `${glyphs.spinnerStill} ` : '', headline] }))] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, paddingLeft: 1, ...railBorderProps(glyphs.railVertical, theme.border), children: [runs.map((run) => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: isOk(run) ? theme.toolDone : theme.toolError, children: isOk(run) ? glyphs.toolDone : glyphs.toolError }), _jsxs(Text, { color: theme.primary, children: [' ', `${run.label}${run.tier === 'fast' ? '~' : ''}`.padEnd(8).slice(0, 8)] }), _jsxs(Text, { color: theme.muted, children: [' ', run.description, ' ', run.startedAt ? formatDuration(Math.max(0, (run.endedAt ?? 0) - run.startedAt)) : '', ` ${run.turns} turns`, run.filesTouched.length > 0 ? ` ${run.filesTouched.length} files` : '', run.error ? ` ${run.error}` : ''] }), run.retry && (_jsxs(Text, { color: theme.noticeWarn, bold: true, children: [' ', "retry ", run.retry.attempt, "/", run.retry.maxRetries] }))] }, run.label))), expanded &&
51
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: theme.accent, bold: true, children: "team" }), _jsxs(Text, { color: theme.muted, children: [' ', count, dropped, ' '] }), active && !reducedMotion && caps.unicode ? (_jsxs(Text, { color: color, children: [_jsx(Spinner, { type: "dots" }), " ", headline] })) : (_jsxs(Text, { color: color, children: [active ? `${glyphs.spinnerStill} ` : '', headline] }))] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, paddingLeft: 1, ...railBorderProps(glyphs.railVertical, theme.border), children: [runs.map((run) => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: isOk(run) ? theme.toolDone : theme.toolError, children: isOk(run) ? glyphs.toolDone : glyphs.toolError }), _jsxs(Text, { color: theme.primary, children: [' ', run.label.padEnd(8).slice(0, 8)] }), _jsxs(Text, { color: theme.muted, children: [' ', run.description, ' ', run.startedAt ? formatDuration(Math.max(0, (run.endedAt ?? 0) - run.startedAt)) : '', ` ${run.turns} turns`, run.filesTouched.length > 0 ? ` ${run.filesTouched.length} files` : '', run.error ? ` ${run.error}` : ''] }), run.retry && (_jsxs(Text, { color: theme.noticeWarn, bold: true, children: [' ', "retry ", run.retry.attempt, "/", run.retry.maxRetries] }))] }, run.label))), expanded &&
57
52
  runs.map((run) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: theme.accent, children: [run.label, " ", glyphs.midDot, " ", run.description] }), _jsx(Text, { color: theme.muted, children: run.summary ?? '(no summary)' })] }, `s-${run.label}`))), !expanded && !active && runs.length > 0 && (_jsxs(Text, { color: theme.muted, children: ["+", runs.length, " ", runs.length === 1 ? 'summary' : 'summaries', " (Ctrl+O)"] })), expanded && _jsx(Text, { color: theme.muted, children: "(Ctrl+O to collapse)" })] })] }));
58
53
  }
59
54
  /**
@@ -1,6 +1,17 @@
1
1
  import { formatCost, formatTokens, promptTokensOf } from '../../agent/usage.js';
2
2
  import { actionCandidates, clipStatusText, escapeClause } from '../interaction-hints.js';
3
+ import { MODE_LABEL } from '../../agent/agent-mode.js';
3
4
  import { statusField, statusLinePlan, validContext } from './status-layout.js';
5
+ /**
6
+ * Uppercase mode word for the details row, mirroring the primary row's field:
7
+ * same vocabulary (`MODE_LABEL`), same `CUR>PENDING` deferred-switch shape.
8
+ */
9
+ function detailModeText(mode, pendingMode) {
10
+ const word = (value) => MODE_LABEL[value] ?? value.toUpperCase();
11
+ if (pendingMode)
12
+ return `${word(mode || 'build')}>${word(pendingMode)}`;
13
+ return word(mode || 'build');
14
+ }
4
15
  export function planStatusDetail(input) {
5
16
  const { status, hints } = input;
6
17
  const budget = Math.max(0, Math.floor(status.columns) - 1);
@@ -26,8 +37,7 @@ export function planStatusDetail(input) {
26
37
  if (status.servicesActive?.live)
27
38
  add(statusField('services', 'Svc' + status.servicesActive.live));
28
39
  if (status.mode || status.pendingMode)
29
- add(statusField('mode', status.pendingMode
30
- ? (status.mode ?? 'build') + '>' + status.pendingMode : status.mode));
40
+ add(statusField('mode', detailModeText(status.mode, status.pendingMode), 'accent'));
31
41
  if (validContext(status.context)) {
32
42
  const approximate = status.context.source === 'estimate' || status.context.deltaTokens > 0 ? '~' : '';
33
43
  add(statusField('context', 'Context ' + approximate + formatTokens(status.context.occupied)
@@ -25,6 +25,7 @@ export interface StatusLayoutInput {
25
25
  max: number;
26
26
  secondsLeft: number;
27
27
  };
28
+ /** Named on the primary row in EVERY mode - the §6.4 guarantee, restored. */
28
29
  mode?: string;
29
30
  pendingMode?: string | null;
30
31
  servicesActive?: {
@@ -54,7 +55,8 @@ export interface StatusField {
54
55
  id: string;
55
56
  text: string;
56
57
  cells: number;
57
- tone: 'normal' | 'muted' | 'warning' | 'error';
58
+ /** 'accent' carries the mode word - the row's one always-on emphasis. */
59
+ tone: 'normal' | 'muted' | 'warning' | 'error' | 'accent';
58
60
  }
59
61
  export interface StatusLinePlan {
60
62
  fields: readonly StatusField[];
@@ -68,5 +70,5 @@ export declare function statusLinePlan(fields: readonly StatusField[], separator
68
70
  export declare function phaseLabels(input: Pick<StatusLayoutInput, 'phase' | 'activeTool' | 'runOutcome' | 'waitingForConfirmation' | 'stopping' | 'compacting' | 'retryActive'>): readonly [string, string];
69
71
  export declare function validContext(context: ContextUsageSnapshot): boolean;
70
72
  export declare function shortDuration(ms: number): string;
71
- /** All five fields survive each width rung; only the redraw carrier is outside this plan. */
73
+ /** Every field survives each width rung; only the redraw carrier is outside this plan. */
72
74
  export declare function planPrimaryStatusFields(input: StatusPrimaryInput): StatusLinePlan;
@@ -1,6 +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 { MODE_LABEL } from '../../agent/agent-mode.js';
4
5
  import { interactionCopy as copy } from '../interaction-copy.js';
5
6
  export function cleanStatusText(text) {
6
7
  // eslint-disable-next-line no-control-regex
@@ -89,7 +90,27 @@ function feedbackLabel(input, form) {
89
90
  return labels[feedback.kind][form === 'full' ? 0 : form === 'short' ? 1 : 2];
90
91
  }
91
92
  const thinkingCodes = { off: 'O', minimal: 'N', low: 'L', medium: 'M', high: 'H', xhigh: 'X' };
92
- /** All five fields survive each width rung; only the redraw carrier is outside this plan. */
93
+ /**
94
+ * The mode word for the primary row: full rungs name the mode with
95
+ * `MODE_LABEL`, tight rungs keep only its first letter, and a deferred switch
96
+ * renders `CUR>PENDING` in both tiers. `null` when the caller names no mode at
97
+ * all, so callers and tests that predate the field keep a byte-identical plan.
98
+ * The word must survive every width rung: the border color cannot be the only
99
+ * mode signal the default view carries (plan-mode P1-6).
100
+ */
101
+ function modeFieldText(mode, pendingMode, full) {
102
+ if (!mode && !pendingMode)
103
+ return null;
104
+ const word = (value) => {
105
+ const label = MODE_LABEL[value];
106
+ if (!label)
107
+ return full ? value.toUpperCase() : (value[0] ?? '?').toUpperCase();
108
+ return full ? label : label[0];
109
+ };
110
+ const current = word(mode || 'build');
111
+ return pendingMode ? `${current}>${word(pendingMode)}` : current;
112
+ }
113
+ /** Every field survives each width rung; only the redraw carrier is outside this plan. */
93
114
  export function planPrimaryStatusFields(input) {
94
115
  const budget = Math.max(0, (Number.isFinite(input.columns) ? Math.floor(input.columns) : 80) - 1);
95
116
  const labels = phaseLabels(input);
@@ -104,8 +125,10 @@ export function planPrimaryStatusFields(input) {
104
125
  const full = rung === 0;
105
126
  const label = feedbackLabel(input, full ? 'full' : rung >= 4 ? 'tiny' : 'short')
106
127
  ?? (full ? labels[1] : rung >= 4 ? labels[0].slice(0, 4) : labels[0]);
128
+ const modeText = modeFieldText(input.mode, input.pendingMode, full);
107
129
  return statusLinePlan([
108
130
  statusField('phase', glyph + (rung >= 5 ? '' : ' ') + label, tone),
131
+ ...(modeText ? [statusField('mode', modeText, 'accent')] : []),
109
132
  statusField('context', (full ? 'Context ' : 'C') + pct),
110
133
  statusField('thinking', full ? 'Think ' + (thinkingCodes[input.thinkingLevel] ? input.thinkingLevel : '?')
111
134
  : (rung >= 2 ? '' : 'Th:') + code),
@@ -31,6 +31,7 @@ function keyRows(times) {
31
31
  ['Drag right edge', 'Scroll messages; input stays fixed; type to return to newest output'],
32
32
  ['Bottom rows', 'Fixed input, one status row; Ctrl+G adds one detail row'],
33
33
  ['C / Th / t/s', 'Context percent / thinking level / run-average output tokens per second'],
34
+ ['B / P / P>B', 'Status-row mode word: BUILD / PLAN; CUR>PENDING = switch deferred to run end'],
34
35
  ['O/N/L/M/H/X', 'Thinking: off/minimal/low/medium/high/xhigh'],
35
36
  ['~ / ? / --', 'Estimated context / unknown context / unavailable output speed'],
36
37
  ['Output speed', 'Main Agent reported output tokens / total run time, including tools and waits'],
@@ -40,7 +41,6 @@ function keyRows(times) {
40
41
  ['Ctrl+C (with a selection)', 'Copy the pending selection - does not arm exit'],
41
42
  ['Native terminal selection', 'Copy-on-select is controlled by your terminal settings'],
42
43
  ['Queue / /queue', 'Pending receipt; /queue opens full text, PgUp/PgDn pages, Esc closes'],
43
- ['plan>build / build>plan', 'A mode switch pending until the current run ends'],
44
44
  ['/bg / /todo status', 'Show full service and TODO counts when status fields are hidden'],
45
45
  ['Queue paused', 'Unreceived messages persist after interruption until accepted or cancelled'],
46
46
  ['Up / Down', 'Prompt history (empty input)'],
@@ -113,7 +113,6 @@ const FAST_ROWS = [
113
113
  ['/fast model <id>', 'Set the fast model; accepts provider:model'],
114
114
  ['/fast same', 'Run the fast tier on the main model'],
115
115
  ['/fast review <n> | off', 'Turns between automatic reviews, or turn them off'],
116
- ['/fast delegate on | off', 'Allow model:"fast" on task subagents'],
117
116
  ['--fast / --no-fast', 'Start a session with the tier on or off (also ARAGON_FAST=1)'],
118
117
  ['<fast_review>', 'An automated second opinion from the fast model - advice, not the user'],
119
118
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aragon-agent/cli",
3
- "version": "0.6.16",
3
+ "version": "0.6.17",
4
4
  "description": "AragonAgent CLI — a Claude-Code / Codex-style interactive terminal UI (TUI) on top of the @aragon-agent/core engine. Installable and runnable via npm / npx.",
5
5
  "license": "MIT",
6
6
  "author": "AragonAgent contributors",
@@ -56,7 +56,7 @@
56
56
  "prepublishOnly": "npm run build"
57
57
  },
58
58
  "dependencies": {
59
- "@aragon-agent/core": "^0.2.28",
59
+ "@aragon-agent/core": "^0.2.29",
60
60
  "chalk": "^5.6.2",
61
61
  "cli-highlight": "^2.0.0",
62
62
  "commander": "^12.1.0",
@@ -1,18 +0,0 @@
1
- import React from 'react';
2
- import type { Theme } from './theme.js';
3
- import type { TermCapabilities } from './capabilities.js';
4
- import { type AgentMode } from '../agent/agent-mode.js';
5
- interface ModeChipProps {
6
- mode: AgentMode;
7
- theme: Theme;
8
- caps: TermCapabilities;
9
- }
10
- /**
11
- * Columns `ModeChip` occupies, including its trailing gap (plan => label + 3, any
12
- * other mode 0). Both render branches (padded background chip, bracketed text)
13
- * come to the same width, which is what lets the run status row budget for it
14
- * without measuring a rendered element.
15
- */
16
- export declare function modeChipCols(mode: AgentMode): number;
17
- export declare function ModeChip({ mode, theme, caps }: ModeChipProps): React.ReactElement | null;
18
- export {};
@@ -1,24 +0,0 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { Box, Text } from 'ink';
3
- import { MODE_LABEL } from '../agent/agent-mode.js';
4
- /**
5
- * Columns `ModeChip` occupies, including its trailing gap (plan => label + 3, any
6
- * other mode 0). Both render branches (padded background chip, bracketed text)
7
- * come to the same width, which is what lets the run status row budget for it
8
- * without measuring a rendered element.
9
- */
10
- export function modeChipCols(mode) {
11
- return mode === 'plan' ? MODE_LABEL[mode].length + 3 : 0;
12
- }
13
- export function ModeChip({ mode, theme, caps }) {
14
- if (mode !== 'plan')
15
- return null;
16
- const label = MODE_LABEL[mode];
17
- // `Box backgroundColor` degrades to plain text at colorLevel 0, where the chip
18
- // would become invisible against the hint row. Brackets carry the same
19
- // "this is a badge" reading with no color at all.
20
- if (caps.colorLevel === 0) {
21
- return (_jsxs(Text, { wrap: "truncate", bold: true, children: ["[", label, "]", ' '] }));
22
- }
23
- return (_jsx(Box, { flexShrink: 0, marginRight: 1, children: _jsxs(Text, { backgroundColor: theme.chip.bg, color: theme.chip.fg, bold: true, children: [' ', label, ' '] }) }));
24
- }