@oh-my-pi/pi-tui 18.13.1 → 18.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -15
- package/dist/types/apps/session-picker.d.ts +4 -0
- package/dist/types/chat/assistant-message.d.ts +1 -1
- package/dist/types/chat/bash-execution.d.ts +2 -0
- package/dist/types/chat/display-preferences.d.ts +2 -0
- package/dist/types/chat/tool-execution.d.ts +1 -1
- package/dist/types/chrome/context-map.d.ts +15 -0
- package/dist/types/chrome/context-presentation.d.ts +48 -0
- package/dist/types/chrome/context-view-model.d.ts +77 -0
- package/dist/types/components/editor.d.ts +2 -0
- package/dist/types/overlays/advisor-config.d.ts +6 -0
- package/dist/types/overlays/annotation-overlay.d.ts +7 -1
- package/dist/types/overlays/annotation-types.d.ts +28 -0
- package/dist/types/overlays/ask-dialog.d.ts +9 -2
- package/dist/types/overlays/context-viewer-format.d.ts +42 -0
- package/dist/types/overlays/context-viewer-overview.d.ts +70 -0
- package/dist/types/overlays/context-viewer.d.ts +25 -0
- package/dist/types/overlays/hook-input.d.ts +1 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +2 -0
- package/dist/types/overlays/running-subagent-badge.d.ts +7 -1
- package/dist/types/overlays/session-selector.d.ts +13 -2
- package/dist/types/status-line/context-usage.d.ts +15 -30
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme.d.ts +0 -23
- package/dist/types/tools/ask.d.ts +2 -0
- package/dist/types/tools/bash-interactive.d.ts +6 -0
- package/dist/types/tools/fetch.d.ts +1 -0
- package/dist/types/tools/github.d.ts +3 -0
- package/dist/types/tools/read.d.ts +3 -0
- package/dist/types/tools/wait.d.ts +6 -0
- package/dist/types/tui.d.ts +6 -1
- package/package.json +10 -10
- package/src/apps/session-picker.ts +5 -0
- package/src/chat/advisor-message.ts +2 -0
- package/src/chat/assistant-message.ts +3 -1
- package/src/chat/bash-execution.ts +6 -0
- package/src/chat/display-preferences.ts +3 -0
- package/src/chat/tool-execution.ts +7 -3
- package/src/chrome/context-map.ts +62 -0
- package/src/chrome/context-presentation.ts +26 -0
- package/src/chrome/context-view-model.ts +73 -0
- package/src/components/editor.ts +26 -1
- package/src/components/markdown.ts +14 -2
- package/src/native/backend.ts +2 -64
- package/src/overlays/advisor-config.ts +9 -2
- package/src/overlays/annotation-overlay.ts +125 -37
- package/src/overlays/annotation-types.ts +24 -0
- package/src/overlays/ask-dialog.ts +55 -15
- package/src/overlays/context-viewer-format.ts +145 -0
- package/src/overlays/context-viewer-overview.ts +381 -0
- package/src/overlays/context-viewer.ts +728 -0
- package/src/overlays/hook-input.ts +2 -0
- package/src/overlays/hook-selector.ts +2 -2
- package/src/overlays/plan-review-overlay.ts +13 -2
- package/src/overlays/running-subagent-badge.ts +12 -1
- package/src/overlays/session-selector.ts +195 -26
- package/src/overlays/usage-dashboard.ts +5 -12
- package/src/prompt/custom-editor.ts +6 -1
- package/src/render/output-pane.ts +3 -3
- package/src/status-line/context-usage.ts +56 -580
- package/src/terminal.ts +24 -2
- package/src/theme/symbols.ts +16 -0
- package/src/theme/theme.ts +0 -65
- package/src/tools/ask.ts +2 -0
- package/src/tools/bash-interactive.ts +6 -1
- package/src/tools/bash.ts +5 -23
- package/src/tools/fetch.ts +1 -0
- package/src/tools/github.ts +23 -3
- package/src/tools/job-widget.ts +13 -1
- package/src/tools/json-tree.ts +1 -1
- package/src/tools/read.ts +3 -1
- package/src/tools/wait.ts +6 -0
- package/src/tui.ts +30 -5
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import type { AgentMessage, Tokenizer } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { CompactionSettings } from "@oh-my-pi/pi-agent-core/compaction";
|
|
3
3
|
import type { Tool as AiTool, Model } from "@oh-my-pi/pi-ai";
|
|
4
|
-
import type { Theme } from "../theme/index.js";
|
|
5
|
-
import { Container } from "../tui.js";
|
|
6
|
-
import type { TspText } from "@oh-my-pi/pi-wire";
|
|
7
|
-
import type { DescribeContext, NativeNode } from "../native/node.js";
|
|
8
4
|
interface ContextSkill {
|
|
9
5
|
readonly name: string;
|
|
10
6
|
readonly description?: string;
|
|
@@ -63,8 +59,6 @@ interface CategoryInfo {
|
|
|
63
59
|
id: CategoryId;
|
|
64
60
|
label: string;
|
|
65
61
|
tokens: number;
|
|
66
|
-
color: "accent" | "warning" | "success" | "userMessageText" | "customMessageLabel";
|
|
67
|
-
glyph: string;
|
|
68
62
|
}
|
|
69
63
|
export interface ContextBreakdown {
|
|
70
64
|
model: Model | undefined;
|
|
@@ -128,6 +122,21 @@ export declare function getToolSchemaMetadataRevision(tools: ToolSchemaSource):
|
|
|
128
122
|
* discovered-agent metadata changes).
|
|
129
123
|
*/
|
|
130
124
|
export declare function invalidateToolSchemaMetadata(tools: ToolSchemaSource): void;
|
|
125
|
+
/** Provider-visible text of one tool definition, in wire order. */
|
|
126
|
+
export interface ToolSchemaFragments {
|
|
127
|
+
/** Name, description, wire schema JSON, then rendered examples; fields that cannot be serialized are omitted. */
|
|
128
|
+
readonly fragments: string[];
|
|
129
|
+
/** `fragments` with the schema JSON indented for reading; present only when a display indent was requested. */
|
|
130
|
+
readonly display?: string[];
|
|
131
|
+
/** Why serialization stopped; the schema or examples after that point are missing from `fragments`. */
|
|
132
|
+
readonly schemaError?: string;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Provider-visible text of one tool definition. `displayIndent` additionally
|
|
136
|
+
* returns `display`, the same fragments with the schema JSON indented, from
|
|
137
|
+
* the same schema conversion.
|
|
138
|
+
*/
|
|
139
|
+
export declare function toolSchemaFragments(tool: ContextTool, displayIndent?: number): ToolSchemaFragments;
|
|
131
140
|
/**
|
|
132
141
|
* Estimate provider-visible tool-schema tokens.
|
|
133
142
|
*
|
|
@@ -158,28 +167,4 @@ export declare function computeNonMessageBreakdown(session: NonMessageTokenSourc
|
|
|
158
167
|
* session and model.
|
|
159
168
|
*/
|
|
160
169
|
export declare function computeContextBreakdown(session: ContextUsageSession, options: ContextUsageOptions): ContextBreakdown;
|
|
161
|
-
/** The `/context` title: the model once one is selected (the body leads with its window). */
|
|
162
|
-
export declare function contextUsageHead(breakdown: ContextBreakdown): TspText;
|
|
163
|
-
/**
|
|
164
|
-
* Native context-usage panel for terminals without `meter`: the cell grid as
|
|
165
|
-
* unwrapped styled rows beside the legend. The terminal places the legend
|
|
166
|
-
* beside the grid, or below it when narrow.
|
|
167
|
-
*/
|
|
168
|
-
export declare function describeContextUsage(breakdown: ContextBreakdown): NativeNode;
|
|
169
|
-
/**
|
|
170
|
-
* The `/context` report body, without a frame or title of its own (the panel
|
|
171
|
-
* showing it is titled {@link contextUsageHead}): ANSI renders the cell grid
|
|
172
|
-
* beside the legend; natively it is drawn with `meter`s where the terminal has
|
|
173
|
-
* them and as the glyph grid otherwise.
|
|
174
|
-
*/
|
|
175
|
-
export declare class ContextUsageView extends Container {
|
|
176
|
-
#private;
|
|
177
|
-
constructor(breakdown: ContextBreakdown, theme: Theme);
|
|
178
|
-
describe(cx: DescribeContext): NativeNode;
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Render a colorful context-usage panel as ANSI text. Output is a series of
|
|
182
|
-
* lines pairing the grid (left) with the legend (right).
|
|
183
|
-
*/
|
|
184
|
-
export declare function renderContextUsage(breakdown: ContextBreakdown, theme: Theme): string;
|
|
185
170
|
export {};
|
|
@@ -2,7 +2,7 @@ export type SymbolPreset = "unicode" | "nerd" | "ascii";
|
|
|
2
2
|
/**
|
|
3
3
|
* All available symbol keys organized by category.
|
|
4
4
|
*/
|
|
5
|
-
export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted" | "status.done" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "progress.filled" | "progress.empty" | "context.speculation" | "context.compaction" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxDotted.horizontal" | "boxDotted.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.powerlineCapLeft" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.prewalk" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.worktree" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.pin" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.subscription" | "icon.advisor" | "icon.advisorClosed" | "icon.time" | "icon.omp" | "icon.ghost" | "icon.agents" | "icon.job" | "icon.cache" | "icon.cacheMiss" | "icon.input" | "icon.output" | "icon.throughput" | "icon.intelligence" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.vimNormal" | "icon.vimInsert" | "icon.vimVisual" | "icon.vimVisualLine" | "icon.vimReplace" | "cmd.action" | "cmd.prompt" | "cmd.extension" | "cmd.settings" | "cmd.gear" | "cmd.shield" | "cmd.wave" | "cmd.compass" | "cmd.inbox" | "cmd.swap" | "cmd.expand" | "cmd.computer" | "cmd.eye" | "cmd.todo" | "cmd.stats" | "cmd.news" | "cmd.keyboard" | "cmd.export" | "cmd.clipboard" | "cmd.share" | "cmd.broadcast" | "cmd.globe" | "cmd.copy" | "cmd.plus" | "cmd.restart" | "cmd.eraser" | "cmd.trash" | "cmd.compress" | "cmd.vibrate" | "cmd.handoff" | "cmd.history" | "cmd.question" | "cmd.rocket" | "cmd.stethoscope" | "cmd.redo" | "cmd.bug" | "cmd.memory" | "cmd.pencil" | "cmd.folderMove" | "cmd.folderPlus" | "cmd.folderMinus" | "cmd.hammer" | "cmd.power" | "cmd.cart" | "icon.mic" | "icon.camera" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.max" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "advisor.rail" | "skill.rail" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.julia" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "chip.image" | "chip.video" | "chip.paste" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.files" | "tab.shell" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers" | "tool.write" | "tool.edit" | "tool.bash" | "tool.ssh" | "tool.lsp" | "tool.gh" | "tool.webSearch" | "tool.exa" | "tool.browser" | "tool.eval" | "tool.debug" | "tool.mcp" | "tool.job" | "tool.launch" | "tool.task" | "tool.todo" | "tool.memory" | "tool.ask" | "tool.resolve" | "tool.review" | "tool.goal" | "tool.irc" | "tool.delete" | "tool.move" | "key.ctrl" | "key.ctrlMac" | "key.shift" | "key.alt" | "key.altMac" | "key.super" | "key.superMac" | "key.joiner" | "key.enter" | "key.esc" | "key.tab" | "key.backspace" | "key.delete" | "key.space" | "key.up" | "key.down" | "key.left" | "key.right";
|
|
5
|
+
export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted" | "status.done" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "progress.filled" | "progress.empty" | "context.speculation" | "context.compaction" | "context.content" | "context.messages" | "context.buffer" | "context.free" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxDotted.horizontal" | "boxDotted.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.powerlineCapLeft" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.prewalk" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.worktree" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.pin" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.subscription" | "icon.advisor" | "icon.advisorClosed" | "icon.time" | "icon.omp" | "icon.ghost" | "icon.agents" | "icon.job" | "icon.cache" | "icon.cacheMiss" | "icon.input" | "icon.output" | "icon.throughput" | "icon.intelligence" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.vimNormal" | "icon.vimInsert" | "icon.vimVisual" | "icon.vimVisualLine" | "icon.vimReplace" | "cmd.action" | "cmd.prompt" | "cmd.extension" | "cmd.settings" | "cmd.gear" | "cmd.shield" | "cmd.wave" | "cmd.compass" | "cmd.inbox" | "cmd.swap" | "cmd.expand" | "cmd.computer" | "cmd.eye" | "cmd.todo" | "cmd.stats" | "cmd.news" | "cmd.keyboard" | "cmd.export" | "cmd.clipboard" | "cmd.share" | "cmd.broadcast" | "cmd.globe" | "cmd.copy" | "cmd.plus" | "cmd.restart" | "cmd.eraser" | "cmd.trash" | "cmd.compress" | "cmd.vibrate" | "cmd.handoff" | "cmd.history" | "cmd.question" | "cmd.rocket" | "cmd.stethoscope" | "cmd.redo" | "cmd.bug" | "cmd.memory" | "cmd.pencil" | "cmd.folderMove" | "cmd.folderPlus" | "cmd.folderMinus" | "cmd.hammer" | "cmd.power" | "cmd.cart" | "icon.mic" | "icon.camera" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.max" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "advisor.rail" | "skill.rail" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.julia" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "chip.image" | "chip.video" | "chip.paste" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.files" | "tab.shell" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers" | "tool.write" | "tool.edit" | "tool.bash" | "tool.ssh" | "tool.lsp" | "tool.gh" | "tool.webSearch" | "tool.exa" | "tool.browser" | "tool.eval" | "tool.debug" | "tool.mcp" | "tool.job" | "tool.launch" | "tool.task" | "tool.todo" | "tool.memory" | "tool.ask" | "tool.resolve" | "tool.review" | "tool.goal" | "tool.irc" | "tool.delete" | "tool.move" | "key.ctrl" | "key.ctrlMac" | "key.shift" | "key.alt" | "key.altMac" | "key.super" | "key.superMac" | "key.joiner" | "key.enter" | "key.esc" | "key.tab" | "key.backspace" | "key.delete" | "key.space" | "key.up" | "key.down" | "key.left" | "key.right";
|
|
6
6
|
export type SymbolMap = Record<SymbolKey, string>;
|
|
7
7
|
/**
|
|
8
8
|
* Icon vocabulary for slash-command autocomplete type indicators. Each name
|
|
@@ -114,29 +114,6 @@ export declare function stopThemeWatcher(): void;
|
|
|
114
114
|
* Used by HTML export to generate CSS custom properties.
|
|
115
115
|
*/
|
|
116
116
|
export declare function getResolvedThemeColors(themeName?: string): Promise<Record<string, string>>;
|
|
117
|
-
/** One appearance variant of {@link NativeThemePalette}: token name → `#rrggbb`. */
|
|
118
|
-
export type NativeThemeVariant = Record<string, string>;
|
|
119
|
-
/** omp's resolved theme for a Tern Surface Protocol terminal (the `t` verb body minus `sf`). */
|
|
120
|
-
export interface NativeThemePalette {
|
|
121
|
-
dark?: NativeThemeVariant;
|
|
122
|
-
light?: NativeThemeVariant;
|
|
123
|
-
name: {
|
|
124
|
-
dark?: string;
|
|
125
|
-
light?: string;
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Cheap identity of everything {@link getNativeThemePalette} depends on;
|
|
130
|
-
* recompute the palette only when it changes.
|
|
131
|
-
*/
|
|
132
|
-
export declare function getNativeThemePaletteKey(): string;
|
|
133
|
-
/**
|
|
134
|
-
* omp's theme for a native surface: with auto theme, the configured dark and
|
|
135
|
-
* light themes; otherwise (or while the theme selector previews one) the
|
|
136
|
-
* active theme under its own appearance only. Themes without a loadable
|
|
137
|
-
* definition (in-memory instances) contribute nothing.
|
|
138
|
-
*/
|
|
139
|
-
export declare function getNativeThemePalette(): NativeThemePalette;
|
|
140
117
|
/**
|
|
141
118
|
* Check if a theme is a "light" theme by analyzing its status-line background
|
|
142
119
|
* luminance. Loads theme JSON synchronously (built-in or custom file on disk)
|
|
@@ -26,6 +26,8 @@ export interface AskToolDetails {
|
|
|
26
26
|
customInput?: string;
|
|
27
27
|
/** Optional note attached to the selected answer in the rich ask dialog. */
|
|
28
28
|
note?: string;
|
|
29
|
+
/** Label of the option the note is on; unset when it is on the custom answer. */
|
|
30
|
+
noteOption?: string;
|
|
29
31
|
/** True when the answer was auto-selected because the dialog timed out. */
|
|
30
32
|
timedOut?: boolean;
|
|
31
33
|
/** Multi-part question mode */
|
|
@@ -6,6 +6,12 @@ import type { Theme } from "../theme/theme.js";
|
|
|
6
6
|
export interface BashInteractiveTerminalBackend {
|
|
7
7
|
resize(columns: number, rows: number): void;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Translates a TUI key event into the bytes a terminal would send to a PTY
|
|
11
|
+
* child; key releases encode to "". Shared by the agent `pty` console and
|
|
12
|
+
* `!` commands (`BashExecutionComponent.encodePtyInput`).
|
|
13
|
+
*/
|
|
14
|
+
export declare function normalizeInputForPty(data: string, applicationCursorKeysMode: boolean): string;
|
|
9
15
|
/** Interactive terminal overlay driven by an external PTY controller. */
|
|
10
16
|
export declare class BashInteractiveOverlayComponent implements Component {
|
|
11
17
|
#private;
|
|
@@ -85,6 +85,7 @@ export interface GhRunWatchFailedLogDetails {
|
|
|
85
85
|
conclusion?: string;
|
|
86
86
|
tail?: string;
|
|
87
87
|
available: boolean;
|
|
88
|
+
unavailableReason?: string;
|
|
88
89
|
}
|
|
89
90
|
/** Live workflow watcher display snapshot. */
|
|
90
91
|
export interface GhRunWatchViewDetails {
|
|
@@ -130,6 +131,8 @@ export interface GhFailedJobLog {
|
|
|
130
131
|
full?: string;
|
|
131
132
|
tail?: string;
|
|
132
133
|
available: boolean;
|
|
134
|
+
unavailableReason?: string;
|
|
135
|
+
truncated?: boolean;
|
|
133
136
|
}
|
|
134
137
|
/** Choose the terminal conclusion or current job state. */
|
|
135
138
|
export declare function formatJobState(job: GhRunJobSnapshot): string;
|
|
@@ -29,7 +29,10 @@ export interface ReadToolDetails {
|
|
|
29
29
|
suffixResolution?: {
|
|
30
30
|
from: string;
|
|
31
31
|
to: string;
|
|
32
|
+
raw?: boolean;
|
|
32
33
|
};
|
|
34
|
+
/** Raw source output keeps descriptive diagnostics in details, not in the body. */
|
|
35
|
+
raw?: boolean;
|
|
33
36
|
url?: string;
|
|
34
37
|
finalUrl?: string;
|
|
35
38
|
contentType?: string;
|
|
@@ -55,6 +55,12 @@ export interface JobSnapshot {
|
|
|
55
55
|
* on collision, else the job id itself. See {@link AsyncJob.agentId}.
|
|
56
56
|
*/
|
|
57
57
|
agentUrlId?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Whether this job's turn published `<agentUrlId>.md`. False where the job
|
|
60
|
+
* body reported that it wrote none, so `agent://` still holds an earlier
|
|
61
|
+
* turn's output; undefined where the body does not report it.
|
|
62
|
+
*/
|
|
63
|
+
agentOutputPublished?: boolean;
|
|
58
64
|
}
|
|
59
65
|
/** Outcome of cancelling one background job. */
|
|
60
66
|
export type CancelStatus = "cancelled" | "not_found" | "already_completed";
|
package/dist/types/tui.d.ts
CHANGED
|
@@ -363,6 +363,8 @@ export declare class TUI extends Container {
|
|
|
363
363
|
* observers (live stream publisher, session recorder) coexist.
|
|
364
364
|
*/
|
|
365
365
|
addPaintListener(listener: PaintListener): () => void;
|
|
366
|
+
/** Observe actual ANSI frame writes, including transient resize paints; returns the unsubscribe. */
|
|
367
|
+
addFrameWriteListener(listener: (data: string) => void): () => void;
|
|
366
368
|
/** Install the product-owned bounded frame provider. */
|
|
367
369
|
setFrameProvider(provider: TerminalFrameProvider | undefined): void;
|
|
368
370
|
get fullRedraws(): number;
|
|
@@ -505,7 +507,10 @@ export declare class TUI extends Container {
|
|
|
505
507
|
* never come.
|
|
506
508
|
*/
|
|
507
509
|
addStartListener(listener: StartListener): () => void;
|
|
508
|
-
|
|
510
|
+
/** Registers a key listener; `prepend` gives a foreground input owner first refusal over global shortcuts. */
|
|
511
|
+
addInputListener(listener: InputListener, options?: {
|
|
512
|
+
prepend?: boolean;
|
|
513
|
+
}): () => void;
|
|
509
514
|
removeInputListener(listener: InputListener): void;
|
|
510
515
|
stop(): void;
|
|
511
516
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-tui",
|
|
4
|
-
"version": "18.13.
|
|
4
|
+
"version": "18.13.3",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": {
|
|
@@ -41,20 +41,20 @@
|
|
|
41
41
|
"gen:glyphs": "bun scripts/gen-glyphs.ts"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@oh-my-pi/omptype": "18.13.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "18.13.
|
|
46
|
-
"@oh-my-pi/pi-ai": "18.13.
|
|
47
|
-
"@oh-my-pi/pi-catalog": "18.13.
|
|
48
|
-
"@oh-my-pi/pi-natives": "18.13.
|
|
49
|
-
"@oh-my-pi/pi-utils": "18.13.
|
|
50
|
-
"@oh-my-pi/pi-wire": "18.13.
|
|
51
|
-
"@oh-my-pi/snapcompact": "18.13.
|
|
44
|
+
"@oh-my-pi/omptype": "18.13.3",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "18.13.3",
|
|
46
|
+
"@oh-my-pi/pi-ai": "18.13.3",
|
|
47
|
+
"@oh-my-pi/pi-catalog": "18.13.3",
|
|
48
|
+
"@oh-my-pi/pi-natives": "18.13.3",
|
|
49
|
+
"@oh-my-pi/pi-utils": "18.13.3",
|
|
50
|
+
"@oh-my-pi/pi-wire": "18.13.3",
|
|
51
|
+
"@oh-my-pi/snapcompact": "18.13.3"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"kitty-vt-wasm": "^0.2.0"
|
|
55
55
|
},
|
|
56
56
|
"engines": {
|
|
57
|
-
"bun": ">=1.
|
|
57
|
+
"bun": ">=1.4.4"
|
|
58
58
|
},
|
|
59
59
|
"files": [
|
|
60
60
|
"src",
|
|
@@ -11,6 +11,8 @@ export interface SessionPickerHost<T extends SessionSelectorEntry = SessionSelec
|
|
|
11
11
|
loadPinnedIds?(): Promise<ReadonlySet<string>>;
|
|
12
12
|
loadHistoryMatcher?(): SessionHistoryMatcher;
|
|
13
13
|
deleteSession?(session: T): Promise<boolean>;
|
|
14
|
+
/** Rename the session and resolve with the title as stored, or null when it did not happen. */
|
|
15
|
+
renameSession?(session: T, title: string): Promise<string | null>;
|
|
14
16
|
loadAllSessions?(): Promise<T[]>;
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -21,6 +23,8 @@ export interface SessionPickerOptions<T extends SessionSelectorEntry = SessionSe
|
|
|
21
23
|
scopeLabel?: string | false;
|
|
22
24
|
showCwd?: boolean;
|
|
23
25
|
allowDelete?: boolean;
|
|
26
|
+
/** Rename the selected session; resolve with the title as stored, or null when it did not happen. */
|
|
27
|
+
onRename?: (session: T, title: string) => Promise<string | null>;
|
|
24
28
|
allowGlobalScope?: boolean;
|
|
25
29
|
historySearch?: boolean;
|
|
26
30
|
pinnedIds?: ReadonlySet<string>;
|
|
@@ -63,6 +67,7 @@ export async function selectSession<T extends SessionSelectorEntry>(
|
|
|
63
67
|
},
|
|
64
68
|
{
|
|
65
69
|
onDelete: options.allowDelete === false ? undefined : host.deleteSession,
|
|
70
|
+
onRename: options.onRename ?? host.renameSession,
|
|
66
71
|
historyMatcher,
|
|
67
72
|
loadAllSessions: options.allowGlobalScope === false ? undefined : host.loadAllSessions,
|
|
68
73
|
allSessions: options.allSessions,
|
|
@@ -2,6 +2,7 @@ import { type Component } from "../tui";
|
|
|
2
2
|
import { Disclosure } from "../components/disclosure";
|
|
3
3
|
import { visibleWidth } from "../utils";
|
|
4
4
|
import type { AdvisorMessageDetails, AdvisorNote, AdvisorSeverity } from "./messages";
|
|
5
|
+
import { chatTranscriptDisplayPreferences } from "./display-preferences";
|
|
5
6
|
import { formatBadge, replaceTabs, type ToolUIColor, wrapTextWithAnsi } from "../render/render-utils";
|
|
6
7
|
import { Ellipsis, truncateToWidth } from "../render";
|
|
7
8
|
import type { Theme } from "../theme";
|
|
@@ -274,6 +275,7 @@ export function createAdvisorMessageCard(
|
|
|
274
275
|
body: () => new AdvisorNotes(notes, 0, uiTheme),
|
|
275
276
|
expanded: getExpanded(),
|
|
276
277
|
paddingX: 1,
|
|
278
|
+
maxCollapsedRows: chatTranscriptDisplayPreferences.advisorCollapsedLines || undefined,
|
|
277
279
|
});
|
|
278
280
|
// The tool-output toggle owns expansion state; synchronize the controlled
|
|
279
281
|
// disclosure from the callback on every render.
|
|
@@ -792,8 +792,9 @@ export class AssistantMessageComponent extends Container {
|
|
|
792
792
|
return this.#transcriptBlockFinalized;
|
|
793
793
|
}
|
|
794
794
|
|
|
795
|
-
/**
|
|
795
|
+
/** Hold image and figure rendering until the completed block can enter immutable scrollback. */
|
|
796
796
|
isTranscriptBlockPending(): boolean {
|
|
797
|
+
if (this.#showImages && this.#kittyConversionsAwaited.size > 0) return true;
|
|
797
798
|
for (const block of this.#figureBlocks.values()) if (block.pending) return true;
|
|
798
799
|
return false;
|
|
799
800
|
}
|
|
@@ -1615,6 +1616,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
1615
1616
|
})
|
|
1616
1617
|
.catch(() => {
|
|
1617
1618
|
this.#kittyConversionsAwaited.delete(key);
|
|
1619
|
+
this.#onImageUpdate?.();
|
|
1618
1620
|
});
|
|
1619
1621
|
}
|
|
1620
1622
|
}
|
|
@@ -16,6 +16,7 @@ import type { TruncationMeta } from "../tools/output-meta";
|
|
|
16
16
|
import { resolveImageOptions } from "../render/render-utils";
|
|
17
17
|
import { OutputPane } from "../render/output-pane";
|
|
18
18
|
import { loadXtermTerminal, readTerminalRows, styleTerminalRow } from "../tools/terminal-output";
|
|
19
|
+
import { normalizeInputForPty } from "../tools/bash-interactive";
|
|
19
20
|
import { getSixelLineMask, isSixelPassthroughEnabled, sanitizeWithOptionalSixelPassthrough } from "../render/sixel";
|
|
20
21
|
import {
|
|
21
22
|
buildExecutionFrame,
|
|
@@ -286,6 +287,11 @@ export class BashExecutionComponent extends Container {
|
|
|
286
287
|
this.#drainPtyQueue();
|
|
287
288
|
}
|
|
288
289
|
|
|
290
|
+
/** Bytes a terminal would send for a key event, honoring the command's cursor-key mode; "" for key releases. */
|
|
291
|
+
encodePtyInput(data: string): string {
|
|
292
|
+
return normalizeInputForPty(data, this.#ptyTerminal?.modes.applicationCursorKeysMode ?? false);
|
|
293
|
+
}
|
|
294
|
+
|
|
289
295
|
#drainPtyQueue(): void {
|
|
290
296
|
const terminal = this.#ptyTerminal;
|
|
291
297
|
if (!terminal || this.#ptyWriting) return;
|
|
@@ -6,6 +6,8 @@ export interface ChatTranscriptDisplayPreferences {
|
|
|
6
6
|
cacheMissMarker: boolean;
|
|
7
7
|
showTokenUsage: boolean;
|
|
8
8
|
showTurnTime: boolean;
|
|
9
|
+
/** Max rows a collapsed advisor card shows below its header; 0 = no cap. */
|
|
10
|
+
advisorCollapsedLines: number;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
/** Current transcript display preferences. */
|
|
@@ -16,6 +18,7 @@ export const chatTranscriptDisplayPreferences: ChatTranscriptDisplayPreferences
|
|
|
16
18
|
cacheMissMarker: false,
|
|
17
19
|
showTokenUsage: false,
|
|
18
20
|
showTurnTime: false,
|
|
21
|
+
advisorCollapsedLines: 0,
|
|
19
22
|
};
|
|
20
23
|
|
|
21
24
|
/** Apply host display preferences without pulling settings into the renderer. */
|
|
@@ -711,14 +711,15 @@ export class ToolExecutionComponent extends Container {
|
|
|
711
711
|
// Convert async - catch errors from processing
|
|
712
712
|
convertImageToPngShared(image)
|
|
713
713
|
.then(converted => {
|
|
714
|
+
this.#kittyConversionsAwaited.delete(key);
|
|
714
715
|
this.#kittyConverted.set(key, converted);
|
|
715
716
|
this.#displayInputVersion++;
|
|
716
717
|
this.#updateDisplay();
|
|
717
718
|
this.#ui.requestRender();
|
|
718
719
|
})
|
|
719
720
|
.catch(() => {
|
|
720
|
-
// Ignore conversion failures - display will use original image format
|
|
721
721
|
this.#kittyConversionsAwaited.delete(key);
|
|
722
|
+
this.#ui.requestRender();
|
|
722
723
|
});
|
|
723
724
|
}
|
|
724
725
|
}
|
|
@@ -859,9 +860,12 @@ export class ToolExecutionComponent extends Container {
|
|
|
859
860
|
}
|
|
860
861
|
}
|
|
861
862
|
|
|
862
|
-
/**
|
|
863
|
+
/** Hold visible results until their figures and Kitty images are ready for immutable scrollback. */
|
|
863
864
|
isTranscriptBlockPending(): boolean {
|
|
864
|
-
return
|
|
865
|
+
return (
|
|
866
|
+
this.#toolActivityVisible &&
|
|
867
|
+
((this.#showImages && this.#kittyConversionsAwaited.size > 0) || this.#figure?.pending === true)
|
|
868
|
+
);
|
|
865
869
|
}
|
|
866
870
|
|
|
867
871
|
/**
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** Pure occupancy calculations for contiguous token segments, independent of themes and renderers. */
|
|
2
|
+
export interface ContextMapSegment<T> {
|
|
3
|
+
readonly id: T;
|
|
4
|
+
readonly tokens: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface ContextMapCell<T> {
|
|
8
|
+
readonly id: T;
|
|
9
|
+
readonly full: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Lays `segments` out in order over a `columns` × `rows` grid representing `windowTokens`; each cell takes the
|
|
14
|
+
* segment covering most of it and is `full` at 70% coverage or more. Returns `[]` when the window or grid
|
|
15
|
+
* size is unusable, and omits cells no segment touches.
|
|
16
|
+
*/
|
|
17
|
+
export function planContextMap<T>(
|
|
18
|
+
segments: readonly ContextMapSegment<T>[],
|
|
19
|
+
windowTokens: number,
|
|
20
|
+
columns: number,
|
|
21
|
+
rows: number,
|
|
22
|
+
): ContextMapCell<T>[] {
|
|
23
|
+
if (
|
|
24
|
+
!Number.isFinite(windowTokens) ||
|
|
25
|
+
windowTokens <= 0 ||
|
|
26
|
+
!Number.isInteger(columns) ||
|
|
27
|
+
!Number.isInteger(rows) ||
|
|
28
|
+
columns <= 0 ||
|
|
29
|
+
rows <= 0
|
|
30
|
+
)
|
|
31
|
+
return [];
|
|
32
|
+
const cellCount = columns * rows;
|
|
33
|
+
const tokensPerCell = windowTokens / cellCount;
|
|
34
|
+
const ranges: { id: T; start: number; end: number }[] = [];
|
|
35
|
+
let cursor = 0;
|
|
36
|
+
for (const segment of segments) {
|
|
37
|
+
if (!Number.isFinite(segment.tokens) || segment.tokens <= 0) continue;
|
|
38
|
+
const end = Math.min(windowTokens, cursor + segment.tokens);
|
|
39
|
+
if (end > cursor) ranges.push({ id: segment.id, start: cursor, end });
|
|
40
|
+
cursor = end;
|
|
41
|
+
if (cursor >= windowTokens) break;
|
|
42
|
+
}
|
|
43
|
+
const cells: ContextMapCell<T>[] = [];
|
|
44
|
+
let first = 0;
|
|
45
|
+
for (let index = 0; index < cellCount; index++) {
|
|
46
|
+
const start = index * tokensPerCell;
|
|
47
|
+
const end = (index + 1) * tokensPerCell;
|
|
48
|
+
while (first < ranges.length && ranges[first]!.end <= start) first++;
|
|
49
|
+
let winner: T | undefined;
|
|
50
|
+
let overlap = 0;
|
|
51
|
+
for (let part = first; part < ranges.length && ranges[part]!.start < end; part++) {
|
|
52
|
+
const range = ranges[part]!;
|
|
53
|
+
const covered = Math.min(end, range.end) - Math.max(start, range.start);
|
|
54
|
+
if (covered > overlap) {
|
|
55
|
+
overlap = covered;
|
|
56
|
+
winner = range.id;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (winner !== undefined) cells.push({ id: winner, full: overlap / tokensPerCell >= 0.7 });
|
|
60
|
+
}
|
|
61
|
+
return cells;
|
|
62
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ThemeColor } from "../theme";
|
|
2
|
+
import type { SymbolKey } from "../theme/symbols";
|
|
3
|
+
import type { ContextCategoryId } from "./context-view-model";
|
|
4
|
+
|
|
5
|
+
/** Shared category styles for maps and interactive legends; resolve glyphs at render time. */
|
|
6
|
+
const SYSTEM = { color: "accent", symbol: "context.content" } as const;
|
|
7
|
+
const DETAILS = { color: "customMessageLabel", symbol: "context.content" } as const;
|
|
8
|
+
const TOOLS = { color: "warning", symbol: "context.content" } as const;
|
|
9
|
+
const SKILLS = { color: "success", symbol: "context.content" } as const;
|
|
10
|
+
const MESSAGES = { color: "userMessageText", symbol: "context.messages" } as const;
|
|
11
|
+
const OUTPUT = { color: "muted", symbol: "context.messages" } as const;
|
|
12
|
+
export const CONTEXT_PRESENTATION = {
|
|
13
|
+
"system-prompt": SYSTEM,
|
|
14
|
+
skills: SKILLS,
|
|
15
|
+
"built-in-tools": TOOLS,
|
|
16
|
+
"custom-tools": DETAILS,
|
|
17
|
+
// Shares the System Prompt color; the message glyph tells them apart in the map.
|
|
18
|
+
"user-messages": { color: "accent", symbol: "context.messages" },
|
|
19
|
+
"assistant-messages": MESSAGES,
|
|
20
|
+
"assistant-thinking": DETAILS,
|
|
21
|
+
"tool-calls": TOOLS,
|
|
22
|
+
"tool-output": OUTPUT,
|
|
23
|
+
buffer: { color: "warning", symbol: "context.buffer" },
|
|
24
|
+
free: { color: "dim", symbol: "context.free" },
|
|
25
|
+
} as const satisfies Record<ContextPresentationId, { color: ThemeColor; symbol: SymbolKey }>;
|
|
26
|
+
export type ContextPresentationId = ContextCategoryId | "buffer" | "free";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Context snapshot shapes shared by the session snapshot builder and the context viewer. */
|
|
2
|
+
import type { ContextBreakdown } from "../status-line/context-usage";
|
|
3
|
+
|
|
4
|
+
interface ContextCategoryDescriptor {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly label: string;
|
|
7
|
+
/** Overview row label when the Overview weight differs from the category's content. */
|
|
8
|
+
readonly overviewLabel?: string;
|
|
9
|
+
/** Listed with its weight but never opened. */
|
|
10
|
+
readonly openable?: false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Context categories in display order. */
|
|
14
|
+
export const CONTEXT_CATEGORIES = [
|
|
15
|
+
// The Overview weight excludes the Skills listing; content views keep complete system blocks.
|
|
16
|
+
{ id: "system-prompt", label: "System Prompt", overviewLabel: "System Prompt (excluding Skills)" },
|
|
17
|
+
// An estimate of the prompt's skill listing, without entries of its own.
|
|
18
|
+
{ id: "skills", label: "Skills", openable: false },
|
|
19
|
+
{ id: "built-in-tools", label: "Built-in Tools" },
|
|
20
|
+
{ id: "custom-tools", label: "Custom Tools" },
|
|
21
|
+
{ id: "user-messages", label: "User / Developer Messages" },
|
|
22
|
+
{ id: "assistant-messages", label: "Assistant Messages" },
|
|
23
|
+
{ id: "assistant-thinking", label: "Assistant Thinking" },
|
|
24
|
+
{ id: "tool-calls", label: "Tool Calls" },
|
|
25
|
+
{ id: "tool-output", label: "Tool Output" },
|
|
26
|
+
] as const satisfies readonly ContextCategoryDescriptor[];
|
|
27
|
+
|
|
28
|
+
export type ContextCategoryId = (typeof CONTEXT_CATEGORIES)[number]["id"];
|
|
29
|
+
|
|
30
|
+
const DESCRIPTORS_BY_ID = new Map<ContextCategoryId, ContextCategoryDescriptor>(
|
|
31
|
+
CONTEXT_CATEGORIES.map(descriptor => [descriptor.id, descriptor]),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
/** Descriptor of a category id: its label, optional Overview label, and whether it can be opened. */
|
|
35
|
+
export function contextCategoryDescriptor(id: ContextCategoryId): ContextCategoryDescriptor {
|
|
36
|
+
return DESCRIPTORS_BY_ID.get(id)!;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ContextThinkingGroup {
|
|
40
|
+
readonly id: string;
|
|
41
|
+
readonly label: string;
|
|
42
|
+
readonly tokens: number;
|
|
43
|
+
readonly interrupted?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ContextViewEntry {
|
|
47
|
+
readonly id: string;
|
|
48
|
+
readonly label: string;
|
|
49
|
+
readonly text: string;
|
|
50
|
+
/** Aggregation weight for the unscaled replay estimate; shared blocks contribute through their group. */
|
|
51
|
+
readonly tokens: number;
|
|
52
|
+
readonly interrupted?: boolean;
|
|
53
|
+
readonly thinkingGroup?: ContextThinkingGroup;
|
|
54
|
+
readonly path?: string;
|
|
55
|
+
readonly info?: string;
|
|
56
|
+
readonly timestamp?: number;
|
|
57
|
+
readonly callId?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ContextViewCategory {
|
|
61
|
+
id: ContextCategoryId;
|
|
62
|
+
key?: string;
|
|
63
|
+
label: string;
|
|
64
|
+
/** Overview attribution weight, not reported usage; may differ from the entry sum (e.g. Skills subtraction). */
|
|
65
|
+
tokens: number;
|
|
66
|
+
entries: ContextViewEntry[];
|
|
67
|
+
children?: ContextViewCategory[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ContextViewData {
|
|
71
|
+
readonly breakdown: ContextBreakdown;
|
|
72
|
+
readonly categories: readonly ContextViewCategory[];
|
|
73
|
+
}
|