c8ctl-plugin-nano 1.14.0 → 1.15.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 CHANGED
@@ -169,6 +169,18 @@ capabilities `code-review, testing` the matrix is:
169
169
  so a BPMN service task can target a worker at any granularity by setting its job
170
170
  type to the matching token.
171
171
 
172
+ To also service a job type the matrix can't express — for example a code-first
173
+ [`@nanobpm/workflow`](https://www.npmjs.com/package/@nanobpm/workflow) flow whose
174
+ external task type is `<flowId>:<taskName>`, or any bespoke token — add one or
175
+ more `--job-type <token>` flags. They are serviced **in addition to** the
176
+ rank×capability matrix, so a single hired reviewer can drive both a model-first
177
+ `senior:pr-review` task and a code-first flow's task without re-hiring:
178
+
179
+ ```bash
180
+ c8ctl nano work reviewer --job-type convergence-loop:review-round
181
+ c8ctl nano work reviewer --job-type senior:pr-review --job-type senior:triage
182
+ ```
183
+
172
184
  ```bash
173
185
  c8ctl nano work reviewer # poll for work until Ctrl-C
174
186
  c8ctl nano work reviewer --max-parallel 2 --job-timeout 600000
package/c8ctl-plugin.js CHANGED
@@ -1401,6 +1401,35 @@ function parseEnvPairs(input) {
1401
1401
  return { env, errors };
1402
1402
  }
1403
1403
 
1404
+ // A worker job-type token: rank/capability tokens use `:` (rank↔cap) and `+`
1405
+ // (combined caps) as delimiters, and code-first `@nanobpm/workflow` job types
1406
+ // are `<flowId>:<taskName>` or an explicit override. The first character must be
1407
+ // a letter, digit, or `_`; the remainder may also include `. : + -`. Mirrors the
1408
+ // SDK's assertJobType so a token authored on one side is accepted on the other.
1409
+ const JOB_TYPE_TOKEN_RE = /^[A-Za-z0-9_][A-Za-z0-9_.:+-]*$/;
1410
+
1411
+ // Parse repeatable `--job-type <token>` CLI input (string | string[]) into a
1412
+ // deduped, validated list of explicit job types a worker should also service,
1413
+ // in addition to its rank×capability matrix. Returns { jobTypes, errors }.
1414
+ function parseJobTypeFlags(input) {
1415
+ const list = input == null ? [] : (Array.isArray(input) ? input : [input]);
1416
+ const seen = new Set();
1417
+ const jobTypes = [];
1418
+ const errors = [];
1419
+ for (const item of list) {
1420
+ const token = String(item).trim();
1421
+ if (!token) { errors.push('--job-type must be a non-empty token'); continue; }
1422
+ if (!JOB_TYPE_TOKEN_RE.test(token)) {
1423
+ errors.push(`--job-type "${token}" is invalid (must match ${JOB_TYPE_TOKEN_RE.source})`);
1424
+ continue;
1425
+ }
1426
+ if (seen.has(token)) continue;
1427
+ seen.add(token);
1428
+ jobTypes.push(token);
1429
+ }
1430
+ return { jobTypes, errors };
1431
+ }
1432
+
1404
1433
  /** A profile name must be a safe, filesystem/token-friendly slug. */
1405
1434
  function isValidProfileName(name) {
1406
1435
  return typeof name === 'string' && /^[a-z0-9][a-z0-9._-]*$/i.test(name);
@@ -2658,6 +2687,16 @@ async function workAgent(req, flags) {
2658
2687
  }
2659
2688
 
2660
2689
  const matrix = jobTypeMatrix(profile.rank, profile.capabilities);
2690
+ // Optional explicit job types (repeatable `--job-type`), serviced in addition
2691
+ // to the rank×capability matrix. This lets a hired profile also drive a pool
2692
+ // keyed on a token the matrix can't express — e.g. a code-first
2693
+ // `@nanobpm/workflow` flow whose external task type isn't a `rank:cap` token.
2694
+ const { jobTypes: extraJobTypes, errors: jobTypeErrors } = parseJobTypeFlags(flags?.['job-type']);
2695
+ if (jobTypeErrors.length > 0) {
2696
+ logger.error(jobTypeErrors.join('; '));
2697
+ process.exit(1);
2698
+ }
2699
+ const jobTypes = [...new Set([...matrix, ...extraJobTypes])];
2661
2700
  const camunda = globalThis.c8ctl.createClient();
2662
2701
 
2663
2702
  logger.info(`Putting "${name}" [${profile.rank}] to work → ${profile.command}`);
@@ -2665,12 +2704,13 @@ async function workAgent(req, flags) {
2665
2704
  logger.info(` sandbox: ${sandbox}${isContainer ? ` (image ${image})` : ''}`);
2666
2705
  const profileEnvKeys = Object.keys(profileEnv);
2667
2706
  if (profileEnvKeys.length > 0) logger.info(` harness env: ${profileEnvKeys.join(', ')}`);
2668
- logger.info(` listening on ${matrix.length} job type(s): ${matrix.join(' ')}`);
2707
+ const extraNote = extraJobTypes.length > 0 ? ` (${extraJobTypes.length} via --job-type)` : '';
2708
+ logger.info(` listening on ${jobTypes.length} job type(s)${extraNote}: ${jobTypes.join(' ')}`);
2669
2709
  logger.info(` max parallel: ${maxParallelJobs}; job timeout: ${jobTimeoutMs}ms`);
2670
2710
  logger.info(` console: ${webConsoleUrl(runningConsoleBaseUrl(), JOURNEY_AGENTIC_AUTHOR)}`);
2671
2711
  logger.info('Polling for work — press Ctrl-C to stop.');
2672
2712
 
2673
- const workers = matrix.map((jobType) =>
2713
+ const workers = jobTypes.map((jobType) =>
2674
2714
  camunda.createJobWorker({
2675
2715
  jobType,
2676
2716
  workerName: `${name}:${jobType}`,
@@ -4236,6 +4276,7 @@ export {
4236
4276
  ProvisionError,
4237
4277
  normalizeStoredProfile,
4238
4278
  jobTypeMatrix,
4279
+ parseJobTypeFlags,
4239
4280
  AGENT_TASK_NS,
4240
4281
  AGENT_RESULT_KEY,
4241
4282
  RESULT_SENTINEL,
@@ -4338,6 +4379,7 @@ export const commands = {
4338
4379
  list: { type: 'boolean', description: 'hire: list existing agent profiles instead of creating one' },
4339
4380
  'max-parallel': { type: 'string', description: 'work: max concurrent jobs per worker (default 1)' },
4340
4381
  'job-timeout': { type: 'string', description: 'work: max harness runtime per job in ms; the spawned process is killed past this (default 300000)' },
4382
+ 'job-type': { type: 'string', multiple: true, description: 'work: extra job type to service alongside the rank×capability matrix (repeatable)' },
4341
4383
  },
4342
4384
  handler: async (args, flags) => {
4343
4385
  const logger = getLogger();
@@ -4481,7 +4523,7 @@ function printUsage() {
4481
4523
  console.log(' c8ctl nano config');
4482
4524
  console.log(' c8ctl nano update [--check]');
4483
4525
  console.log(' c8ctl nano hire [--name <n>] [--rank <r>] [--command <c>] [--model <m>] [--capabilities <a,b>] [--sandbox none|docker|podman] [--image <ref>] [--env NAME=VALUE ...] [--list]');
4484
- console.log(' c8ctl nano work <profileName> [--max-parallel <n>] [--job-timeout <ms>] [--sandbox none|docker|podman] [--image <ref>] [--env NAME=VALUE ...] [--secret-resolver host] [--min-free-mb <n>] [--clone-timeout <ms>] [--keep-runs] [--stream]');
4526
+ console.log(' c8ctl nano work <profileName> [--max-parallel <n>] [--job-timeout <ms>] [--job-type <token> ...] [--sandbox none|docker|podman] [--image <ref>] [--env NAME=VALUE ...] [--secret-resolver host] [--min-free-mb <n>] [--clone-timeout <ms>] [--keep-runs] [--stream]');
4485
4527
  console.log('');
4486
4528
  console.log('Subcommands:');
4487
4529
  console.log(' start Spawn an N-node local cluster wired to talk to each other on localhost');
@@ -4522,6 +4564,7 @@ function printUsage() {
4522
4564
  console.log(' --env NAME=VALUE hire/work: static env var for the harness (repeatable); persisted on hire, work extends/overrides');
4523
4565
  console.log(' --list hire: list existing agent profiles instead of creating one');
4524
4566
  console.log(' --max-parallel <n> work: max concurrent jobs per worker (default 1)');
4567
+ console.log(' --job-type <token> work: extra job type to service alongside the rank×capability matrix (repeatable)');
4525
4568
  console.log(' --job-timeout <ms> work: max harness runtime per job in ms (default 300000)');
4526
4569
  console.log(' --secret-resolver <r> work: secret resolver for task secretRefs (host; default host)');
4527
4570
  console.log(' --reap-age <ms> work: age before a finished agent container/workspace is reaped (default 3600000)');
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.0.10",
3
- "commit": "820a1fb",
4
- "updated": "2026-07-23T06:01:00Z"
2
+ "version": "0.0.11",
3
+ "commit": "6eed16b",
4
+ "updated": "2026-07-31T02:08:36Z"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c8ctl-plugin-nano",
3
- "version": "1.14.0",
3
+ "version": "1.15.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",
@@ -49,12 +49,12 @@
49
49
  "semantic-release": "^25.0.3"
50
50
  },
51
51
  "optionalDependencies": {
52
- "@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.14.0",
53
- "@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.14.0",
54
- "@nanobpm/c8ctl-plugin-nano-linux-x64": "1.14.0",
55
- "@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.14.0",
56
- "@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.14.0",
57
- "@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.14.0",
58
- "@nanobpm/c8ctl-plugin-nano-win32-x64": "1.14.0"
52
+ "@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.15.0",
53
+ "@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.15.0",
54
+ "@nanobpm/c8ctl-plugin-nano-linux-x64": "1.15.0",
55
+ "@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.15.0",
56
+ "@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.15.0",
57
+ "@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.15.0",
58
+ "@nanobpm/c8ctl-plugin-nano-win32-x64": "1.15.0"
59
59
  }
60
60
  }