c8ctl-plugin-nano 1.50.0 → 1.52.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/c8ctl-plugin.js +178 -388
- package/package.json +11 -10
- package/supervisor-engine.mjs +76 -1
- package/supervisor.dist.js +6 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c8ctl-plugin-nano",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.52.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",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@commitlint/cli": "^20.4.1",
|
|
57
57
|
"@commitlint/config-conventional": "^20.4.1",
|
|
58
|
+
"@nanobpm/engine-wasm": "^0.8.6",
|
|
58
59
|
"@semantic-release/exec": "^7.1.0",
|
|
59
60
|
"@semantic-release/github": "^12.0.6",
|
|
60
61
|
"@types/node": "^22.20.1",
|
|
@@ -64,17 +65,17 @@
|
|
|
64
65
|
"typescript": "^5.9.3"
|
|
65
66
|
},
|
|
66
67
|
"dependencies": {
|
|
67
|
-
"@nanobpm/agentic": "^0.
|
|
68
|
-
"@nanobpm/urban-agent-client": "^0.1.
|
|
68
|
+
"@nanobpm/agentic": "^0.12.0",
|
|
69
|
+
"@nanobpm/urban-agent-client": "^0.1.12"
|
|
69
70
|
},
|
|
70
71
|
"optionalDependencies": {
|
|
71
72
|
"node-pty": "^1.0.0",
|
|
72
|
-
"@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.
|
|
73
|
-
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.
|
|
74
|
-
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.
|
|
75
|
-
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.
|
|
76
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.
|
|
77
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.
|
|
78
|
-
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.
|
|
73
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.52.0",
|
|
74
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.52.0",
|
|
75
|
+
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.52.0",
|
|
76
|
+
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.52.0",
|
|
77
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.52.0",
|
|
78
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.52.0",
|
|
79
|
+
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.52.0"
|
|
79
80
|
}
|
|
80
81
|
}
|
package/supervisor-engine.mjs
CHANGED
|
@@ -57,6 +57,18 @@ function buildHeaders({ token, authHeaders }) {
|
|
|
57
57
|
return headers;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* True only for a plain object map (rejects `null`, arrays, and exotic objects
|
|
62
|
+
* like `Date`/`Map`). The engine expects `variables`/`customHeaders` to be a
|
|
63
|
+
* key/value map, so anything else is dropped rather than POSTed as a malformed
|
|
64
|
+
* body or surfaced to the runner as an unexpected shape.
|
|
65
|
+
*/
|
|
66
|
+
function isPlainObjectMap(v) {
|
|
67
|
+
if (v === null || typeof v !== "object" || Array.isArray(v)) return false;
|
|
68
|
+
const proto = Object.getPrototypeOf(v);
|
|
69
|
+
return proto === Object.prototype || proto === null;
|
|
70
|
+
}
|
|
71
|
+
|
|
60
72
|
/**
|
|
61
73
|
* Map one raw v2 activated-job record to the port's {@link ActivatedJob}. Keys are
|
|
62
74
|
* strings in v2. A record missing `jobKey`/`type` violates the `ActivatedJob`
|
|
@@ -86,6 +98,22 @@ function mapJob(raw) {
|
|
|
86
98
|
const pdk = raw.processDefinitionKey ?? raw.processDefinitionId;
|
|
87
99
|
if (pdk !== undefined && pdk !== null) job.processDefinitionKey = String(pdk);
|
|
88
100
|
if (raw.variables !== undefined) job.variables = raw.variables;
|
|
101
|
+
// Settle-path passthrough (issue #156): the runner needs the task headers (to
|
|
102
|
+
// assemble the reserved task envelope + read the `linkName="prompt"` marker),
|
|
103
|
+
// the retry count (to preserve/decrement it on a `fail`, matching the SDK job
|
|
104
|
+
// object), and the process-instance key (audit/logging). These ride opaquely
|
|
105
|
+
// to the runner exactly as the SDK job worker surfaces them.
|
|
106
|
+
if (raw.customHeaders !== undefined && raw.customHeaders !== null && isPlainObjectMap(raw.customHeaders))
|
|
107
|
+
job.customHeaders = raw.customHeaders;
|
|
108
|
+
const rawRetries = raw.retries;
|
|
109
|
+
if (rawRetries !== undefined && rawRetries !== null) {
|
|
110
|
+
const n = Number(rawRetries);
|
|
111
|
+
// Downstream treats retries as a non-negative integer (decremented on fail),
|
|
112
|
+
// so normalize floats/negatives rather than pass a fractional/negative count.
|
|
113
|
+
if (Number.isFinite(n)) job.retries = Math.max(0, Math.trunc(n));
|
|
114
|
+
}
|
|
115
|
+
const pik = raw.processInstanceKey;
|
|
116
|
+
if (pik !== undefined && pik !== null) job.processInstanceKey = String(pik);
|
|
89
117
|
return job;
|
|
90
118
|
}
|
|
91
119
|
|
|
@@ -108,7 +136,7 @@ async function readErrorBody(res) {
|
|
|
108
136
|
* @param {Record<string,string>|(() => (Record<string,string>|Promise<Record<string,string>>))} [opts.authHeaders] Ready-made auth header map, OR a resolver invoked per request (so rotating SDK auth — e.g. an OAuth bearer that refreshes — is re-derived each call rather than frozen). Wins over `token`.
|
|
109
137
|
* @param {typeof fetch} [opts.fetchImpl] Injected `fetch` (defaults to the global; overridden in tests).
|
|
110
138
|
* @param {number} [opts.requestTimeoutSlackMs] Extra ms added to a call's abort budget over its server long-poll (default 5000).
|
|
111
|
-
* @returns {{ activate(req: ActivateRequest): Promise<ReadonlyArray<ActivatedJob>>, extendLock(jobKey: string, ms: number): Promise<void> }}
|
|
139
|
+
* @returns {{ activate(req: ActivateRequest): Promise<ReadonlyArray<ActivatedJob>>, extendLock(jobKey: string, ms: number): Promise<void>, complete(jobKey: string, variables?: object): Promise<void>, fail(jobKey: string, opts?: { retries?: number, errorMessage?: string, retryBackOff?: number, variables?: object }): Promise<void> }}
|
|
112
140
|
*/
|
|
113
141
|
export function createRawEngineClient(opts = {}) {
|
|
114
142
|
const {
|
|
@@ -189,5 +217,52 @@ export function createRawEngineClient(opts = {}) {
|
|
|
189
217
|
throw new Error(`extendLock ${jobKey}: HTTP ${status} from ${url}${res ? await readErrorBody(res) : ""}`);
|
|
190
218
|
}
|
|
191
219
|
},
|
|
220
|
+
|
|
221
|
+
// ---- Job completion / failure ------------------------------------------
|
|
222
|
+
// The narrow surface the supervisor's JobRunner needs to SETTLE a job once
|
|
223
|
+
// its agent harness finishes — the direct-REST analogue of the SDK job
|
|
224
|
+
// object's `job.complete()` / `job.fail()` (which the per-type SDK poller
|
|
225
|
+
// path in `workAgent` still uses). A plain `ActivatedJob` (jobKey/type/
|
|
226
|
+
// variables) carries no settle methods, so the supervisor path completes via
|
|
227
|
+
// these calls instead. Effect-free and wire-testable like `activate` /
|
|
228
|
+
// `extendLock`; a non-2xx (e.g. a 409 when the lock already lapsed and the
|
|
229
|
+
// job was reclaimed) surfaces as a rejected promise for the port to map.
|
|
230
|
+
|
|
231
|
+
async complete(jobKey, variables) {
|
|
232
|
+
// `POST <base>/v2/jobs/{jobKey}/completion` with `{ variables }` — the
|
|
233
|
+
// result-variable map the model produced is merged onto the process
|
|
234
|
+
// instance. C8 v2 answers 204 No Content on success.
|
|
235
|
+
const url = `${base}/jobs/${encodeURIComponent(jobKey)}/completion`;
|
|
236
|
+
const body = JSON.stringify(isPlainObjectMap(variables) ? { variables } : {});
|
|
237
|
+
const res = await call(url, { method: "POST", body }, 15_000);
|
|
238
|
+
if (!res || !res.ok) {
|
|
239
|
+
const status = res ? res.status : "?";
|
|
240
|
+
throw new Error(`complete ${jobKey}: HTTP ${status} from ${url}${res ? await readErrorBody(res) : ""}`);
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
async fail(jobKey, opts) {
|
|
245
|
+
// Tolerate a `null` opts the same as `undefined` (mirrors `complete`'s
|
|
246
|
+
// null-safe `variables`), so a caller passing `null` never trips the
|
|
247
|
+
// signature-destructure TypeError.
|
|
248
|
+
const { retries = 0, errorMessage, retryBackOff, variables } = opts || {};
|
|
249
|
+
// `POST <base>/v2/jobs/{jobKey}/failure` with `{ retries, errorMessage?,
|
|
250
|
+
// retryBackOff?, variables? }` — `retries > 0` re-queues for another
|
|
251
|
+
// attempt, `retries === 0` raises an incident. Optional fields are omitted
|
|
252
|
+
// when absent so the engine applies its own defaults. C8 v2 answers 204.
|
|
253
|
+
const url = `${base}/jobs/${encodeURIComponent(jobKey)}/failure`;
|
|
254
|
+
// Normalize retries to a non-negative integer (mirrors `mapJob`), so a
|
|
255
|
+
// string/float/negative never reaches the engine as an invalid count.
|
|
256
|
+
const nRetries = Number(retries);
|
|
257
|
+
const payload = { retries: Number.isFinite(nRetries) ? Math.max(0, Math.trunc(nRetries)) : 0 };
|
|
258
|
+
if (errorMessage !== undefined && errorMessage !== null) payload.errorMessage = String(errorMessage);
|
|
259
|
+
if (Number.isFinite(retryBackOff) && retryBackOff > 0) payload.retryBackOff = retryBackOff;
|
|
260
|
+
if (isPlainObjectMap(variables)) payload.variables = variables;
|
|
261
|
+
const res = await call(url, { method: "POST", body: JSON.stringify(payload) }, 15_000);
|
|
262
|
+
if (!res || !res.ok) {
|
|
263
|
+
const status = res ? res.status : "?";
|
|
264
|
+
throw new Error(`fail ${jobKey}: HTTP ${status} from ${url}${res ? await readErrorBody(res) : ""}`);
|
|
265
|
+
}
|
|
266
|
+
},
|
|
192
267
|
};
|
|
193
268
|
}
|