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 +9 -0
- package/README.md +10 -3
- package/docs/architecture.md +5 -1
- package/docs/configuration.md +14 -2
- package/extensions/goala/config.ts +103 -31
- package/extensions/goala/index.ts +34 -20
- package/extensions/goala/setup.ts +109 -0
- package/package.json +1 -1
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.
|
|
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.
|
|
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.
|
|
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,
|
package/docs/architecture.md
CHANGED
|
@@ -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
|
package/docs/configuration.md
CHANGED
|
@@ -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":
|
|
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:
|
|
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:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
81
|
-
configVersion:
|
|
127
|
+
...current,
|
|
128
|
+
configVersion: 2,
|
|
82
129
|
reviewPolicy: parsed.reviewPolicy === "per-step" ? "per-step" : "final",
|
|
83
|
-
planner:
|
|
84
|
-
|
|
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
|
-
...
|
|
87
|
-
|
|
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:
|
|
90
|
-
|
|
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
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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:
|
|
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.
|
|
182
|
-
`Executor: ${config.
|
|
183
|
-
`Step verifier: ${config.
|
|
184
|
-
`Verifier: ${config.
|
|
185
|
-
`Fallback: ${config.
|
|
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(
|
|
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: ${
|
|
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: ${
|
|
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(
|
|
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
|
-
|
|
574
|
-
if (
|
|
575
|
-
ctx
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
);
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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
|
+
}
|