@underactive/pi-topping-moa-fusion 0.4.4 → 0.4.5

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 CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.4.5]
6
+
7
+ ### Changed
8
+
9
+ - Verified compatibility with pi 1.3.0: no pi export this extension imports was removed or changed, and typecheck and the full test suite pass against 1.3.0.
10
+ - Updated all four pi development dependencies from 1.1.0 to 1.3.0.
11
+
12
+ ### Fixed
13
+
14
+ - On pi 1.3.0 and later, a bare model id in `moa-explore` or `mf-plan` frontmatter (e.g. `gpt-6-luna`) no longer makes the `mf_plan_subagent` child exit with `Model "…" is ambiguous across providers` when several authenticated providers offer that id. Earlier pi picked the first in catalog order. The tool now qualifies a bare id as `provider/id` before spawning: it prefers a provider with working credentials, then catalog order. Qualified refs and ids the registry does not know are passed through unchanged.
15
+ - On pi 1.2.0 and later, `before_agent_start` also fires for runs another extension starts with `pi.sendMessage(…, { triggerTurn: true })`. Those runs no longer receive plan-mode instructions or consume the one-shot plan-exit reminder meant for the user's next prompt. Hosts that do not report the run's message type are treated as user runs, as before.
16
+
5
17
  ## [0.4.4]
6
18
 
7
19
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@underactive/pi-topping-moa-fusion",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
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": "1.1.0",
54
- "@earendil-works/pi-ai": "1.1.0",
55
- "@earendil-works/pi-coding-agent": "1.1.0",
56
- "@earendil-works/pi-tui": "1.1.0",
53
+ "@earendil-works/pi-agent-core": "1.3.0",
54
+ "@earendil-works/pi-ai": "1.3.0",
55
+ "@earendil-works/pi-coding-agent": "1.3.0",
56
+ "@earendil-works/pi-tui": "1.3.0",
57
57
  "@types/node": "^24.0.0",
58
58
  "typebox": "^1.3.3",
59
59
  "typescript": "^5.8.0"
@@ -1,4 +1,4 @@
1
- import { calculateCost } from "@earendil-works/pi-ai";
1
+ import { calculateCost, type Api, type Model } from "@earendil-works/pi-ai";
2
2
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
3
3
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
4
4
  import * as fs from "node:fs";
@@ -59,6 +59,34 @@ export function modelExtensionOptions(ctx: ExtensionContext, ref: ModelRef): { l
59
59
  return modelUsesExtensionRegisteredProvider(ctx, ref) ? { loadExtensions: true } : {};
60
60
  }
61
61
 
62
+ /** The slice of pi's `ModelRegistry` that qualifying a bare model id reads. */
63
+ export interface BareModelRegistry {
64
+ getAll(): Model<Api>[];
65
+ getAvailable(): Model<Api>[];
66
+ }
67
+
68
+ /**
69
+ * Qualify a bare frontmatter model id (`claude-haiku-4-5`) as `provider/id` before it
70
+ * reaches a child's `--model`. Since pi 1.3.0 a bare id that several authenticated
71
+ * providers offer is a hard "ambiguous across providers" error, where earlier pi
72
+ * picked the first in catalog order. Prefers a provider with working credentials,
73
+ * then catalog order, which restores the earlier behavior. Values that already
74
+ * contain a `/`, and ids the registry does not know, are returned unchanged for
75
+ * the child to resolve.
76
+ */
77
+ export function qualifyBareModelId(registry: BareModelRegistry, model: string): string {
78
+ if (model.includes("/")) return model;
79
+ const lower = model.toLowerCase();
80
+ const matches = (candidate: Model<Api>) => candidate.id.toLowerCase() === lower;
81
+ try {
82
+ const chosen = registry.getAvailable().find(matches) ?? registry.getAll().find(matches);
83
+ return chosen ? `${chosen.provider}/${chosen.id}` : model;
84
+ } catch {
85
+ // A stale registry must not block the spawn; the child resolves the bare id.
86
+ return model;
87
+ }
88
+ }
89
+
62
90
  export function resolveContextWindow(ctx: ExtensionContext, ref: ModelRef): number | undefined {
63
91
  return ctx.modelRegistry.find(ref.provider, ref.id)?.contextWindow;
64
92
  }
