@aragon-agent/cli 0.6.14 → 0.6.16

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 (131) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +295 -238
  3. package/dist/agent/controller.d.ts +37 -1
  4. package/dist/agent/controller.js +195 -45
  5. package/dist/agent/plan-prompt.d.ts +1 -1
  6. package/dist/agent/plan-prompt.js +4 -3
  7. package/dist/agent/project-guidance-prompt.d.ts +4 -0
  8. package/dist/agent/project-guidance-prompt.js +104 -0
  9. package/dist/agent/reducer.d.ts +2 -0
  10. package/dist/agent/reducer.js +4 -0
  11. package/dist/agent/system-prompt.js +4 -0
  12. package/dist/cli.js +49 -2
  13. package/dist/commands/builtins.js +26 -28
  14. package/dist/compaction/child.d.ts +9 -1
  15. package/dist/compaction/child.js +39 -13
  16. package/dist/compaction/command.js +20 -10
  17. package/dist/compaction/compactor.d.ts +33 -204
  18. package/dist/compaction/compactor.js +311 -726
  19. package/dist/compaction/context-command.js +14 -3
  20. package/dist/compaction/digest.d.ts +15 -51
  21. package/dist/compaction/digest.js +113 -148
  22. package/dist/compaction/limits.d.ts +19 -142
  23. package/dist/compaction/limits.js +20 -147
  24. package/dist/compaction/memory-identity.d.ts +16 -0
  25. package/dist/compaction/memory-identity.js +63 -0
  26. package/dist/compaction/memory-input.d.ts +25 -0
  27. package/dist/compaction/memory-input.js +106 -0
  28. package/dist/compaction/memory-render.d.ts +4 -0
  29. package/dist/compaction/memory-render.js +47 -0
  30. package/dist/compaction/memory-validation.d.ts +21 -0
  31. package/dist/compaction/memory-validation.js +268 -0
  32. package/dist/compaction/memory.d.ts +79 -0
  33. package/dist/compaction/memory.js +118 -0
  34. package/dist/compaction/meter.d.ts +14 -10
  35. package/dist/compaction/meter.js +53 -19
  36. package/dist/compaction/operation.d.ts +18 -0
  37. package/dist/compaction/operation.js +68 -0
  38. package/dist/compaction/pressure.d.ts +5 -20
  39. package/dist/compaction/pressure.js +13 -23
  40. package/dist/compaction/summarize-call.d.ts +3 -16
  41. package/dist/compaction/summarize-call.js +20 -20
  42. package/dist/compaction/summary-prompt.d.ts +29 -66
  43. package/dist/compaction/summary-prompt.js +132 -178
  44. package/dist/compaction/types.d.ts +27 -4
  45. package/dist/compaction/wiring.d.ts +10 -25
  46. package/dist/compaction/wiring.js +56 -66
  47. package/dist/config/app-paths.d.ts +10 -0
  48. package/dist/config/app-paths.js +12 -0
  49. package/dist/config/env.d.ts +2 -1
  50. package/dist/config/env.js +13 -1
  51. package/dist/config/load.d.ts +9 -0
  52. package/dist/config/load.js +14 -0
  53. package/dist/config/model-windows.d.ts +91 -0
  54. package/dist/config/model-windows.js +181 -0
  55. package/dist/config/schema.d.ts +18 -1
  56. package/dist/config/schema.js +2 -1
  57. package/dist/exec/events.d.ts +8 -0
  58. package/dist/exec/index.js +13 -6
  59. package/dist/exec/runner.d.ts +8 -0
  60. package/dist/exec/runner.js +26 -2
  61. package/dist/input/csiu-keys.d.ts +66 -0
  62. package/dist/input/csiu-keys.js +170 -0
  63. package/dist/input/keyboard-enhancement.d.ts +42 -0
  64. package/dist/input/keyboard-enhancement.js +65 -0
  65. package/dist/input/stdin-filter.d.ts +9 -0
  66. package/dist/input/stdin-filter.js +45 -9
  67. package/dist/input/win32-input-mode.d.ts +61 -0
  68. package/dist/input/win32-input-mode.js +251 -0
  69. package/dist/session/persist.d.ts +3 -0
  70. package/dist/session/persist.js +9 -0
  71. package/dist/session/store.js +2 -0
  72. package/dist/session/validate-session.js +34 -0
  73. package/dist/team/subagent.js +8 -0
  74. package/dist/ui/App.js +218 -116
  75. package/dist/ui/BottomStatusRow.d.ts +5 -10
  76. package/dist/ui/BottomStatusRow.js +7 -11
  77. package/dist/ui/Composer.d.ts +4 -2
  78. package/dist/ui/Composer.js +2 -2
  79. package/dist/ui/Header.d.ts +6 -2
  80. package/dist/ui/Header.js +7 -6
  81. package/dist/ui/PromptInput.d.ts +3 -1
  82. package/dist/ui/PromptInput.js +32 -41
  83. package/dist/ui/StatusBar.d.ts +6 -0
  84. package/dist/ui/StatusBar.js +8 -7
  85. package/dist/ui/Transcript.js +1 -1
  86. package/dist/ui/entries/CompactionCard.d.ts +3 -1
  87. package/dist/ui/entries/CompactionCard.js +7 -5
  88. package/dist/ui/frame-differ.js +27 -0
  89. package/dist/ui/frame-observer.js +18 -5
  90. package/dist/ui/interaction-copy.d.ts +73 -73
  91. package/dist/ui/interaction-copy.js +30 -38
  92. package/dist/ui/interaction-hints.d.ts +8 -1
  93. package/dist/ui/interaction-hints.js +56 -69
  94. package/dist/ui/layout/AppShell.d.ts +6 -2
  95. package/dist/ui/layout/AppShell.js +2 -2
  96. package/dist/ui/layout/OverlayFrame.d.ts +2 -0
  97. package/dist/ui/layout/OverlayFrame.js +23 -14
  98. package/dist/ui/layout/ScrollViewport.d.ts +3 -1
  99. package/dist/ui/layout/ScrollViewport.js +10 -2
  100. package/dist/ui/layout/budget.d.ts +25 -26
  101. package/dist/ui/layout/budget.js +29 -22
  102. package/dist/ui/layout/document-layout.d.ts +2 -2
  103. package/dist/ui/layout/document-layout.js +2 -3
  104. package/dist/ui/layout/queue-layout.js +4 -4
  105. package/dist/ui/layout/status-detail-layout.d.ts +9 -0
  106. package/dist/ui/layout/status-detail-layout.js +56 -0
  107. package/dist/ui/layout/status-layout.d.ts +19 -2
  108. package/dist/ui/layout/status-layout.js +95 -84
  109. package/dist/ui/layout/wrap-rows.d.ts +2 -0
  110. package/dist/ui/layout/wrap-rows.js +21 -7
  111. package/dist/ui/overlays/ConfirmDialog.d.ts +2 -1
  112. package/dist/ui/overlays/ConfirmDialog.js +22 -3
  113. package/dist/ui/overlays/HelpOverlay.js +9 -2
  114. package/dist/ui/overlays/ModelPicker.d.ts +5 -8
  115. package/dist/ui/overlays/ModelPicker.js +44 -10
  116. package/dist/ui/overlays/ModelProfileEditor.js +2 -0
  117. package/dist/ui/overlays/ModelProfilePicker.d.ts +1 -0
  118. package/dist/ui/overlays/ModelProfilePicker.js +4 -2
  119. package/dist/ui/overlays/PlanReviewOverlay.d.ts +2 -1
  120. package/dist/ui/overlays/PlanReviewOverlay.js +7 -4
  121. package/dist/ui/overlays/QuestionOverlay.d.ts +2 -1
  122. package/dist/ui/overlays/QuestionOverlay.js +45 -32
  123. package/dist/ui/overlays/QueueOverlay.js +2 -2
  124. package/dist/ui/overlays/SettingsScreen.d.ts +1 -0
  125. package/dist/ui/overlays/SettingsScreen.js +10 -3
  126. package/dist/ui/run-metrics.d.ts +23 -0
  127. package/dist/ui/run-metrics.js +27 -0
  128. package/dist/ui/status-feedback.d.ts +33 -0
  129. package/dist/ui/status-feedback.js +36 -0
  130. package/dist/ui/stdout-frame-writer.js +28 -0
  131. package/package.json +2 -2
