@underactive/pi-topping-moa-fusion 0.1.0 → 0.2.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 +34 -0
- package/README.md +31 -8
- package/agents/moa-synthesizer.md +9 -6
- package/package.json +5 -5
- package/src/config/planName.ts +29 -24
- package/src/config/settings.ts +38 -1
- package/src/debate/debateFanout.ts +14 -4
- package/src/debate/runDebate.ts +13 -4
- package/src/index.ts +1 -1
- package/src/moa/conflictContract.ts +2 -1
- package/src/moa/contextContract.ts +1 -1
- package/src/moa/fanout.ts +115 -4
- package/src/moa/fanoutWiring.ts +11 -1
- package/src/moa/implementationRetry.ts +2 -2
- package/src/moa/orchestration.ts +3 -0
- package/src/moa/panelDependence.ts +87 -0
- package/src/moa/reviewLoop.ts +2 -2
- package/src/moa/synthesis.ts +12 -8
- package/src/moa/verification.ts +114 -17
- package/src/moa/verificationCriteria.ts +1 -1
- package/src/opinion/opinionFanout.ts +5 -4
- package/src/opinion/runOpinion.ts +14 -13
- package/src/planning/modeState.ts +17 -0
- package/src/planning/planFile.ts +2 -1
- package/src/planning/planMode.ts +46 -5
- package/src/planning/tools/exitPlanMode.ts +7 -2
- package/src/planning/tools/mfPlanSubagent.ts +3 -0
- package/src/runtime/activityTracking.ts +28 -12
- package/src/runtime/processPool.ts +2 -0
- package/src/runtime/runner.ts +45 -9
- package/src/runtime/wire.ts +11 -0
- package/src/ui/cancelOverlay.ts +1 -1
- package/src/ui/moaModelPicker.ts +11 -1
- package/src/ui/moaProgressWidget.ts +59 -8
- package/src/ui/moaSetupOverlay.ts +42 -4
- package/src/ui/workingOverlay.ts +265 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [0.2.0]
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- A `Naming the plan`/`Naming the opinion`/`Naming the debate` working overlay while the cheap/fast model summarizes a submitted prompt into its slug, so the TUI no longer sits unresponsive during that call. Press **Esc** to skip naming and fall back to a name derived from the prompt.
|
|
10
|
+
- Panel dependence note in the synthesizer input: a blinded summary of proposal count, distinct models, and same-model clusters so agreement is weighted by dependence, not vote count alone.
|
|
11
|
+
- Pre-flight and replacement-time diversity warning when proposer slots share a model or provider, reminding that agreement between them is not independent evidence.
|
|
12
|
+
- Evidence-backed vs asserted tags in synthesizer `### Proposer alignment`, plus distinct-model support counts on conflict recommendation Details lines.
|
|
13
|
+
- Configurable `maxConcurrentAgents` setting (1–8, default 1) for MoA, opinion, debate, and plan subagent fan-outs.
|
|
14
|
+
- Configurable `maxVerificationRepairs` setting (0–5, default 2) for verifier-driven implementation repairs. When repairs are exhausted or disabled, a `.pi/mf-plan/<slug>__verification-handoff.md` file is written with every criterion's status, the full frozen checklist, gaps, and guidance to continue verification manually — and its path is named in the exhaustion notification.
|
|
15
|
+
- Cancelling a single proposer from the MoA cancel overlay now prompts, once the fan-out settles, to replace that slot with a different model or continue without it, instead of silently dropping the proposal and proceeding with the survivors. **Cancel ALL** (including during a replacement run) still aborts the run.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Resuming, forking, or `--continue`-ing a session saved mid-plan no longer leaves the implementer with a read-only tool loadout after approval. The pre-plan-mode loadout is now persisted with the plan state.
|
|
20
|
+
- Subagent runs no longer log two spurious `event parse error: invalid session event` lines per run. pi 0.86.0's `system` wire messages are accepted instead of leading every failure report.
|
|
21
|
+
- Typechecking against pi 0.84.3 and newer declarations.
|
|
22
|
+
- Remove trailing ellipses from status labels.
|
|
23
|
+
- Correct progress timing and running counts for agents waiting in the concurrency queue.
|
|
24
|
+
- Show concurrency-limited fan-out agents as queued until they acquire a process-pool slot.
|
|
25
|
+
- Use success colors for positive verification notices instead of warning colors.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- Child-agent concurrency default drops from the previous hard-coded limit of 5 to **1** via the new `maxConcurrentAgents` setting.
|
|
30
|
+
- Plan naming now uses `ctx.modelRegistry.streamSimple()` instead of the temporary `pi-ai/compat` entry, so proxy and OAuth base-URL overrides are honored.
|
|
31
|
+
- The subagent `CTX` column can now advance mid-turn from `message_update` usage, so one-long-turn provider bridges that report streaming usage are no longer forced to sit at `0.0%` until the run ends. A malformed usage reading is ignored without discarding the subagent's output.
|
|
32
|
+
|
|
33
|
+
### Removed
|
|
34
|
+
|
|
35
|
+
- The unfiled `docs/upstream-issue-live-usage.md` draft for a change that has shipped upstream.
|
|
36
|
+
|
|
3
37
|
## [0.1.0]
|
|
4
38
|
|
|
5
39
|
- Initial commit
|
package/README.md
CHANGED
|
@@ -80,7 +80,7 @@ Fanning out a coding task and merging the resulting token streams does not work
|
|
|
80
80
|
5. If the proposers substantively disagreed, a conflict-review overlay opens first: one tab per conflict, plain-language options, the synthesized pick tagged **(Recommended)**, and a **Chat about this** box for free-text feedback. Your resolutions are fed back for re-synthesis. Esc cancels the whole run.
|
|
81
81
|
6. The plan opens in the same review overlay `exit_plan_mode` uses, where you can inspect any proposer's original plan, edit the synthesized one, or send the synthesizer feedback for a revised version (up to five rounds).
|
|
82
82
|
7. On approval, the synthesizer makes one read-only pass to create a frozen observable checklist at `.pi/mf-plan/<slug>__criteria.md`, then plan mode exits and implementation begins in the current session using the implementer you already picked — there is **no** second picker (the single-model flow picks its implementing model up front when you choose **Single model**, and neither flow shows a model picker after approval — one appears only via `/mf-plan-implement`, or when an MoA run is resumed without a saved roster). If implementation fails (for example, auth expiration or provider error), an automatic recovery dialog prompts to retry, switch to a different model, or continue manually. You can also run `/mf-plan-implement` anytime to resume the approved plan.
|
|
83
|
-
8. Once the implementation turn settles, the **verification phase** runs when a verifier is configured: the project's own `check` / `lint` / `test` scripts run first, then a read-only `moa-verifier` subprocess judges the working tree against the frozen approved plan and scores every frozen criterion pass/fail/cannot-verify. The overall verdict is derived from those scores. If criteria are unavailable, it falls back to judging plan steps directly. If it finds gaps (or a check fails), the repair prompt shows a short Simplified Technical English summary, then the verifier summary, unmet criteria or gaps, and failing project checks, followed in the TUI by three choices in order: *View full findings*, *Send verifier findings to the implementer* (a bounded **repair round**, up to two), and *Accept implementation as-is*. *View full findings* opens the complete verifier report in a read-only, scrollable popup and, when closed, returns to the same three choices, so viewing never consumes a repair round. A repair round sends the findings back to the implementer and re-verifies. The raw verdict is saved next to the plan as `<slug>__verification.md`. A user-cancelled implementation is never verified.
|
|
83
|
+
8. Once the implementation turn settles, the **verification phase** runs when a verifier is configured: the project's own `check` / `lint` / `test` scripts run first, then a read-only `moa-verifier` subprocess judges the working tree against the frozen approved plan and scores every frozen criterion pass/fail/cannot-verify. The overall verdict is derived from those scores. If criteria are unavailable, it falls back to judging plan steps directly. If it finds gaps (or a check fails), the repair prompt shows a short Simplified Technical English summary, then the verifier summary, unmet criteria or gaps, and failing project checks, followed in the TUI by three choices in order: *View full findings*, *Send verifier findings to the implementer* (a bounded **repair round**, up to the [max verification repairs](#max-verification-repairs) setting — two by default), and *Accept implementation as-is*. *View full findings* opens the complete verifier report in a read-only, scrollable popup and, when closed, returns to the same three choices, so viewing never consumes a repair round. A repair round sends the findings back to the implementer and re-verifies. The raw verdict is saved next to the plan as `<slug>__verification.md`. Once the repair budget is spent, a handoff file is written as `<slug>__verification-handoff.md` and named in the exhaustion notification. A user-cancelled implementation is never verified.
|
|
84
84
|
|
|
85
85
|
Before building the full verification task, MoA Fusion runs a cheap verifier preflight that asks for a one-word response. Auth, quota, model, and liveness problems therefore surface quickly with an actionable child error instead of after the full audit attempt.
|
|
86
86
|
|
|
@@ -94,8 +94,8 @@ Several safeguards target known LLM judging biases:
|
|
|
94
94
|
|
|
95
95
|
- **Identity blinding** — proposals arrive headed `### Proposal from Proposer N`; real model names never appear in its input. This counters *self-preference bias*, where a model favors output it recognizes as its own. Enforcement runs both ways: proposers must not hint at their own model name, family, or provider, and the synthesizer must not infer or emit one — including in its own output, which keeps re-synthesis rounds blind too.
|
|
96
96
|
- **Separate evaluation dimensions** — proposals are judged on correctness, completeness, feasibility, risk, and simplicity as distinct dimensions rather than one overall impression. The prompt states outright that confidence, detail, and length are not evidence of quality, guarding against *verbosity bias*.
|
|
97
|
-
- **Agreement is signal, not proof** — where proposers agree, the synthesizer still sanity-checks the shared assumption against the repo, since independent models can share the same error.
|
|
98
|
-
- **Auditable reasoning** — the plan opens with a Context section recording dimension reasoning, proposer alignment, and synthesis decisions, so recombination choices are inspectable. Reconciliation commentary is confined there; the rest reads as one coherent plan. The orchestrator verifies all three subsections are present and rejects a first plan that omits them (one corrective retry, then a warning), the same way it enforces `## Proposer Verdicts`.
|
|
97
|
+
- **Agreement is signal, not proof** — where proposers agree, the synthesizer still sanity-checks the shared assumption against the repo, since independent models can share the same error. A blinded `Panel dependence:` note in the synthesizer input names how many distinct models contributed and which slots share one model, and the pre-flight picker warns when assigned slots repeat a model or provider.
|
|
98
|
+
- **Auditable reasoning** — the plan opens with a Context section recording dimension reasoning, proposer alignment, and synthesis decisions, so recombination choices are inspectable. `### Proposer alignment` labels support as evidence-backed or asserted, and conflict recommendation Details lines include distinct-model support counts. Reconciliation commentary is confined there; the rest reads as one coherent plan. The orchestrator verifies all three subsections are present and rejects a first plan that omits them (one corrective retry, then a warning), the same way it enforces `## Proposer Verdicts`.
|
|
99
99
|
- **Disagreements go to the user** — every substantive disagreement must be emitted as a `## Conflicts` block, even when the synthesizer is confident, so its judgment is a default you can override rather than a decision made for you. Genuine ambiguity about *your intent* instead produces a single `## Open Question`, and the synthesizer is re-run with your answer.
|
|
100
100
|
|
|
101
101
|
**Protocol-internal, always current at runtime.** The proposer, synthesizer, and verifier prompts are tightly coupled to this extension's parsers and UI (e.g. the `## Conflicts` markup, and the verifier's `**Verdict:**`/`### Gaps` markup). A hand-edited copy under `~/.pi/agent/agents/` is still written for inspection, but MoA runs always use the bundled definition from the installed extension, so a protocol update can never desync from what the subprocess runs. `moa-explore` and `mf-plan` remain fully user-customizable.
|
|
@@ -160,7 +160,7 @@ During the run, `F3` opens the live observer and `Esc` or `F4` opens cancellatio
|
|
|
160
160
|
| `/mf-opinion` | — | Ask 1–5 models for independent read-only opinions about the repository |
|
|
161
161
|
| `/mf-debate` | — | Run a read-only multi-round debate between up to 5 models |
|
|
162
162
|
| `/mf-preview` | — | Toggle the inline/live preview of streamed agent output |
|
|
163
|
-
| `/mf-plan-settings` | — | Configure the explore and cheap/fast agents, named agent rosters for the MoA roles,
|
|
163
|
+
| `/mf-plan-settings` | — | Configure the explore and cheap/fast agents, named agent rosters for the MoA roles, plan options, and max concurrent agents |
|
|
164
164
|
| `/mf-plan-implement` | — | Retry implementation of the last approved MoA plan, optionally with a different model |
|
|
165
165
|
| `/mf-plan-clear` | — | Clear completed plan state so the next `/mf-plan` starts a fresh round; approved plans stay in `.pi/mf-plan/` and remain re-implementable |
|
|
166
166
|
| `--mf-plan` | — | Start pi with plan mode enabled |
|
|
@@ -172,7 +172,7 @@ On Mac laptops the top row may send brightness/media keys by default. Enable the
|
|
|
172
172
|
|
|
173
173
|
### Cancelling running subagents
|
|
174
174
|
|
|
175
|
-
During an MoA fan-out or synthesis, **Esc** opens a cancel overlay. Enter
|
|
175
|
+
During an MoA fan-out or synthesis, **Esc** opens a cancel overlay. Enter cancels the selected agent — its siblings keep going, and once the fan-out settles you are asked, for each cancelled agent, to select a replacement model or continue without it; **Cancel ALL** (including during a replacement run) aborts the run and reopens the prompt editor prefilled with your original prompt. Each agent's live tool call is highlighted the same way as the Fusion table's ACTIVITY column.
|
|
176
176
|
|
|
177
177
|
During the single-model workflow's `mf_plan_subagent` runs, plain Esc keeps pi's default abort-the-turn behavior; use **F4** to kill an individual agent instead. A cancelled agent reports "cancelled by user" back to the model, which continues with the other agents' results.
|
|
178
178
|
|
|
@@ -191,7 +191,7 @@ Both modes share the same plan-mode container. While it is active:
|
|
|
191
191
|
- **Phase 5: Exit** — call `exit_plan_mode` for user approval
|
|
192
192
|
|
|
193
193
|
This is the **single-model workflow**: the model you pick drives all five phases. [MoA](#mixture-of-agents-moa) replaces phases 1–2 with the proposer fan-out.
|
|
194
|
-
3. **Per-session plan file** — one slug per session, persisted across `/resume`.
|
|
194
|
+
3. **Per-session plan file** — one slug per session, persisted across `/resume`. Submitting a prompt shows a `Naming the plan` card while the cheap/fast model summarizes it into that slug; press **Esc** to skip straight to a name derived from the prompt instead of waiting.
|
|
195
195
|
4. **User approval** — `exit_plan_mode` prompts to approve or keep planning. Approving restores full tool access and hands the plan back to the model.
|
|
196
196
|
|
|
197
197
|
- A `● MoA Fusion (plan mode)` indicator sits in the footer while planning, and stays as `● MoA Fusion` through implementation and verification. Green: agents are working. Yellow: waiting for you (a prompt, an overlay, a questionnaire, or an idle plan-mode session). Red: a flow stopped with nothing left to try; it clears on your next turn or when you leave plan mode.
|
|
@@ -274,6 +274,26 @@ Rosters persist in `~/.pi/agent/mf-plan/settings.json`:
|
|
|
274
274
|
|
|
275
275
|
Names are 1–24 alphanumeric characters, unique case-insensitively, up to 20 rosters. The roster manager stages every edit in memory — nothing is written until the settings overlay's **Save and Close**.
|
|
276
276
|
|
|
277
|
+
#### Max concurrent agents
|
|
278
|
+
|
|
279
|
+
The **max concurrent agents** row in `/mf-plan-settings` controls how many child-agent subprocesses may run at once during MoA proposer fan-out (including retries), opinion fan-out, debate rounds (including retries), and `mf_plan_subagent` parallel mode. Use ←/→ to pick a value from 1 to 8. The setting persists in `~/.pi/agent/mf-plan/settings.json`:
|
|
280
|
+
|
|
281
|
+
```json
|
|
282
|
+
{ "maxConcurrentAgents": 1 }
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
The default is **1** — several local models running in parallel are usually unusably slow, so keep this at 1 for local agents and raise it for cloud models. Out-of-range values clamp to 1–8; malformed values fall back to 1. The limit is read once when a run starts, so edits apply to the next run rather than one already in flight.
|
|
286
|
+
|
|
287
|
+
#### Max verification repairs
|
|
288
|
+
|
|
289
|
+
The **max verification repairs** row in `/mf-plan-settings` controls how many automatic verifier-driven repair rounds a single implementation may consume after verification finds gaps. Use ←/→ to pick a value from 0 to 5. The setting persists in `~/.pi/agent/mf-plan/settings.json`:
|
|
290
|
+
|
|
291
|
+
```json
|
|
292
|
+
{ "maxVerificationRepairs": 2 }
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
The default is **2**. Set **0** to disable automatic repair offers — verification still runs, but gaps go straight to an exhaustion notification. When repairs are exhausted or disabled, MoA Fusion writes `.pi/mf-plan/<slug>__verification-handoff.md` with each criterion's pass/fail status, the full frozen criteria checklist, gaps, and failing project checks so you can continue the fix/verification loop manually (for example via `/mf-plan-implement`). The exhaustion notification includes the handoff file path.
|
|
296
|
+
|
|
277
297
|
**First run.** Until the setup overlay has been saved once, `/mf-plan` opens it — set up agents and rosters before planning — instead of the plan prompt. Only interactive TUI sessions are gated; headless plan mode is unaffected.
|
|
278
298
|
|
|
279
299
|
**Agentic provider bridges.** Some pi providers are not plain chat-completion APIs but bridges to full coding agents with their *own* local edit/shell tools (e.g. pi-cursor-bridge, whose models run Cursor agents in the repo cwd). Pi's tool allowlist cannot restrain those agent-side tools, so plan mode adds two more layers:
|
|
@@ -283,7 +303,9 @@ Names are 1–24 alphanumeric characters, unique case-insensitively, up to 20 ro
|
|
|
283
303
|
|
|
284
304
|
### Parallel execution
|
|
285
305
|
|
|
286
|
-
In the single-model flow, Phase 1 launches up to **3 moa-explore agents in parallel** and Phase 2 up to **1 mf-plan agent**.
|
|
306
|
+
In the single-model flow, Phase 1 launches up to **3 moa-explore agents in parallel** and Phase 2 up to **1 mf-plan agent**. Child-agent concurrency is configurable from 1 to 8 in `/mf-plan-settings` (default **1**); it applies to MoA proposer fan-out, opinion fan-out, debate rounds, and `mf_plan_subagent` parallel mode, including every retry batch. The separate **8-task submission cap** per batch is unchanged. Per-task output is capped at 50KB.
|
|
307
|
+
|
|
308
|
+
**Cache warming.** pi 0.86.0 defaults `cacheWarming` to `"streaming"`. On Anthropic models, long `mf_plan_subagent` or MoA tool executions can therefore trigger paid cache refreshes while an agent is working. Set `cacheWarming` to `"off"` in your pi config if you would rather not pay for those.
|
|
287
309
|
|
|
288
310
|
```
|
|
289
311
|
mf_plan_subagent({
|
|
@@ -398,7 +420,8 @@ src/
|
|
|
398
420
|
├── promptEditor.ts # File-path/@name completion editor
|
|
399
421
|
├── shimmer.ts
|
|
400
422
|
├── toolActivity.ts
|
|
401
|
-
|
|
423
|
+
├── verificationFindingsOverlay.ts
|
|
424
|
+
└── workingOverlay.ts # Blocking-work overlay (plan/opinion/debate naming)
|
|
402
425
|
|
|
403
426
|
agents/ # Shipped agent definitions
|
|
404
427
|
├── moa-explore.md
|
|
@@ -12,6 +12,8 @@ You must NOT make any changes. You may use read/grep/find/ls to verify a propose
|
|
|
12
12
|
|
|
13
13
|
**Blinding notice.** Proposer identities are intentionally blinded in your input: each proposal is headed `### Proposal from Proposer N` (where N is the proposer's slot order, deterministic for this run) instead of naming the model. This prevents self-preference bias — proposals must be judged purely on merit. Do not attempt to infer, attribute, or guess which model wrote which proposal, and never output any model name, family, provider, or version anywhere in your plan.
|
|
14
14
|
|
|
15
|
+
**Panel dependence note.** Your input opens with a one-paragraph `Panel dependence:` note giving the number of proposals, distinct models, and providers, plus which `Proposer N` slots share one model. It carries no model names by design. Read it before weighing agreement: proposals inside one same-model cluster are one vote, and the shared prompt template means even distinct models are not fully independent.
|
|
16
|
+
|
|
15
17
|
## Verification criteria task
|
|
16
18
|
|
|
17
19
|
After a plan is approved, you may be re-invoked solely to write a `## Verification Criteria` checklist. Emit only that section, with binary observable `- **C<n>:** condition — how to check` bullets. Every item must be checkable through repository reading, not execution. Cover plan steps, named call sites, and implied regression/scope constraints. The orchestrator retries once if no criteria can be parsed.
|
|
@@ -26,7 +28,7 @@ Guidelines:
|
|
|
26
28
|
- Simplicity: does it avoid unnecessary complexity?
|
|
27
29
|
- Build a new plan by recombining the best compatible ideas across proposals. Only synthesize prose/structure, never code — you are merging natural-language plans, not diffs.
|
|
28
30
|
- Do not mechanically hybridize mutually incompatible alternatives. Where two or more proposers take genuinely different approaches to the same decision, decide which approach is best on the merits — clear repo evidence, unambiguous engineering judgment, or a direct match with the user's stated request — and use it in the synthesized plan. This surfacing is unconditional and mandatory: state the choice and why in one line in the Context section AND emit it in a `## Conflicts` section (format below) so the user can confirm or override it before the plan is finalized. Repo evidence or confident engineering judgment may justify your recommended pick — it never excuses omitting the conflict block. Never include a conflict the user has no realistic alternative for (e.g. only one proposer covered the point at all). You may choose one incompatible approach while still incorporating compatible strengths, safeguards, verification steps, or sequencing from other proposals.
|
|
29
|
-
- Where proposers agree, treat agreement as useful signal but still sanity-check it; shared assumptions can be shared errors.
|
|
31
|
+
- Where proposers agree, treat agreement as useful signal but still sanity-check it; shared assumptions can be shared errors. Weight agreement by the `Panel dependence` note — count each same-model cluster as one vote — and treat an uncited unanimous point as an untested claim until you check it against the repo.
|
|
30
32
|
- If no proposal handles something the user goal requires, add the missing piece yourself and note it in Context. If a proposal suggests something actively wrong, risky, or unnecessary, drop it and say so briefly.
|
|
31
33
|
- Always begin the final plan with a **Context** section (see Output format). This is the one place you may — and must — describe the MoA reconciliation process: how proposals scored on the evaluation dimensions, what they agreed on, what was missing or rejected, where they differed, and how you resolved disagreements.
|
|
32
34
|
- If a disagreement is a genuine ambiguity in user intent that you cannot resolve from the plans + repo alone, ask the user ONE clarifying question. To do this, output a section titled exactly `## Open Question` containing only that question, and STOP — do not write the rest of the plan yet. You will be re-invoked with the user's answer appended, at which point you should incorporate it and produce the full plan below instead of the question section.
|
|
@@ -43,7 +45,7 @@ When two or more proposers substantively disagree on one or more decision points
|
|
|
43
45
|
|
|
44
46
|
- **Decision:** <ASD-STE100 Simplified Technical English Issue 9 plain-language explanation of what the person is choosing and why it matters>
|
|
45
47
|
- **Recommended:** <short, non-technical outcome summary>
|
|
46
|
-
- **Details:** <one-line technical implementation details and tradeoffs
|
|
48
|
+
- **Details:** <one-line technical implementation details and tradeoffs; for the recommended option, end with `backed by K of N proposals (M distinct models)`>
|
|
47
49
|
- **Alternative** (Proposer N): <short, non-technical outcome summary>
|
|
48
50
|
- **Details:** <one-line technical implementation details and tradeoffs>
|
|
49
51
|
- **Alternative** (Proposer M): <short, non-technical outcome summary>
|
|
@@ -59,7 +61,7 @@ Worked example (matches the parser exactly — one `-` bullet per line, with `De
|
|
|
59
61
|
|
|
60
62
|
- **Decision:** Choose where sign-in information is stored; this affects both security and how much of the existing sign-in flow can stay unchanged.
|
|
61
63
|
- **Recommended:** Keep the current sign-in experience
|
|
62
|
-
- **Details:** Store session IDs in `Secure`, `HttpOnly` cookies so the existing middleware stays compatible and page scripts cannot read them.
|
|
64
|
+
- **Details:** Store session IDs in `Secure`, `HttpOnly` cookies so the existing middleware stays compatible and page scripts cannot read them; backed by 2 of 3 proposals (2 distinct models).
|
|
63
65
|
- **Alternative** (Proposer 2): Make sign-in data available to browser code
|
|
64
66
|
- **Details:** Store JWTs in `localStorage`; this can simplify cross-service access but lets an injected page script read the token.
|
|
65
67
|
```
|
|
@@ -70,10 +72,11 @@ Rules:
|
|
|
70
72
|
- Exactly one `**Recommended:**` line per conflict, using a short, non-technical summary of your synthesized outcome, immediately followed by exactly one `**Details:**` line.
|
|
71
73
|
- One `**Alternative**` line per other distinct approach worth surfacing, each attributed to a blinded `Proposer N` slot label already present in your input — never invent or rename labels, and never leak real model names here either. Immediately follow every alternative with its own `**Details:**` line.
|
|
72
74
|
- Write each recommended or alternative summary as an outcome the person can choose, not an implementation mechanism. It is displayed as the primary selectable line, so it must be understandable without code syntax, type names, file paths, configuration keys, or unexplained acronyms.
|
|
73
|
-
- Use `**Details:**` for the technical implementation specifics, rationale, benefits, drawbacks, and tradeoffs. It is displayed dimmed beneath the summary, may name concrete types, files, or configuration when helpful, and must stay to one line.
|
|
75
|
+
- Use `**Details:**` for the technical implementation specifics, rationale, benefits, drawbacks, and tradeoffs. It is displayed dimmed beneath the summary, may name concrete types, files, or configuration when helpful, and must stay to one line. The recommended option's `**Details:**` must end with `backed by K of N proposals (M distinct models)`, where K = proposals backing that option, N = proposals received, and M = distinct models among the K (slots in one same-model cluster count once).
|
|
76
|
+
- The `**Recommended:**` pick must be justified on the merits — repo evidence, engineering judgment, or a direct match with the user's request — and a majority of proposers is not by itself a reason, especially when that majority sits inside one same-model cluster named in the Panel dependence note.
|
|
74
77
|
- If you are re-invoked with the user's conflict resolutions appended (see below), honor their selections, resolve any free-form chat feedback, drop the `## Conflicts` section entirely, and emit the complete final plan. If the chat feedback introduces a genuine new ambiguity you cannot resolve yourself, fall back to the `## Open Question` mechanism (one question) and stop, exactly as you would on first pass.
|
|
75
78
|
- When the input contains post-review user feedback, treat it as binding revision directives. Answer clarifying questions where possible, incorporate the requested changes, and return the complete revised plan rather than a description of changes. Omit both `## Conflicts` and `## Open Question` from that revised output.
|
|
76
|
-
- **Self-check before finalizing:** review every disagreement you described in Context and verify each has a matching `### Conflict:` block; add any missing ones now. Then verify Context contains all three subsections (`### Evaluation dimensions`, `### Proposer alignment`, `### Synthesis decisions`). Finally verify `## Proposer Verdicts` contains one bullet for every proposal you received.
|
|
79
|
+
- **Self-check before finalizing:** review every disagreement you described in Context and verify each has a matching `### Conflict:` block; add any missing ones now. Then verify Context contains all three subsections (`### Evaluation dimensions`, `### Proposer alignment`, `### Synthesis decisions`), that every unanimous point in `### Proposer alignment` carries an evidence-backed or asserted tag with each asserted point's repo check recorded, and that every Recommended `**Details:**` line ends with its support count. Finally verify `## Proposer Verdicts` contains one bullet for every proposal you received.
|
|
77
80
|
|
|
78
81
|
Output format (when not asking a question):
|
|
79
82
|
|
|
@@ -89,7 +92,7 @@ Then include these three subsections. They are MANDATORY in every full-plan outp
|
|
|
89
92
|
Briefly reason about correctness, completeness, feasibility & effort, risk, and simplicity as separate dimensions. Do not collapse them into one overall impression, and do not reward verbosity.
|
|
90
93
|
|
|
91
94
|
### Proposer alignment
|
|
92
|
-
Summarize where the proposers **unanimously agreed**, what they **unanimously rejected** (shared dead ends or dismissed ideas), and where they **differed**. Be specific — name files, approaches, or tradeoffs, not vague generalities.
|
|
95
|
+
Summarize where the proposers **unanimously agreed**, what they **unanimously rejected** (shared dead ends or dismissed ideas), and where they **differed**. Be specific — name files, approaches, or tradeoffs, not vague generalities. For every unanimous point (agreed or unanimously rejected), tag it **evidence-backed** (two or more proposers cite a concrete repo location — file path, symbol, or line) or **asserted** (no citation, or all citations identical and unverified). Sanity-check every asserted point against the repo with read/grep/find/ls *before* relying on it, stating the location checked and what was found, and drop or downgrade it if it does not hold. Note explicitly when a "unanimous" point rests inside a single same-model cluster from the Panel dependence note.
|
|
93
96
|
|
|
94
97
|
### Synthesis decisions
|
|
95
98
|
State what the final plan recombines from multiple proposers, what required pieces you added because no proposer covered them, and what actively wrong, risky, or unnecessary ideas you dropped. Where proposers disagreed, use the blinded slot labels from each `### Proposal from …` heading (e.g. `Proposer 1`, `Proposer 2`) and give a one-line decision for each disagreement: which approach you chose and why, citing repo evidence, engineering tradeoffs, or alignment with the user's request. These disagreements must also appear in `## Conflicts` for user review.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@underactive/pi-topping-moa-fusion",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Pi extension implementing /mf-plan — Claude Code-style 5-phase plan mode with a Mixture-of-Agents proposer/synthesizer pipeline.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"typebox": "*"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@earendil-works/pi-agent-core": "0.
|
|
54
|
-
"@earendil-works/pi-ai": "0.
|
|
55
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
56
|
-
"@earendil-works/pi-tui": "0.
|
|
53
|
+
"@earendil-works/pi-agent-core": "0.86.0",
|
|
54
|
+
"@earendil-works/pi-ai": "0.86.0",
|
|
55
|
+
"@earendil-works/pi-coding-agent": "0.86.0",
|
|
56
|
+
"@earendil-works/pi-tui": "0.86.0",
|
|
57
57
|
"@types/node": "^24.0.0",
|
|
58
58
|
"typebox": "^1.3.3",
|
|
59
59
|
"typescript": "^5.8.0"
|
package/src/config/planName.ts
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
* LLM-based 4-word plan name generation for repo-local plan files.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
|
6
5
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
6
|
import { loadMoaConfig } from "./settings.ts";
|
|
8
|
-
import {
|
|
7
|
+
import { generateWordSlug, slugifyPlanName, trySlugifyPlanName } from "../planning/planFile.ts";
|
|
9
8
|
|
|
10
9
|
function buildSummarizePrompt(prompt: string): string {
|
|
11
10
|
return [
|
|
@@ -18,10 +17,14 @@ function buildSummarizePrompt(prompt: string): string {
|
|
|
18
17
|
].join("\n");
|
|
19
18
|
}
|
|
20
19
|
|
|
20
|
+
export interface PlanNameOptions {
|
|
21
|
+
/** Caller cancellation — esc from the working overlay. */
|
|
22
|
+
signal?: AbortSignal;
|
|
23
|
+
}
|
|
24
|
+
|
|
21
25
|
/** Call the active model to summarize a plan prompt into a 4-word slug. */
|
|
22
|
-
export async function summarizePlanPromptName(ctx: ExtensionContext, prompt: string): Promise<string> {
|
|
23
|
-
const
|
|
24
|
-
const fallback = () => promptSlug ?? fallbackPlanName();
|
|
26
|
+
export async function summarizePlanPromptName(ctx: ExtensionContext, prompt: string, options?: PlanNameOptions): Promise<string> {
|
|
27
|
+
const fallback = () => slugifyPlanName(prompt);
|
|
25
28
|
|
|
26
29
|
const config = loadMoaConfig();
|
|
27
30
|
|
|
@@ -34,31 +37,33 @@ export async function summarizePlanPromptName(ctx: ExtensionContext, prompt: str
|
|
|
34
37
|
: ctx.model;
|
|
35
38
|
if (!model) return fallback();
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
if (
|
|
40
|
+
// Short-circuit before registry streaming so an already-aborted signal costs nothing.
|
|
41
|
+
if (options?.signal?.aborted) return fallback();
|
|
39
42
|
|
|
40
43
|
try {
|
|
41
44
|
// Naming a plan file is not a reasoning task, and the cheap/fast model is
|
|
42
45
|
// chosen to be cheap. Omitting `reasoning` is how pi's simple-stream API
|
|
43
46
|
// spells "thinking off" (its ThinkingLevel type has no "off" member), so
|
|
44
47
|
// providers send their disabled-reasoning mapping rather than a default.
|
|
45
|
-
const response = await
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
48
|
+
const response = await ctx.modelRegistry
|
|
49
|
+
.streamSimple(
|
|
50
|
+
model,
|
|
51
|
+
{
|
|
52
|
+
messages: [
|
|
53
|
+
{
|
|
54
|
+
role: "user",
|
|
55
|
+
content: [{ type: "text", text: buildSummarizePrompt(prompt) }],
|
|
56
|
+
timestamp: Date.now(),
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
{ signal: options?.signal },
|
|
61
|
+
)
|
|
62
|
+
.result();
|
|
63
|
+
|
|
64
|
+
// An aborted request can resolve with stopReason "aborted" and empty text
|
|
65
|
+
// rather than throwing — fall back explicitly instead of slugifying a stub.
|
|
66
|
+
if (response.stopReason === "aborted") return fallback();
|
|
62
67
|
|
|
63
68
|
const text = response.content
|
|
64
69
|
.filter((part): part is { type: "text"; text: string } => part.type === "text")
|
package/src/config/settings.ts
CHANGED
|
@@ -21,6 +21,35 @@ import { parseRosters, type PlanRoster } from "./rosters.ts";
|
|
|
21
21
|
|
|
22
22
|
export type MoaMode = "single" | "moa";
|
|
23
23
|
|
|
24
|
+
export const MIN_CONCURRENT_AGENTS = 1;
|
|
25
|
+
export const MAX_CONCURRENT_AGENTS = 8;
|
|
26
|
+
export const DEFAULT_MAX_CONCURRENT_AGENTS = MIN_CONCURRENT_AGENTS;
|
|
27
|
+
|
|
28
|
+
export const MIN_VERIFICATION_REPAIRS = 0;
|
|
29
|
+
export const MAX_VERIFICATION_REPAIRS_LIMIT = 5;
|
|
30
|
+
export const DEFAULT_MAX_VERIFICATION_REPAIRS = 2;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Sanitize verifier-driven repair rounds: missing or malformed values fall back
|
|
34
|
+
* to the default; finite numbers are rounded and clamped into [0, 5]. Zero
|
|
35
|
+
* disables automatic repair offers after verification gaps.
|
|
36
|
+
*/
|
|
37
|
+
export function normalizeMaxVerificationRepairs(value: unknown): number {
|
|
38
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_MAX_VERIFICATION_REPAIRS;
|
|
39
|
+
return Math.min(MAX_VERIFICATION_REPAIRS_LIMIT, Math.max(MIN_VERIFICATION_REPAIRS, Math.round(value)));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The one place a loaded/passed concurrency value becomes safe: missing,
|
|
44
|
+
* non-numeric, NaN and infinite values fall back to the default; finite
|
|
45
|
+
* numbers are rounded to an integer and clamped into [MIN, MAX], so a
|
|
46
|
+
* fractional or out-of-range value can never reach the runner.
|
|
47
|
+
*/
|
|
48
|
+
export function normalizeMaxConcurrentAgents(value: unknown): number {
|
|
49
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_MAX_CONCURRENT_AGENTS;
|
|
50
|
+
return Math.min(MAX_CONCURRENT_AGENTS, Math.max(MIN_CONCURRENT_AGENTS, Math.round(value)));
|
|
51
|
+
}
|
|
52
|
+
|
|
24
53
|
export interface MoaConfig {
|
|
25
54
|
mode: MoaMode;
|
|
26
55
|
proposers: ModelRef[];
|
|
@@ -28,6 +57,10 @@ export interface MoaConfig {
|
|
|
28
57
|
debateModels: ModelRef[];
|
|
29
58
|
/** Round ceiling for /mf-debate, clamped to the picker's 2–5 range. */
|
|
30
59
|
debateRounds: number;
|
|
60
|
+
/** Child agents allowed to run at once in any fan-out, clamped to 1–8. Defaults to 1 — parallel local agents are unusably slow. */
|
|
61
|
+
maxConcurrentAgents: number;
|
|
62
|
+
/** Verifier-driven repair rounds after implementation, clamped to 0–5. Defaults to 2; 0 disables automatic repairs. */
|
|
63
|
+
maxVerificationRepairs: number;
|
|
31
64
|
synthesizer?: ModelRef;
|
|
32
65
|
implementer?: ModelRef;
|
|
33
66
|
verifier?: ModelRef;
|
|
@@ -56,6 +89,8 @@ function emptyConfig(): MoaConfig {
|
|
|
56
89
|
opinionModels: [],
|
|
57
90
|
debateModels: [],
|
|
58
91
|
debateRounds: 3,
|
|
92
|
+
maxConcurrentAgents: DEFAULT_MAX_CONCURRENT_AGENTS,
|
|
93
|
+
maxVerificationRepairs: DEFAULT_MAX_VERIFICATION_REPAIRS,
|
|
59
94
|
synthesizer: undefined,
|
|
60
95
|
implementer: undefined,
|
|
61
96
|
verifier: undefined,
|
|
@@ -89,6 +124,8 @@ function parseSettingsFile(path: string): MoaConfig {
|
|
|
89
124
|
const debateRounds = typeof parsed.debateRounds === "number" && Number.isFinite(parsed.debateRounds)
|
|
90
125
|
? Math.min(5, Math.max(2, Math.round(parsed.debateRounds)))
|
|
91
126
|
: 3;
|
|
127
|
+
const maxConcurrentAgents = normalizeMaxConcurrentAgents(parsed.maxConcurrentAgents);
|
|
128
|
+
const maxVerificationRepairs = normalizeMaxVerificationRepairs(parsed.maxVerificationRepairs);
|
|
92
129
|
const synthesizer = isModelRef(parsed.synthesizer) ? parsed.synthesizer : undefined;
|
|
93
130
|
const implementer = isModelRef(parsed.implementer) ? parsed.implementer : undefined;
|
|
94
131
|
const verifier = isModelRef(parsed.verifier) ? parsed.verifier : undefined;
|
|
@@ -113,7 +150,7 @@ function parseSettingsFile(path: string): MoaConfig {
|
|
|
113
150
|
const agentDefaultsConfigured = parsed.agentDefaultsConfigured === true;
|
|
114
151
|
const rosters = parseRosters(parsed.rosters);
|
|
115
152
|
|
|
116
|
-
return { mode, proposers, opinionModels, debateModels, debateRounds, synthesizer, implementer, verifier, cheap, thinkingOverrides, rosters, autoResolveConflicts, useSummaryName, agentDefaultsConfigured };
|
|
153
|
+
return { mode, proposers, opinionModels, debateModels, debateRounds, maxConcurrentAgents, maxVerificationRepairs, synthesizer, implementer, verifier, cheap, thinkingOverrides, rosters, autoResolveConflicts, useSummaryName, agentDefaultsConfigured };
|
|
117
154
|
} catch (err) {
|
|
118
155
|
console.error("mf-plan: failed to parse settings, using defaults:", err);
|
|
119
156
|
return emptyConfig();
|
|
@@ -34,6 +34,7 @@ export interface DebateFanoutOptions {
|
|
|
34
34
|
session: CancelSession;
|
|
35
35
|
widget: MoaProgressWidget;
|
|
36
36
|
agents: AgentConfig[];
|
|
37
|
+
maxConcurrency: number;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
export type DebateFanoutResult =
|
|
@@ -53,7 +54,7 @@ interface CumulativeUsage {
|
|
|
53
54
|
* accumulate across rounds per slot.
|
|
54
55
|
*/
|
|
55
56
|
export async function runDebateRounds(options: DebateFanoutOptions): Promise<DebateFanoutResult> {
|
|
56
|
-
const { host, ctx, topic, models, thinking, rounds: totalRounds, session, widget, agents } = options;
|
|
57
|
+
const { host, ctx, topic, models, thinking, rounds: totalRounds, session, widget, agents, maxConcurrency } = options;
|
|
57
58
|
const alive = models.map(() => true);
|
|
58
59
|
const positions: (string | undefined)[] = models.map(() => undefined);
|
|
59
60
|
const completedRounds: DebateRound[] = [];
|
|
@@ -146,6 +147,15 @@ export async function runDebateRounds(options: DebateFanoutOptions): Promise<Deb
|
|
|
146
147
|
observed.partial = result.partialAssistant;
|
|
147
148
|
}
|
|
148
149
|
},
|
|
150
|
+
{
|
|
151
|
+
maxConcurrency,
|
|
152
|
+
onStart: (batchIndex) => {
|
|
153
|
+
const debateIndex = entries[batchIndex].index;
|
|
154
|
+
widget.update(debateIndex, "working");
|
|
155
|
+
const observed = observe.agents[debateIndex];
|
|
156
|
+
if (observed) observed.state = "working";
|
|
157
|
+
},
|
|
158
|
+
},
|
|
149
159
|
);
|
|
150
160
|
} finally {
|
|
151
161
|
unsubscribe();
|
|
@@ -179,7 +189,7 @@ export async function runDebateRounds(options: DebateFanoutOptions): Promise<Deb
|
|
|
179
189
|
}
|
|
180
190
|
};
|
|
181
191
|
|
|
182
|
-
widget.startFanout(models, thinking);
|
|
192
|
+
widget.startFanout(models, thinking, maxConcurrency);
|
|
183
193
|
|
|
184
194
|
let stoppedEarly: string | undefined;
|
|
185
195
|
for (let round = 1; round <= totalRounds; round++) {
|
|
@@ -193,7 +203,7 @@ export async function runDebateRounds(options: DebateFanoutOptions): Promise<Deb
|
|
|
193
203
|
if (round > 1) {
|
|
194
204
|
for (const entry of entries) {
|
|
195
205
|
// Re-activates the settled row and resets its transcript.
|
|
196
|
-
widget.update(entry.index, "
|
|
206
|
+
widget.update(entry.index, "queued", `round ${round}/${totalRounds} · responding`);
|
|
197
207
|
const observed = observe.agents[entry.index];
|
|
198
208
|
if (observed) {
|
|
199
209
|
observed.state = "working";
|
|
@@ -233,7 +243,7 @@ export async function runDebateRounds(options: DebateFanoutOptions): Promise<Deb
|
|
|
233
243
|
session.title = `Debate agents — round ${round}/${totalRounds} (retry)`;
|
|
234
244
|
session.getExtras = makeGetExtras(retryEntries);
|
|
235
245
|
for (const entry of retryEntries) {
|
|
236
|
-
widget.update(entry.index, "
|
|
246
|
+
widget.update(entry.index, "queued", "retrying: no position produced");
|
|
237
247
|
const observed = observe.agents[entry.index];
|
|
238
248
|
if (observed) {
|
|
239
249
|
observed.state = "working";
|
package/src/debate/runDebate.ts
CHANGED
|
@@ -3,16 +3,17 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
|
|
|
3
3
|
import { installShippedAgents, shippedAgentsDir, withAuthoritativeMoaAgents } from "../agents/authoritative.ts";
|
|
4
4
|
import { discoverAgents } from "../agents/discovery.ts";
|
|
5
5
|
import { summarizePlanPromptName } from "../config/planName.ts";
|
|
6
|
+
import { slugifyPlanName } from "../planning/planFile.ts";
|
|
6
7
|
import { loadMoaConfig, saveMoaConfig } from "../config/settings.ts";
|
|
7
8
|
import type { OpinionHost } from "../opinion/runOpinion.ts";
|
|
8
9
|
import { resolveContextWindow } from "../moa/modelRuntime.ts";
|
|
9
10
|
import { type CancelSession, subscribeCancelOverlayOnEsc } from "../runtime/cancelRun.ts";
|
|
10
11
|
import { formatMutationWarning, MutationTripwire } from "../runtime/mutationTripwire.ts";
|
|
11
|
-
import { type SendUserMessageOptions
|
|
12
|
+
import { type SendUserMessageOptions } from "../shared/modelRefs.ts";
|
|
12
13
|
import { MoaProgressWidget } from "../ui/moaProgressWidget.ts";
|
|
13
|
-
import type { ObserveSession } from "../ui/observeOverlay.ts";
|
|
14
14
|
import { showDebateModelPicker } from "../ui/debateModelPicker.ts";
|
|
15
15
|
import { showPromptEditor } from "../ui/promptEditor.ts";
|
|
16
|
+
import { planNamingOverlay, withWorkingOverlay } from "../ui/workingOverlay.ts";
|
|
16
17
|
import { repoDebateDisplayPath, saveRepoDebateFile } from "./debateFile.ts";
|
|
17
18
|
import { runDebateRounds } from "./debateFanout.ts";
|
|
18
19
|
import { collectDebateOutcomes, formatDebateMarkdown } from "./debateResults.ts";
|
|
@@ -69,7 +70,11 @@ export async function runInteractiveDebate(
|
|
|
69
70
|
thinkingOverrides: { ...settings.thinkingOverrides, ...thinkingSelections },
|
|
70
71
|
});
|
|
71
72
|
|
|
72
|
-
const slug = await
|
|
73
|
+
const slug = await withWorkingOverlay(
|
|
74
|
+
ctx,
|
|
75
|
+
planNamingOverlay("debate", () => slugifyPlanName(topic)),
|
|
76
|
+
(signal) => summarizePlanPromptName(ctx, topic, { signal }),
|
|
77
|
+
);
|
|
73
78
|
try {
|
|
74
79
|
saveRepoDebateFile(topic, ctx.cwd, slug, "debate-prompt");
|
|
75
80
|
} catch {
|
|
@@ -101,7 +106,7 @@ export async function runInteractiveDebate(
|
|
|
101
106
|
host.setRunningProgressWidget(widget);
|
|
102
107
|
let outcome;
|
|
103
108
|
try {
|
|
104
|
-
outcome = await runDebateRounds({ host, ctx, topic, models, thinking, rounds, session, widget, agents });
|
|
109
|
+
outcome = await runDebateRounds({ host, ctx, topic, models, thinking, rounds, session, widget, agents, maxConcurrency: settings.maxConcurrentAgents });
|
|
105
110
|
} finally {
|
|
106
111
|
host.setRunningProgressWidget(undefined);
|
|
107
112
|
widget.stopWidget();
|
|
@@ -126,6 +131,10 @@ export async function runInteractiveDebate(
|
|
|
126
131
|
} catch {
|
|
127
132
|
saved = false;
|
|
128
133
|
}
|
|
134
|
+
// `sendUserMessage` routes to prompt() and ignores triggerTurn (only
|
|
135
|
+
// sendCustomMessage honors it), so this report does start a model turn.
|
|
136
|
+
// Genuinely append-only delivery needs pi.sendMessage({ customType, … },
|
|
137
|
+
// { triggerTurn: false }) plus a message renderer — a UX change, not made here.
|
|
129
138
|
const appendOnly: SendUserMessageOptions = { triggerTurn: false };
|
|
130
139
|
await pi.sendUserMessage(markdown, appendOnly);
|
|
131
140
|
const active = outcomes.filter((item) => item.finalStatus === "active-at-close").length;
|
package/src/index.ts
CHANGED
|
@@ -74,7 +74,7 @@ export default function mfPlanExtension(pi: ExtensionAPI): void {
|
|
|
74
74
|
handler: async (_args, ctx) => { if (controller.questionnaireBusy(ctx)) return; await controller.clearCompletedPlan(ctx); },
|
|
75
75
|
});
|
|
76
76
|
pi.registerCommand("mf-plan-settings", {
|
|
77
|
-
description: "Configure the explore and cheap/fast agents, agent rosters for MoA roles, and
|
|
77
|
+
description: "Configure the explore and cheap/fast agents, agent rosters for MoA roles, plan options, and max concurrent agents",
|
|
78
78
|
handler: async (_args, ctx) => {
|
|
79
79
|
if (controller.questionnaireBusy(ctx)) return;
|
|
80
80
|
installShippedAgents();
|
|
@@ -24,7 +24,7 @@ export const CONFLICT_CONTRACT_EXAMPLE = [
|
|
|
24
24
|
"",
|
|
25
25
|
"- **Decision:** Choose where sign-in information is stored; this affects both security and how much of the existing sign-in flow can stay unchanged.",
|
|
26
26
|
"- **Recommended:** Keep the current sign-in experience",
|
|
27
|
-
"- **Details:** Store session IDs in `Secure`, `HttpOnly` cookies so the existing middleware stays compatible and page scripts cannot read them.",
|
|
27
|
+
"- **Details:** Store session IDs in `Secure`, `HttpOnly` cookies so the existing middleware stays compatible and page scripts cannot read them; backed by 2 of 3 proposals (2 distinct models).",
|
|
28
28
|
"- **Alternative** (Proposer 2): Make sign-in data available to browser code",
|
|
29
29
|
"- **Details:** Store JWTs in `localStorage`; this can simplify cross-service access but lets an injected page script read the token.",
|
|
30
30
|
].join("\n");
|
|
@@ -42,6 +42,7 @@ export function buildConflictContract(labels: string[]): string {
|
|
|
42
42
|
"- Exactly one `**Decision:**` line per conflict, placed before its options. Exactly one `**Recommended:**` line per conflict, immediately followed by exactly one `**Details:**` line. One `**Alternative** (Proposer N)` line per other distinct approach worth surfacing, each immediately followed by its own `**Details:**` line.",
|
|
43
43
|
`- Attribute alternatives only to blinded slot labels already present in your input. Blinded proposer slots in this run: ${labels.join(", ")}. Never invent or rename labels, and never output any model name, family, provider, or version anywhere.`,
|
|
44
44
|
"- Write each Recommended/Alternative summary as an outcome the person can choose, understandable without code syntax, type names, or file paths; put technical specifics and tradeoffs in `**Details:**`, kept to one line.",
|
|
45
|
+
"- The `**Recommended:**` pick must be justified on the merits — repo evidence, engineering judgment, or a direct match with the user's request — and a majority of proposers is not by itself a reason, especially when that majority sits inside one same-model cluster named in the Panel dependence note. The recommended option's `**Details:**` line must end with `backed by K of N proposals (M distinct models)`, where K = proposals backing that option, N = proposals received, and M = distinct models among the K (slots in one cluster count once).",
|
|
45
46
|
"- Supersession rule: if your input contains user conflict resolutions (a \"User reviewed your conflict recommendations\" block) or post-review user feedback, those directives override this requirement — honor every selection, resolve any free-form chat feedback, drop the `## Conflicts` section entirely from your revised output, and emit the complete final plan without it.",
|
|
46
47
|
"",
|
|
47
48
|
"Clarifying-question option: only when a disagreement is a genuine ambiguity in user intent that you cannot resolve from the plans alone, output a section titled exactly `## Open Question` containing only that one question, as your ENTIRE output — write nothing else in that reply, and none of the sections above. You will be re-invoked with the user's answer appended. Prefer resolving ambiguities yourself and noting the resolution in the final plan; if your input already contains an answer to an earlier question, incorporate it and do not emit `## Open Question` again.",
|
|
@@ -27,7 +27,7 @@ export function buildContextSubsectionsContract(): string {
|
|
|
27
27
|
...CONTEXT_SUBSECTION_HEADINGS.map((heading) => `- \`### ${heading}\``),
|
|
28
28
|
"",
|
|
29
29
|
"- `### Evaluation dimensions` — reason separately about correctness, completeness, feasibility & effort, risk, and simplicity; never collapse them into one impression.",
|
|
30
|
-
"- `### Proposer alignment` — where proposers unanimously agreed, what they unanimously rejected, and where they differed,
|
|
30
|
+
"- `### Proposer alignment` — where proposers unanimously agreed, what they unanimously rejected, and where they differed; tag every unanimous point as evidence-backed (two or more proposers cite a concrete repo location) or asserted (no citation, or all citations identical and unverified); sanity-check every asserted point against the repo with read/grep/find/ls before relying on it, stating what you checked and what you found; note when a unanimous point rests inside a single same-model cluster from the Panel dependence note; treat each same-model cluster as one vote.",
|
|
31
31
|
"- `### Synthesis decisions` — what you recombined, added, or dropped, and one-line decisions for each disagreement using blinded slot labels.",
|
|
32
32
|
"",
|
|
33
33
|
"All MoA reconciliation commentary belongs inside `## Context` — nowhere else. A plan missing any of these three subsections will be rejected and sent back to you. They are required in every full-plan output, including revisions after conflict resolutions or user feedback.",
|