@underactive/pi-topping-moa-fusion 0.1.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 +5 -0
- package/LICENSE +21 -0
- package/README.md +437 -0
- package/agents/mf-plan.md +43 -0
- package/agents/moa-debater.md +37 -0
- package/agents/moa-explore.md +56 -0
- package/agents/moa-opinion.md +29 -0
- package/agents/moa-proposer.md +49 -0
- package/agents/moa-synthesizer.md +124 -0
- package/agents/moa-verifier.md +67 -0
- package/index.ts +3 -0
- package/package.json +61 -0
- package/src/activityMeter.ts +193 -0
- package/src/agents/authoritative.ts +91 -0
- package/src/agents/defaults.ts +123 -0
- package/src/agents/discovery.ts +119 -0
- package/src/config/modelCatalogue.ts +54 -0
- package/src/config/planName.ts +74 -0
- package/src/config/rosters.ts +118 -0
- package/src/config/settings.ts +161 -0
- package/src/debate/debateContract.ts +89 -0
- package/src/debate/debateFanout.ts +285 -0
- package/src/debate/debateFile.ts +38 -0
- package/src/debate/debateResults.ts +115 -0
- package/src/debate/debateRounds.ts +61 -0
- package/src/debate/runDebate.ts +143 -0
- package/src/index.ts +283 -0
- package/src/moa/conflictContract.ts +49 -0
- package/src/moa/conflicts.ts +153 -0
- package/src/moa/contextContract.ts +52 -0
- package/src/moa/fanout.ts +152 -0
- package/src/moa/fanoutWiring.ts +88 -0
- package/src/moa/implementationRetry.ts +292 -0
- package/src/moa/modelRuntime.ts +87 -0
- package/src/moa/orchestration.ts +105 -0
- package/src/moa/planInfo.ts +57 -0
- package/src/moa/planlessRetry.ts +72 -0
- package/src/moa/reviewLoop.ts +170 -0
- package/src/moa/runContext.ts +118 -0
- package/src/moa/synthesis.ts +420 -0
- package/src/moa/verdicts.ts +81 -0
- package/src/moa/verification.ts +791 -0
- package/src/moa/verificationCriteria.ts +127 -0
- package/src/moa/verifyGate.ts +137 -0
- package/src/opinion/opinionContract.ts +21 -0
- package/src/opinion/opinionFanout.ts +135 -0
- package/src/opinion/opinionFile.ts +38 -0
- package/src/opinion/opinionResults.ts +73 -0
- package/src/opinion/runOpinion.ts +156 -0
- package/src/planning/askUserQuestion.ts +83 -0
- package/src/planning/instructions.ts +146 -0
- package/src/planning/modeState.ts +61 -0
- package/src/planning/planFile.ts +273 -0
- package/src/planning/planMode.ts +673 -0
- package/src/planning/tools/enterPlanMode.ts +165 -0
- package/src/planning/tools/exitPlanMode.ts +159 -0
- package/src/planning/tools/mfPlanSubagent.ts +311 -0
- package/src/planning/tools/shared.ts +19 -0
- package/src/planning/tools/writePlan.ts +33 -0
- package/src/runtime/activityTracking.ts +141 -0
- package/src/runtime/cancelRun.ts +134 -0
- package/src/runtime/mutationTripwire.ts +251 -0
- package/src/runtime/processPool.ts +55 -0
- package/src/runtime/results.ts +103 -0
- package/src/runtime/runner.ts +538 -0
- package/src/runtime/wire.ts +177 -0
- package/src/shared/functionKeys.ts +30 -0
- package/src/shared/modelRefs.ts +91 -0
- package/src/ui/agentStatus.ts +84 -0
- package/src/ui/agentTranscript.ts +112 -0
- package/src/ui/cancelOverlay.ts +191 -0
- package/src/ui/chrome.ts +151 -0
- package/src/ui/conflictOverlay.ts +363 -0
- package/src/ui/debateModelPicker.ts +273 -0
- package/src/ui/menu.ts +679 -0
- package/src/ui/moaModelPicker.ts +900 -0
- package/src/ui/moaProgressWidget.ts +910 -0
- package/src/ui/moaSetupOverlay.ts +368 -0
- package/src/ui/modelLabel.ts +61 -0
- package/src/ui/observeOverlay.ts +206 -0
- package/src/ui/opinionModelPicker.ts +246 -0
- package/src/ui/planReviewOverlay.ts +315 -0
- package/src/ui/promptEditor.ts +87 -0
- package/src/ui/rosterEditor.ts +310 -0
- package/src/ui/shimmer.ts +77 -0
- package/src/ui/toolActivity.ts +35 -0
- package/src/ui/twoPaneModelThinking.ts +272 -0
- package/src/ui/verificationFindingsOverlay.ts +137 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import type { ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Key, matchesKey, visibleWidth, type Component, type TUI } from "@earendil-works/pi-tui";
|
|
3
|
+
import { loadMoaConfig, type MoaConfig } from "../config/settings.ts";
|
|
4
|
+
import { modelRefLabel, type ModelRef, type ThinkingLevel } from "../shared/modelRefs.ts";
|
|
5
|
+
import {
|
|
6
|
+
SELECTOR_POINTER,
|
|
7
|
+
SQUARE_SINGLE_BOX,
|
|
8
|
+
UNSELECTED_POINTER,
|
|
9
|
+
createFrame,
|
|
10
|
+
ratioViewport,
|
|
11
|
+
} from "./chrome.ts";
|
|
12
|
+
import { getAvailableModelRefs } from "./moaModelPicker.ts";
|
|
13
|
+
import { smartTruncateModelLabel } from "./modelLabel.ts";
|
|
14
|
+
import { TwoPaneModelThinking } from "./twoPaneModelThinking.ts";
|
|
15
|
+
|
|
16
|
+
const HINT_BASE = "type filters models • ↑↓ navigate pane • tab panes/buttons • ←→ switch/select • enter select";
|
|
17
|
+
|
|
18
|
+
export const MAX_DEBATE_MODELS = 5;
|
|
19
|
+
export const MIN_DEBATE_MODELS = 2;
|
|
20
|
+
export const MIN_DEBATE_ROUNDS = 2;
|
|
21
|
+
export const MAX_DEBATE_ROUNDS = 5;
|
|
22
|
+
export const ROUNDS_ROW = MAX_DEBATE_MODELS;
|
|
23
|
+
export const START_ROW = MAX_DEBATE_MODELS + 1;
|
|
24
|
+
export const OVERVIEW_ROW_COUNT = MAX_DEBATE_MODELS + 2;
|
|
25
|
+
const OVERVIEW_SCREEN = 0;
|
|
26
|
+
const SLOT_TITLES = ["Debater 1", "Debater 2", "Debater 3", "Debater 4", "Debater 5"];
|
|
27
|
+
|
|
28
|
+
export interface DebatePickerResult {
|
|
29
|
+
models: ModelRef[];
|
|
30
|
+
thinking: (ThinkingLevel | undefined)[];
|
|
31
|
+
thinkingSelections: Record<string, ThinkingLevel>;
|
|
32
|
+
rounds: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class DebateModelPickerComponent implements Component {
|
|
36
|
+
private screen = OVERVIEW_SCREEN;
|
|
37
|
+
private overviewIndex = 0;
|
|
38
|
+
private rounds: number;
|
|
39
|
+
private readonly refs: (ModelRef | undefined)[] = Array.from({ length: MAX_DEBATE_MODELS }, () => undefined);
|
|
40
|
+
private readonly thinking: (ThinkingLevel | undefined)[] = Array.from({ length: MAX_DEBATE_MODELS }, () => undefined);
|
|
41
|
+
private readonly thinkingSelections: Record<string, ThinkingLevel> = {};
|
|
42
|
+
private readonly tui: TUI;
|
|
43
|
+
private readonly theme: Theme;
|
|
44
|
+
private readonly defaults: (ModelRef | undefined)[];
|
|
45
|
+
private readonly done: (result: DebatePickerResult | undefined) => void;
|
|
46
|
+
private readonly twoPane: TwoPaneModelThinking;
|
|
47
|
+
|
|
48
|
+
constructor(
|
|
49
|
+
tui: TUI,
|
|
50
|
+
theme: Theme,
|
|
51
|
+
availableRefs: ModelRef[],
|
|
52
|
+
defaults: (ModelRef | undefined)[],
|
|
53
|
+
config: MoaConfig,
|
|
54
|
+
currentThinking: ThinkingLevel,
|
|
55
|
+
ctx: ExtensionContext,
|
|
56
|
+
done: (result: DebatePickerResult | undefined) => void,
|
|
57
|
+
) {
|
|
58
|
+
this.tui = tui;
|
|
59
|
+
this.theme = theme;
|
|
60
|
+
this.defaults = defaults;
|
|
61
|
+
this.done = done;
|
|
62
|
+
this.rounds = Math.min(MAX_DEBATE_ROUNDS, Math.max(MIN_DEBATE_ROUNDS, config.debateRounds));
|
|
63
|
+
this.twoPane = new TwoPaneModelThinking(tui, theme, availableRefs, config, currentThinking, ctx);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private assignedCount(): number {
|
|
67
|
+
return this.refs.reduce((count, ref) => count + (ref ? 1 : 0), 0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private isReady(): boolean {
|
|
71
|
+
return this.assignedCount() >= MIN_DEBATE_MODELS;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private openSlot(slot: number): void {
|
|
75
|
+
const committed = this.refs[slot];
|
|
76
|
+
if (committed) this.twoPane.reset(committed, this.thinking[slot]);
|
|
77
|
+
else this.twoPane.reset(this.defaults[slot]);
|
|
78
|
+
this.screen = slot + 1;
|
|
79
|
+
this.tui.requestRender();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private commitSlot(selection: { ref: ModelRef; thinking: ThinkingLevel }): void {
|
|
83
|
+
const slot = this.screen - 1;
|
|
84
|
+
this.refs[slot] = selection.ref;
|
|
85
|
+
this.thinking[slot] = selection.thinking;
|
|
86
|
+
this.thinkingSelections[modelRefLabel(selection.ref)] = selection.thinking;
|
|
87
|
+
this.screen = OVERVIEW_SCREEN;
|
|
88
|
+
this.tui.requestRender();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
finish(): void {
|
|
92
|
+
if (!this.isReady()) return;
|
|
93
|
+
const models: ModelRef[] = [];
|
|
94
|
+
const thinking: (ThinkingLevel | undefined)[] = [];
|
|
95
|
+
for (let index = 0; index < MAX_DEBATE_MODELS; index++) {
|
|
96
|
+
const ref = this.refs[index];
|
|
97
|
+
if (!ref) continue;
|
|
98
|
+
models.push(ref);
|
|
99
|
+
thinking.push(this.thinking[index]);
|
|
100
|
+
}
|
|
101
|
+
this.done({ models, thinking, thinkingSelections: { ...this.thinkingSelections }, rounds: this.rounds });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
handleInput(data: string): void {
|
|
105
|
+
if (this.screen === OVERVIEW_SCREEN) {
|
|
106
|
+
if (this.overviewIndex === ROUNDS_ROW && matchesKey(data, Key.left)) {
|
|
107
|
+
this.rounds = Math.max(MIN_DEBATE_ROUNDS, this.rounds - 1);
|
|
108
|
+
this.tui.requestRender();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (this.overviewIndex === ROUNDS_ROW && matchesKey(data, Key.right)) {
|
|
112
|
+
this.rounds = Math.min(MAX_DEBATE_ROUNDS, this.rounds + 1);
|
|
113
|
+
this.tui.requestRender();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (matchesKey(data, Key.up)) {
|
|
117
|
+
if (this.overviewIndex > 0) {
|
|
118
|
+
this.overviewIndex--;
|
|
119
|
+
this.tui.requestRender();
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (matchesKey(data, Key.down)) {
|
|
124
|
+
if (this.overviewIndex < OVERVIEW_ROW_COUNT - 1) {
|
|
125
|
+
this.overviewIndex++;
|
|
126
|
+
this.tui.requestRender();
|
|
127
|
+
}
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (matchesKey(data, Key.enter)) {
|
|
131
|
+
if (this.overviewIndex < ROUNDS_ROW) this.openSlot(this.overviewIndex);
|
|
132
|
+
else if (this.overviewIndex === START_ROW) this.finish();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (matchesKey(data, Key.escape)) this.done(undefined);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const action = this.twoPane.handleInput(data);
|
|
140
|
+
if (action === "confirm") this.commitSlot(this.twoPane.getSelected());
|
|
141
|
+
else if (action === "back") {
|
|
142
|
+
this.screen = OVERVIEW_SCREEN;
|
|
143
|
+
this.tui.requestRender();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
render(width: number): string[] {
|
|
148
|
+
const th = this.theme;
|
|
149
|
+
const innerWidth = Math.max(20, width - 4);
|
|
150
|
+
const frame = createFrame(th, innerWidth, {
|
|
151
|
+
glyphs: SQUARE_SINGLE_BOX,
|
|
152
|
+
horizontalPadding: 0,
|
|
153
|
+
truncationMark: "...",
|
|
154
|
+
padToWidth: true,
|
|
155
|
+
minimumBodyWidth: 10,
|
|
156
|
+
});
|
|
157
|
+
const bodyWidth = frame.bodyWidth;
|
|
158
|
+
const row = (content: string) => frame.row(` ${content}`);
|
|
159
|
+
const viewport = ratioViewport(process.stdout.rows, {
|
|
160
|
+
fallbackRows: 24,
|
|
161
|
+
ratio: 0.7,
|
|
162
|
+
minimum: 6,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (this.screen === OVERVIEW_SCREEN) {
|
|
166
|
+
const ready = this.isReady();
|
|
167
|
+
const remaining = MIN_DEBATE_MODELS - this.assignedCount();
|
|
168
|
+
const slotRow = (label: string, detail: string, index: number, disabled = false) => {
|
|
169
|
+
const active = index === this.overviewIndex;
|
|
170
|
+
const pointer = active ? th.fg("accent", SELECTOR_POINTER) : UNSELECTED_POINTER;
|
|
171
|
+
const labelText = disabled
|
|
172
|
+
? (active ? th.bold(th.fg("muted", label)) : th.fg("dim", label))
|
|
173
|
+
: (active ? th.bold(th.fg("accent", label)) : th.bold(label));
|
|
174
|
+
return frame.row(` ${pointer}${labelText}${detail ? ` ${th.fg("muted", detail)}` : ""}`);
|
|
175
|
+
};
|
|
176
|
+
const detail = (label: string, ref: ModelRef | undefined, thinking: ThinkingLevel | undefined) => {
|
|
177
|
+
if (!ref) return "(none)";
|
|
178
|
+
const suffix = ` · thinking: ${thinking ?? "—"}`;
|
|
179
|
+
const modelWidth = Math.max(4, bodyWidth - 5 - visibleWidth(label) - visibleWidth(suffix));
|
|
180
|
+
return `${smartTruncateModelLabel(modelRefLabel(ref), modelWidth)}${suffix}`;
|
|
181
|
+
};
|
|
182
|
+
const slotRows = this.refs.map((ref, index) => {
|
|
183
|
+
const label = SLOT_TITLES[index] ?? `Debater ${index + 1}`;
|
|
184
|
+
return slotRow(label, detail(label, ref, this.thinking[index]), index);
|
|
185
|
+
});
|
|
186
|
+
const roundsRow = slotRow(
|
|
187
|
+
"Rounds",
|
|
188
|
+
`${this.rounds} (←/→ to adjust)`,
|
|
189
|
+
ROUNDS_ROW,
|
|
190
|
+
);
|
|
191
|
+
const action = slotRow(
|
|
192
|
+
"Start debate",
|
|
193
|
+
ready ? "" : `needs ${remaining} more debating model${remaining === 1 ? "" : "s"}`,
|
|
194
|
+
START_ROW,
|
|
195
|
+
!ready,
|
|
196
|
+
);
|
|
197
|
+
const fullRows = [...slotRows, roundsRow, row(""), action];
|
|
198
|
+
const lines = [
|
|
199
|
+
frame.top(),
|
|
200
|
+
row(th.fg("accent", "Select Debating Models")),
|
|
201
|
+
frame.separator(),
|
|
202
|
+
...fullRows,
|
|
203
|
+
frame.separator(),
|
|
204
|
+
row(th.fg("dim", "↑↓ navigate • enter select • esc cancel")),
|
|
205
|
+
frame.bottom(),
|
|
206
|
+
];
|
|
207
|
+
if (lines.length <= viewport) return lines;
|
|
208
|
+
const maxRows = Math.max(1, viewport - 4);
|
|
209
|
+
const compactRows = [...slotRows, roundsRow, action];
|
|
210
|
+
const start = Math.max(0, Math.min(this.overviewIndex - Math.floor(maxRows / 2), compactRows.length - maxRows));
|
|
211
|
+
return [
|
|
212
|
+
frame.top(),
|
|
213
|
+
row(th.fg("accent", "Select Debating Models")),
|
|
214
|
+
frame.separator(),
|
|
215
|
+
...compactRows.slice(start, start + maxRows),
|
|
216
|
+
frame.bottom(),
|
|
217
|
+
].slice(0, viewport);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const slot = this.screen - 1;
|
|
221
|
+
const { actionRow, hintRows } = this.twoPane.renderFooter(bodyWidth, `${HINT_BASE} • esc back`);
|
|
222
|
+
this.twoPane.setMaxVisibleRows(Math.max(1, viewport - 8 - (hintRows.length - 1)));
|
|
223
|
+
const pane = this.twoPane.render(bodyWidth).map((line) => frame.row(line));
|
|
224
|
+
const lines = [
|
|
225
|
+
frame.top(),
|
|
226
|
+
row(th.fg("accent", `Debater ${slot + 1} (${slot + 1}/${MAX_DEBATE_MODELS})`)),
|
|
227
|
+
frame.separator(),
|
|
228
|
+
...pane,
|
|
229
|
+
frame.separator(),
|
|
230
|
+
frame.row(actionRow),
|
|
231
|
+
frame.separator(),
|
|
232
|
+
...hintRows.map((line) => frame.row(line)),
|
|
233
|
+
frame.bottom(),
|
|
234
|
+
];
|
|
235
|
+
if (lines.length <= viewport) return lines;
|
|
236
|
+
return [frame.top(), row(th.fg("accent", `Debater ${slot + 1}`)), ...pane.slice(0, Math.max(1, viewport - 4)), frame.row(actionRow), frame.bottom()].slice(0, viewport);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
invalidate(): void {
|
|
240
|
+
this.twoPane.invalidate();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export async function showDebateModelPicker(
|
|
245
|
+
ctx: ExtensionContext,
|
|
246
|
+
currentThinking: ThinkingLevel,
|
|
247
|
+
): Promise<DebatePickerResult | undefined> {
|
|
248
|
+
if (!ctx.hasUI) return undefined;
|
|
249
|
+
const available = getAvailableModelRefs(ctx);
|
|
250
|
+
if (available.length === 0) return undefined;
|
|
251
|
+
|
|
252
|
+
const saved = loadMoaConfig();
|
|
253
|
+
const current = ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined;
|
|
254
|
+
const fallback = current ?? saved.debateModels[0];
|
|
255
|
+
const defaults = Array.from({ length: MAX_DEBATE_MODELS }, (_, index) =>
|
|
256
|
+
saved.debateModels[index] ?? fallback,
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
return await ctx.ui.custom<DebatePickerResult | undefined>(
|
|
260
|
+
(tui, theme, _keybindings, done) =>
|
|
261
|
+
new DebateModelPickerComponent(tui, theme, available, defaults, saved, currentThinking, ctx, done),
|
|
262
|
+
{
|
|
263
|
+
overlay: true,
|
|
264
|
+
overlayOptions: {
|
|
265
|
+
anchor: "center",
|
|
266
|
+
width: "82%",
|
|
267
|
+
minWidth: 64,
|
|
268
|
+
maxHeight: "70%",
|
|
269
|
+
margin: 1,
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
);
|
|
273
|
+
}
|