@wrongstack/tui 1.0.1 → 1.0.4

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 +3 -0
  2. package/dist/app-action-type.d.ts +27 -1
  3. package/dist/app-key-handler.d.ts +3 -1
  4. package/dist/app-settings-type.d.ts +3 -0
  5. package/dist/app-state.d.ts +29 -12
  6. package/dist/auth-panel-model.d.ts +12 -2
  7. package/dist/components/history/copy-geometry.d.ts +41 -0
  8. package/dist/components/history/copy-icon.d.ts +17 -0
  9. package/dist/components/history/copy-registry.d.ts +2 -0
  10. package/dist/components/history/entry-helpers.d.ts +7 -15
  11. package/dist/components/history/entry.d.ts +3 -1
  12. package/dist/components/history/index.d.ts +1 -1
  13. package/dist/components/history/sage-memory-block.d.ts +7 -9
  14. package/dist/components/history/scroll-controller-types.d.ts +16 -1
  15. package/dist/components/history/scrollbar-rail.d.ts +3 -4
  16. package/dist/components/history/stream-box.d.ts +7 -0
  17. package/dist/components/history/tool-card-geometry.d.ts +35 -0
  18. package/dist/components/history/tool-card.d.ts +3 -1
  19. package/dist/components/history/tool-entry.d.ts +3 -1
  20. package/dist/components/history/tool-group.d.ts +5 -3
  21. package/dist/components/history/types.d.ts +5 -0
  22. package/dist/components/history/use-history-controller.d.ts +2 -0
  23. package/dist/components/history/utils.d.ts +2 -2
  24. package/dist/components/history/visual-lines.d.ts +1 -1
  25. package/dist/components/history/wrap-geometry.d.ts +4 -4
  26. package/dist/components/history.d.ts +1 -1
  27. package/dist/components/input.d.ts +9 -1
  28. package/dist/components/inspect-overlay.d.ts +30 -0
  29. package/dist/components/provider-colors.d.ts +1 -1
  30. package/dist/components/scrollable-history.d.ts +4 -4
  31. package/dist/components/settings-picker-constants.d.ts +11 -0
  32. package/dist/components/settings-picker-model.d.ts +8 -0
  33. package/dist/components/settings-picker.d.ts +4 -7
  34. package/dist/components/status-bar-icons.d.ts +1 -0
  35. package/dist/components/status-bar-rails-common.d.ts +14 -6
  36. package/dist/components/status-bar-rails-safety.d.ts +2 -2
  37. package/dist/history-entry.d.ts +14 -2
  38. package/dist/hit-test.d.ts +5 -5
  39. package/dist/hooks/subagent-history-format.d.ts +12 -0
  40. package/dist/hooks/use-bug-hunt-loop.d.ts +1 -1
  41. package/dist/hooks/use-provider-quota.d.ts +32 -0
  42. package/dist/hooks/use-theme-state.d.ts +3 -9
  43. package/dist/index.d.ts +1 -1
  44. package/dist/index.js +3729 -3092
  45. package/dist/input-key-router.d.ts +1 -1
  46. package/dist/input-validation/action-types.d.ts +1 -1
  47. package/dist/reducers/composer.d.ts +1 -1
  48. package/dist/reducers/conversation.d.ts +1 -1
  49. package/dist/reducers/dialogs.d.ts +1 -1
  50. package/dist/reducers/helpers.d.ts +3 -1
  51. package/dist/settings-contracts.d.ts +4 -0
  52. package/dist/theme.d.ts +7 -0
  53. package/dist/tool-result-view-mode.d.ts +8 -0
  54. package/dist/ui-contracts.d.ts +2 -3
  55. package/package.json +7 -7
package/README.md CHANGED
@@ -69,6 +69,9 @@ process.exit(exitCode);
69
69
  The wordmark and route label render in the brand gradient (orange → pink).
70
70
 
71
71
  - **History pane** — assistant text, tool calls, tool results, errors, turn summaries
72
+ - **Tool-result density** — each committed tool card has copy / less / more controls;
73
+ choose the global `minimal`, `normal`, or bounded `full` default under
74
+ `/settings tool-result-view <mode>`
72
75
  - **Streaming text** — partial deltas render live; on abort, partial response is preserved
73
76
  - **Status bar** — model · provider · context-window % · YOLO chip · spinner
74
77
  - **Input box** — multi-line buffer with bracketed-paste detection, history (↑/↓), placeholder pills for attachments
@@ -9,7 +9,7 @@ import type { BrainLogEntry, BrainRiskLevel } from './brain-contracts.js';
9
9
  import type { BrainPanelSettings } from './brain-panel-model.js';
10
10
  import type { StatuslineItem } from './components/statusline-picker.js';
11
11
  import type { HistoryEntry } from './history-entry.js';
12
- import type { AuditLevel, CacheTtl, CompactorStrategy, ContextMode, LogLevel, ReasoningEffort, SettingsMode, SettingsPickerPatch, StatuslineMode } from './settings-contracts.js';
12
+ import type { AuditLevel, CacheTtl, CompactorStrategy, ContextMode, LogLevel, ReasoningEffort, SettingsMode, SettingsPickerPatch, StatuslineMode, ToolResultViewMode } from './settings-contracts.js';
13
13
  import type { AutonomyOption, ChipMeta, HelpEntry, LiveSessionEntry, McpPickerItem, ModeOption, PluginPickerItem, ProjectPickerItem, PromptPickEntry, ProviderOption, ResourceMenuAction, ResourceMenuSnapshot, ShadowState, ToolPickerItem, WorktreeRow } from './ui-contracts.js';
