@oh-my-pi/pi-coding-agent 15.5.15 → 15.6.0
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 +46 -0
- package/dist/types/cli/classify-install-target.d.ts +0 -10
- package/dist/types/cli/initial-message.d.ts +1 -1
- package/dist/types/cli/tiny-models-cli.d.ts +9 -0
- package/dist/types/commands/tiny-models.d.ts +22 -0
- package/dist/types/commit/analysis/conventional.d.ts +1 -1
- package/dist/types/commit/analysis/summary.d.ts +1 -1
- package/dist/types/commit/changelog/generate.d.ts +1 -1
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
- package/dist/types/config/model-id-affixes.d.ts +10 -0
- package/dist/types/config/settings-schema.d.ts +232 -7
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
- package/dist/types/eval/js/shared/rewrite-imports.d.ts +16 -1
- package/dist/types/internal-urls/agent-protocol.d.ts +2 -1
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -1
- package/dist/types/internal-urls/local-protocol.d.ts +2 -1
- package/dist/types/internal-urls/memory-protocol.d.ts +2 -1
- package/dist/types/internal-urls/omp-protocol.d.ts +2 -1
- package/dist/types/internal-urls/router.d.ts +8 -1
- package/dist/types/internal-urls/rule-protocol.d.ts +2 -1
- package/dist/types/internal-urls/skill-protocol.d.ts +2 -1
- package/dist/types/internal-urls/types.d.ts +26 -0
- package/dist/types/memory-backend/index.d.ts +1 -0
- package/dist/types/memory-backend/resolve.d.ts +2 -1
- package/dist/types/memory-backend/types.d.ts +7 -1
- package/dist/types/mnemosyne/backend.d.ts +4 -0
- package/dist/types/mnemosyne/config.d.ts +29 -0
- package/dist/types/mnemosyne/index.d.ts +3 -0
- package/dist/types/mnemosyne/state.d.ts +72 -0
- package/dist/types/modes/components/custom-editor.d.ts +2 -3
- package/dist/types/modes/components/hook-selector.d.ts +27 -0
- package/dist/types/modes/components/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/context-thresholds.d.ts +6 -0
- package/dist/types/modes/components/tiny-title-download-progress.d.ts +11 -0
- package/dist/types/modes/components/welcome.d.ts +1 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +4 -1
- package/dist/types/modes/gradient-highlight.d.ts +23 -0
- package/dist/types/modes/interactive-mode.d.ts +4 -2
- package/dist/types/modes/internal-url-autocomplete.d.ts +43 -0
- package/dist/types/modes/orchestrate.d.ts +10 -0
- package/dist/types/modes/theme/defaults/index.d.ts +8406 -8406
- package/dist/types/modes/ultrathink.d.ts +3 -3
- package/dist/types/modes/utils/keybinding-matchers.d.ts +5 -0
- package/dist/types/sdk.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +33 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/task/render.d.ts +5 -1
- package/dist/types/tiny/models.d.ts +185 -0
- package/dist/types/tiny/text.d.ts +4 -0
- package/dist/types/tiny/title-client.d.ts +24 -0
- package/dist/types/tiny/title-protocol.d.ts +74 -0
- package/dist/types/tiny/worker.d.ts +2 -0
- package/dist/types/tools/bash.d.ts +3 -1
- package/dist/types/tools/index.d.ts +7 -3
- package/dist/types/tools/memory-edit.d.ts +40 -0
- package/dist/types/tools/{hindsight-recall.d.ts → memory-recall.d.ts} +6 -6
- package/dist/types/tools/{hindsight-reflect.d.ts → memory-reflect.d.ts} +6 -6
- package/dist/types/tools/memory-render.d.ts +60 -0
- package/dist/types/tools/{hindsight-retain.d.ts → memory-retain.d.ts} +6 -6
- package/dist/types/tools/todo-write.d.ts +8 -0
- package/dist/types/tools/tool-result.d.ts +2 -0
- package/dist/types/utils/title-generator.d.ts +3 -0
- package/package.json +18 -14
- package/scripts/build-binary.ts +1 -0
- package/src/cli/tiny-models-cli.ts +127 -0
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +8 -8
- package/src/commands/tiny-models.ts +36 -0
- package/src/config/model-equivalence.ts +43 -2
- package/src/config/model-id-affixes.ts +64 -0
- package/src/config/model-registry.ts +84 -10
- package/src/config/settings-schema.ts +205 -4
- package/src/edit/hashline/diff.ts +5 -7
- package/src/eval/__tests__/shared-executors.test.ts +36 -0
- package/src/eval/js/shared/local-module-loader.ts +13 -1
- package/src/eval/js/shared/rewrite-imports.ts +31 -26
- package/src/internal-urls/agent-protocol.ts +18 -1
- package/src/internal-urls/artifact-protocol.ts +19 -1
- package/src/internal-urls/docs-index.generated.ts +3 -1
- package/src/internal-urls/local-protocol.ts +14 -1
- package/src/internal-urls/memory-protocol.ts +6 -1
- package/src/internal-urls/omp-protocol.ts +5 -1
- package/src/internal-urls/router.ts +20 -1
- package/src/internal-urls/rule-protocol.ts +8 -1
- package/src/internal-urls/skill-protocol.ts +8 -1
- package/src/internal-urls/types.ts +27 -0
- package/src/lsp/render.ts +1 -1
- package/src/mcp/oauth-flow.ts +2 -2
- package/src/memory-backend/index.ts +1 -0
- package/src/memory-backend/resolve.ts +4 -1
- package/src/memory-backend/types.ts +8 -1
- package/src/mnemosyne/backend.ts +374 -0
- package/src/mnemosyne/config.ts +160 -0
- package/src/mnemosyne/index.ts +3 -0
- package/src/mnemosyne/state.ts +548 -0
- package/src/modes/acp/acp-agent.ts +11 -6
- package/src/modes/components/agent-dashboard.ts +4 -4
- package/src/modes/components/custom-editor.ts +3 -2
- package/src/modes/components/diff.ts +2 -2
- package/src/modes/components/extensions/extension-list.ts +3 -2
- package/src/modes/components/footer.ts +5 -6
- package/src/modes/components/history-search.ts +3 -3
- package/src/modes/components/hook-selector.ts +94 -8
- package/src/modes/components/index.ts +1 -0
- package/src/modes/components/mcp-add-wizard.ts +3 -3
- package/src/modes/components/model-selector.ts +5 -4
- package/src/modes/components/oauth-selector.ts +3 -3
- package/src/modes/components/session-observer-overlay.ts +19 -13
- package/src/modes/components/session-selector.ts +3 -3
- package/src/modes/components/settings-defs.ts +7 -0
- package/src/modes/components/status-line/context-thresholds.ts +11 -0
- package/src/modes/components/status-line/segments.ts +2 -2
- package/src/modes/components/tiny-title-download-progress.ts +90 -0
- package/src/modes/components/tips.txt +12 -0
- package/src/modes/components/tool-execution.ts +67 -3
- package/src/modes/components/tree-selector.ts +3 -3
- package/src/modes/components/user-message-selector.ts +3 -3
- package/src/modes/components/welcome.ts +55 -1
- package/src/modes/controllers/command-controller.ts +16 -1
- package/src/modes/controllers/extension-ui-controller.ts +3 -1
- package/src/modes/controllers/input-controller.ts +57 -0
- package/src/modes/gradient-highlight.ts +70 -0
- package/src/modes/interactive-mode.ts +58 -109
- package/src/modes/internal-url-autocomplete.ts +143 -0
- package/src/modes/orchestrate.ts +36 -0
- package/src/modes/prompt-action-autocomplete.ts +12 -0
- package/src/modes/ultrathink.ts +9 -53
- package/src/modes/utils/keybinding-matchers.ts +11 -0
- package/src/prompts/system/memory-consolidation-system.md +8 -0
- package/src/prompts/system/memory-extraction-system.md +26 -0
- package/src/prompts/{commands/orchestrate.md → system/orchestrate-notice.md} +5 -16
- package/src/prompts/system/system-prompt.md +2 -0
- package/src/prompts/system/tiny-title-system.md +8 -0
- package/src/prompts/tools/memory-edit.md +8 -0
- package/src/prompts/tools/task.md +4 -7
- package/src/sdk.ts +8 -6
- package/src/session/agent-session.ts +128 -44
- package/src/slash-commands/builtin-registry.ts +10 -1
- package/src/system-prompt.ts +4 -0
- package/src/task/commands.ts +1 -5
- package/src/task/executor.ts +8 -0
- package/src/task/index.ts +2 -0
- package/src/task/render.ts +69 -26
- package/src/tiny/models.ts +217 -0
- package/src/tiny/text.ts +19 -0
- package/src/tiny/title-client.ts +340 -0
- package/src/tiny/title-protocol.ts +51 -0
- package/src/tiny/worker.ts +523 -0
- package/src/tools/bash.ts +58 -16
- package/src/tools/browser/tab-worker.ts +1 -1
- package/src/tools/index.ts +17 -11
- package/src/tools/memory-edit.ts +59 -0
- package/src/tools/memory-recall.ts +100 -0
- package/src/tools/memory-reflect.ts +88 -0
- package/src/tools/memory-render.ts +185 -0
- package/src/tools/memory-retain.ts +91 -0
- package/src/tools/renderers.ts +4 -0
- package/src/tools/todo-write.ts +128 -29
- package/src/tools/tool-result.ts +8 -0
- package/src/utils/title-generator.ts +115 -13
- package/src/tools/hindsight-recall.ts +0 -69
- package/src/tools/hindsight-reflect.ts +0 -58
- package/src/tools/hindsight-retain.ts +0 -57
|
@@ -7,7 +7,7 @@ import type { AgentSession } from "../../session/agent-session";
|
|
|
7
7
|
import { shortenPath } from "../../tools/render-utils";
|
|
8
8
|
import * as git from "../../utils/git";
|
|
9
9
|
import { sanitizeStatusText } from "../shared";
|
|
10
|
-
import { getContextUsageLevel, getContextUsageThemeColor } from "./status-line/context-thresholds";
|
|
10
|
+
import { formatContextUsage, getContextUsageLevel, getContextUsageThemeColor } from "./status-line/context-thresholds";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Footer component that shows pwd, token stats, and context usage
|
|
@@ -136,7 +136,6 @@ export class FooterComponent implements Component {
|
|
|
136
136
|
const contextUsage = this.session.getContextUsage();
|
|
137
137
|
const contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;
|
|
138
138
|
const contextPercentValue = contextUsage?.percent ?? 0;
|
|
139
|
-
const contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : "?";
|
|
140
139
|
|
|
141
140
|
// Replace home directory with ~
|
|
142
141
|
let pwd = shortenPath(getProjectDir());
|
|
@@ -180,10 +179,10 @@ export class FooterComponent implements Component {
|
|
|
180
179
|
// Colorize context percentage based on usage
|
|
181
180
|
let contextPercentStr: string;
|
|
182
181
|
const autoIndicator = this.#autoCompactEnabled ? " (auto)" : "";
|
|
183
|
-
const contextPercentDisplay =
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
182
|
+
const contextPercentDisplay = `${formatContextUsage(
|
|
183
|
+
contextUsage?.percent === null ? null : contextPercentValue,
|
|
184
|
+
contextWindow,
|
|
185
|
+
)}${autoIndicator}`;
|
|
187
186
|
if (contextUsage?.percent !== null && contextUsage?.percent !== undefined) {
|
|
188
187
|
const color = getContextUsageThemeColor(getContextUsageLevel(contextPercentValue, contextWindow));
|
|
189
188
|
contextPercentStr =
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
visibleWidth,
|
|
12
12
|
} from "@oh-my-pi/pi-tui";
|
|
13
13
|
import { theme } from "../../modes/theme/theme";
|
|
14
|
-
import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
|
|
14
|
+
import { matchesAppInterrupt, matchesSelectDown, matchesSelectUp } from "../../modes/utils/keybinding-matchers";
|
|
15
15
|
import type { HistoryEntry, HistoryStorage } from "../../session/history-storage";
|
|
16
16
|
import { DynamicBorder } from "./dynamic-border";
|
|
17
17
|
|
|
@@ -116,14 +116,14 @@ export class HistorySearchComponent extends Container {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
handleInput(keyData: string): void {
|
|
119
|
-
if (
|
|
119
|
+
if (matchesSelectUp(keyData)) {
|
|
120
120
|
if (this.#results.length === 0) return;
|
|
121
121
|
this.#selectedIndex = Math.max(0, this.#selectedIndex - 1);
|
|
122
122
|
this.#resultsList.setSelectedIndex(this.#selectedIndex);
|
|
123
123
|
return;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
if (
|
|
126
|
+
if (matchesSelectDown(keyData)) {
|
|
127
127
|
if (this.#results.length === 0) return;
|
|
128
128
|
this.#selectedIndex = Math.min(this.#results.length - 1, this.#selectedIndex + 1);
|
|
129
129
|
this.#resultsList.setSelectedIndex(this.#selectedIndex);
|
|
@@ -15,11 +15,43 @@ import {
|
|
|
15
15
|
truncateToWidth,
|
|
16
16
|
visibleWidth,
|
|
17
17
|
} from "@oh-my-pi/pi-tui";
|
|
18
|
-
import { getMarkdownTheme, theme } from "../../modes/theme/theme";
|
|
19
|
-
import {
|
|
18
|
+
import { getMarkdownTheme, type ThemeColor, theme } from "../../modes/theme/theme";
|
|
19
|
+
import {
|
|
20
|
+
matchesAppExternalEditor,
|
|
21
|
+
matchesSelectCancel,
|
|
22
|
+
matchesSelectDown,
|
|
23
|
+
matchesSelectUp,
|
|
24
|
+
} from "../../modes/utils/keybinding-matchers";
|
|
20
25
|
import { CountdownTimer } from "./countdown-timer";
|
|
21
26
|
import { DynamicBorder } from "./dynamic-border";
|
|
22
27
|
|
|
28
|
+
/** One segment of a {@link HookSelectorSlider} — a label, its accent color, and
|
|
29
|
+
* an optional detail line (e.g. the resolved model name) shown beneath the
|
|
30
|
+
* track while the segment is active. */
|
|
31
|
+
export interface HookSelectorSliderSegment {
|
|
32
|
+
label: string;
|
|
33
|
+
/** Theme color for the segment label; defaults to `accent`. */
|
|
34
|
+
color?: ThemeColor;
|
|
35
|
+
/** Secondary line rendered under the track when this segment is selected. */
|
|
36
|
+
detail?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A horizontal left/right selector rendered above the option list. Unlike the
|
|
41
|
+
* up/down option cursor, the slider is moved with the left/right arrows from
|
|
42
|
+
* any list position, letting the caller capture an orthogonal choice (e.g. the
|
|
43
|
+
* model tier to continue execution with) alongside the selected option.
|
|
44
|
+
*/
|
|
45
|
+
export interface HookSelectorSlider {
|
|
46
|
+
/** Dim caption rendered before the slider track (e.g. "continue with"). */
|
|
47
|
+
caption?: string;
|
|
48
|
+
segments: HookSelectorSliderSegment[];
|
|
49
|
+
/** Initially highlighted segment index. */
|
|
50
|
+
index: number;
|
|
51
|
+
/** Invoked with the new index whenever the slider moves. */
|
|
52
|
+
onChange?: (index: number) => void;
|
|
53
|
+
}
|
|
54
|
+
|
|
23
55
|
export interface HookSelectorOptions {
|
|
24
56
|
tui?: TUI;
|
|
25
57
|
timeout?: number;
|
|
@@ -31,6 +63,7 @@ export interface HookSelectorOptions {
|
|
|
31
63
|
onRight?: () => void;
|
|
32
64
|
onExternalEditor?: () => void;
|
|
33
65
|
helpText?: string;
|
|
66
|
+
slider?: HookSelectorSlider;
|
|
34
67
|
}
|
|
35
68
|
|
|
36
69
|
class OutlinedList extends Container {
|
|
@@ -69,6 +102,9 @@ export class HookSelectorComponent extends Container {
|
|
|
69
102
|
#onLeftCallback: (() => void) | undefined;
|
|
70
103
|
#onRightCallback: (() => void) | undefined;
|
|
71
104
|
#onExternalEditorCallback: (() => void) | undefined;
|
|
105
|
+
#slider: HookSelectorSlider | undefined;
|
|
106
|
+
#sliderIndex: number = 0;
|
|
107
|
+
#sliderComponent: Text | undefined;
|
|
72
108
|
constructor(
|
|
73
109
|
title: string,
|
|
74
110
|
options: string[],
|
|
@@ -87,6 +123,10 @@ export class HookSelectorComponent extends Container {
|
|
|
87
123
|
this.#onLeftCallback = opts?.onLeft;
|
|
88
124
|
this.#onRightCallback = opts?.onRight;
|
|
89
125
|
this.#onExternalEditorCallback = opts?.onExternalEditor;
|
|
126
|
+
if (opts?.slider && opts.slider.segments.length > 0) {
|
|
127
|
+
this.#slider = opts.slider;
|
|
128
|
+
this.#sliderIndex = Math.max(0, Math.min(opts.slider.index, opts.slider.segments.length - 1));
|
|
129
|
+
}
|
|
90
130
|
|
|
91
131
|
this.addChild(new DynamicBorder());
|
|
92
132
|
this.addChild(new Spacer(1));
|
|
@@ -95,6 +135,12 @@ export class HookSelectorComponent extends Container {
|
|
|
95
135
|
this.addChild(this.#titleComponent);
|
|
96
136
|
this.addChild(new Spacer(1));
|
|
97
137
|
|
|
138
|
+
if (this.#slider) {
|
|
139
|
+
this.#sliderComponent = new Text(this.#renderSliderLine(), 1, 0);
|
|
140
|
+
this.addChild(this.#sliderComponent);
|
|
141
|
+
this.addChild(new Spacer(1));
|
|
142
|
+
}
|
|
143
|
+
|
|
98
144
|
if (opts?.timeout && opts.timeout > 0 && opts.tui) {
|
|
99
145
|
this.#countdown = new CountdownTimer(
|
|
100
146
|
opts.timeout,
|
|
@@ -160,23 +206,63 @@ export class HookSelectorComponent extends Container {
|
|
|
160
206
|
}
|
|
161
207
|
}
|
|
162
208
|
|
|
209
|
+
/** Render the slider block: the track (dim caption, edge arrows that brighten
|
|
210
|
+
* while there is room to move, one styled segment per option — active = bold
|
|
211
|
+
* in its color, the rest dim, joined by `›`) plus, when the active segment
|
|
212
|
+
* carries a `detail`, a muted second line beneath it (e.g. the resolved model
|
|
213
|
+
* name). Returns one or two `\n`-joined lines. */
|
|
214
|
+
#renderSliderLine(): string {
|
|
215
|
+
const slider = this.#slider;
|
|
216
|
+
if (!slider) return "";
|
|
217
|
+
const segments = slider.segments;
|
|
218
|
+
const sep = theme.fg("dim", " › ");
|
|
219
|
+
const track = segments
|
|
220
|
+
.map((segment, i) =>
|
|
221
|
+
i === this.#sliderIndex
|
|
222
|
+
? theme.bold(theme.fg(segment.color ?? "accent", segment.label))
|
|
223
|
+
: theme.fg("dim", segment.label),
|
|
224
|
+
)
|
|
225
|
+
.join(sep);
|
|
226
|
+
const leftArrow = theme.fg(this.#sliderIndex > 0 ? "accent" : "dim", "◂");
|
|
227
|
+
const rightArrow = theme.fg(this.#sliderIndex < segments.length - 1 ? "accent" : "dim", "▸");
|
|
228
|
+
const caption = slider.caption ? `${theme.fg("dim", slider.caption)} ` : "";
|
|
229
|
+
const trackLine = `${caption}${leftArrow} ${theme.fg("dim", "[")} ${track} ${theme.fg("dim", "]")} ${rightArrow}`;
|
|
230
|
+
const detail = segments[this.#sliderIndex]?.detail;
|
|
231
|
+
if (!detail) return trackLine;
|
|
232
|
+
return `${trackLine}\n ${theme.fg("dim", "↳")} ${theme.fg("muted", detail)}`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Move the slider by `delta`, clamped to the segment range, refresh the
|
|
236
|
+
* rendered track, and notify the caller only when the index actually moves. */
|
|
237
|
+
#moveSlider(delta: number): void {
|
|
238
|
+
const slider = this.#slider;
|
|
239
|
+
if (!slider) return;
|
|
240
|
+
const next = Math.max(0, Math.min(slider.segments.length - 1, this.#sliderIndex + delta));
|
|
241
|
+
if (next === this.#sliderIndex) return;
|
|
242
|
+
this.#sliderIndex = next;
|
|
243
|
+
this.#sliderComponent?.setText(this.#renderSliderLine());
|
|
244
|
+
slider.onChange?.(next);
|
|
245
|
+
}
|
|
246
|
+
|
|
163
247
|
handleInput(keyData: string): void {
|
|
164
248
|
// Reset countdown on any interaction
|
|
165
249
|
this.#countdown?.reset();
|
|
166
250
|
|
|
167
|
-
if (
|
|
251
|
+
if (matchesSelectUp(keyData) || keyData === "k") {
|
|
168
252
|
this.#selectedIndex = Math.max(0, this.#selectedIndex - 1);
|
|
169
253
|
this.#updateList();
|
|
170
|
-
} else if (
|
|
254
|
+
} else if (matchesSelectDown(keyData) || keyData === "j") {
|
|
171
255
|
this.#selectedIndex = Math.min(this.#options.length - 1, this.#selectedIndex + 1);
|
|
172
256
|
this.#updateList();
|
|
173
257
|
} else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
174
258
|
const selected = this.#options[this.#selectedIndex];
|
|
175
259
|
if (selected) this.#onSelectCallback(selected);
|
|
176
|
-
} else if (matchesKey(keyData, "left")) {
|
|
177
|
-
this.#
|
|
178
|
-
|
|
179
|
-
|
|
260
|
+
} else if (matchesKey(keyData, "left") || (this.#slider && keyData === "h")) {
|
|
261
|
+
if (this.#slider) this.#moveSlider(-1);
|
|
262
|
+
else this.#onLeftCallback?.();
|
|
263
|
+
} else if (matchesKey(keyData, "right") || (this.#slider && keyData === "l")) {
|
|
264
|
+
if (this.#slider) this.#moveSlider(1);
|
|
265
|
+
else this.#onRightCallback?.();
|
|
180
266
|
} else if (this.#onExternalEditorCallback && matchesAppExternalEditor(keyData)) {
|
|
181
267
|
this.#onExternalEditorCallback();
|
|
182
268
|
} else if (matchesSelectCancel(keyData)) {
|
|
@@ -26,6 +26,7 @@ export * from "./show-images-selector";
|
|
|
26
26
|
export * from "./status-line";
|
|
27
27
|
export * from "./theme-selector";
|
|
28
28
|
export * from "./thinking-selector";
|
|
29
|
+
export * from "./tiny-title-download-progress";
|
|
29
30
|
export * from "./todo-reminder";
|
|
30
31
|
export * from "./tool-execution";
|
|
31
32
|
export * from "./tree-selector";
|
|
@@ -19,7 +19,7 @@ import { analyzeAuthError, discoverOAuthEndpoints } from "../../mcp/oauth-discov
|
|
|
19
19
|
import type { MCPHttpServerConfig, MCPServerConfig, MCPSseServerConfig, MCPStdioServerConfig } from "../../mcp/types";
|
|
20
20
|
import { shortenPath } from "../../tools/render-utils";
|
|
21
21
|
import { theme } from "../theme/theme";
|
|
22
|
-
import { matchesAppInterrupt } from "../utils/keybinding-matchers";
|
|
22
|
+
import { matchesAppInterrupt, matchesSelectDown, matchesSelectUp } from "../utils/keybinding-matchers";
|
|
23
23
|
import { DynamicBorder } from "./dynamic-border";
|
|
24
24
|
|
|
25
25
|
type TransportType = "stdio" | "http" | "sse";
|
|
@@ -501,11 +501,11 @@ export class MCPAddWizard extends Container {
|
|
|
501
501
|
}
|
|
502
502
|
|
|
503
503
|
// Handle up/down arrows for selectors
|
|
504
|
-
if (
|
|
504
|
+
if (matchesSelectUp(keyData)) {
|
|
505
505
|
this.#moveSelection(-1);
|
|
506
506
|
return;
|
|
507
507
|
}
|
|
508
|
-
if (
|
|
508
|
+
if (matchesSelectDown(keyData)) {
|
|
509
509
|
this.#moveSelection(1);
|
|
510
510
|
return;
|
|
511
511
|
}
|
|
@@ -18,6 +18,7 @@ import { getKnownRoleIds, getRoleInfo, MODEL_ROLE_IDS, MODEL_ROLES } from "../..
|
|
|
18
18
|
import { resolveModelRoleValue } from "../../config/model-resolver";
|
|
19
19
|
import type { Settings } from "../../config/settings";
|
|
20
20
|
import { type ThemeColor, theme } from "../../modes/theme/theme";
|
|
21
|
+
import { matchesSelectDown, matchesSelectUp } from "../../modes/utils/keybinding-matchers";
|
|
21
22
|
import { getThinkingLevelMetadata } from "../../thinking";
|
|
22
23
|
import { getTabBarTheme } from "../shared";
|
|
23
24
|
import { DynamicBorder } from "./dynamic-border";
|
|
@@ -971,7 +972,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
971
972
|
}
|
|
972
973
|
|
|
973
974
|
// Up arrow - navigate list (wrap to bottom when at top)
|
|
974
|
-
if (
|
|
975
|
+
if (matchesSelectUp(keyData)) {
|
|
975
976
|
const itemCount = this.#isCanonicalTab() ? this.#filteredCanonicalModels.length : this.#filteredModels.length;
|
|
976
977
|
if (itemCount === 0) return;
|
|
977
978
|
this.#selectedIndex = this.#selectedIndex === 0 ? itemCount - 1 : this.#selectedIndex - 1;
|
|
@@ -980,7 +981,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
980
981
|
}
|
|
981
982
|
|
|
982
983
|
// Down arrow - navigate list (wrap to top when at bottom)
|
|
983
|
-
if (
|
|
984
|
+
if (matchesSelectDown(keyData)) {
|
|
984
985
|
const itemCount = this.#isCanonicalTab() ? this.#filteredCanonicalModels.length : this.#filteredModels.length;
|
|
985
986
|
if (itemCount === 0) return;
|
|
986
987
|
this.#selectedIndex = this.#selectedIndex === itemCount - 1 ? 0 : this.#selectedIndex + 1;
|
|
@@ -1022,13 +1023,13 @@ export class ModelSelectorComponent extends Container {
|
|
|
1022
1023
|
: this.#menuRoleActions.length;
|
|
1023
1024
|
if (optionCount === 0) return;
|
|
1024
1025
|
|
|
1025
|
-
if (
|
|
1026
|
+
if (matchesSelectUp(keyData)) {
|
|
1026
1027
|
this.#menuSelectedIndex = (this.#menuSelectedIndex - 1 + optionCount) % optionCount;
|
|
1027
1028
|
this.#updateMenu();
|
|
1028
1029
|
return;
|
|
1029
1030
|
}
|
|
1030
1031
|
|
|
1031
|
-
if (
|
|
1032
|
+
if (matchesSelectDown(keyData)) {
|
|
1032
1033
|
this.#menuSelectedIndex = (this.#menuSelectedIndex + 1) % optionCount;
|
|
1033
1034
|
this.#updateMenu();
|
|
1034
1035
|
return;
|
|
@@ -2,7 +2,7 @@ import { getOAuthProviders } from "@oh-my-pi/pi-ai/utils/oauth";
|
|
|
2
2
|
import type { OAuthProviderInfo } from "@oh-my-pi/pi-ai/utils/oauth/types";
|
|
3
3
|
import { Container, matchesKey, Spacer, TruncatedText } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { theme } from "../../modes/theme/theme";
|
|
5
|
-
import { matchesSelectCancel } from "../../modes/utils/keybinding-matchers";
|
|
5
|
+
import { matchesSelectCancel, matchesSelectDown, matchesSelectUp } from "../../modes/utils/keybinding-matchers";
|
|
6
6
|
import type { AuthStorage } from "../../session/auth-storage";
|
|
7
7
|
import { DynamicBorder } from "./dynamic-border";
|
|
8
8
|
|
|
@@ -193,7 +193,7 @@ export class OAuthSelectorComponent extends Container {
|
|
|
193
193
|
}
|
|
194
194
|
handleInput(keyData: string): void {
|
|
195
195
|
// Up arrow
|
|
196
|
-
if (
|
|
196
|
+
if (matchesSelectUp(keyData)) {
|
|
197
197
|
if (this.#allProviders.length > 0) {
|
|
198
198
|
this.#selectedIndex = this.#selectedIndex === 0 ? this.#allProviders.length - 1 : this.#selectedIndex - 1;
|
|
199
199
|
}
|
|
@@ -201,7 +201,7 @@ export class OAuthSelectorComponent extends Container {
|
|
|
201
201
|
this.#updateList();
|
|
202
202
|
}
|
|
203
203
|
// Down arrow
|
|
204
|
-
else if (
|
|
204
|
+
else if (matchesSelectDown(keyData)) {
|
|
205
205
|
if (this.#allProviders.length > 0) {
|
|
206
206
|
this.#selectedIndex = this.#selectedIndex === this.#allProviders.length - 1 ? 0 : this.#selectedIndex + 1;
|
|
207
207
|
}
|
|
@@ -25,7 +25,9 @@ import { PREVIEW_LIMITS, replaceTabs, TRUNCATE_LENGTHS, truncateToWidth } from "
|
|
|
25
25
|
import { toPathList } from "../../tools/search";
|
|
26
26
|
import type { ObservableSession, SessionObserverRegistry } from "../session-observer-registry";
|
|
27
27
|
import { getMarkdownTheme, theme } from "../theme/theme";
|
|
28
|
+
import { matchesSelectDown, matchesSelectUp } from "../utils/keybinding-matchers";
|
|
28
29
|
import { DynamicBorder } from "./dynamic-border";
|
|
30
|
+
import { formatContextUsage } from "./status-line/context-thresholds";
|
|
29
31
|
|
|
30
32
|
/** Max thinking characters in collapsed state */
|
|
31
33
|
const MAX_THINKING_CHARS_COLLAPSED = 200;
|
|
@@ -266,23 +268,27 @@ export class SessionObserverOverlayComponent extends Container {
|
|
|
266
268
|
const progress = session?.progress;
|
|
267
269
|
if (!progress) return "";
|
|
268
270
|
const stats: string[] = [];
|
|
269
|
-
|
|
270
|
-
// Current per-turn context — what the user reads as "how full is the context".
|
|
271
|
-
// Falls back to cumulative billing volume (Σ-prefixed) when context size is unknown.
|
|
271
|
+
// Current per-turn context — match the status line's `<pct>%/<window>` gauge (e.g. `5.1%/1M`).
|
|
272
272
|
if (progress.contextTokens && progress.contextTokens > 0) {
|
|
273
273
|
const ctx =
|
|
274
274
|
progress.contextWindow && progress.contextWindow > 0
|
|
275
|
-
?
|
|
276
|
-
: `${formatNumber(progress.contextTokens)}
|
|
275
|
+
? formatContextUsage((progress.contextTokens / progress.contextWindow) * 100, progress.contextWindow)
|
|
276
|
+
: `${formatNumber(progress.contextTokens)}`;
|
|
277
277
|
stats.push(ctx);
|
|
278
|
-
if (progress.tokens > 0) stats.push(`Σ${formatNumber(progress.tokens)}`);
|
|
279
|
-
} else if (progress.tokens > 0) {
|
|
280
|
-
stats.push(`Σ${formatNumber(progress.tokens)}`);
|
|
281
278
|
}
|
|
282
|
-
if (progress.durationMs > 0)
|
|
279
|
+
if (progress.durationMs > 0) {
|
|
280
|
+
stats.push(formatDuration(progress.durationMs));
|
|
281
|
+
}
|
|
283
282
|
const parts: string[] = [];
|
|
284
|
-
if (stats.length > 0
|
|
285
|
-
|
|
283
|
+
if (stats.length > 0 || progress.toolCount > 0) {
|
|
284
|
+
const toolCountStat =
|
|
285
|
+
progress.toolCount > 0 ? `${formatNumber(progress.toolCount)} ${theme.icon.extensionTool}` : undefined;
|
|
286
|
+
const statSegments = [toolCountStat, ...stats].filter((segment): segment is string => Boolean(segment));
|
|
287
|
+
parts.push(theme.fg("dim", statSegments.join(theme.sep.dot)));
|
|
288
|
+
}
|
|
289
|
+
if (progress.cost > 0) {
|
|
290
|
+
parts.push(theme.fg("statusLineCost", `$${progress.cost.toFixed(2)}`));
|
|
291
|
+
}
|
|
286
292
|
return parts.join(theme.sep.dot);
|
|
287
293
|
}
|
|
288
294
|
|
|
@@ -660,7 +666,7 @@ export class SessionObserverOverlayComponent extends Container {
|
|
|
660
666
|
}
|
|
661
667
|
|
|
662
668
|
// j / down — move selection down
|
|
663
|
-
if (keyData === "j" ||
|
|
669
|
+
if (keyData === "j" || matchesSelectDown(keyData)) {
|
|
664
670
|
if (entryCount > 0) {
|
|
665
671
|
this.#selectedEntryIndex = Math.min(this.#selectedEntryIndex + 1, entryCount - 1);
|
|
666
672
|
}
|
|
@@ -669,7 +675,7 @@ export class SessionObserverOverlayComponent extends Container {
|
|
|
669
675
|
}
|
|
670
676
|
|
|
671
677
|
// k / up — move selection up
|
|
672
|
-
if (keyData === "k" ||
|
|
678
|
+
if (keyData === "k" || matchesSelectUp(keyData)) {
|
|
673
679
|
if (entryCount > 0) {
|
|
674
680
|
this.#selectedEntryIndex = Math.max(this.#selectedEntryIndex - 1, 0);
|
|
675
681
|
}
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "@oh-my-pi/pi-tui";
|
|
14
14
|
import { formatBytes } from "@oh-my-pi/pi-utils";
|
|
15
15
|
import { theme } from "../../modes/theme/theme";
|
|
16
|
-
import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
|
|
16
|
+
import { matchesAppInterrupt, matchesSelectDown, matchesSelectUp } from "../../modes/utils/keybinding-matchers";
|
|
17
17
|
import type { SessionInfo } from "../../session/session-manager";
|
|
18
18
|
import { DynamicBorder } from "./dynamic-border";
|
|
19
19
|
import { HookSelectorComponent } from "./hook-selector";
|
|
@@ -192,12 +192,12 @@ class SessionList implements Component {
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
// Up arrow
|
|
195
|
-
if (
|
|
195
|
+
if (matchesSelectUp(keyData)) {
|
|
196
196
|
this.#selectedIndex = Math.max(0, this.#selectedIndex - 1);
|
|
197
197
|
return;
|
|
198
198
|
}
|
|
199
199
|
// Down arrow
|
|
200
|
-
if (
|
|
200
|
+
if (matchesSelectDown(keyData)) {
|
|
201
201
|
this.#selectedIndex = Math.min(this.#filteredSessions.length - 1, this.#selectedIndex + 1);
|
|
202
202
|
return;
|
|
203
203
|
}
|
|
@@ -79,6 +79,13 @@ const CONDITIONS: Record<string, () => boolean> = {
|
|
|
79
79
|
return false;
|
|
80
80
|
}
|
|
81
81
|
},
|
|
82
|
+
mnemosyneActive: () => {
|
|
83
|
+
try {
|
|
84
|
+
return Settings.instance.get("memory.backend") === "mnemosyne";
|
|
85
|
+
} catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
},
|
|
82
89
|
};
|
|
83
90
|
|
|
84
91
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { formatNumber } from "@oh-my-pi/pi-utils";
|
|
1
2
|
import type { ThemeColor } from "../../../modes/theme/theme";
|
|
2
3
|
|
|
3
4
|
export type ContextUsageLevel = "normal" | "warning" | "purple" | "error";
|
|
@@ -54,6 +55,16 @@ export function getContextUsageLevel(contextPercent: number, contextWindow: numb
|
|
|
54
55
|
return "normal";
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Format context usage as `<percent>%/<window>` (e.g. `5.1%/1M`), matching the
|
|
60
|
+
* status line's context gauge so subagent and footer renderers stay in sync.
|
|
61
|
+
* A `null`/`undefined` percent (unknown, e.g. right after compaction) renders as `?`.
|
|
62
|
+
*/
|
|
63
|
+
export function formatContextUsage(contextPercent: number | null | undefined, contextWindow: number): string {
|
|
64
|
+
const pct = contextPercent === null || contextPercent === undefined ? "?" : `${contextPercent.toFixed(1)}%`;
|
|
65
|
+
return `${pct}/${formatNumber(contextWindow)}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
57
68
|
export function getContextUsageThemeColor(level: ContextUsageLevel): ThemeColor {
|
|
58
69
|
switch (level) {
|
|
59
70
|
case "error":
|
|
@@ -7,7 +7,7 @@ import { type ThemeColor, theme } from "../../../modes/theme/theme";
|
|
|
7
7
|
import { shortenPath } from "../../../tools/render-utils";
|
|
8
8
|
import { getSessionAccentAnsi, getSessionAccentHex } from "../../../utils/session-color";
|
|
9
9
|
import { sanitizeStatusText } from "../../shared";
|
|
10
|
-
import { getContextUsageLevel, getContextUsageThemeColor } from "./context-thresholds";
|
|
10
|
+
import { formatContextUsage, getContextUsageLevel, getContextUsageThemeColor } from "./context-thresholds";
|
|
11
11
|
import type { RenderedSegment, SegmentContext, StatusLineSegment, StatusLineSegmentId } from "./types";
|
|
12
12
|
|
|
13
13
|
export type { SegmentContext } from "./types";
|
|
@@ -350,7 +350,7 @@ const contextPctSegment: StatusLineSegment = {
|
|
|
350
350
|
const window = ctx.contextWindow;
|
|
351
351
|
|
|
352
352
|
const autoIcon = ctx.autoCompactEnabled && theme.icon.auto ? ` ${theme.icon.auto}` : "";
|
|
353
|
-
const text = `${pct
|
|
353
|
+
const text = `${formatContextUsage(pct, window)}${autoIcon}`;
|
|
354
354
|
|
|
355
355
|
const color = getContextUsageThemeColor(getContextUsageLevel(pct, window));
|
|
356
356
|
const content = withIcon(theme.icon.context, theme.fg(color, text));
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { type Component, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
2
|
+
import { formatBytes } from "@oh-my-pi/pi-utils";
|
|
3
|
+
import { getTinyTitleModelSpec, type TinyTitleLocalModelKey } from "../../tiny/models";
|
|
4
|
+
import type { TinyTitleProgressEvent } from "../../tiny/title-protocol";
|
|
5
|
+
import { theme } from "../theme/theme";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_BAR_WIDTH = 24;
|
|
8
|
+
|
|
9
|
+
function padLine(line: string, width: number): string {
|
|
10
|
+
const visible = visibleWidth(line);
|
|
11
|
+
return visible >= width ? truncateToWidth(line, width) : `${line}${" ".repeat(width - visible)}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function progressBar(progress: number | undefined, width: number): string {
|
|
15
|
+
const barWidth = Math.max(8, Math.min(DEFAULT_BAR_WIDTH, width));
|
|
16
|
+
if (progress === undefined) return theme.fg("muted", "░".repeat(barWidth));
|
|
17
|
+
const ratio = Math.max(0, Math.min(1, progress / 100));
|
|
18
|
+
const filled = Math.round(ratio * barWidth);
|
|
19
|
+
return `${theme.fg("accent", "█".repeat(filled))}${theme.fg("muted", "░".repeat(barWidth - filled))}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function currentFile(event: TinyTitleProgressEvent | undefined): string | undefined {
|
|
23
|
+
if (!event) return undefined;
|
|
24
|
+
if (event.file) return event.file.split("/").at(-1) ?? event.file;
|
|
25
|
+
if (event.files) {
|
|
26
|
+
let largestFile: string | undefined;
|
|
27
|
+
let largestLoaded = -1;
|
|
28
|
+
for (const file in event.files) {
|
|
29
|
+
const state = event.files[file];
|
|
30
|
+
if (state.loaded <= largestLoaded || state.loaded >= state.total) continue;
|
|
31
|
+
largestFile = file;
|
|
32
|
+
largestLoaded = state.loaded;
|
|
33
|
+
}
|
|
34
|
+
return largestFile?.split("/").at(-1) ?? largestFile;
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function statusLabel(event: TinyTitleProgressEvent | undefined): string {
|
|
40
|
+
if (!event) return "Preparing";
|
|
41
|
+
if (event.status === "error") return "Failed";
|
|
42
|
+
if (event.status === "ready") return "Ready";
|
|
43
|
+
if (event.status === "done") return "Downloaded";
|
|
44
|
+
if (event.status === "download") return "Downloading";
|
|
45
|
+
if (event.status === "progress" || event.status === "progress_total") return "Downloading";
|
|
46
|
+
return "Preparing";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function byteLabel(event: TinyTitleProgressEvent | undefined): string | undefined {
|
|
50
|
+
if (!event?.loaded || !event.total) return undefined;
|
|
51
|
+
return `${formatBytes(event.loaded)} / ${formatBytes(event.total)}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class TinyTitleDownloadProgressComponent implements Component {
|
|
55
|
+
#modelKey: TinyTitleLocalModelKey;
|
|
56
|
+
#event: TinyTitleProgressEvent | undefined;
|
|
57
|
+
|
|
58
|
+
constructor(modelKey: TinyTitleLocalModelKey) {
|
|
59
|
+
this.#modelKey = modelKey;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
update(event: TinyTitleProgressEvent): void {
|
|
63
|
+
this.#event = event;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
isComplete(): boolean {
|
|
67
|
+
return this.#event?.status === "ready" || this.#event?.status === "error";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
invalidate(): void {
|
|
71
|
+
// No cached state.
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
render(width: number): string[] {
|
|
75
|
+
width = Math.max(1, width);
|
|
76
|
+
const spec = getTinyTitleModelSpec(this.#modelKey);
|
|
77
|
+
const border = theme.fg("border", theme.boxSharp.horizontal.repeat(width));
|
|
78
|
+
const status = statusLabel(this.#event);
|
|
79
|
+
const file = currentFile(this.#event);
|
|
80
|
+
const pct =
|
|
81
|
+
this.#event?.progress === undefined ? "" : `${Math.floor(this.#event.progress).toString().padStart(3, " ")}%`;
|
|
82
|
+
const bytes = byteLabel(this.#event);
|
|
83
|
+
const title = `${theme.fg("accent", "Tiny model")} ${theme.fg("muted", status)} ${spec.label}`;
|
|
84
|
+
const details = [progressBar(this.#event?.progress, Math.max(8, width - 36)), pct, bytes, file]
|
|
85
|
+
.filter((part): part is string => Boolean(part))
|
|
86
|
+
.join(" ");
|
|
87
|
+
|
|
88
|
+
return [border, padLine(` ${title}`, width), padLine(` ${details}`, width), border];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Tired of typing "keep going"? Just send a '.'
|
|
2
|
+
You can /btw to ask a side question
|
|
3
|
+
Ctrl+D can be used to exit, but with your draft saved!
|
|
4
|
+
Find out which model you emotionally abuse the most with `omp stats`
|
|
5
|
+
Try task isolation to create CoW worktrees
|
|
6
|
+
Your LLM can call an LLM using `llm(x...)`. Have a big batch of tasks? Ask clanker to use it!
|
|
7
|
+
Next time you see spaghet try: "omp, create a TTSR rule that will prevent this pattern, use omp://"
|
|
8
|
+
Did you know? Each kitty/tmux split keeps its own session — `omp -c` resumes the right one
|
|
9
|
+
Drop the word `ultrathink` in your message for harder multi-step reasoning — watch it glow rainbow as you type
|
|
10
|
+
Say `orchestrate` in your message to drive a multi-phase task with parallel subagents — watch it glow as you type
|
|
11
|
+
Log in to several accounts of the same provider — `/login` again — and omp load-balances across them automatically
|
|
12
|
+
Run `omp auth-broker serve` once and every machine pulls live tokens over the wire — refresh keys never leave the host; `omp auth-gateway` fronts it as a drop-in proxy any OpenAI-compatible client can hit
|