@nanobpm/workflow 0.9.0 → 0.11.0
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 +80 -2
- package/dist/declarative.d.ts +8 -2
- package/dist/declarative.js +5 -2
- package/dist/index.d.ts +1 -0
- package/dist/nodes/boundary.d.ts +46 -0
- package/dist/nodes/boundary.js +92 -0
- package/dist/nodes/forEach.js +8 -3
- package/dist/nodes/index.d.ts +2 -0
- package/dist/nodes/index.js +2 -0
- package/dist/nodes/race.d.ts +54 -0
- package/dist/nodes/race.js +134 -0
- package/dist/nodes/signal.js +8 -3
- package/dist/nodes/task.d.ts +37 -1
- package/dist/nodes/task.js +79 -3
- package/package.json +1 -1
- package/src/declarative.ts +16 -4
- package/src/index.ts +3 -0
- package/src/nodes/boundary.ts +149 -0
- package/src/nodes/forEach.ts +8 -3
- package/src/nodes/index.ts +2 -0
- package/src/nodes/race.ts +185 -0
- package/src/nodes/signal.ts +9 -3
- package/src/nodes/task.ts +126 -5
package/src/nodes/task.ts
CHANGED
|
@@ -2,27 +2,148 @@
|
|
|
2
2
|
// OUTSIDE this program (no locally-hosted handler). Its job type defaults to the
|
|
3
3
|
// derived `${flowId}:${name}` unless overridden with `{ jobType }`. Contributed
|
|
4
4
|
// from this module through the extension seam (epic #314, S0/#315).
|
|
5
|
+
//
|
|
6
|
+
// This module also owns the AGENT PROMPT BINDING (epic #314, S4/#319): an
|
|
7
|
+
// optional `{ prompt }` option binds an LLM prompt resource to the worker via a
|
|
8
|
+
// `<zeebe:linkedResource … resourceType="GenericScript" linkName="prompt">`,
|
|
9
|
+
// mirroring the nano-workforce agent service tasks. The extension is contributed
|
|
10
|
+
// entirely from this module — the `task` node's own registered emit handler and
|
|
11
|
+
// its option type — WITHOUT re-opening the shared `FlowNode` union, the central
|
|
12
|
+
// `walkNodes`/`emitNode` dispatch, or the generated barrel.
|
|
5
13
|
|
|
6
14
|
import type { NodeEnvelopes } from "../types.js";
|
|
7
|
-
import { assertJobType } from "../xml.js";
|
|
15
|
+
import { assertJobType, escapeXml } from "../xml.js";
|
|
8
16
|
import { registerNodeKind } from "./registry.js";
|
|
9
17
|
|
|
18
|
+
/** Binds an LLM prompt resource to an agent service task. Emits, alongside the
|
|
19
|
+
* task's `zeebe:taskDefinition` capability token, a
|
|
20
|
+
* `<zeebe:linkedResources><zeebe:linkedResource resourceId=… bindingType=…
|
|
21
|
+
* resourceType="GenericScript" linkName="prompt"/></zeebe:linkedResources>`
|
|
22
|
+
* extension — the shape the nano-workforce agent tasks use to attach a prompt
|
|
23
|
+
* script to the worker that services the job. */
|
|
24
|
+
export interface PromptBinding {
|
|
25
|
+
/** The `GenericScript` resource id bound as the prompt (the linkedResource
|
|
26
|
+
* `resourceId`, e.g. `"retro.md"`). Required and non-empty. */
|
|
27
|
+
resourceId: string;
|
|
28
|
+
/** The linkedResource `bindingType` — how the engine resolves the resource
|
|
29
|
+
* version at deploy time. Defaults to `"latest"`. */
|
|
30
|
+
bindingType?: string;
|
|
31
|
+
/** Optional prompt addendum: a FEEL expression (or literal) fed to the worker
|
|
32
|
+
* through a `<zeebe:ioMapping><zeebe:input source=… target="appendPrompt"/>`
|
|
33
|
+
* input, the convention the agent tasks use to append runtime context to the
|
|
34
|
+
* bound prompt. Omitted → no `ioMapping` is emitted. */
|
|
35
|
+
append?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
10
38
|
declare module "../types.js" {
|
|
11
39
|
interface FlowNodeRegistry {
|
|
12
|
-
task: {
|
|
40
|
+
task: {
|
|
41
|
+
kind: "task";
|
|
42
|
+
name: string;
|
|
43
|
+
envelopes?: NodeEnvelopes;
|
|
44
|
+
jobType?: string;
|
|
45
|
+
/** The agent prompt binding, when the task is an LLM-agent service task. */
|
|
46
|
+
prompt?: PromptBinding;
|
|
47
|
+
};
|
|
13
48
|
}
|
|
14
49
|
}
|
|
15
50
|
|
|
51
|
+
declare module "../declarative.js" {
|
|
52
|
+
interface FlowBuilder<C extends object> {
|
|
53
|
+
/**
|
|
54
|
+
* An AGENT external service task: as `task`, but additionally binds an LLM
|
|
55
|
+
* `prompt` resource to the worker via a `zeebe:linkedResource`
|
|
56
|
+
* (`resourceType="GenericScript" linkName="prompt"`). Pair it with a
|
|
57
|
+
* `{ jobType }` capability token (e.g. `senior:retro`) so an agent pool
|
|
58
|
+
* services it. `prompt.append` (optional) feeds a FEEL expression to the
|
|
59
|
+
* worker through a `zeebe:ioMapping` `appendPrompt` input.
|
|
60
|
+
*/
|
|
61
|
+
task<K extends string>(name: K, opts: { jobType?: string; prompt: PromptBinding }): FlowBuilder<C>;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Normalize + validate a prompt binding at authoring time, applying the
|
|
66
|
+
* `bindingType` default. Emitted string fields are trimmed before storing (in
|
|
67
|
+
* parity with how `timer` normalizes `{ after }`/`{ at }`), so a value like
|
|
68
|
+
* `resourceId: " retro.md "` passes validation AND is emitted without stray
|
|
69
|
+
* surrounding whitespace. Returns a fully-resolved binding stored on the node. */
|
|
70
|
+
function resolvePrompt(name: string, prompt: PromptBinding): PromptBinding {
|
|
71
|
+
if (typeof prompt !== "object" || prompt === null) {
|
|
72
|
+
throw new Error(`task("${name}") prompt must be an object with a non-empty resourceId`);
|
|
73
|
+
}
|
|
74
|
+
const resourceId = prompt.resourceId;
|
|
75
|
+
if (typeof resourceId !== "string" || resourceId.trim() === "") {
|
|
76
|
+
throw new Error(`task("${name}") prompt.resourceId must be a non-empty string`);
|
|
77
|
+
}
|
|
78
|
+
// Only an ABSENT bindingType defaults to "latest"; `??` would also swallow an
|
|
79
|
+
// explicit `null`, masking an authoring mistake. Distinguish `undefined` (use
|
|
80
|
+
// the default) from any other non-string (reject), in parity with how `append`
|
|
81
|
+
// below treats `!== undefined` as "provided, so validate strictly".
|
|
82
|
+
const bindingType = prompt.bindingType === undefined ? "latest" : prompt.bindingType;
|
|
83
|
+
if (typeof bindingType !== "string" || bindingType.trim() === "") {
|
|
84
|
+
throw new Error(`task("${name}") prompt.bindingType must be a non-empty string`);
|
|
85
|
+
}
|
|
86
|
+
const resolved: PromptBinding = { resourceId: resourceId.trim(), bindingType: bindingType.trim() };
|
|
87
|
+
if (prompt.append !== undefined) {
|
|
88
|
+
if (typeof prompt.append !== "string" || prompt.append.trim() === "") {
|
|
89
|
+
throw new Error(`task("${name}") prompt.append must be a non-empty string when provided`);
|
|
90
|
+
}
|
|
91
|
+
resolved.append = prompt.append.trim();
|
|
92
|
+
}
|
|
93
|
+
return resolved;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Build the prompt-specific `<bpmn:extensionElements>` delta appended to the
|
|
97
|
+
* shared service-task emission: the `zeebe:linkedResources` prompt binding and
|
|
98
|
+
* (optionally) a `zeebe:ioMapping` `appendPrompt` input. The task shell
|
|
99
|
+
* (taskDefinition + envelope properties) is rendered by the built-in
|
|
100
|
+
* `EmitApi.addServiceTask`, so this variant only contributes its delta and the
|
|
101
|
+
* envelope serialization can never drift from the plain service task. */
|
|
102
|
+
function promptExtensions(prompt: PromptBinding): string {
|
|
103
|
+
const { resourceId, bindingType, append } = prompt;
|
|
104
|
+
const linked =
|
|
105
|
+
` <zeebe:linkedResources>\n` +
|
|
106
|
+
` <zeebe:linkedResource resourceId="${escapeXml(resourceId)}" ` +
|
|
107
|
+
`bindingType="${escapeXml(bindingType ?? "latest")}" resourceType="GenericScript" linkName="prompt" />\n` +
|
|
108
|
+
` </zeebe:linkedResources>\n`;
|
|
109
|
+
const io =
|
|
110
|
+
append !== undefined
|
|
111
|
+
? ` <zeebe:ioMapping>\n` +
|
|
112
|
+
` <zeebe:input source="${escapeXml(append)}" target="appendPrompt" />\n` +
|
|
113
|
+
` </zeebe:ioMapping>\n`
|
|
114
|
+
: "";
|
|
115
|
+
return linked + io;
|
|
116
|
+
}
|
|
117
|
+
|
|
16
118
|
registerNodeKind("task", {
|
|
17
|
-
build: (api) => (name: string, opts?: { jobType?: string }) => {
|
|
119
|
+
build: (api) => (name: string, opts?: { jobType?: string; prompt?: PromptBinding }) => {
|
|
18
120
|
api.claim(name);
|
|
19
121
|
const override = opts?.jobType;
|
|
20
122
|
if (override !== undefined) assertJobType("task jobType", override);
|
|
21
|
-
|
|
123
|
+
const prompt = opts?.prompt !== undefined ? resolvePrompt(name, opts.prompt) : undefined;
|
|
124
|
+
api.out.push({
|
|
125
|
+
kind: "task",
|
|
126
|
+
name,
|
|
127
|
+
envelopes: api.contractEnvelopes(name),
|
|
128
|
+
jobType: override,
|
|
129
|
+
prompt,
|
|
130
|
+
});
|
|
22
131
|
return api.self();
|
|
23
132
|
},
|
|
24
133
|
emit: (node, incoming, _loop, api) => {
|
|
25
|
-
|
|
134
|
+
// No prompt → the standard service-task emission is preserved unchanged (no
|
|
135
|
+
// `linkedResources` are emitted).
|
|
136
|
+
if (node.prompt === undefined) {
|
|
137
|
+
api.addServiceTask(node);
|
|
138
|
+
api.connect(incoming, node.name);
|
|
139
|
+
return [api.newEdge(node.name)];
|
|
140
|
+
}
|
|
141
|
+
// Prompt binding present → the shared `addServiceTask` renders the task
|
|
142
|
+
// shell (taskDefinition + envelope properties, recording envelopes); we only
|
|
143
|
+
// supply the `zeebe:linkedResource` (and optional `appendPrompt` ioMapping)
|
|
144
|
+
// delta, so the envelope/taskDefinition serialization has a single source.
|
|
145
|
+
const prompt = node.prompt;
|
|
146
|
+
api.addServiceTask(node, { extraExt: promptExtensions(prompt) });
|
|
26
147
|
api.connect(incoming, node.name);
|
|
27
148
|
return [api.newEdge(node.name)];
|
|
28
149
|
},
|