@gtrabanco/pi-agentic-workflow 0.15.3 → 0.16.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/README.md +49 -0
- package/dist/conductor/loop.js +23 -1
- package/dist/conductor/types.d.ts +6 -0
- package/dist/config/defaults.js +11 -0
- package/dist/config/merge.js +93 -1
- package/dist/config/profiles.d.ts +36 -0
- package/dist/config/profiles.js +102 -0
- package/dist/config/recommended.d.ts +14 -0
- package/dist/config/recommended.js +28 -0
- package/dist/config/schema.js +159 -3
- package/dist/config/types.d.ts +52 -0
- package/dist/config/types.js +10 -0
- package/dist/extension/conductor-command.d.ts +2 -5
- package/dist/extension/conductor-command.js +18 -6
- package/dist/extension/factory.d.ts +2 -0
- package/dist/extension/factory.js +10 -3
- package/dist/routing/dispatch.d.ts +9 -2
- package/dist/routing/dispatch.js +115 -46
- package/dist/routing/state.d.ts +26 -0
- package/dist/routing/state.js +67 -1
- package/dist/routing/types.d.ts +7 -0
- package/dist/settings/console.d.ts +7 -0
- package/dist/settings/console.js +214 -35
- package/dist/settings/view.d.ts +3 -1
- package/dist/settings/view.js +8 -1
- package/package.json +1 -1
package/dist/settings/console.js
CHANGED
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
// a scope whose file does not parse cannot be overwritten (that file is evidence
|
|
8
8
|
// the operator must fix, not noise to clobber), and the project scope is refused
|
|
9
9
|
// while the project is untrusted — the same gate the loader applies (AC13).
|
|
10
|
+
//
|
|
11
|
+
// P5 (feature 63): the flow is scope → profile → routes. Every command operates
|
|
12
|
+
// on the active profile's route (for "default" this is top-level; for a named
|
|
13
|
+
// profile it is `profiles.<name>`). The profile defaults to "default", so
|
|
14
|
+
// existing tests that never answer a profile prompt keep working unchanged.
|
|
10
15
|
import { loadConfig, configFilePaths } from "../config/load.js";
|
|
11
16
|
import { effectiveRoute } from "../config/merge.js";
|
|
12
17
|
import { parseConfigFile, parseModelReference } from "../config/schema.js";
|
|
@@ -32,6 +37,13 @@ export const prompts = {
|
|
|
32
37
|
clearOverride: "Clear a command override",
|
|
33
38
|
bulkApply: "Apply one route to several commands",
|
|
34
39
|
bulkClear: "Clear several overrides",
|
|
40
|
+
switchProfile: "Switch the profile being edited",
|
|
41
|
+
rotateProfile: "Rotate the active profile",
|
|
42
|
+
toggleRecommended: "Toggle recommended models",
|
|
43
|
+
profile: "Which profile?",
|
|
44
|
+
newProfile: "Name the new profile",
|
|
45
|
+
profileBuiltIn: (name) => `The "${name}" profile is built into the package and cannot be edited — create a profile to override it.`,
|
|
46
|
+
profileNamed: (name) => `Editing profile "${name}"`,
|
|
35
47
|
addAnother: "Add another?",
|
|
36
48
|
policy: "Set the unavailable-route policy",
|
|
37
49
|
settle: "Set the post-command model keep/restore policy",
|
|
@@ -57,23 +69,27 @@ const INHERIT = "inherit";
|
|
|
57
69
|
export async function runSettingsConsole(deps) {
|
|
58
70
|
const paths = configFilePaths(deps.agentDir, deps.cwd);
|
|
59
71
|
const merged = loadConfig({ agentDir: deps.agentDir, cwd: deps.cwd, projectTrusted: deps.projectTrusted, readFile: deps.readFile });
|
|
60
|
-
deps.ui.notify(renderMergedConfig(merged, deps.commands).
|
|
61
|
-
|
|
62
|
-
|
|
72
|
+
deps.ui.notify(renderMergedConfig(merged, deps.commands, merged.config.profiles?.nan !== undefined || (deps.models ?? []).some((m) => m.startsWith("nan/"))
|
|
73
|
+
? { providerAvailable: (provider) => (deps.models ?? []).some((m) => m.startsWith(`${provider}/`)) }
|
|
74
|
+
: undefined).join("\n"), "info");
|
|
63
75
|
const opened = await openAScope(deps, paths.global, paths.project);
|
|
64
76
|
if (!opened)
|
|
65
77
|
return { status: "cancelled", edited: false };
|
|
66
78
|
const { scope, path, original } = opened;
|
|
67
79
|
let draft = original;
|
|
80
|
+
let active = "default";
|
|
68
81
|
for (;;) {
|
|
69
82
|
// Only offered while a turn actually holds the latch: an option that does
|
|
70
83
|
// nothing is how a console becomes noise.
|
|
71
84
|
const choice = await deps.ui.select(prompts.menu, [
|
|
85
|
+
prompts.switchProfile,
|
|
72
86
|
prompts.setDefaultRoute,
|
|
73
87
|
prompts.setOverride,
|
|
74
88
|
prompts.clearOverride,
|
|
75
89
|
prompts.bulkApply,
|
|
76
90
|
prompts.bulkClear,
|
|
91
|
+
prompts.rotateProfile,
|
|
92
|
+
prompts.toggleRecommended,
|
|
77
93
|
prompts.policy,
|
|
78
94
|
prompts.settle,
|
|
79
95
|
...(deps.routing?.inFlight() ? [prompts.undoInFlight] : []),
|
|
@@ -88,57 +104,172 @@ export async function runSettingsConsole(deps) {
|
|
|
88
104
|
if (choice === undefined || choice === prompts.cancel) {
|
|
89
105
|
return { status: "cancelled", edited: dirty(draft, original) ? await discard(deps.ui) : false };
|
|
90
106
|
}
|
|
107
|
+
// Profile: switch to another profile (scope → profile → routes)
|
|
108
|
+
if (choice === prompts.switchProfile) {
|
|
109
|
+
const picked = await pickProfile(deps, draft, { allowNew: true });
|
|
110
|
+
if (picked === undefined)
|
|
111
|
+
continue;
|
|
112
|
+
// P5: the built-in "nan" profile cannot be edited — create a profile
|
|
113
|
+
// to override it.
|
|
114
|
+
if (picked === "nan" && draft.profiles?.nan === undefined) {
|
|
115
|
+
deps.ui.notify(prompts.profileBuiltIn("nan"), "warning");
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (picked !== "default" && !Object.prototype.hasOwnProperty.call(draft.profiles ?? {}, picked)) {
|
|
119
|
+
draft = { ...draft, profiles: { ...(draft.profiles ?? {}), [picked]: {} } };
|
|
120
|
+
}
|
|
121
|
+
active = picked;
|
|
122
|
+
deps.ui.notify(prompts.profileNamed(active), "info");
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
// Rotate: move a profile to the front of profileOrder, clear demotion
|
|
126
|
+
if (choice === prompts.rotateProfile) {
|
|
127
|
+
const picked = await pickProfile(deps, draft, { allowNew: false });
|
|
128
|
+
if (picked === undefined)
|
|
129
|
+
continue;
|
|
130
|
+
const base = draft.profileOrder && draft.profileOrder.length > 0 ? draft.profileOrder : ["default"];
|
|
131
|
+
draft = { ...draft, profileOrder: [picked, ...base.filter((n) => n !== picked)] };
|
|
132
|
+
deps.routing?.clearProfileDemotion?.();
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
// Toggle recommended models
|
|
136
|
+
if (choice === prompts.toggleRecommended) {
|
|
137
|
+
const current = draft.recommendedModels ?? merged.config.recommendedModels;
|
|
138
|
+
draft = { ...draft, recommendedModels: !current };
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
// Set default route on the active profile
|
|
91
142
|
if (choice === prompts.setDefaultRoute) {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
143
|
+
const target = active === "default" ? DEFAULT_ROUTE : `profile ${active} default`;
|
|
144
|
+
const routeFile = active === "default"
|
|
145
|
+
? (draft.default ?? {})
|
|
146
|
+
: (draft.profiles?.[active]?.default ?? {});
|
|
147
|
+
const currentRoute = {
|
|
148
|
+
model: routeFile.model ?? "inherit",
|
|
149
|
+
thinking: routeFile.thinking ?? "inherit",
|
|
150
|
+
};
|
|
151
|
+
const edited = await editRoute(deps, target, currentRoute);
|
|
152
|
+
if (edited) {
|
|
153
|
+
if (active === "default") {
|
|
154
|
+
draft = { ...draft, default: edited };
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
draft = {
|
|
158
|
+
...draft,
|
|
159
|
+
profiles: { ...(draft.profiles ?? {}), [active]: { ...(draft.profiles?.[active] ?? {}), default: edited } },
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
95
163
|
continue;
|
|
96
164
|
}
|
|
165
|
+
// Set override on active profile's commands
|
|
97
166
|
if (choice === prompts.setOverride) {
|
|
98
|
-
|
|
167
|
+
// Use the full command registry so operators can add new overrides (AC4/OB-3).
|
|
168
|
+
const cmdNames = [...new Set([...deps.commands, ...Object.keys(draft.commands ?? {})])];
|
|
169
|
+
const name = await pickCommand(deps, cmdNames);
|
|
99
170
|
if (name === undefined)
|
|
100
171
|
continue;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
172
|
+
// The initial value comes from the merged config so the picker shows the
|
|
173
|
+
// value in force, matching the original `currentFor(name)` behavior.
|
|
174
|
+
const effective = effectiveRoute(merged.config, name);
|
|
175
|
+
const routeFile = active === "default"
|
|
176
|
+
? (draft.commands?.[name] ?? {})
|
|
177
|
+
: (draft.profiles?.[active]?.commands?.[name] ?? {});
|
|
178
|
+
const currentRoute = {
|
|
179
|
+
model: typeof routeFile?.model !== "undefined" ? routeFile.model : effective.model,
|
|
180
|
+
thinking: typeof routeFile?.thinking !== "undefined" ? routeFile.thinking : effective.thinking,
|
|
181
|
+
};
|
|
182
|
+
const route = await editRoute(deps, name, currentRoute);
|
|
183
|
+
if (route) {
|
|
184
|
+
if (active === "default") {
|
|
185
|
+
draft = { ...draft, commands: { ...draft.commands, [name]: route } };
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
draft = {
|
|
189
|
+
...draft,
|
|
190
|
+
profiles: {
|
|
191
|
+
...(draft.profiles ?? {}),
|
|
192
|
+
[active]: { ...(draft.profiles?.[active] ?? {}), commands: { ...(draft.profiles?.[active]?.commands ?? {}), [name]: route } },
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
104
197
|
continue;
|
|
105
198
|
}
|
|
199
|
+
// Clear override on active profile's commands
|
|
106
200
|
if (choice === prompts.clearOverride) {
|
|
107
|
-
const
|
|
201
|
+
const cmdNames = Object.keys(profileOf(draft, active).commands ?? {});
|
|
202
|
+
const name = await pickCommand(deps, cmdNames);
|
|
108
203
|
if (name === undefined)
|
|
109
204
|
continue;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
205
|
+
if (active === "default") {
|
|
206
|
+
const rest = { ...draft.commands };
|
|
207
|
+
delete rest[name];
|
|
208
|
+
draft = { ...draft, commands: rest };
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
const profile = draft.profiles?.[active] ?? {};
|
|
212
|
+
const rest = { ...(profile.commands ?? {}) };
|
|
213
|
+
delete rest[name];
|
|
214
|
+
draft = { ...draft, profiles: { ...(draft.profiles ?? {}), [active]: { ...profile, commands: rest } } };
|
|
215
|
+
}
|
|
113
216
|
continue;
|
|
114
217
|
}
|
|
218
|
+
// Bulk apply on active profile's commands
|
|
115
219
|
if (choice === prompts.bulkApply) {
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// authoritative availability gate).
|
|
120
|
-
const selected = await pickCommandsMulti(deps, commandChoices(deps, draft));
|
|
220
|
+
// Use the full command registry so operators can apply to new commands (AC4/OB-3).
|
|
221
|
+
const cmdNames = [...new Set([...deps.commands, ...Object.keys(draft.commands ?? {})])];
|
|
222
|
+
const selected = await pickCommandsMulti(deps, cmdNames);
|
|
121
223
|
if (selected === undefined || selected.length === 0)
|
|
122
224
|
continue;
|
|
123
|
-
const
|
|
225
|
+
const routeFile = active === "default"
|
|
226
|
+
? (draft.commands?.[selected[0]] ?? {})
|
|
227
|
+
: (draft.profiles?.[active]?.commands?.[selected[0]] ?? {});
|
|
228
|
+
const currentRoute = {
|
|
229
|
+
model: routeFile.model ?? "inherit",
|
|
230
|
+
thinking: routeFile.thinking ?? "inherit",
|
|
231
|
+
};
|
|
232
|
+
const route = await editRoute(deps, selected[0], currentRoute);
|
|
124
233
|
if (route === undefined)
|
|
125
234
|
continue;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
235
|
+
if (active === "default") {
|
|
236
|
+
const nextCommands = { ...draft.commands };
|
|
237
|
+
for (const name of selected) {
|
|
238
|
+
nextCommands[name] = route;
|
|
239
|
+
warnMissingModel(deps, name, route.model ?? INHERIT);
|
|
240
|
+
}
|
|
241
|
+
draft = { ...draft, commands: nextCommands };
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
const profile = draft.profiles?.[active] ?? {};
|
|
245
|
+
const nextCommands = { ...profile.commands };
|
|
246
|
+
for (const name of selected) {
|
|
247
|
+
nextCommands[name] = route;
|
|
248
|
+
warnMissingModel(deps, name, route.model ?? INHERIT);
|
|
249
|
+
}
|
|
250
|
+
draft = { ...draft, profiles: { ...(draft.profiles ?? {}), [active]: { ...profile, commands: nextCommands } } };
|
|
130
251
|
}
|
|
131
|
-
draft = { ...draft, commands: nextCommands };
|
|
132
252
|
continue;
|
|
133
253
|
}
|
|
254
|
+
// Bulk clear on active profile's commands
|
|
134
255
|
if (choice === prompts.bulkClear) {
|
|
135
|
-
const
|
|
256
|
+
const cmdNames = Object.keys(profileOf(draft, active).commands ?? {});
|
|
257
|
+
const names = await pickCommandsMulti(deps, cmdNames);
|
|
136
258
|
if (names === undefined || names.length === 0)
|
|
137
259
|
continue;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
260
|
+
if (active === "default") {
|
|
261
|
+
const rest = { ...draft.commands };
|
|
262
|
+
for (const name of names)
|
|
263
|
+
delete rest[name];
|
|
264
|
+
draft = { ...draft, commands: rest };
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
const profile = draft.profiles?.[active] ?? {};
|
|
268
|
+
const rest = { ...(profile.commands ?? {}) };
|
|
269
|
+
for (const name of names)
|
|
270
|
+
delete rest[name];
|
|
271
|
+
draft = { ...draft, profiles: { ...(draft.profiles ?? {}), [active]: { ...profile, commands: rest } } };
|
|
272
|
+
}
|
|
142
273
|
continue;
|
|
143
274
|
}
|
|
144
275
|
if (choice === prompts.policy) {
|
|
@@ -154,7 +285,7 @@ export async function runSettingsConsole(deps) {
|
|
|
154
285
|
continue;
|
|
155
286
|
}
|
|
156
287
|
if (choice === prompts.save) {
|
|
157
|
-
const saved = await saveScope(deps, scope, path, draft);
|
|
288
|
+
const saved = await saveScope(deps, scope, path, draft, merged);
|
|
158
289
|
if (saved)
|
|
159
290
|
return { status: "saved", scope, path, file: clean(draft) };
|
|
160
291
|
continue;
|
|
@@ -167,6 +298,38 @@ export async function runSettingsConsole(deps) {
|
|
|
167
298
|
* without being read, and a scope whose file does not parse is refused so the
|
|
168
299
|
* operator's own file survives (AC10, AC13).
|
|
169
300
|
*/
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
// Profile helpers (P5, feature 63)
|
|
303
|
+
// ---------------------------------------------------------------------------
|
|
304
|
+
/** The profile file for a named profile or the default. */
|
|
305
|
+
function profileOf(draft, name) {
|
|
306
|
+
if (name === "default")
|
|
307
|
+
return { ...(draft.default ? { default: draft.default } : {}), ...(draft.commands ? { commands: draft.commands } : {}) };
|
|
308
|
+
return draft.profiles?.[name] ?? {};
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Pick a profile from the list. `allowNew` adds "Name the new profile" to the
|
|
312
|
+
* options; the input must match a lowercase slug pattern.
|
|
313
|
+
*/
|
|
314
|
+
async function pickProfile(deps, draft, { allowNew }) {
|
|
315
|
+
const names = ["default", ...Object.keys(draft.profiles ?? {}).filter((n) => n !== "default")];
|
|
316
|
+
// "nan" is always listed (built-in unless user-defined)
|
|
317
|
+
if (!names.includes("nan"))
|
|
318
|
+
names.push("nan");
|
|
319
|
+
const options = allowNew ? [...names, prompts.newProfile] : names;
|
|
320
|
+
const picked = await pagedSelect((title, list) => deps.ui.select(title, list), prompts.profile, options);
|
|
321
|
+
if (picked === undefined)
|
|
322
|
+
return undefined;
|
|
323
|
+
if (picked === prompts.newProfile) {
|
|
324
|
+
const name = await deps.ui.input(prompts.newProfile, "lowercase slug");
|
|
325
|
+
if (name === undefined || !/^[a-z0-9][a-z0-9._-]*$/u.test(name.trim())) {
|
|
326
|
+
deps.ui.notify("Rejected: a profile name must be a lowercase slug.", "error");
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
return name.trim();
|
|
330
|
+
}
|
|
331
|
+
return picked;
|
|
332
|
+
}
|
|
170
333
|
async function openAScope(deps, globalPath, projectPath) {
|
|
171
334
|
for (;;) {
|
|
172
335
|
const label = await deps.ui.select(prompts.scope, [GLOBAL_LABEL, PROJECT_LABEL]);
|
|
@@ -439,11 +602,14 @@ function warnMissingModel(deps, target, model) {
|
|
|
439
602
|
}
|
|
440
603
|
}
|
|
441
604
|
}
|
|
442
|
-
function
|
|
443
|
-
return [...new Set([...deps.commands, ...Object.keys(draft.commands ?? {})])];
|
|
444
|
-
}
|
|
445
|
-
async function saveScope(deps, scope, path, draft) {
|
|
605
|
+
async function saveScope(deps, scope, path, draft, merged) {
|
|
446
606
|
const file = clean(draft);
|
|
607
|
+
// P5: materialize recommendedModels when the nan provider is available but
|
|
608
|
+
// the key is absent — the save writes the effective value (AC11).
|
|
609
|
+
const nanAvailable = (deps.models ?? []).some((m) => m.startsWith("nan/"));
|
|
610
|
+
if (file.recommendedModels === undefined && nanAvailable) {
|
|
611
|
+
file.recommendedModels = merged.config.recommendedModels;
|
|
612
|
+
}
|
|
447
613
|
const text = `${JSON.stringify(file, null, 2)}\n`;
|
|
448
614
|
const problems = problemsFor(file, scope);
|
|
449
615
|
if (problems.length > 0) {
|
|
@@ -477,6 +643,8 @@ function describeRouting(file) {
|
|
|
477
643
|
const routes = [
|
|
478
644
|
file.default ? `default: ${file.default.model ?? "inherit"} / ${file.default.thinking ?? "inherit"}` : "default: inherit / inherit",
|
|
479
645
|
...Object.entries(file.commands ?? {}).map(([name, route]) => `${name}: ${route.model ?? "inherit"} / ${route.thinking ?? "inherit"}`),
|
|
646
|
+
...(file.profileOrder && file.profileOrder.length > 0 ? [`profiles: ${file.profileOrder.join(" → ")}`] : []),
|
|
647
|
+
...(file.recommendedModels !== undefined ? [`recommended models: ${file.recommendedModels}`] : []),
|
|
480
648
|
`unavailable: ${file.onUnavailableRoute ?? "stop"}`,
|
|
481
649
|
`settle: ${file.onSettle ?? "keep"}`,
|
|
482
650
|
];
|
|
@@ -504,6 +672,17 @@ function clean(draft) {
|
|
|
504
672
|
file.onUnavailableRoute = draft.onUnavailableRoute;
|
|
505
673
|
if (draft.onSettle)
|
|
506
674
|
file.onSettle = draft.onSettle;
|
|
675
|
+
if (draft.recommendedModels !== undefined)
|
|
676
|
+
file.recommendedModels = draft.recommendedModels;
|
|
677
|
+
if (draft.profiles && Object.keys(draft.profiles).length > 0) {
|
|
678
|
+
file.profiles = structuredClone(draft.profiles);
|
|
679
|
+
}
|
|
680
|
+
if (draft.profileOrder && draft.profileOrder.length > 0) {
|
|
681
|
+
file.profileOrder = [...draft.profileOrder];
|
|
682
|
+
}
|
|
683
|
+
if (draft.profileFallback && Object.keys(draft.profileFallback).length > 0) {
|
|
684
|
+
file.profileFallback = { ...draft.profileFallback };
|
|
685
|
+
}
|
|
507
686
|
return file;
|
|
508
687
|
}
|
|
509
688
|
function parseFile(text, scope) {
|
package/dist/settings/view.d.ts
CHANGED
|
@@ -8,4 +8,6 @@ export declare const DEFAULT_ROUTE = "the default route";
|
|
|
8
8
|
*/
|
|
9
9
|
export declare function routePath(target: string): string;
|
|
10
10
|
/** What the operator reads when the console opens. */
|
|
11
|
-
export declare function renderMergedConfig(loaded: LoadedConfig, commands: readonly string[]
|
|
11
|
+
export declare function renderMergedConfig(loaded: LoadedConfig, commands: readonly string[], opts?: {
|
|
12
|
+
providerAvailable?: (provider: string) => boolean;
|
|
13
|
+
}): string[];
|
package/dist/settings/view.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/settings/view.ts
|
|
2
2
|
// The settings console's display half (AC10, SPEC S4): what is in effect right
|
|
3
3
|
// now, and which files the values came from.
|
|
4
|
+
import { effectiveProfileOrder } from "../config/profiles.js";
|
|
4
5
|
/** The label for the unqualified route, so `default` is never mistaken for a command name. */
|
|
5
6
|
export const DEFAULT_ROUTE = "the default route";
|
|
6
7
|
/**
|
|
@@ -12,9 +13,15 @@ export function routePath(target) {
|
|
|
12
13
|
return target === DEFAULT_ROUTE ? "$.default" : `$.commands.${target}`;
|
|
13
14
|
}
|
|
14
15
|
/** What the operator reads when the console opens. */
|
|
15
|
-
export function renderMergedConfig(loaded, commands) {
|
|
16
|
+
export function renderMergedConfig(loaded, commands, opts) {
|
|
17
|
+
const order = effectiveProfileOrder(loaded.config, {
|
|
18
|
+
providerAvailable: opts?.providerAvailable ?? (() => false),
|
|
19
|
+
});
|
|
16
20
|
return [
|
|
17
21
|
"agentic-workflow routing — what each command runs on right now",
|
|
22
|
+
` profiles: ${order.join(" → ")}`,
|
|
23
|
+
` active profile: ${order[0] ?? "default"}`,
|
|
24
|
+
` recommended models: ${loaded.config.recommendedModels ? "on" : "off"}${loaded.config.recommendedModels && opts?.providerAvailable?.("nan") ? " (built-in nan profile in the chain)" : ""}`,
|
|
18
25
|
...configLines(loaded.config, commands),
|
|
19
26
|
...problems(loaded.problems),
|
|
20
27
|
];
|
package/package.json
CHANGED