@@ -20,6 +20,7 @@ function keyRows(times) {
20
20
  [`Ctrl+C ${times}2`, 'Exit'],
21
21
  ['Ctrl+L', 'Redraw the frame'],
22
22
  ['Ctrl+T', 'Show/hide thinking (off by default)'],
23
+ ['Ctrl+G', 'Expand/collapse status details; disabled while an overlay is open'],
23
24
  ['PgUp / PgDn', 'Scroll a page (transcript, or this overlay)'],
24
25
  ['Shift+Up / Shift+Down', 'Scroll the transcript a line (full-screen mode)'],
25
26
  ['Wheel', 'Scroll the transcript (or the open overlay)'],
@@ -27,8 +28,14 @@ function keyRows(times) {
27
28
  // Drag-select is the one affordance here a user cannot discover by pressing
28
29
  // a key, so it has to be named somewhere they can look it up. The one-shot
29
30
  // startup notice is the other place, and it scrolls away.
30
- ['Drag right edge', 'Scroll messages and input together; type to return'],
31
- ['Bottom rows', 'Input, fixed action hints, then global runtime / queue / context status'],
31
+ ['Drag right edge', 'Scroll messages; input stays fixed; type to return to newest output'],
32
+ ['Bottom rows', 'Fixed input, one status row; Ctrl+G adds one detail row'],
33
+ ['C / Th / t/s', 'Context percent / thinking level / run-average output tokens per second'],
34
+ ['O/N/L/M/H/X', 'Thinking: off/minimal/low/medium/high/xhigh'],
35
+ ['~ / ? / --', 'Estimated context / unknown context / unavailable output speed'],
36
+ ['Output speed', 'Main Agent reported output tokens / total run time, including tools and waits'],
37
+ ['Copy / Sent / E / F', 'Copying / unconfirmed copy sent / copy error / Esc force-stop'],
38
+ ['^N', 'Transcript line offset from the bottom, not a new-message count'],
32
39
  ['Drag text (left button)', 'Select text on screen; the highlight waits'],
33
40
  ['Ctrl+C (with a selection)', 'Copy the pending selection - does not arm exit'],
34
41
  ['Native terminal selection', 'Copy-on-select is controlled by your terminal settings'],
@@ -3,19 +3,16 @@
3
3
  * providers only (R5) so it never offers a model whose provider would fail at
4
4
  * run with `Unknown LLM provider`. Lists each provider's builtin models.
5
5
  *
6
- * Uses `OverlayFrame`'s SELF-MANAGED mode (§4.4 / R-12). `ink-select-input`
7
- * windows itself via `limit` and registers its own `useInput` with `isFocused`
8
- * defaulting to true; Ink delivers every key to every mounted handler, so a
9
- * frame that also claimed the arrow keys would move the selection twice per
10
- * press. Slicing it by element would be worse still — it is one component, not
11
- * a list of rows. So the frame draws chrome only, and `App` registers no arrow
12
- * keys while this overlay is open.
6
+ * Selection belongs to a model ID, independently of the visible row window.
7
+ * Resizing or hiding the overlay never resets it. Each item occupies one row;
8
+ * this component owns navigation while App owns Escape.
13
9
  */
14
10
  import React from 'react';
15
11
  import type { ModelRegistry } from '@aragon-agent/core';
16
12
  import type { Theme } from '../theme.js';
17
13
  import type { TermCapabilities } from '../capabilities.js';
18
14
  interface ModelPickerProps {
15
+ isActive?: boolean;
19
16
  registry: ModelRegistry;
20
17
  currentProvider: string;
21
18
  currentModel: string;
@@ -25,5 +22,5 @@ interface ModelPickerProps {
25
22
  caps: TermCapabilities;
26
23
  onSelect: (provider: string, model: string) => void;
27
24
  }
28
- export declare function ModelPicker({ registry, currentProvider, currentModel, maxRows, cols, theme, caps, onSelect, }: ModelPickerProps): React.ReactElement;
25
+ export declare function ModelPicker({ isActive, registry, currentProvider, currentModel, maxRows, cols, theme, caps, onSelect, }: ModelPickerProps): React.ReactElement;
29
26
  export {};
@@ -1,10 +1,19 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import SelectInput from 'ink-select-input';
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Model picker overlay. Sources its provider set from the adapter-backed
4
+ * providers only (R5) so it never offers a model whose provider would fail at
5
+ * run with `Unknown LLM provider`. Lists each provider's builtin models.
6
+ *
7
+ * Selection belongs to a model ID, independently of the visible row window.
8
+ * Resizing or hiding the overlay never resets it. Each item occupies one row;
9
+ * this component owns navigation while App owns Escape.
10
+ */
11
+ import { useState } from 'react';
12
+ import { Text, useInput } from 'ink';
3
13
  import { ADAPTER_PROVIDERS } from '../../config/schema.js';
4
14
  import { pickGlyphs } from '../glyphs.js';
5
- import { OverlayFrame } from '../layout/OverlayFrame.js';
6
- import { overlayListLimit } from '../layout/overlay-window.js';
7
- export function ModelPicker({ registry, currentProvider, currentModel, maxRows, cols, theme, caps, onSelect, }) {
15
+ import { OverlayFrame, overlayBodyRows } from '../layout/OverlayFrame.js';
16
+ export function ModelPicker({ isActive = true, registry, currentProvider, currentModel, maxRows, cols, theme, caps, onSelect, }) {
8
17
  const glyphs = pickGlyphs(caps);
9
18
  const items = [];
10
19
  const seen = new Set();
@@ -21,9 +30,34 @@ export function ModelPicker({ registry, currentProvider, currentModel, maxRows,
21
30
  });
22
31
  }
23
32
  }
24
- const initialIndex = Math.max(0, items.findIndex((it) => it.value === `${currentProvider}:${currentModel}`));
25
- return (_jsx(OverlayFrame, { title: "Select a model", hint: `Enter choose ${glyphs.midDot} Esc cancel`, maxRows: maxRows, cols: cols, theme: theme, caps: caps, children: _jsx(SelectInput, { items: items, limit: overlayListLimit(maxRows), initialIndex: initialIndex >= 0 ? initialIndex : 0, onSelect: (item) => {
26
- const [provider, ...rest] = item.value.split(':');
27
- onSelect(provider, rest.join(':'));
28
- } }) }));
33
+ const [selectedValue, setSelectedValue] = useState(`${currentProvider}:${currentModel}`);
34
+ const selectedIndex = Math.max(0, items.findIndex(item => item.value === selectedValue));
35
+ const visibleRows = overlayBodyRows(maxRows, cols);
36
+ const offset = Math.max(0, Math.min(selectedIndex - visibleRows + 1, items.length - visibleRows));
37
+ const choose = (item) => {
38
+ if (!item)
39
+ return;
40
+ const [provider, ...rest] = item.value.split(':');
41
+ onSelect(provider, rest.join(':'));
42
+ };
43
+ useInput((input, key) => {
44
+ // Consume hidden input without changing selection, including when this is
45
+ // the only mounted input handler; otherwise buffered keys can replay later.
46
+ if (!isActive || !items.length)
47
+ return;
48
+ if (key.upArrow || input === 'k') {
49
+ setSelectedValue(items[(selectedIndex - 1 + items.length) % items.length].value);
50
+ }
51
+ else if (key.downArrow || input === 'j') {
52
+ setSelectedValue(items[(selectedIndex + 1) % items.length].value);
53
+ }
54
+ else if (key.return)
55
+ choose(items[selectedIndex]);
56
+ else if (/^[1-9]$/.test(input)) {
57
+ const visibleIndex = Number(input) - 1;
58
+ if (visibleIndex < visibleRows)
59
+ choose(items[offset + visibleIndex]);
60
+ }
61
+ });
62
+ return (_jsx(OverlayFrame, { title: "Select a model", hint: `Enter choose ${glyphs.midDot} Esc cancel`, maxRows: maxRows, cols: cols, theme: theme, caps: caps, scrollOffset: offset, rows: items.length ? items.map((item, index) => _jsxs(Text, { wrap: "truncate", color: index === selectedIndex ? theme.accent : theme.primary, children: [index === selectedIndex ? `${glyphs.caret} ` : ' ', item.label] }, item.value)) : [_jsx(Text, { wrap: "truncate", children: "No models available" }, "empty")] }));
29
63
  }
@@ -24,6 +24,8 @@ function useEditorInput(props, navigation) {
24
24
  dispatch({ type: 'merge-editor' });
25
25
  }
26
26
  useInput((input, key) => {
27
+ if (props.isActive === false)
28
+ return;
27
29
  if (key.escape) {
28
30
  dispatch({ type: 'cancel-editor' });
29
31
  return;
@@ -3,6 +3,7 @@ import type { ModelProfile, ModelProfilesConfig, ModelRole } from '../../config/
3
3
  import type { Theme } from '../theme.js';
4
4
  import type { TermCapabilities } from '../capabilities.js';
5
5
  export interface ProfileFrameProps {
6
+ isActive?: boolean;
6
7
  theme: Theme;
7
8
  caps: TermCapabilities;
8
9
  cols: number;
@@ -3,7 +3,7 @@ import { useEffect, useState } from 'react';
3
3
  import { Text, useInput } from 'ink';
4
4
  import stringWidth from 'string-width';
5
5
  import { pickGlyphs } from '../glyphs.js';
6
- import { OverlayFrame } from '../layout/OverlayFrame.js';
6
+ import { OverlayFrame, overlayBodyRows } from '../layout/OverlayFrame.js';
7
7
  import { stripPasteFrames } from '../paste-frames.js';
8
8
  import { stripEnterFrames } from '../enter-frames.js';
9
9
  import { profileUsers } from './model-profile-state.js';
@@ -13,7 +13,7 @@ export function useProfileScroll(props, focus) {
13
13
  useEffect(() => { props.onScrollClamp?.(0); }, []);
14
14
  useEffect(() => { setOffset(props.scrollOffset); }, [props.scrollOffset]);
15
15
  useEffect(() => {
16
- const height = Math.max(1, props.maxRows - (props.cols >= 48 ? 5 : 3));
16
+ const height = overlayBodyRows(props.maxRows, props.cols);
17
17
  const next = focus < offset ? focus : focus >= offset + height ? focus - height + 1 : offset;
18
18
  if (next !== offset) {
19
19
  setOffset(next);
@@ -98,6 +98,8 @@ function usePickerInput(props, state) {
98
98
  props.onDelete(selected.id);
99
99
  }
100
100
  useInput((input, key) => {
101
+ if (props.isActive === false)
102
+ return;
101
103
  if (key.escape) {
102
104
  if (searching)
103
105
  setSearching(false);
@@ -30,6 +30,7 @@ export type PlanVerdict = {
30
30
  feedback: string;
31
31
  };
32
32
  interface PlanReviewOverlayProps {
33
+ isActive?: boolean;
33
34
  plan: NormalizedPlan;
34
35
  maxRows: number;
35
36
  cols: number;
@@ -39,7 +40,7 @@ interface PlanReviewOverlayProps {
39
40
  caps: TermCapabilities;
40
41
  onVerdict: (verdict: PlanVerdict) => void;
41
42
  }
42
- export declare function PlanReviewOverlay({ plan, maxRows, cols, scrollOffset, onScrollClamp, theme, caps, onVerdict, }: PlanReviewOverlayProps): React.ReactElement;
43
+ export declare function PlanReviewOverlay({ isActive, plan, maxRows, cols, scrollOffset, onScrollClamp, theme, caps, onVerdict, }: PlanReviewOverlayProps): React.ReactElement;
43
44
  /**
44
45
  * Flatten the plan into one-row elements.
45
46
  *
@@ -25,16 +25,18 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
25
25
  import { useState } from 'react';
26
26
  import { Text, useInput } from 'ink';
27
27
  import { pickGlyphs } from '../glyphs.js';
28
- import { OverlayFrame } from '../layout/OverlayFrame.js';
28
+ import { OverlayFrame, overlayBodyRows } from '../layout/OverlayFrame.js';
29
29
  import { wrapToRows } from '../layout/wrap-rows.js';
30
30
  import { stripPasteFrames } from '../paste-frames.js';
31
31
  import { stripEnterFrames } from '../enter-frames.js';
32
32
  /** The frame's border plus its one-column padding on each side. */
33
33
  const FRAME_CHROME_COLS = 4;
34
- export function PlanReviewOverlay({ plan, maxRows, cols, scrollOffset, onScrollClamp, theme, caps, onVerdict, }) {
34
+ export function PlanReviewOverlay({ isActive = true, plan, maxRows, cols, scrollOffset, onScrollClamp, theme, caps, onVerdict, }) {
35
35
  const glyphs = pickGlyphs(caps);
36
36
  const [feedback, setFeedback] = useState(null);
37
37
  useInput((input, key) => {
38
+ if (!isActive)
39
+ return;
38
40
  if (key.escape)
39
41
  return; // App owns dismiss.
40
42
  if (feedback !== null) {
@@ -84,9 +86,10 @@ export function PlanReviewOverlay({ plan, maxRows, cols, scrollOffset, onScrollC
84
86
  // whole review away from either sub-state. `<-` is the route back to the
85
87
  // card, which is why it is named here too.
86
88
  const feedbackHint = `${glyphs.enterKey} send ${glyphs.midDot} ${glyphs.arrowLeft} back ${glyphs.midDot} esc dismiss`;
87
- return (_jsxs(OverlayFrame, { title: "What should change?", hint: feedbackHint, maxRows: maxRows, cols: cols, theme: theme, caps: caps, children: [_jsx(Text, { wrap: "truncate", color: theme.muted, children: "The agent revises the plan and submits it again." }), _jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: theme.primary, bold: true, children: [glyphs.caret, ' '] }), feedback.length > 0 ? (_jsx(Text, { children: feedback })) : (_jsx(Text, { color: theme.muted, children: "e.g. use the existing session store instead" })), _jsx(Text, { inverse: true, children: " " })] })] }));
89
+ return (_jsxs(OverlayFrame, { title: "What should change?", hint: feedbackHint, maxRows: maxRows, cols: cols, theme: theme, caps: caps, children: [overlayBodyRows(maxRows, cols) >= 2 && _jsx(Text, { wrap: "truncate", color: theme.muted, children: "The agent revises the plan and submits it again." }), _jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: theme.primary, bold: true, children: [glyphs.caret, ' '] }), feedback.length > 0 ? (_jsx(Text, { children: wrapToRows(feedback, Math.max(1, cols - FRAME_CHROME_COLS - 3)).at(-1) })) : (_jsx(Text, { color: theme.muted, children: "type feedback" })), _jsx(Text, { inverse: true, children: " " })] })] }));
88
90
  }
89
- return (_jsx(OverlayFrame, { title: "Review plan", hint: `a approve ${glyphs.midDot} r revise ${glyphs.midDot} esc dismiss ${glyphs.midDot} ${glyphs.arrowUp}${glyphs.arrowDown} scroll`, maxRows: maxRows, cols: cols, rows: rows, scrollOffset: scrollOffset, onScrollClamp: onScrollClamp, theme: theme, caps: caps }));
91
+ return (_jsx(OverlayFrame, { title: "Review plan", hint: cols <= 48 ? "a approve r revise Esc cancel"
92
+ : `a approve ${glyphs.midDot} r revise ${glyphs.midDot} esc dismiss ${glyphs.midDot} ${glyphs.arrowUp}${glyphs.arrowDown} scroll`, maxRows: maxRows, cols: cols, rows: rows, scrollOffset: scrollOffset, onScrollClamp: onScrollClamp, theme: theme, caps: caps }));
90
93
  }
91
94
  /**
92
95
  * Flatten the plan into one-row elements.
@@ -19,6 +19,7 @@ import type { Theme } from '../theme.js';
19
19
  import type { TermCapabilities } from '../capabilities.js';
20
20
  import type { Answer, NormalizedQuestion } from '../../tools/human-input.js';
21
21
  interface QuestionOverlayProps {
22
+ isActive?: boolean;
22
23
  questions: NormalizedQuestion[];
23
24
  maxRows: number;
24
25
  cols: number;
@@ -26,5 +27,5 @@ interface QuestionOverlayProps {
26
27
  caps: TermCapabilities;
27
28
  onSubmit: (answers: Answer[]) => void;
28
29
  }
29
- export declare function QuestionOverlay({ questions, maxRows, cols, theme, caps, onSubmit, }: QuestionOverlayProps): React.ReactElement;
30
+ export declare function QuestionOverlay({ isActive, questions, maxRows, cols, theme, caps, onSubmit, }: QuestionOverlayProps): React.ReactElement;
30
31
  export {};
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  /**
3
3
  * QuestionOverlay — the keyboard-driven multiple-choice wizard `ask_user`
4
4
  * renders (plan-mode §6.5).
@@ -15,10 +15,11 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
15
15
  * opposite — both it and `App` call `resolve` — which is harmless there only by
16
16
  * accident of promise semantics, and is not a pattern to copy (R-P12).
17
17
  */
18
- import { useState } from 'react';
19
- import { Box, Text, useInput } from 'ink';
18
+ import { useState, useLayoutEffect } from 'react';
19
+ import { Text, useInput } from 'ink';
20
20
  import { pickGlyphs } from '../glyphs.js';
21
- import { OverlayFrame } from '../layout/OverlayFrame.js';
21
+ import { wrapToRows } from '../layout/wrap-rows.js';
22
+ import { OverlayFrame, overlayBodyRows } from '../layout/OverlayFrame.js';
22
23
  import { stripPasteFrames } from '../paste-frames.js';
23
24
  import { stripEnterFrames } from '../enter-frames.js';
24
25
  /** Which option index each question starts on: the recommended one. */
@@ -31,7 +32,7 @@ function initialSelections(questions) {
31
32
  return new Set(recommended >= 0 ? [recommended] : [0]);
32
33
  });
33
34
  }
34
- export function QuestionOverlay({ questions, maxRows, cols, theme, caps, onSubmit, }) {
35
+ export function QuestionOverlay({ isActive = true, questions, maxRows, cols, theme, caps, onSubmit, }) {
35
36
  const glyphs = pickGlyphs(caps);
36
37
  const [step, setStep] = useState(0);
37
38
  const [phase, setPhase] = useState('choosing');
@@ -39,6 +40,7 @@ export function QuestionOverlay({ questions, maxRows, cols, theme, caps, onSubmi
39
40
  const [selections, setSelections] = useState(() => initialSelections(questions));
40
41
  const [customs, setCustoms] = useState(() => questions.map(() => ''));
41
42
  const [draft, setDraft] = useState('');
43
+ const [offset, setOffset] = useState(0);
42
44
  const question = questions[Math.min(step, questions.length - 1)];
43
45
  const cursor = cursors[step] ?? 0;
44
46
  const setCursorAt = (index, value) => setCursors((prev) => prev.map((c, i) => (i === index ? value : c)));
@@ -80,6 +82,16 @@ export function QuestionOverlay({ questions, maxRows, cols, theme, caps, onSubmi
80
82
  setPhase('choosing');
81
83
  };
82
84
  useInput((input, key) => {
85
+ if (!isActive)
86
+ return;
87
+ if (key.pageUp) {
88
+ setOffset(n => Math.max(0, n - 1));
89
+ return;
90
+ }
91
+ if (key.pageDown) {
92
+ setOffset(n => n + 1);
93
+ return;
94
+ }
83
95
  // App owns Esc for both new overlays (R-P12). Swallow it here so the
84
96
  // free-text editor below cannot eat the raw escape byte as a character.
85
97
  if (key.escape)
@@ -172,31 +184,32 @@ export function QuestionOverlay({ questions, maxRows, cols, theme, caps, onSubmi
172
184
  : phase === 'review'
173
185
  ? `${glyphs.enterKey} submit ${glyphs.midDot} ${glyphs.arrowLeft} back ${glyphs.midDot} esc cancel`
174
186
  : `${glyphs.arrowUp}${glyphs.arrowDown} choose ${glyphs.midDot} ${glyphs.enterKey} next${question.allowMultiple ? ` ${glyphs.midDot} space toggle` : ''} ${glyphs.midDot} ${glyphs.arrowLeft} back ${glyphs.midDot} esc cancel`;
175
- return (_jsx(OverlayFrame, { title: `${title}${position}`, hint: hint, maxRows: maxRows, cols: cols, theme: theme, caps: caps, children: phase === 'review' ? (_jsx(ReviewBody, { questions: questions, answers: buildAnswers(), theme: theme })) : (_jsx(QuestionBody, { question: question, cursor: cursor, selected: selections[step] ?? new Set(), typing: phase === 'typingOther', draft: draft, theme: theme, caps: caps })) }));
176
- }
177
- /**
178
- * ONE QUESTION PER SCREEN, not all of them at once. Three questions x four
179
- * options x a description line is 20+ rows against a ~15-row viewport on a
180
- * 24-row terminal.
181
- */
182
- function QuestionBody({ question, cursor, selected, typing, draft, theme, caps, }) {
183
- const glyphs = pickGlyphs(caps);
184
- const markOn = question.allowMultiple ? glyphs.boxChecked : glyphs.keyOn;
185
- const markOff = question.allowMultiple ? glyphs.boxEmpty : glyphs.keyOff;
186
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { wrap: "truncate", color: theme.accent, bold: true, children: question.header }), _jsx(Text, { wrap: "truncate", color: theme.assistant, children: question.question }), _jsx(Text, { children: " " }), question.options.map((option, i) => {
187
- const active = i === cursor;
188
- const on = selected.has(i);
189
- return (_jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: active ? theme.primary : theme.muted, children: [active ? glyphs.caret : ' ', ' '] }), _jsxs(Text, { color: on ? theme.primary : theme.muted, children: [on ? markOn : markOff, " "] }), _jsx(Text, { color: active ? theme.assistant : undefined, bold: active, children: option.isOther ? `${option.label}${glyphs.ellipsis}` : option.label }), option.description.length > 0 && (_jsxs(Text, { color: theme.muted, children: [' ', option.description] })), option.recommended && (_jsxs(Text, { backgroundColor: theme.chip.bg, color: theme.chip.fg, children: [' ', "RECOMMENDED", ' '] }))] }, `${question.id}-${i}`));
190
- }), typing && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: theme.primary, bold: true, children: [glyphs.caret, ' '] }), draft.length > 0 ? (_jsx(Text, { children: draft })) : (_jsx(Text, { color: theme.muted, children: "type your own answer" })), _jsx(Text, { inverse: true, children: " " })] })] }))] }));
191
- }
192
- /**
193
- * The review step exists because a wizard that fires on the last `Enter` with no
194
- * confirmation makes a mis-keyed answer unrecoverable.
195
- */
196
- function ReviewBody({ questions, answers, theme, }) {
197
- return (_jsx(Box, { flexDirection: "column", children: questions.map((q, i) => {
198
- const answer = answers[i];
199
- const value = answer?.custom ?? answer?.selected.join(', ') ?? '';
200
- return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: theme.accent, children: q.header.padEnd(14) }), _jsx(Text, { color: theme.assistant, children: value })] }, q.id));
201
- }) }));
187
+ const rowTexts = [];
188
+ const optionStarts = [];
189
+ const push = (text) => { rowTexts.push(...wrapToRows(text, Math.max(1, cols - 4))); };
190
+ if (phase === 'review') {
191
+ buildAnswers().forEach((answer, index) => push(`${questions[index].header}: ${answer.custom ?? answer.selected.join(', ')}`));
192
+ }
193
+ else {
194
+ push(question.header);
195
+ push(question.question);
196
+ question.options.forEach((option, index) => {
197
+ optionStarts.push(rowTexts.length);
198
+ const mark = (selections[step] ?? new Set()).has(index) ? '[x]' : '[ ]';
199
+ push(`${index === cursor ? glyphs.caret : ' '} ${mark} ${option.label}${option.recommended ? ' RECOMMENDED' : ''} ${option.description}`);
200
+ });
201
+ if (phase === 'typingOther')
202
+ push(`${glyphs.caret} ${draft || 'type your own answer'} `);
203
+ }
204
+ const focus = phase === 'review' ? 0 : phase === 'typingOther'
205
+ ? Math.max(0, rowTexts.length - 1) : optionStarts[cursor] ?? 0;
206
+ useLayoutEffect(() => {
207
+ const height = overlayBodyRows(maxRows, cols);
208
+ setOffset(previous => focus < previous ? focus : focus >= previous + height
209
+ ? focus - height + 1 : previous);
210
+ }, [focus, maxRows, cols, step, phase]);
211
+ const shortHint = phase === 'typingOther' ? 'Enter commit | Left back | Esc cancel'
212
+ : phase === 'review' ? 'Enter submit | Left back | Esc cancel'
213
+ : question.allowMultiple ? '^v choose Space toggle Enter next Esc' : 'Up/Down choose Enter next Esc cancel';
214
+ return _jsx(OverlayFrame, { title: `${title}${position}`, hint: cols <= 48 ? shortHint : hint, maxRows: maxRows, cols: cols, theme: theme, caps: caps, rows: rowTexts.map((line, index) => _jsx(Text, { wrap: "truncate", children: line || ' ' }, index)), scrollOffset: offset, onScrollClamp: setOffset });
202
215
  }
