@nanobpm/workflow 0.14.0 → 0.15.1
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/dist/declarative.d.ts +7 -0
- package/dist/declarative.js +20 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/nodes/forEach.js +4 -2
- package/dist/nodes/task.d.ts +27 -0
- package/dist/nodes/task.js +20 -4
- package/package.json +1 -1
- package/src/declarative.ts +19 -0
- package/src/index.ts +1 -1
- package/src/nodes/forEach.ts +4 -2
- package/src/nodes/task.ts +42 -6
package/dist/declarative.d.ts
CHANGED
|
@@ -239,6 +239,13 @@ export declare function walkNodes(nodes: FlowNode[], visit: (n: FlowNode) => voi
|
|
|
239
239
|
* { jobType })`. Deduplicated (preserving first-seen order) since several steps
|
|
240
240
|
* may intentionally share one override token. */
|
|
241
241
|
export declare function externalJobTypes(flow: DeclarativeFlow): string[];
|
|
242
|
+
/** The job types of a flow's inline-handled `run` steps (anywhere in the tree) —
|
|
243
|
+
* exactly the set the in-process `Worker` hosts (same walk, same derived
|
|
244
|
+
* `<flowId>:<stepName>`; a `run` step has no `jobType` override). Codegen
|
|
245
|
+
* consumes this to treat those steps as already-served: a worker-stub
|
|
246
|
+
* scaffolder must never plan a stub for them (nano-ide#595). Deduplicated,
|
|
247
|
+
* preserving first-seen order. */
|
|
248
|
+
export declare function inlineJobTypes(flow: DeclarativeFlow): string[];
|
|
242
249
|
/** A renderable BPMN element the emitter has placed. A kind's `emit` handler
|
|
243
250
|
* adds one (or more) via `api.addNode(...)` (or a typed helper like
|
|
244
251
|
* `api.addServiceTask`). */
|
package/dist/declarative.js
CHANGED
|
@@ -319,6 +319,26 @@ export function externalJobTypes(flow) {
|
|
|
319
319
|
});
|
|
320
320
|
return types;
|
|
321
321
|
}
|
|
322
|
+
/** The job types of a flow's inline-handled `run` steps (anywhere in the tree) —
|
|
323
|
+
* exactly the set the in-process `Worker` hosts (same walk, same derived
|
|
324
|
+
* `<flowId>:<stepName>`; a `run` step has no `jobType` override). Codegen
|
|
325
|
+
* consumes this to treat those steps as already-served: a worker-stub
|
|
326
|
+
* scaffolder must never plan a stub for them (nano-ide#595). Deduplicated,
|
|
327
|
+
* preserving first-seen order. */
|
|
328
|
+
export function inlineJobTypes(flow) {
|
|
329
|
+
const seen = new Set();
|
|
330
|
+
const types = [];
|
|
331
|
+
walkNodes(flow.steps, (n) => {
|
|
332
|
+
if (n.kind !== "run")
|
|
333
|
+
return;
|
|
334
|
+
const type = jobType(flow.id, n.name);
|
|
335
|
+
if (seen.has(type))
|
|
336
|
+
return;
|
|
337
|
+
seen.add(type);
|
|
338
|
+
types.push(type);
|
|
339
|
+
});
|
|
340
|
+
return types;
|
|
341
|
+
}
|
|
322
342
|
function requireTimerAt(node) {
|
|
323
343
|
if (node.at === undefined)
|
|
324
344
|
throw new Error(`timer event is missing its { at } value`);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { defineWorkflow, imperativeToBpmn, replayOnce } from "./imperative.js";
|
|
2
2
|
export type { Journal, ReplayStep } from "./imperative.js";
|
|
3
|
-
export { defineFlow, declarativeToBpmn, externalJobTypes, walkNodes } from "./declarative.js";
|
|
3
|
+
export { defineFlow, declarativeToBpmn, externalJobTypes, inlineJobTypes, walkNodes } from "./declarative.js";
|
|
4
4
|
export type { FlowBuilder } from "./declarative.js";
|
|
5
5
|
export type { BoundaryOptions } from "./nodes/boundary.js";
|
|
6
6
|
export type { HumanOptions, HumanIoMapping, HumanIoEntry } from "./nodes/human.js";
|
package/dist/index.js
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
// code-first surface (steps are not engine-visible; requires determinism
|
|
40
40
|
// discipline). Retained for advanced durable-orchestration use only.
|
|
41
41
|
export { defineWorkflow, imperativeToBpmn, replayOnce } from "./imperative.js";
|
|
42
|
-
export { defineFlow, declarativeToBpmn, externalJobTypes, walkNodes } from "./declarative.js";
|
|
42
|
+
export { defineFlow, declarativeToBpmn, externalJobTypes, inlineJobTypes, walkNodes } from "./declarative.js";
|
|
43
43
|
export { layoutBpmn, declarativeToLayoutedBpmn } from "./layout.js";
|
|
44
44
|
export { envelope } from "./envelope.js";
|
|
45
45
|
export { WorkflowClient, WorkflowError, toBpmn, toDeployableBpmn } from "./client.js";
|
package/dist/nodes/forEach.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// wrapped in an embedded MI sub-process. Contributed through the extension seam.
|
|
5
5
|
import { assertIdent, escapeXml } from "../xml.js";
|
|
6
6
|
import { registerNodeKind } from "./registry.js";
|
|
7
|
+
import { hasTaskExtensions } from "./task.js";
|
|
7
8
|
/** The `<bpmn:multiInstanceLoopCharacteristics>` block for a `forEach` node — the
|
|
8
9
|
* same shape whether lifted onto a leaf service task or a sub-process. */
|
|
9
10
|
function miCharacteristics(node) {
|
|
@@ -82,13 +83,14 @@ registerNodeKind("forEach", {
|
|
|
82
83
|
const only = node.body.length === 1 ? node.body[0] : undefined;
|
|
83
84
|
// A single service-task body carries the MI characteristics directly; a
|
|
84
85
|
// multi-step body is wrapped in an embedded MI sub-process (its own token
|
|
85
|
-
// scope, with its own start/end). A
|
|
86
|
+
// scope, with its own start/end). A `task` with an extension delta (agent
|
|
87
|
+
// marker, prompt binding or io mapping — see `hasTaskExtensions`) is deliberately
|
|
86
88
|
// EXCLUDED from this fast path: computing its `zeebe:linkedResources`/
|
|
87
89
|
// `appendPrompt` delta lives in the `task` kind's own emitter, so lifting it
|
|
88
90
|
// here would silently drop that binding. It falls through to the sub-process
|
|
89
91
|
// path, where the `task` emitter renders the agent service task (prompt
|
|
90
92
|
// intact) inside the MI scope.
|
|
91
|
-
if (only && (only.kind === "run" || (only.kind === "task" && only
|
|
93
|
+
if (only && (only.kind === "run" || (only.kind === "task" && !hasTaskExtensions(only)))) {
|
|
92
94
|
api.addServiceTask(only, { mi });
|
|
93
95
|
api.connect(incoming, only.name);
|
|
94
96
|
return [api.newEdge(only.name)];
|
package/dist/nodes/task.d.ts
CHANGED
|
@@ -27,6 +27,11 @@ declare module "../types.js" {
|
|
|
27
27
|
jobType?: string;
|
|
28
28
|
/** The agent prompt binding, when the task is an LLM-agent service task. */
|
|
29
29
|
prompt?: PromptBinding;
|
|
30
|
+
/** Whether the task is an AGENT service task, emitting the engine-native
|
|
31
|
+
* AgentTask marker `<zeebe:agentDefinition agentType="external" />`
|
|
32
|
+
* (issue #597). Resolved at authoring time: an explicit `agent` option
|
|
33
|
+
* wins, otherwise it is `true` exactly when a `prompt` is bound. */
|
|
34
|
+
agent?: boolean;
|
|
30
35
|
/** An explicit `<zeebe:ioMapping>` on the service task — arbitrary input
|
|
31
36
|
* (and output) variable mappings, using the shared ioMapping shape. When a
|
|
32
37
|
* `prompt.append` is ALSO present it is folded into this single mapping as
|
|
@@ -49,7 +54,29 @@ declare module "../declarative.js" {
|
|
|
49
54
|
task<K extends string>(name: K, opts: {
|
|
50
55
|
jobType?: string;
|
|
51
56
|
prompt: PromptBinding;
|
|
57
|
+
agent?: boolean;
|
|
58
|
+
io?: HumanIoMapping;
|
|
59
|
+
}): FlowBuilder<C>;
|
|
60
|
+
/**
|
|
61
|
+
* A PROMPT-LESS agent external service task: as `task`, but `agent: true`
|
|
62
|
+
* emits the AgentTask marker `<zeebe:agentDefinition agentType="external" />`
|
|
63
|
+
* (the nano-workforce agent-task signal, issue #597) without binding a
|
|
64
|
+
* prompt resource — the worker is steered by its inputs alone.
|
|
65
|
+
*/
|
|
66
|
+
task<K extends string>(name: K, opts: {
|
|
67
|
+
jobType?: string;
|
|
68
|
+
agent: boolean;
|
|
52
69
|
io?: HumanIoMapping;
|
|
53
70
|
}): FlowBuilder<C>;
|
|
54
71
|
}
|
|
55
72
|
}
|
|
73
|
+
/** Whether a `task` node carries an extension delta (agent marker, prompt
|
|
74
|
+
* binding, or io mapping) that only this module's emitter renders. The SINGLE
|
|
75
|
+
* predicate any other emitter (e.g. `forEach`'s single-task fast path) must
|
|
76
|
+
* consult before rendering the task shell itself, so a new delta can never be
|
|
77
|
+
* silently dropped by a bypass. */
|
|
78
|
+
export declare function hasTaskExtensions(node: {
|
|
79
|
+
agent?: boolean;
|
|
80
|
+
prompt?: PromptBinding;
|
|
81
|
+
io?: HumanIoMapping;
|
|
82
|
+
}): boolean;
|
package/dist/nodes/task.js
CHANGED
|
@@ -57,7 +57,10 @@ function resolvePrompt(name, prompt) {
|
|
|
57
57
|
* `<zeebe:ioMapping>` (never two). Element order matches the golden:
|
|
58
58
|
* `linkedResources` (when any) then `ioMapping` (when any), the latter after
|
|
59
59
|
* `taskDefinition`/`properties`. */
|
|
60
|
-
function taskExtensions(prompt, io) {
|
|
60
|
+
function taskExtensions(agent, prompt, io) {
|
|
61
|
+
// The AgentTask marker leads the delta so it sits directly after the
|
|
62
|
+
// taskDefinition, matching the nano-workforce reference shape.
|
|
63
|
+
const marker = agent === true ? ` <zeebe:agentDefinition agentType="external" />\n` : "";
|
|
61
64
|
const linked = prompt !== undefined
|
|
62
65
|
? ` <zeebe:linkedResources>\n` +
|
|
63
66
|
` <zeebe:linkedResource resourceId="${escapeXml(prompt.resourceId)}" ` +
|
|
@@ -73,7 +76,15 @@ function taskExtensions(prompt, io) {
|
|
|
73
76
|
if (prompt?.append !== undefined)
|
|
74
77
|
inputs.push({ source: prompt.append, target: "appendPrompt" });
|
|
75
78
|
const merged = { input: inputs, output: io?.output ?? [] };
|
|
76
|
-
return linked + renderIoMapping(merged);
|
|
79
|
+
return marker + linked + renderIoMapping(merged);
|
|
80
|
+
}
|
|
81
|
+
/** Whether a `task` node carries an extension delta (agent marker, prompt
|
|
82
|
+
* binding, or io mapping) that only this module's emitter renders. The SINGLE
|
|
83
|
+
* predicate any other emitter (e.g. `forEach`'s single-task fast path) must
|
|
84
|
+
* consult before rendering the task shell itself, so a new delta can never be
|
|
85
|
+
* silently dropped by a bypass. */
|
|
86
|
+
export function hasTaskExtensions(node) {
|
|
87
|
+
return node.agent === true || node.prompt !== undefined || node.io !== undefined;
|
|
77
88
|
}
|
|
78
89
|
registerNodeKind("task", {
|
|
79
90
|
build: (api) => (name, opts) => {
|
|
@@ -83,12 +94,17 @@ registerNodeKind("task", {
|
|
|
83
94
|
assertJobType("task jobType", override);
|
|
84
95
|
const prompt = opts?.prompt !== undefined ? resolvePrompt(name, opts.prompt) : undefined;
|
|
85
96
|
assertIoMapping(`task("${name}")`, opts?.io);
|
|
97
|
+
if (opts?.agent !== undefined && typeof opts.agent !== "boolean") {
|
|
98
|
+
throw new Error(`task("${name}") agent must be a boolean when provided`);
|
|
99
|
+
}
|
|
100
|
+
const agent = opts?.agent ?? prompt !== undefined;
|
|
86
101
|
api.out.push({
|
|
87
102
|
kind: "task",
|
|
88
103
|
name,
|
|
89
104
|
envelopes: api.contractEnvelopes(name),
|
|
90
105
|
jobType: override,
|
|
91
106
|
prompt,
|
|
107
|
+
agent,
|
|
92
108
|
io: opts?.io,
|
|
93
109
|
});
|
|
94
110
|
return api.self();
|
|
@@ -97,7 +113,7 @@ registerNodeKind("task", {
|
|
|
97
113
|
// Neither a prompt binding nor an explicit io mapping → the standard
|
|
98
114
|
// service-task emission is preserved unchanged (no `linkedResources`, no
|
|
99
115
|
// `ioMapping`).
|
|
100
|
-
if (node
|
|
116
|
+
if (!hasTaskExtensions(node)) {
|
|
101
117
|
api.addServiceTask(node);
|
|
102
118
|
api.connect(incoming, node.name);
|
|
103
119
|
return [api.newEdge(node.name)];
|
|
@@ -107,7 +123,7 @@ registerNodeKind("task", {
|
|
|
107
123
|
// properties, recording envelopes); we only supply the `linkedResource`
|
|
108
124
|
// and/or `ioMapping` delta, so the envelope/taskDefinition serialization has
|
|
109
125
|
// a single source.
|
|
110
|
-
api.addServiceTask(node, { extraExt: taskExtensions(node.prompt, node.io) });
|
|
126
|
+
api.addServiceTask(node, { extraExt: taskExtensions(node.agent, node.prompt, node.io) });
|
|
111
127
|
api.connect(incoming, node.name);
|
|
112
128
|
return [api.newEdge(node.name)];
|
|
113
129
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/workflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "Code-first durable orchestration for nanobpmn (ADR 0044/0045). Author a durable workflow as a declarative flow — `defineFlow(id, w => { w.run(...); w.signal(...); w.task(...) })` — and the SDK derives the executable BPMN model, the job types, and the message/correlation wiring, then hosts a generic worker. `w.run` is a local step, `w.signal` is a durable human-in-the-loop wait, `w.task` is a step serviced by an external worker. `defineFlow` is the single code-first surface; the imperative `defineWorkflow` replay function is experimental/internal, not a recommended API. No diagram, no task-type wiring, no correlation plumbing authored by hand — and you can optionally auto-generate a diagram (DI) for the derived model with `declarativeToLayoutedBpmn` via the optional `bpmn-auto-layout` peer dependency. Talks to a running nanobpmn gateway through @nanobpm/nano-sdk (the shared engine-transport spine — ADR 0055), which negotiates the Falcon protocol on a Nano server and REST elsewhere.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
package/src/declarative.ts
CHANGED
|
@@ -562,6 +562,25 @@ export function externalJobTypes(flow: DeclarativeFlow): string[] {
|
|
|
562
562
|
return types;
|
|
563
563
|
}
|
|
564
564
|
|
|
565
|
+
/** The job types of a flow's inline-handled `run` steps (anywhere in the tree) —
|
|
566
|
+
* exactly the set the in-process `Worker` hosts (same walk, same derived
|
|
567
|
+
* `<flowId>:<stepName>`; a `run` step has no `jobType` override). Codegen
|
|
568
|
+
* consumes this to treat those steps as already-served: a worker-stub
|
|
569
|
+
* scaffolder must never plan a stub for them (nano-ide#595). Deduplicated,
|
|
570
|
+
* preserving first-seen order. */
|
|
571
|
+
export function inlineJobTypes(flow: DeclarativeFlow): string[] {
|
|
572
|
+
const seen = new Set<string>();
|
|
573
|
+
const types: string[] = [];
|
|
574
|
+
walkNodes(flow.steps, (n) => {
|
|
575
|
+
if (n.kind !== "run") return;
|
|
576
|
+
const type = jobType(flow.id, n.name);
|
|
577
|
+
if (seen.has(type)) return;
|
|
578
|
+
seen.add(type);
|
|
579
|
+
types.push(type);
|
|
580
|
+
});
|
|
581
|
+
return types;
|
|
582
|
+
}
|
|
583
|
+
|
|
565
584
|
function requireTimerAt(node: { at?: string }): string {
|
|
566
585
|
if (node.at === undefined) throw new Error(`timer event is missing its { at } value`);
|
|
567
586
|
return node.at;
|
package/src/index.ts
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
// discipline). Retained for advanced durable-orchestration use only.
|
|
42
42
|
export { defineWorkflow, imperativeToBpmn, replayOnce } from "./imperative.js";
|
|
43
43
|
export type { Journal, ReplayStep } from "./imperative.js";
|
|
44
|
-
export { defineFlow, declarativeToBpmn, externalJobTypes, walkNodes } from "./declarative.js";
|
|
44
|
+
export { defineFlow, declarativeToBpmn, externalJobTypes, inlineJobTypes, walkNodes } from "./declarative.js";
|
|
45
45
|
export type { FlowBuilder } from "./declarative.js";
|
|
46
46
|
// The `.boundary(...)` option type (its builder method augments FlowBuilder from
|
|
47
47
|
// the boundary kind module — S2/#317).
|
package/src/nodes/forEach.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { FlowNode } from "../types.js";
|
|
|
7
7
|
import type { Block } from "../declarative.js";
|
|
8
8
|
import { assertIdent, escapeXml } from "../xml.js";
|
|
9
9
|
import { registerNodeKind } from "./registry.js";
|
|
10
|
+
import { hasTaskExtensions } from "./task.js";
|
|
10
11
|
|
|
11
12
|
/** The `forEach` node shape (its `FlowNode` variant). */
|
|
12
13
|
interface ForEachNode {
|
|
@@ -115,13 +116,14 @@ registerNodeKind("forEach", {
|
|
|
115
116
|
const only = node.body.length === 1 ? node.body[0] : undefined;
|
|
116
117
|
// A single service-task body carries the MI characteristics directly; a
|
|
117
118
|
// multi-step body is wrapped in an embedded MI sub-process (its own token
|
|
118
|
-
// scope, with its own start/end). A
|
|
119
|
+
// scope, with its own start/end). A `task` with an extension delta (agent
|
|
120
|
+
// marker, prompt binding or io mapping — see `hasTaskExtensions`) is deliberately
|
|
119
121
|
// EXCLUDED from this fast path: computing its `zeebe:linkedResources`/
|
|
120
122
|
// `appendPrompt` delta lives in the `task` kind's own emitter, so lifting it
|
|
121
123
|
// here would silently drop that binding. It falls through to the sub-process
|
|
122
124
|
// path, where the `task` emitter renders the agent service task (prompt
|
|
123
125
|
// intact) inside the MI scope.
|
|
124
|
-
if (only && (only.kind === "run" || (only.kind === "task" && only
|
|
126
|
+
if (only && (only.kind === "run" || (only.kind === "task" && !hasTaskExtensions(only)))) {
|
|
125
127
|
api.addServiceTask(only, { mi });
|
|
126
128
|
api.connect(incoming, only.name);
|
|
127
129
|
return [api.newEdge(only.name)];
|
package/src/nodes/task.ts
CHANGED
|
@@ -46,6 +46,11 @@ declare module "../types.js" {
|
|
|
46
46
|
jobType?: string;
|
|
47
47
|
/** The agent prompt binding, when the task is an LLM-agent service task. */
|
|
48
48
|
prompt?: PromptBinding;
|
|
49
|
+
/** Whether the task is an AGENT service task, emitting the engine-native
|
|
50
|
+
* AgentTask marker `<zeebe:agentDefinition agentType="external" />`
|
|
51
|
+
* (issue #597). Resolved at authoring time: an explicit `agent` option
|
|
52
|
+
* wins, otherwise it is `true` exactly when a `prompt` is bound. */
|
|
53
|
+
agent?: boolean;
|
|
49
54
|
/** An explicit `<zeebe:ioMapping>` on the service task — arbitrary input
|
|
50
55
|
* (and output) variable mappings, using the shared ioMapping shape. When a
|
|
51
56
|
* `prompt.append` is ALSO present it is folded into this single mapping as
|
|
@@ -68,7 +73,17 @@ declare module "../declarative.js" {
|
|
|
68
73
|
*/
|
|
69
74
|
task<K extends string>(
|
|
70
75
|
name: K,
|
|
71
|
-
opts: { jobType?: string; prompt: PromptBinding; io?: HumanIoMapping },
|
|
76
|
+
opts: { jobType?: string; prompt: PromptBinding; agent?: boolean; io?: HumanIoMapping },
|
|
77
|
+
): FlowBuilder<C>;
|
|
78
|
+
/**
|
|
79
|
+
* A PROMPT-LESS agent external service task: as `task`, but `agent: true`
|
|
80
|
+
* emits the AgentTask marker `<zeebe:agentDefinition agentType="external" />`
|
|
81
|
+
* (the nano-workforce agent-task signal, issue #597) without binding a
|
|
82
|
+
* prompt resource — the worker is steered by its inputs alone.
|
|
83
|
+
*/
|
|
84
|
+
task<K extends string>(
|
|
85
|
+
name: K,
|
|
86
|
+
opts: { jobType?: string; agent: boolean; io?: HumanIoMapping },
|
|
72
87
|
): FlowBuilder<C>;
|
|
73
88
|
}
|
|
74
89
|
}
|
|
@@ -118,7 +133,14 @@ function resolvePrompt(name: string, prompt: PromptBinding): PromptBinding {
|
|
|
118
133
|
* `<zeebe:ioMapping>` (never two). Element order matches the golden:
|
|
119
134
|
* `linkedResources` (when any) then `ioMapping` (when any), the latter after
|
|
120
135
|
* `taskDefinition`/`properties`. */
|
|
121
|
-
function taskExtensions(
|
|
136
|
+
function taskExtensions(
|
|
137
|
+
agent: boolean | undefined,
|
|
138
|
+
prompt: PromptBinding | undefined,
|
|
139
|
+
io: HumanIoMapping | undefined,
|
|
140
|
+
): string {
|
|
141
|
+
// The AgentTask marker leads the delta so it sits directly after the
|
|
142
|
+
// taskDefinition, matching the nano-workforce reference shape.
|
|
143
|
+
const marker = agent === true ? ` <zeebe:agentDefinition agentType="external" />\n` : "";
|
|
122
144
|
const linked =
|
|
123
145
|
prompt !== undefined
|
|
124
146
|
? ` <zeebe:linkedResources>\n` +
|
|
@@ -134,22 +156,36 @@ function taskExtensions(prompt: PromptBinding | undefined, io: HumanIoMapping |
|
|
|
134
156
|
const inputs: HumanIoEntry[] = [...(io?.input ?? [])];
|
|
135
157
|
if (prompt?.append !== undefined) inputs.push({ source: prompt.append, target: "appendPrompt" });
|
|
136
158
|
const merged: HumanIoMapping = { input: inputs, output: io?.output ?? [] };
|
|
137
|
-
return linked + renderIoMapping(merged);
|
|
159
|
+
return marker + linked + renderIoMapping(merged);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Whether a `task` node carries an extension delta (agent marker, prompt
|
|
163
|
+
* binding, or io mapping) that only this module's emitter renders. The SINGLE
|
|
164
|
+
* predicate any other emitter (e.g. `forEach`'s single-task fast path) must
|
|
165
|
+
* consult before rendering the task shell itself, so a new delta can never be
|
|
166
|
+
* silently dropped by a bypass. */
|
|
167
|
+
export function hasTaskExtensions(node: { agent?: boolean; prompt?: PromptBinding; io?: HumanIoMapping }): boolean {
|
|
168
|
+
return node.agent === true || node.prompt !== undefined || node.io !== undefined;
|
|
138
169
|
}
|
|
139
170
|
|
|
140
171
|
registerNodeKind("task", {
|
|
141
|
-
build: (api) => (name: string, opts?: { jobType?: string; prompt?: PromptBinding; io?: HumanIoMapping }) => {
|
|
172
|
+
build: (api) => (name: string, opts?: { jobType?: string; prompt?: PromptBinding; agent?: boolean; io?: HumanIoMapping }) => {
|
|
142
173
|
api.claim(name);
|
|
143
174
|
const override = opts?.jobType;
|
|
144
175
|
if (override !== undefined) assertJobType("task jobType", override);
|
|
145
176
|
const prompt = opts?.prompt !== undefined ? resolvePrompt(name, opts.prompt) : undefined;
|
|
146
177
|
assertIoMapping(`task("${name}")`, opts?.io);
|
|
178
|
+
if (opts?.agent !== undefined && typeof opts.agent !== "boolean") {
|
|
179
|
+
throw new Error(`task("${name}") agent must be a boolean when provided`);
|
|
180
|
+
}
|
|
181
|
+
const agent = opts?.agent ?? prompt !== undefined;
|
|
147
182
|
api.out.push({
|
|
148
183
|
kind: "task",
|
|
149
184
|
name,
|
|
150
185
|
envelopes: api.contractEnvelopes(name),
|
|
151
186
|
jobType: override,
|
|
152
187
|
prompt,
|
|
188
|
+
agent,
|
|
153
189
|
io: opts?.io,
|
|
154
190
|
});
|
|
155
191
|
return api.self();
|
|
@@ -158,7 +194,7 @@ registerNodeKind("task", {
|
|
|
158
194
|
// Neither a prompt binding nor an explicit io mapping → the standard
|
|
159
195
|
// service-task emission is preserved unchanged (no `linkedResources`, no
|
|
160
196
|
// `ioMapping`).
|
|
161
|
-
if (node
|
|
197
|
+
if (!hasTaskExtensions(node)) {
|
|
162
198
|
api.addServiceTask(node);
|
|
163
199
|
api.connect(incoming, node.name);
|
|
164
200
|
return [api.newEdge(node.name)];
|
|
@@ -168,7 +204,7 @@ registerNodeKind("task", {
|
|
|
168
204
|
// properties, recording envelopes); we only supply the `linkedResource`
|
|
169
205
|
// and/or `ioMapping` delta, so the envelope/taskDefinition serialization has
|
|
170
206
|
// a single source.
|
|
171
|
-
api.addServiceTask(node, { extraExt: taskExtensions(node.prompt, node.io) });
|
|
207
|
+
api.addServiceTask(node, { extraExt: taskExtensions(node.agent, node.prompt, node.io) });
|
|
172
208
|
api.connect(incoming, node.name);
|
|
173
209
|
return [api.newEdge(node.name)];
|
|
174
210
|
},
|