@nodeint/migent 0.1.6 → 0.1.7
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 +5 -1
- package/dist/commands/agent.js +3 -3
- package/dist/commands/config.d.ts +3 -3
- package/dist/commands/config.js +54 -54
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/execute.js +2 -2
- package/dist/commands/init.d.ts +3 -3
- package/dist/commands/init.js +20 -20
- package/dist/commands/prepare.js +1 -0
- package/dist/commands/provider.d.ts +10 -0
- package/dist/commands/provider.js +77 -0
- package/dist/commands/sessions.js +1 -0
- package/dist/display.d.ts +16 -0
- package/dist/display.js +40 -1
- package/dist/hooks.d.ts +2 -0
- package/dist/hooks.js +1 -0
- package/dist/index.js +1 -0
- package/dist/migrate.d.ts +3 -3
- package/dist/migrate.js +26 -24
- package/dist/program.js +16 -3
- package/dist/provider.d.ts +13 -11
- package/dist/provider.js +38 -33
- package/dist/session-row.d.ts +2 -0
- package/dist/session-row.js +2 -0
- package/dist/templates/migent/SKILL.md +10 -6
- package/dist/templates/migent/references/config.md +4 -0
- package/dist/templates/migent/references/schema-config.yaml +10 -1
- package/dist/templates/migent/references/schema-workflow.yaml +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Migent
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
+
<img src="assets/stylized-m.svg" alt="Migent logo" width="150" /><br>
|
|
4
5
|
<strong>Autonomous, multi-stage engineering pipelines for your coding agents.</strong><br>
|
|
5
6
|
Repeatable workflows. Multi-agent orchestration. Native CLI execution. Full observability.
|
|
6
7
|
</p>
|
|
@@ -73,7 +74,7 @@ migent session metrics <session-id> --json
|
|
|
73
74
|
| :--- | :--- |
|
|
74
75
|
| **Manual handoffs:** Copying plans and pasting stack traces across conversations. | **Automated handoffs:** Upstream artifacts and diffs pass automatically into downstream stages. |
|
|
75
76
|
| **Single-agent bottleneck:** One model handles planning, coding, and review, accumulating context bloat. | **Specialized roles:** Multi-agent pipeline with dedicated models per role (e.g., Planner $\rightarrow$ Coder $\rightarrow$ Reviewer). |
|
|
76
|
-
| **Vendor lock-in & wrappers:** Restricted to one provider or requiring custom API
|
|
77
|
+
| **Vendor lock-in & wrappers:** Restricted to one provider or requiring custom API providers. | **Native CLI execution:** Drives your installed agent CLIs (`agy`, `claude`, `codex`, `grok`) directly in your workspace. |
|
|
77
78
|
| **Blind trust:** Agents claim "all tests pass" without actually running project commands. | **Verification gates:** Real local checks (`pnpm test`, `pytest`, `cargo test`) must succeed before advancing. |
|
|
78
79
|
| **Fragile one-off prompts:** Team members execute tasks with inconsistent, ad-hoc instructions. | **Process as code:** Workflows live in Git (`.migent/workflows/`), making best practices versioned and repeatable. |
|
|
79
80
|
| **No execution visibility:** No record of how long tasks took, why checks failed, or how many retries occurred. | **Telemetry & Metrics:** Append-only event logs, live streaming, and full JSON metrics for duration, retries, and token cost. |
|
|
@@ -117,6 +118,8 @@ Decouple **what** a stage does from **which model** executes it. Migent drives t
|
|
|
117
118
|
| **Google Antigravity** | `agy` | Deep repository analysis, plan generation & review |
|
|
118
119
|
| **OpenAI Codex** | `codex` | Fast, focused implementation & unit test authoring |
|
|
119
120
|
| **xAI Grok** | `grok` | Independent logic review & edge case discovery |
|
|
121
|
+
| **OpenCode** | `opencode` | Multi-vendor models behind one CLI, for any role |
|
|
122
|
+
| **BytePlus ModelArk** | none (set `client` per model) | BytePlus models run through another installed CLI, e.g. `opencode` |
|
|
120
123
|
|
|
121
124
|
Switch models anytime in `.migent/config.yaml` without touching your workflow logic.
|
|
122
125
|
|
|
@@ -210,6 +213,7 @@ outputs:
|
|
|
210
213
|
# Setup & Health
|
|
211
214
|
migent init --preset plan # Initialize starter configuration
|
|
212
215
|
migent doctor # Validate configuration, schemas, and CLI availability
|
|
216
|
+
migent provider list # List registered provider providers and their available models
|
|
213
217
|
migent skill install # Install the Migent Skill for coding agents (.agents/skills/migent/)
|
|
214
218
|
|
|
215
219
|
# Execution
|
package/dist/commands/agent.js
CHANGED
|
@@ -5,7 +5,7 @@ import { printAgent, printError, printNotice, printResumeCommand } from "../disp
|
|
|
5
5
|
import { cliHooks } from "../hooks.js";
|
|
6
6
|
import { requireCurrentSchema } from "../migrate.js";
|
|
7
7
|
import { bindCancellationSignals } from "../signals.js";
|
|
8
|
-
import {
|
|
8
|
+
import { providerMap, parseOptions } from "../provider.js";
|
|
9
9
|
import { resolvePrompt } from "../workspace.js";
|
|
10
10
|
export async function dispatchAgent(workspace, args) {
|
|
11
11
|
requireCurrentSchema(workspace);
|
|
@@ -65,7 +65,7 @@ export async function dispatchAgent(workspace, args) {
|
|
|
65
65
|
let result;
|
|
66
66
|
let resumedMany = null;
|
|
67
67
|
try {
|
|
68
|
-
const runner = args.runner ?? cliHooks.runner ?? new AgentToolRunner(
|
|
68
|
+
const runner = args.runner ?? cliHooks.runner ?? new AgentToolRunner(providerMap());
|
|
69
69
|
const waitedUntil = new Map();
|
|
70
70
|
const rerun = (prompt, resumeId) => runner.run({
|
|
71
71
|
provider: target.provider,
|
|
@@ -206,7 +206,7 @@ export async function continueAgent(workspace, args) {
|
|
|
206
206
|
printNotice(`agent ${role} · ${profile.provider}/${profile.model} (${optionLabel}).`);
|
|
207
207
|
const unbind = bindCancellationSignals();
|
|
208
208
|
try {
|
|
209
|
-
const runner = args.runner ?? cliHooks.runner ?? new AgentToolRunner(
|
|
209
|
+
const runner = args.runner ?? cliHooks.runner ?? new AgentToolRunner(providerMap());
|
|
210
210
|
if (sessionStatus === "cancelled") {
|
|
211
211
|
return await runCancelledRole(workspace, args.sessionId, role, profile, runner, false, args.waitHooks);
|
|
212
212
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Provider } from "@nodeint/migent-kernel";
|
|
2
2
|
import { type RunCommand, type Which } from "../provider.js";
|
|
3
3
|
export interface ConfigPrompter {
|
|
4
4
|
select(message: string, choices: ReadonlyArray<readonly [string, string]>, defaultValue?: string | null): string | Promise<string>;
|
|
@@ -14,7 +14,7 @@ export declare function runConfigModel(workspace: string, options?: {
|
|
|
14
14
|
clearOptions?: readonly string[];
|
|
15
15
|
asJson?: boolean;
|
|
16
16
|
interactive?: boolean;
|
|
17
|
-
|
|
17
|
+
providers?: Record<string, Provider> | null;
|
|
18
18
|
which?: Which | null;
|
|
19
19
|
prompter?: ConfigPrompter | null;
|
|
20
20
|
runCommand?: RunCommand | null;
|
|
@@ -28,7 +28,7 @@ export declare function runConfigRole(workspace: string, options?: {
|
|
|
28
28
|
clearOptions?: readonly string[];
|
|
29
29
|
asJson?: boolean;
|
|
30
30
|
interactive?: boolean;
|
|
31
|
-
|
|
31
|
+
providers?: Record<string, Provider> | null;
|
|
32
32
|
which?: Which | null;
|
|
33
33
|
prompter?: ConfigPrompter | null;
|
|
34
34
|
runCommand?: RunCommand | null;
|
package/dist/commands/config.js
CHANGED
|
@@ -6,7 +6,7 @@ import { printError } from "../display.js";
|
|
|
6
6
|
import { PromptCancelled } from "../errors.js";
|
|
7
7
|
import { cliHooks } from "../hooks.js";
|
|
8
8
|
import { requireCurrentSchema } from "../migrate.js";
|
|
9
|
-
import { acceptedOptions,
|
|
9
|
+
import { acceptedOptions, providerMap, asCatalog, detected, knownModel, loadCatalog, loadOptionValues, parseOptions, requireProvider, runProviderCommand, } from "../provider.js";
|
|
10
10
|
import { runDoctor } from "./doctor.js";
|
|
11
11
|
const NEW = "__new__";
|
|
12
12
|
const UNSET = Symbol("unset");
|
|
@@ -19,7 +19,7 @@ export async function runConfigModel(workspace, options = {}) {
|
|
|
19
19
|
try {
|
|
20
20
|
return await runModel(workspace, {
|
|
21
21
|
...options,
|
|
22
|
-
|
|
22
|
+
providers: options.providers ?? providerMap(),
|
|
23
23
|
which: options.which ?? cliHooks.which ?? whichSync,
|
|
24
24
|
prompter: options.prompter ?? new ClackConfigPrompter(),
|
|
25
25
|
runCommand: options.runCommand ?? runProviderCommand,
|
|
@@ -39,7 +39,7 @@ export async function runConfigRole(workspace, options = {}) {
|
|
|
39
39
|
try {
|
|
40
40
|
return await runRole(workspace, {
|
|
41
41
|
...options,
|
|
42
|
-
|
|
42
|
+
providers: options.providers ?? providerMap(),
|
|
43
43
|
which: options.which ?? cliHooks.which ?? whichSync,
|
|
44
44
|
prompter: options.prompter ?? new ClackConfigPrompter(),
|
|
45
45
|
runCommand: options.runCommand ?? runProviderCommand,
|
|
@@ -69,7 +69,7 @@ async function runModel(workspace, args) {
|
|
|
69
69
|
const mutating = provider !== null || optionUpdates !== UNSET;
|
|
70
70
|
if (key === null && !mutating) {
|
|
71
71
|
if (interactive) {
|
|
72
|
-
return interactiveModels(workspace, config, workflows, view, args.
|
|
72
|
+
return interactiveModels(workspace, config, workflows, view, args.providers, args.which, args.prompter, args.runCommand, asJson);
|
|
73
73
|
}
|
|
74
74
|
emitView(view, asJson, true, false);
|
|
75
75
|
return 0;
|
|
@@ -78,19 +78,19 @@ async function runModel(workspace, args) {
|
|
|
78
78
|
throw new ConfigEditError("Pass a name.");
|
|
79
79
|
if (!mutating) {
|
|
80
80
|
if (interactive) {
|
|
81
|
-
return editModel(workspace, config, workflows, view, key, args.
|
|
81
|
+
return editModel(workspace, config, workflows, view, key, args.providers, args.which, args.prompter, args.runCommand, asJson);
|
|
82
82
|
}
|
|
83
83
|
emitModels(selectedModels(view, [key]), asJson);
|
|
84
84
|
return 0;
|
|
85
85
|
}
|
|
86
86
|
let catalog = null;
|
|
87
87
|
if (provider !== null && model !== null) {
|
|
88
|
-
[provider, model, catalog] = await catalogModel(args.
|
|
88
|
+
[provider, model, catalog] = await catalogModel(args.providers, args.which, args.runCommand, provider, model);
|
|
89
89
|
}
|
|
90
90
|
if (optionUpdates !== UNSET) {
|
|
91
91
|
const currentProvider = provider ?? modelProvider(config, key);
|
|
92
92
|
const currentModel = model ?? loadModelTarget(config, key).model;
|
|
93
|
-
optionUpdates = await checkedOptions(args.
|
|
93
|
+
optionUpdates = await checkedOptions(args.providers, args.which, args.runCommand, currentProvider, currentModel, optionUpdates, provider !== null ? catalog : null);
|
|
94
94
|
}
|
|
95
95
|
const change = upsertModel(config, key, {
|
|
96
96
|
provider,
|
|
@@ -98,7 +98,7 @@ async function runModel(workspace, args) {
|
|
|
98
98
|
...(optionUpdates === UNSET ? {} : { options: optionUpdates }),
|
|
99
99
|
workflows,
|
|
100
100
|
});
|
|
101
|
-
validateChange(config, change.config, workflows, args.
|
|
101
|
+
validateChange(config, change.config, workflows, args.providers);
|
|
102
102
|
return await commit(workspace, change, { asJson, which: args.which });
|
|
103
103
|
}
|
|
104
104
|
async function runRole(workspace, args) {
|
|
@@ -119,7 +119,7 @@ async function runRole(workspace, args) {
|
|
|
119
119
|
const mutating = modelName !== null || provider !== null || optionUpdates !== UNSET;
|
|
120
120
|
if (roles.length === 0 && !mutating) {
|
|
121
121
|
if (interactive) {
|
|
122
|
-
return interactiveRoles(workspace, config, workflows, view, args.
|
|
122
|
+
return interactiveRoles(workspace, config, workflows, view, args.providers, args.which, args.prompter, args.runCommand, asJson);
|
|
123
123
|
}
|
|
124
124
|
emitView(view, asJson, false, true);
|
|
125
125
|
return 0;
|
|
@@ -132,7 +132,7 @@ async function runRole(workspace, args) {
|
|
|
132
132
|
if (!mutating) {
|
|
133
133
|
if (interactive && roles.length === 1) {
|
|
134
134
|
knownRole(view, roles[0] ?? "");
|
|
135
|
-
return pointRole(workspace, config, workflows, view, roles[0] ?? "", args.
|
|
135
|
+
return pointRole(workspace, config, workflows, view, roles[0] ?? "", args.providers, args.which, args.prompter, args.runCommand, asJson);
|
|
136
136
|
}
|
|
137
137
|
emitRoles(selectedRoles(view, roles), asJson);
|
|
138
138
|
return 0;
|
|
@@ -140,9 +140,9 @@ async function runRole(workspace, args) {
|
|
|
140
140
|
let change;
|
|
141
141
|
if (provider !== null && model !== null) {
|
|
142
142
|
let catalog;
|
|
143
|
-
[provider, model, catalog] = await catalogModel(args.
|
|
143
|
+
[provider, model, catalog] = await catalogModel(args.providers, args.which, args.runCommand, provider, model);
|
|
144
144
|
if (optionUpdates !== UNSET) {
|
|
145
|
-
optionUpdates = await checkedOptions(args.
|
|
145
|
+
optionUpdates = await checkedOptions(args.providers, args.which, args.runCommand, provider, model, optionUpdates, catalog);
|
|
146
146
|
}
|
|
147
147
|
change = placeRoleModel(config, roles[0] ?? "", {
|
|
148
148
|
provider,
|
|
@@ -155,7 +155,7 @@ async function runRole(workspace, args) {
|
|
|
155
155
|
change = assignRoles(config, roles, modelName, { workflows });
|
|
156
156
|
if (optionUpdates !== UNSET) {
|
|
157
157
|
const target = loadModelTarget(change.config, modelName);
|
|
158
|
-
optionUpdates = await checkedOptions(args.
|
|
158
|
+
optionUpdates = await checkedOptions(args.providers, args.which, args.runCommand, target.provider, target.model, optionUpdates);
|
|
159
159
|
const follow = setRoleOptions(change.config, roles[0] ?? "", optionUpdates, { workflows });
|
|
160
160
|
change = describeChange(config, follow.config, workflows);
|
|
161
161
|
}
|
|
@@ -164,40 +164,40 @@ async function runRole(workspace, args) {
|
|
|
164
164
|
const target = roleModel(config, roles[0] ?? "");
|
|
165
165
|
if (optionUpdates === UNSET)
|
|
166
166
|
throw new ConfigEditError("Pass a role.");
|
|
167
|
-
optionUpdates = await checkedOptions(args.
|
|
167
|
+
optionUpdates = await checkedOptions(args.providers, args.which, args.runCommand, target.provider, target.model, optionUpdates);
|
|
168
168
|
change = setRoleOptions(config, roles[0] ?? "", optionUpdates, { workflows });
|
|
169
169
|
}
|
|
170
|
-
validateChange(config, change.config, workflows, args.
|
|
170
|
+
validateChange(config, change.config, workflows, args.providers);
|
|
171
171
|
return await commit(workspace, change, { asJson, which: args.which });
|
|
172
172
|
}
|
|
173
|
-
async function interactiveModels(workspace, config, workflows, view,
|
|
173
|
+
async function interactiveModels(workspace, config, workflows, view, providers, which, prompter, runCommand, asJson) {
|
|
174
174
|
const choices = view.models.map((model) => [modelLabel(model), model.key]);
|
|
175
175
|
choices.push(["New model", NEW]);
|
|
176
176
|
const selected = await prompter.select("Model", choices, view.models[0]?.key ?? NEW);
|
|
177
177
|
if (selected === NEW)
|
|
178
|
-
return addModel(workspace, config, workflows, null,
|
|
179
|
-
return editModel(workspace, config, workflows, view, selected,
|
|
178
|
+
return addModel(workspace, config, workflows, null, providers, which, prompter, runCommand, asJson);
|
|
179
|
+
return editModel(workspace, config, workflows, view, selected, providers, which, prompter, runCommand, asJson);
|
|
180
180
|
}
|
|
181
|
-
async function editModel(workspace, config, workflows, view, key,
|
|
181
|
+
async function editModel(workspace, config, workflows, view, key, providers, which, prompter, runCommand, asJson) {
|
|
182
182
|
const current = selectedModels(view, [key])[0];
|
|
183
183
|
if (current === undefined)
|
|
184
184
|
throw new ConfigEditError(`Unknown name: ${key}.`);
|
|
185
|
-
const provider = await selectProvider(prompter,
|
|
186
|
-
const
|
|
187
|
-
const catalog = await loadCatalog(
|
|
185
|
+
const provider = await selectProvider(prompter, providers, which, "Provider", current.provider);
|
|
186
|
+
const impl = requireProvider(providers, provider);
|
|
187
|
+
const catalog = await loadCatalog(impl, which, runCommand);
|
|
188
188
|
const initial = catalog.ids.includes(current.model) ? current.model : (catalog.defaultId ?? undefined);
|
|
189
189
|
let modelId = knownModel(await prompter.select("Model", catalog.ids.map((item) => [item, item]), initial), catalog);
|
|
190
|
-
const stored = await promptedOptions(prompter,
|
|
190
|
+
const stored = await promptedOptions(prompter, impl, modelId, catalog, which, runCommand, current.options);
|
|
191
191
|
const change = upsertModel(config, key, { provider, model: modelId, options: stored, workflows });
|
|
192
|
-
validateChange(config, change.config, workflows,
|
|
192
|
+
validateChange(config, change.config, workflows, providers);
|
|
193
193
|
return confirmAndCommit(workspace, change, prompter, asJson, which);
|
|
194
194
|
}
|
|
195
|
-
async function addModel(workspace, config, workflows, role,
|
|
196
|
-
const provider = await selectProvider(prompter,
|
|
197
|
-
const
|
|
198
|
-
const catalog = await loadCatalog(
|
|
195
|
+
async function addModel(workspace, config, workflows, role, providers, which, prompter, runCommand, asJson) {
|
|
196
|
+
const provider = await selectProvider(prompter, providers, which, "Provider", null);
|
|
197
|
+
const impl = requireProvider(providers, provider);
|
|
198
|
+
const catalog = await loadCatalog(impl, which, runCommand);
|
|
199
199
|
const modelId = knownModel(await prompter.select("Model", catalog.ids.map((item) => [item, item]), catalog.defaultId), catalog);
|
|
200
|
-
const options = await promptedOptions(prompter,
|
|
200
|
+
const options = await promptedOptions(prompter, impl, modelId, catalog, which, runCommand, {});
|
|
201
201
|
const present = presentOptions(options);
|
|
202
202
|
const existing = matchingModelName(config, { provider, model: modelId, options: present });
|
|
203
203
|
let change;
|
|
@@ -224,10 +224,10 @@ async function addModel(workspace, config, workflows, role, adapters, which, pro
|
|
|
224
224
|
change = describeChange(config, assigned.config, workflows);
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
|
-
validateChange(config, change.config, workflows,
|
|
227
|
+
validateChange(config, change.config, workflows, providers);
|
|
228
228
|
return confirmAndCommit(workspace, change, prompter, asJson, which);
|
|
229
229
|
}
|
|
230
|
-
async function interactiveRoles(workspace, config, workflows, view,
|
|
230
|
+
async function interactiveRoles(workspace, config, workflows, view, providers, which, prompter, runCommand, asJson) {
|
|
231
231
|
const choices = view.roles.map((role) => [roleLabel(role), role.role]);
|
|
232
232
|
choices.push(["New role", NEW]);
|
|
233
233
|
const selected = await prompter.select("Role", choices, view.roles[0]?.role ?? NEW);
|
|
@@ -237,20 +237,20 @@ async function interactiveRoles(workspace, config, workflows, view, adapters, wh
|
|
|
237
237
|
throw new ConfigEditError("Role name is required.");
|
|
238
238
|
if (view.roles.some((role) => role.role === name))
|
|
239
239
|
throw new ConfigEditError(`Role ${name} already exists.`);
|
|
240
|
-
return pointRole(workspace, config, workflows, view, name,
|
|
240
|
+
return pointRole(workspace, config, workflows, view, name, providers, which, prompter, runCommand, asJson);
|
|
241
241
|
}
|
|
242
|
-
return pointRole(workspace, config, workflows, view, selected,
|
|
242
|
+
return pointRole(workspace, config, workflows, view, selected, providers, which, prompter, runCommand, asJson);
|
|
243
243
|
}
|
|
244
|
-
async function pointRole(workspace, config, workflows, view, role,
|
|
244
|
+
async function pointRole(workspace, config, workflows, view, role, providers, which, prompter, runCommand, asJson) {
|
|
245
245
|
const current = view.roles.find((item) => item.role === role) ?? null;
|
|
246
246
|
const choices = view.models.map((model) => [modelLabel(model), model.key]);
|
|
247
247
|
choices.push(["New model", NEW]);
|
|
248
248
|
const selected = await prompter.select("Model", choices, current?.modelName ?? NEW);
|
|
249
249
|
if (selected === NEW) {
|
|
250
|
-
return addModel(workspace, config, workflows, role,
|
|
250
|
+
return addModel(workspace, config, workflows, role, providers, which, prompter, runCommand, asJson);
|
|
251
251
|
}
|
|
252
252
|
const change = assignRoles(config, [role], selected, { workflows });
|
|
253
|
-
validateChange(config, change.config, workflows,
|
|
253
|
+
validateChange(config, change.config, workflows, providers);
|
|
254
254
|
return confirmAndCommit(workspace, change, prompter, asJson, which);
|
|
255
255
|
}
|
|
256
256
|
async function confirmAndCommit(workspace, change, prompter, asJson, which) {
|
|
@@ -274,7 +274,7 @@ async function commit(workspace, change, args) {
|
|
|
274
274
|
}
|
|
275
275
|
writeFileSync(filePath, updated, "utf8");
|
|
276
276
|
if (args.asJson) {
|
|
277
|
-
const checks = diagnose(workspace, {
|
|
277
|
+
const checks = diagnose(workspace, { providers: providerMap(), which: args.which });
|
|
278
278
|
const doctor = doctorPayload(checks);
|
|
279
279
|
process.stdout.write(`${JSON.stringify(changePayload(change, true, doctor))}\n`);
|
|
280
280
|
return doctor.ok ? 0 : 1;
|
|
@@ -283,7 +283,7 @@ async function commit(workspace, change, args) {
|
|
|
283
283
|
printChange(change);
|
|
284
284
|
return runDoctor(workspace, { which: args.which });
|
|
285
285
|
}
|
|
286
|
-
function validateChange(before, after, workflows,
|
|
286
|
+
function validateChange(before, after, workflows, providers) {
|
|
287
287
|
const beforeModels = isRecord(before.models) ? before.models : {};
|
|
288
288
|
const afterModels = isRecord(after.models) ? after.models : {};
|
|
289
289
|
const changed = new Set();
|
|
@@ -294,7 +294,7 @@ function validateChange(before, after, workflows, adapters) {
|
|
|
294
294
|
}
|
|
295
295
|
for (const key of changed) {
|
|
296
296
|
const target = loadModelTarget(after, key);
|
|
297
|
-
|
|
297
|
+
requireProvider(providers, target.provider).validateOptions(target.options);
|
|
298
298
|
}
|
|
299
299
|
const beforeRoles = isRecord(before.roles) ? before.roles : {};
|
|
300
300
|
const afterRoles = isRecord(after.roles) ? after.roles : {};
|
|
@@ -312,7 +312,7 @@ function validateChange(before, after, workflows, adapters) {
|
|
|
312
312
|
options[optionName] = value.trim();
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
|
-
|
|
315
|
+
requireProvider(providers, target.provider).validateOptions(options);
|
|
316
316
|
}
|
|
317
317
|
for (const document of workflows) {
|
|
318
318
|
for (const stage of Object.values(document.stages)) {
|
|
@@ -331,35 +331,35 @@ function validateChange(before, after, workflows, adapters) {
|
|
|
331
331
|
}
|
|
332
332
|
if (sameTarget(beforeTarget, afterTarget))
|
|
333
333
|
continue;
|
|
334
|
-
|
|
334
|
+
requireProvider(providers, afterTarget.provider).validateOptions(afterTarget.options);
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
337
|
}
|
|
338
|
-
async function catalogModel(
|
|
339
|
-
const
|
|
340
|
-
const catalog = await loadCatalog(
|
|
338
|
+
async function catalogModel(providers, which, runCommand, provider, model) {
|
|
339
|
+
const impl = requireProvider(providers, provider);
|
|
340
|
+
const catalog = await loadCatalog(impl, which, runCommand);
|
|
341
341
|
return [provider, knownModel(model, catalog), catalog];
|
|
342
342
|
}
|
|
343
|
-
async function checkedOptions(
|
|
344
|
-
const
|
|
345
|
-
const resolved = catalog ?? (await loadCatalog(
|
|
343
|
+
async function checkedOptions(providers, which, runCommand, provider, model, updates, catalog = null) {
|
|
344
|
+
const impl = requireProvider(providers, provider);
|
|
345
|
+
const resolved = catalog ?? (await loadCatalog(impl, which, runCommand));
|
|
346
346
|
const present = {};
|
|
347
347
|
for (const [key, value] of Object.entries(updates)) {
|
|
348
348
|
if (value !== null)
|
|
349
349
|
present[key] = value;
|
|
350
350
|
}
|
|
351
351
|
if (Object.keys(present).length > 0) {
|
|
352
|
-
await acceptedOptions(
|
|
352
|
+
await acceptedOptions(impl, model, resolved, which, runCommand, present);
|
|
353
353
|
}
|
|
354
354
|
return { ...updates };
|
|
355
355
|
}
|
|
356
|
-
async function promptedOptions(prompter,
|
|
356
|
+
async function promptedOptions(prompter, provider, model, catalog, which, runCommand, current) {
|
|
357
357
|
const stored = {};
|
|
358
|
-
const
|
|
359
|
-
for (const spec of
|
|
358
|
+
const catalogProvider = asCatalog(provider);
|
|
359
|
+
for (const spec of catalogProvider.providerOptions()) {
|
|
360
360
|
if (!spec.prompt)
|
|
361
361
|
continue;
|
|
362
|
-
const values = await loadOptionValues(
|
|
362
|
+
const values = await loadOptionValues(provider, spec.name, model, catalog, which, runCommand);
|
|
363
363
|
if (values === null || values.length === 0)
|
|
364
364
|
continue;
|
|
365
365
|
const choices = values.map((value) => [value, value]);
|
|
@@ -386,8 +386,8 @@ function optionUpdatesOf(options, clearOptions) {
|
|
|
386
386
|
}
|
|
387
387
|
return updates;
|
|
388
388
|
}
|
|
389
|
-
async function selectProvider(prompter,
|
|
390
|
-
const rows = detected(
|
|
389
|
+
async function selectProvider(prompter, providers, which, message, defaultValue) {
|
|
390
|
+
const rows = detected(providers, which);
|
|
391
391
|
if (rows.length === 0)
|
|
392
392
|
throw new ConfigurationError("No providers are available.");
|
|
393
393
|
const names = new Set(rows.map(([name]) => name));
|
package/dist/commands/doctor.js
CHANGED
|
@@ -2,11 +2,11 @@ import { diagnose, whichSync } from "@nodeint/migent-kernel";
|
|
|
2
2
|
import { printDoctor } from "../display.js";
|
|
3
3
|
import { cliHooks } from "../hooks.js";
|
|
4
4
|
import { requireCurrentSchema } from "../migrate.js";
|
|
5
|
-
import {
|
|
5
|
+
import { providerMap } from "../provider.js";
|
|
6
6
|
export async function runDoctor(workspace, options = {}) {
|
|
7
7
|
requireCurrentSchema(workspace);
|
|
8
8
|
const which = options.which ?? cliHooks.which ?? whichSync;
|
|
9
|
-
const checks = diagnose(workspace, {
|
|
9
|
+
const checks = diagnose(workspace, { providers: providerMap(), which });
|
|
10
10
|
if (options.asJson)
|
|
11
11
|
process.stdout.write(`${JSON.stringify(payload(checks))}\n`);
|
|
12
12
|
else
|
package/dist/commands/execute.js
CHANGED
|
@@ -4,7 +4,7 @@ import { AgentToolRunner, DEFAULT_MAX_DISPATCHES, executeNamedWorkflow, formatDu
|
|
|
4
4
|
import { printResumeCommand } from "../display.js";
|
|
5
5
|
import { cliHooks } from "../hooks.js";
|
|
6
6
|
import { requireCurrentSchema } from "../migrate.js";
|
|
7
|
-
import {
|
|
7
|
+
import { providerMap } from "../provider.js";
|
|
8
8
|
import { text } from "../text.js";
|
|
9
9
|
export async function executeWorkflow(workspace, options) {
|
|
10
10
|
requireCurrentSchema(workspace);
|
|
@@ -13,7 +13,7 @@ export async function executeWorkflow(workspace, options) {
|
|
|
13
13
|
const sessionId = text(options.sessionId);
|
|
14
14
|
const creating = !startPrepared && (options.action === "start" || (once && sessionId === null));
|
|
15
15
|
const maxDispatches = options.maxDispatches === undefined ? DEFAULT_MAX_DISPATCHES : options.maxDispatches;
|
|
16
|
-
const runner = options.runner ?? cliHooks.runner ?? new AgentToolRunner(
|
|
16
|
+
const runner = options.runner ?? cliHooks.runner ?? new AgentToolRunner(providerMap());
|
|
17
17
|
runner.itemProgress = options.itemProgress === true;
|
|
18
18
|
const result = await executeNamedWorkflow(workspace, {
|
|
19
19
|
workflowId: creating || startPrepared ? (options.workflowId ?? null) : null,
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ModelCatalog } from "@nodeint/migent-adapters";
|
|
2
|
-
import { type
|
|
2
|
+
import { type Provider } from "@nodeint/migent-kernel";
|
|
3
3
|
import { type RunCommand, type Which } from "../provider.js";
|
|
4
4
|
export declare const PRESETS: readonly ["implement", "plan"];
|
|
5
5
|
export interface ModelChoice {
|
|
@@ -8,7 +8,7 @@ export interface ModelChoice {
|
|
|
8
8
|
options: Record<string, string>;
|
|
9
9
|
}
|
|
10
10
|
export interface Prompter {
|
|
11
|
-
selectProvider(rows: ReadonlyArray<readonly [string, string | null,
|
|
11
|
+
selectProvider(rows: ReadonlyArray<readonly [string, string | null, Provider]>, message: string): string | Promise<string>;
|
|
12
12
|
selectModel(provider: string, catalog: ModelCatalog, message: string): string | Promise<string>;
|
|
13
13
|
selectOption(name: string, values: readonly string[], allowDefault: boolean): string | Promise<string>;
|
|
14
14
|
confirm(message: string): boolean | Promise<boolean>;
|
|
@@ -21,7 +21,7 @@ export declare function runInit(workspace: string, options?: {
|
|
|
21
21
|
presetExplicit?: boolean;
|
|
22
22
|
skill?: boolean;
|
|
23
23
|
interactive?: boolean;
|
|
24
|
-
|
|
24
|
+
providers?: Record<string, Provider> | null;
|
|
25
25
|
which?: Which | null;
|
|
26
26
|
prompter?: Prompter | null;
|
|
27
27
|
runCommand?: RunCommand | null;
|
package/dist/commands/init.js
CHANGED
|
@@ -8,7 +8,7 @@ import { printAlreadyInitialized, printCreated, printError, printInitConflicts,
|
|
|
8
8
|
import { cliHooks } from "../hooks.js";
|
|
9
9
|
import { runDoctor } from "./doctor.js";
|
|
10
10
|
import { installSkill } from "./skill.js";
|
|
11
|
-
import { acceptedOptions,
|
|
11
|
+
import { acceptedOptions, providerMap, asCatalog, detected, knownModel, loadCatalog, loadOptionValues, parseOptions, requireProvider, runProviderCommand, } from "../provider.js";
|
|
12
12
|
export const PRESETS = ["implement", "plan"];
|
|
13
13
|
export async function runInit(workspace, options = {}) {
|
|
14
14
|
const provider = options.provider ?? null;
|
|
@@ -16,7 +16,7 @@ export async function runInit(workspace, options = {}) {
|
|
|
16
16
|
const preset = options.preset ?? "implement";
|
|
17
17
|
const skill = options.skill === true;
|
|
18
18
|
const interactive = options.interactive ?? false;
|
|
19
|
-
const chosen = options.
|
|
19
|
+
const chosen = options.providers ?? providerMap();
|
|
20
20
|
const resolve = options.which ?? cliHooks.which ?? whichSync;
|
|
21
21
|
const prompt = options.prompter ?? new ClackPrompter();
|
|
22
22
|
const execute = options.runCommand ?? runProviderCommand;
|
|
@@ -94,42 +94,42 @@ export async function runInit(workspace, options = {}) {
|
|
|
94
94
|
}
|
|
95
95
|
return 0;
|
|
96
96
|
}
|
|
97
|
-
function writeDoctorProblems(workspace,
|
|
98
|
-
const problems = diagnose(workspace, {
|
|
97
|
+
function writeDoctorProblems(workspace, providers, which) {
|
|
98
|
+
const problems = diagnose(workspace, { providers, which }).flatMap((check) => check.problems);
|
|
99
99
|
if (problems.length === 0)
|
|
100
100
|
return;
|
|
101
101
|
process.stderr.write("Doctor reported problems:\n");
|
|
102
102
|
for (const problem of problems)
|
|
103
103
|
process.stderr.write(`${problem}\n`);
|
|
104
104
|
}
|
|
105
|
-
async function choices(
|
|
105
|
+
async function choices(providers, which, prompt, args) {
|
|
106
106
|
let provider = args.provider;
|
|
107
107
|
let model = args.model;
|
|
108
108
|
let stored;
|
|
109
109
|
if (provider === null) {
|
|
110
|
-
const rows = detected(
|
|
110
|
+
const rows = detected(providers, which);
|
|
111
111
|
provider = await prompt.selectProvider(rows, "Default provider");
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
112
|
+
const impl = providers[provider];
|
|
113
|
+
if (impl === undefined)
|
|
114
114
|
throw new ConfigurationError(`Unknown provider: ${provider}.`);
|
|
115
|
-
const catalog = await loadCatalog(
|
|
115
|
+
const catalog = await loadCatalog(impl, which, args.runCommand);
|
|
116
116
|
model = await prompt.selectModel(provider, catalog, "Model");
|
|
117
|
-
stored = await promptOptions(
|
|
117
|
+
stored = await promptOptions(impl, model, catalog, which, args.runCommand, prompt);
|
|
118
118
|
}
|
|
119
119
|
else {
|
|
120
|
-
const
|
|
120
|
+
const impl = requireProvider(providers, provider);
|
|
121
121
|
if (model === null)
|
|
122
122
|
throw new ConfigurationError("Model is required.");
|
|
123
|
-
const catalog = await loadCatalog(
|
|
123
|
+
const catalog = await loadCatalog(impl, which, args.runCommand);
|
|
124
124
|
model = knownModel(model, catalog);
|
|
125
|
-
stored = await acceptedOptions(
|
|
125
|
+
stored = await acceptedOptions(impl, model, catalog, which, args.runCommand, args.options);
|
|
126
126
|
}
|
|
127
127
|
if (model.trim() === "")
|
|
128
128
|
throw new ConfigurationError("Model is required.");
|
|
129
129
|
const primary = { provider, model: model.trim(), options: stored };
|
|
130
130
|
return primary;
|
|
131
131
|
}
|
|
132
|
-
function classify(workspace,
|
|
132
|
+
function classify(workspace, providers, which) {
|
|
133
133
|
const root = path.join(workspace, ".migent");
|
|
134
134
|
const config = path.join(root, "config.yaml");
|
|
135
135
|
const workflows = path.join(root, "workflows");
|
|
@@ -154,7 +154,7 @@ function classify(workspace, adapters, which) {
|
|
|
154
154
|
? readdirSync(workflows).filter((name) => name.endsWith(".yaml") && isFile(path.join(workflows, name)))
|
|
155
155
|
: [];
|
|
156
156
|
if (isFile(config) && yamlFiles.length > 0) {
|
|
157
|
-
const checks = diagnose(workspace, {
|
|
157
|
+
const checks = diagnose(workspace, { providers, which });
|
|
158
158
|
const byName = new Map(checks.map((check) => [check.name, check]));
|
|
159
159
|
if (byName.get("config")?.ok && byName.get("workflows")?.ok)
|
|
160
160
|
return { state: "ready", conflicts: [] };
|
|
@@ -236,12 +236,12 @@ function renderWorkflow(preset) {
|
|
|
236
236
|
" from_stage: plan\n" +
|
|
237
237
|
" artifact: plan.md\n");
|
|
238
238
|
}
|
|
239
|
-
async function promptOptions(
|
|
239
|
+
async function promptOptions(provider, model, catalog, which, runCommand, prompt) {
|
|
240
240
|
const stored = {};
|
|
241
|
-
for (const spec of providerOptions(
|
|
241
|
+
for (const spec of providerOptions(provider)) {
|
|
242
242
|
if (!spec.prompt)
|
|
243
243
|
continue;
|
|
244
|
-
const values = await loadOptionValues(
|
|
244
|
+
const values = await loadOptionValues(provider, spec.name, model, catalog, which, runCommand);
|
|
245
245
|
if (values === null || values.length === 0)
|
|
246
246
|
continue;
|
|
247
247
|
const picked = await prompt.selectOption(spec.name, values, spec.allowDefault ?? false);
|
|
@@ -250,8 +250,8 @@ async function promptOptions(adapter, model, catalog, which, runCommand, prompt)
|
|
|
250
250
|
}
|
|
251
251
|
return stored;
|
|
252
252
|
}
|
|
253
|
-
function providerOptions(
|
|
254
|
-
return asCatalog(
|
|
253
|
+
function providerOptions(provider) {
|
|
254
|
+
return asCatalog(provider).providerOptions();
|
|
255
255
|
}
|
|
256
256
|
class ClackPrompter {
|
|
257
257
|
async selectProvider(rows, message) {
|
package/dist/commands/prepare.js
CHANGED
|
@@ -5,6 +5,7 @@ export function prepareWorkflowSession(workspace, sessionId, workflowId, priorSe
|
|
|
5
5
|
const id = createPreparedSession(workspace, sessionId, workflowId, priorSessionId);
|
|
6
6
|
const payload = {
|
|
7
7
|
session_id: id,
|
|
8
|
+
title: sessionId.trim(),
|
|
8
9
|
status: "prepared",
|
|
9
10
|
task_path: relativeTaskPath(id),
|
|
10
11
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Provider } from "@nodeint/migent-kernel";
|
|
2
|
+
import { type ProviderRow } from "../display.js";
|
|
3
|
+
import { type RunCommand, type Which } from "../provider.js";
|
|
4
|
+
export declare function collectProviders(providers: Record<string, Provider>, which: Which, runCommand: RunCommand): Promise<ProviderRow[]>;
|
|
5
|
+
export declare function runProviderList(options?: {
|
|
6
|
+
asJson?: boolean;
|
|
7
|
+
providers?: Record<string, Provider> | null;
|
|
8
|
+
which?: Which | null;
|
|
9
|
+
runCommand?: RunCommand | null;
|
|
10
|
+
}): Promise<number>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { whichSync } from "@nodeint/migent-kernel";
|
|
2
|
+
import { printProviders } from "../display.js";
|
|
3
|
+
import { cliHooks } from "../hooks.js";
|
|
4
|
+
import { providerMap, loadCatalog, runProviderCommand, } from "../provider.js";
|
|
5
|
+
function mapProviderOption(opt) {
|
|
6
|
+
return {
|
|
7
|
+
name: opt.name,
|
|
8
|
+
prompt: opt.prompt === true,
|
|
9
|
+
allow_default: opt.allowDefault === true,
|
|
10
|
+
overridable: opt.overridable === true,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function getProviderOptions(provider) {
|
|
14
|
+
const candidate = provider;
|
|
15
|
+
if (typeof candidate.providerOptions === "function") {
|
|
16
|
+
return candidate.providerOptions().map(mapProviderOption);
|
|
17
|
+
}
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
export async function collectProviders(providers, which, runCommand) {
|
|
21
|
+
// A provider without a default client has no CLI of its own to probe.
|
|
22
|
+
const names = Object.keys(providers).filter((name) => providers[name].requiresClient !== true).sort();
|
|
23
|
+
const rows = [];
|
|
24
|
+
for (const name of names) {
|
|
25
|
+
const provider = providers[name];
|
|
26
|
+
const path = which(provider.command);
|
|
27
|
+
const options = getProviderOptions(provider);
|
|
28
|
+
const problems = [];
|
|
29
|
+
let models = [];
|
|
30
|
+
let default_model = null;
|
|
31
|
+
if (path === null) {
|
|
32
|
+
problems.push(`${provider.command} is not on PATH.`);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
try {
|
|
36
|
+
const catalog = await loadCatalog(provider, which, runCommand);
|
|
37
|
+
models = [...catalog.ids];
|
|
38
|
+
default_model = catalog.defaultId;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
42
|
+
problems.push(message);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
rows.push({
|
|
46
|
+
name,
|
|
47
|
+
command: provider.command,
|
|
48
|
+
path,
|
|
49
|
+
available: path !== null,
|
|
50
|
+
models,
|
|
51
|
+
default_model,
|
|
52
|
+
options,
|
|
53
|
+
problems,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return rows;
|
|
57
|
+
}
|
|
58
|
+
function providerPayload(rows) {
|
|
59
|
+
return {
|
|
60
|
+
ok: rows.every((row) => row.problems.length === 0),
|
|
61
|
+
providers: rows.map((row) => ({ ...row })),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export async function runProviderList(options = {}) {
|
|
65
|
+
const providers = options.providers ?? providerMap();
|
|
66
|
+
const which = options.which ?? cliHooks.which ?? whichSync;
|
|
67
|
+
const runCommand = options.runCommand ?? cliHooks.runCommand ?? runProviderCommand;
|
|
68
|
+
const rows = await collectProviders(providers, which, runCommand);
|
|
69
|
+
if (options.asJson) {
|
|
70
|
+
process.stdout.write(`${JSON.stringify(providerPayload(rows))}\n`);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
printProviders(rows);
|
|
74
|
+
}
|
|
75
|
+
const hasAnyModels = rows.some((row) => row.models.length > 0);
|
|
76
|
+
return hasAnyModels ? 0 : 1;
|
|
77
|
+
}
|