c8ctl-plugin-nano 1.14.1 → 1.16.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 +12 -0
- package/c8ctl-plugin.js +55 -38
- package/package.json +8 -8
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
|
@@ -120,15 +120,6 @@ const PROCESSOS_STATE_FILE = 'processos.json';
|
|
|
120
120
|
const PROCESSOS_DEFAULT_PORT = 8090;
|
|
121
121
|
const DEFAULT_NANO_URL = 'http://localhost:8080';
|
|
122
122
|
|
|
123
|
-
// Guided-journey deep links (ADR 0049 §2). The console renders a first-run tour
|
|
124
|
-
// chosen by `…/console?tour=<journeyId>`, and the *command the user ran* already
|
|
125
|
-
// encodes the persona — so the CLI bakes the journey into the console URL it
|
|
126
|
-
// prints rather than making the console guess. These ids are a contract with the
|
|
127
|
-
// console (`console/src/lib/tour`): an unknown id is silently ignored, so they
|
|
128
|
-
// are asserted verbatim in the tests.
|
|
129
|
-
const JOURNEY_LOCALDEV = 'localdev';
|
|
130
|
-
const JOURNEY_AGENTIC_AUTHOR = 'agentic-author';
|
|
131
|
-
|
|
132
123
|
// Passive update notifier (npm-style): refresh the latest published version
|
|
133
124
|
// from the registry in a detached background process at most once per day, and
|
|
134
125
|
// surface a one-line "update available" notice at most once per day. Never
|
|
@@ -459,27 +450,15 @@ function liveNodeCount(state) {
|
|
|
459
450
|
}
|
|
460
451
|
|
|
461
452
|
/**
|
|
462
|
-
* Compose a web-console URL for a node's base URL
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
return journey ? `${base}?tour=${encodeURIComponent(journey)}` : base;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* Base URL of the running cluster the user would actually open: the first
|
|
473
|
-
* still-alive node, falling back to the first recorded node, then to the default
|
|
474
|
-
* gateway URL when no cluster has been started. Used by hire/work, which surface
|
|
475
|
-
* a console link for the agentic-SDLC journey.
|
|
453
|
+
* Compose a web-console URL for a node's base URL. Never hardcodes a port:
|
|
454
|
+
* `baseUrl` is the real address the node came up on.
|
|
455
|
+
*
|
|
456
|
+
* No longer carries a `?tour=<id>` deep link: onboarding is chosen in the
|
|
457
|
+
* console's own startup persona panel (nano-bpm #464), not sprayed across every
|
|
458
|
+
* command's output.
|
|
476
459
|
*/
|
|
477
|
-
function
|
|
478
|
-
|
|
479
|
-
const alive = state.nodes.find((n) => isPidAlive(n.pid));
|
|
480
|
-
return (alive || state.nodes[0]).url;
|
|
481
|
-
}
|
|
482
|
-
return DEFAULT_NANO_URL;
|
|
460
|
+
function webConsoleUrl(baseUrl) {
|
|
461
|
+
return `${baseUrl}/console`;
|
|
483
462
|
}
|
|
484
463
|
|
|
485
464
|
/** Probe a node's always-on GET /v2/topology endpoint for reachability. */
|
|
@@ -861,7 +840,7 @@ async function printSummary(state) {
|
|
|
861
840
|
console.log(` REST API ${entry.url}/v2`);
|
|
862
841
|
console.log(` Topology ${entry.url}/v2/topology`);
|
|
863
842
|
if (hasConsole) {
|
|
864
|
-
console.log(` Web console ${webConsoleUrl(entry.url
|
|
843
|
+
console.log(` Web console ${webConsoleUrl(entry.url)}`);
|
|
865
844
|
console.log(` User guide ${entry.url}/docs`);
|
|
866
845
|
}
|
|
867
846
|
if (state.workspaceDir) {
|
|
@@ -1401,6 +1380,35 @@ function parseEnvPairs(input) {
|
|
|
1401
1380
|
return { env, errors };
|
|
1402
1381
|
}
|
|
1403
1382
|
|
|
1383
|
+
// A worker job-type token: rank/capability tokens use `:` (rank↔cap) and `+`
|
|
1384
|
+
// (combined caps) as delimiters, and code-first `@nanobpm/workflow` job types
|
|
1385
|
+
// are `<flowId>:<taskName>` or an explicit override. The first character must be
|
|
1386
|
+
// a letter, digit, or `_`; the remainder may also include `. : + -`. Mirrors the
|
|
1387
|
+
// SDK's assertJobType so a token authored on one side is accepted on the other.
|
|
1388
|
+
const JOB_TYPE_TOKEN_RE = /^[A-Za-z0-9_][A-Za-z0-9_.:+-]*$/;
|
|
1389
|
+
|
|
1390
|
+
// Parse repeatable `--job-type <token>` CLI input (string | string[]) into a
|
|
1391
|
+
// deduped, validated list of explicit job types a worker should also service,
|
|
1392
|
+
// in addition to its rank×capability matrix. Returns { jobTypes, errors }.
|
|
1393
|
+
function parseJobTypeFlags(input) {
|
|
1394
|
+
const list = input == null ? [] : (Array.isArray(input) ? input : [input]);
|
|
1395
|
+
const seen = new Set();
|
|
1396
|
+
const jobTypes = [];
|
|
1397
|
+
const errors = [];
|
|
1398
|
+
for (const item of list) {
|
|
1399
|
+
const token = String(item).trim();
|
|
1400
|
+
if (!token) { errors.push('--job-type must be a non-empty token'); continue; }
|
|
1401
|
+
if (!JOB_TYPE_TOKEN_RE.test(token)) {
|
|
1402
|
+
errors.push(`--job-type "${token}" is invalid (must match ${JOB_TYPE_TOKEN_RE.source})`);
|
|
1403
|
+
continue;
|
|
1404
|
+
}
|
|
1405
|
+
if (seen.has(token)) continue;
|
|
1406
|
+
seen.add(token);
|
|
1407
|
+
jobTypes.push(token);
|
|
1408
|
+
}
|
|
1409
|
+
return { jobTypes, errors };
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1404
1412
|
/** A profile name must be a safe, filesystem/token-friendly slug. */
|
|
1405
1413
|
function isValidProfileName(name) {
|
|
1406
1414
|
return typeof name === 'string' && /^[a-z0-9][a-z0-9._-]*$/i.test(name);
|
|
@@ -1633,7 +1641,6 @@ async function hireWorker(req, flags) {
|
|
|
1633
1641
|
if (envKeys.length > 0) logger.info(` env: ${envKeys.join(', ')}`);
|
|
1634
1642
|
logger.info(` job types (${matrix.length}): ${matrix.join(' ')}`);
|
|
1635
1643
|
logger.info(`Put it to work with: c8ctl nano work ${name}`);
|
|
1636
|
-
logger.info(`Open the console: ${webConsoleUrl(runningConsoleBaseUrl(), JOURNEY_AGENTIC_AUTHOR)}`);
|
|
1637
1644
|
}
|
|
1638
1645
|
|
|
1639
1646
|
/**
|
|
@@ -2658,6 +2665,16 @@ async function workAgent(req, flags) {
|
|
|
2658
2665
|
}
|
|
2659
2666
|
|
|
2660
2667
|
const matrix = jobTypeMatrix(profile.rank, profile.capabilities);
|
|
2668
|
+
// Optional explicit job types (repeatable `--job-type`), serviced in addition
|
|
2669
|
+
// to the rank×capability matrix. This lets a hired profile also drive a pool
|
|
2670
|
+
// keyed on a token the matrix can't express — e.g. a code-first
|
|
2671
|
+
// `@nanobpm/workflow` flow whose external task type isn't a `rank:cap` token.
|
|
2672
|
+
const { jobTypes: extraJobTypes, errors: jobTypeErrors } = parseJobTypeFlags(flags?.['job-type']);
|
|
2673
|
+
if (jobTypeErrors.length > 0) {
|
|
2674
|
+
logger.error(jobTypeErrors.join('; '));
|
|
2675
|
+
process.exit(1);
|
|
2676
|
+
}
|
|
2677
|
+
const jobTypes = [...new Set([...matrix, ...extraJobTypes])];
|
|
2661
2678
|
const camunda = globalThis.c8ctl.createClient();
|
|
2662
2679
|
|
|
2663
2680
|
logger.info(`Putting "${name}" [${profile.rank}] to work → ${profile.command}`);
|
|
@@ -2665,12 +2682,12 @@ async function workAgent(req, flags) {
|
|
|
2665
2682
|
logger.info(` sandbox: ${sandbox}${isContainer ? ` (image ${image})` : ''}`);
|
|
2666
2683
|
const profileEnvKeys = Object.keys(profileEnv);
|
|
2667
2684
|
if (profileEnvKeys.length > 0) logger.info(` harness env: ${profileEnvKeys.join(', ')}`);
|
|
2668
|
-
|
|
2685
|
+
const extraNote = extraJobTypes.length > 0 ? ` (${extraJobTypes.length} via --job-type)` : '';
|
|
2686
|
+
logger.info(` listening on ${jobTypes.length} job type(s)${extraNote}: ${jobTypes.join(' ')}`);
|
|
2669
2687
|
logger.info(` max parallel: ${maxParallelJobs}; job timeout: ${jobTimeoutMs}ms`);
|
|
2670
|
-
logger.info(` console: ${webConsoleUrl(runningConsoleBaseUrl(), JOURNEY_AGENTIC_AUTHOR)}`);
|
|
2671
2688
|
logger.info('Polling for work — press Ctrl-C to stop.');
|
|
2672
2689
|
|
|
2673
|
-
const workers =
|
|
2690
|
+
const workers = jobTypes.map((jobType) =>
|
|
2674
2691
|
camunda.createJobWorker({
|
|
2675
2692
|
jobType,
|
|
2676
2693
|
workerName: `${name}:${jobType}`,
|
|
@@ -4200,10 +4217,7 @@ export { resolveBinary, findBinary, launcherEnvMarkers };
|
|
|
4200
4217
|
export { buildNpmInvocation };
|
|
4201
4218
|
export {
|
|
4202
4219
|
webConsoleUrl,
|
|
4203
|
-
runningConsoleBaseUrl,
|
|
4204
4220
|
hireWorker,
|
|
4205
|
-
JOURNEY_LOCALDEV,
|
|
4206
|
-
JOURNEY_AGENTIC_AUTHOR,
|
|
4207
4221
|
};
|
|
4208
4222
|
export {
|
|
4209
4223
|
normalizeTaskEnvelope,
|
|
@@ -4236,6 +4250,7 @@ export {
|
|
|
4236
4250
|
ProvisionError,
|
|
4237
4251
|
normalizeStoredProfile,
|
|
4238
4252
|
jobTypeMatrix,
|
|
4253
|
+
parseJobTypeFlags,
|
|
4239
4254
|
AGENT_TASK_NS,
|
|
4240
4255
|
AGENT_RESULT_KEY,
|
|
4241
4256
|
RESULT_SENTINEL,
|
|
@@ -4338,6 +4353,7 @@ export const commands = {
|
|
|
4338
4353
|
list: { type: 'boolean', description: 'hire: list existing agent profiles instead of creating one' },
|
|
4339
4354
|
'max-parallel': { type: 'string', description: 'work: max concurrent jobs per worker (default 1)' },
|
|
4340
4355
|
'job-timeout': { type: 'string', description: 'work: max harness runtime per job in ms; the spawned process is killed past this (default 300000)' },
|
|
4356
|
+
'job-type': { type: 'string', multiple: true, description: 'work: extra job type to service alongside the rank×capability matrix (repeatable)' },
|
|
4341
4357
|
},
|
|
4342
4358
|
handler: async (args, flags) => {
|
|
4343
4359
|
const logger = getLogger();
|
|
@@ -4481,7 +4497,7 @@ function printUsage() {
|
|
|
4481
4497
|
console.log(' c8ctl nano config');
|
|
4482
4498
|
console.log(' c8ctl nano update [--check]');
|
|
4483
4499
|
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]');
|
|
4500
|
+
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
4501
|
console.log('');
|
|
4486
4502
|
console.log('Subcommands:');
|
|
4487
4503
|
console.log(' start Spawn an N-node local cluster wired to talk to each other on localhost');
|
|
@@ -4522,6 +4538,7 @@ function printUsage() {
|
|
|
4522
4538
|
console.log(' --env NAME=VALUE hire/work: static env var for the harness (repeatable); persisted on hire, work extends/overrides');
|
|
4523
4539
|
console.log(' --list hire: list existing agent profiles instead of creating one');
|
|
4524
4540
|
console.log(' --max-parallel <n> work: max concurrent jobs per worker (default 1)');
|
|
4541
|
+
console.log(' --job-type <token> work: extra job type to service alongside the rank×capability matrix (repeatable)');
|
|
4525
4542
|
console.log(' --job-timeout <ms> work: max harness runtime per job in ms (default 300000)');
|
|
4526
4543
|
console.log(' --secret-resolver <r> work: secret resolver for task secretRefs (host; default host)');
|
|
4527
4544
|
console.log(' --reap-age <ms> work: age before a finished agent container/workspace is reaped (default 3600000)');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c8ctl-plugin-nano",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.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.
|
|
53
|
-
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.
|
|
54
|
-
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.
|
|
55
|
-
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.
|
|
56
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.
|
|
57
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.
|
|
58
|
-
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.
|
|
52
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.16.0",
|
|
53
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.16.0",
|
|
54
|
+
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.16.0",
|
|
55
|
+
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.16.0",
|
|
56
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.16.0",
|
|
57
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.16.0",
|
|
58
|
+
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.16.0"
|
|
59
59
|
}
|
|
60
60
|
}
|