@nodeint/migent 0.1.5 → 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.
@@ -1,12 +1,12 @@
1
1
  import { readFileSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { confirm as clackConfirm, isCancel, select as clackSelect, text as clackText } from "@clack/prompts";
4
- import { ConfigEditError, ConfigurationError, assignRoles, describeChange, diagnose, inspectConfig, loadModelTarget, loadWorkflowCatalog, loadYamlMapping, matchingModelName, patchConfigText, placeRoleModel, resolveStageSpec, setRoleOptions, upsertModel, whichSync, } from "@nodeint/migent-kernel";
4
+ import { ConfigEditError, ConfigurationError, assignRoles, describeChange, diagnose, inspectConfig, loadModelTarget, loadWorkflowCatalog, loadYamlMapping, matchingModelName, patchConfigText, placeRoleModel, isRoleStage, resolveStageSpec, setRoleOptions, upsertModel, whichSync, } from "@nodeint/migent-kernel";
5
5
  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, adapterMap, asCatalog, detected, knownModel, loadCatalog, loadOptionValues, parseOptions, requireAdapter, runProviderCommand, } from "../provider.js";
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
- adapters: options.adapters ?? adapterMap(),
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
- adapters: options.adapters ?? adapterMap(),
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.adapters, args.which, args.prompter, args.runCommand, asJson);
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.adapters, args.which, args.prompter, args.runCommand, asJson);
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.adapters, args.which, args.runCommand, provider, model);
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.adapters, args.which, args.runCommand, currentProvider, currentModel, optionUpdates, provider !== null ? catalog : null);
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.adapters);
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.adapters, args.which, args.prompter, args.runCommand, asJson);
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.adapters, args.which, args.prompter, args.runCommand, asJson);
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.adapters, args.which, args.runCommand, provider, model);
143
+ [provider, model, catalog] = await catalogModel(args.providers, args.which, args.runCommand, provider, model);
144
144
  if (optionUpdates !== UNSET) {
145
- optionUpdates = await checkedOptions(args.adapters, args.which, args.runCommand, provider, model, optionUpdates, catalog);
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.adapters, args.which, args.runCommand, target.provider, target.model, optionUpdates);
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.adapters, args.which, args.runCommand, target.provider, target.model, optionUpdates);
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.adapters);
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, adapters, which, prompter, runCommand, asJson) {
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, adapters, which, prompter, runCommand, asJson);
179
- return editModel(workspace, config, workflows, view, selected, adapters, which, prompter, runCommand, asJson);
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, adapters, which, prompter, runCommand, asJson) {
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, adapters, which, "Provider", current.provider);
186
- const adapter = requireAdapter(adapters, provider);
187
- const catalog = await loadCatalog(adapter, which, runCommand);
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, adapter, modelId, catalog, which, runCommand, current.options);
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, adapters);
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, adapters, which, prompter, runCommand, asJson) {
196
- const provider = await selectProvider(prompter, adapters, which, "Provider", null);
197
- const adapter = requireAdapter(adapters, provider);
198
- const catalog = await loadCatalog(adapter, which, runCommand);
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, adapter, modelId, catalog, which, runCommand, {});
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, adapters);
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, adapters, which, prompter, runCommand, asJson) {
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, adapters, which, prompter, runCommand, asJson);
240
+ return pointRole(workspace, config, workflows, view, name, providers, which, prompter, runCommand, asJson);
241
241
  }
242
- return pointRole(workspace, config, workflows, view, selected, adapters, which, prompter, runCommand, asJson);
242
+ return pointRole(workspace, config, workflows, view, selected, providers, which, prompter, runCommand, asJson);
243
243
  }
244
- async function pointRole(workspace, config, workflows, view, role, adapters, which, prompter, runCommand, asJson) {
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, adapters, which, prompter, runCommand, asJson);
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, adapters);
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, { adapters: adapterMap(), which: args.which });
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, adapters) {
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
- requireAdapter(adapters, target.provider).validateOptions(target.options);
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,10 +312,12 @@ function validateChange(before, after, workflows, adapters) {
312
312
  options[optionName] = value.trim();
313
313
  }
314
314
  }
315
- requireAdapter(adapters, target.provider).validateOptions(options);
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)) {
319
+ if (!isRoleStage(stage))
320
+ continue;
319
321
  let beforeTarget;
320
322
  let afterTarget;
321
323
  try {
@@ -329,35 +331,35 @@ function validateChange(before, after, workflows, adapters) {
329
331
  }
330
332
  if (sameTarget(beforeTarget, afterTarget))
331
333
  continue;
332
- requireAdapter(adapters, afterTarget.provider).validateOptions(afterTarget.options);
334
+ requireProvider(providers, afterTarget.provider).validateOptions(afterTarget.options);
333
335
  }
334
336
  }
