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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c8ctl-plugin-nano",
3
- "version": "1.50.0",
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.11.0",
68
- "@nanobpm/urban-agent-client": "^0.1.10"
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.50.0",
73
- "@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.50.0",
74
- "@nanobpm/c8ctl-plugin-nano-linux-x64": "1.50.0",
75
- "@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.50.0",
76
- "@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.50.0",
77
- "@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.50.0",
78
- "@nanobpm/c8ctl-plugin-nano-win32-x64": "1.50.0"
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
  }
@@ -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
  }