@@ -74,6 +74,6 @@ export function QueueOverlay(props) {
74
74
  onScrollClamp(offset);
75
75
  }, [projected, offset, scrollOffset, onScrollClamp]);
76
76
  const rows = useMemo(() => projected.length ? projected.map((row, index) => _jsx(Text, { wrap: "truncate", color: theme.primary, children: row.text || ' ' }, `${row.queueId}:${index}`))
77
- : [_jsx(Text, { children: '\u961f\u5217\u5df2\u5904\u7406\u5b8c\u6bd5' }, "empty")], [projected, theme]);
78
- return _jsx(OverlayFrame, { ...props, title: `Queue: \u5f85\u5904\u7406 ${pending.length}`, hint: "PgUp/PgDn | Up/Down | Esc", rows: rows, scrollOffset: offset });
77
+ : [_jsx(Text, { children: "Queue is empty" }, "empty")], [projected, theme]);
78
+ return _jsx(OverlayFrame, { ...props, title: `Queue: ${pending.length} pending`, hint: "PgUp/PgDn | Up/Down | Esc", rows: rows, scrollOffset: offset });
79
79
  }
@@ -87,6 +87,7 @@ export interface SettingsValues {
87
87
  compactionArchive: 'on' | 'off';
88
88
  }
89
89
  interface SettingsScreenProps {
90
+ isActive?: boolean;
90
91
  initial: SettingsValues;
91
92
  /** Resolved keys per provider so switching providers reloads the right key. */
92
93
  apiKeys: Record<string, string | undefined>;
@@ -247,11 +247,12 @@ export function SettingsScreen(props) {
247
247
  return props.draft ? _jsx(ProfileSettingsScreen, { ...props, draft: props.draft })
248
248
  : _jsx(LegacySettingsScreen, { ...props });
249
249
  }
250
- function LegacySettingsScreen({ initial, apiKeys, maxRows, cols, scrollOffset, onScrollClamp, theme, caps, onSave, retrySummary, }) {
250
+ function LegacySettingsScreen({ isActive = true, initial, apiKeys, maxRows, cols, scrollOffset, onScrollClamp, theme, caps, onSave, retrySummary, }) {
251
251
  const [values, setValues] = useState(initial);
252
252
  const [index, setIndex] = useState(0);
253
253
  const glyphs = pickGlyphs(caps);
254
254
  const field = FIELDS[index];
255
+ const focusedOffset = useProfileScroll({ maxRows, cols, scrollOffset, onScrollClamp, theme, caps }, index);
255
256
  const cycle = (dir) => {
256
257
  if (field.kind !== 'enum' || !field.options)
257
258
  return;
@@ -271,6 +272,8 @@ function LegacySettingsScreen({ initial, apiKeys, maxRows, cols, scrollOffset, o
271
272
  });
272
273
  };
273
274
  useInput((input, key) => {
275
+ if (!isActive)
276
+ return;
274
277
  if (key.escape)
275
278
  return; // App closes the overlay.
276
279
  if (key.upArrow) {
@@ -380,7 +383,7 @@ function LegacySettingsScreen({ initial, apiKeys, maxRows, cols, scrollOffset, o
380
383
  rows.push(_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', 'API retries'.padEnd(18), retrySummary] }, "__retry"));
381
384
  }
382
385
  rows.push(_jsx(Text, { wrap: "truncate", color: theme.muted, children: "Enter saves. Keys persist to the config file (0600)." }, "__save"));
383
- return (_jsx(OverlayFrame, { title: "Settings", hint: `${glyphs.arrowUp}${glyphs.arrowDown} field ${glyphs.midDot} ${glyphs.arrowLeft}${glyphs.arrowRight} change ${glyphs.midDot} Enter save ${glyphs.midDot} Esc close`, maxRows: maxRows, cols: cols, rows: rows, scrollOffset: scrollOffset, onScrollClamp: onScrollClamp, theme: theme, caps: caps }));
386
+ return (_jsx(OverlayFrame, { title: "Settings", hint: `${glyphs.arrowUp}${glyphs.arrowDown} field ${glyphs.midDot} ${glyphs.arrowLeft}${glyphs.arrowRight} change ${glyphs.midDot} Enter save ${glyphs.midDot} Esc close`, maxRows: maxRows, cols: cols, rows: rows, scrollOffset: focusedOffset, onScrollClamp: onScrollClamp, theme: theme, caps: caps }));
384
387
  }
385
388
  const PROFILE_FIELDS = FIELDS.filter((field) => ![
386
389
  'provider', 'model', 'baseUrl', 'apiKey', 'fastProvider', 'fastModel', 'fastEnabled',
@@ -592,6 +595,8 @@ function activateRootSetting(props, view) {
592
595
  function useRootSettingsInput(props, view) {
593
596
  const { index, count, field, readError, persisted, values, state, setIndex, setValues } = view;
594
597
  useInput((input, key) => {
598
+ if (props.isActive === false)
599
+ return;
595
600
  if (key.escape) {
596
601
  cancelProfileSettings(props, view);
597
602
  return;
@@ -635,6 +640,8 @@ function useRootSettingsInput(props, view) {
635
640
  function useSettingsConfirmationInput(props, view) {
636
641
  const { state, dispatch, confirmIndex, setConfirmIndex, reloadPending, confirming } = view;
637
642
  useInput((_input, key) => {
643
+ if (props.isActive === false)
644
+ return;
638
645
  if (key.escape) {
639
646
  dispatch({ type: 'page', page: state.page === 'delete-confirm'
640
647
  ? 'manager' : 'root' });
@@ -664,7 +671,7 @@ function renderSettingsConfirmation(props, view) {
664
671
  const name = library.entries.find((entry) => entry.id === state.deleteId)?.name ?? '';
665
672
  const labels = deleting ? ['Cancel', 'Delete'] : ['Continue', 'Discard'];
666
673
  return _jsx(OverlayFrame, { ...props, title: deleting ? `Delete ${name}?` : 'Discard unsaved changes?', hint: "Enter choose | Esc continue", rows: labels
667
- .map((label, at) => _jsxs(Text, { wrap: "truncate", color: props.theme.primary, children: [confirmIndex === at ? `${glyphs.caret} ` : ' ', label] }, label)), scrollOffset: 0 });
674
+ .map((label, at) => _jsxs(Text, { wrap: "truncate", color: props.theme.primary, children: [confirmIndex === at ? `${glyphs.caret} ` : ' ', label] }, label)), scrollOffset: confirmIndex });
668
675
  }
669
676
  function renderProfileSubpage(props, view) {
670
677
  const { state, library, dispatch, setConfirmIndex } = view;
@@ -0,0 +1,23 @@
1
+ /** UI-only metrics. Feed only the existing generation-guarded main subscription. */
2
+ export interface RunMetrics {
3
+ runId: number;
4
+ startedAt: number | null;
5
+ endedAt: number | null;
6
+ outputTokens: number;
7
+ usageKnown: boolean;
8
+ }
9
+ export interface RunMetricsEvent {
10
+ type: string;
11
+ usage?: {
12
+ outputTokens?: number;
13
+ };
14
+ }
15
+ export interface RunMetricsProjection {
16
+ elapsedMs: number;
17
+ tokPerSec: number;
18
+ speedKnown: boolean;
19
+ }
20
+ export declare function createRunMetrics(): RunMetrics;
21
+ export declare function freezeRunMetrics(state: RunMetrics, now: number): RunMetrics;
22
+ export declare function updateRunMetrics(state: RunMetrics, event: RunMetricsEvent, now: number): RunMetrics;
23
+ export declare function projectRunMetrics(state: RunMetrics, now: number): RunMetricsProjection;
@@ -0,0 +1,27 @@
1
+ export function createRunMetrics() {
2
+ return { runId: 0, startedAt: null, endedAt: null, outputTokens: 0, usageKnown: false };
3
+ }
4
+ export function freezeRunMetrics(state, now) {
5
+ if (state.startedAt === null || state.endedAt !== null)
6
+ return state;
7
+ return { ...state, endedAt: Number.isFinite(now) ? Math.max(state.startedAt, now) : state.startedAt };
8
+ }
9
+ export function updateRunMetrics(state, event, now) {
10
+ if (event.type === 'agent_start')
11
+ return { ...createRunMetrics(), runId: state.runId + 1,
12
+ startedAt: Number.isFinite(now) ? now : 0 };
13
+ if (state.startedAt === null || state.endedAt !== null)
14
+ return state;
15
+ if (event.type === 'agent_end' || event.type === 'error')
16
+ return freezeRunMetrics(state, now);
17
+ const output = event.usage?.outputTokens;
18
+ if (event.type !== 'turn_end' || output === undefined || !Number.isFinite(output) || output < 0)
19
+ return state;
20
+ return { ...state, outputTokens: Math.min(Number.MAX_VALUE, state.outputTokens + output), usageKnown: true };
21
+ }
22
+ export function projectRunMetrics(state, now) {
23
+ const end = state.endedAt ?? (Number.isFinite(now) ? now : state.startedAt ?? 0);
24
+ const elapsedMs = state.startedAt === null ? 0 : Math.max(0, end - state.startedAt);
25
+ const speedKnown = state.startedAt !== null && state.endedAt === null && state.usageKnown && elapsedMs > 500;
26
+ return { elapsedMs, speedKnown, tokPerSec: speedKnown ? state.outputTokens / (elapsedMs / 1000) : 0 };
27
+ }
@@ -0,0 +1,33 @@
1
+ /** Facts, never translated toast text, determine action ownership and feedback. */
2
+ export interface StatusFeedback {
3
+ kind: 'confirm' | 'stopping' | 'force-stop' | 'exit' | 'copying' | 'copied' | 'copy-sent' | 'copy-cleanup' | 'copy-error' | 'notice';
4
+ text: string;
5
+ level: 'info' | 'warn' | 'error';
6
+ }
7
+ export type EscapeAction = 'menu' | 'cancel-start' | 'confirm-stop' | 'force-stop';
8
+ export interface StatusFeedbackInput {
9
+ interactionPhase: 'idle' | 'starting' | 'running';
10
+ interruptPhase?: 'ready' | 'armed' | 'stopping';
11
+ completion?: 'none' | 'slash' | 'file';
12
+ overlay?: string | null;
13
+ copyState?: {
14
+ busy: boolean;
15
+ cleanupPending: boolean;
16
+ };
17
+ copyResult?: {
18
+ status: 'confirmed' | 'sent' | 'error';
19
+ text: string;
20
+ };
21
+ ctrlCArmed?: boolean;
22
+ selectionPending?: boolean;
23
+ liveServices?: number;
24
+ toast?: {
25
+ text: string;
26
+ level: 'info' | 'warn' | 'error';
27
+ };
28
+ }
29
+ export interface StatusFeedbackProjection {
30
+ feedback?: StatusFeedback;
31
+ escapeAction?: EscapeAction;
32
+ }
33
+ export declare function projectStatusFeedback(input: StatusFeedbackInput): StatusFeedbackProjection;
@@ -0,0 +1,36 @@
1
+ export function projectStatusFeedback(input) {
2
+ const escapeAction = input.overlay ? undefined
3
+ : input.completion && input.completion !== 'none' ? 'menu'
4
+ : input.interactionPhase === 'starting' ? 'cancel-start'
5
+ : input.interactionPhase !== 'running' ? undefined
6
+ : input.interruptPhase === 'stopping' ? 'force-stop' : 'confirm-stop';
7
+ let feedback;
8
+ if (input.ctrlCArmed && !input.copyState?.busy && !input.copyState?.cleanupPending
9
+ && !input.selectionPending && !input.liveServices) {
10
+ feedback = { kind: 'exit', text: 'Press Ctrl+C again to exit', level: 'warn' };
11
+ }
12
+ else if (input.copyState?.cleanupPending) {
13
+ feedback = { kind: 'copy-cleanup', text: 'Copy failed; cleaning up', level: 'error' };
14
+ }
15
+ else if (input.copyResult?.status === 'error') {
16
+ feedback = { kind: 'copy-error', text: input.copyResult.text, level: 'error' };
17
+ }
18
+ else if (input.copyState?.busy) {
19
+ feedback = { kind: 'copying', text: 'Copying', level: 'info' };
20
+ }
21
+ else if (input.copyResult) {
22
+ feedback = { kind: input.copyResult.status === 'confirmed' ? 'copied' : 'copy-sent',
23
+ text: input.copyResult.text, level: 'info' };
24
+ }
25
+ else if (input.interruptPhase === 'stopping' && escapeAction === 'force-stop') {
26
+ feedback = { kind: 'force-stop', text: 'Esc again to force-stop', level: 'warn' };
27
+ }
28
+ else if (input.interruptPhase === 'armed' && escapeAction && escapeAction !== 'menu') {
29
+ feedback = { kind: 'stopping', text: input.interactionPhase === 'starting'
30
+ ? 'Esc again to cancel start' : 'Esc again to stop', level: 'warn' };
31
+ }
32
+ else if (input.toast) {
33
+ feedback = { kind: 'notice', ...input.toast };
34
+ }
35
+ return { feedback, escapeAction };
36
+ }
@@ -34,6 +34,28 @@ function isUtf8Encoding(encoding) {
34
34
  const e = encoding.toLowerCase();
35
35
  return e === 'utf8' || e === 'utf-8';
36
36
  }
37
+ /**
38
+ * DEC private mode 25 set/reset — hide / show the cursor.
39
+ *
40
+ * Ink 5.2.1's own root `<App>` writes these two EXACT strings through the
41
+ * render stdout on mount and unmount (`ink/build/components/App.js` →
42
+ * `cli-cursor`), which is this proxy, not the process stream. Cursor
43
+ * VISIBILITY changes no cell and moves no row, so the differ's
44
+ * absolute-addressing cache survives them untouched — yet `transform` cannot
45
+ * recognise them and answers `passThrough(true)`: counted, `onFirstFallback`
46
+ * raised (the warning fired on EVERY launch), cache dropped for nothing. Worse,
47
+ * in the real CLI the mount write arrives FIRST, so it also consumes the
48
+ * session's one seed-write exemption (`firstChunk`, P1-1) and the seed frame
49
+ * itself becomes the counted fallback.
50
+ *
51
+ * Absorbed here instead: straight to the real stream — no transform, no
52
+ * `invalidate()`, no fallback count, no exemption consumed. The match is
53
+ * exact on the whole chunk, so a future Ink that mixes the sequence into a
54
+ * longer write simply misses and falls back to today's conservative handling;
55
+ * a missed absorb costs one extra repaint, never a swallowed desync.
56
+ */
57
+ const CURSOR_HIDE = '\x1b[?25l';
58
+ const CURSOR_SHOW = '\x1b[?25h';
37
59
  export function wrapStdoutForFrames(stdout, differ) {
38
60
  const bound = new Map();
39
61
  const writeReal = (chunk, encoding, cb) => {
@@ -58,6 +80,12 @@ export function wrapStdoutForFrames(stdout, differ) {
58
80
  differ.invalidate();
59
81
  return writeReal(chunk, encoding, cb);
60
82
  }
83
+ // Ink's own cursor-mode pair (see CURSOR_HIDE / CURSOR_SHOW above): not a
84
+ // frame and not a desync — the bytes reach the terminal untransformed,
85
+ // uncounted, and without touching the cache or the seed exemption.
86
+ if (chunk === CURSOR_HIDE || chunk === CURSOR_SHOW) {
87
+ return writeReal(chunk, encoding, cb);
88
+ }
61
89
  const out = differ.transform(chunk);
62
90
  if (out === null)
63
91
  return writeReal(chunk, encoding, cb);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aragon-agent/cli",
3
- "version": "0.6.14",
3
+ "version": "0.6.16",
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.26",
59
+ "@aragon-agent/core": "^0.2.28",
60
60
  "chalk": "^5.6.2",
61
61
  "cli-highlight": "^2.0.0",
62
62
  "commander": "^12.1.0",