pi-goala 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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 (2026-07-26)
4
+
5
+ - Add interactive per-phase provider, model, and reasoning selection from Pi's
6
+ authenticated available-model registry.
7
+ - Support mixed-provider planner, executor, step-verifier, final-verifier, and
8
+ repeated-repair roles.
9
+ - Migrate version 1 single-provider configuration to the version 2 per-role
10
+ schema without retaining the obsolete top-level provider.
11
+
3
12
  ## 0.2.0 (2026-07-26)
4
13
 
5
14
  - Rename the package, repository, extension namespace, storage, environment
package/README.md CHANGED
@@ -39,7 +39,7 @@ pi install npm:pi-goala
39
39
  Pin a release for reproducible team installations:
40
40
 
41
41
  ```text
42
- pi install npm:pi-goala@0.2.0
42
+ pi install npm:pi-goala@0.3.0
43
43
  ```
44
44
 
45
45
  Pi's package command both downloads Goala and registers its extension. A plain
@@ -49,7 +49,7 @@ supported installation path.
49
49
  From GitHub or a local checkout:
50
50
 
51
51
  ```text
52
- pi install git:github.com/barryking/pi-goala@v0.2.0
52
+ pi install git:github.com/barryking/pi-goala@v0.3.0
53
53
  pi install /absolute/path/to/pi-goala
54
54
  ```
55
55
 
@@ -115,6 +115,7 @@ Useful commands:
115
115
  /memory retire <id> Exclude an obsolete episode from recall
116
116
  /memory restore <id> Restore a retired episode
117
117
  /goala-setup Choose a model preset
118
+ /goala-setup custom Choose an available provider/model for every role
118
119
  /goala-setup status Show effective configuration
119
120
  ```
120
121
 
@@ -379,7 +380,13 @@ The recommended OpenAI Codex preset is:
379
380
 
380
381
  If those models are unavailable, Goala can use the model that was active
381
382
  when Pi started. Run `/goala-setup current` to persist that portable
382
- single-model configuration. Advanced users can configure each role in
383
+ single-model configuration. Run `/goala-setup custom` to choose an
384
+ authenticated provider/model and reasoning level separately for planning,
385
+ execution, step verification, final verification, and repeated repair. The
386
+ interactive picker reads Pi's available model registry and saves only after
387
+ every role has been selected.
388
+
389
+ Advanced users can edit the same per-role profiles in
383
390
  `~/.pi/agent/pi-goala/config.json`.
384
391
 
385
392
  The package does not overwrite Pi's `settings.json`, model list, skills,
@@ -23,6 +23,7 @@ own one concern:
23
23
  | `workflow.ts` | Goal-state types, normalization, and invariants |
24
24
  | `memory.ts` | Verified episodic storage and retrieval |
25
25
  | `recovery.ts` | Discovery of unfinished goals in saved Pi sessions |
26
+ | `setup.ts` | Interactive available-model selection for each lifecycle role |
26
27
  | `config.ts` | Namespaced configuration and model-role presets |
27
28
 
28
29
  Simple concern names are intentional: the directory already supplies the
@@ -122,7 +123,10 @@ planning and final verification, a faster model for implementation and routine
122
123
  step verification, and a balanced fallback after repeated repair. Independent
123
124
  step context and the final strong verifier preserve the trust boundary without
124
125
  paying the strongest-model cost at every human checkpoint. A portable
125
- current-model preset is available for other providers.
126
+ current-model preset is available for other providers. The advanced setup
127
+ enumerates Pi's authenticated available models and stores provider, model, and
128
+ reasoning independently for every role; mixed-provider workflows therefore use
129
+ the same phase-routing path as the bundled preset.
126
130
 
127
131
  If a configured model cannot be resolved and current-model fallback is
128
132
  enabled, Goala uses the model that was active when the extension session
@@ -46,35 +46,43 @@ and restore the backup to `~/.pi/agent/`.
46
46
  /goala-setup status
47
47
  /goala-setup openai
48
48
  /goala-setup current
49
+ /goala-setup custom
49
50
  ```
50
51
 
51
52
  `openai` selects the tested Sol/Luna/Terra model split when those models are
52
53
  available. `current` assigns the model active at Pi startup to every role.
54
+ `custom` interactively selects a provider/model and reasoning level for each
55
+ role from Pi's authenticated, available model registry. Cancelling any prompt
56
+ leaves the existing configuration unchanged.
53
57
 
54
58
  ## Full schema
