c8ctl-plugin-nano 1.43.1 → 1.44.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
@@ -900,9 +900,10 @@ c8ctl nano workforce stop # remove this manifest's workers (+ st
900
900
  c8ctl nano workforce remove qwen # drop an entry ("all" clears the manifest)
901
901
  ```
902
902
 
903
- Every subcommand takes `--profile <name>` (default `default`) to select which
904
- manifest it operates on, so you can keep several — `--profile review-only`,
905
- `--profile full-fleet` — side by side.
903
+ Every subcommand takes `--manifest <name>` (default `default`) to select which
904
+ manifest it operates on, so you can keep several — `--manifest review-only`,
905
+ `--manifest full-fleet` — side by side. (This flag was renamed from `--profile`,
906
+ which now collides with c8ctl's global connection-profile flag.)
906
907
 
907
908
  ### The manifest
908
909
 
@@ -991,8 +992,9 @@ hired with `--capabilities ""`.
991
992
  - `start` with an empty/absent manifest → a friendly pointer at `workforce add`,
992
993
  exit 0.
993
994
 
994
- `--json` on `list`/`status` emits machine-readable output (through the same
995
- output-mode-aware logger) so the install script and CI can consume it.
995
+ `--json` on `list`/`status` emits machine-readable output (via c8ctl's global
996
+ `--json` flag, whose parsed value is passed through to the handler) so the
997
+ install script and CI can consume it.
996
998
 
997
999
  ## Cleaning up disk
998
1000
 
package/c8ctl-plugin.js CHANGED
@@ -2588,13 +2588,64 @@ function resolveAutoRestConfig(camunda, env = process.env) {
2588
2588
  // dot-grammar.
2589
2589
  // ---------------------------------------------------------------------------
2590
2590
 
2591
+ // Legacy (pre-nano-workforce#203) agent-task marker: a service task carried the
2592
+ // agent's prompt in an `io.nanobpm.agentTask.*` `<zeebe:header>` rather than a
2593
+ // `linkName="prompt"` linked resource. The current package detector
2594
+ // (`scanTaskDefinitions`, whose `agentic` flag keys SOLELY off the linked-prompt
2595
+ // side-car) therefore reports `agentic:false` for such tasks. We keep a narrow,
2596
+ // self-contained fallback so `--auto` still discovers agent job types against an
2597
+ // engine still holding a pre-#203 deployment (issue #120 acceptance criterion:
2598
+ // "Legacy header-based BPMN still discovers correctly"). This is a supplement to
2599
+ // — never a replacement for — the package flag: the authoritative task-type /
2600
+ // process derivation still comes from `scanTaskDefinitions`; this only answers
2601
+ // "is this leaf an agent task?" for the legacy shape.
2602
+ //
2603
+ // True when `body` (a service task's inner XML) declares any
2604
+ // `io.nanobpm.agentTask*` `<zeebe:header>` key — the sole such header on a
2605
+ // pre-#203 agent service task. The regex is compiled once (it is called once
2606
+ // per matched `<serviceTask>` during `--auto` scans, so recompiling per call
2607
+ // would allocate needlessly across many deployed definitions).
2608
+ const AGENT_TASK_HEADER_RE = new RegExp(
2609
+ `<(?:\\w+:)?header\\b[^>]*\\bkey\\s*=\\s*(["'])${AGENT_TASK_NS.replace(/[.]/g, '\\.')}(?:\\.[^"']*)?\\1`,
2610
+ 'i'
2611
+ );
2612
+ function serviceTaskHasAgentHeader(body) {
2613
+ return AGENT_TASK_HEADER_RE.test(String(body || ''));
2614
+ }
2615
+
2616
+ // The set of service-task element ids in `xml` bearing the legacy agent-task
2617
+ // header. Correlates back to `scanTaskDefinitions` leaves by `elementId`, so a
2618
+ // leaf is treated as legacy-agentic only when it ALSO has a `taskDefinition type`
2619
+ // (the package only yields leaves that do) — matching the issue rule "prompt link
2620
+ // OR legacy header, AND a non-empty task type".
2621
+ function legacyAgentHeaderElementIds(xml) {
2622
+ const ids = new Set();
2623
+ const src = String(xml || '');
2624
+ // Cheap guard: a legacy agent-task header always contains the literal
2625
+ // `io.nanobpm.agentTask` namespace, so a document lacking that substring
2626
+ // cannot match — skip the full `<serviceTask>` walk entirely. This avoids
2627
+ // parsing every deployed definition in `--auto` enrolment loops when none
2628
+ // carry the legacy marker.
2629
+ if (!src.includes(AGENT_TASK_NS)) return ids;
2630
+ const taskRe =
2631
+ /<(?:\w+:)?serviceTask\b[^>]*?\bid\s*=\s*(["'])(.*?)\1[^>]*?>([\s\S]*?)<\/(?:\w+:)?serviceTask>/g;
2632
+ let m;
2633
+ while ((m = taskRe.exec(src)) !== null) {
2634
+ if (serviceTaskHasAgentHeader(m[3])) ids.add(m[2]);
2635
+ }
2636
+ return ids;
2637
+ }
2638
+
2591
2639
  // Scan one deployed BPMN document for its *agent* task-definition leaves: the
2592
2640
  // subset of `@nanobpm/agentic` `demand.scanTaskDefinitions(xml)` leaves whose
2593
2641
  // canonical `agentic` flag is set (i.e. the service task declares a
2594
- // `linkName="prompt"` linked resource). Returns `{ taskType, process }` leaves in
2595
- // first-occurrence order. The published `scanTaskDefinitions` is INJECTED so this
2596
- // stays a pure, synchronous function; `readDeployedAgentJobTypes` supplies the
2597
- // real one from the lazily-imported demand surface (`agentic.mjs`).
2642
+ // `linkName="prompt"` linked resource) OR for backward compatibility with
2643
+ // pre-#203 deployments which carry the legacy `io.nanobpm.agentTask` header.
2644
+ // Returns `{ taskType, process }` leaves in first-occurrence order; a task
2645
+ // matched by both signals is the same leaf, so it is emitted once. The published
2646
+ // `scanTaskDefinitions` is INJECTED so this stays a pure, synchronous function;
2647
+ // `readDeployedAgentJobTypes` supplies the real one from the lazily-imported
2648
+ // demand surface (`agentic.mjs`).
2598
2649
  function scanAgentTaskLeaves(xml, scanTaskDefinitions) {
2599
2650
  if (typeof scanTaskDefinitions !== 'function') {
2600
2651
  throw new TypeError(
@@ -2602,8 +2653,10 @@ function scanAgentTaskLeaves(xml, scanTaskDefinitions) {
2602
2653
  `(got ${typeof scanTaskDefinitions}); pass demand.scanTaskDefinitions from ./agentic.mjs`
2603
2654
  );
2604
2655
  }
2605
- return scanTaskDefinitions(String(xml || ''))
2606
- .filter((leaf) => leaf.agentic)
2656
+ const src = String(xml || '');
2657
+ const legacyIds = legacyAgentHeaderElementIds(src);
2658
+ return scanTaskDefinitions(src)
2659
+ .filter((leaf) => leaf.agentic || legacyIds.has(leaf.elementId))
2607
2660
  .map((leaf) => ({ taskType: leaf.taskType, process: leaf.process }));
2608
2661
  }
2609
2662
 
@@ -8709,7 +8762,7 @@ function formatWorkforceStatus(report) {
8709
8762
  const missing = entries.filter((e) => e.running < e.desired);
8710
8763
  if (missing.length > 0) {
8711
8764
  lines.push('');
8712
- lines.push(` Missing: ${missing.map((e) => `${e.profile} (${e.running}/${e.desired})`).join(', ')} — run: c8ctl nano workforce start${report.name === DEFAULT_WORKFORCE_MANIFEST ? '' : ` --profile ${report.name}`}`);
8765
+ lines.push(` Missing: ${missing.map((e) => `${e.profile} (${e.running}/${e.desired})`).join(', ')} — run: c8ctl nano workforce start${report.name === DEFAULT_WORKFORCE_MANIFEST ? '' : ` --manifest ${report.name}`}`);
8713
8766
  }
8714
8767
  if (Array.isArray(report.extra) && report.extra.length > 0) {
8715
8768
  lines.push('');
@@ -8718,16 +8771,16 @@ function formatWorkforceStatus(report) {
8718
8771
  return lines.join('\n');
8719
8772
  }
8720
8773
 
8721
- /** Resolve the manifest name from `--profile` (default `default`). Pure. */
8722
- function lastProfileValue(flags) {
8723
- // A repeated `--profile` flag arrives as a string[]; honor the last value.
8724
- let profile = flags?.profile;
8725
- if (Array.isArray(profile)) profile = profile.length ? profile[profile.length - 1] : '';
8726
- return typeof profile === 'string' ? profile.trim() : '';
8774
+ /** Resolve the manifest name from `--manifest` (default `default`). Pure. */
8775
+ function lastManifestValue(flags) {
8776
+ // A repeated `--manifest` flag arrives as a string[]; honor the last value.
8777
+ let manifest = flags?.manifest;
8778
+ if (Array.isArray(manifest)) manifest = manifest.length ? manifest[manifest.length - 1] : '';
8779
+ return typeof manifest === 'string' ? manifest.trim() : '';
8727
8780
  }
8728
8781
 
8729
8782
  function workforceManifestName(flags) {
8730
- return lastProfileValue(flags) || DEFAULT_WORKFORCE_MANIFEST;
8783
+ return lastManifestValue(flags) || DEFAULT_WORKFORCE_MANIFEST;
8731
8784
  }
8732
8785
 
8733
8786
  /** Fetch the live supervisor worker set, or `[]` when no daemon is running. */
@@ -8745,7 +8798,7 @@ async function workforceAddCmd(req, flags, manifestName) {
8745
8798
  const logger = getLogger();
8746
8799
  const profile = req.positional[1];
8747
8800
  if (!profile) {
8748
- logger.error('Usage: c8ctl nano workforce add <profile> [--instances <n>] [--auto [--auto-scope <s>] | --roles a,b,c] [--arg <flag> ...] [--profile <manifest>]');
8801
+ logger.error('Usage: c8ctl nano workforce add <profile> [--instances <n>] [--auto [--auto-scope <s>] | --roles a,b,c] [--arg <flag> ...] [--manifest <manifest>]');
8749
8802
  process.exit(1);
8750
8803
  }
8751
8804
  if (!isValidProfileName(profile)) {
@@ -8819,14 +8872,14 @@ async function workforceAddCmd(req, flags, manifestName) {
8819
8872
  manifest = upsertManifestEntry(manifest, entry);
8820
8873
  writeWorkforceManifest(manifest);
8821
8874
  logger.info(`${existed ? 'Updated' : 'Added'} "${profile}" in workforce "${manifestName}": instances ${count}, roles ${describeEntryRoles(entry)}${extraArgs.length ? `, args ${redactWorkArgs(extraArgs).join(' ')}` : ''}.`);
8822
- logger.info(`Bring it up with: c8ctl nano workforce start${manifestName === DEFAULT_WORKFORCE_MANIFEST ? '' : ` --profile ${manifestName}`}`);
8875
+ logger.info(`Bring it up with: c8ctl nano workforce start${manifestName === DEFAULT_WORKFORCE_MANIFEST ? '' : ` --manifest ${manifestName}`}`);
8823
8876
  }
8824
8877
 
8825
8878
  async function workforceRemoveCmd(req, flags, manifestName) {
8826
8879
  const logger = getLogger();
8827
8880
  const profile = req.positional[1];
8828
8881
  if (!profile) {
8829
- logger.error('Usage: c8ctl nano workforce remove <profile|all> [--profile <manifest>]');
8882
+ logger.error('Usage: c8ctl nano workforce remove <profile|all> [--manifest <manifest>]');
8830
8883
  process.exit(1);
8831
8884
  }
8832
8885
  const manifest = readWorkforceManifestStrict(manifestName);
@@ -8841,9 +8894,9 @@ async function workforceRemoveCmd(req, flags, manifestName) {
8841
8894
  async function workforceListCmd(req, flags, manifestName) {
8842
8895
  const logger = getLogger();
8843
8896
  const json = coerceBool(flags?.json, false);
8844
- const explicitProfile = lastProfileValue(flags) !== '';
8897
+ const explicitManifest = lastManifestValue(flags) !== '';
8845
8898
  const manifest = readWorkforceManifestStrict(manifestName);
8846
- const others = explicitProfile ? null : listWorkforceManifestNames();
8899
+ const others = explicitManifest ? null : listWorkforceManifestNames();
8847
8900
  if (json) {
8848
8901
  const payload = { manifest: manifest || null };
8849
8902
  if (others) payload.manifests = others;
@@ -9042,15 +9095,15 @@ async function workforceStopCmd(req, flags, manifestName) {
9042
9095
  async function workforceCommand(req, flags) {
9043
9096
  const logger = getLogger();
9044
9097
  const action = (req.positional[0] || '').toLowerCase();
9045
- // A bare `--profile` (no value) is parsed as boolean `true`; reject it so we
9098
+ // A bare `--manifest` (no value) is parsed as boolean `true`; reject it so we
9046
9099
  // fail fast instead of silently operating on the default manifest.
9047
- if (flags?.profile === true) {
9048
- logger.error('--profile requires a manifest name.');
9100
+ if (flags?.manifest === true) {
9101
+ logger.error('--manifest requires a manifest name.');
9049
9102
  process.exit(1);
9050
9103
  }
9051
9104
  const manifestName = workforceManifestName(flags);
9052
9105
  if (!isValidManifestName(manifestName)) {
9053
- logger.error(`Invalid --profile "${manifestName}". Use letters, digits, dot, dash or underscore.`);
9106
+ logger.error(`Invalid --manifest "${manifestName}". Use letters, digits, dot, dash or underscore.`);
9054
9107
  process.exit(1);
9055
9108
  }
9056
9109
  switch (action) {
@@ -10820,6 +10873,7 @@ export {
10820
10873
  diffJobTypes,
10821
10874
  parseJobTypeFlags,
10822
10875
  scanAgentTaskLeaves,
10876
+ serviceTaskHasAgentHeader,
10823
10877
  readDeployedAgentJobTypes,
10824
10878
  resolveAutoJobTypes,
10825
10879
  workAgent,
@@ -10903,7 +10957,7 @@ export {
10903
10957
  buildWorkforceStatus,
10904
10958
  formatWorkforceStatus,
10905
10959
  workforceManifestName,
10906
- lastProfileValue,
10960
+ lastManifestValue,
10907
10961
  };
10908
10962
 
10909
10963
  export const metadata = {
@@ -10961,7 +11015,7 @@ export const metadata = {
10961
11015
  { command: 'c8ctl nano workforce add copilot --instances 5 --auto', description: 'Compose a reusable fleet: 5 copilot workers serving every deployed agent job type (--auto)' },
10962
11016
  { command: 'c8ctl nano workforce add qwen --instances 2 --roles pr-review,feature', description: "Add an entry mapped to explicit job types (<rank>:pr-review, <rank>:feature, where <rank> is the qwen hire's rank at start) — does not mutate the hired profile" },
10963
11017
  { command: 'c8ctl nano workforce start', description: "Ensure the daemon is up, then reconcile running workers to the 'default' manifest (idempotent — a second run changes nothing)" },
10964
- { command: 'c8ctl nano workforce start --profile review-only', description: 'Bring up a named manifest (<stateHome>/workforce/review-only.json)' },
11018
+ { command: 'c8ctl nano workforce start --manifest review-only', description: 'Bring up a named manifest (<stateHome>/workforce/review-only.json)' },
10965
11019
  { command: 'c8ctl nano workforce status --json', description: 'Manifest entries joined against live supervisor status (desired vs actual), machine-readable for the install script / CI' },
10966
11020
  { command: 'c8ctl nano workforce list', description: 'Print the default manifest and list the manifests that exist on this machine' },
10967
11021
  { command: 'c8ctl nano workforce stop', description: "Remove this manifest's workers; stop the daemon too if no supervised workers remain" },
@@ -11036,9 +11090,8 @@ export const commands = {
11036
11090
  worker: { type: 'string', multiple: true, description: 'supervisor start: profile to launch as a supervised worker (repeatable)' },
11037
11091
  instances: { type: 'string', description: `supervisor add / workforce add: spawn/compose N distinct instances of the profile in one call (default 1, max ${MAX_ADD_INSTANCES}; for supervisor add cannot combine with --name)` },
11038
11092
  attach: { type: 'boolean', description: 'supervisor start: attach the interactive console after starting the daemon' },
11039
- profile: { type: 'string', description: `workforce: manifest name to operate on (default ${DEFAULT_WORKFORCE_MANIFEST}); each subcommand reads/writes <stateHome>/workforce/<name>.json` },
11093
+ manifest: { type: 'string', description: `workforce: manifest name to operate on (default ${DEFAULT_WORKFORCE_MANIFEST}); each subcommand reads/writes <stateHome>/workforce/<name>.json. Renamed from --profile (which now collides with c8ctl's global connection-profile flag).` },
11040
11094
  roles: { type: 'string', description: 'workforce add: comma-separated role list for the entry (→ --job-type <rank>:<role> at start); mutually exclusive with --auto' },
11041
- json: { type: 'boolean', description: 'workforce list/status: emit machine-readable JSON (for the install script / CI)' },
11042
11095
  },
11043
11096
  handler: async (args, flags) => {
11044
11097
  const logger = getLogger();
@@ -11198,7 +11251,7 @@ function printUsage() {
11198
11251
  console.log(' c8ctl nano assign <profileName> <cap[,cap...]> [--name <n>] [--capabilities <a,b>]');
11199
11252
  console.log(' c8ctl nano work <profileName> [--auto [--auto-scope <p>]] [--arg <switch> ...] [--recovery-window <ms>] [--idle-timeout <ms>] [--job-timeout <ms>] [--poll-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]');
11200
11253
  console.log(' c8ctl nano supervisor [start|status|add|remove|restart|stop|logs|attach] ... (manage many workers from one terminal)');
11201
- console.log(' c8ctl nano workforce [add|remove|list|start|status|stop] ... [--profile <manifest>] (declarative, reusable fleet manifests)');
11254
+ console.log(' c8ctl nano workforce [add|remove|list|start|status|stop] ... [--manifest <manifest>] (declarative, reusable fleet manifests)');
11202
11255
  console.log('');
11203
11256
  console.log('Subcommands:');
11204
11257
  console.log(' start Spawn an N-node local cluster wired to talk to each other on localhost');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c8ctl-plugin-nano",
3
- "version": "1.43.1",
3
+ "version": "1.44.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",
@@ -57,12 +57,12 @@
57
57
  },
58
58
  "optionalDependencies": {
59
59
  "node-pty": "^1.0.0",
60
- "@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.43.1",
61
- "@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.43.1",
62
- "@nanobpm/c8ctl-plugin-nano-linux-x64": "1.43.1",
63
- "@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.43.1",
64
- "@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.43.1",
65
- "@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.43.1",
66
- "@nanobpm/c8ctl-plugin-nano-win32-x64": "1.43.1"
60
+ "@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.44.0",
61
+ "@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.44.0",
62
+ "@nanobpm/c8ctl-plugin-nano-linux-x64": "1.44.0",
63
+ "@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.44.0",
64
+ "@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.44.0",
65
+ "@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.44.0",
66
+ "@nanobpm/c8ctl-plugin-nano-win32-x64": "1.44.0"
67
67
  }
68
68
  }