@underactive/pi-topping-moa-fusion 0.2.0 → 0.3.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 +20 -0
- package/README.md +23 -4
- package/package.json +5 -5
- package/src/debate/debateFanout.ts +2 -2
- package/src/index.ts +13 -2
- package/src/moa/fanoutWiring.ts +2 -2
- package/src/moa/verification.ts +5 -5
- package/src/planning/contextEdits.ts +62 -0
- package/src/planning/planFile.ts +30 -13
- package/src/planning/planMode.ts +98 -6
- package/src/planning/tools/shared.ts +1 -0
- package/src/runtime/results.ts +18 -0
- package/src/spec/runSpec.ts +343 -0
- package/src/spec/specContract.ts +324 -0
- package/src/ui/moaModelPicker.ts +21 -3
- package/src/ui/moaProgressWidget.ts +55 -4
- package/src/ui/moaSetupOverlay.ts +9 -3
- package/src/ui/planReviewOverlay.ts +16 -8
- package/src/ui/rosterEditor.ts +18 -12
- package/src/ui/workingOverlay.ts +2 -2
package/src/ui/moaModelPicker.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* starting unassigned at `(none)`, plus a Load Roster row that applies a saved
|
|
9
9
|
* roster to every slot wholesale, and a Start fan-out action. Enter on a slot
|
|
10
10
|
* opens its two-pane model/thinking picker; confirming writes only that slot
|
|
11
|
-
* and returns to the overview, cancelling returns without changing it.
|
|
11
|
+
* and returns to the overview, cancelling returns without changing it.
|
|
12
|
+
* Backspace or Delete on a slot clears it back to `(none)`. The
|
|
12
13
|
* overview's "Start fan-out" action stays visually disabled until at least two
|
|
13
14
|
* proposer slots and all three required roles are assigned, then it finishes
|
|
14
15
|
* the picker.
|
|
@@ -213,7 +214,7 @@ class MoaModelPickerComponent implements Component {
|
|
|
213
214
|
return undefined;
|
|
214
215
|
}
|
|
215
216
|
|
|
216
|
-
private setRole(role: "synthesizer" | "implementer" | "verifier", ref: ModelRef, thinking: ThinkingLevel): void {
|
|
217
|
+
private setRole(role: "synthesizer" | "implementer" | "verifier", ref: ModelRef | undefined, thinking: ThinkingLevel | undefined): void {
|
|
217
218
|
if (role === "synthesizer") { this.synthesizerRef = ref; this.synthesizerThinking = thinking; }
|
|
218
219
|
else if (role === "implementer") { this.implementerRef = ref; this.implementerThinking = thinking; }
|
|
219
220
|
else { this.verifierRef = ref; this.verifierThinking = thinking; }
|
|
@@ -240,6 +241,18 @@ class MoaModelPickerComponent implements Component {
|
|
|
240
241
|
this.tui.requestRender();
|
|
241
242
|
}
|
|
242
243
|
|
|
244
|
+
/** Return a slot to `(none)`; the overview cursor stays on its row. */
|
|
245
|
+
private clearSlot(screen: number): void {
|
|
246
|
+
const role = this.nonProposerRole(screen);
|
|
247
|
+
if (role) {
|
|
248
|
+
this.setRole(role, undefined, undefined);
|
|
249
|
+
} else {
|
|
250
|
+
this.proposerRefs[screen - 1] = undefined;
|
|
251
|
+
this.proposerThinking[screen - 1] = undefined;
|
|
252
|
+
}
|
|
253
|
+
this.tui.requestRender();
|
|
254
|
+
}
|
|
255
|
+
|
|
243
256
|
/** Count of proposer slots holding an explicit assignment. */
|
|
244
257
|
private assignedProposerCount(): number {
|
|
245
258
|
return this.proposerRefs.reduce((n, ref) => (ref ? n + 1 : n), 0);
|
|
@@ -429,6 +442,11 @@ class MoaModelPickerComponent implements Component {
|
|
|
429
442
|
// A disabled Start swallows Enter with no state change.
|
|
430
443
|
return;
|
|
431
444
|
}
|
|
445
|
+
if (matchesKey(data, Key.backspace) || matchesKey(data, Key.delete)) {
|
|
446
|
+
// Load Roster and Start have nothing to clear; every other row is a slot.
|
|
447
|
+
if (this.confirmIndex !== LOAD_ROW && this.confirmIndex !== START_ROW) this.clearSlot(this.confirmIndex);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
432
450
|
if (matchesKey(data, Key.escape)) {
|
|
433
451
|
// Back to the mode chooser; assignments are preserved.
|
|
434
452
|
this.screen = 0;
|
|
@@ -604,7 +622,7 @@ class MoaModelPickerComponent implements Component {
|
|
|
604
622
|
sepBorder,
|
|
605
623
|
...reviewRows,
|
|
606
624
|
sepBorder,
|
|
607
|
-
row(th.fg("dim", "\u2191\u2193 navigate \u2022 enter select \u2022 esc back")),
|
|
625
|
+
row(th.fg("dim", "\u2191\u2193 navigate \u2022 enter select \u2022 \u232B clear \u2022 esc back")),
|
|
608
626
|
botBorder,
|
|
609
627
|
];
|
|
610
628
|
if (lines.length <= viewport) return lines;
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
import type { Message } from "@earendil-works/pi-ai";
|
|
29
29
|
import type { ExtensionContext, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
30
|
-
import { visibleWidth, type Component, type TUI } from "@earendil-works/pi-tui";
|
|
30
|
+
import { visibleWidth, wrapTextWithAnsi, type Component, type TUI } from "@earendil-works/pi-tui";
|
|
31
31
|
import { ACTIVITY_METER_WIDTH, ActivityMeter, rateToLevel, TokRateTracker } from "../activityMeter.ts";
|
|
32
32
|
import { modelRefLabel, shortModelName, type ModelRef, type ThinkingLevel } from "../shared/modelRefs.ts";
|
|
33
33
|
import { contextPercent, CTX_COL_WIDTH, formatCost, formatElapsed, formatTokens } from "./agentStatus.ts";
|
|
@@ -122,6 +122,14 @@ const AGENT_COL_MIN = 8;
|
|
|
122
122
|
/** Agent width below which a model ref stops being distinguishable from its siblings. */
|
|
123
123
|
const AGENT_COL_READABLE = 24;
|
|
124
124
|
const ACTIVITY_COL_MIN = 10;
|
|
125
|
+
/** Gutter marking a sub-row as belonging to the agent row above it. */
|
|
126
|
+
const SUB_ROW_GUTTER = "↳ ";
|
|
127
|
+
/**
|
|
128
|
+
* Most wrapped lines one failed row may spend on its error detail. A provider
|
|
129
|
+
* error fits well inside this at any usable width; the cap only bites on a
|
|
130
|
+
* pathological message, which must not swallow the whole table.
|
|
131
|
+
*/
|
|
132
|
+
const ERROR_DETAIL_LINES = 8;
|
|
125
133
|
const PREVIEW_LINES = 4;
|
|
126
134
|
const PREVIEW_INDENT = STATUS_COL_WIDTH + 2;
|
|
127
135
|
const PREVIEW_GUTTER = "│ ";
|
|
@@ -819,10 +827,24 @@ export class MoaProgressTableComponent implements Component {
|
|
|
819
827
|
0,
|
|
820
828
|
this.rowBudget() - TABLE_CHROME_ROWS - rows.length - phaseSectionRows(rows) - (band.length > 0 ? PHASE_BAND_ROWS : 0),
|
|
821
829
|
);
|
|
830
|
+
// A failed row's message is the one status a reader has to act on, and it
|
|
831
|
+
// routinely outruns the ACTIVITY cell, so its wrapped detail claims budget
|
|
832
|
+
// ahead of live activity and previews. Failed rows are settled, so they
|
|
833
|
+
// compete for nothing else.
|
|
834
|
+
const subRowWidth = Math.max(0, bodyWidth - STATUS_COL_WIDTH - visibleWidth(SUB_ROW_GUTTER));
|
|
835
|
+
const errorDetails = new Map<number, string[]>();
|
|
836
|
+
let remaining = free;
|
|
837
|
+
for (let i = 0; i < rows.length && remaining > 0; i++) {
|
|
838
|
+
const detail = this.errorDetailLines(rows[i], cols.activity, subRowWidth, Math.min(ERROR_DETAIL_LINES, remaining));
|
|
839
|
+
if (detail.length === 0) continue;
|
|
840
|
+
errorDetails.set(i, detail);
|
|
841
|
+
remaining -= detail.length;
|
|
842
|
+
}
|
|
843
|
+
|
|
822
844
|
const activeIndices = rows.flatMap((r, index) => isActive(r.state) ? [index] : []);
|
|
823
845
|
const activityIndices = activeIndices.filter((index) => rows[index]?.activity);
|
|
824
|
-
const renderedActivityIndices = new Set(activityIndices.slice(0,
|
|
825
|
-
|
|
846
|
+
const renderedActivityIndices = new Set(activityIndices.slice(0, remaining));
|
|
847
|
+
remaining = Math.max(0, remaining - renderedActivityIndices.size);
|
|
826
848
|
|
|
827
849
|
const previewWidth = Math.max(0, leftTableWidth - PREVIEW_INDENT - visibleWidth(PREVIEW_GUTTER));
|
|
828
850
|
const previewIndices = this.view.previewVisible && previewWidth >= PREVIEW_MIN_WIDTH
|
|
@@ -895,6 +917,9 @@ export class MoaProgressTableComponent implements Component {
|
|
|
895
917
|
),
|
|
896
918
|
);
|
|
897
919
|
|
|
920
|
+
const errorDetail = errorDetails.get(i) ?? [];
|
|
921
|
+
for (const [offset, text] of errorDetail.entries()) lines.push(row(this.errorDetailRow(text, offset === 0, subRowWidth)));
|
|
922
|
+
|
|
898
923
|
if (renderedActivityIndices.has(i)) lines.push(row(this.activitySubRow(r, bodyWidth)));
|
|
899
924
|
else if (reservedActivityIndices.has(i)) lines.push(row(""));
|
|
900
925
|
|
|
@@ -932,11 +957,37 @@ export class MoaProgressTableComponent implements Component {
|
|
|
932
957
|
/** Merged-cell tool activity line, spanning everything right of the status icon. */
|
|
933
958
|
private activitySubRow(r: ProgressRow, bodyWidth: number): string {
|
|
934
959
|
const room = Math.max(0, bodyWidth - STATUS_COL_WIDTH);
|
|
935
|
-
const gutter = this.theme.fg("dim",
|
|
960
|
+
const gutter = this.theme.fg("dim", SUB_ROW_GUTTER);
|
|
936
961
|
const activity = highlightActivity(this.theme, r.activity ?? "");
|
|
937
962
|
return `${" ".repeat(STATUS_COL_WIDTH)}${cell(`${gutter}${activity}`, room)}`;
|
|
938
963
|
}
|
|
939
964
|
|
|
965
|
+
/**
|
|
966
|
+
* A failed row's message re-laid across the table body, or nothing when the
|
|
967
|
+
* ACTIVITY cell already shows it whole. Provider failures arrive as one long
|
|
968
|
+
* JSON payload whose cause sits well past the column edge, so the text is
|
|
969
|
+
* wrapped — breaking the payload mid-token when it has no spaces to break on
|
|
970
|
+
* — rather than cut off at a width the reader cannot change.
|
|
971
|
+
*/
|
|
972
|
+
private errorDetailLines(row: ProgressRow, activityWidth: number, width: number, maxLines: number): string[] {
|
|
973
|
+
if (row.state !== "error" || !row.statusText) return [];
|
|
974
|
+
if (width <= 0 || maxLines <= 0) return [];
|
|
975
|
+
if (visibleWidth(row.statusText) <= activityWidth) return [];
|
|
976
|
+
const wrapped = wrapTextWithAnsi(row.statusText, width);
|
|
977
|
+
if (wrapped.length <= maxLines) return wrapped;
|
|
978
|
+
// Mark the cut, so a clipped payload never reads as the whole message.
|
|
979
|
+
const shown = wrapped.slice(0, maxLines);
|
|
980
|
+
shown[maxLines - 1] = fitVisible(`${shown[maxLines - 1]}…`, width, { truncationMark: "…", padToWidth: false });
|
|
981
|
+
return shown;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/** One wrapped error line: the gutter carries the first, the rest hang under it. */
|
|
985
|
+
private errorDetailRow(text: string, first: boolean, width: number): string {
|
|
986
|
+
const gutter = first ? this.theme.fg("dim", SUB_ROW_GUTTER) : " ".repeat(visibleWidth(SUB_ROW_GUTTER));
|
|
987
|
+
const detail = fitVisible(text, width, { truncationMark: "…", padToWidth: false });
|
|
988
|
+
return `${" ".repeat(STATUS_COL_WIDTH)}${gutter}${this.theme.fg("error", detail)}`;
|
|
989
|
+
}
|
|
990
|
+
|
|
940
991
|
private topBorder(innerWidth: number, border: (s: string) => string): string {
|
|
941
992
|
const title = ` ${this.view.title} `;
|
|
942
993
|
const head = `${border("══")}${this.theme.fg("accent", title)}`;
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Two kinds of slot are configured here. Planning-subagent slots persist into
|
|
10
10
|
* the frontmatter of the installed agent files; MoA fan-out roles are no
|
|
11
|
-
* longer set per role here — they live in named rosters
|
|
12
|
-
*
|
|
11
|
+
* longer set per role here — they live in named rosters whose completed edits
|
|
12
|
+
* are persisted immediately by the roster manager.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import type { ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
@@ -361,7 +361,13 @@ export async function showMoaSetup(
|
|
|
361
361
|
draft.useSummaryName = result.draft.useSummaryName;
|
|
362
362
|
draft.maxConcurrentAgents = result.draft.maxConcurrentAgents;
|
|
363
363
|
draft.maxVerificationRepairs = result.draft.maxVerificationRepairs;
|
|
364
|
-
draft.rosters = await showRosterManager(ctx, draft.rosters, {
|
|
364
|
+
draft.rosters = await showRosterManager(ctx, draft.rosters, {
|
|
365
|
+
currentThinking,
|
|
366
|
+
onChange: (rosters) => {
|
|
367
|
+
const current = loadMoaConfig();
|
|
368
|
+
saveMoaConfig({ ...current, rosters });
|
|
369
|
+
},
|
|
370
|
+
});
|
|
365
371
|
continue;
|
|
366
372
|
}
|
|
367
373
|
break;
|
|
@@ -71,16 +71,15 @@ function formatProposersList(proposers: ModelRef[]): string {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
function formatSplitRow(left: string, right: string | undefined, bodyWidth: number): string {
|
|
74
|
-
const leftText = left ?? "";
|
|
75
74
|
if (!right) {
|
|
76
|
-
return padRow(
|
|
75
|
+
return padRow(left, bodyWidth);
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
const minLeft = Math.min(visibleWidth(
|
|
78
|
+
const minLeft = Math.min(visibleWidth(left), 18);
|
|
80
79
|
const maxRight = Math.max(1, bodyWidth - minLeft);
|
|
81
80
|
const rightPart = truncateToWidth(right, maxRight, "…", false);
|
|
82
81
|
const rightVis = visibleWidth(rightPart);
|
|
83
|
-
const leftPart = truncateToWidth(
|
|
82
|
+
const leftPart = truncateToWidth(left, Math.max(1, bodyWidth - rightVis), "…", false);
|
|
84
83
|
const gap = bodyWidth - visibleWidth(leftPart) - visibleWidth(rightPart);
|
|
85
84
|
const row = gap > 0 ? leftPart + " ".repeat(gap) + rightPart : leftPart + rightPart;
|
|
86
85
|
return padRow(row, bodyWidth);
|
|
@@ -92,15 +91,23 @@ function formatRightRow(content: string, bodyWidth: number): string {
|
|
|
92
91
|
return padRow(gap > 0 ? " ".repeat(gap) + truncated : truncated, bodyWidth);
|
|
93
92
|
}
|
|
94
93
|
|
|
94
|
+
export interface PlanReviewLabels {
|
|
95
|
+
title?: string;
|
|
96
|
+
approveHint?: string;
|
|
97
|
+
keepHint?: string;
|
|
98
|
+
chatHint?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
95
101
|
export async function showPlanReview(
|
|
96
102
|
ctx: ExtensionContext,
|
|
97
103
|
planMarkdown: string,
|
|
98
104
|
moaInfo?: MfPlanInfo,
|
|
99
105
|
planName?: string,
|
|
100
106
|
allowChat = false,
|
|
107
|
+
labels?: PlanReviewLabels,
|
|
101
108
|
): Promise<PlanReviewDecision> {
|
|
102
109
|
const result = await ctx.ui.custom<PlanReviewDecision>(
|
|
103
|
-
(tui, theme, _keybindings, done) => new PlanReviewOverlay(tui, theme, planMarkdown, moaInfo, ctx.cwd, planName, allowChat, done),
|
|
110
|
+
(tui, theme, _keybindings, done) => new PlanReviewOverlay(tui, theme, planMarkdown, moaInfo, ctx.cwd, planName, allowChat, labels, done),
|
|
104
111
|
{
|
|
105
112
|
overlay: true,
|
|
106
113
|
overlayOptions: {
|
|
@@ -136,6 +143,7 @@ class PlanReviewOverlay implements Component {
|
|
|
136
143
|
private readonly repoCwd: string,
|
|
137
144
|
private readonly planName: string | undefined,
|
|
138
145
|
private readonly allowChat: boolean,
|
|
146
|
+
private readonly labels: PlanReviewLabels | undefined,
|
|
139
147
|
private readonly done: (decision: PlanReviewDecision) => void,
|
|
140
148
|
) {
|
|
141
149
|
this.markdown = new Markdown(stripTerminalSequences(planMarkdown), 0, 0, getMarkdownTheme());
|
|
@@ -234,9 +242,9 @@ class PlanReviewOverlay implements Component {
|
|
|
234
242
|
.join(",");
|
|
235
243
|
const proposalHelp = helpParts ? ` · ${helpParts} proposer plans · \`/0 synthesized` : "";
|
|
236
244
|
const verdictsHelp = this.verdictsMarkdown ? " · v verdicts" : "";
|
|
237
|
-
const chatHelp = this.allowChat ?
|
|
245
|
+
const chatHelp = this.allowChat ? ` · c ${this.labels?.chatHint ?? "chat"}` : "";
|
|
238
246
|
const helpLines = wrapDimLine(
|
|
239
|
-
`↑/↓ or j/k scroll · u/d page${proposalHelp}${verdictsHelp} · e edit${chatHelp} · a/Enter approve · q/Esc keep planning`,
|
|
247
|
+
`↑/↓ or j/k scroll · u/d page${proposalHelp}${verdictsHelp} · e edit${chatHelp} · a/Enter ${this.labels?.approveHint ?? "approve"} · q/Esc ${this.labels?.keepHint ?? "keep planning"}`,
|
|
240
248
|
bodyWidth,
|
|
241
249
|
);
|
|
242
250
|
this.helpLineCount = helpLines.length;
|
|
@@ -261,7 +269,7 @@ class PlanReviewOverlay implements Component {
|
|
|
261
269
|
const planTitle = showingVerdicts
|
|
262
270
|
? "Proposer Verdicts"
|
|
263
271
|
: this.activeProposerIndex === undefined
|
|
264
|
-
? "Proposed Plan"
|
|
272
|
+
? this.labels?.title ?? "Proposed Plan"
|
|
265
273
|
: `Proposer ${this.activeProposerIndex + 1} Plan`;
|
|
266
274
|
const proposerModel = this.activeProposerIndex === undefined
|
|
267
275
|
? undefined
|
package/src/ui/rosterEditor.ts
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A staged CRUD surface over the saved `PlanRoster` list: list → name prompt →
|
|
5
5
|
* slot editor → per-slot model/thinking picker, ported from persona-audit's
|
|
6
|
-
* RosterEditor.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* RosterEditor. Draft slot changes remain in memory until Save roster is
|
|
7
|
+
* chosen; completed roster saves and confirmed deletions are then persisted
|
|
8
|
+
* immediately through the manager's onChange callback.
|
|
9
9
|
*
|
|
10
10
|
* Each roster carries one model + thinking level for every fan-out role —
|
|
11
11
|
* 2–5 proposers plus synthesizer, implementer, and verifier. A roster must be
|
|
@@ -31,8 +31,6 @@ import { ratioViewport } from "./chrome.ts";
|
|
|
31
31
|
import { MenuComponent, showOverlayPrompt, type ActionMenuItem } from "./menu.ts";
|
|
32
32
|
import { showModelThinkingPicker } from "./moaModelPicker.ts";
|
|
33
33
|
|
|
34
|
-
const BACKSPACE = "\x7f";
|
|
35
|
-
|
|
36
34
|
type SlotRole = "synthesizer" | "implementer" | "verifier";
|
|
37
35
|
type SlotTarget = { kind: "proposer"; index: number } | { kind: "role"; role: SlotRole };
|
|
38
36
|
type ListResult = { action: "create" } | { action: "edit"; index: number } | { action: "back" };
|
|
@@ -198,7 +196,7 @@ function showSlotEditor(ctx: ExtensionContext, draft: DraftRoster, focusItemId:
|
|
|
198
196
|
{ title: "roles", items: roleItems },
|
|
199
197
|
],
|
|
200
198
|
onItemKey: (item, data) => {
|
|
201
|
-
if (data
|
|
199
|
+
if (!matchesKey(data, Key.backspace) && !matchesKey(data, Key.delete)) return false;
|
|
202
200
|
const target = parseTargetItemId(item.id);
|
|
203
201
|
if (!target) return true;
|
|
204
202
|
if (target.kind === "proposer") working.proposers[target.index] = undefined;
|
|
@@ -245,7 +243,7 @@ async function editRoster(
|
|
|
245
243
|
if (result.action === "cancel") return undefined;
|
|
246
244
|
if (result.action === "save") return fromDraft(result.draft);
|
|
247
245
|
if (result.action === "delete") {
|
|
248
|
-
if (await ctx.ui.confirm("Delete agent roster?", `Delete ${draft.name}? This
|
|
246
|
+
if (await ctx.ui.confirm("Delete agent roster?", `Delete ${draft.name}? This takes effect immediately.`)) return "delete";
|
|
249
247
|
continue;
|
|
250
248
|
}
|
|
251
249
|
if (result.action === "rename") {
|
|
@@ -272,11 +270,11 @@ async function editRoster(
|
|
|
272
270
|
}
|
|
273
271
|
}
|
|
274
272
|
|
|
275
|
-
/** Manage
|
|
273
|
+
/** Manage roster drafts and publish completed saves immediately through onChange. */
|
|
276
274
|
export async function showRosterManager(
|
|
277
275
|
ctx: ExtensionContext,
|
|
278
276
|
initial: PlanRoster[],
|
|
279
|
-
deps: { currentThinking: ThinkingLevel },
|
|
277
|
+
deps: { currentThinking: ThinkingLevel; onChange?: (rosters: PlanRoster[]) => void },
|
|
280
278
|
): Promise<PlanRoster[]> {
|
|
281
279
|
const rosters = initial.map((roster) => ({ ...roster, proposers: [...roster.proposers] }));
|
|
282
280
|
for (;;) {
|
|
@@ -298,13 +296,21 @@ export async function showRosterManager(
|
|
|
298
296
|
undefined,
|
|
299
297
|
deps.currentThinking,
|
|
300
298
|
);
|
|
301
|
-
if (created && created !== "delete")
|
|
299
|
+
if (created && created !== "delete") {
|
|
300
|
+
rosters.push(created);
|
|
301
|
+
deps.onChange?.(rosters);
|
|
302
|
+
}
|
|
302
303
|
continue;
|
|
303
304
|
}
|
|
304
305
|
const current = rosters[action.index];
|
|
305
306
|
if (!current) continue;
|
|
306
307
|
const edited = await editRoster(ctx, rosters, toDraft(current), action.index, deps.currentThinking);
|
|
307
|
-
if (edited === "delete")
|
|
308
|
-
|
|
308
|
+
if (edited === "delete") {
|
|
309
|
+
rosters.splice(action.index, 1);
|
|
310
|
+
deps.onChange?.(rosters);
|
|
311
|
+
} else if (edited) {
|
|
312
|
+
rosters[action.index] = edited;
|
|
313
|
+
deps.onChange?.(rosters);
|
|
314
|
+
}
|
|
309
315
|
}
|
|
310
316
|
}
|
package/src/ui/workingOverlay.ts
CHANGED
|
@@ -150,8 +150,8 @@ export async function withWorkingOverlay<T>(
|
|
|
150
150
|
return unwrap(await settled);
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
/** Copy for the plan/opinion/debate naming wait. No trailing ellipses — see CHANGELOG 0.2.0. */
|
|
154
|
-
export function planNamingOverlay(subject: "plan" | "opinion" | "debate", skip: () => string): WorkingOverlayOptions<string> {
|
|
153
|
+
/** Copy for the plan/opinion/debate/spec naming wait. No trailing ellipses — see CHANGELOG 0.2.0. */
|
|
154
|
+
export function planNamingOverlay(subject: "plan" | "opinion" | "debate" | "spec", skip: () => string): WorkingOverlayOptions<string> {
|
|
155
155
|
return {
|
|
156
156
|
title: `Naming the ${subject}`,
|
|
157
157
|
detail: `summarizing your prompt into a short ${subject} name`,
|