@nanobpm/bojtos-kit 0.1.0 → 0.3.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/dist/index.d.ts +2 -2
- package/dist/session.d.ts +68 -1
- package/dist/session.js +37 -0
- package/dist/types.d.ts +103 -0
- package/dist/worker.d.ts +29 -1
- package/dist/worker.js +57 -4
- package/package.json +3 -3
- package/src/index.ts +3 -0
- package/src/session.ts +175 -1
- package/src/types.ts +112 -0
- package/src/worker.ts +89 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { ensureWasm, createBojtosSession, type BojtosSession, type WasmSource, } from "./session.js";
|
|
2
|
-
export { dispatchWorkers, dispatchRound, JobFailure, type JobHandler, type JobResult, type DispatchOptions, type DispatchResult, type RoundResult, } from "./worker.js";
|
|
3
|
-
export type { Snapshot, InstanceDto, JobDto, ActivatedJob, IncidentDto, TimerDto, ActiveEl, WasmEvent, } from "./types.js";
|
|
2
|
+
export { dispatchWorkers, dispatchRound, JobFailure, type JobHandler, type JobResult, type AgentHandler, type DispatchOptions, type DispatchResult, type RoundResult, } from "./worker.js";
|
|
3
|
+
export type { Snapshot, InstanceDto, JobDto, ActivatedJob, IncidentDto, TimerDto, ActiveEl, AgentActivation, AgentResult, WasmEvent, } from "./types.js";
|
package/dist/session.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type InitInput } from "@nanobpm/engine-wasm";
|
|
2
|
-
import type { ActivatedJob, Snapshot, WasmEvent } from "./types.js";
|
|
2
|
+
import type { ActivatedJob, ActivateInstruction, AgentResult, Snapshot, WasmEvent } from "./types.js";
|
|
3
3
|
/**
|
|
4
4
|
* The source of the engine wasm binary. Under a bundler that understands
|
|
5
5
|
* `new URL(..., import.meta.url)` (e.g. Vite) the default loader needs no
|
|
@@ -46,8 +46,75 @@ export interface BojtosSession {
|
|
|
46
46
|
activateJobs(jobType: string, maxJobs: number, timeoutMs: number, worker: string): ActivatedJob[];
|
|
47
47
|
/** Complete a waiting job, merging `variablesJson` into the instance. */
|
|
48
48
|
completeJob(jobKey: string, variablesJson: string): Snapshot;
|
|
49
|
+
/**
|
|
50
|
+
* Complete an ad-hoc sub-process's **agent** job — the container's JOB_WORKER
|
|
51
|
+
* job (Camunda's agentic `aiagent-job-worker`) — carrying the agent's
|
|
52
|
+
* {@link AgentResult}. Its `activateElements` run the chosen inner tools this
|
|
53
|
+
* turn; `completionConditionFulfilled` ends the agent loop; `variables` merge
|
|
54
|
+
* into the instance (e.g. the agent's final decision). This is the ad-hoc seam
|
|
55
|
+
* plain {@link completeJob} deliberately omits. Register agents on the dispatch
|
|
56
|
+
* loop via `DispatchOptions.agents` rather than calling this directly.
|
|
57
|
+
*/
|
|
58
|
+
completeAgentJob(jobKey: string, result: AgentResult): Snapshot;
|
|
49
59
|
/** Fail a waiting job; with no retries left this raises an incident. */
|
|
50
60
|
failJob(jobKey: string, retries: number, message: string): Snapshot;
|
|
61
|
+
/**
|
|
62
|
+
* Throw a BPMN business error from a waiting job: interrupts the activity via a
|
|
63
|
+
* matching error boundary/event-subprocess catch, or raises an incident if
|
|
64
|
+
* uncaught. The job is consumed either way.
|
|
65
|
+
*/
|
|
66
|
+
throwError(jobKey: string, errorCode: string, errorMessage: string): Snapshot;
|
|
67
|
+
/**
|
|
68
|
+
* Set a job's remaining retries. Used to recover a job parked on a no-retries
|
|
69
|
+
* incident before resolving that incident; does not itself unblock the job.
|
|
70
|
+
*/
|
|
71
|
+
updateRetries(jobKey: string, retries: number): Snapshot;
|
|
72
|
+
/**
|
|
73
|
+
* Resolve an open incident by key, retrying the work that failed (returns a
|
|
74
|
+
* parked job to the activatable pool / re-evaluates a gateway / re-creates a
|
|
75
|
+
* service-task job).
|
|
76
|
+
*/
|
|
77
|
+
resolveIncident(incidentKey: string): Snapshot;
|
|
78
|
+
/**
|
|
79
|
+
* Merge variables into a scope (a process-instance or element-instance key).
|
|
80
|
+
* When `local` is true they are written strictly into the target scope,
|
|
81
|
+
* otherwise they propagate up to the nearest ancestor scope defining each name.
|
|
82
|
+
*/
|
|
83
|
+
setVariables(scopeKey: string, variablesJson: string, local: boolean): Snapshot;
|
|
84
|
+
/**
|
|
85
|
+
* Broadcast a signal by name to every open subscription that matches, across
|
|
86
|
+
* all instances, merging `variablesJson` into each correlated instance.
|
|
87
|
+
*/
|
|
88
|
+
broadcastSignal(signalName: string, variablesJson: string): Snapshot;
|
|
89
|
+
/** Cancel (terminate) a running process instance: every token is discarded. */
|
|
90
|
+
cancelInstance(instanceKey: string): Snapshot;
|
|
91
|
+
/**
|
|
92
|
+
* Modify a running process instance (Zeebe "modify process instance"): move
|
|
93
|
+
* tokens by terminating existing element instances and/or activating new
|
|
94
|
+
* ones. Each activate instruction places a token at `elementId` (in the
|
|
95
|
+
* process root scope), first merging its optional `variables` into the root
|
|
96
|
+
* scope; `terminateElementInstanceKeys` are the keys of active element
|
|
97
|
+
* instances (from `instances[].activeElements[].key`) to terminate. If the
|
|
98
|
+
* terminations drain the last token and nothing is activated, the instance is
|
|
99
|
+
* terminated.
|
|
100
|
+
*/
|
|
101
|
+
modify(instanceKey: string, activateInstructions: ActivateInstruction[], terminateElementInstanceKeys: string[]): Snapshot;
|
|
102
|
+
/** Complete a waiting user task, merging `variablesJson` into the instance. */
|
|
103
|
+
completeUserTask(userTaskKey: string, variablesJson: string): Snapshot;
|
|
104
|
+
/**
|
|
105
|
+
* Assign a user task to `assignee`. With `allowOverride` false the command is
|
|
106
|
+
* rejected if the task already has an assignee (unassign it first).
|
|
107
|
+
*/
|
|
108
|
+
assignUserTask(userTaskKey: string, assignee: string, allowOverride: boolean): Snapshot;
|
|
109
|
+
/** Clear a user task's assignee. */
|
|
110
|
+
unassignUserTask(userTaskKey: string): Snapshot;
|
|
111
|
+
/**
|
|
112
|
+
* Update a user task's attributes from a JSON changeset. Recognised keys (all
|
|
113
|
+
* optional): `candidateGroups` / `candidateUsers` (string arrays),
|
|
114
|
+
* `dueDate` / `followUpDate` (ISO-8601 string, or `null`/`""` to clear),
|
|
115
|
+
* `priority` (0..=100). Only present keys are changed.
|
|
116
|
+
*/
|
|
117
|
+
updateUserTask(userTaskKey: string, changesetJson: string): Snapshot;
|
|
51
118
|
/**
|
|
52
119
|
* Correlate a message to any instance waiting on it: publishes `messageName`
|
|
53
120
|
* with `correlationKey` (the value the waiting subscription's `correlationKey`
|
package/dist/session.js
CHANGED
|
@@ -45,9 +45,46 @@ class WasmBojtosSession {
|
|
|
45
45
|
completeJob(jobKey, variablesJson) {
|
|
46
46
|
return parseSnapshot(this.engine.completeJob(jobKey, variablesJson || "{}"));
|
|
47
47
|
}
|
|
48
|
+
completeAgentJob(jobKey, result) {
|
|
49
|
+
const { variables, ...agentResult } = result ?? {};
|
|
50
|
+
return parseSnapshot(this.engine.completeAgentJob(jobKey, JSON.stringify(variables ?? {}), JSON.stringify(agentResult ?? {})));
|
|
51
|
+
}
|
|
48
52
|
failJob(jobKey, retries, message) {
|
|
49
53
|
return parseSnapshot(this.engine.failJob(jobKey, retries, message));
|
|
50
54
|
}
|
|
55
|
+
throwError(jobKey, errorCode, errorMessage) {
|
|
56
|
+
return parseSnapshot(this.engine.throwError(jobKey, errorCode, errorMessage));
|
|
57
|
+
}
|
|
58
|
+
updateRetries(jobKey, retries) {
|
|
59
|
+
return parseSnapshot(this.engine.updateRetries(jobKey, retries));
|
|
60
|
+
}
|
|
61
|
+
resolveIncident(incidentKey) {
|
|
62
|
+
return parseSnapshot(this.engine.resolveIncident(incidentKey));
|
|
63
|
+
}
|
|
64
|
+
setVariables(scopeKey, variablesJson, local) {
|
|
65
|
+
return parseSnapshot(this.engine.setVariables(scopeKey, variablesJson || "{}", local));
|
|
66
|
+
}
|
|
67
|
+
broadcastSignal(signalName, variablesJson) {
|
|
68
|
+
return parseSnapshot(this.engine.broadcastSignal(signalName, variablesJson || "{}"));
|
|
69
|
+
}
|
|
70
|
+
cancelInstance(instanceKey) {
|
|
71
|
+
return parseSnapshot(this.engine.cancelInstance(instanceKey));
|
|
72
|
+
}
|
|
73
|
+
modify(instanceKey, activateInstructions, terminateElementInstanceKeys) {
|
|
74
|
+
return parseSnapshot(this.engine.modify(instanceKey, JSON.stringify(activateInstructions ?? []), JSON.stringify(terminateElementInstanceKeys ?? [])));
|
|
75
|
+
}
|
|
76
|
+
completeUserTask(userTaskKey, variablesJson) {
|
|
77
|
+
return parseSnapshot(this.engine.completeUserTask(userTaskKey, variablesJson || "{}"));
|
|
78
|
+
}
|
|
79
|
+
assignUserTask(userTaskKey, assignee, allowOverride) {
|
|
80
|
+
return parseSnapshot(this.engine.assignUserTask(userTaskKey, assignee, allowOverride));
|
|
81
|
+
}
|
|
82
|
+
unassignUserTask(userTaskKey) {
|
|
83
|
+
return parseSnapshot(this.engine.unassignUserTask(userTaskKey));
|
|
84
|
+
}
|
|
85
|
+
updateUserTask(userTaskKey, changesetJson) {
|
|
86
|
+
return parseSnapshot(this.engine.updateUserTask(userTaskKey, changesetJson || "{}"));
|
|
87
|
+
}
|
|
51
88
|
correlateMessage(messageName, correlationKey, variablesJson) {
|
|
52
89
|
return parseSnapshot(this.engine.correlateMessage(messageName, correlationKey, variablesJson || "{}"));
|
|
53
90
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -51,10 +51,107 @@ export interface TimerDto {
|
|
|
51
51
|
dueAt: number;
|
|
52
52
|
dueInMs: number;
|
|
53
53
|
}
|
|
54
|
+
/** A user task parked on a `userTask` element, awaiting a human. */
|
|
55
|
+
export interface UserTaskDto {
|
|
56
|
+
key: string;
|
|
57
|
+
instanceKey: string;
|
|
58
|
+
elementInstanceKey: string;
|
|
59
|
+
elementId: string;
|
|
60
|
+
/** `Created` (waiting), `Completed`, or `Canceled`. */
|
|
61
|
+
state: string;
|
|
62
|
+
assignee?: string;
|
|
63
|
+
candidateGroups: string[];
|
|
64
|
+
candidateUsers: string[];
|
|
65
|
+
dueDate?: string;
|
|
66
|
+
followUpDate?: string;
|
|
67
|
+
priority: number;
|
|
68
|
+
}
|
|
69
|
+
/** An open message subscription (a waiting message catch/boundary event). */
|
|
70
|
+
export interface MessageSubscriptionDto {
|
|
71
|
+
key: string;
|
|
72
|
+
instanceKey: string;
|
|
73
|
+
elementId: string;
|
|
74
|
+
messageName: string;
|
|
75
|
+
correlationKey: string;
|
|
76
|
+
/** What the subscription guards (intermediate/boundary, interrupting or not). */
|
|
77
|
+
kind: string;
|
|
78
|
+
}
|
|
79
|
+
/** An open signal subscription (a waiting signal catch/boundary event). */
|
|
80
|
+
export interface SignalSubscriptionDto {
|
|
81
|
+
key: string;
|
|
82
|
+
instanceKey: string;
|
|
83
|
+
elementId: string;
|
|
84
|
+
signalName: string;
|
|
85
|
+
kind: string;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Per-element token statistics for diagram overlays: `active` live tokens,
|
|
89
|
+
* cumulative `completed` element instances, and current `incidents`.
|
|
90
|
+
*/
|
|
91
|
+
export interface ElementStatDto {
|
|
92
|
+
elementId: string;
|
|
93
|
+
active: number;
|
|
94
|
+
completed: number;
|
|
95
|
+
incidents: number;
|
|
96
|
+
}
|
|
97
|
+
/** A traversed connection, as source/target element ids. */
|
|
98
|
+
export interface SequenceFlowDto {
|
|
99
|
+
from: string;
|
|
100
|
+
to: string;
|
|
101
|
+
}
|
|
102
|
+
/** An evaluated decision instance (from a `businessRuleTask` / DMN). */
|
|
103
|
+
export interface DecisionInstanceDto {
|
|
104
|
+
instanceKey: string;
|
|
105
|
+
elementId: string;
|
|
106
|
+
decisionKey: string;
|
|
107
|
+
decisionId: string;
|
|
108
|
+
output: unknown;
|
|
109
|
+
evaluatedAt: number;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* One activation instruction for {@link BojtosSession.modify}: place a new token
|
|
113
|
+
* at `elementId`, first merging `variables` into the instance's root scope.
|
|
114
|
+
* Mirrors Zeebe's process-instance-modification activate instruction (the token
|
|
115
|
+
* is activated in the process root scope).
|
|
116
|
+
*/
|
|
117
|
+
export interface ActivateInstruction {
|
|
118
|
+
elementId: string;
|
|
119
|
+
variables?: Record<string, unknown>;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* One tool activation an agent asks for inside an ad-hoc sub-process (Camunda
|
|
123
|
+
* agentic `activateElements[]`): activate the inner element `elementId`, seeding
|
|
124
|
+
* `variables` into its local scope. Distinct from {@link ActivateInstruction}
|
|
125
|
+
* (process-instance modification) — this activates a *child* of the ad-hoc
|
|
126
|
+
* container, not a token in the process root.
|
|
127
|
+
*/
|
|
128
|
+
export interface AgentActivation {
|
|
129
|
+
elementId: string;
|
|
130
|
+
variables?: Record<string, unknown>;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The result an agent returns when completing an ad-hoc sub-process's agent job
|
|
134
|
+
* (Camunda's agentic `JobResult`): the tools to run this turn
|
|
135
|
+
* (`activateElements`), whether the container's `<completionCondition>` is now
|
|
136
|
+
* satisfied (`completionConditionFulfilled`), and whether to cancel any
|
|
137
|
+
* still-running tools (`cancelRemainingInstances`). `variables` merges into the
|
|
138
|
+
* instance on completion exactly like a plain job result (e.g. the agent's final
|
|
139
|
+
* decision). All fields optional: an empty result completes the container this
|
|
140
|
+
* turn with nothing activated.
|
|
141
|
+
*/
|
|
142
|
+
export interface AgentResult {
|
|
143
|
+
activateElements?: AgentActivation[];
|
|
144
|
+
completionConditionFulfilled?: boolean;
|
|
145
|
+
cancelRemainingInstances?: boolean;
|
|
146
|
+
variables?: Record<string, unknown>;
|
|
147
|
+
}
|
|
54
148
|
/**
|
|
55
149
|
* The full simulation state returned by every engine command. `activeElementIds`
|
|
56
150
|
* / `incidentElementIds` drive the token/incident highlight (the visual
|
|
57
151
|
* contract, ADR 0043 §4); `instances[].variables` is the live payload.
|
|
152
|
+
* `userTasks`, `messageSubscriptions`, `signalSubscriptions`, `elementStats`,
|
|
153
|
+
* `takenSequenceFlows` and `decisionInstances` back a Web-Modeler-Play-style UI
|
|
154
|
+
* (task panels, correlation/broadcast, overlays, DMN results).
|
|
58
155
|
*/
|
|
59
156
|
export interface Snapshot {
|
|
60
157
|
now: number;
|
|
@@ -67,6 +164,12 @@ export interface Snapshot {
|
|
|
67
164
|
jobs: JobDto[];
|
|
68
165
|
incidents: IncidentDto[];
|
|
69
166
|
timers: TimerDto[];
|
|
167
|
+
userTasks: UserTaskDto[];
|
|
168
|
+
messageSubscriptions: MessageSubscriptionDto[];
|
|
169
|
+
signalSubscriptions: SignalSubscriptionDto[];
|
|
170
|
+
elementStats: ElementStatDto[];
|
|
171
|
+
takenSequenceFlows: SequenceFlowDto[];
|
|
172
|
+
decisionInstances: DecisionInstanceDto[];
|
|
70
173
|
activeElementIds: string[];
|
|
71
174
|
incidentElementIds: string[];
|
|
72
175
|
}
|
package/dist/worker.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BojtosSession } from "./session.js";
|
|
2
|
-
import type { ActivatedJob, Snapshot } from "./types.js";
|
|
2
|
+
import type { ActivatedJob, AgentResult, Snapshot } from "./types.js";
|
|
3
3
|
/**
|
|
4
4
|
* The variables a handler merges into its instance on completion. Return an
|
|
5
5
|
* object to merge it, or `void`/`undefined` to complete with no new variables.
|
|
@@ -14,6 +14,17 @@ export type JobResult = Record<string, unknown>;
|
|
|
14
14
|
* completion. May be async. Throw to fail the job.
|
|
15
15
|
*/
|
|
16
16
|
export type JobHandler = (job: ActivatedJob) => JobResult | void | Promise<JobResult | void>;
|
|
17
|
+
/**
|
|
18
|
+
* A handler for an ad-hoc sub-process's **agent** job (the container's
|
|
19
|
+
* JOB_WORKER job). Given the activated container job (carrying the instance's
|
|
20
|
+
* current variables — e.g. accumulated tool outputs), return the
|
|
21
|
+
* {@link AgentResult} for this turn: which inner tools to activate, whether the
|
|
22
|
+
* agent is done, and any variables to merge. Called once per agent turn; the
|
|
23
|
+
* engine re-emits the agent job after the activated tools drain, so a stateful
|
|
24
|
+
* closure can drive a multi-turn agent (activate tools → read results →
|
|
25
|
+
* decide → complete). May be async. Throw to fail the container job.
|
|
26
|
+
*/
|
|
27
|
+
export type AgentHandler = (job: ActivatedJob) => AgentResult | Promise<AgentResult>;
|
|
17
28
|
/**
|
|
18
29
|
* Throw from a {@link JobHandler} to fail a job with an explicit remaining
|
|
19
30
|
* `retries` count (default is `job.retries - 1`). With `retries: 0` the engine
|
|
@@ -40,6 +51,16 @@ export interface DispatchOptions {
|
|
|
40
51
|
* exceeding the cap throws instead.
|
|
41
52
|
*/
|
|
42
53
|
maxRounds?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Handlers for ad-hoc sub-process **agent** job types (Camunda agentic
|
|
56
|
+
* `aiagent-job-worker`), keyed by the container's `zeebe:taskDefinition type`.
|
|
57
|
+
* Dispatched like {@link JobHandler}s but completed via
|
|
58
|
+
* {@link BojtosSession.completeAgentJob}, so their returned
|
|
59
|
+
* {@link AgentResult} drives the tools to activate this turn. The engine
|
|
60
|
+
* re-emits the agent job across turns, so the standard drain loop advances the
|
|
61
|
+
* whole agent conversation to quiescence.
|
|
62
|
+
*/
|
|
63
|
+
agents?: Record<string, AgentHandler>;
|
|
43
64
|
}
|
|
44
65
|
/** What one {@link dispatchRound} pass did. */
|
|
45
66
|
export interface RoundResult {
|
|
@@ -66,6 +87,13 @@ export interface DispatchResult {
|
|
|
66
87
|
* exactly one step. That makes this the animatable unit: drive it on a timer to
|
|
67
88
|
* watch the token(s) hop task-to-task. {@link dispatchWorkers} loops it to
|
|
68
89
|
* quiescence.
|
|
90
|
+
*
|
|
91
|
+
* Ad-hoc **agent** job types registered via `opts.agents` are activated and
|
|
92
|
+
* completed in the same frontier-snapshot pass, but through
|
|
93
|
+
* {@link BojtosSession.completeAgentJob} so their {@link AgentResult} activates
|
|
94
|
+
* the chosen tools. A tool a turn activates joins the *next* frontier, and the
|
|
95
|
+
* engine re-emits the agent job after those tools drain, so the agent's whole
|
|
96
|
+
* multi-turn conversation animates one step per round like any other token.
|
|
69
97
|
*/
|
|
70
98
|
export declare function dispatchRound(session: BojtosSession, workers: Record<string, JobHandler>, opts?: DispatchOptions): Promise<RoundResult>;
|
|
71
99
|
/**
|
package/dist/worker.js
CHANGED
|
@@ -35,6 +35,30 @@ async function runOne(session, handler, job) {
|
|
|
35
35
|
// we let it bubble to the caller.
|
|
36
36
|
session.completeJob(job.key, payload);
|
|
37
37
|
}
|
|
38
|
+
async function runOneAgent(session, handler, job) {
|
|
39
|
+
let result;
|
|
40
|
+
try {
|
|
41
|
+
// As with a plain job, only the handler is treated as demo logic: a throw
|
|
42
|
+
// (or a result the engine can't serialize) fails the container job rather
|
|
43
|
+
// than bubbling up as an engine/ABI error.
|
|
44
|
+
result = await handler(job);
|
|
45
|
+
// Mirror runOne: a result the engine can't serialize is the demo's own
|
|
46
|
+
// logic failing, not an engine/ABI error. session.completeAgentJob
|
|
47
|
+
// stringifies the result internally (outside this try), so probe-serialize
|
|
48
|
+
// it here to route a serialization failure through failJob instead of
|
|
49
|
+
// letting it bubble out of the dispatch loop.
|
|
50
|
+
JSON.stringify(result);
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
const retries = e instanceof JobFailure && e.retries !== undefined
|
|
54
|
+
? e.retries
|
|
55
|
+
: Math.max(0, job.retries - 1);
|
|
56
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
57
|
+
session.failJob(job.key, retries, message);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
session.completeAgentJob(job.key, result);
|
|
61
|
+
}
|
|
38
62
|
/**
|
|
39
63
|
* Run one activate-and-handle pass: activate every registered job type's
|
|
40
64
|
* currently-`Created` jobs *first* (a snapshot of the token frontier), then hand
|
|
@@ -44,25 +68,54 @@ async function runOne(session, handler, job) {
|
|
|
44
68
|
* exactly one step. That makes this the animatable unit: drive it on a timer to
|
|
45
69
|
* watch the token(s) hop task-to-task. {@link dispatchWorkers} loops it to
|
|
46
70
|
* quiescence.
|
|
71
|
+
*
|
|
72
|
+
* Ad-hoc **agent** job types registered via `opts.agents` are activated and
|
|
73
|
+
* completed in the same frontier-snapshot pass, but through
|
|
74
|
+
* {@link BojtosSession.completeAgentJob} so their {@link AgentResult} activates
|
|
75
|
+
* the chosen tools. A tool a turn activates joins the *next* frontier, and the
|
|
76
|
+
* engine re-emits the agent job after those tools drain, so the agent's whole
|
|
77
|
+
* multi-turn conversation animates one step per round like any other token.
|
|
47
78
|
*/
|
|
48
79
|
export async function dispatchRound(session, workers, opts = {}) {
|
|
49
80
|
const maxJobs = opts.maxJobsPerActivation ?? 10;
|
|
50
81
|
const timeout = opts.lockTimeoutMs ?? 30_000;
|
|
51
82
|
const worker = opts.worker ?? "bojtos";
|
|
83
|
+
const agents = opts.agents ?? {};
|
|
84
|
+
// A job type registered as both a worker and an agent is ambiguous: the
|
|
85
|
+
// worker pass below would activate and plain-complete it first, so its
|
|
86
|
+
// agentic `activateElements` could never be sent. Reject up front rather than
|
|
87
|
+
// silently no-op the tool activation.
|
|
88
|
+
for (const jobType of Object.keys(agents)) {
|
|
89
|
+
if (jobType in workers) {
|
|
90
|
+
throw new Error(`dispatchRound: job type "${jobType}" is registered as both a worker and an agent — register it as exactly one`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
52
93
|
// Activation pass: lock the whole current frontier before running any handler,
|
|
53
94
|
// so a job a handler unblocks isn't also picked up this round (which would
|
|
54
95
|
// cascade the entire chain in a single "step").
|
|
55
|
-
const
|
|
96
|
+
const jobBatch = [];
|
|
56
97
|
for (const [jobType, handler] of Object.entries(workers)) {
|
|
57
98
|
for (const job of session.activateJobs(jobType, maxJobs, timeout, worker)) {
|
|
58
|
-
|
|
99
|
+
jobBatch.push({ handler, job });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const agentBatch = [];
|
|
103
|
+
for (const [jobType, handler] of Object.entries(agents)) {
|
|
104
|
+
for (const job of session.activateJobs(jobType, maxJobs, timeout, worker)) {
|
|
105
|
+
agentBatch.push({ handler, job });
|
|
59
106
|
}
|
|
60
107
|
}
|
|
61
108
|
// Handle pass.
|
|
62
|
-
for (const { handler, job } of
|
|
109
|
+
for (const { handler, job } of jobBatch) {
|
|
63
110
|
await runOne(session, handler, job);
|
|
64
111
|
}
|
|
65
|
-
|
|
112
|
+
for (const { handler, job } of agentBatch) {
|
|
113
|
+
await runOneAgent(session, handler, job);
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
snapshot: session.snapshot(),
|
|
117
|
+
handled: jobBatch.length + agentBatch.length,
|
|
118
|
+
};
|
|
66
119
|
}
|
|
67
120
|
/**
|
|
68
121
|
* Drive an in-browser worker loop over a {@link BojtosSession}: repeatedly
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/bojtos-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Framework-agnostic core of the Bojtos in-browser BPMN demo framework (ADR 0043): a single scenario runner over the @nanobpm/engine-wasm engine (deploy, start instances, complete/fail jobs, advance the clock, read snapshots and the event log), plus the engine's snapshot/event contract types. Consumed by @nanobpm/bojtos-react and the console test-run panel.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/Magikcraft/nano-bpm",
|
|
9
|
+
"url": "git+https://github.com/Magikcraft/nano-bpm.git",
|
|
10
10
|
"directory": "bojtos-kit"
|
|
11
11
|
},
|
|
12
12
|
"main": "./dist/index.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"prepack": "npm run build"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@nanobpm/engine-wasm": "^0.
|
|
35
|
+
"@nanobpm/engine-wasm": "^0.3.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"typescript": "^5.6.3"
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export {
|
|
|
14
14
|
JobFailure,
|
|
15
15
|
type JobHandler,
|
|
16
16
|
type JobResult,
|
|
17
|
+
type AgentHandler,
|
|
17
18
|
type DispatchOptions,
|
|
18
19
|
type DispatchResult,
|
|
19
20
|
type RoundResult,
|
|
@@ -26,5 +27,7 @@ export type {
|
|
|
26
27
|
IncidentDto,
|
|
27
28
|
TimerDto,
|
|
28
29
|
ActiveEl,
|
|
30
|
+
AgentActivation,
|
|
31
|
+
AgentResult,
|
|
29
32
|
WasmEvent,
|
|
30
33
|
} from "./types.js";
|
package/src/session.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import init, { type InitInput, TestEngine } from "@nanobpm/engine-wasm";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
ActivatedJob,
|
|
4
|
+
ActivateInstruction,
|
|
5
|
+
AgentResult,
|
|
6
|
+
Snapshot,
|
|
7
|
+
WasmEvent,
|
|
8
|
+
} from "./types.js";
|
|
3
9
|
|
|
4
10
|
// Lazily initialise the wasm module exactly once per page, no matter how many
|
|
5
11
|
// sessions are created. Mirrors the console's original `ensureWasm`.
|
|
@@ -68,8 +74,87 @@ export interface BojtosSession {
|
|
|
68
74
|
): ActivatedJob[];
|
|
69
75
|
/** Complete a waiting job, merging `variablesJson` into the instance. */
|
|
70
76
|
completeJob(jobKey: string, variablesJson: string): Snapshot;
|
|
77
|
+
/**
|
|
78
|
+
* Complete an ad-hoc sub-process's **agent** job — the container's JOB_WORKER
|
|
79
|
+
* job (Camunda's agentic `aiagent-job-worker`) — carrying the agent's
|
|
80
|
+
* {@link AgentResult}. Its `activateElements` run the chosen inner tools this
|
|
81
|
+
* turn; `completionConditionFulfilled` ends the agent loop; `variables` merge
|
|
82
|
+
* into the instance (e.g. the agent's final decision). This is the ad-hoc seam
|
|
83
|
+
* plain {@link completeJob} deliberately omits. Register agents on the dispatch
|
|
84
|
+
* loop via `DispatchOptions.agents` rather than calling this directly.
|
|
85
|
+
*/
|
|
86
|
+
completeAgentJob(jobKey: string, result: AgentResult): Snapshot;
|
|
71
87
|
/** Fail a waiting job; with no retries left this raises an incident. */
|
|
72
88
|
failJob(jobKey: string, retries: number, message: string): Snapshot;
|
|
89
|
+
/**
|
|
90
|
+
* Throw a BPMN business error from a waiting job: interrupts the activity via a
|
|
91
|
+
* matching error boundary/event-subprocess catch, or raises an incident if
|
|
92
|
+
* uncaught. The job is consumed either way.
|
|
93
|
+
*/
|
|
94
|
+
throwError(jobKey: string, errorCode: string, errorMessage: string): Snapshot;
|
|
95
|
+
/**
|
|
96
|
+
* Set a job's remaining retries. Used to recover a job parked on a no-retries
|
|
97
|
+
* incident before resolving that incident; does not itself unblock the job.
|
|
98
|
+
*/
|
|
99
|
+
updateRetries(jobKey: string, retries: number): Snapshot;
|
|
100
|
+
/**
|
|
101
|
+
* Resolve an open incident by key, retrying the work that failed (returns a
|
|
102
|
+
* parked job to the activatable pool / re-evaluates a gateway / re-creates a
|
|
103
|
+
* service-task job).
|
|
104
|
+
*/
|
|
105
|
+
resolveIncident(incidentKey: string): Snapshot;
|
|
106
|
+
/**
|
|
107
|
+
* Merge variables into a scope (a process-instance or element-instance key).
|
|
108
|
+
* When `local` is true they are written strictly into the target scope,
|
|
109
|
+
* otherwise they propagate up to the nearest ancestor scope defining each name.
|
|
110
|
+
*/
|
|
111
|
+
setVariables(
|
|
112
|
+
scopeKey: string,
|
|
113
|
+
variablesJson: string,
|
|
114
|
+
local: boolean,
|
|
115
|
+
): Snapshot;
|
|
116
|
+
/**
|
|
117
|
+
* Broadcast a signal by name to every open subscription that matches, across
|
|
118
|
+
* all instances, merging `variablesJson` into each correlated instance.
|
|
119
|
+
*/
|
|
120
|
+
broadcastSignal(signalName: string, variablesJson: string): Snapshot;
|
|
121
|
+
/** Cancel (terminate) a running process instance: every token is discarded. */
|
|
122
|
+
cancelInstance(instanceKey: string): Snapshot;
|
|
123
|
+
/**
|
|
124
|
+
* Modify a running process instance (Zeebe "modify process instance"): move
|
|
125
|
+
* tokens by terminating existing element instances and/or activating new
|
|
126
|
+
* ones. Each activate instruction places a token at `elementId` (in the
|
|
127
|
+
* process root scope), first merging its optional `variables` into the root
|
|
128
|
+
* scope; `terminateElementInstanceKeys` are the keys of active element
|
|
129
|
+
* instances (from `instances[].activeElements[].key`) to terminate. If the
|
|
130
|
+
* terminations drain the last token and nothing is activated, the instance is
|
|
131
|
+
* terminated.
|
|
132
|
+
*/
|
|
133
|
+
modify(
|
|
134
|
+
instanceKey: string,
|
|
135
|
+
activateInstructions: ActivateInstruction[],
|
|
136
|
+
terminateElementInstanceKeys: string[],
|
|
137
|
+
): Snapshot;
|
|
138
|
+
/** Complete a waiting user task, merging `variablesJson` into the instance. */
|
|
139
|
+
completeUserTask(userTaskKey: string, variablesJson: string): Snapshot;
|
|
140
|
+
/**
|
|
141
|
+
* Assign a user task to `assignee`. With `allowOverride` false the command is
|
|
142
|
+
* rejected if the task already has an assignee (unassign it first).
|
|
143
|
+
*/
|
|
144
|
+
assignUserTask(
|
|
145
|
+
userTaskKey: string,
|
|
146
|
+
assignee: string,
|
|
147
|
+
allowOverride: boolean,
|
|
148
|
+
): Snapshot;
|
|
149
|
+
/** Clear a user task's assignee. */
|
|
150
|
+
unassignUserTask(userTaskKey: string): Snapshot;
|
|
151
|
+
/**
|
|
152
|
+
* Update a user task's attributes from a JSON changeset. Recognised keys (all
|
|
153
|
+
* optional): `candidateGroups` / `candidateUsers` (string arrays),
|
|
154
|
+
* `dueDate` / `followUpDate` (ISO-8601 string, or `null`/`""` to clear),
|
|
155
|
+
* `priority` (0..=100). Only present keys are changed.
|
|
156
|
+
*/
|
|
157
|
+
updateUserTask(userTaskKey: string, changesetJson: string): Snapshot;
|
|
73
158
|
/**
|
|
74
159
|
* Correlate a message to any instance waiting on it: publishes `messageName`
|
|
75
160
|
* with `correlationKey` (the value the waiting subscription's `correlationKey`
|
|
@@ -137,10 +222,99 @@ class WasmBojtosSession implements BojtosSession {
|
|
|
137
222
|
return parseSnapshot(this.engine.completeJob(jobKey, variablesJson || "{}"));
|
|
138
223
|
}
|
|
139
224
|
|
|
225
|
+
completeAgentJob(jobKey: string, result: AgentResult): Snapshot {
|
|
226
|
+
const { variables, ...agentResult } = result ?? {};
|
|
227
|
+
return parseSnapshot(
|
|
228
|
+
this.engine.completeAgentJob(
|
|
229
|
+
jobKey,
|
|
230
|
+
JSON.stringify(variables ?? {}),
|
|
231
|
+
JSON.stringify(agentResult ?? {}),
|
|
232
|
+
),
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
140
236
|
failJob(jobKey: string, retries: number, message: string): Snapshot {
|
|
141
237
|
return parseSnapshot(this.engine.failJob(jobKey, retries, message));
|
|
142
238
|
}
|
|
143
239
|
|
|
240
|
+
throwError(
|
|
241
|
+
jobKey: string,
|
|
242
|
+
errorCode: string,
|
|
243
|
+
errorMessage: string,
|
|
244
|
+
): Snapshot {
|
|
245
|
+
return parseSnapshot(
|
|
246
|
+
this.engine.throwError(jobKey, errorCode, errorMessage),
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
updateRetries(jobKey: string, retries: number): Snapshot {
|
|
251
|
+
return parseSnapshot(this.engine.updateRetries(jobKey, retries));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
resolveIncident(incidentKey: string): Snapshot {
|
|
255
|
+
return parseSnapshot(this.engine.resolveIncident(incidentKey));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
setVariables(
|
|
259
|
+
scopeKey: string,
|
|
260
|
+
variablesJson: string,
|
|
261
|
+
local: boolean,
|
|
262
|
+
): Snapshot {
|
|
263
|
+
return parseSnapshot(
|
|
264
|
+
this.engine.setVariables(scopeKey, variablesJson || "{}", local),
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
broadcastSignal(signalName: string, variablesJson: string): Snapshot {
|
|
269
|
+
return parseSnapshot(
|
|
270
|
+
this.engine.broadcastSignal(signalName, variablesJson || "{}"),
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
cancelInstance(instanceKey: string): Snapshot {
|
|
275
|
+
return parseSnapshot(this.engine.cancelInstance(instanceKey));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
modify(
|
|
279
|
+
instanceKey: string,
|
|
280
|
+
activateInstructions: ActivateInstruction[],
|
|
281
|
+
terminateElementInstanceKeys: string[],
|
|
282
|
+
): Snapshot {
|
|
283
|
+
return parseSnapshot(
|
|
284
|
+
this.engine.modify(
|
|
285
|
+
instanceKey,
|
|
286
|
+
JSON.stringify(activateInstructions ?? []),
|
|
287
|
+
JSON.stringify(terminateElementInstanceKeys ?? []),
|
|
288
|
+
),
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
completeUserTask(userTaskKey: string, variablesJson: string): Snapshot {
|
|
293
|
+
return parseSnapshot(
|
|
294
|
+
this.engine.completeUserTask(userTaskKey, variablesJson || "{}"),
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
assignUserTask(
|
|
299
|
+
userTaskKey: string,
|
|
300
|
+
assignee: string,
|
|
301
|
+
allowOverride: boolean,
|
|
302
|
+
): Snapshot {
|
|
303
|
+
return parseSnapshot(
|
|
304
|
+
this.engine.assignUserTask(userTaskKey, assignee, allowOverride),
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
unassignUserTask(userTaskKey: string): Snapshot {
|
|
309
|
+
return parseSnapshot(this.engine.unassignUserTask(userTaskKey));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
updateUserTask(userTaskKey: string, changesetJson: string): Snapshot {
|
|
313
|
+
return parseSnapshot(
|
|
314
|
+
this.engine.updateUserTask(userTaskKey, changesetJson || "{}"),
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
144
318
|
correlateMessage(
|
|
145
319
|
messageName: string,
|
|
146
320
|
correlationKey: string,
|
package/src/types.ts
CHANGED
|
@@ -63,10 +63,116 @@ export interface TimerDto {
|
|
|
63
63
|
dueInMs: number;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/** A user task parked on a `userTask` element, awaiting a human. */
|
|
67
|
+
export interface UserTaskDto {
|
|
68
|
+
key: string;
|
|
69
|
+
instanceKey: string;
|
|
70
|
+
elementInstanceKey: string;
|
|
71
|
+
elementId: string;
|
|
72
|
+
/** `Created` (waiting), `Completed`, or `Canceled`. */
|
|
73
|
+
state: string;
|
|
74
|
+
assignee?: string;
|
|
75
|
+
candidateGroups: string[];
|
|
76
|
+
candidateUsers: string[];
|
|
77
|
+
dueDate?: string;
|
|
78
|
+
followUpDate?: string;
|
|
79
|
+
priority: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** An open message subscription (a waiting message catch/boundary event). */
|
|
83
|
+
export interface MessageSubscriptionDto {
|
|
84
|
+
key: string;
|
|
85
|
+
instanceKey: string;
|
|
86
|
+
elementId: string;
|
|
87
|
+
messageName: string;
|
|
88
|
+
correlationKey: string;
|
|
89
|
+
/** What the subscription guards (intermediate/boundary, interrupting or not). */
|
|
90
|
+
kind: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** An open signal subscription (a waiting signal catch/boundary event). */
|
|
94
|
+
export interface SignalSubscriptionDto {
|
|
95
|
+
key: string;
|
|
96
|
+
instanceKey: string;
|
|
97
|
+
elementId: string;
|
|
98
|
+
signalName: string;
|
|
99
|
+
kind: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Per-element token statistics for diagram overlays: `active` live tokens,
|
|
104
|
+
* cumulative `completed` element instances, and current `incidents`.
|
|
105
|
+
*/
|
|
106
|
+
export interface ElementStatDto {
|
|
107
|
+
elementId: string;
|
|
108
|
+
active: number;
|
|
109
|
+
completed: number;
|
|
110
|
+
incidents: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** A traversed connection, as source/target element ids. */
|
|
114
|
+
export interface SequenceFlowDto {
|
|
115
|
+
from: string;
|
|
116
|
+
to: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** An evaluated decision instance (from a `businessRuleTask` / DMN). */
|
|
120
|
+
export interface DecisionInstanceDto {
|
|
121
|
+
instanceKey: string;
|
|
122
|
+
elementId: string;
|
|
123
|
+
decisionKey: string;
|
|
124
|
+
decisionId: string;
|
|
125
|
+
output: unknown;
|
|
126
|
+
evaluatedAt: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* One activation instruction for {@link BojtosSession.modify}: place a new token
|
|
131
|
+
* at `elementId`, first merging `variables` into the instance's root scope.
|
|
132
|
+
* Mirrors Zeebe's process-instance-modification activate instruction (the token
|
|
133
|
+
* is activated in the process root scope).
|
|
134
|
+
*/
|
|
135
|
+
export interface ActivateInstruction {
|
|
136
|
+
elementId: string;
|
|
137
|
+
variables?: Record<string, unknown>;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* One tool activation an agent asks for inside an ad-hoc sub-process (Camunda
|
|
142
|
+
* agentic `activateElements[]`): activate the inner element `elementId`, seeding
|
|
143
|
+
* `variables` into its local scope. Distinct from {@link ActivateInstruction}
|
|
144
|
+
* (process-instance modification) — this activates a *child* of the ad-hoc
|
|
145
|
+
* container, not a token in the process root.
|
|
146
|
+
*/
|
|
147
|
+
export interface AgentActivation {
|
|
148
|
+
elementId: string;
|
|
149
|
+
variables?: Record<string, unknown>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The result an agent returns when completing an ad-hoc sub-process's agent job
|
|
154
|
+
* (Camunda's agentic `JobResult`): the tools to run this turn
|
|
155
|
+
* (`activateElements`), whether the container's `<completionCondition>` is now
|
|
156
|
+
* satisfied (`completionConditionFulfilled`), and whether to cancel any
|
|
157
|
+
* still-running tools (`cancelRemainingInstances`). `variables` merges into the
|
|
158
|
+
* instance on completion exactly like a plain job result (e.g. the agent's final
|
|
159
|
+
* decision). All fields optional: an empty result completes the container this
|
|
160
|
+
* turn with nothing activated.
|
|
161
|
+
*/
|
|
162
|
+
export interface AgentResult {
|
|
163
|
+
activateElements?: AgentActivation[];
|
|
164
|
+
completionConditionFulfilled?: boolean;
|
|
165
|
+
cancelRemainingInstances?: boolean;
|
|
166
|
+
variables?: Record<string, unknown>;
|
|
167
|
+
}
|
|
168
|
+
|
|
66
169
|
/**
|
|
67
170
|
* The full simulation state returned by every engine command. `activeElementIds`
|
|
68
171
|
* / `incidentElementIds` drive the token/incident highlight (the visual
|
|
69
172
|
* contract, ADR 0043 §4); `instances[].variables` is the live payload.
|
|
173
|
+
* `userTasks`, `messageSubscriptions`, `signalSubscriptions`, `elementStats`,
|
|
174
|
+
* `takenSequenceFlows` and `decisionInstances` back a Web-Modeler-Play-style UI
|
|
175
|
+
* (task panels, correlation/broadcast, overlays, DMN results).
|
|
70
176
|
*/
|
|
71
177
|
export interface Snapshot {
|
|
72
178
|
now: number;
|
|
@@ -79,6 +185,12 @@ export interface Snapshot {
|
|
|
79
185
|
jobs: JobDto[];
|
|
80
186
|
incidents: IncidentDto[];
|
|
81
187
|
timers: TimerDto[];
|
|
188
|
+
userTasks: UserTaskDto[];
|
|
189
|
+
messageSubscriptions: MessageSubscriptionDto[];
|
|
190
|
+
signalSubscriptions: SignalSubscriptionDto[];
|
|
191
|
+
elementStats: ElementStatDto[];
|
|
192
|
+
takenSequenceFlows: SequenceFlowDto[];
|
|
193
|
+
decisionInstances: DecisionInstanceDto[];
|
|
82
194
|
activeElementIds: string[];
|
|
83
195
|
incidentElementIds: string[];
|
|
84
196
|
}
|
package/src/worker.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BojtosSession } from "./session.js";
|
|
2
|
-
import type { ActivatedJob, Snapshot } from "./types.js";
|
|
2
|
+
import type { ActivatedJob, AgentResult, Snapshot } from "./types.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The variables a handler merges into its instance on completion. Return an
|
|
@@ -19,6 +19,20 @@ export type JobHandler = (
|
|
|
19
19
|
job: ActivatedJob,
|
|
20
20
|
) => JobResult | void | Promise<JobResult | void>;
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* A handler for an ad-hoc sub-process's **agent** job (the container's
|
|
24
|
+
* JOB_WORKER job). Given the activated container job (carrying the instance's
|
|
25
|
+
* current variables — e.g. accumulated tool outputs), return the
|
|
26
|
+
* {@link AgentResult} for this turn: which inner tools to activate, whether the
|
|
27
|
+
* agent is done, and any variables to merge. Called once per agent turn; the
|
|
28
|
+
* engine re-emits the agent job after the activated tools drain, so a stateful
|
|
29
|
+
* closure can drive a multi-turn agent (activate tools → read results →
|
|
30
|
+
* decide → complete). May be async. Throw to fail the container job.
|
|
31
|
+
*/
|
|
32
|
+
export type AgentHandler = (
|
|
33
|
+
job: ActivatedJob,
|
|
34
|
+
) => AgentResult | Promise<AgentResult>;
|
|
35
|
+
|
|
22
36
|
/**
|
|
23
37
|
* Throw from a {@link JobHandler} to fail a job with an explicit remaining
|
|
24
38
|
* `retries` count (default is `job.retries - 1`). With `retries: 0` the engine
|
|
@@ -48,6 +62,16 @@ export interface DispatchOptions {
|
|
|
48
62
|
* exceeding the cap throws instead.
|
|
49
63
|
*/
|
|
50
64
|
maxRounds?: number;
|
|
65
|
+
/**
|
|
66
|
+
* Handlers for ad-hoc sub-process **agent** job types (Camunda agentic
|
|
67
|
+
* `aiagent-job-worker`), keyed by the container's `zeebe:taskDefinition type`.
|
|
68
|
+
* Dispatched like {@link JobHandler}s but completed via
|
|
69
|
+
* {@link BojtosSession.completeAgentJob}, so their returned
|
|
70
|
+
* {@link AgentResult} drives the tools to activate this turn. The engine
|
|
71
|
+
* re-emits the agent job across turns, so the standard drain loop advances the
|
|
72
|
+
* whole agent conversation to quiescence.
|
|
73
|
+
*/
|
|
74
|
+
agents?: Record<string, AgentHandler>;
|
|
51
75
|
}
|
|
52
76
|
|
|
53
77
|
/** What one {@link dispatchRound} pass did. */
|
|
@@ -96,6 +120,35 @@ async function runOne(
|
|
|
96
120
|
session.completeJob(job.key, payload);
|
|
97
121
|
}
|
|
98
122
|
|
|
123
|
+
async function runOneAgent(
|
|
124
|
+
session: BojtosSession,
|
|
125
|
+
handler: AgentHandler,
|
|
126
|
+
job: ActivatedJob,
|
|
127
|
+
): Promise<void> {
|
|
128
|
+
let result: AgentResult;
|
|
129
|
+
try {
|
|
130
|
+
// As with a plain job, only the handler is treated as demo logic: a throw
|
|
131
|
+
// (or a result the engine can't serialize) fails the container job rather
|
|
132
|
+
// than bubbling up as an engine/ABI error.
|
|
133
|
+
result = await handler(job);
|
|
134
|
+
// Mirror runOne: a result the engine can't serialize is the demo's own
|
|
135
|
+
// logic failing, not an engine/ABI error. session.completeAgentJob
|
|
136
|
+
// stringifies the result internally (outside this try), so probe-serialize
|
|
137
|
+
// it here to route a serialization failure through failJob instead of
|
|
138
|
+
// letting it bubble out of the dispatch loop.
|
|
139
|
+
JSON.stringify(result);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
const retries =
|
|
142
|
+
e instanceof JobFailure && e.retries !== undefined
|
|
143
|
+
? e.retries
|
|
144
|
+
: Math.max(0, job.retries - 1);
|
|
145
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
146
|
+
session.failJob(job.key, retries, message);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
session.completeAgentJob(job.key, result);
|
|
150
|
+
}
|
|
151
|
+
|
|
99
152
|
/**
|
|
100
153
|
* Run one activate-and-handle pass: activate every registered job type's
|
|
101
154
|
* currently-`Created` jobs *first* (a snapshot of the token frontier), then hand
|
|
@@ -105,6 +158,13 @@ async function runOne(
|
|
|
105
158
|
* exactly one step. That makes this the animatable unit: drive it on a timer to
|
|
106
159
|
* watch the token(s) hop task-to-task. {@link dispatchWorkers} loops it to
|
|
107
160
|
* quiescence.
|
|
161
|
+
*
|
|
162
|
+
* Ad-hoc **agent** job types registered via `opts.agents` are activated and
|
|
163
|
+
* completed in the same frontier-snapshot pass, but through
|
|
164
|
+
* {@link BojtosSession.completeAgentJob} so their {@link AgentResult} activates
|
|
165
|
+
* the chosen tools. A tool a turn activates joins the *next* frontier, and the
|
|
166
|
+
* engine re-emits the agent job after those tools drain, so the agent's whole
|
|
167
|
+
* multi-turn conversation animates one step per round like any other token.
|
|
108
168
|
*/
|
|
109
169
|
export async function dispatchRound(
|
|
110
170
|
session: BojtosSession,
|
|
@@ -114,20 +174,44 @@ export async function dispatchRound(
|
|
|
114
174
|
const maxJobs = opts.maxJobsPerActivation ?? 10;
|
|
115
175
|
const timeout = opts.lockTimeoutMs ?? 30_000;
|
|
116
176
|
const worker = opts.worker ?? "bojtos";
|
|
177
|
+
const agents = opts.agents ?? {};
|
|
178
|
+
// A job type registered as both a worker and an agent is ambiguous: the
|
|
179
|
+
// worker pass below would activate and plain-complete it first, so its
|
|
180
|
+
// agentic `activateElements` could never be sent. Reject up front rather than
|
|
181
|
+
// silently no-op the tool activation.
|
|
182
|
+
for (const jobType of Object.keys(agents)) {
|
|
183
|
+
if (jobType in workers) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`dispatchRound: job type "${jobType}" is registered as both a worker and an agent — register it as exactly one`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
117
189
|
// Activation pass: lock the whole current frontier before running any handler,
|
|
118
190
|
// so a job a handler unblocks isn't also picked up this round (which would
|
|
119
191
|
// cascade the entire chain in a single "step").
|
|
120
|
-
const
|
|
192
|
+
const jobBatch: { handler: JobHandler; job: ActivatedJob }[] = [];
|
|
121
193
|
for (const [jobType, handler] of Object.entries(workers)) {
|
|
122
194
|
for (const job of session.activateJobs(jobType, maxJobs, timeout, worker)) {
|
|
123
|
-
|
|
195
|
+
jobBatch.push({ handler, job });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const agentBatch: { handler: AgentHandler; job: ActivatedJob }[] = [];
|
|
199
|
+
for (const [jobType, handler] of Object.entries(agents)) {
|
|
200
|
+
for (const job of session.activateJobs(jobType, maxJobs, timeout, worker)) {
|
|
201
|
+
agentBatch.push({ handler, job });
|
|
124
202
|
}
|
|
125
203
|
}
|
|
126
204
|
// Handle pass.
|
|
127
|
-
for (const { handler, job } of
|
|
205
|
+
for (const { handler, job } of jobBatch) {
|
|
128
206
|
await runOne(session, handler, job);
|
|
129
207
|
}
|
|
130
|
-
|
|
208
|
+
for (const { handler, job } of agentBatch) {
|
|
209
|
+
await runOneAgent(session, handler, job);
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
snapshot: session.snapshot(),
|
|
213
|
+
handled: jobBatch.length + agentBatch.length,
|
|
214
|
+
};
|
|
131
215
|
}
|
|
132
216
|
|
|
133
217
|
/**
|