@nodeint/migent 0.1.4 → 0.1.6

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.
Files changed (48) hide show
  1. package/README.md +194 -138
  2. package/dist/commands/agent.d.ts +8 -1
  3. package/dist/commands/agent.js +381 -56
  4. package/dist/commands/config.js +23 -9
  5. package/dist/commands/execute.d.ts +6 -1
  6. package/dist/commands/execute.js +105 -20
  7. package/dist/commands/init.d.ts +1 -0
  8. package/dist/commands/init.js +70 -63
  9. package/dist/commands/prepare.d.ts +1 -0
  10. package/dist/commands/prepare.js +14 -0
  11. package/dist/commands/repair.d.ts +1 -0
  12. package/dist/commands/repair.js +22 -0
  13. package/dist/commands/service.js +5 -4
  14. package/dist/commands/sessions.d.ts +24 -1
  15. package/dist/commands/sessions.js +165 -13
  16. package/dist/commands/skill.js +58 -32
  17. package/dist/commands/watch.d.ts +2 -1
  18. package/dist/commands/watch.js +130 -67
  19. package/dist/commands/workspace-registry.js +10 -1
  20. package/dist/display.d.ts +1 -0
  21. package/dist/display.js +24 -9
  22. package/dist/errors.d.ts +3 -0
  23. package/dist/errors.js +6 -0
  24. package/dist/exits.d.ts +12 -0
  25. package/dist/exits.js +33 -0
  26. package/dist/follow-log.d.ts +29 -0
  27. package/dist/follow-log.js +172 -0
  28. package/dist/fs-utils.d.ts +3 -0
  29. package/dist/fs-utils.js +26 -0
  30. package/dist/index.js +5 -1
  31. package/dist/migrate.js +2 -17
  32. package/dist/program.js +429 -177
  33. package/dist/session-row.d.ts +20 -0
  34. package/dist/session-row.js +21 -0
  35. package/dist/signals.d.ts +3 -0
  36. package/dist/signals.js +56 -0
  37. package/dist/templates/migent/SKILL.md +163 -56
  38. package/dist/templates/migent/references/config.md +52 -0
  39. package/dist/templates/migent/references/inspect.md +32 -0
  40. package/dist/templates/migent/references/schema-config.yaml +35 -0
  41. package/dist/templates/migent/references/schema-workflow.yaml +71 -0
  42. package/dist/templates/migent/references/sessions.md +65 -0
  43. package/dist/text.d.ts +4 -0
  44. package/dist/text.js +13 -0
  45. package/dist/version.d.ts +1 -0
  46. package/dist/version.js +3 -0
  47. package/dist/workspace.js +5 -12
  48. package/package.json +4 -4
