@underactive/pi-topping-moa-fusion 0.4.4 → 0.5.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 +41 -0
- package/README.md +0 -3
- package/package.json +9 -6
- package/src/agents/discovery.ts +24 -88
- package/src/config/settings.ts +79 -69
- package/src/debate/runDebate.ts +6 -5
- package/src/moa/modelRuntime.ts +36 -24
- package/src/opinion/opinionResults.ts +2 -2
- package/src/opinion/runOpinion.ts +6 -5
- package/src/planning/planMode.ts +15 -4
- package/src/planning/tools/enterPlanMode.ts +3 -3
- package/src/planning/tools/mfPlanSubagent.ts +13 -4
- package/src/runtime/activityTracking.ts +1 -91
- package/src/runtime/piCapabilities.ts +8 -19
- package/src/runtime/processPool.ts +14 -52
- package/src/runtime/results.ts +13 -3
- package/src/runtime/runner.ts +7 -20
- package/src/shared/modelRefs.ts +10 -71
- package/src/ui/agentStatus.ts +3 -9
- package/src/ui/cancelOverlay.ts +1 -1
- package/src/ui/chrome.ts +1 -2
- package/src/ui/debateModelPicker.ts +3 -3
- package/src/ui/moaModelPicker.ts +7 -8
- package/src/ui/moaProgressWidget.ts +14 -33
- package/src/ui/moaSetupOverlay.ts +13 -9
- package/src/ui/opinionModelPicker.ts +3 -3
- package/src/ui/twoPaneModelThinking.ts +31 -268
- package/src/ui/workingOverlay.ts +2 -2
- package/src/activityMeter.ts +0 -193
- package/src/config/modelCatalogue.ts +0 -54
- package/src/ui/modelLabel.ts +0 -61
- package/src/ui/shimmer.ts +0 -77
- package/src/ui/toolActivity.ts +0 -35
|
@@ -31,13 +31,19 @@
|
|
|
31
31
|
import type { Message } from "@earendil-works/pi-ai";
|
|
32
32
|
import type { ExtensionContext, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
33
33
|
import { visibleWidth, wrapTextWithAnsi, type Component, type TUI } from "@earendil-works/pi-tui";
|
|
34
|
-
import { ACTIVITY_METER_WIDTH, ActivityMeter, rateToLevel, TokRateTracker } from "
|
|
34
|
+
import { ACTIVITY_METER_WIDTH, ActivityMeter, rateToLevel, TokRateTracker } from "@underactive/pi-topping-primitives/activity";
|
|
35
|
+
import {
|
|
36
|
+
type AnsiTheme,
|
|
37
|
+
highlightActivity,
|
|
38
|
+
renderIdleMeter,
|
|
39
|
+
renderMeter,
|
|
40
|
+
shimmerString,
|
|
41
|
+
thinkingLevelThemeColor,
|
|
42
|
+
} from "@underactive/pi-topping-primitives/activity-tui";
|
|
35
43
|
import { modelRefLabel, shortModelName, type ModelRef, type ThinkingLevel } from "../shared/modelRefs.ts";
|
|
36
44
|
import { contextPercent, CTX_COL_WIDTH, formatCost, formatElapsed, formatTokens } from "./agentStatus.ts";
|
|
37
45
|
import { formatAgentPreview } from "./agentTranscript.ts";
|
|
38
46
|
import { QUADRANT_SPINNER_FRAMES, UI_TICK_MS, fitVisible, ratioViewport } from "./chrome.ts";
|
|
39
|
-
import { shimmerString, type ShimmerTheme } from "./shimmer.ts";
|
|
40
|
-
import { highlightActivity } from "./toolActivity.ts";
|
|
41
47
|
|
|
42
48
|
const WIDGET_KEY = "mf-plan-moa-status";
|
|
43
49
|
|
|
@@ -140,28 +146,6 @@ const PREVIEW_GUTTER = "│ ";
|
|
|
140
146
|
const PREVIEW_MIN_WIDTH = 16;
|
|
141
147
|
const TRANSCRIPT_MESSAGE_LIMIT = 40;
|
|
142
148
|
|
|
143
|
-
/**
|
|
144
|
-
* MONITOR meter hue per selected thinking level: each row's activity meter is
|
|
145
|
-
* tinted with its level's native theme color (`thinkingOff` … `thinkingMax`),
|
|
146
|
-
* so the table reads the thinking effort at a glance. Rows whose level is
|
|
147
|
-
* unknown fall back to `accent` (see `meterColorFor`). Exhaustive over
|
|
148
|
-
* `ThinkingLevel` so a new level cannot silently render as accent.
|
|
149
|
-
*/
|
|
150
|
-
const THINKING_METER_COLORS: Record<ThinkingLevel, ThemeColor> = {
|
|
151
|
-
off: "thinkingOff",
|
|
152
|
-
minimal: "thinkingMinimal",
|
|
153
|
-
low: "thinkingLow",
|
|
154
|
-
medium: "thinkingMedium",
|
|
155
|
-
high: "thinkingHigh",
|
|
156
|
-
xhigh: "thinkingXhigh",
|
|
157
|
-
max: "thinkingMax",
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
/** Resolve a row's meter hue, defaulting an unknown level to the neutral accent. */
|
|
161
|
-
function meterColorFor(thinking: ThinkingLevel | undefined): ThemeColor {
|
|
162
|
-
return thinking ? THINKING_METER_COLORS[thinking] : "accent";
|
|
163
|
-
}
|
|
164
|
-
|
|
165
149
|
/** Push an activity onto a bounded history buffer (most-recent-last). */
|
|
166
150
|
function pushActivity(history: string[], activity: string): void {
|
|
167
151
|
history.push(activity);
|
|
@@ -747,7 +731,7 @@ export class MoaProgressTableComponent implements Component {
|
|
|
747
731
|
const row = rows[i];
|
|
748
732
|
let entry = this.meters[i];
|
|
749
733
|
if (!entry) {
|
|
750
|
-
entry = { meter: new ActivityMeter("rtl"), tracker: new TokRateTracker(), revision: row.outputRevision };
|
|
734
|
+
entry = { meter: new ActivityMeter({ direction: "rtl" }), tracker: new TokRateTracker(), revision: row.outputRevision };
|
|
751
735
|
this.meters[i] = entry;
|
|
752
736
|
}
|
|
753
737
|
if (entry.revision !== row.outputRevision) {
|
|
@@ -762,14 +746,11 @@ export class MoaProgressTableComponent implements Component {
|
|
|
762
746
|
|
|
763
747
|
private renderMeter(index: number, active: boolean, thinking: ThinkingLevel | undefined): string {
|
|
764
748
|
const meter = this.meters[index]?.meter;
|
|
765
|
-
if (!meter) return this.theme
|
|
749
|
+
if (!meter) return renderIdleMeter(this.theme);
|
|
766
750
|
// Each row's meter carries its selected thinking level's native theme hue;
|
|
767
751
|
// an unknown level falls back to accent. IDLE cells and settled traces keep
|
|
768
|
-
// their existing dimming inside
|
|
769
|
-
|
|
770
|
-
return meter.render((level, char) =>
|
|
771
|
-
ActivityMeter.colorizeCell(level, char, this.theme, color, !active),
|
|
772
|
-
);
|
|
752
|
+
// their existing dimming inside renderMeter regardless of the hue.
|
|
753
|
+
return renderMeter(meter, this.theme, thinkingLevelThemeColor(thinking), !active);
|
|
773
754
|
}
|
|
774
755
|
|
|
775
756
|
/** How many lines the table may use, so it never crowds out the editor below it. */
|
|
@@ -798,7 +779,7 @@ export class MoaProgressTableComponent implements Component {
|
|
|
798
779
|
const th = this.theme;
|
|
799
780
|
if (!lit) return th.fg("dim", text);
|
|
800
781
|
return th.getFgAnsi
|
|
801
|
-
? shimmerString(text, now - this.animationStartedAt, th as
|
|
782
|
+
? shimmerString(text, now - this.animationStartedAt, th as AnsiTheme, "ltr", "normal", true)
|
|
802
783
|
: th.fg("text", text);
|
|
803
784
|
}
|
|
804
785
|
|
|
@@ -21,7 +21,8 @@ import {
|
|
|
21
21
|
MAX_VERIFICATION_REPAIRS_LIMIT,
|
|
22
22
|
MIN_CONCURRENT_AGENTS,
|
|
23
23
|
MIN_VERIFICATION_REPAIRS,
|
|
24
|
-
|
|
24
|
+
moaSaveFailureMessage,
|
|
25
|
+
updateMoaConfig,
|
|
25
26
|
type MoaConfig,
|
|
26
27
|
} from "../config/settings.ts";
|
|
27
28
|
import type { PlanRoster } from "../config/rosters.ts";
|
|
@@ -29,8 +30,8 @@ import { modelRefLabel, type ModelRef, type ThinkingLevel } from "../shared/mode
|
|
|
29
30
|
import { CONFIGURABLE_AGENTS, readAgentDefault, writeAgentDefault } from "../agents/defaults.ts";
|
|
30
31
|
import { getAvailableModelRefs } from "./moaModelPicker.ts";
|
|
31
32
|
import { showRosterManager } from "./rosterEditor.ts";
|
|
32
|
-
import { TwoPaneModelThinking } from "./twoPaneModelThinking.ts";
|
|
33
|
-
import { getModelCatalogue } from "
|
|
33
|
+
import { createMoaModelPicker, type TwoPaneModelThinking } from "./twoPaneModelThinking.ts";
|
|
34
|
+
import { getModelCatalogue } from "@underactive/pi-topping-primitives/models";
|
|
34
35
|
import {
|
|
35
36
|
MenuComponent,
|
|
36
37
|
PLAN_OVERLAY_OPTIONS,
|
|
@@ -108,7 +109,7 @@ class MoaSetupComponent implements Component {
|
|
|
108
109
|
title: string,
|
|
109
110
|
private readonly done: (result: SetupResult) => void,
|
|
110
111
|
) {
|
|
111
|
-
this.twoPane =
|
|
112
|
+
this.twoPane = createMoaModelPicker(tui, theme, availableRefs, config, currentThinking, ctx);
|
|
112
113
|
this.autoResolveConflicts = draft.autoResolveConflicts;
|
|
113
114
|
this.useSummaryName = draft.useSummaryName;
|
|
114
115
|
this.maxConcurrentAgents = draft.maxConcurrentAgents;
|
|
@@ -377,8 +378,8 @@ export async function showMoaSetup(
|
|
|
377
378
|
draft.rosters = await showRosterManager(ctx, draft.rosters, {
|
|
378
379
|
currentThinking,
|
|
379
380
|
onChange: (rosters) => {
|
|
380
|
-
const
|
|
381
|
-
|
|
381
|
+
const persisted = updateMoaConfig((current) => ({ ...current, rosters }));
|
|
382
|
+
if (!persisted.ok) ctx.ui.notify(moaSaveFailureMessage(persisted), "warning");
|
|
382
383
|
},
|
|
383
384
|
});
|
|
384
385
|
continue;
|
|
@@ -408,8 +409,7 @@ export async function showMoaSetup(
|
|
|
408
409
|
// intentionally left untouched: they remain load-bearing for resume,
|
|
409
410
|
// verifier fallback, and picker seeding, and are only written by a run's
|
|
410
411
|
// own confirmations.
|
|
411
|
-
const
|
|
412
|
-
saveMoaConfig({
|
|
412
|
+
const persisted = updateMoaConfig((current) => ({
|
|
413
413
|
...current,
|
|
414
414
|
mode: "moa",
|
|
415
415
|
agentDefaultsConfigured: true,
|
|
@@ -421,6 +421,10 @@ export async function showMoaSetup(
|
|
|
421
421
|
cheap: finalDraft.selections[AGENT_SLOT_COUNT],
|
|
422
422
|
rosters: finalDraft.rosters,
|
|
423
423
|
thinkingOverrides: { ...current.thinkingOverrides, ...finalDraft.thinkingOverrides },
|
|
424
|
-
});
|
|
424
|
+
}));
|
|
425
|
+
if (!persisted.ok) {
|
|
426
|
+
ctx.ui.notify(moaSaveFailureMessage(persisted), "warning");
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
425
429
|
return true;
|
|
426
430
|
}
|
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
ratioViewport,
|
|
11
11
|
} from "./chrome.ts";
|
|
12
12
|
import { getAvailableModelRefs } from "./moaModelPicker.ts";
|
|
13
|
-
import { smartTruncateModelLabel } from "
|
|
14
|
-
import { TwoPaneModelThinking } from "./twoPaneModelThinking.ts";
|
|
13
|
+
import { smartTruncateModelLabel } from "@underactive/pi-topping-primitives/models";
|
|
14
|
+
import { createMoaModelPicker, type TwoPaneModelThinking } from "./twoPaneModelThinking.ts";
|
|
15
15
|
|
|
16
16
|
const HINT_BASE = "type filters models • ↑↓ navigate pane • tab panes/buttons • ←→ switch/select • enter select";
|
|
17
17
|
|
|
@@ -54,7 +54,7 @@ export class OpinionModelPickerComponent implements Component {
|
|
|
54
54
|
this.theme = theme;
|
|
55
55
|
this.defaults = defaults;
|
|
56
56
|
this.done = done;
|
|
57
|
-
this.twoPane =
|
|
57
|
+
this.twoPane = createMoaModelPicker(tui, theme, availableRefs, config, currentThinking, ctx);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
private assignedCount(): number {
|
|
@@ -1,272 +1,35 @@
|
|
|
1
1
|
import type { ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import type { TUI } from "@earendil-works/pi-tui";
|
|
3
|
+
import { registryLevelOptions, TwoPaneModelThinking } from "@underactive/pi-topping-primitives/models";
|
|
4
|
+
import { defaultThinkingForModel, type MoaConfig } from "../config/settings.ts";
|
|
5
|
+
import { modelRefLabel, type ModelRef, type ThinkingLevel } from "../shared/modelRefs.ts";
|
|
4
6
|
import { SELECTOR_POINTER } from "./chrome.ts";
|
|
5
|
-
import { renderMenuFooterContents } from "./menu.ts";
|
|
6
|
-
import { MODEL_LIST_LAYOUT } from "./modelLabel.ts";
|
|
7
|
-
import { getModelCatalogue } from "../config/modelCatalogue.ts";
|
|
8
|
-
import {
|
|
9
|
-
defaultThinkingForModel,
|
|
10
|
-
thinkingOptionsForModel,
|
|
11
|
-
type MoaConfig,
|
|
12
|
-
} from "../config/settings.ts";
|
|
13
|
-
import {
|
|
14
|
-
isThinkingLevel,
|
|
15
|
-
modelRefLabel,
|
|
16
|
-
parseRef,
|
|
17
|
-
THINKING_LEVELS,
|
|
18
|
-
type ModelRef,
|
|
19
|
-
type ThinkingLevel,
|
|
20
|
-
} from "../shared/modelRefs.ts";
|
|
21
7
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
private activePane: "model" | "level" | "buttons" = "model";
|
|
50
|
-
private activeButton = 0;
|
|
51
|
-
private maxVisibleRows = MAX_VISIBLE_ROWS;
|
|
52
|
-
private readonly modelItems: SelectItem[];
|
|
53
|
-
|
|
54
|
-
constructor(
|
|
55
|
-
private readonly tui: TUI,
|
|
56
|
-
private readonly theme: Theme,
|
|
57
|
-
availableRefs: ModelRef[],
|
|
58
|
-
private readonly config: MoaConfig,
|
|
59
|
-
private readonly currentThinking: ThinkingLevel,
|
|
60
|
-
private readonly ctx: ExtensionContext,
|
|
61
|
-
) {
|
|
62
|
-
this.modelItems = toModelItems(availableRefs);
|
|
63
|
-
this.modelList = new SelectList([], 1, getSelectListTheme());
|
|
64
|
-
this.levelList = new SelectList([], 1, getSelectListTheme());
|
|
65
|
-
this.reset();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
isModelPane(): boolean {
|
|
69
|
-
return this.activePane === "model";
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Limit the complete two-pane body, including filter/header/scroll rows. */
|
|
73
|
-
setMaxVisibleRows(limit: number): void {
|
|
74
|
-
const next = Math.max(1, Math.floor(limit));
|
|
75
|
-
if (next === this.maxVisibleRows) return;
|
|
76
|
-
this.maxVisibleRows = next;
|
|
77
|
-
this.rebuildPreservingSelection();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
reset(defaultRef?: ModelRef, defaultThinking?: ThinkingLevel): void {
|
|
81
|
-
this.filter = "";
|
|
82
|
-
this.activePane = "model";
|
|
83
|
-
this.activeButton = 0;
|
|
84
|
-
this.rebuildModelList();
|
|
85
|
-
if (defaultRef) {
|
|
86
|
-
const defaultIndex = this.modelItems.findIndex((item) => item.value === modelRefLabel(defaultRef));
|
|
87
|
-
if (defaultIndex >= 0) this.modelList.setSelectedIndex(defaultIndex);
|
|
88
|
-
}
|
|
89
|
-
this.rebuildThinkingList();
|
|
90
|
-
if (defaultThinking) this.selectThinking(defaultThinking);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
private selectThinking(thinking: ThinkingLevel): void {
|
|
94
|
-
const key = this.modelList.getSelectedItem()?.value;
|
|
95
|
-
if (!key) return;
|
|
96
|
-
const levels = thinkingOptionsForModel(getModelCatalogue(this.ctx.modelRegistry).thinkingLevelsFor(parseRef(key)));
|
|
97
|
-
const index = levels.indexOf(thinking);
|
|
98
|
-
if (index >= 0) this.levelList.setSelectedIndex(index);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private listItemRows(itemCount: number): number {
|
|
102
|
-
const chromeRows = 1 + (this.filter ? 1 : 0);
|
|
103
|
-
const available = Math.max(1, this.maxVisibleRows - chromeRows);
|
|
104
|
-
return itemCount > available ? Math.max(1, available - 1) : available;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
private filteredModelItems(): SelectItem[] {
|
|
108
|
-
return this.filter.trim()
|
|
109
|
-
? fuzzyFilter(this.modelItems, this.filter, (item) => item.label)
|
|
110
|
-
: this.modelItems;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
private rebuildModelList(): SelectItem[] {
|
|
114
|
-
const items = this.filteredModelItems();
|
|
115
|
-
this.modelList = new SelectList(
|
|
116
|
-
items,
|
|
117
|
-
Math.min(Math.max(items.length, 1), this.listItemRows(items.length)),
|
|
118
|
-
getSelectListTheme(),
|
|
119
|
-
MODEL_LIST_LAYOUT,
|
|
120
|
-
);
|
|
121
|
-
this.modelList.onSelectionChange = () => {
|
|
122
|
-
this.rebuildThinkingList();
|
|
123
|
-
this.tui.requestRender();
|
|
124
|
-
};
|
|
125
|
-
return items;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
private rebuildPreservingSelection(): void {
|
|
129
|
-
const selectedModel = this.modelList.getSelectedItem()?.value;
|
|
130
|
-
const selectedLevel = this.levelList.getSelectedItem()?.value;
|
|
131
|
-
const items = this.rebuildModelList();
|
|
132
|
-
if (selectedModel) {
|
|
133
|
-
const index = items.findIndex((item) => item.value === selectedModel);
|
|
134
|
-
if (index >= 0) this.modelList.setSelectedIndex(index);
|
|
135
|
-
}
|
|
136
|
-
this.rebuildThinkingList();
|
|
137
|
-
if (selectedLevel && isThinkingLevel(selectedLevel)) this.selectThinking(selectedLevel);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
private rebuildThinkingList(): void {
|
|
141
|
-
const key = this.modelList.getSelectedItem()?.value;
|
|
142
|
-
const registryLevels = key
|
|
143
|
-
? getModelCatalogue(this.ctx.modelRegistry).thinkingLevelsFor(parseRef(key))
|
|
144
|
-
: [];
|
|
145
|
-
const levels = thinkingOptionsForModel(registryLevels);
|
|
146
|
-
const items = (levels.length > 0 ? levels : ["off"]).map((level) => ({ value: level, label: level }));
|
|
147
|
-
this.levelList = new SelectList(items, Math.min(Math.max(items.length, 1), this.listItemRows(items.length)), getSelectListTheme());
|
|
148
|
-
if (!key) return;
|
|
149
|
-
const preferred = defaultThinkingForModel(key, this.config, this.currentThinking, registryLevels);
|
|
150
|
-
const preferredIndex = levels.indexOf(preferred);
|
|
151
|
-
if (preferredIndex >= 0) this.levelList.setSelectedIndex(preferredIndex);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
handleInput(data: string): "confirm" | "back" | undefined {
|
|
155
|
-
if (matchesKey(data, Key.escape)) return "back";
|
|
156
|
-
if (matchesKey(data, Key.tab)) {
|
|
157
|
-
// Tab cycles Models → Thinking → action buttons → Models;
|
|
158
|
-
// left/right continue to switch between Models and Thinking (or
|
|
159
|
-
// between buttons, once the action bar is focused).
|
|
160
|
-
this.activePane = this.activePane === "model" ? "level" : this.activePane === "level" ? "buttons" : "model";
|
|
161
|
-
this.tui.requestRender();
|
|
162
|
-
return undefined;
|
|
163
|
-
}
|
|
164
|
-
if (this.activePane === "buttons") {
|
|
165
|
-
if (matchesKey(data, Key.left) || matchesKey(data, Key.right)) {
|
|
166
|
-
this.activeButton = this.activeButton === 0 ? 1 : 0;
|
|
167
|
-
this.tui.requestRender();
|
|
168
|
-
return undefined;
|
|
169
|
-
}
|
|
170
|
-
if (matchesKey(data, Key.enter)) {
|
|
171
|
-
return this.activeButton === 0 && this.modelList.getSelectedItem() && this.levelList.getSelectedItem()
|
|
172
|
-
? "confirm"
|
|
173
|
-
: "back";
|
|
174
|
-
}
|
|
175
|
-
return undefined;
|
|
176
|
-
}
|
|
177
|
-
if (matchesKey(data, Key.enter)) {
|
|
178
|
-
return this.modelList.getSelectedItem() && this.levelList.getSelectedItem() ? "confirm" : undefined;
|
|
179
|
-
}
|
|
180
|
-
if (matchesKey(data, Key.left) || matchesKey(data, Key.right)) {
|
|
181
|
-
this.activePane = this.activePane === "model" ? "level" : "model";
|
|
182
|
-
this.tui.requestRender();
|
|
183
|
-
return undefined;
|
|
184
|
-
}
|
|
185
|
-
if (data === "\x7f" || data === "\b") {
|
|
186
|
-
this.activePane = "model";
|
|
187
|
-
if (this.filter.length > 0) {
|
|
188
|
-
this.filter = this.filter.slice(0, -1);
|
|
189
|
-
this.rebuildPreservingSelection();
|
|
190
|
-
this.tui.requestRender();
|
|
191
|
-
}
|
|
192
|
-
return undefined;
|
|
193
|
-
}
|
|
194
|
-
if (data.length === 1 && data >= " " && data !== "\x7f") {
|
|
195
|
-
this.activePane = "model";
|
|
196
|
-
this.filter += data;
|
|
197
|
-
this.rebuildPreservingSelection();
|
|
198
|
-
this.tui.requestRender();
|
|
199
|
-
return undefined;
|
|
200
|
-
}
|
|
201
|
-
(this.activePane === "model" ? this.modelList : this.levelList).handleInput(data);
|
|
202
|
-
this.tui.requestRender();
|
|
203
|
-
return undefined;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
getSelected(): { ref: ModelRef; thinking: ThinkingLevel } {
|
|
207
|
-
const model = this.modelList.getSelectedItem();
|
|
208
|
-
const level = this.levelList.getSelectedItem();
|
|
209
|
-
if (!model || !level || !isThinkingLevel(level.value)) {
|
|
210
|
-
throw new Error("Cannot confirm an empty model or thinking-level selection");
|
|
211
|
-
}
|
|
212
|
-
return { ref: parseRef(model.value), thinking: level.value };
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
render(bodyWidth: number): string[] {
|
|
216
|
-
const { left: leftWidth, right: rightWidth } = modelThinkingPaneWidths(bodyWidth);
|
|
217
|
-
const column = (text: string, width: number) => truncateToWidth(text, width, "", true);
|
|
218
|
-
const paneDivider = this.theme.fg("border", "│");
|
|
219
|
-
const headers = column(
|
|
220
|
-
this.activePane === "model" ? this.theme.bold(this.theme.fg("accent", " Models")) : this.theme.bold(" Models"),
|
|
221
|
-
leftWidth,
|
|
222
|
-
) + paneDivider + column(
|
|
223
|
-
` ${this.activePane === "level" ? this.theme.bold(this.theme.fg("accent", "Thinking")) : this.theme.bold("Thinking")}`,
|
|
224
|
-
rightWidth,
|
|
225
|
-
);
|
|
226
|
-
const modelLines = this.modelList.render(Math.max(1, leftWidth - 1));
|
|
227
|
-
const levelLines = this.levelList.render(Math.max(1, rightWidth - 1));
|
|
228
|
-
const rows = Math.max(modelLines.length, levelLines.length);
|
|
229
|
-
const lines = [
|
|
230
|
-
...(this.filter ? [column(this.theme.fg("muted", ` filter: ${this.filter}`), bodyWidth)] : []),
|
|
231
|
-
headers,
|
|
232
|
-
];
|
|
233
|
-
for (let index = 0; index < rows; index++) {
|
|
234
|
-
const modelLine = modelLines[index] ?? "";
|
|
235
|
-
const levelLine = levelLines[index] ?? "";
|
|
236
|
-
let leftCell = column(` ${withPointer(modelLine)}`, leftWidth);
|
|
237
|
-
let rightCell = column(` ${withPointer(levelLine)}`, rightWidth);
|
|
238
|
-
// SelectList renders "→ " only on the selected row; other rows use " ".
|
|
239
|
-
if (this.activePane === "model" && modelLine.includes("→")) {
|
|
240
|
-
leftCell = this.theme.bg("selectedBg", leftCell);
|
|
241
|
-
}
|
|
242
|
-
if (this.activePane === "level" && levelLine.includes("→")) {
|
|
243
|
-
rightCell = this.theme.bg("selectedBg", rightCell);
|
|
244
|
-
}
|
|
245
|
-
lines.push(leftCell + paneDivider + rightCell);
|
|
246
|
-
}
|
|
247
|
-
return lines.slice(0, this.maxVisibleRows);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
renderFooter(bodyWidth: number, hints: string): { actionRow: string; hintRows: string[] } {
|
|
251
|
-
const buttonText = (label: string, index: number) => {
|
|
252
|
-
const isActive = this.activeButton === index;
|
|
253
|
-
const text = isActive ? `[ ${label} ]` : `‹ ${label} ›`;
|
|
254
|
-
return isActive && this.activePane === "buttons"
|
|
255
|
-
? this.theme.bold(this.theme.fg("accent", text))
|
|
256
|
-
: this.theme.fg("muted", text);
|
|
257
|
-
};
|
|
258
|
-
const rows = renderMenuFooterContents(
|
|
259
|
-
this.theme,
|
|
260
|
-
bodyWidth,
|
|
261
|
-
`${buttonText("Select", 0)} ${buttonText("Cancel", 1)}`,
|
|
262
|
-
hints,
|
|
263
|
-
{ wrapHints: true },
|
|
264
|
-
);
|
|
265
|
-
return { actionRow: rows[1] ?? "", hintRows: rows.slice(3) };
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
invalidate(): void {
|
|
269
|
-
this.modelList.invalidate();
|
|
270
|
-
this.levelList.invalidate();
|
|
271
|
-
}
|
|
8
|
+
export type { TwoPaneModelThinking } from "@underactive/pi-topping-primitives/models";
|
|
9
|
+
|
|
10
|
+
/** Keep MoA's registry fallback and saved/current thinking policy in the host. */
|
|
11
|
+
export function createMoaModelPicker(
|
|
12
|
+
tui: TUI,
|
|
13
|
+
theme: Theme,
|
|
14
|
+
refs: ModelRef[],
|
|
15
|
+
config: MoaConfig,
|
|
16
|
+
currentThinking: ThinkingLevel,
|
|
17
|
+
ctx: ExtensionContext,
|
|
18
|
+
): TwoPaneModelThinking {
|
|
19
|
+
// Filter edits rebuild the level list for the same model: keep the user's
|
|
20
|
+
// level then, and apply the saved/current default only when the model changes.
|
|
21
|
+
let lastKey: string | undefined;
|
|
22
|
+
return new TwoPaneModelThinking({
|
|
23
|
+
theme,
|
|
24
|
+
refs,
|
|
25
|
+
levelOptions: registryLevelOptions(ctx.modelRegistry, { fallback: ["off"] }),
|
|
26
|
+
preferredLevel: (ref, options, previous) => {
|
|
27
|
+
const key = modelRefLabel(ref);
|
|
28
|
+
const keep = key === lastKey && previous !== undefined && options.includes(previous);
|
|
29
|
+
lastKey = key;
|
|
30
|
+
return keep ? previous : defaultThinkingForModel(key, config, currentThinking, options);
|
|
31
|
+
},
|
|
32
|
+
requestRender: () => tui.requestRender(),
|
|
33
|
+
marker: SELECTOR_POINTER,
|
|
34
|
+
});
|
|
272
35
|
}
|
package/src/ui/workingOverlay.ts
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
import type { ExtensionContext, Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import { isKeyRelease, Key, matchesKey, visibleWidth, type Component, type TUI } from "@earendil-works/pi-tui";
|
|
13
13
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
14
|
+
import { shimmerString, type AnsiTheme } from "@underactive/pi-topping-primitives/activity-tui";
|
|
14
15
|
import { formatElapsed } from "./agentStatus.ts";
|
|
15
16
|
import { BRAILLE_SPINNER_FRAMES, ROUNDED_SINGLE_BOX, UI_TICK_MS, createFrame } from "./chrome.ts";
|
|
16
|
-
import { shimmerString, type ShimmerTheme } from "./shimmer.ts";
|
|
17
17
|
|
|
18
18
|
const OVERLAY_MARGIN = 1;
|
|
19
19
|
/** Grace before mounting: work that settles inside this never flashes a card. */
|
|
@@ -246,7 +246,7 @@ class WorkingOverlayComponent implements Component {
|
|
|
246
246
|
|
|
247
247
|
const spin = th.fg("accent", BRAILLE_SPINNER_FRAMES[this.spinFrame % BRAILLE_SPINNER_FRAMES.length]!);
|
|
248
248
|
const detailText = th.getFgAnsi
|
|
249
|
-
? shimmerString(this.options.detail, Date.now() - this.startedAt, th as
|
|
249
|
+
? shimmerString(this.options.detail, Date.now() - this.startedAt, th as AnsiTheme, "ltr", "normal")
|
|
250
250
|
: th.fg("dim", this.options.detail);
|
|
251
251
|
const detailRow = frame.row(`${spin} ${detailText}`);
|
|
252
252
|
|