@@ -5,6 +5,7 @@ import type {
5
5
  AgentBeforeSettleEvent,
6
6
  AgentSettledEvent,
7
7
  AgentStartEvent,
8
+ BeforeAgentStartEvent,
8
9
  ExtensionAPI,
9
10
  ExtensionContext,
10
11
  MessageEndEvent,
@@ -739,7 +740,15 @@ export function createPlanModeController(pi: ExtensionAPI) {
739
740
  return !isPlanModeInstructionText(msg.content);
740
741
  }) };
741
742
  },
742
- onBeforeAgentStart: async () => {
743
+ onBeforeAgentStart: async (event?: Partial<Pick<BeforeAgentStartEvent, "messageType">>) => {
744
+ // pi >= 1.2 also fires this for runs another extension starts with
745
+ // sendMessage(..., { triggerTurn: true }). Those must neither receive plan
746
+ // instructions nor consume the one-shot exit reminder meant for the user's
747
+ // next prompt. Older hosts omit messageType and only fire for user runs.
748
+ if (event?.messageType === "custom") {
749
+ if (!planModeEnabled) deactivatePlanOnlyTools();
750
+ return;
751
+ }
743
752
  if (needsExitReminder) {
744
753
  needsExitReminder = false;
745
754
  // The reminder turn is also the first normal-mode turn: repair a
@@ -8,7 +8,7 @@ import * as path from "node:path";
8
8
  import { installShippedAgents, shippedAgentsDir, withAuthoritativeMoaAgents } from "../../agents/authoritative.ts";
9
9
  import { loadMoaConfig } from "../../config/settings.ts";
10
10
  import { discoverAgents, parseAgentFile } from "../../agents/discovery.ts";
11
- import { modelExtensionOptions } from "../../moa/modelRuntime.ts";
11
+ import { modelExtensionOptions, qualifyBareModelId } from "../../moa/modelRuntime.ts";
12
12
  import { CancelRun, type CancelRowExtras, type CancelSession } from "../../runtime/cancelRun.ts";
13
13
  import { MutationTripwire, formatMutationWarning } from "../../runtime/mutationTripwire.ts";
14
14
  import { getFinalOutput, getResultOutput, isFailedResult, truncateOutput } from "../../runtime/results.ts";
@@ -141,7 +141,16 @@ export function registerMfPlanSubagentTool(pi: ExtensionAPI, host: MfPlanSubagen
141
141
  const planOverride = sessionModel
142
142
  ? { model: `${sessionModel.provider}/${sessionModel.id}`, thinking: host.currentThinkingLevel() }
143
143
  : undefined;
144
- const overrideFor = (agentName: string) => (agentName === "mf-plan" ? planOverride : undefined);
144
+ // Shipped and hand-edited frontmatter can carry a bare id, which pi >= 1.3.0
145
+ // rejects as ambiguous when several authenticated providers offer it. Qualify
146
+ // it here; thinking stays undefined so the frontmatter level still applies.
147
+ const frontmatterOverrideFor = (agentName: string): { model: string; thinking?: ThinkingLevel } | undefined => {
148
+ const model = agents.find((agent) => agent.name === agentName)?.model;
149
+ if (!model) return undefined;
150
+ const qualified = qualifyBareModelId(ctx.modelRegistry, model);
151
+ return qualified === model ? undefined : { model: qualified };
152
+ };
153
+ const overrideFor = (agentName: string) => (agentName === "mf-plan" && planOverride ? planOverride : frontmatterOverrideFor(agentName));
145
154
 
146
155
  // An agent whose effective model belongs to an extension-registered
147
156
  // provider (claude-bridge, cursor-bridge) or is an extension-registered virtual model