55
59
 
56
60
  ```json
57
61
  {
58
- "configVersion": 1,
59
- "provider": "openai-codex",
62
+ "configVersion": 2,
60
63
  "planner": {
64
+ "provider": "openai-codex",
61
65
  "model": "gpt-5.6-sol",
62
66
  "thinkingLevel": "medium"
63
67
  },
64
68
  "executor": {
69
+ "provider": "openai-codex",
65
70
  "model": "gpt-5.6-luna",
66
71
  "thinkingLevel": "medium"
67
72
  },
68
73
  "fallbackExecutor": {
74
+ "provider": "openai-codex",
69
75
  "model": "gpt-5.6-terra",
70
76
  "thinkingLevel": "medium",
71
77
  "afterRepairCycle": 2
72
78
  },
73
79
  "stepVerifier": {
80
+ "provider": "openai-codex",
74
81
  "model": "gpt-5.6-luna",
75
82
  "thinkingLevel": "medium"
76
83
  },
77
84
  "verifier": {
85
+ "provider": "openai-codex",
78
86
  "model": "gpt-5.6-sol",
79
87
  "thinkingLevel": "medium"
80
88
  },
@@ -96,6 +104,10 @@ available. `current` assigns the model active at Pi startup to every role.
96
104
 
97
105
  Restart Pi or use `/reload` after manually editing the file.
98
106
 
107
+ Version 1 configuration used one top-level `provider`. Goala reads that schema,
108
+ applies the provider to every role, and writes only version 2 when the
109
+ configuration is next saved.
110
+
99
111
  `reviewPolicy` accepts:
100
112
 
101
113
  - `final`: run the full approved plan and review the final result;
@@ -12,6 +12,7 @@ export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhi
12
12
  export type ReviewPolicy = "final" | "per-step";
13
13
 
14
14
  export interface ModelProfile {
15
+ provider: string;
15
16
  model: string;
16
17
  thinkingLevel: ThinkingLevel;
17
18
  }
@@ -28,8 +29,7 @@ export interface GoalaSetupPreset {
28
29
  }
29
30
 
30
31
  export interface GoalaConfig {
31
- configVersion: 1;
32
- provider: string;
32
+ configVersion: 2;
33
33
  planner: ModelProfile;
34
34
  executor: ModelProfile;
35
35
  fallbackExecutor: ModelProfile & { afterRepairCycle: number };
@@ -44,13 +44,33 @@ export interface GoalaConfig {
44
44
  }
45
45
 
46
46
  export const OPENAI_CODEX_PRESET: GoalaConfig = {
47
- configVersion: 1,
48
- provider: "openai-codex",
49
- planner: { model: "gpt-5.6-sol", thinkingLevel: "medium" },
50
- executor: { model: "gpt-5.6-luna", thinkingLevel: "medium" },
51
- fallbackExecutor: { model: "gpt-5.6-terra", thinkingLevel: "medium", afterRepairCycle: 2 },
52
- stepVerifier: { model: "gpt-5.6-luna", thinkingLevel: "medium" },
53
- verifier: { model: "gpt-5.6-sol", thinkingLevel: "medium" },
47
+ configVersion: 2,
48
+ planner: {
49
+ provider: "openai-codex",
50
+ model: "gpt-5.6-sol",
51
+ thinkingLevel: "medium",
52
+ },
53
+ executor: {
54
+ provider: "openai-codex",
55
+ model: "gpt-5.6-luna",
56
+ thinkingLevel: "medium",
57
+ },
58
+ fallbackExecutor: {
59
+ provider: "openai-codex",
60
+ model: "gpt-5.6-terra",
61
+ thinkingLevel: "medium",
62
+ afterRepairCycle: 2,
63
+ },
64
+ stepVerifier: {
65
+ provider: "openai-codex",
66
+ model: "gpt-5.6-luna",
67
+ thinkingLevel: "medium",
68
+ },
69
+ verifier: {
70
+ provider: "openai-codex",
71
+ model: "gpt-5.6-sol",
72
+ thinkingLevel: "medium",
73
+ },
54
74
  reviewPolicy: "final",
55
75
  autoVerify: true,
56
76
  maxRepairCycles: 3,
@@ -74,20 +94,69 @@ export function configPath(): string {
74
94
  return join(goalaHome(), "config.json");
75
95
  }
76
96
 
77
- function mergeConfig(parsed: Partial<GoalaConfig>): GoalaConfig {
97
+ type PersistedConfig = Partial<GoalaConfig> & {
98
+ provider?: unknown;
99
+ };
100
+
101
+ function profileProvider(
102
+ profile: Partial<ModelProfile> | undefined,
103
+ legacyProvider: unknown,
104
+ fallback: ModelProfile,
105
+ ): string {
106
+ if (typeof profile?.provider === "string" && profile.provider) return profile.provider;
107
+ if (typeof legacyProvider === "string" && legacyProvider) return legacyProvider;
108
+ return fallback.provider;
109
+ }
110
+
111
+ function mergeProfile(
112
+ profile: Partial<ModelProfile> | undefined,
113
+ legacyProvider: unknown,
114
+ fallback: ModelProfile,
115
+ ): ModelProfile {
116
+ return {
117
+ ...fallback,
118
+ ...profile,
119
+ provider: profileProvider(profile, legacyProvider, fallback),
120
+ };
121
+ }
122
+
123
+ function mergeConfig(parsed: PersistedConfig): GoalaConfig {
124
+ const { provider: legacyProvider, ...current } = parsed;
78
125
  return {
79
126
  ...OPENAI_CODEX_PRESET,
80
- ...parsed,
81
- configVersion: 1,
127
+ ...current,
128
+ configVersion: 2,
82
129
  reviewPolicy: parsed.reviewPolicy === "per-step" ? "per-step" : "final",
83
- planner: { ...OPENAI_CODEX_PRESET.planner, ...parsed.planner },
84
- executor: { ...OPENAI_CODEX_PRESET.executor, ...parsed.executor },
130
+ planner: mergeProfile(
131
+ parsed.planner,
132
+ legacyProvider,
133
+ OPENAI_CODEX_PRESET.planner,
134
+ ),
135
+ executor: mergeProfile(
136
+ parsed.executor,
137
+ legacyProvider,
138
+ OPENAI_CODEX_PRESET.executor,
139
+ ),
85
140
  fallbackExecutor: {
86
- ...OPENAI_CODEX_PRESET.fallbackExecutor,
87
- ...parsed.fallbackExecutor,
141
+ ...mergeProfile(
142
+ parsed.fallbackExecutor,
143
+ legacyProvider,
144
+ OPENAI_CODEX_PRESET.fallbackExecutor,
145
+ ),
146
+ afterRepairCycle:
147
+ parsed.fallbackExecutor?.afterRepairCycle ??
148
+ OPENAI_CODEX_PRESET.fallbackExecutor.afterRepairCycle,
88
149
  },
89
- stepVerifier: { ...OPENAI_CODEX_PRESET.stepVerifier, ...parsed.stepVerifier },
90
- verifier: { ...OPENAI_CODEX_PRESET.verifier, ...parsed.verifier },
150
+ stepVerifier: mergeProfile(
151
+ parsed.stepVerifier,
152
+ legacyProvider,
153
+ OPENAI_CODEX_PRESET.stepVerifier,
154
+ ),
155
+ verifier: mergeProfile(
156
+ parsed.verifier,
157
+ legacyProvider,
158
+ OPENAI_CODEX_PRESET.verifier,
159
+ ),
91
160
  memory: { ...OPENAI_CODEX_PRESET.memory, ...parsed.memory },
92
161
  };
93
162
  }
@@ -95,7 +164,7 @@ function mergeConfig(parsed: Partial<GoalaConfig>): GoalaConfig {
95
164
  export function loadConfig(): GoalaConfig {
96
165
  let config = OPENAI_CODEX_PRESET;
97
166
  try {
98
- const parsed = JSON.parse(readFileSync(configPath(), "utf8")) as Partial<GoalaConfig>;
167
+ const parsed = JSON.parse(readFileSync(configPath(), "utf8")) as PersistedConfig;
99
168
  config = mergeConfig(parsed);
100
169
  } catch {
101
170
  config = mergeConfig({});
@@ -129,12 +198,11 @@ export function currentModelConfig(
129
198
  ): GoalaConfig {
130
199
  return {
131
200
  ...OPENAI_CODEX_PRESET,
132
- provider,
133
- planner: { model, thinkingLevel },
134
- executor: { model, thinkingLevel },
135
- fallbackExecutor: { model, thinkingLevel, afterRepairCycle: 2 },
136
- stepVerifier: { model, thinkingLevel },
137
- verifier: { model, thinkingLevel },
201
+ planner: { provider, model, thinkingLevel },
202
+ executor: { provider, model, thinkingLevel },
203
+ fallbackExecutor: { provider, model, thinkingLevel, afterRepairCycle: 2 },
204
+ stepVerifier: { provider, model, thinkingLevel },
205
+ verifier: { provider, model, thinkingLevel },
138
206
  };
139
207
  }
140
208
 
@@ -165,7 +233,7 @@ export function configuredModels(config: GoalaConfig): ModelIdentity[] {
165
233
  config.fallbackExecutor,
166
234
  config.stepVerifier,
167
235
  config.verifier,
168
- ].map((profile) => ({ provider: config.provider, id: profile.model }));
236
+ ].map((profile) => ({ provider: profile.provider, id: profile.model }));
169
237
  return identities.filter(
170
238
  (identity, index) =>
171
239
  identities.findIndex(
@@ -178,13 +246,17 @@ export function configuredModels(config: GoalaConfig): ModelIdentity[] {
178
246
  export function formatConfig(config: GoalaConfig): string {
179
247
  return [
180
248
  `Config: ${configPath()}`,
181
- `Planner: ${config.provider}/${config.planner.model}:${config.planner.thinkingLevel}`,
182
- `Executor: ${config.provider}/${config.executor.model}:${config.executor.thinkingLevel}`,
183
- `Step verifier: ${config.provider}/${config.stepVerifier.model}:${config.stepVerifier.thinkingLevel}`,
184
- `Verifier: ${config.provider}/${config.verifier.model}:${config.verifier.thinkingLevel}`,
185
- `Fallback: ${config.provider}/${config.fallbackExecutor.model}:${config.fallbackExecutor.thinkingLevel} from repair ${config.fallbackExecutor.afterRepairCycle}`,
249
+ `Planner: ${formatProfile(config.planner)}`,
250
+ `Executor: ${formatProfile(config.executor)}`,
251
+ `Step verifier: ${formatProfile(config.stepVerifier)}`,
252
+ `Verifier: ${formatProfile(config.verifier)}`,
253
+ `Fallback: ${formatProfile(config.fallbackExecutor)} from repair ${config.fallbackExecutor.afterRepairCycle}`,
186
254
  `Review policy: ${config.reviewPolicy}`,
187
255
  `Memory: ${config.memory.enabled ? "enabled" : "disabled"}; auto-recall ${config.memory.autoRecall ? "on" : "off"}`,
188
256
  `Current-model fallback: ${config.allowCurrentModelFallback ? "allowed" : "disabled"}`,
189
257
  ].join("\n");
190
258
  }
259
+
260
+ function formatProfile(profile: ModelProfile): string {
261
+ return `${profile.provider}/${profile.model}:${profile.thinkingLevel}`;
262
+ }
@@ -9,6 +9,7 @@ import {
9
9
  GOALA_SETUP_PRESETS,
10
10
  loadConfig,
11
11
  writeConfig,
12
+ type GoalaConfig,
12
13
  type ModelProfile,
13
14
  type ReviewPolicy,
14
15
  type ThinkingLevel,
@@ -40,6 +41,7 @@ import {
40
41
  moveToFreshSession,
41
42
  slicePhaseContext,
42
43
  } from "./session.ts";
44
+ import { selectModelRoles } from "./setup.ts";
43
45
  import {
44
46
  formatSourceDrift,
45
47
  inspectGoalSources,
@@ -142,7 +144,7 @@ export default function goala(pi: ExtensionAPI): void {
142
144
  }
143
145
 
144
146
  const profile = profileForPhase();
145
- let model = ctx.modelRegistry.find(config.provider, profile.model);
147
+ let model = ctx.modelRegistry.find(profile.provider, profile.model);
146
148
  if (!model && config.allowCurrentModelFallback) {
147
149
  const fallback = sessionDefaultModel ?? (ctx.model
148
150
  ? { provider: ctx.model.provider, id: ctx.model.id }
@@ -151,7 +153,7 @@ export default function goala(pi: ExtensionAPI): void {
151
153
  model = ctx.modelRegistry.find(fallback.provider, fallback.id);
152
154
  if (model && !fallbackNoticeShown) {
153
155
  ctx.ui.notify(
154
- `Goala: ${config.provider}/${profile.model} is unavailable; using ${fallback.provider}/${fallback.id}. Run /goala-setup to configure model roles.`,
156
+ `Goala: ${profile.provider}/${profile.model} is unavailable; using ${fallback.provider}/${fallback.id}. Run /goala-setup to configure model roles.`,
155
157
  "warning",
156
158
  );
157
159
  fallbackNoticeShown = true;
@@ -160,7 +162,7 @@ export default function goala(pi: ExtensionAPI): void {
160
162
  }
161
163
  if (!model) {
162
164
  ctx.ui.notify(
163
- `Goala: model not found: ${config.provider}/${profile.model}. Run /goala-setup current or edit the namespaced config.`,
165
+ `Goala: model not found: ${profile.provider}/${profile.model}. Run /goala-setup current or edit the namespaced config.`,
164
166
  "error",
165
167
  );
166
168
  updateGoalUi(ctx, state);
@@ -169,7 +171,10 @@ export default function goala(pi: ExtensionAPI): void {
169
171
 
170
172
  const selected = await pi.setModel(model);
171
173
  if (!selected) {
172
- ctx.ui.notify(`Goala: authenticate ${config.provider} before using ${profile.model}`, "warning");
174
+ ctx.ui.notify(
175
+ `Goala: authenticate ${profile.provider} before using ${profile.model}`,
176
+ "warning",
177
+ );
173
178
  updateGoalUi(ctx, state);
174
179
  return false;
175
180
  }
@@ -558,10 +563,13 @@ Do not edit files or rely on executor claims. Finish with submit_step_verificati
558
563
  if (!choice && ctx.hasUI) {
559
564
  const selected = await ctx.ui.select("Goala setup", [
560
565
  ...GOALA_SETUP_PRESETS.map((preset) => preset.label),
566
+ "Configure each phase",
561
567
  "Show current configuration",
562
568
  ]);
563
569
  choice = selected === "Show current configuration"
564
570
  ? "status"
571
+ : selected === "Configure each phase"
572
+ ? "custom"
565
573
  : GOALA_SETUP_PRESETS.find((preset) => preset.label === selected)?.id ?? "";
566
574
  }
567
575
 
@@ -570,21 +578,27 @@ Do not edit files or rely on executor claims. Finish with submit_step_verificati
570
578
  return;
571
579
  }
572
580
 
573
- const preset = GOALA_SETUP_PRESETS.find((candidate) => candidate.id === choice);
574
- if (!preset) {
575
- ctx.ui.notify(
576
- `Usage: /goala-setup [status | ${GOALA_SETUP_PRESETS.map((candidate) => candidate.id).join(" | ")}]`,
577
- "warning",
578
- );
579
- return;
580
- }
581
- const current = ctx.model
582
- ? { provider: ctx.model.provider, id: ctx.model.id }
583
- : sessionDefaultModel;
584
- const proposed = preset.create(current);
585
- if (!proposed) {
586
- ctx.ui.notify(`Preset "${preset.label}" requires a current model.`, "warning");
587
- return;
581
+ let proposed: GoalaConfig | undefined;
582
+ if (choice === "custom") {
583
+ proposed = await selectModelRoles(ctx, config);
584
+ if (!proposed) return;
585
+ } else {
586
+ const preset = GOALA_SETUP_PRESETS.find((candidate) => candidate.id === choice);
587
+ if (!preset) {
588
+ ctx.ui.notify(
589
+ `Usage: /goala-setup [status | custom | ${GOALA_SETUP_PRESETS.map((candidate) => candidate.id).join(" | ")}]`,
590
+ "warning",
591
+ );
592
+ return;
593
+ }
594
+ const current = ctx.model
595
+ ? { provider: ctx.model.provider, id: ctx.model.id }
596
+ : sessionDefaultModel;
597
+ proposed = preset.create(current);
598
+ if (!proposed) {
599
+ ctx.ui.notify(`Preset "${preset.label}" requires a current model.`, "warning");
600
+ return;
601
+ }
588
602
  }
589
603
  const missing = configuredModels(proposed).filter(
590
604
  (model) => !ctx.modelRegistry.find(model.provider, model.id),
@@ -605,7 +619,7 @@ Do not edit files or rely on executor claims. Finish with submit_step_verificati
605
619
  }
606
620
 
607
621
  pi.registerCommand("goala-setup", {
608
- description: `Configure model roles: /goala-setup [status | ${GOALA_SETUP_PRESETS.map((preset) => preset.id).join(" | ")}]`,
622
+ description: `Configure model roles: /goala-setup [status | custom | ${GOALA_SETUP_PRESETS.map((preset) => preset.id).join(" | ")}]`,
609
623
  handler: configureGoala,
610
624
  });
611
625
 
@@ -0,0 +1,109 @@
1
+ import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
+ import type {
3
+ GoalaConfig,
4
+ ModelProfile,
5
+ ThinkingLevel,
6
+ } from "./config.ts";
7
+
8
+ type ModelRole =
9
+ | "planner"
10
+ | "executor"
11
+ | "stepVerifier"
12
+ | "verifier"
13
+ | "fallbackExecutor";
14
+
15
+ const MODEL_ROLES: ReadonlyArray<{ key: ModelRole; label: string }> = [
16
+ { key: "planner", label: "Planner" },
17
+ { key: "executor", label: "Executor" },
18
+ { key: "stepVerifier", label: "Step verifier" },
19
+ { key: "verifier", label: "Final verifier" },
20
+ { key: "fallbackExecutor", label: "Repair fallback" },
21
+ ];
22
+
23
+ const THINKING_LEVELS: readonly ThinkingLevel[] = [
24
+ "off",
25
+ "minimal",
26
+ "low",
27
+ "medium",
28
+ "high",
29
+ "xhigh",
30
+ "max",
31
+ ];
32
+
33
+ function profileLabel(profile: ModelProfile): string {
34
+ return `${profile.provider}/${profile.model}:${profile.thinkingLevel}`;
35
+ }
36
+
37
+ export async function selectModelRoles(
38
+ ctx: ExtensionCommandContext,
39
+ current: GoalaConfig,
40
+ ): Promise<GoalaConfig | undefined> {
41
+ if (!ctx.hasUI) {
42
+ ctx.ui.notify("Per-phase model setup requires interactive Pi.", "warning");
43
+ return undefined;
44
+ }
45
+
46
+ try {
47
+ await ctx.modelRegistry.refresh();
48
+ } catch {
49
+ // The current registry can still contain usable cached and built-in models.
50
+ }
51
+ const available = ctx.modelRegistry.getAvailable();
52
+ if (available.length === 0) {
53
+ ctx.ui.notify(
54
+ "No authenticated models are available. Run /login, then retry /goala-setup.",
55
+ "warning",
56
+ );
57
+ return undefined;
58
+ }
59
+
60
+ const proposed = structuredClone(current);
61
+ for (const role of MODEL_ROLES) {
62
+ const existing = proposed[role.key];
63
+ const ordered = [...available].sort((left, right) => {
64
+ const leftCurrent =
65
+ left.provider === existing.provider && left.id === existing.model;
66
+ const rightCurrent =
67
+ right.provider === existing.provider && right.id === existing.model;
68
+ if (leftCurrent !== rightCurrent) return leftCurrent ? -1 : 1;
69
+ return `${left.provider}/${left.id}`.localeCompare(`${right.provider}/${right.id}`);
70
+ });
71
+ const choices = ordered.map((model) => {
72
+ const provider = ctx.modelRegistry.getProviderDisplayName(model.provider);
73
+ const name = model.name && model.name !== model.id ? `${model.name} · ` : "";
74
+ return `${provider} · ${name}${model.provider}/${model.id}`;
75
+ });
76
+ const selected = await ctx.ui.select(
77
+ `${role.label} model\nCurrent: ${profileLabel(existing)}`,
78
+ choices,
79
+ );
80
+ if (!selected) return undefined;
81
+ const model = ordered[choices.indexOf(selected)];
82
+ if (!model) return undefined;
83
+
84
+ let thinkingLevel: ThinkingLevel = "off";
85
+ if (model.reasoning) {
86
+ const selectedThinking = await ctx.ui.select(
87
+ `${role.label} reasoning\n${model.provider}/${model.id}`,
88
+ [...THINKING_LEVELS],
89
+ );
90
+ if (!selectedThinking) return undefined;
91
+ thinkingLevel = selectedThinking as ThinkingLevel;
92
+ }
93
+
94
+ const profile: ModelProfile = {
95
+ provider: model.provider,
96
+ model: model.id,
97
+ thinkingLevel,
98
+ };
99
+ if (role.key === "fallbackExecutor") {
100
+ proposed.fallbackExecutor = {
101
+ ...profile,
102
+ afterRepairCycle: current.fallbackExecutor.afterRepairCycle,
103
+ };
104
+ } else {
105
+ proposed[role.key] = profile;
106
+ }
107
+ }
108
+ return proposed;
109
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goala",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Goal-oriented agent lifecycle, evidence, and verified memory for Pi.",
5
5
  "type": "module",
6
6
  "author": "Barry King",