335
337
  }
336
- async function catalogModel(adapters, which, runCommand, provider, model) {
337
- const adapter = requireAdapter(adapters, provider);
338
- const catalog = await loadCatalog(adapter, which, runCommand);
338
+ async function catalogModel(providers, which, runCommand, provider, model) {
339
+ const impl = requireProvider(providers, provider);
340
+ const catalog = await loadCatalog(impl, which, runCommand);
339
341
  return [provider, knownModel(model, catalog), catalog];
340
342
  }
341
- async function checkedOptions(adapters, which, runCommand, provider, model, updates, catalog = null) {
342
- const adapter = requireAdapter(adapters, provider);
343
- const resolved = catalog ?? (await loadCatalog(adapter, which, runCommand));
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));
344
346
  const present = {};
345
347
  for (const [key, value] of Object.entries(updates)) {
346
348
  if (value !== null)
347
349
  present[key] = value;
348
350
  }
349
351
  if (Object.keys(present).length > 0) {
350
- await acceptedOptions(adapter, model, resolved, which, runCommand, present);
352
+ await acceptedOptions(impl, model, resolved, which, runCommand, present);
351
353
  }
352
354
  return { ...updates };
353
355
  }
354
- async function promptedOptions(prompter, adapter, model, catalog, which, runCommand, current) {
356
+ async function promptedOptions(prompter, provider, model, catalog, which, runCommand, current) {
355
357
  const stored = {};
356
- const catalogAdapter = asCatalog(adapter);
357
- for (const spec of catalogAdapter.providerOptions()) {
358
+ const catalogProvider = asCatalog(provider);
359
+ for (const spec of catalogProvider.providerOptions()) {
358
360
  if (!spec.prompt)
359
361
  continue;
360
- const values = await loadOptionValues(adapter, spec.name, model, catalog, which, runCommand);
362
+ const values = await loadOptionValues(provider, spec.name, model, catalog, which, runCommand);
361
363
  if (values === null || values.length === 0)
362
364
  continue;
363
365
  const choices = values.map((value) => [value, value]);
@@ -384,8 +386,8 @@ function optionUpdatesOf(options, clearOptions) {
384
386
  }
385
387
  return updates;
386
388
  }
387
- async function selectProvider(prompter, adapters, which, message, defaultValue) {
388
- const rows = detected(adapters, which);
389
+ async function selectProvider(prompter, providers, which, message, defaultValue) {
390
+ const rows = detected(providers, which);
389
391
  if (rows.length === 0)
390
392
  throw new ConfigurationError("No providers are available.");
391
393
  const names = new Set(rows.map(([name]) => name));
@@ -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 { adapterMap } from "../provider.js";
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, { adapters: adapterMap(), which });
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
@@ -5,11 +5,10 @@ export interface ExecuteWorkflowOptions {
5
5
  task?: string | null;
6
6
  sessionId?: string | null;
7
7
  priorSessionId?: string | null;
8
- maxAttempts?: number | null;
9
8
  maxDispatches?: number | null;
9
+ continueFailed?: boolean;
10
10
  stageId?: string | null;
11
11
  runner?: AgentToolRunner | null;
12
- verbose?: boolean;
13
12
  itemProgress?: boolean;
14
13
  startPrepared?: boolean;
15
14
  refreshWorkflow?: boolean;
@@ -1,7 +1,10 @@
1
- import { AgentToolRunner, DEFAULT_MAX_DISPATCHES, executeNamedWorkflow, } from "@nodeint/migent-kernel";
1
+ import { readdirSync, readFileSync, realpathSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { AgentToolRunner, DEFAULT_MAX_DISPATCHES, executeNamedWorkflow, formatDuration, log, } from "@nodeint/migent-kernel";
4
+ import { printResumeCommand } from "../display.js";
2
5
  import { cliHooks } from "../hooks.js";
3
6
  import { requireCurrentSchema } from "../migrate.js";
4
- import { adapterMap } from "../provider.js";
7
+ import { providerMap } from "../provider.js";
5
8
  import { text } from "../text.js";
6
9
  export async function executeWorkflow(workspace, options) {
7
10
  requireCurrentSchema(workspace);
@@ -9,9 +12,8 @@ export async function executeWorkflow(workspace, options) {
9
12
  const startPrepared = options.startPrepared === true;
10
13
  const sessionId = text(options.sessionId);
11
14
  const creating = !startPrepared && (options.action === "start" || (once && sessionId === null));
12
- const maxAttempts = options.maxAttempts === undefined ? 3 : options.maxAttempts;
13
15
  const maxDispatches = options.maxDispatches === undefined ? DEFAULT_MAX_DISPATCHES : options.maxDispatches;
14
- const runner = options.runner ?? cliHooks.runner ?? new AgentToolRunner(adapterMap());
16
+ const runner = options.runner ?? cliHooks.runner ?? new AgentToolRunner(providerMap());
15
17
  runner.itemProgress = options.itemProgress === true;
16
18
  const result = await executeNamedWorkflow(workspace, {
17
19
  workflowId: creating || startPrepared ? (options.workflowId ?? null) : null,
@@ -19,37 +21,104 @@ export async function executeWorkflow(workspace, options) {
19
21
  sessionId: creating ? null : sessionId,
20
22
  priorSessionId: options.priorSessionId ?? null,
21
23
  startPrepared,
22
- maxAttempts: once ? null : maxAttempts,
23
24
  maxDispatches: once ? null : maxDispatches,
25
+ continueFailed: options.continueFailed === true,
24
26
  stageId: options.stageId ?? null,
25
27
  once,
26
28
  resetRevisions: options.action === "continue",
27
29
  refreshWorkflow: options.refreshWorkflow === true,
28
30
  reply: options.reply ?? null,
29
31
  }, runner);
30
- printResult(result, options.verbose === true);
31
- if (result.stopReason === "retry_exhausted" ||
32
- result.stopReason === "revisions_exhausted" ||
33
- result.stopReason === "dispatch_limit" ||
34
- result.stopReason === "verify_exhausted") {
35
- process.stderr.write(`Resume with: migent session continue ${result.sessionId}\n`);
36
- }
32
+ printResult(workspace, result);
33
+ printResumeHint(result);
37
34
  return result.code;
38
35
  }
39
- const INSPECT_EXIT_CODES = new Set([1, 3, 6]);
40
- function printResult(result, verbose) {
41
- const payload = {
42
- session_id: result.sessionId,
43
- session_status: result.status,
44
- stop_reason: result.stopReason,
45
- latest_decision: result.latestDecision,
46
- outputs: result.outputs,
47
- };
36
+ function printResumeHint(result) {
37
+ if (result.sessionId === "" || result.status === "completed" || result.stopReason === "completed")
38
+ return;
39
+ if (result.stopReason === "configuration")
40
+ return;
41
+ printResumeCommand(result.sessionId);
42
+ }
43
+ const INSPECT_EXIT_CODES = new Set([1, 3, 6, 8]);
44
+ function printResult(workspace, result) {
45
+ if (result.status === "completed" || result.stopReason === "completed") {
46
+ const duration = sessionDurationLabel(workspace, result.sessionId);
47
+ log(duration === null ? "completed" : `completed · ${duration}`);
48
+ for (const filePath of artifactPaths(workspace, result.sessionId, result.outputs)) {
49
+ log(`artifact: ${filePath}`);
50
+ }
51
+ return;
52
+ }
48
53
  if (INSPECT_EXIT_CODES.has(result.code) && result.latestExecutionId) {
49
- payload.latest_execution_id = result.latestExecutionId;
50
- payload.response_path = `executions/${result.latestExecutionId}/response.md`;
54
+ log(`response: executions/${result.latestExecutionId}/response.md`);
55
+ }
56
+ if (result.code === 7 && result.latestExecutionId) {
57
+ log(`verify: executions/${result.latestExecutionId}/turns/01/verify.md`);
58
+ }
59
+ }
60
+ function artifactPaths(workspace, sessionId, outputs) {
61
+ if (sessionId === "")
62
+ return [];
63
+ const sessionDir = path.resolve(workspace, ".migent", "sessions", sessionId);
64
+ const paths = [];
65
+ for (const [name, value] of Object.entries(outputs)) {
66
+ const record = isRecord(value) ? value : {};
67
+ const relative = typeof record.path === "string" && record.path !== "" ? record.path : path.join("outputs", name);
68
+ paths.push(absoluteFromRoot(path.resolve(sessionDir, relative)));
69
+ }
70
+ return paths;
71
+ }
72
+ function absoluteFromRoot(target) {
73
+ try {
74
+ return realpathSync(target);
75
+ }
76
+ catch {
77
+ return path.resolve(target);
51
78
  }
52
- if (verbose)
53
- payload.stages = result.stages;
54
- process.stdout.write(`${JSON.stringify(payload)}\n`);
79
+ }
80
+ function sessionDurationLabel(workspace, sessionId) {
81
+ if (sessionId === "")
82
+ return null;
83
+ const executions = path.join(workspace, ".migent", "sessions", sessionId, "executions");
84
+ let names;
85
+ try {
86
+ names = readdirSync(executions);
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ let total = 0;
92
+ let counted = false;
93
+ for (const name of names) {
94
+ const elapsed = executionElapsedMs(path.join(executions, name, "execution.json"));
95
+ if (elapsed === null)
96
+ continue;
97
+ total += elapsed;
98
+ counted = true;
99
+ }
100
+ return counted ? formatDuration(total) : null;
101
+ }
102
+ function executionElapsedMs(filePath) {
103
+ try {
104
+ const metadata = JSON.parse(readFileSync(filePath, "utf8"));
105
+ if (!isRecord(metadata))
106
+ return null;
107
+ const started = parsedTime(metadata.created_at);
108
+ if (started === null)
109
+ return null;
110
+ return (parsedTime(metadata.completed_at) ?? Date.now()) - started;
111
+ }
112
+ catch {
113
+ return null;
114
+ }
115
+ }
116
+ function parsedTime(value) {
117
+ if (typeof value !== "string" || value === "")
118
+ return null;
119
+ const parsed = Date.parse(value);
120
+ return Number.isNaN(parsed) ? null : parsed;
121
+ }
122
+ function isRecord(value) {
123
+ return typeof value === "object" && value !== null && !Array.isArray(value);
55
124
  }
@@ -1,5 +1,5 @@
1
1
  import type { ModelCatalog } from "@nodeint/migent-adapters";
2
- import { type AgentAdapter } from "@nodeint/migent-kernel";
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, AgentAdapter]>, message: string): string | Promise<string>;
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
- adapters?: Record<string, AgentAdapter> | null;
24
+ providers?: Record<string, Provider> | null;
25
25
  which?: Which | null;
26
26
  prompter?: Prompter | null;
27
27
  runCommand?: RunCommand | null;
@@ -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, adapterMap, asCatalog, detected, knownModel, loadCatalog, loadOptionValues, parseOptions, requireAdapter, runProviderCommand, } from "../provider.js";
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.adapters ?? adapterMap();
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, adapters, which) {
98
- const problems = diagnose(workspace, { adapters, which }).flatMap((check) => check.problems);
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(adapters, which, prompt, args) {
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(adapters, which);
110
+ const rows = detected(providers, which);
111
111
  provider = await prompt.selectProvider(rows, "Default provider");
112
- const adapter = adapters[provider];
113
- if (adapter === undefined)
112
+ const impl = providers[provider];
113
+ if (impl === undefined)
114
114
  throw new ConfigurationError(`Unknown provider: ${provider}.`);
115
- const catalog = await loadCatalog(adapter, which, args.runCommand);
115
+ const catalog = await loadCatalog(impl, which, args.runCommand);
116
116
  model = await prompt.selectModel(provider, catalog, "Model");
117
- stored = await promptOptions(adapter, model, catalog, which, args.runCommand, prompt);
117
+ stored = await promptOptions(impl, model, catalog, which, args.runCommand, prompt);
118
118
  }
119
119
  else {
120
- const adapter = requireAdapter(adapters, provider);
120
+ const impl = requireProvider(providers, provider);
121
121
  if (model === null)
122
122
  throw new ConfigurationError("Model is required.");
123
- const catalog = await loadCatalog(adapter, which, args.runCommand);
123
+ const catalog = await loadCatalog(impl, which, args.runCommand);
124
124
  model = knownModel(model, catalog);
125
- stored = await acceptedOptions(adapter, model, catalog, which, args.runCommand, args.options);
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, adapters, which) {
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, { adapters, which });
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(adapter, model, catalog, which, runCommand, prompt) {
239
+ async function promptOptions(provider, model, catalog, which, runCommand, prompt) {
240
240
  const stored = {};
241
- for (const spec of providerOptions(adapter)) {
241
+ for (const spec of providerOptions(provider)) {
242
242
  if (!spec.prompt)
243
243
  continue;
244
- const values = await loadOptionValues(adapter, spec.name, model, catalog, which, runCommand);
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(adapter) {
254
- return asCatalog(adapter).providerOptions();
253
+ function providerOptions(provider) {
254
+ return asCatalog(provider).providerOptions();
255
255
  }
256
256
  class ClackPrompter {
257
257
  async selectProvider(rows, message) {
@@ -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>;