@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.
- package/README.md +194 -138
- package/dist/commands/agent.d.ts +8 -1
- package/dist/commands/agent.js +381 -56
- package/dist/commands/config.js +23 -9
- package/dist/commands/execute.d.ts +6 -1
- package/dist/commands/execute.js +105 -20
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +70 -63
- package/dist/commands/prepare.d.ts +1 -0
- package/dist/commands/prepare.js +14 -0
- package/dist/commands/repair.d.ts +1 -0
- package/dist/commands/repair.js +22 -0
- package/dist/commands/service.js +5 -4
- package/dist/commands/sessions.d.ts +24 -1
- package/dist/commands/sessions.js +165 -13
- package/dist/commands/skill.js +58 -32
- package/dist/commands/watch.d.ts +2 -1
- package/dist/commands/watch.js +130 -67
- package/dist/commands/workspace-registry.js +10 -1
- package/dist/display.d.ts +1 -0
- package/dist/display.js +24 -9
- package/dist/errors.d.ts +3 -0
- package/dist/errors.js +6 -0
- package/dist/exits.d.ts +12 -0
- package/dist/exits.js +33 -0
- package/dist/follow-log.d.ts +29 -0
- package/dist/follow-log.js +172 -0
- package/dist/fs-utils.d.ts +3 -0
- package/dist/fs-utils.js +26 -0
- package/dist/index.js +5 -1
- package/dist/migrate.js +2 -17
- package/dist/program.js +429 -177
- package/dist/session-row.d.ts +20 -0
- package/dist/session-row.js +21 -0
- package/dist/signals.d.ts +3 -0
- package/dist/signals.js +56 -0
- package/dist/templates/migent/SKILL.md +163 -56
- package/dist/templates/migent/references/config.md +52 -0
- package/dist/templates/migent/references/inspect.md +32 -0
- package/dist/templates/migent/references/schema-config.yaml +35 -0
- package/dist/templates/migent/references/schema-workflow.yaml +71 -0
- package/dist/templates/migent/references/sessions.md +65 -0
- package/dist/text.d.ts +4 -0
- package/dist/text.js +13 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +3 -0
- package/dist/workspace.js +5 -12
- package/package.json +4 -4
package/dist/commands/agent.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
133
|
+
finally {
|
|
134
|
+
unbind();
|
|
40
135
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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() !== "") {
|
package/dist/commands/config.js
CHANGED
|
@@ -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
|
|
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
|
|
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)
|
|
598
|
-
throw new
|
|
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)
|
|
604
|
-
throw new
|
|
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)
|
|
610
|
-
throw new
|
|
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>;
|