14
14
  export type Action = {
15
15
  type: 'addEntry';
@@ -27,6 +27,17 @@ export type Action = {
27
27
  cursor: number;
28
28
  } | {
29
29
  type: 'clearInput';
30
+ }
31
+ /**
32
+ * Flip the composer into bash mode (`!` pressed on an empty draft): the
33
+ * input becomes a shell-command line and Enter runs it via the `!` path.
34
+ */
35
+ | {
36
+ type: 'bashModeEnter';
37
+ }
38
+ /** Leave bash mode and restore the normal chat composer. */
39
+ | {
40
+ type: 'bashModeExit';
30
41
  } | {
31
42
  type: 'clearHistory';
32
43
  model?: string | undefined;
@@ -63,6 +74,15 @@ export type Action = {
63
74
  type: 'copiedNotice';
64
75
  text: string;
65
76
  entryId: number | null;
77
+ } | {
78
+ type: 'inspectOverlayOpen';
79
+ entryId: number;
80
+ entryIds?: readonly number[] | undefined;
81
+ } | {
82
+ type: 'inspectOverlayClose';
83
+ } | {
84
+ type: 'inspectOverlayScroll';
85
+ delta: number;
66
86
  } | {
67
87
  type: 'brainStatus';
68
88
  state: State['brain']['state'];
@@ -379,6 +399,7 @@ export type Action = {
379
399
  breakerEnabled: boolean;
380
400
  breakerAutoKillResetMs: number;
381
401
  showModelReasoning: boolean;
402
+ toolResultViewMode: ToolResultViewMode;
382
403
  showAgentSwarmPanel: import('./app-settings-type.js').AgentSwarmPanelMode;
383
404
  showSidebar?: boolean | undefined;
384
405
  panelPositions: import('./ui-contracts.js').PanelPositionMap;
@@ -417,6 +438,10 @@ export type Action = {
417
438
  | {
418
439
  type: 'settingsValueSet';
419
440
  patch: SettingsPickerPatch;
441
+ } | {
442
+ type: 'toolResultViewSet';
443
+ entryIds: readonly number[];
444
+ mode: ToolResultViewMode;
420
445
  }
421
446
  /**
422
447
  * Update the live row-search filter. Empty string clears the filter.
@@ -659,6 +684,7 @@ export type Action = {
659
684
  view?: Extract<AuthPanelView, 'list' | 'oauth'> | undefined;
660
685
  providers?: AuthProviderRow[] | undefined;
661
686
  presets?: AuthLocalPresetRow[] | undefined;
687
+ oauthStrategies?: import('./auth-panel-model.js').AuthOAuthStrategyRow[] | undefined;
662
688
  } | {
663
689
  type: 'authClose';
664
690
  } | {
@@ -44,7 +44,9 @@ interface AppKeyHandlerOptions {
44
44
  cursor: number;
45
45
  }>;
46
46
  setDraft: (buffer: string, cursor: number) => void;
47
- submit: () => void;
47
+ /** Submits the composer. `overrideRaw` bypasses the buffer (bash mode
48
+ * forwards its draft through the `!` shell path this way). */
49
+ submit: (overrideRaw?: string) => void;
48
50
  mouseMode: boolean;
49
51
  termRows: number;
50
52
  terminalColumns: number;
@@ -2,6 +2,7 @@ import type { FleetChatVerbosity, TokenSavingTier } from '@wrongstack/core/types
2
2
  import type { AgentSwarmPanelMode, CacheTtl, ContextMode, ReasoningEffort, StatuslineMode } from './settings-contracts.js';
3
3
  export type { PanelId, PanelPosition, PanelPositionMap } from './ui-contracts.js';
4
4
  export { coercePanelPositionMap, DEFAULT_PANEL_POSITIONS, PANEL_IDS, } from './ui-contracts.js';
5
+ import type { ToolResultViewMode } from './tool-result-view-mode.js';
5
6
  import type { PanelPositionMap } from './ui-contracts.js';
6
7
  export type { AgentSwarmPanelMode } from './settings-contracts.js';
7
8
  /**
@@ -72,6 +73,8 @@ export type Settings = {
72
73
  /** Single word shown in the TUI rainbow working-state chip. */
73
74
  thinkingWord: string;
74
75
  showModelReasoning: boolean;
76
+ /** Default presentation density for committed TUI tool results. */
77
+ toolResultViewMode?: ToolResultViewMode | undefined;
75
78
  /** Agent swarm panel placement: 'bottom' (lower region), 'sidebar' (right
76
79
  * sidebar, vertical), or 'off' (hidden). Default: 'bottom'.
77
80
  * Backward-compat: old boolean configs are coerced — true→'bottom', false→'off'. */
@@ -9,7 +9,7 @@ import type { BrainPanelSettings } from './brain-panel-model.js';
9
9
  import type { HistoryEntry } from './history-entry.js';
10
10
  import type { TuiHistoryBudget } from './history-retention.js';
11
11
  import type { ResumeLoadState } from './resume-load.js';
12
- import type { AuditLevel, CacheTtl, CompactorStrategy, ContextMode, LogLevel, ReasoningEffort, SettingsMode, StatuslineMode } from './settings-contracts.js';
12
+ import type { AuditLevel, CacheTtl, CompactorStrategy, ContextMode, LogLevel, ReasoningEffort, SettingsMode, StatuslineMode, ToolResultViewMode } from './settings-contracts.js';
13
13
  import type { AutonomyOption, ChipMeta, HelpEntry, LiveSessionEntry, McpPickerItem, ModeOption, PanelPositionMap, PluginPickerItem, ProjectPickerItem, PromptPickEntry, ProviderOption, RefineFailureDecision, RefineFailureModel, ResourceMenuAction, ResourceMenuSnapshot, SendMode, ShadowState, StatuslineItem, ToolPickerItem, WorktreeRow } from './ui-contracts.js';
14
14
  export type { Settings } from './app-settings-type.js';
15
15
  export type { DraftEntry, GoalSummary, QueueItem, ResumeSessionEntry, SlashCommandMatch, } from './app-state-core-types.js';
@@ -62,6 +62,14 @@ export type State = {
62
62
  resumeLoad: ResumeLoadState | null;
63
63
  buffer: string;
64
64
  cursor: number;
65
+ /**
66
+ * Bash mode: the composer is dedicated to shell commands. Entered by
67
+ * pressing `!` on an empty draft, exited by Esc / Backspace-on-empty (or a
68
+ * successful run). While on, Enter submits the draft through the `!` shell
69
+ * path (`/dev`) instead of sending it to the model, and the composer rail
70
+ * relabels itself so the mode is unmistakable.
71
+ */
72
+ bashMode: boolean;
65
73
  streamingText: string;
66
74
  /**
67
75
  * Live tail of the currently streaming tool's stdout/progress text. Mirrors
@@ -104,21 +112,28 @@ export type State = {
104
112
  partialAssistantText: string;
105
113
  } | null;
106
114
  hint: string;
115
+ /** Transient clipboard confirmation; separate from competing hint producers. */
116
+ copiedNotice: string;
117
+ /** Card whose copy icon should flash; null means no highlighted card. */
118
+ copiedEntryId: number | null;
107
119
  /**
108
- * Transient "Copied" confirmation shown when a chat card's copy icon is
109
- * clicked and its content lands on the clipboard. Separate from `hint` so it
110
- * takes precedence over the running-tools indicator (copying tool output
111
- * mid-run is a prime use case) and never races another hint producer. Empty
112
- * string = no notice. Auto-cleared by a host timer.
120
+ * Tool-result inspect overlay. Holds the clicked card's ids; the view
121
+ * resolves the full (untruncated) payload from live history / stream.
122
+ * Null when closed.
113
123
  */
114
- copiedNotice: string;
124
+ inspectOverlay: {
125
+ entryId: number;
126
+ entryIds?: readonly number[] | undefined;
127
+ scroll: number;
128
+ } | null;
115
129
  /**
116
- * Entry id of the card whose copy icon was just clicked, so ScrollableHistory
117
- * can flash that specific icon in the success color while the "Copied" notice
118
- * is active. `null` = no card highlighted. Set and cleared in lockstep with
119
- * `copiedNotice`.
130
+ * Per-card tool-result density overrides, keyed by immutable history id.
131
+ * These are presentation-only: the retained/canonical payload is untouched,
132
+ * so expanding after a compact render never loses data. Changing the global
133
+ * menu setting or clearing the session drops every local override.
134
+ * Group controls write the same override to each member id.
120
135
  */
121
- copiedEntryId: number | null;
136
+ toolResultViewOverrides: ReadonlyMap<number, ToolResultViewMode>;
122
137
  brain: {
123
138
  state: 'idle' | 'deciding' | 'answered' | 'ask_human' | 'denied';
124
139
  source?: string | undefined;
@@ -340,6 +355,8 @@ export type State = {
340
355
  * (API-level provisioning). Default: true.
341
356
  */
342
357
  showModelReasoning: boolean;
358
+ /** Global default for all committed tool-result cards. */
359
+ toolResultViewMode: ToolResultViewMode;
343
360
  /** Agent swarm panel placement: 'bottom' (lower region), 'sidebar' (right sidebar), or 'off'. Default: 'bottom'. */
344
361
  showAgentSwarmPanel: import('./app-settings-type.js').AgentSwarmPanelMode;
345
362
  /** Right sidebar visibility (mirrors Settings.showSidebar). Default: true. */
@@ -36,7 +36,15 @@ export interface AuthLocalPresetRow {
36
36
  noAuth: boolean;
37
37
  hint: string;
38
38
  }
39
- export type AuthOAuthKind = 'chatgpt' | 'claude' | 'copilot';
39
+ /** UI-safe interactive-login descriptor supplied by the CLI host. */
40
+ export interface AuthOAuthStrategyRow {
41
+ id: string;
42
+ providerId: string;
43
+ label: string;
44
+ description?: string | undefined;
45
+ }
46
+ /** Strategy ids are runtime-extensible. */
47
+ export type AuthOAuthKind = string;
40
48
  /**
41
49
  * Bridge the CLI flows use to talk to the panel while a flow runs.
42
50
  * `prompt` REJECTS when the user cancels (Esc) — flows treat a thrown
@@ -120,6 +128,7 @@ export interface AuthPanelHost {
120
128
  listProviders(): Promise<AuthProviderRow[]>;
121
129
  listCatalog(): Promise<AuthCatalogRow[]>;
122
130
  localPresets(): AuthLocalPresetRow[];
131
+ oauthStrategies?: (() => AuthOAuthStrategyRow[]) | undefined;
123
132
  setActiveKey(providerId: string, label: string): Promise<string | null>;
124
133
  deleteKey(providerId: string, label: string): Promise<string | null>;
125
134
  removeProvider(providerId: string): Promise<string | null>;
@@ -198,6 +207,7 @@ export interface AuthPanelState {
198
207
  hint?: string | undefined;
199
208
  providers: AuthProviderRow[];
200
209
  presets: AuthLocalPresetRow[];
210
+ oauthStrategies?: AuthOAuthStrategyRow[] | undefined;
201
211
  catalog: AuthCatalogRow[];
202
212
  /** Cursor index into `authPanelRows(state)` for the current view. */
203
213
  selected: number;
@@ -295,7 +305,7 @@ export type AuthPanelRow = {
295
305
  preset: AuthLocalPresetRow;
296
306
  } | {
297
307
  kind: 'oauth-option';
298
- oauth: AuthOAuthKind;
308
+ oauth: AuthOAuthStrategyRow;
299
309
  } | {
300
310
  kind: 'form-field';
301
311
  field: AuthFormFieldId;
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Copy-hit geometry helpers — pure functions for resolving copy-icon targets
3
+ * in viewport coordinates.
4
+ *
5
+ * Extracted from scrollable-history.tsx.
6
+ */
7
+ import type { ToolResultViewMode } from '../../tool-result-view-mode.js';
1
8
  import type { HistoryEntry } from './index.js';
2
9
  /**
3
10
  * A clickable copy-icon target resolved in viewport coordinates during the
@@ -13,7 +20,30 @@ export interface CopyHit {
13
20
  startRow: number;
14
21
  endRow: number;
15
22
  iconCol: number;
23
+ /**
24
+ * Column of the inspect glyph when this card can open the detail overlay
25
+ * (tools and the live tool stream). Absent for non-tool cards.
26
+ */
27
+ inspectCol?: number | undefined;
28
+ /** Present only for committed tool cards/groups that expose −/+ controls. */
29
+ toolEntryIds?: readonly number[] | undefined;
30
+ toolViewMode?: ToolResultViewMode | undefined;
31
+ /** History-band columns for the spaced `▲ ▼` controls. */
32
+ lessCol?: number | undefined;
33
+ moreCol?: number | undefined;
16
34
  }
35
+ /**
36
+ * Resolve a click on a committed tool card's view-mode control.
37
+ *
38
+ * The control spans {@link VIEW_CONTROL_HIT_WIDTH} cells, not one: an exact
39
+ * single-column match on a one-row header is a target the user cannot
40
+ * reliably hit even when the column is right (and it was not — the columns
41
+ * used to be hard-coded one cell left of the glyphs).
42
+ */
43
+ export declare function findToolViewControl(hits: readonly CopyHit[], row: number, col: number): {
44
+ hit: CopyHit;
45
+ delta: -1 | 1;
46
+ } | null;
17
47
  /** Sentinel returned after copying the active, non-retained tool-stream box. */
18
48
  export declare const LIVE_TOOL_STREAM_COPY_ID = -1;
19
49
  /** Sentinel returned after copying a drag-select-then-right-click selection.
@@ -28,11 +58,22 @@ export declare const SELECTION_COPY_ID = -2;
28
58
  * the most recently rendered card. Exported-shape pure helper for unit tests.
29
59
  */
30
60
  export declare function findCopyHit(hits: readonly CopyHit[], row: number, col: number): CopyHit | null;
61
+ /** Resolve the inspect-icon target under a viewport cell, or null. */
62
+ export declare function findInspectHit(hits: readonly CopyHit[], row: number, col: number): CopyHit | null;
31
63
  /** Resolve a hit to the complete current clipboard payload without performing I/O. */
32
64
  export declare function resolveCopyPayload(hit: CopyHit, entriesById: ReadonlyMap<number, HistoryEntry>, liveToolText?: string | undefined): {
33
65
  entryId: number;
34
66
  text: string;
35
67
  } | null;
68
+ /** Resolve a hit to the inspect overlay payload (title + full body). */
69
+ export declare function resolveInspectPayload(hit: CopyHit, entriesById: ReadonlyMap<number, HistoryEntry>, liveTool?: {
70
+ name: string;
71
+ text: string;
72
+ } | undefined): {
73
+ entryId: number;
74
+ title: string;
75
+ body: string;
76
+ } | null;
36
77
  /**
37
78
  * Rows clipped from the top of the mounted history stack before it appears in
38
79
  * the viewport. Scrolled frames clip by the anchor's row offset; pinned frames
@@ -11,6 +11,17 @@ export declare const COPY_ICON = "\u274F";
11
11
  * the mouse handler treats the icon column plus this span as the target.
12
12
  */
13
13
  export declare const COPY_ICON_WIDTH = 1;
14
+ /**
15
+ * Inspect affordance (U+2315). Same single-cell contract as COPY_ICON.
16
+ * A blank column sits between copy and this glyph so the two hits are
17
+ * not adjacent.
18
+ */
19
+ export declare const INSPECT_ICON = "\u2315";
20
+ export declare const INSPECT_ICON_WIDTH = 1;
21
+ /** Blank cells between the copy glyph and the inspect glyph. */
22
+ export declare const INSPECT_ICON_GAP = 1;
23
+ /** History-band column of inspect relative to the copy column. */
24
+ export declare const INSPECT_COL_OFFSET: number;
14
25
  /**
15
26
  * Return the most useful lossless clipboard representation for any retained
16
27
  * history card. Text cards preserve their original text, pasted user cards use
@@ -22,4 +33,10 @@ export declare function copyableTextForEntry(entry: HistoryEntry): string;
22
33
  export declare function copyableTextForEntries(entries: readonly HistoryEntry[]): string;
23
34
  /** Every retained history entry has either natural text or a raw JSON fallback. */
24
35
  export declare function isCopyableEntry(_entry: HistoryEntry): boolean;
36
+ /** Tool cards (and live tool streams) expose the inspect overlay. */
37
+ export declare function isInspectableEntry(entry: HistoryEntry): boolean;
38
+ /** Full inspect document: status, input, and the canonical untruncated result. */
39
+ export declare function inspectTextForEntry(entry: HistoryEntry): string;
40
+ export declare function inspectTextForEntries(entries: readonly HistoryEntry[]): string;
41
+ export declare function inspectTitleForEntries(entries: readonly HistoryEntry[]): string;
25
42
  //# sourceMappingURL=copy-icon.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import type { EntryHeightCache } from '../../height-cache.js';
2
+ import type { ToolResultViewMode } from '../../tool-result-view-mode.js';
2
3
  import type { CopyHit } from './copy-geometry.js';
3
4
  import { type RenderGroup } from './tool-group.js';
4
5
  interface CopyRegistry {
@@ -24,6 +25,7 @@ export declare function buildCopyRegistry(opts: {
24
25
  iconCol: number;
25
26
  showModelReasoning?: boolean | undefined;
26
27
  liveToolVisible: boolean;
28
+ viewModeForEntry?: ((entryId: number) => ToolResultViewMode) | undefined;
27
29
  }): CopyRegistry;
28
30
  export {};
29
31
  //# sourceMappingURL=copy-registry.d.ts.map
@@ -6,6 +6,12 @@ import type { HistoryEntry } from './types.js';
6
6
  * and count the rest; the side panel keeps the full set.
7
7
  */
8
8
  export declare const MAX_MEMORY_PROOF_ROWS = 4;
9
+ /** Shared open left-rail used by transcript cards (user, thinking, error, …). */
10
+ export declare function HistoryRail({ color, marginY, children, }: {
11
+ color: string;
12
+ marginY?: number;
13
+ children: React.ReactNode;
14
+ }): React.ReactElement;
9
15
  /**
10
16
  * Render the signed score contributions as `metadata 0.72×0.48 +0.34 │ …`.
11
17
  * Uses U+2212 for negatives so a minus is not mistaken for a hyphen inside
@@ -46,23 +52,9 @@ export declare function memoryLifecycleStyle(action: Extract<HistoryEntry, {
46
52
  }>['action']): {
47
53
  icon: string;
48
54
  color: string;
55
+ title: string;
49
56
  };
50
57
  export declare function brainRiskColor(risk: Extract<HistoryEntry, {
51
58
  kind: 'brain';
52
59
  }>['risk']): string;
53
- /**
54
- * Full-bordered notice card for `warn`/`error` entries. A rounded box with an
55
- * icon + label header in the accent color and the message body below, wrapped
56
- * to the terminal width and split across lines so multi-line diagnostics stay
57
- * readable instead of overflowing a single-line strip. Slash-command output
58
- * is normalized before it reaches Ink so cursor/control sequences can never
59
- * paint outside the notice card's measured geometry.
60
- */
61
- export declare function NoticeCard({ icon, label, color, text, termWidth, }: {
62
- icon: string;
63
- label: string;
64
- color: string;
65
- text: string;
66
- termWidth: number;
67
- }): React.ReactElement;
68
60
  //# sourceMappingURL=entry-helpers.d.ts.map
@@ -1,8 +1,9 @@
1
1
  import type { TodoItem } from '@wrongstack/core/agent';
2
2
  import React from 'react';
3
+ import type { ToolResultViewMode } from '../../tool-result-view-mode.js';
3
4
  import type { HistoryEntry } from './types.js';
4
5
  export { councilHeadline, councilSeatLine } from './entry-helpers.js';
5
- export declare const Entry: React.MemoExoticComponent<({ entry, termWidth, termHeight, setSuggestions, autonomyMode, multiDiffSummaryThreshold, todos, showModelReasoning, showSageMemoryInject, }: {
6
+ export declare const Entry: React.MemoExoticComponent<({ entry, termWidth, termHeight, setSuggestions, autonomyMode, multiDiffSummaryThreshold, todos, showModelReasoning, showSageMemoryInject, toolResultViewMode, }: {
6
7
  entry: HistoryEntry;
7
8
  termWidth: number;
8
9
  /** Available managed-history rows; used by height-aware entries such as the banner. */
@@ -29,5 +30,6 @@ export declare const Entry: React.MemoExoticComponent<({ entry, termWidth, termH
29
30
  * Default: false.
30
31
  */
31
32
  showSageMemoryInject?: boolean | undefined;
33
+ toolResultViewMode?: ToolResultViewMode | undefined;
32
34
  }) => React.ReactElement | null>;
33
35
  //# sourceMappingURL=entry.d.ts.map
@@ -16,5 +16,5 @@ export { AssistantStreamBox, assistantStreamBoxHeight, countLines, firstNonEmpty
16
16
  * primitives or stable reducer references, so default shallow
17
17
  * comparison is sufficient.
18
18
  */
19
- export declare const History: React.MemoExoticComponent<({ entries, generation, toolStream, setSuggestions, autonomyMode, multiDiffSummaryThreshold, todos, showModelReasoning, showSageMemoryInject, }: HistoryProps) => React.ReactElement>;
19
+ export declare const History: React.MemoExoticComponent<({ entries, generation, toolStream, setSuggestions, autonomyMode, multiDiffSummaryThreshold, todos, showModelReasoning, showSageMemoryInject, toolResultViewMode, toolResultViewOverrides, }: HistoryProps) => React.ReactElement>;
20
20
  //# sourceMappingURL=index.d.ts.map
@@ -1,17 +1,16 @@
1
1
  import type React from 'react';
2
+ /** Header the SAGE inject panel and its height estimator share. */
3
+ export declare function sageInjectTitle(toolName: string, count: number, stats?: string): string;
2
4
  /**
3
5
  * Compact magenta-bordered panel rendering SAGE memory-injection lines
4
6
  * appended to a tool result. Renders nothing when `sageLines` is empty.
5
7
  *
6
8
  * Layout:
7
9
  *
8
- * ┌ 🧠 SAGE MEMORY INJECTED · <tool> N memories ────────────────┐
9
- * │ [kind][importance] │
10
- * │ text body (wrapped to panel width) │
11
- * │ id: mem_… anchor: pkg/path relation: about_file │
12
- * │ tags: t1, t2, t3 │
13
- * │ [next memory …] │
14
- * └────────────────────────────────────────────────────────────────────┘
10
+ * │ 🧠 SAGE · <tool> N memories
11
+ * │ [kind][importance]
12
+ * │ text body
13
+ * │ id / anchor / relation / tags
15
14
  *
16
15
  * Each memory is parsed by `parseSageMemoryLine` and rendered as key/value
17
16
  * rows instead of the raw `[kind] <memory id="…">text</memory> (anchor) […]`
@@ -24,8 +23,7 @@ export declare function SageMemoryBlock({ sageLines, toolName, stats, compact, }
24
23
  stats?: string | undefined;
25
24
  /**
26
25
  * Compact (opt-in): one magenta chip with count + first preview.
27
- * Full (default): bordered panel with every memory row. The bordered form
28
- * is the canonical SAGE surface and is asserted by every render test in
26
+ * Full (default): left-rail panel with every memory row. Asserted by
29
27
  * `tests/glob-sage-render.test.tsx` and `tests/sage-memory-block-render.test.tsx`.
30
28
  */
31
29
  compact?: boolean | undefined;
@@ -34,6 +34,11 @@ export interface HistoryScrollController {
34
34
  * coordinate contract.
35
35
  */
36
36
  hasCopyTargetAt(row: number, col: number): boolean;
37
+ /**
38
+ * True when the viewport cell lands on a tool card's inspect icon.
39
+ * Same `row`/`col` contract as {@link hasCopyTargetAt}.
40
+ */
41
+ hasInspectTargetAt(row: number, col: number): boolean;
37
42
  /**
38
43
  * Handle a left-click inside the history viewport. `row` is 0-based from the
39
44
  * viewport top; `col` is 0-based from the LEFT EDGE OF THE HISTORY BAND, not
@@ -45,6 +50,16 @@ export interface HistoryScrollController {
45
50
  * entry id is returned; otherwise returns null.
46
51
  */
47
52
  copyAtViewportCell(row: number, col: number): Promise<number | null>;
53
+ /**
54
+ * Resolve the inspect target for the cell, or null on a miss.
55
+ * The host opens the overlay; the view loads the full payload from live state.
56
+ */
57
+ inspectAtViewportCell(row: number, col: number): {
58
+ entryId: number;
59
+ entryIds?: readonly number[];
60
+ } | null;
61
+ /** Activate a committed tool card's −/+ control. */
62
+ activateToolViewControlAt(row: number, col: number): boolean;
48
63
  /**
49
64
  * Begin a drag-to-select gesture at `row`,`col` (history-band viewport cell).
50
65
  * The cell must be inside a card's row range and inside the rendered band
@@ -66,7 +81,7 @@ export interface HistoryScrollController {
66
81
  * Copy contract (block-based): the selection rect only decides WHICH
67
82
  * blocks it touches — every touched block is copied in full via
68
83
  * copyableTextForEntry (the same payload the copy icon writes), so inline
69
- * chrome (card gutter, `👤 USER ` label, `ℹ ` icon) neither leaks into
84
+ * chrome (card gutter, `▸ USER ` label, `· ` icon) neither leaks into
70
85
  * nor offsets the payload. Columns still drive the GESTURE: band bounds
71
86
  * (`isOutOfBand`), the highlight band, and the drag-vs-click distinction
72
87
  * (a zero-size selection commits nothing).
@@ -1,8 +1,7 @@
1
1
  /**
2
- * Right-edge rail for the managed viewport. Its first column is the copy icon
3
- * (or blank), the second is a visual gap, and the third is the scrollbar track.
4
- * All three columns are always reserved, so copy affordances and scrollability
5
- * never reflow chat content.
2
+ * Right-edge rail for the managed viewport: copy, inspect, selection band,
3
+ * and scrollbar track. Columns are always reserved, so affordances never
4
+ * reflow chat content.
6
5
  *
7
6
  * The gap column doubles as the drag-selection highlight band: while a drag is
8
7
  * in progress, the subscribing component re-renders ONLY this rail (via
@@ -4,6 +4,13 @@ export declare const MAX_STREAM_DISPLAY_CHARS = 480;
4
4
  export declare function assistantStreamBoxHeight(): number;
5
5
  /** Fixed layout height of the live tool-stream tail for scroll-range math. */
6
6
  export declare function toolStreamBoxHeight(name: string): number;
7
+ /** A started tool without partial output occupies one stable live-tail row. */
8
+ export declare function toolPendingLineHeight(): number;
9
+ export declare const ToolPendingLine: React.MemoExoticComponent<({ name, startedAt, termWidth, }: {
10
+ name: string;
11
+ startedAt: number;
12
+ termWidth: number;
13
+ }) => React.ReactElement>;
7
14
  /**
8
15
  * Build the CONSTANT-height content block for the live tool-stream box: always
9
16
  * exactly `maxLines` rows, newest-pinned-to-bottom, every line truncated to
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Shared geometry for the committed tool card / tool group header.
3
+ *
4
+ * The header is rendered as a single `<Text>` run but hit-tested by column in
5
+ * `copy-registry.ts`, so the two MUST agree on where the ▲ / ▼ view-mode
6
+ * controls sit. They did not: the registry hard-coded columns 2 and 5 while
7
+ * the glyphs render at 3 and 6 (the card lead is three cells), so a click on
8
+ * either triangle landed one cell left of the glyph and did nothing. Both
9
+ * sides now derive their columns from the strings below — the same
10
+ * render/hit-test single-source pattern the status-bar chips use.
11
+ */
12
+ /** Card lead for a card that owns a body (`hasBody`), and for a closed one. */
13
+ export declare const CARD_LEAD_OPEN = "\u256D\u2500 ";
14
+ export declare const CARD_LEAD_CLOSED = "\u2514\u2500 ";
15
+ /** "Show less" (▲) and "show more" (▼) controls: glyph plus two spaces. */
16
+ export declare const VIEW_CONTROL_LESS = "\u25B2 ";
17
+ export declare const VIEW_CONTROL_MORE = "\u25BC ";
18
+ export declare const VIEW_CONTROL_PREFIX = "\u25B2 \u25BC ";
19
+ /**
20
+ * Clickable width of one control: the glyph plus one trailing space. A
21
+ * one-cell-wide target in a one-row-tall header is unforgiving enough that a
22
+ * correct column still feels broken. The SECOND trailing space is
23
+ * deliberately excluded so a dead cell separates the two controls and a
24
+ * slightly-right click on ▲ can never fire ▼.
25
+ */
26
+ export declare const VIEW_CONTROL_HIT_WIDTH = 2;
27
+ /**
28
+ * 0-based history-band columns of the two controls. Both card leads are the
29
+ * same width, so the columns do not depend on `hasBody` or view mode.
30
+ */
31
+ export declare function viewControlColumns(): {
32
+ lessCol: number;
33
+ moreCol: number;
34
+ };
35
+ //# sourceMappingURL=tool-card-geometry.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import type React from 'react';
2
+ import type { ToolResultViewMode } from '../../tool-result-view-mode.js';
2
3
  interface ToolCardProps {
3
4
  glyph: string;
4
5
  color: string;
@@ -9,6 +10,7 @@ interface ToolCardProps {
9
10
  termWidth: number;
10
11
  hasBody: boolean;
11
12
  children?: React.ReactNode;
13
+ viewMode?: ToolResultViewMode | undefined;
12
14
  }
13
15
  /**
14
16
  * Shared terminal-native anatomy for committed tool results. The open left
@@ -16,6 +18,6 @@ interface ToolCardProps {
16
18
  * heavy full-width modal, while the compact header makes tool/state/timing
17
19
  * easy to scan in long transcripts.
18
20
  */
19
- export declare function ToolCard({ glyph, color, title, detail, meta, ok, termWidth, hasBody, children, }: ToolCardProps): React.ReactElement;
21
+ export declare function ToolCard({ glyph, color, title, detail, meta, ok, termWidth, hasBody, children, viewMode, }: ToolCardProps): React.ReactElement;
20
22
  export {};
21
23
  //# sourceMappingURL=tool-card.d.ts.map
@@ -1,11 +1,13 @@
1
1
  import type React from 'react';
2
+ import type { ToolResultViewMode } from '../../tool-result-view-mode.js';
2
3
  import type { HistoryEntry } from './types.js';
3
- export declare function ToolEntry({ entry, termWidth, multiDiffSummaryThreshold, showSageMemoryInject, }: {
4
+ export declare function ToolEntry({ entry, termWidth, multiDiffSummaryThreshold, showSageMemoryInject, viewMode, }: {
4
5
  entry: Extract<HistoryEntry, {
5
6
  kind: 'tool';
6
7
  }>;
7
8
  termWidth: number;
8
9
  multiDiffSummaryThreshold?: number | undefined;
9
10
  showSageMemoryInject?: boolean | undefined;
11
+ viewMode?: ToolResultViewMode | undefined;
10
12
  }): React.ReactElement;
11
13
  //# sourceMappingURL=tool-entry.d.ts.map
@@ -15,6 +15,7 @@
15
15
  * isolated tool entries pass through unchanged.
16
16
  */
17
17
  import type React from 'react';
18
+ import type { ToolResultViewMode } from '../../tool-result-view-mode.js';
18
19
  import type { HistoryEntry } from './types.js';
19
20
  export interface ToolGroupData {
20
21
  /** Shared tool name (e.g. "read", "bash", "grep"). */
@@ -38,13 +39,13 @@ export type RenderGroup = {
38
39
  /** Stable id used by the height cache and React keys for a render group. */
39
40
  export declare function renderGroupId(group: RenderGroup): number;
40
41
  /** Conservative, bounded row estimate used before a group is measured. */
41
- export declare function estimateRenderGroupRows(group: RenderGroup, contentWidth: number, showSageMemoryInject?: boolean): number;
42
+ export declare function estimateRenderGroupRows(group: RenderGroup, contentWidth: number, showSageMemoryInject?: boolean, viewMode?: ToolResultViewMode): number;
42
43
  /**
43
44
  * Walk entries and merge consecutive same-tool entries into bounded groups.
44
45
  * Non-tool entries and isolated tool entries (no consecutive sibling)
45
46
  * remain as individual items.
46
47
  */
47
- export declare function groupEntries(entries: readonly HistoryEntry[]): RenderGroup[];
48
+ export declare function groupEntries(entries: readonly HistoryEntry[], viewModeForEntry?: (entryId: number) => ToolResultViewMode): RenderGroup[];
48
49
  /**
49
50
  * Render a group of consecutive same-tool entries.
50
51
  *
@@ -59,9 +60,10 @@ export declare function groupEntries(entries: readonly HistoryEntry[]): RenderGr
59
60
  * Each sub-entry is rendered as a compact list item with its index,
60
61
  * arg summary, timing, and a single-line output preview.
61
62
  */
62
- declare function ToolGroupImpl({ data, termWidth, }: {
63
+ declare function ToolGroupImpl({ data, termWidth, viewMode, }: {
63
64
  data: ToolGroupData;
64
65
  termWidth: number;
66
+ viewMode?: ToolResultViewMode | undefined;
65
67
  }): React.ReactElement;
66
68
  /**
67
69
  * One render group of compacted tool calls.
@@ -1,6 +1,7 @@
1
1
  import type { TodoItem } from '@wrongstack/core/agent';
2
2
  import type { Lang } from '../../highlight.js';
3
3
  import type { HistoryEntry } from '../../history-entry.js';
4
+ import type { ToolResultViewMode } from '../../tool-result-view-mode.js';
4
5
  export type { AutonomyAgentStatus, HistoryEntry, MemoryActivationItem, } from '../../history-entry.js';
5
6
  export interface HistoryProps {
6
7
  entries: HistoryEntry[];
@@ -64,6 +65,10 @@ export interface HistoryProps {
64
65
  * Default: false.
65
66
  */
66
67
  showSageMemoryInject?: boolean | undefined;
68
+ /** Global default; individual entry ids may override it. */
69
+ toolResultViewMode?: ToolResultViewMode | undefined;
70
+ toolResultViewOverrides?: ReadonlyMap<number, ToolResultViewMode> | undefined;
71
+ onToolResultViewChange?: ((entryIds: readonly number[], mode: ToolResultViewMode) => void) | undefined;
67
72
  }
68
73
  export interface BodySegment {
69
74
  type: 'prose' | 'code';
@@ -1,4 +1,5 @@
1
1
  import { type ScrollAnchor, type ScrollGeometry } from '../../scroll-anchor.js';
2
+ import type { ToolResultViewMode } from '../../tool-result-view-mode.js';
2
3
  import type { CopyHit } from './copy-geometry.js';
3
4
  import type { HistoryScrollController } from './scroll-controller-types.js';
4
5
  import type { MountedCardSpan } from './scrollbar-geometry.js';
@@ -54,6 +55,7 @@ interface UseHistoryControllerOptions {
54
55
  controllerRef?: {
55
56
  current: HistoryScrollController | null;
56
57
  } | undefined;
58
+ onToolResultViewChange?: ((entryIds: readonly number[], mode: ToolResultViewMode) => void) | undefined;
57
59
  }
58
60
  export declare function useHistoryController(opts: UseHistoryControllerOptions): {
59
61
  controller: HistoryScrollController;