@@ -1,7 +1,10 @@
1
- import { AgentToolRunner, CancellationRequested, ConfigurationError, createAgentSession, log, raiseCancellation, requireAgentSession, resolveRoleTarget, SessionStore, } from "@nodeint/migent-kernel";
2
- import { printAgent, printError, printNotice } from "../display.js";
1
+ import { readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { AgentToolRunner, CancellationRequested, ConfigurationError, ProviderEventReporter, SessionStore, createAgentSession, loadRoleInstructions, loadYamlMapping, log, prependRoleInstructions, requireAgentSession, isSessionLockLost, repairSessionUnlocked, SessionLock, stringOptions, resolveRoleTarget, waitUntil, } from "@nodeint/migent-kernel";
4
+ import { printAgent, printError, printNotice, printResumeCommand } from "../display.js";
3
5
  import { cliHooks } from "../hooks.js";
4
6
  import { requireCurrentSchema } from "../migrate.js";
7
+ import { bindCancellationSignals } from "../signals.js";
5
8
  import { adapterMap, parseOptions } from "../provider.js";
6
9
  import { resolvePrompt } from "../workspace.js";
7
10
  export async function dispatchAgent(workspace, args) {
@@ -13,76 +16,398 @@ export async function dispatchAgent(workspace, args) {
13
16
  options: Object.keys(parsed).length > 0 ? parsed : null,
14
17
  });
15
18
  const [resolvedSessionId, createdSession] = resolveSession(workspace, args.sessionId ?? null, args.role, args.task ?? null);
16
- let resumeExecutionId = null;
17
- if (!createdSession) {
18
- const previous = new SessionStore(workspace).latestExecution(resolvedSessionId);
19
- if (previous !== null)
20
- resumeExecutionId = previous.executionId;
21
- }
22
- const optionLabel = Object.entries(target.options)
23
- .map(([key, value]) => `${key}: ${value}`)
24
- .join(", ") || "default";
25
- printNotice(`agent ${args.role} · ${target.provider}/${target.model} (${optionLabel}).`);
26
- const onSigint = () => {
27
- try {
28
- raiseCancellation(2);
19
+ if (createdSession)
20
+ log(`session_id: ${resolvedSessionId}`);
21
+ let lock;
22
+ try {
23
+ lock = SessionLock.acquire(path.join(workspace, ".migent", "sessions", resolvedSessionId));
24
+ }
25
+ catch (error) {
26
+ if (error instanceof Error) {
27
+ printError(error.message);
28
+ return 2;
29
29
  }
30
- catch {
31
- // The runner observes the pending cancellation.
30
+ throw error;
31
+ }
32
+ try {
33
+ return await runLocked();
34
+ }
35
+ catch (error) {
36
+ if (isSessionLockLost(error)) {
37
+ printError(error.message);
38
+ return 2;
32
39
  }
33
- };
34
- const onSigterm = () => {
40
+ throw error;
41
+ }
42
+ finally {
43
+ lock.release();
44
+ }
45
+ async function runLocked() {
46
+ let resumeExecutionId = null;
47
+ let resumeCancelled = false;
48
+ if (!createdSession) {
49
+ repairSessionUnlocked(workspace, resolvedSessionId);
50
+ const store = new SessionStore(workspace);
51
+ if (store.readSessionStatus(resolvedSessionId).status === "cancelled") {
52
+ resumeCancelled = true;
53
+ }
54
+ else {
55
+ const previous = store.latestExecution(resolvedSessionId);
56
+ if (previous !== null)
57
+ resumeExecutionId = previous.executionId;
58
+ }
59
+ }
60
+ const optionLabel = Object.entries(target.options)
61
+ .map(([key, value]) => `${key}: ${value}`)
62
+ .join(", ") || "default";
63
+ printNotice(`agent ${args.role} · ${target.provider}/${target.model} (${optionLabel}).`);
64
+ const unbind = bindCancellationSignals();
65
+ let result;
66
+ let resumedMany = null;
35
67
  try {
36
- raiseCancellation(15);
68
+ const runner = args.runner ?? cliHooks.runner ?? new AgentToolRunner(adapterMap());
69
+ const waitedUntil = new Map();
70
+ const rerun = (prompt, resumeId) => runner.run({
71
+ provider: target.provider,
72
+ model: target.model,
73
+ options: { ...target.options },
74
+ prompt,
75
+ workspace,
76
+ sessionId: resolvedSessionId,
77
+ resumeExecutionId: resumeId,
78
+ stageId: args.role,
79
+ });
80
+ if (resumeCancelled) {
81
+ const reopened = new SessionStore(workspace).reopenCancelledExecutions(resolvedSessionId);
82
+ for (const item of reopened) {
83
+ log(`session ${resolvedSessionId} was cancelled during ${item.executionId}. Resuming that execution.`);
84
+ }
85
+ const settled = await Promise.allSettled(reopened.map((item) => runner.continueCancelled(workspace, resolvedSessionId, item.executionId)));
86
+ const rejected = settled.find((item) => item.status === "rejected");
87
+ if (rejected && rejected.status === "rejected")
88
+ throw rejected.reason;
89
+ const continued = settled.flatMap((item) => (item.status === "fulfilled" ? [item.value] : []));
90
+ if (continued.length === 0) {
91
+ throw new ConfigurationError(`Session ${resolvedSessionId} is cancelled without an execution.`);
92
+ }
93
+ const finished = [];
94
+ for (const item of continued) {
95
+ finished.push(await rerunQuota(workspace, resolvedSessionId, item, (current) => lastUserPrompt(workspace, resolvedSessionId, current.execution_id), rerun, waitedUntil, args.waitHooks));
96
+ }
97
+ if (finished.length === 1)
98
+ result = finished[0];
99
+ else
100
+ resumedMany = finished;
101
+ }
102
+ else {
103
+ const prompt = prependRoleInstructions(loadRoleInstructions(loadYamlMapping(path.join(workspace, ".migent", "config.yaml")), args.role), resolvePrompt({
104
+ prompt: args.prompt ?? null,
105
+ promptFile: args.promptFile ?? null,
106
+ promptStdin: args.promptStdin ?? false,
107
+ }));
108
+ result = await rerunQuota(workspace, resolvedSessionId, await runner.run({
109
+ provider: target.provider,
110
+ model: target.model,
111
+ options: { ...target.options },
112
+ prompt,
113
+ workspace,
114
+ sessionId: resolvedSessionId,
115
+ resumeExecutionId,
116
+ stageId: args.role,
117
+ }), () => prompt, rerun, waitedUntil, args.waitHooks);
118
+ }
119
+ }
120
+ catch (error) {
121
+ if (error instanceof CancellationRequested) {
122
+ log(error.message);
123
+ return finishRole(resolvedSessionId, 130);
124
+ }
125
+ if (error instanceof ConfigurationError)
126
+ throw error;
127
+ if (error instanceof Error) {
128
+ printError(error.message);
129
+ return 2;
130
+ }
131
+ throw error;
37
132
  }
38
- catch {
39
- // The runner observes the pending cancellation.
133
+ finally {
134
+ unbind();
40
135
  }
41
- };
42
- process.on("SIGINT", onSigint);
43
- process.on("SIGTERM", onSigterm);
44
- let result;
136
+ if (resumedMany !== null) {
137
+ if (args.asJson)
138
+ process.stdout.write(`${JSON.stringify(resumedMany)}\n`);
139
+ else {
140
+ for (const item of resumedMany)
141
+ printAgent({ ...item }, args.role, target.provider, target.model);
142
+ }
143
+ if (resumedMany.some((item) => item.failure_reason === "verify_exhausted")) {
144
+ return finishRole(resolvedSessionId, 6);
145
+ }
146
+ return finishRole(resolvedSessionId, resumedMany.every((item) => item.outcome_status === "complete") ? 0 : 1);
147
+ }
148
+ if (result === undefined)
149
+ throw new Error("Agent run produced no result.");
150
+ if (args.asJson)
151
+ process.stdout.write(`${JSON.stringify(result)}\n`);
152
+ else
153
+ printAgent({ ...result }, args.role, target.provider, target.model);
154
+ return finishRole(resolvedSessionId, result.outcome_status === "complete" ? 0 : 1);
155
+ }
156
+ }
157
+ export async function continueAgent(workspace, args) {
158
+ requireCurrentSchema(workspace);
159
+ requireAgentSession(workspace, args.sessionId);
160
+ let lock;
45
161
  try {
46
- const runner = args.runner ?? cliHooks.runner ?? new AgentToolRunner(adapterMap());
47
- result = await runner.run({
48
- provider: target.provider,
49
- model: target.model,
50
- options: { ...target.options },
51
- prompt: resolvePrompt({
52
- prompt: args.prompt ?? null,
53
- promptFile: args.promptFile ?? null,
54
- promptStdin: args.promptStdin ?? false,
55
- }),
56
- workspace,
57
- sessionId: resolvedSessionId,
58
- resumeExecutionId,
59
- stageId: args.role,
60
- });
162
+ lock = SessionLock.acquire(path.join(workspace, ".migent", "sessions", args.sessionId));
61
163
  }
62
164
  catch (error) {
63
- if (error instanceof CancellationRequested) {
64
- log(error.message);
65
- return 130;
66
- }
67
- if (error instanceof ConfigurationError)
68
- throw error;
69
165
  if (error instanceof Error) {
70
166
  printError(error.message);
71
167
  return 2;
72
168
  }
73
169
  throw error;
74
170
  }
171
+ try {
172
+ return await continueLocked();
173
+ }
174
+ catch (error) {
175
+ if (isSessionLockLost(error)) {
176
+ printError(error.message);
177
+ return 2;
178
+ }
179
+ throw error;
180
+ }
75
181
  finally {
76
- process.off("SIGINT", onSigint);
77
- process.off("SIGTERM", onSigterm);
182
+ lock.release();
183
+ }
184
+ async function continueLocked() {
185
+ repairSessionUnlocked(workspace, args.sessionId);
186
+ const store = new SessionStore(workspace);
187
+ const status = store.readSessionStatus(args.sessionId);
188
+ const sessionStatus = typeof status.status === "string" ? status.status : "";
189
+ const role = typeof status.role === "string" ? status.role : "";
190
+ if (role === "")
191
+ throw new ConfigurationError(`Session ${args.sessionId} has no role.`);
192
+ if (sessionStatus === "completed" && args.reply === null) {
193
+ printNotice(`Session ${args.sessionId} is completed.`);
194
+ return 0;
195
+ }
196
+ if (sessionStatus === "cancelled" && args.reply !== null) {
197
+ throw new ConfigurationError(`Session ${args.sessionId} was cancelled. session continue resumes that execution. Omit --reply.`);
198
+ }
199
+ const latest = store.latestExecution(args.sessionId);
200
+ if (latest === null)
201
+ throw new ConfigurationError(`Session ${args.sessionId} has no execution.`);
202
+ const profile = executionProfile(latest.directory, latest.executionId);
203
+ const optionLabel = Object.entries(profile.options)
204
+ .map(([key, value]) => `${key}: ${value}`)
205
+ .join(", ") || "default";
206
+ printNotice(`agent ${role} · ${profile.provider}/${profile.model} (${optionLabel}).`);
207
+ const unbind = bindCancellationSignals();
208
+ try {
209
+ const runner = args.runner ?? cliHooks.runner ?? new AgentToolRunner(adapterMap());
210
+ if (sessionStatus === "cancelled") {
211
+ return await runCancelledRole(workspace, args.sessionId, role, profile, runner, false, args.waitHooks);
212
+ }
213
+ let prompt;
214
+ if (args.reply !== null) {
215
+ prompt = rolePrompt(workspace, role, args.reply);
216
+ }
217
+ else if (sessionStatus === "blocked") {
218
+ const blocker = executionBlocker(latest.directory);
219
+ const resumed = store.resumeBlockedSession(args.sessionId);
220
+ for (const item of resumed) {
221
+ log(`session ${args.sessionId} was blocked by ${item.executionId}. Resuming ${role}.`);
222
+ }
223
+ const lines = ["Previous attempt:", `- execution: ${latest.executionId}`];
224
+ if (blocker)
225
+ lines.push(`- blocker: ${blocker}`);
226
+ lines.push("That attempt ended blocked. The blocker was handled outside this session. Continue.");
227
+ prompt = rolePrompt(workspace, role, lines.join("\n"));
228
+ }
229
+ else if (sessionStatus === "active") {
230
+ log(`session ${args.sessionId} is active. Resuming ${role} with its last prompt.`);
231
+ prompt = lastUserPrompt(workspace, args.sessionId, latest.executionId);
232
+ }
233
+ else {
234
+ throw new ConfigurationError(`Session ${args.sessionId} is ${sessionStatus || "unknown"}.`);
235
+ }
236
+ const waitedUntil = new Map();
237
+ const rerun = (nextPrompt, resumeId) => runner.run({
238
+ provider: profile.provider,
239
+ model: profile.model,
240
+ options: { ...profile.options },
241
+ prompt: nextPrompt,
242
+ workspace,
243
+ sessionId: args.sessionId,
244
+ resumeExecutionId: resumeId,
245
+ stageId: role,
246
+ });
247
+ const result = await rerunQuota(workspace, args.sessionId, await rerun(prompt, latest.executionId), () => prompt, rerun, waitedUntil, args.waitHooks);
248
+ printAgent({ ...result }, role, profile.provider, profile.model);
249
+ return finishRole(args.sessionId, result.outcome_status === "complete" ? 0 : 1);
250
+ }
251
+ catch (error) {
252
+ if (error instanceof CancellationRequested) {
253
+ log(error.message);
254
+ return finishRole(args.sessionId, 130);
255
+ }
256
+ if (error instanceof ConfigurationError)
257
+ throw error;
258
+ if (error instanceof Error) {
259
+ printError(error.message);
260
+ return 2;
261
+ }
262
+ throw error;
263
+ }
264
+ finally {
265
+ unbind();
266
+ }
267
+ }
268
+ }
269
+ async function runCancelledRole(workspace, sessionId, role, profile, runner, asJson, waitHooks) {
270
+ const reopened = new SessionStore(workspace).reopenCancelledExecutions(sessionId);
271
+ for (const item of reopened) {
272
+ log(`session ${sessionId} was cancelled during ${item.executionId}. Resuming that execution.`);
273
+ }
274
+ const waitedUntil = new Map();
275
+ const rerun = (prompt, resumeId) => runner.run({
276
+ provider: profile.provider,
277
+ model: profile.model,
278
+ options: { ...profile.options },
279
+ prompt,
280
+ workspace,
281
+ sessionId,
282
+ resumeExecutionId: resumeId,
283
+ stageId: role,
284
+ });
285
+ const settled = await Promise.allSettled(reopened.map((item) => runner.continueCancelled(workspace, sessionId, item.executionId)));
286
+ const rejected = settled.find((item) => item.status === "rejected");
287
+ if (rejected && rejected.status === "rejected")
288
+ throw rejected.reason;
289
+ const continued = settled.flatMap((item) => (item.status === "fulfilled" ? [item.value] : []));
290
+ if (continued.length === 0) {
291
+ throw new ConfigurationError(`Session ${sessionId} is cancelled without an execution.`);
292
+ }
293
+ const finished = [];
294
+ for (const item of continued) {
295
+ finished.push(await rerunQuota(workspace, sessionId, item, (current) => lastUserPrompt(workspace, sessionId, current.execution_id), rerun, waitedUntil, waitHooks));
296
+ }
297
+ if (finished.length === 1) {
298
+ const result = finished[0];
299
+ if (result === undefined)
300
+ throw new Error("Agent run produced no result.");
301
+ if (asJson)
302
+ process.stdout.write(`${JSON.stringify(result)}\n`);
303
+ else
304
+ printAgent({ ...result }, role, profile.provider, profile.model);
305
+ return finishRole(sessionId, result.outcome_status === "complete" ? 0 : 1);
306
+ }
307
+ if (asJson)
308
+ process.stdout.write(`${JSON.stringify(finished)}\n`);
309
+ else {
310
+ for (const item of finished)
311
+ printAgent({ ...item }, role, profile.provider, profile.model);
312
+ }
313
+ if (finished.some((item) => item.failure_reason === "verify_exhausted"))
314
+ return finishRole(sessionId, 6);
315
+ return finishRole(sessionId, finished.every((item) => item.outcome_status === "complete") ? 0 : 1);
316
+ }
317
+ async function rerunQuota(workspace, sessionId, initial, promptFor, run, waitedUntil, hooks) {
318
+ let current = initial;
319
+ let armed = null;
320
+ while (current.failure_kind === "quota" && current.reset_at !== "") {
321
+ if (armed !== null && !laterInstant(current.reset_at, armed))
322
+ return current;
323
+ const resetAt = current.reset_at;
324
+ const provider = current.provider;
325
+ const shared = waitedUntil.get(provider);
326
+ const now = hooks?.now ?? Date.now;
327
+ const past = !Number.isFinite(Date.parse(resetAt)) || now() >= Date.parse(resetAt);
328
+ if (!past && (shared === undefined || laterInstant(resetAt, shared))) {
329
+ emitRoleQuota(workspace, sessionId, current);
330
+ await waitUntil(resetAt, hooks).promise;
331
+ }
332
+ waitedUntil.set(provider, resetAt);
333
+ armed = resetAt;
334
+ current = await run(promptFor(current), current.execution_id);
335
+ }
336
+ return current;
337
+ }
338
+ function laterInstant(next, previous) {
339
+ const left = Date.parse(next);
340
+ const right = Date.parse(previous);
341
+ if (Number.isFinite(left) && Number.isFinite(right))
342
+ return left > right;
343
+ return next > previous;
344
+ }
345
+ function emitRoleQuota(workspace, sessionId, result) {
346
+ const store = new SessionStore(workspace);
347
+ const context = store.executionContext(sessionId, result.execution_id);
348
+ if (context === null)
349
+ return;
350
+ let model = "";
351
+ try {
352
+ const metadata = JSON.parse(readFileSync(path.join(context.directory, "execution.json"), "utf8"));
353
+ if (isRecord(metadata) && typeof metadata.model === "string")
354
+ model = metadata.model;
355
+ }
356
+ catch {
357
+ model = "";
358
+ }
359
+ const summary = [result.response, result.reset_at].filter((item) => item !== "").join(" ");
360
+ new ProviderEventReporter(store, context, result.provider, model).emit("provider.quota", summary, {
361
+ retry_at: result.reset_at,
362
+ provider: result.provider,
363
+ });
364
+ }
365
+ function lastUserPrompt(workspace, sessionId, executionId) {
366
+ const context = new SessionStore(workspace).executionContext(sessionId, executionId);
367
+ if (context === null)
368
+ throw new ConfigurationError(`Execution ${executionId} was not found.`);
369
+ const raw = JSON.parse(readFileSync(context.conversationReadPath, "utf8"));
370
+ const history = Array.isArray(raw) ? raw : isRecord(raw) && Array.isArray(raw.history) ? raw.history : [];
371
+ for (let index = history.length - 1; index >= 0; index -= 1) {
372
+ const entry = history[index];
373
+ if (isRecord(entry) && entry.role === "user" && typeof entry.content === "string" && entry.content !== "") {
374
+ return entry.content;
375
+ }
376
+ }
377
+ throw new ConfigurationError(`Execution ${executionId} has no prompt to resume.`);
378
+ }
379
+ function isRecord(value) {
380
+ return typeof value === "object" && value !== null && !Array.isArray(value);
381
+ }
382
+ function finishRole(sessionId, code) {
383
+ if (code === 1 || code === 6 || code === 130)
384
+ printResumeCommand(sessionId);
385
+ return code;
386
+ }
387
+ function rolePrompt(workspace, role, text) {
388
+ return prependRoleInstructions(loadRoleInstructions(loadYamlMapping(path.join(workspace, ".migent", "config.yaml")), role), text);
389
+ }
390
+ function executionProfile(directory, executionId) {
391
+ const raw = JSON.parse(readFileSync(path.join(directory, "execution.json"), "utf8"));
392
+ if (!isRecord(raw) ||
393
+ typeof raw.provider !== "string" ||
394
+ raw.provider === "" ||
395
+ typeof raw.model !== "string" ||
396
+ raw.model === "") {
397
+ throw new ConfigurationError(`Execution ${executionId} has no provider.`);
398
+ }
399
+ return { provider: raw.provider, model: raw.model, options: stringOptions(raw.options) };
400
+ }
401
+ function executionBlocker(directory) {
402
+ try {
403
+ const raw = JSON.parse(readFileSync(path.join(directory, "execution.json"), "utf8"));
404
+ if (!isRecord(raw) || !isRecord(raw.outcome))
405
+ return null;
406
+ return typeof raw.outcome.blocker === "string" && raw.outcome.blocker !== "" ? raw.outcome.blocker : null;
407
+ }
408
+ catch {
409
+ return null;
78
410
  }
79
- if (createdSession)
80
- printNotice(`session_id: ${result.session_id}`);
81
- if (args.asJson)
82
- process.stdout.write(`${JSON.stringify(result)}\n`);
83
- else
84
- printAgent({ ...result }, args.role, target.provider, target.model);
85
- return result.outcome_status === "complete" ? 0 : 1;
86
411
  }
87
412
  function resolveSession(workspace, sessionId, role, task) {
88
413
  if (sessionId !== null && typeof task === "string" && task.trim() !== "") {
@@ -1,8 +1,9 @@
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
+ import { PromptCancelled } from "../errors.js";
6
7
  import { cliHooks } from "../hooks.js";
7
8
  import { requireCurrentSchema } from "../migrate.js";
8
9
  import { acceptedOptions, adapterMap, asCatalog, detected, knownModel, loadCatalog, loadOptionValues, parseOptions, requireAdapter, runProviderCommand, } from "../provider.js";
@@ -25,6 +26,8 @@ export async function runConfigModel(workspace, options = {}) {
25
26
  });
26
27
  }
27
28
  catch (error) {
29
+ if (error instanceof PromptCancelled)
30
+ throw error;
28
31
  if (error instanceof ConfigurationError || error instanceof ConfigEditError || error instanceof Error) {
29
32
  printError(error.message);
30
33
  return 2;
@@ -43,6 +46,8 @@ export async function runConfigRole(workspace, options = {}) {
43
46
  });
44
47
  }
45
48
  catch (error) {
49
+ if (error instanceof PromptCancelled)
50
+ throw error;
46
51
  if (error instanceof ConfigurationError || error instanceof ConfigEditError || error instanceof Error) {
47
52
  printError(error.message);
48
53
  return 2;
@@ -311,6 +316,8 @@ function validateChange(before, after, workflows, adapters) {
311
316
  }
312
317
  for (const document of workflows) {
313
318
  for (const stage of Object.values(document.stages)) {
319
+ if (!isRoleStage(stage))
320
+ continue;
314
321
  let beforeTarget;
315
322
  let afterTarget;
316
323
  try {
@@ -382,7 +389,7 @@ function optionUpdatesOf(options, clearOptions) {
382
389
  async function selectProvider(prompter, adapters, which, message, defaultValue) {
383
390
  const rows = detected(adapters, which);
384
391
  if (rows.length === 0)
385
- throw new Error("No providers are available.");
392
+ throw new ConfigurationError("No providers are available.");
386
393
  const names = new Set(rows.map(([name]) => name));
387
394
  return prompter.select(message, rows.map(([name, found]) => [`${name} ${found ?? "not found"}`, name]), defaultValue !== null && names.has(defaultValue) ? defaultValue : rows[0]?.[0]);
388
395
  }
@@ -486,6 +493,7 @@ function roleJson(role) {
486
493
  provider: role.provider,
487
494
  model: role.model,
488
495
  options: { ...role.options },
496
+ instructions: [...role.instructions],
489
497
  shared_with: [...role.sharedWith],
490
498
  stages: role.stages.map((stage) => ({
491
499
  workflow: stage.workflow,
@@ -586,7 +594,7 @@ function isRecord(value) {
586
594
  class ClackConfigPrompter {
587
595
  async select(message, choices, defaultValue) {
588
596
  if (choices.length === 0)
589
- throw new Error("Choose a value.");
597
+ throw new ConfigurationError("Choose a value.");
590
598
  const values = new Set(choices.map(([, value]) => value));
591
599
  const initial = defaultValue != null && values.has(defaultValue) ? defaultValue : choices[0]?.[1];
592
600
  const result = await clackSelect({
@@ -594,20 +602,26 @@ class ClackConfigPrompter {
594
602
  options: choices.map(([label, value]) => ({ label, value })),
595
603
  initialValue: initial,
596
604
  });
597
- if (isCancel(result) || typeof result !== "string" || !values.has(result))
598
- throw new Error("Choose a value.");
605
+ if (isCancel(result))
606
+ throw new PromptCancelled();
607
+ if (typeof result !== "string" || !values.has(result))
608
+ throw new ConfigurationError("Choose a value.");
599
609
  return result;
600
610
  }
601
611
  async confirm(message) {
602
612
  const result = await clackConfirm({ message, initialValue: true });
603
- if (isCancel(result) || typeof result !== "boolean")
604
- throw new Error("Answer yes or no.");
613
+ if (isCancel(result))
614
+ throw new PromptCancelled();
615
+ if (typeof result !== "boolean")
616
+ throw new ConfigurationError("Answer yes or no.");
605
617
  return result;
606
618
  }
607
619
  async text(message, defaultValue = "") {
608
620
  const result = await clackText({ message, initialValue: defaultValue });
609
- if (isCancel(result) || typeof result !== "string")
610
- throw new Error("Enter a value.");
621
+ if (isCancel(result))
622
+ throw new PromptCancelled();
623
+ if (typeof result !== "string")
624
+ throw new ConfigurationError("Enter a value.");
611
625
  return result;
612
626
  }
613
627
  }
@@ -5,8 +5,13 @@ 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
+ stageId?: string | null;
10
11
  runner?: AgentToolRunner | null;
12
+ itemProgress?: boolean;
13
+ startPrepared?: boolean;
14
+ refreshWorkflow?: boolean;
15
+ reply?: string | null;
11
16
  }
12
17
  export declare function executeWorkflow(workspace: string, options: ExecuteWorkflowOptions): Promise<number>;