c8ctl-plugin-nano 1.59.0 → 1.60.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/agent-instance.mjs +15 -9
- package/c8ctl-plugin.js +90 -34
- package/package.json +8 -8
- package/supervisor-engine.mjs +18 -7
package/agent-instance.mjs
CHANGED
|
@@ -34,9 +34,10 @@ const isPlainObject = (v) => v != null && typeof v === 'object' && !Array.isArra
|
|
|
34
34
|
* element carries `zeebe:agentDefinition agentType="external"`?
|
|
35
35
|
*
|
|
36
36
|
* The engine surfaces exactly this eligibility as the pair of fields it stamps on
|
|
37
|
-
* an external agent job's activation: an opaque per-activation
|
|
38
|
-
*
|
|
39
|
-
* the
|
|
37
|
+
* an external agent job's activation: an opaque per-activation lease token —
|
|
38
|
+
* `leaseToken` on the activated job (Camunda v10 ActivatedJobResult; distinct from
|
|
39
|
+
* the job's `deadline`, nanobpmn #1106) — plus the `elementInstanceKey` the
|
|
40
|
+
* AgentInstance correlates on. The engine-native `aiAgentTask`/`aiAgentSubProcess`
|
|
40
41
|
* variants auto-mint their AgentInstance and never create an activatable job, so any
|
|
41
42
|
* job a worker actually activates that carries a lease token IS an external agent
|
|
42
43
|
* job. Absence of either field means "not an external agent job" → the producer
|
|
@@ -44,7 +45,7 @@ const isPlainObject = (v) => v != null && typeof v === 'object' && !Array.isArra
|
|
|
44
45
|
*/
|
|
45
46
|
export function isExternalAgentJob(job) {
|
|
46
47
|
if (!isPlainObject(job)) return false;
|
|
47
|
-
return isNonBlank(job.
|
|
48
|
+
return isNonBlank(job.leaseToken) && isNonBlank(job.elementInstanceKey);
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
/** Best-effort provider inference from a model identifier (openai/anthropic/…). */
|
|
@@ -145,7 +146,7 @@ function contentForResult(result) {
|
|
|
145
146
|
*
|
|
146
147
|
* @param {object} opts
|
|
147
148
|
* @param {object} opts.camunda Host SDK client (createAgentInstance/updateAgentInstance).
|
|
148
|
-
* @param {object} opts.job The activated job (jobKey/
|
|
149
|
+
* @param {object} opts.job The activated job (jobKey/leaseToken/elementInstanceKey/elementId).
|
|
149
150
|
* @param {object} [opts.profile] The worker profile (model/provider seed the definition).
|
|
150
151
|
* @param {object} [opts.envelope] The normalized task envelope (task.prompt → systemPrompt).
|
|
151
152
|
* @param {object} [opts.logger] Output-mode-aware logger (warn/info/debug).
|
|
@@ -166,7 +167,12 @@ export function createAgentInstanceProducer(opts = {}) {
|
|
|
166
167
|
const classify = typeof sessionAcp?.classifyUpdate === 'function' ? sessionAcp.classifyUpdate : null;
|
|
167
168
|
|
|
168
169
|
const jobKey = job?.jobKey != null ? String(job.jobKey) : '';
|
|
169
|
-
|
|
170
|
+
// The activation lease is carried on the job as `leaseToken` (Camunda v10
|
|
171
|
+
// ActivatedJobResult). It is submitted BACK to createAgentInstance/updateAgentInstance
|
|
172
|
+
// as the request-body field `jobLease` (agent-instances.yaml) — the same opaque token,
|
|
173
|
+
// named differently on the two sides of the contract. One internal name here; the
|
|
174
|
+
// translation to `jobLease` happens only at each SDK call below.
|
|
175
|
+
const leaseToken = job?.leaseToken != null ? String(job.leaseToken) : '';
|
|
170
176
|
const elementInstanceKey = job?.elementInstanceKey != null ? String(job.elementInstanceKey) : '';
|
|
171
177
|
const elementId = job?.elementId != null ? String(job.elementId) : null;
|
|
172
178
|
|
|
@@ -212,7 +218,7 @@ export function createAgentInstanceProducer(opts = {}) {
|
|
|
212
218
|
agentInstanceKey,
|
|
213
219
|
elementInstanceKey,
|
|
214
220
|
jobKey,
|
|
215
|
-
jobLease,
|
|
221
|
+
jobLease: leaseToken,
|
|
216
222
|
history: [turn],
|
|
217
223
|
};
|
|
218
224
|
if (status) req.status = status;
|
|
@@ -321,7 +327,7 @@ export function createAgentInstanceProducer(opts = {}) {
|
|
|
321
327
|
const res = await camunda[SDK_CREATE]({
|
|
322
328
|
elementInstanceKey,
|
|
323
329
|
jobKey,
|
|
324
|
-
jobLease,
|
|
330
|
+
jobLease: leaseToken,
|
|
325
331
|
history: [configTurn],
|
|
326
332
|
});
|
|
327
333
|
agentInstanceKey =
|
|
@@ -425,7 +431,7 @@ export function createAgentInstanceProducer(opts = {}) {
|
|
|
425
431
|
agentInstanceKey,
|
|
426
432
|
elementInstanceKey,
|
|
427
433
|
jobKey,
|
|
428
|
-
jobLease,
|
|
434
|
+
jobLease: leaseToken,
|
|
429
435
|
status: 'COMPLETED',
|
|
430
436
|
});
|
|
431
437
|
});
|
package/c8ctl-plugin.js
CHANGED
|
@@ -6373,16 +6373,25 @@ function normalizeProjectApps(projects) {
|
|
|
6373
6373
|
}
|
|
6374
6374
|
|
|
6375
6375
|
/**
|
|
6376
|
-
* Probe whether
|
|
6377
|
-
*
|
|
6376
|
+
* Probe whether a `/agentic` endpoint answers a WebSocket upgrade. Connects to
|
|
6377
|
+
* `ws(s)://<host>:<port><pathPrefix>/agentic?token=…` (host defaults to
|
|
6378
6378
|
* `127.0.0.1`; a bare IPv6 literal is bracketed for the URL authority) and
|
|
6379
6379
|
* resolves `true` only if the socket opens within `timeoutMs`; a refused
|
|
6380
|
-
* connection,
|
|
6381
|
-
*
|
|
6382
|
-
* outcome is known. Never throws.
|
|
6380
|
+
* connection, a `404`/`501`, or a timeout all resolve `false`. Self-cleaning —
|
|
6381
|
+
* the probe socket is closed as soon as the outcome is known. Never throws.
|
|
6383
6382
|
*
|
|
6384
|
-
*
|
|
6385
|
-
*
|
|
6383
|
+
* `pathPrefix` targets either the app's own port (empty prefix → `/agentic`) or
|
|
6384
|
+
* the engine's console **app-view WebSocket tunnel** (engine #1054), where the
|
|
6385
|
+
* prefix is `/console/app-view/<project>` so the channel rides the single engine
|
|
6386
|
+
* port instead of the app's direct port (see {@link discoverAgenticHubs}).
|
|
6387
|
+
*
|
|
6388
|
+
* `secure` selects the WS scheme: `false` → `ws://` (plain), `true` → `wss://`.
|
|
6389
|
+
* The tunnel leg rides the engine's own port, so an `https://` engine base
|
|
6390
|
+
* requires a `wss://` upgrade — probing it with plain `ws://` would spuriously
|
|
6391
|
+
* fail the tunnel and force the direct-port fallback (see {@link discoverAgenticHubs}).
|
|
6392
|
+
*
|
|
6393
|
+
* @param {number|string} port the port the WS connects to (app port, or engine port for the tunnel)
|
|
6394
|
+
* @param {{ host?: string, token?: string, WebSocketImpl?: Function, timeoutMs?: number, pathPrefix?: string, secure?: boolean }} [opts]
|
|
6386
6395
|
* @returns {Promise<boolean>}
|
|
6387
6396
|
*/
|
|
6388
6397
|
function probeAgenticChannel(port, {
|
|
@@ -6390,9 +6399,12 @@ function probeAgenticChannel(port, {
|
|
|
6390
6399
|
token = LOCAL_AGENTIC_TOKEN,
|
|
6391
6400
|
WebSocketImpl = globalThis.WebSocket,
|
|
6392
6401
|
timeoutMs = AGENTIC_DISCOVERY_TIMEOUT_MS,
|
|
6402
|
+
pathPrefix = '',
|
|
6403
|
+
secure = false,
|
|
6393
6404
|
} = {}) {
|
|
6394
6405
|
if (typeof WebSocketImpl !== 'function') return Promise.resolve(false);
|
|
6395
|
-
const
|
|
6406
|
+
const wsScheme = secure ? 'wss' : 'ws';
|
|
6407
|
+
const url = `${wsScheme}://${wsHostPart(host)}:${port}${pathPrefix}/agentic?token=${encodeURIComponent(token)}`;
|
|
6396
6408
|
return new Promise((resolve) => {
|
|
6397
6409
|
let done = false;
|
|
6398
6410
|
let ws;
|
|
@@ -6499,8 +6511,8 @@ async function resolveProbeCandidates(host, { lookupImpl = dnsLookup } = {}) {
|
|
|
6499
6511
|
* slow-to-open one can't stall the whole probe: a fast later candidate still
|
|
6500
6512
|
* wins. Each per-host probe is bounded by `timeoutMs`. A single candidate skips
|
|
6501
6513
|
* the racing machinery entirely (unchanged legacy path).
|
|
6502
|
-
* @param {number} port
|
|
6503
|
-
* @param {{ hosts?: string[], token?: string, timeoutMs?: number, wsProbe?: Function, staggerMs?: number }} [opts]
|
|
6514
|
+
* @param {number|string} port
|
|
6515
|
+
* @param {{ hosts?: string[], token?: string, timeoutMs?: number, wsProbe?: Function, staggerMs?: number, pathPrefix?: string, secure?: boolean }} [opts]
|
|
6504
6516
|
* @returns {Promise<string|null>} the winning host, or null
|
|
6505
6517
|
*/
|
|
6506
6518
|
async function raceProbeCandidates(port, {
|
|
@@ -6509,12 +6521,14 @@ async function raceProbeCandidates(port, {
|
|
|
6509
6521
|
timeoutMs = AGENTIC_DISCOVERY_TIMEOUT_MS,
|
|
6510
6522
|
wsProbe = probeAgenticChannel,
|
|
6511
6523
|
staggerMs = 250,
|
|
6524
|
+
pathPrefix = '',
|
|
6525
|
+
secure = false,
|
|
6512
6526
|
} = {}) {
|
|
6513
6527
|
const list = Array.isArray(hosts) ? hosts.filter(Boolean) : [];
|
|
6514
6528
|
if (list.length === 0) return null;
|
|
6515
6529
|
if (list.length === 1) {
|
|
6516
6530
|
try {
|
|
6517
|
-
return (await wsProbe(port, { host: list[0], token, timeoutMs })) ? list[0] : null;
|
|
6531
|
+
return (await wsProbe(port, { host: list[0], token, timeoutMs, pathPrefix, secure })) ? list[0] : null;
|
|
6518
6532
|
} catch {
|
|
6519
6533
|
return null;
|
|
6520
6534
|
}
|
|
@@ -6531,7 +6545,7 @@ async function raceProbeCandidates(port, {
|
|
|
6531
6545
|
};
|
|
6532
6546
|
const start = (host) => {
|
|
6533
6547
|
Promise.resolve()
|
|
6534
|
-
.then(() => wsProbe(port, { host, token, timeoutMs }))
|
|
6548
|
+
.then(() => wsProbe(port, { host, token, timeoutMs, pathPrefix, secure }))
|
|
6535
6549
|
.catch(() => false)
|
|
6536
6550
|
.then((ok) => {
|
|
6537
6551
|
if (ok) done(host);
|
|
@@ -6547,23 +6561,32 @@ async function raceProbeCandidates(port, {
|
|
|
6547
6561
|
|
|
6548
6562
|
/**
|
|
6549
6563
|
* Auto-discover the embedded nwf agentic hub(s) reachable from an engine base
|
|
6550
|
-
* URL (#75, #96). Reads `GET <engine>/console/api/projects`, keeps the apps
|
|
6551
|
-
* advertise an agentic UI port, and WS-probes each app's `/agentic` **on the
|
|
6552
|
-
* engine's own host** to confirm the channel is actually served there
|
|
6553
|
-
*
|
|
6554
|
-
*
|
|
6555
|
-
*
|
|
6556
|
-
*
|
|
6557
|
-
*
|
|
6558
|
-
*
|
|
6559
|
-
*
|
|
6560
|
-
*
|
|
6561
|
-
*
|
|
6562
|
-
*
|
|
6564
|
+
* URL (#75, #96, #97). Reads `GET <engine>/console/api/projects`, keeps the apps
|
|
6565
|
+
* that advertise an agentic UI port, and WS-probes each app's `/agentic` **on the
|
|
6566
|
+
* engine's own host** to confirm the channel is actually served there. Works
|
|
6567
|
+
* cross-machine on a trusted LAN: a loopback engine probes `127.0.0.1`, a remote
|
|
6568
|
+
* engine (e.g. `merlin.local`) probes that same host — the port is taken from the
|
|
6569
|
+
* projects API but the host is always the engine's, so a rogue projects API can
|
|
6570
|
+
* never steer a probe at the worker's own loopback (#76).
|
|
6571
|
+
*
|
|
6572
|
+
* **Single-port hardening (#97, engine #1054):** each app is probed **tunnel-first** —
|
|
6573
|
+
* `ws://<engineHost>:<enginePort>/console/app-view/<project>/agentic`, the console
|
|
6574
|
+
* app-view WebSocket tunnel on the engine's *own* port (the same host:port the
|
|
6575
|
+
* worker already reached for the projects read). A surviving tunnel hub is marked
|
|
6576
|
+
* `via:'tunnel'` and needs only the engine port to be reachable — the app's direct
|
|
6577
|
+
* port need not be LAN-open. If the tunnel probe fails (an engine that predates
|
|
6578
|
+
* #1054 refuses the `/agentic` WS upgrade with `501`), it falls back to probing the
|
|
6579
|
+
* app's **direct** port and marks the hub `via:'direct'` (unchanged #96 behaviour).
|
|
6580
|
+
*
|
|
6581
|
+
* Gives the projects fetch and each WS probe INDEPENDENT deadlines (#133) so a slow
|
|
6582
|
+
* fetch can't starve the probe, prefers a routable address over a link-local `fe80::`
|
|
6583
|
+
* one (Happy-Eyeballs), and is fail-open: any error — not a nano engine (Camunda),
|
|
6584
|
+
* network failure, malformed body, or a timeout — degrades to `[]` so the worker's
|
|
6585
|
+
* real job is never blocked.
|
|
6563
6586
|
*
|
|
6564
6587
|
* @param {string} engineBaseUrl the engine base URL (e.g. `http://merlin.local:8080`)
|
|
6565
6588
|
* @param {{ token?: string, fetchImpl?: Function, wsProbe?: Function, lookupImpl?: Function, timeoutMs?: number, fetchTimeoutMs?: number, probeTimeoutMs?: number }} [opts]
|
|
6566
|
-
* @returns {Promise<Array<{ project: string, port: number, label?: string, host: string }>>}
|
|
6589
|
+
* @returns {Promise<Array<{ project: string, port: number, label?: string, host: string, via: 'tunnel'|'direct', enginePort?: string, scheme?: string }>>}
|
|
6567
6590
|
*/
|
|
6568
6591
|
async function discoverAgenticHubs(engineBaseUrl, {
|
|
6569
6592
|
token = LOCAL_AGENTIC_TOKEN,
|
|
@@ -6586,12 +6609,15 @@ async function discoverAgenticHubs(engineBaseUrl, {
|
|
|
6586
6609
|
// loopback services — which was the actual #76 concern (a rogue projects API
|
|
6587
6610
|
// making the worker probe its own localhost). So the port comes from the
|
|
6588
6611
|
// engine's projects API, but the HOST is always the engine's, never guessed.
|
|
6589
|
-
let
|
|
6612
|
+
let engineUrl;
|
|
6590
6613
|
try {
|
|
6591
|
-
|
|
6614
|
+
engineUrl = new URL(base);
|
|
6592
6615
|
} catch {
|
|
6593
6616
|
return [];
|
|
6594
6617
|
}
|
|
6618
|
+
const host = engineUrl.hostname;
|
|
6619
|
+
const engineScheme = engineUrl.protocol; // 'http:' | 'https:'
|
|
6620
|
+
const enginePort = engineUrl.port || (engineScheme === 'https:' ? '443' : '80');
|
|
6595
6621
|
const probeHost = isLoopbackHost(host) ? '127.0.0.1' : host;
|
|
6596
6622
|
// (C) Decoupled budgets (#133): the projects fetch and each WS probe get their
|
|
6597
6623
|
// OWN independent deadline. Previously they shared one 2s budget, so a fetch
|
|
@@ -6623,13 +6649,32 @@ async function discoverAgenticHubs(engineBaseUrl, {
|
|
|
6623
6649
|
const candidates = await resolveProbeCandidates(probeHost, { lookupImpl });
|
|
6624
6650
|
const settled = await Promise.all(apps.map(async (app) => {
|
|
6625
6651
|
try {
|
|
6626
|
-
|
|
6652
|
+
// Tunnel-first (#97): confirm the channel over the console app-view WS
|
|
6653
|
+
// tunnel on the ENGINE port — the same host:port the projects read just
|
|
6654
|
+
// used — so the app's direct port need not be reachable. A pre-#1054
|
|
6655
|
+
// engine 501s the upgrade and the probe fails; we then fall back to the
|
|
6656
|
+
// app's direct port (unchanged #96 path).
|
|
6657
|
+
const tunnelHost = await raceProbeCandidates(enginePort, {
|
|
6658
|
+
hosts: candidates,
|
|
6659
|
+
token,
|
|
6660
|
+
timeoutMs: probeTimeoutMs,
|
|
6661
|
+
wsProbe,
|
|
6662
|
+
pathPrefix: `/console/app-view/${encodeURIComponent(app.project)}`,
|
|
6663
|
+
// The tunnel rides the engine's own port, so match its TLS: an
|
|
6664
|
+
// `https://` engine base upgrades over `wss://`, not `ws://` (else the
|
|
6665
|
+
// tunnel probe spuriously fails and we drop to the direct port).
|
|
6666
|
+
secure: engineScheme === 'https:',
|
|
6667
|
+
});
|
|
6668
|
+
if (tunnelHost) {
|
|
6669
|
+
return { ...app, host: tunnelHost, via: 'tunnel', enginePort, scheme: engineScheme };
|
|
6670
|
+
}
|
|
6671
|
+
const directHost = await raceProbeCandidates(app.port, {
|
|
6627
6672
|
hosts: candidates,
|
|
6628
6673
|
token,
|
|
6629
6674
|
timeoutMs: probeTimeoutMs,
|
|
6630
6675
|
wsProbe,
|
|
6631
6676
|
});
|
|
6632
|
-
return
|
|
6677
|
+
return directHost ? { ...app, host: directHost, via: 'direct' } : null;
|
|
6633
6678
|
} catch {
|
|
6634
6679
|
return null;
|
|
6635
6680
|
}
|
|
@@ -6646,8 +6691,11 @@ async function discoverAgenticHubs(engineBaseUrl, {
|
|
|
6646
6691
|
* half-configured. No discovery attempted.
|
|
6647
6692
|
* - `{ status: 'connect', config }` — a target to connect to. Either the
|
|
6648
6693
|
* explicit `NANO_AGENTIC_URL`/`agenticUrl` verbatim (no discovery), or the
|
|
6649
|
-
* single discovered app
|
|
6650
|
-
*
|
|
6694
|
+
* single discovered app: the console app-view WS tunnel on the engine port
|
|
6695
|
+
* (`<engineHost>:<enginePort>/console/app-view/<project>/agentic`, `via:'tunnel'`,
|
|
6696
|
+
* #97) when available, else the direct app port
|
|
6697
|
+
* (`ws://<engineHost>:<appPort>/agentic`, `via:'direct'`, #96) — loopback for a
|
|
6698
|
+
* local engine, the engine's LAN host for a remote one.
|
|
6651
6699
|
* - `{ status: 'ambiguous', message, candidates }` — two+ apps expose a
|
|
6652
6700
|
* channel. Hard stop for the worker: it must not silently pick one.
|
|
6653
6701
|
* - `{ status: 'advisory', message }` — nothing discoverable (zero matches,
|
|
@@ -6681,10 +6729,18 @@ async function resolveAgenticTarget({ camunda, cache, ...opts } = {}) {
|
|
|
6681
6729
|
} catch { /* keep the loopback default */ }
|
|
6682
6730
|
|
|
6683
6731
|
if (hubs.length === 1) {
|
|
6684
|
-
const { project, port, host } = hubs[0];
|
|
6732
|
+
const { project, port, host, via, enginePort, scheme } = hubs[0];
|
|
6733
|
+
// A tunnel hub rides the console app-view WS bridge on the engine's own port
|
|
6734
|
+
// (#97): `<scheme>//<engineHost>:<enginePort>/console/app-view/<project>`, to
|
|
6735
|
+
// which `buildAgenticUrl` appends `/agentic`. A direct hub keeps the #96
|
|
6736
|
+
// `http://<host>:<appPort>` form. `discovered.port` stays the app's advertised
|
|
6737
|
+
// port either way (the hub identity); the local `via` records the route taken.
|
|
6738
|
+
const url = via === 'tunnel'
|
|
6739
|
+
? `${scheme}//${wsHostPart(host)}:${enginePort}/console/app-view/${encodeURIComponent(project)}`
|
|
6740
|
+
: `http://${wsHostPart(host)}:${port}`;
|
|
6685
6741
|
const config = {
|
|
6686
6742
|
...base,
|
|
6687
|
-
url
|
|
6743
|
+
url,
|
|
6688
6744
|
discovered: { project, port, host },
|
|
6689
6745
|
};
|
|
6690
6746
|
// Cache the known-good hub so a later blip self-heals from cache (#133-C).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c8ctl-plugin-nano",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.60.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "c8ctl plugin to start, inspect, and stop a local Nano BPM (nanobpmn) cluster",
|
|
6
6
|
"main": "c8ctl-plugin.js",
|
|
@@ -73,12 +73,12 @@
|
|
|
73
73
|
},
|
|
74
74
|
"optionalDependencies": {
|
|
75
75
|
"node-pty": "^1.0.0",
|
|
76
|
-
"@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.
|
|
77
|
-
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.
|
|
78
|
-
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.
|
|
79
|
-
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.
|
|
80
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.
|
|
81
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.
|
|
82
|
-
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.
|
|
76
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.60.0",
|
|
77
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.60.0",
|
|
78
|
+
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.60.0",
|
|
79
|
+
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.60.0",
|
|
80
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.60.0",
|
|
81
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.60.0",
|
|
82
|
+
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.60.0"
|
|
83
83
|
}
|
|
84
84
|
}
|
package/supervisor-engine.mjs
CHANGED
|
@@ -141,17 +141,22 @@ function mapJob(raw) {
|
|
|
141
141
|
const pik = raw.processInstanceKey;
|
|
142
142
|
if (pik !== undefined && pik !== null) job.processInstanceKey = String(pik);
|
|
143
143
|
// Engine-native AgentInstance attribution (issue #194): the element instance the
|
|
144
|
-
// AgentInstance correlates on, the element id, and the opaque per-activation
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
144
|
+
// AgentInstance correlates on, the element id, and the opaque per-activation lease
|
|
145
|
+
// token that lease-gates a `createAgentInstance` for an `external` agent job
|
|
146
|
+
// (nanobpmn #1099/#1106). The Camunda v10 REST contract carries the lease on the
|
|
147
|
+
// ACTIVATED JOB as `leaseToken` (jobs.yaml ActivatedJobResult) — NOT `jobLease`,
|
|
148
|
+
// which is the field name only on the createAgentInstance/updateAgentInstance REQUEST
|
|
149
|
+
// body (agent-instances.yaml). The harness carries ONE internal name — `leaseToken` —
|
|
150
|
+
// all the way through, and translates to `jobLease` only at that SDK body (see
|
|
151
|
+
// agent-instance.mjs). These ride opaquely to the runner exactly as the SDK activation
|
|
152
|
+
// surfaces them; absent for ordinary (non-agent) jobs, in which case the
|
|
153
|
+
// durable-transcript producer stays inert.
|
|
149
154
|
const eik = raw.elementInstanceKey;
|
|
150
155
|
if (eik !== undefined && eik !== null) job.elementInstanceKey = String(eik);
|
|
151
156
|
const eid = raw.elementId;
|
|
152
157
|
if (eid !== undefined && eid !== null) job.elementId = String(eid);
|
|
153
|
-
const lease = raw.
|
|
154
|
-
if (lease !== undefined && lease !== null) job.
|
|
158
|
+
const lease = raw.leaseToken;
|
|
159
|
+
if (lease !== undefined && lease !== null) job.leaseToken = String(lease);
|
|
155
160
|
return job;
|
|
156
161
|
}
|
|
157
162
|
|
|
@@ -254,6 +259,12 @@ export function createRawEngineClient(opts = {}) {
|
|
|
254
259
|
maxJobsToActivate: req.maxJobsToActivate,
|
|
255
260
|
timeout: req.lockMs,
|
|
256
261
|
requestTimeout: req.requestTimeoutMs,
|
|
262
|
+
// Request a per-activation lease (Camunda v10 `withLease`): the engine then
|
|
263
|
+
// stamps each activated job with a distinct opaque `leaseToken`
|
|
264
|
+
// (ActivatedJobResult.leaseToken) which lease-gates the external agent job's
|
|
265
|
+
// `createAgentInstance` (nanobpmn #1099/#1106). Without it the token is absent
|
|
266
|
+
// and the durable AgentInstance producer stays inert.
|
|
267
|
+
withLease: true,
|
|
257
268
|
};
|
|
258
269
|
// Give the abort budget slack over the server long-poll so we don't cancel
|
|
259
270
|
// a still-valid long-poll a hair before the server would answer it.
|