@qilin-agent/tool-workflow 0.0.0-stage → 3.1.3
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/LICENSE +21 -0
- package/README.i18n.yaml +97 -0
- package/README.md +172 -2
- package/README.zh.md +173 -0
- package/lib/index.js +445 -0
- package/lib/types/index.d.ts +40 -0
- package/lib/types/index.js +411 -0
- package/lib/types/record.d.ts +35 -0
- package/lib/types/record.js +43 -0
- package/lib/types/types.d.ts +57 -0
- package/lib/types/types.js +8 -0
- package/package.json +58 -3
package/lib/index.js
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import z from "@qilin-agent/schemastery";
|
|
2
|
+
import { defineTool } from "@qilin-agent/tools";
|
|
3
|
+
//#region lib/types/record.js
|
|
4
|
+
/**
|
|
5
|
+
* Live-progress mirror for background workflow runs: streams the engine's
|
|
6
|
+
* `workflow/phase`, `workflow/log`, and member lifecycle events into the
|
|
7
|
+
* owning job's output ring as `log` chunks — observer-only narration the
|
|
8
|
+
* model's `job_output` never renders — and keeps the job's live progress
|
|
9
|
+
* line on the current phase. Appends against a settled job log and drop
|
|
10
|
+
* inside the registry, so a straggling event after settlement is harmless.
|
|
11
|
+
* @module @qilin-agent/tool-workflow/record
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Create the run-to-ring mirror and subscribe the engine's live progress
|
|
15
|
+
* events for the runs it tracks.
|
|
16
|
+
* @param ctx - plugin context whose event bus carries the `workflow/*` events.
|
|
17
|
+
* @returns the mirror taps the tool wires around each background run.
|
|
18
|
+
*/
|
|
19
|
+
function createWorkflowRecordMirror(ctx) {
|
|
20
|
+
const active = /* @__PURE__ */ new Map();
|
|
21
|
+
ctx.on("workflow/phase", (info, title) => {
|
|
22
|
+
const job = active.get(info.id);
|
|
23
|
+
if (job === void 0) return;
|
|
24
|
+
job.updateProgress(title);
|
|
25
|
+
job.append(`▸ ${title}\n`, { channel: "log" });
|
|
26
|
+
});
|
|
27
|
+
ctx.on("workflow/log", (info, message) => {
|
|
28
|
+
active.get(info.id)?.append(`${message}\n`, { channel: "log" });
|
|
29
|
+
});
|
|
30
|
+
ctx.on("workflow/agent-start", (info, agent) => {
|
|
31
|
+
active.get(info.id)?.append(`agent #${agent.seq} ${agent.label} started\n`, { channel: "log" });
|
|
32
|
+
});
|
|
33
|
+
ctx.on("workflow/agent-end", (info, agent) => {
|
|
34
|
+
active.get(info.id)?.append(`agent #${agent.seq} ${agent.outcome}\n`, { channel: "log" });
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
start(runId, job) {
|
|
38
|
+
active.set(runId, job);
|
|
39
|
+
},
|
|
40
|
+
stop(runId) {
|
|
41
|
+
active.delete(runId);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region lib/types/index.js
|
|
47
|
+
/**
|
|
48
|
+
* The model-facing `workflow` tool: run a JavaScript orchestration script that fans out
|
|
49
|
+
* subagents, and return the script's final value. It owns the model-facing schema and run lifecycle; script
|
|
50
|
+
* parsing, execution, caps, and cancellation live behind `ctx.workflowEngine`
|
|
51
|
+
* (`@qilin-agent/workflow`), so a hardened engine swaps in without touching what the model
|
|
52
|
+
* sees. Foreground execution awaits `run.result` and always disposes the run; non-completed reasons
|
|
53
|
+
* become tool errors. `run_in_background: true` instead registers the run as an owned `ctx.jobs` job
|
|
54
|
+
* and returns its id immediately — the job's output ring streams live progress, and the run's value
|
|
55
|
+
* arrives with the job's completion notice. Presentation is an args-only generic card
|
|
56
|
+
* titled from `meta.name`. Explicit-ask usage guidance is registered as the tool's own prompt
|
|
57
|
+
* section rather than deployment persona prose.
|
|
58
|
+
* @module @qilin-agent/tool-workflow
|
|
59
|
+
*/
|
|
60
|
+
const name = "tool-workflow";
|
|
61
|
+
const inject = [
|
|
62
|
+
"tools",
|
|
63
|
+
"workflowEngine",
|
|
64
|
+
"systemPrompt"
|
|
65
|
+
];
|
|
66
|
+
const Config = z.object({
|
|
67
|
+
toolName: z.string().default("workflow"),
|
|
68
|
+
maxResultChars: z.natural().min(1).default(5e4),
|
|
69
|
+
enableRunInBackground: z.boolean().default(true)
|
|
70
|
+
});
|
|
71
|
+
/** Render a contained recording failure without trusting the thrown value. */
|
|
72
|
+
function renderRecordingError(error) {
|
|
73
|
+
try {
|
|
74
|
+
return String(error);
|
|
75
|
+
} catch {
|
|
76
|
+
return "[unrenderable thrown value]";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Project active top-level workflow runs into their parent Sessions without
|
|
81
|
+
* letting recording failure affect tool execution.
|
|
82
|
+
*/
|
|
83
|
+
function createWorkflowRecorder(ctx) {
|
|
84
|
+
const active = /* @__PURE__ */ new Map();
|
|
85
|
+
const append = (session, type, data) => {
|
|
86
|
+
const appendRecord = session.append.bind(session);
|
|
87
|
+
try {
|
|
88
|
+
appendRecord(type, data);
|
|
89
|
+
return true;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
ctx.logger.warn(`tool-workflow: disabled durable record after ${type} append failed: ${renderRecordingError(error)}`);
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
ctx.on("workflow/agent-start", (info, agent) => {
|
|
96
|
+
const session = active.get(info.id);
|
|
97
|
+
if (session === void 0) return;
|
|
98
|
+
if (!append(session, "tool-workflow/agent-start", {
|
|
99
|
+
runId: info.id,
|
|
100
|
+
seq: agent.seq,
|
|
101
|
+
label: agent.label,
|
|
102
|
+
...agent.phase === void 0 ? {} : { phase: agent.phase },
|
|
103
|
+
childId: agent.childId
|
|
104
|
+
})) active.delete(info.id);
|
|
105
|
+
});
|
|
106
|
+
ctx.on("workflow/agent-end", (info, agent) => {
|
|
107
|
+
const session = active.get(info.id);
|
|
108
|
+
if (session === void 0) return;
|
|
109
|
+
if (!append(session, "tool-workflow/agent-end", {
|
|
110
|
+
runId: info.id,
|
|
111
|
+
seq: agent.seq,
|
|
112
|
+
outcome: agent.outcome
|
|
113
|
+
})) active.delete(info.id);
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
start(session, run) {
|
|
117
|
+
if (append(session, "tool-workflow/run-start", {
|
|
118
|
+
runId: run.id,
|
|
119
|
+
name: run.meta.name
|
|
120
|
+
})) active.set(run.id, session);
|
|
121
|
+
},
|
|
122
|
+
finish(runId, stopReason) {
|
|
123
|
+
const session = active.get(runId);
|
|
124
|
+
if (session !== void 0) append(session, "tool-workflow/run-end", {
|
|
125
|
+
runId,
|
|
126
|
+
stopReason
|
|
127
|
+
});
|
|
128
|
+
active.delete(runId);
|
|
129
|
+
},
|
|
130
|
+
abandon: (runId) => {
|
|
131
|
+
active.delete(runId);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The script-authoring contract, embedded in the tool description. This IS the
|
|
137
|
+
* model-facing spec: the meta block, the hooks and their exact semantics, and
|
|
138
|
+
* the supported schema subset.
|
|
139
|
+
*/
|
|
140
|
+
const DESCRIPTION = `Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.
|
|
141
|
+
|
|
142
|
+
The workflow's identity rides the \`meta\` parameter as JSON: required \`name\` (short kebab-case) and \`description\` strings, optional \`whenToUse\` string and \`phases\` array (\`{title, detail?, provider?, model?}\`). The \`script\` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO \`export const meta\` statement — meta is a parameter, not code), running with top-level await; end with \`return <value>\` — the value must be JSON-serializable and is this tool's result.
|
|
143
|
+
|
|
144
|
+
Script-body hooks:
|
|
145
|
+
- \`agent(prompt, opts?): Promise<any>\` — run one subagent to completion. Without \`opts.schema\` it resolves to the child's final text; with \`opts.schema\` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves \`null\` when the child fails (filter with \`.filter(Boolean)\`). Other opts: \`label\` (display), \`phase\` (progress group), and independent \`provider\`/\`model\` LLM target overrides (either may be provided alone). Anything else (\`effort\`/\`isolation\`/\`agentType\`) is rejected loudly.
|
|
146
|
+
- \`pipeline(items, ...stages): Promise<any[]>\` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives \`(prev, item, index)\`. An ordinary stage throw drops that ITEM to \`null\` and skips its remaining stages.
|
|
147
|
+
- \`parallel(thunks): Promise<any[]>\` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to \`null\`.
|
|
148
|
+
- \`phase(title)\` — start a progress phase; \`log(message)\` — narrate progress; \`args\` — the tool call's \`args\` input, verbatim.
|
|
149
|
+
|
|
150
|
+
Misused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item \`null\`.
|
|
151
|
+
|
|
152
|
+
Constraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them.`;
|
|
153
|
+
/** The pending-state card: a generic card titled by the workflow's meta name. */
|
|
154
|
+
function presentWorkflowCall(args) {
|
|
155
|
+
return {
|
|
156
|
+
card: "generic",
|
|
157
|
+
title: `workflow: ${args.meta.name}`,
|
|
158
|
+
rawInput: args.script
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** The completed-state card: keep the pending title; render the result content as-is. */
|
|
162
|
+
function presentWorkflowResult(args, result) {
|
|
163
|
+
return { card: "generic" };
|
|
164
|
+
}
|
|
165
|
+
/** A non-`completed` stop reason means the script did not finish cleanly. */
|
|
166
|
+
function stopReasonError(result) {
|
|
167
|
+
switch (result.stopReason) {
|
|
168
|
+
case "completed": return;
|
|
169
|
+
case "cancelled": return `workflow run was cancelled${result.error !== void 0 ? ` (${result.error})` : ""}`;
|
|
170
|
+
case "error": return `workflow run failed: ${result.error ?? "unknown error"}`;
|
|
171
|
+
/* v8 ignore start -- defensive: WorkflowStopReason is a closed union, exhaustive by construction; a future variant fails here loudly */
|
|
172
|
+
default: return `workflow run ended abnormally (${String(result.stopReason)})`;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/** Render the run's outcome text: the meta name, agent count, and the JSON value (capped). */
|
|
176
|
+
function renderResult(name, agentsStarted, value, maxChars) {
|
|
177
|
+
const rendered = JSON.stringify(value, null, 2);
|
|
178
|
+
const clipped = rendered.length > maxChars ? `${rendered.slice(0, maxChars)}\n… [truncated: ${rendered.length - maxChars} more characters]` : rendered;
|
|
179
|
+
return `workflow "${name}" completed (${agentsStarted} agent${agentsStarted === 1 ? "" : "s"}).\nReturn value:\n${clipped}`;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Map a settled background run onto the job outcome vocabulary. A completed
|
|
183
|
+
* run carries the rendered return value as the job's result; a cancelled run
|
|
184
|
+
* leaves the detail to the registry's kill-reason merge (the cancel reason it
|
|
185
|
+
* forwarded is the same string); an errored run fails with the script's
|
|
186
|
+
* failure message.
|
|
187
|
+
* @param result - settled engine result.
|
|
188
|
+
* @param name - the workflow's meta name, for the rendered result.
|
|
189
|
+
* @param maxChars - rendered-result ceiling in characters.
|
|
190
|
+
* @returns the outcome for the job registration.
|
|
191
|
+
*/
|
|
192
|
+
function jobOutcomeOf(result, name, maxChars) {
|
|
193
|
+
switch (result.stopReason) {
|
|
194
|
+
case "completed": return {
|
|
195
|
+
status: "completed",
|
|
196
|
+
detail: `${result.agentsStarted} agent${result.agentsStarted === 1 ? "" : "s"}`,
|
|
197
|
+
result: renderResult(name, result.agentsStarted, result.value, maxChars)
|
|
198
|
+
};
|
|
199
|
+
case "cancelled": return { status: "killed" };
|
|
200
|
+
case "error": return {
|
|
201
|
+
status: "failed",
|
|
202
|
+
detail: result.error ?? "unknown error"
|
|
203
|
+
};
|
|
204
|
+
/* v8 ignore start -- defensive: WorkflowStopReason is a closed union, exhaustive by construction; a future variant fails here loudly */
|
|
205
|
+
default: return {
|
|
206
|
+
status: "failed",
|
|
207
|
+
detail: `workflow run ended abnormally (${String(result.stopReason)})`
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Register a background run as an owned job. The engine run is started inside
|
|
213
|
+
* the job starter with no tool-step signal — the run belongs to the job, so a
|
|
214
|
+
* registry kill or owner teardown is what cancels it — and its settlement is
|
|
215
|
+
* the job's settlement: dispose, stop the mirrors, then map the stop reason
|
|
216
|
+
* onto the job outcome (a completed run's rendered return value rides
|
|
217
|
+
* `result` to the model's first read after settlement).
|
|
218
|
+
* @param ctx - plugin context (engine, optional jobs registry, logger).
|
|
219
|
+
* @param args - the validated tool call.
|
|
220
|
+
* @param parent - the calling agent; owns the job.
|
|
221
|
+
* @param recordsRun - whether this top-level call records durable run events.
|
|
222
|
+
* @param deps - the tool's recorder/mirror taps and the render cap.
|
|
223
|
+
* @returns the background result for the tool's output schema.
|
|
224
|
+
*/
|
|
225
|
+
function startBackgroundRun(ctx, args, parent, recordsRun, deps) {
|
|
226
|
+
const jobs = ctx.get("jobs");
|
|
227
|
+
if (jobs === void 0) throw new Error("background jobs unavailable: load @qilin-agent/jobs and @qilin-agent/tool-jobs");
|
|
228
|
+
let run;
|
|
229
|
+
return {
|
|
230
|
+
kind: "background",
|
|
231
|
+
jobId: jobs.start({
|
|
232
|
+
kind: "workflow",
|
|
233
|
+
label: args.meta.name,
|
|
234
|
+
owner: parent.id,
|
|
235
|
+
run: (job) => {
|
|
236
|
+
run = ctx.workflowEngine.start({
|
|
237
|
+
script: args.script,
|
|
238
|
+
meta: args.meta,
|
|
239
|
+
...args.args !== void 0 ? { args: args.args } : {},
|
|
240
|
+
parent
|
|
241
|
+
});
|
|
242
|
+
deps.mirror.start(run.id, job);
|
|
243
|
+
if (recordsRun) deps.recorder.start(parent.session, run);
|
|
244
|
+
return {
|
|
245
|
+
cancel: (reason) => {
|
|
246
|
+
run.cancel(reason ?? "background workflow job killed");
|
|
247
|
+
},
|
|
248
|
+
done: run.result.then(async (result) => {
|
|
249
|
+
try {
|
|
250
|
+
await run.dispose();
|
|
251
|
+
} catch (error) {
|
|
252
|
+
ctx.logger.warn(`background workflow run ${run.id} dispose failed: ${String(error)}`);
|
|
253
|
+
}
|
|
254
|
+
deps.mirror.stop(run.id);
|
|
255
|
+
if (recordsRun) {
|
|
256
|
+
deps.recorder.finish(run.id, result.stopReason);
|
|
257
|
+
deps.recorder.abandon(run.id);
|
|
258
|
+
}
|
|
259
|
+
return jobOutcomeOf(result, args.meta.name, deps.maxResultChars);
|
|
260
|
+
})
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}),
|
|
264
|
+
runId: run.id
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function apply(ctx, config) {
|
|
268
|
+
const { toolName, maxResultChars, enableRunInBackground } = config;
|
|
269
|
+
const recorder = createWorkflowRecorder(ctx);
|
|
270
|
+
const mirror = createWorkflowRecordMirror(ctx);
|
|
271
|
+
ctx.systemPrompt.section({
|
|
272
|
+
name: `tool:${toolName}`,
|
|
273
|
+
order: ctx.systemPrompt.getSectionOrder("TOOL_WORKFLOW"),
|
|
274
|
+
text: `Use the ${toolName} tool ONLY when the user explicitly asks for a workflow or for large multi-agent orchestration: you write a JavaScript script (the tool description documents the exact format) that fans work out across many subagents with phases and structured results. For one or two delegations, prefer plain subagent calls.`
|
|
275
|
+
});
|
|
276
|
+
ctx.tools.register(defineTool({
|
|
277
|
+
name: toolName,
|
|
278
|
+
description: DESCRIPTION,
|
|
279
|
+
parameters: {
|
|
280
|
+
script: {
|
|
281
|
+
type: "string",
|
|
282
|
+
required: true,
|
|
283
|
+
description: "The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return <json-value>`)."
|
|
284
|
+
},
|
|
285
|
+
meta: {
|
|
286
|
+
type: "object",
|
|
287
|
+
additionalProperties: true,
|
|
288
|
+
required: true,
|
|
289
|
+
description: "The workflow identity block (plain JSON — never code).",
|
|
290
|
+
properties: {
|
|
291
|
+
name: {
|
|
292
|
+
type: "string",
|
|
293
|
+
required: true,
|
|
294
|
+
description: "Short kebab-case workflow name."
|
|
295
|
+
},
|
|
296
|
+
description: {
|
|
297
|
+
type: "string",
|
|
298
|
+
required: true,
|
|
299
|
+
description: "One-line description of what the workflow does."
|
|
300
|
+
},
|
|
301
|
+
whenToUse: {
|
|
302
|
+
type: "string",
|
|
303
|
+
description: "Optional guidance on when this workflow applies."
|
|
304
|
+
},
|
|
305
|
+
phases: {
|
|
306
|
+
type: "array",
|
|
307
|
+
description: "Optional phase declarations matched by phase() calls.",
|
|
308
|
+
items: {
|
|
309
|
+
type: "object",
|
|
310
|
+
additionalProperties: true,
|
|
311
|
+
properties: {
|
|
312
|
+
title: {
|
|
313
|
+
type: "string",
|
|
314
|
+
required: true,
|
|
315
|
+
description: "The phase title phase() calls match by exact string."
|
|
316
|
+
},
|
|
317
|
+
detail: {
|
|
318
|
+
type: "string",
|
|
319
|
+
description: "Optional one-line description of the phase."
|
|
320
|
+
},
|
|
321
|
+
provider: {
|
|
322
|
+
type: "string",
|
|
323
|
+
description: "Optional provider override this phase is expected to use."
|
|
324
|
+
},
|
|
325
|
+
model: {
|
|
326
|
+
type: "string",
|
|
327
|
+
description: "Optional model override this phase is expected to use."
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
args: {
|
|
335
|
+
type: "object",
|
|
336
|
+
additionalProperties: true,
|
|
337
|
+
description: "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]})."
|
|
338
|
+
},
|
|
339
|
+
...enableRunInBackground ? { run_in_background: {
|
|
340
|
+
type: "boolean",
|
|
341
|
+
description: "Run as a background job: return a job id immediately instead of waiting; the return value arrives with the completion notice."
|
|
342
|
+
} } : {}
|
|
343
|
+
},
|
|
344
|
+
output: {
|
|
345
|
+
schema: { oneOf: [{
|
|
346
|
+
type: "object",
|
|
347
|
+
additionalProperties: false,
|
|
348
|
+
properties: {
|
|
349
|
+
kind: {
|
|
350
|
+
type: "string",
|
|
351
|
+
required: true,
|
|
352
|
+
const: "background"
|
|
353
|
+
},
|
|
354
|
+
jobId: {
|
|
355
|
+
type: "string",
|
|
356
|
+
required: true
|
|
357
|
+
},
|
|
358
|
+
runId: {
|
|
359
|
+
type: "string",
|
|
360
|
+
required: true
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}, {
|
|
364
|
+
type: "object",
|
|
365
|
+
additionalProperties: false,
|
|
366
|
+
properties: {
|
|
367
|
+
kind: {
|
|
368
|
+
type: "string",
|
|
369
|
+
required: true,
|
|
370
|
+
const: "foreground"
|
|
371
|
+
},
|
|
372
|
+
runId: {
|
|
373
|
+
type: "string",
|
|
374
|
+
required: true
|
|
375
|
+
},
|
|
376
|
+
agentsStarted: {
|
|
377
|
+
type: "integer",
|
|
378
|
+
required: true
|
|
379
|
+
},
|
|
380
|
+
result: {
|
|
381
|
+
type: "json",
|
|
382
|
+
required: true
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}] },
|
|
386
|
+
render: (args, value) => [{
|
|
387
|
+
type: "text",
|
|
388
|
+
text: value.kind === "background" ? `workflow "${args.meta.name}" started in the background as job ${value.jobId}. Its return value arrives with the completion notice; check on it with job_output, stop it with job_kill.` : renderResult(args.meta.name, value.agentsStarted, value.result, maxResultChars)
|
|
389
|
+
}]
|
|
390
|
+
},
|
|
391
|
+
async execute(args, exec) {
|
|
392
|
+
const parent = exec.agent;
|
|
393
|
+
if (!parent) throw new Error("workflow tool requires a calling agent (exec.agent was undefined)");
|
|
394
|
+
if (args.run_in_background === true) {
|
|
395
|
+
if (!enableRunInBackground) throw new Error("run_in_background is disabled for this tool");
|
|
396
|
+
return startBackgroundRun(ctx, args, parent, exec.parent === void 0, {
|
|
397
|
+
recorder,
|
|
398
|
+
mirror,
|
|
399
|
+
maxResultChars
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
const run = ctx.workflowEngine.start({
|
|
403
|
+
script: args.script,
|
|
404
|
+
meta: args.meta,
|
|
405
|
+
...args.args !== void 0 ? { args: args.args } : {},
|
|
406
|
+
parent,
|
|
407
|
+
signal: exec.signal
|
|
408
|
+
});
|
|
409
|
+
const recordsRun = exec.parent === void 0;
|
|
410
|
+
if (recordsRun) recorder.start(parent.session, run);
|
|
411
|
+
const onAbort = () => {
|
|
412
|
+
run.cancel("parent step aborted");
|
|
413
|
+
};
|
|
414
|
+
exec.signal.addEventListener("abort", onAbort, { once: true });
|
|
415
|
+
let result;
|
|
416
|
+
try {
|
|
417
|
+
result = await run.result;
|
|
418
|
+
const error = stopReasonError(result);
|
|
419
|
+
if (error !== void 0) throw new Error(error);
|
|
420
|
+
return {
|
|
421
|
+
kind: "foreground",
|
|
422
|
+
runId: run.id,
|
|
423
|
+
agentsStarted: result.agentsStarted,
|
|
424
|
+
result: result.value
|
|
425
|
+
};
|
|
426
|
+
} finally {
|
|
427
|
+
exec.signal.removeEventListener("abort", onAbort);
|
|
428
|
+
try {
|
|
429
|
+
await run.dispose();
|
|
430
|
+
if (recordsRun) {
|
|
431
|
+
/* v8 ignore next -- WorkflowRun.result never rejects by contract, so result is assigned before finally. */
|
|
432
|
+
if (result === void 0) throw new Error("workflow run settled without a result");
|
|
433
|
+
recorder.finish(run.id, result.stopReason);
|
|
434
|
+
}
|
|
435
|
+
} finally {
|
|
436
|
+
if (recordsRun) recorder.abandon(run.id);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
presentCall: (args) => presentWorkflowCall(args),
|
|
441
|
+
presentResult: (args, result) => presentWorkflowResult(args, result)
|
|
442
|
+
}));
|
|
443
|
+
}
|
|
444
|
+
//#endregion
|
|
445
|
+
export { Config, apply, inject, name };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model-facing `workflow` tool: run a JavaScript orchestration script that fans out
|
|
3
|
+
* subagents, and return the script's final value. It owns the model-facing schema and run lifecycle; script
|
|
4
|
+
* parsing, execution, caps, and cancellation live behind `ctx.workflowEngine`
|
|
5
|
+
* (`@qilin-agent/workflow`), so a hardened engine swaps in without touching what the model
|
|
6
|
+
* sees. Foreground execution awaits `run.result` and always disposes the run; non-completed reasons
|
|
7
|
+
* become tool errors. `run_in_background: true` instead registers the run as an owned `ctx.jobs` job
|
|
8
|
+
* and returns its id immediately — the job's output ring streams live progress, and the run's value
|
|
9
|
+
* arrives with the job's completion notice. Presentation is an args-only generic card
|
|
10
|
+
* titled from `meta.name`. Explicit-ask usage guidance is registered as the tool's own prompt
|
|
11
|
+
* section rather than deployment persona prose.
|
|
12
|
+
* @module @qilin-agent/tool-workflow
|
|
13
|
+
*/
|
|
14
|
+
import type { Context } from '@qilin-agent/kylin';
|
|
15
|
+
import z from '@qilin-agent/schemastery';
|
|
16
|
+
declare module '@qilin-agent/jobs' {
|
|
17
|
+
interface JobKindMap {
|
|
18
|
+
workflow: 'workflow';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export declare const name = "tool-workflow";
|
|
22
|
+
export declare const inject: string[];
|
|
23
|
+
/** Config: the model-facing tool name plus result rendering caps. */
|
|
24
|
+
export interface Config {
|
|
25
|
+
/** The model-facing tool name to register (default `workflow`). */
|
|
26
|
+
toolName?: string;
|
|
27
|
+
/** Rendered-result ceiling, in characters: a longer JSON value is truncated with a notice (default 50000). */
|
|
28
|
+
maxResultChars?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Expose `run_in_background` (default true); disabled calls are also
|
|
31
|
+
* rejected. A background run needs a live `ctx.jobs` registry with a
|
|
32
|
+
* controller serving the caller (`@qilin-agent/jobs-local` plus `@qilin-agent/tool-jobs`
|
|
33
|
+
* in the shipped composition); without one the call fails with the missing
|
|
34
|
+
* piece named.
|
|
35
|
+
*/
|
|
36
|
+
enableRunInBackground?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export declare const Config: z<Config>;
|
|
39
|
+
export declare function apply(ctx: Context, config: Config): void;
|
|
40
|
+
//# sourceMappingURL=index.d.ts.map
|