c8ctl-plugin-nano 1.41.0 → 1.42.1
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 +83 -0
- package/c8ctl-plugin.js +133 -25
- package/nanobpmn-binary.json +3 -3
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -157,6 +157,9 @@ c8ctl nano hire --name reviewer --rank senior --command copilot \
|
|
|
157
157
|
# Give the harness command-line switches (e.g. run copilot with --allow-all)
|
|
158
158
|
c8ctl nano hire --name coder --rank senior --command copilot --arg --allow-all
|
|
159
159
|
|
|
160
|
+
# Opt a role into the ACP harness mode (JSON-RPC/stdio) — see "ACP harness mode" below
|
|
161
|
+
c8ctl nano hire --name coder --rank senior --command copilot --protocol acp
|
|
162
|
+
|
|
160
163
|
# List profiles
|
|
161
164
|
c8ctl nano hire --list
|
|
162
165
|
```
|
|
@@ -409,6 +412,86 @@ bound is operator-tunable for a long expected outage; raise it with
|
|
|
409
412
|
`NANO_AGENTIC_BUFFER_CAPACITY` (frames). When the bound is hit the worker warns
|
|
410
413
|
rather than silently shedding.
|
|
411
414
|
|
|
415
|
+
### ACP harness mode (opt-in)
|
|
416
|
+
|
|
417
|
+
Alongside the `pipe`/`pty` terminal choice above, a role can opt into driving its
|
|
418
|
+
harness over the **[Agent Client Protocol (ACP)](https://agentclientprotocol.com)**
|
|
419
|
+
— JSON-RPC 2.0 over stdio — instead of the default stdin/scrape **pipe**. ACP is
|
|
420
|
+
**additive, not a switch**: `pipe` stays the default floor, so every CLI harness
|
|
421
|
+
(and non-agent record-keepers) keeps working unchanged, and **YOLO stays the
|
|
422
|
+
default** via auto-approve. You turn ACP on per role, exactly like `--terminal`:
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
# Drive a role's harness over ACP (JSON-RPC/stdio) instead of the pipe
|
|
426
|
+
c8ctl nano hire --name coder --rank senior --command copilot --protocol acp
|
|
427
|
+
|
|
428
|
+
# Override protocol/permission for a one-off worker without re-hiring
|
|
429
|
+
NANO_AGENTIC_PROTOCOL=pipe c8ctl nano work coder
|
|
430
|
+
NANO_AGENTIC_PERMISSION=escalate c8ctl nano work coder
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
- `--protocol pipe|acp` (default `pipe`) selects the harness protocol.
|
|
434
|
+
`NANO_AGENTIC_PROTOCOL` overrides it at work time (mirroring
|
|
435
|
+
`NANO_AGENTIC_TERMINAL`).
|
|
436
|
+
- `--permission yolo|escalate|filter` (default `yolo`) selects the ACP permission
|
|
437
|
+
policy. `NANO_AGENTIC_PERMISSION` overrides it at work time.
|
|
438
|
+
|
|
439
|
+
**What ACP unlocks.** Because the harness speaks a structured protocol rather than
|
|
440
|
+
a scraped terminal, the ACP path gives you a **structured turn/tool event stream**
|
|
441
|
+
(today serialized to text chunks on the relay lane — a *minimal* mode, not yet
|
|
442
|
+
typed turn/tool envelopes), **native permission handling**, and **PTY-free
|
|
443
|
+
steering** — an operator's
|
|
444
|
+
steer text is delivered as a `session/prompt` and an interrupt as a
|
|
445
|
+
`session/cancel`, with no keystroke injection. The ACP path therefore does **not**
|
|
446
|
+
need the optional native `node-pty` dependency at all.
|
|
447
|
+
|
|
448
|
+
**Permission policies — mind the status.** Only `yolo` is enforced today:
|
|
449
|
+
|
|
450
|
+
- **`yolo`** *(default, the only enforced policy today)* — auto-allows every
|
|
451
|
+
permission request the agent raises: full speed, no human in the loop. This is
|
|
452
|
+
the same auto-approve posture the pipe/PTY paths already run with.
|
|
453
|
+
- **`escalate`** and **`filter`** are **RESERVED / not-yet-active** in this build.
|
|
454
|
+
They are **accepted and persisted** for forward-compatibility, but they are
|
|
455
|
+
**not yet enforced**: at work time they fall back to a **safe interim policy**
|
|
456
|
+
(currently auto-allow, like `yolo`) and the CLI **emits a warning** so an
|
|
457
|
+
operator is never misled into thinking destructive operations are gated. Their
|
|
458
|
+
intended future behavior — **`escalate`** blocking a permission request until a
|
|
459
|
+
human answers it, and **`filter`** auto-allowing reads/edits while escalating
|
|
460
|
+
destructive operations — is **not available yet**; it lands once the companion
|
|
461
|
+
permission-event + escalation bridge (nanobpm/nano-workforce#559) ships.
|
|
462
|
+
|
|
463
|
+
**Per-CLI hire examples.** Some CLIs speak ACP natively; others ride a thin
|
|
464
|
+
adapter binary. In every case the profile's command (plus any `--arg`s) assembles
|
|
465
|
+
the ACP invocation; a default `--acp` switch is appended only when the assembled
|
|
466
|
+
command line doesn't already select ACP, so a native/adapter invocation is never
|
|
467
|
+
doubled. All of these run with the enforced default `yolo` policy:
|
|
468
|
+
|
|
469
|
+
```bash
|
|
470
|
+
# Copilot CLI — native ACP (assembles `copilot --acp`)
|
|
471
|
+
c8ctl nano hire --name coder --rank senior --command copilot --protocol acp --permission yolo
|
|
472
|
+
|
|
473
|
+
# OpenCode — native ACP server (assembles `opencode acp`)
|
|
474
|
+
c8ctl nano hire --name coder --rank senior --command opencode --arg acp --protocol acp --permission yolo
|
|
475
|
+
|
|
476
|
+
# Claude Code — via the `claude-agent-acp` adapter
|
|
477
|
+
c8ctl nano hire --name coder --rank senior --command claude-agent-acp --protocol acp --permission yolo
|
|
478
|
+
|
|
479
|
+
# Pi — via the `pi-acp` adapter
|
|
480
|
+
c8ctl nano hire --name coder --rank senior --command pi-acp --protocol acp --permission yolo
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
> `--permission yolo` is the default, so you can omit it. You **may** hire with
|
|
484
|
+
> `--permission escalate` or `--permission filter` today — the value is persisted
|
|
485
|
+
> — but it is **reserved / not-yet-active** (pending nanobpm/nano-workforce#559)
|
|
486
|
+
> and currently behaves as the safe interim policy with a warning, so do **not**
|
|
487
|
+
> rely on it to gate destructive operations yet.
|
|
488
|
+
|
|
489
|
+
**Non-goals.** ACP does not replace anything: the **pipe/PTY** surface stays the
|
|
490
|
+
default floor and every existing harness keeps working unchanged (ACP is enforced
|
|
491
|
+
on the host executor; container sandboxes remain **pipe-only** for now). There is
|
|
492
|
+
**no change to the Camunda-8 worker⇄engine job protocol** — ACP governs only how a
|
|
493
|
+
worker drives its local agent harness, not how it talks to the engine.
|
|
494
|
+
|
|
412
495
|
### Live profile reload (no restart on `assign`)
|
|
413
496
|
|
|
414
497
|
A running `c8ctl nano work <name>` **watches** the profile it is servicing. When
|
package/c8ctl-plugin.js
CHANGED
|
@@ -4026,11 +4026,15 @@ const ACP_MAX_LINE_BYTES = 8 * 1024 * 1024; // 8 MiB
|
|
|
4026
4026
|
|
|
4027
4027
|
// Drive an ACP (Agent Client Protocol) agent over JSON-RPC 2.0 on stdio.
|
|
4028
4028
|
//
|
|
4029
|
-
// This
|
|
4030
|
-
//
|
|
4031
|
-
//
|
|
4032
|
-
//
|
|
4033
|
-
//
|
|
4029
|
+
// This executor drives ACP end-to-end. Each `session/update` is mapped to a
|
|
4030
|
+
// typed `nwfTranscriptEvent` envelope (#110, step 2) and published on the relay
|
|
4031
|
+
// session's typed publish seam (`relayTap.relayEnvelope`) — the rich cockpit
|
|
4032
|
+
// format its derive+render consumes. The raw relay TRANSPORT is untouched (still
|
|
4033
|
+
// `relaySession.relay(text)`); only the payload shape on the lane changes. When
|
|
4034
|
+
// an update has no typed mapping, or the relay exposes no typed seam (minimal
|
|
4035
|
+
// mode / a plain tap), it falls back to the step-1 human-TEXT chunk on the same
|
|
4036
|
+
// lane (`relayTap.onData`) so nothing is dropped. The raw JSON-RPC is never
|
|
4037
|
+
// tee'd either way.
|
|
4034
4038
|
//
|
|
4035
4039
|
// Framing: ACP frames are newline-delimited JSON-RPC 2.0 messages on stdio (one
|
|
4036
4040
|
// compact JSON object per line, `\n`-terminated). We implement a tiny inline
|
|
@@ -4111,22 +4115,35 @@ function spawnCaptureAcp({ command, args = [], cwd, env, stdinData, timeoutMs, i
|
|
|
4111
4115
|
|
|
4112
4116
|
const humanStdout = () => joinCapped(humanChunks);
|
|
4113
4117
|
|
|
4114
|
-
//
|
|
4115
|
-
//
|
|
4116
|
-
//
|
|
4117
|
-
|
|
4118
|
+
// Local mirrors of a human text chunk: the --stream spy tee and the byte-
|
|
4119
|
+
// capped stdout capture (what the result envelope carries). Deliberately does
|
|
4120
|
+
// NOT touch the relay lane, so a typed-transcript update can mirror its human
|
|
4121
|
+
// text locally (for the result + spy) WITHOUT also re-emitting raw text onto
|
|
4122
|
+
// the relay lane — which, in step 2, carries the typed envelope instead.
|
|
4123
|
+
const captureHuman = (text) => {
|
|
4118
4124
|
if (!text) return;
|
|
4119
4125
|
const buf = Buffer.from(text, 'utf8');
|
|
4120
4126
|
if (teeSink) tee(text, false);
|
|
4121
|
-
if (relayTap && typeof relayTap.onData === 'function') {
|
|
4122
|
-
try { relayTap.onData(text); } catch { /* relay best-effort */ }
|
|
4123
|
-
}
|
|
4124
4127
|
const remaining = MAX_CAPTURE_BYTES - humanBytes;
|
|
4125
4128
|
if (remaining <= 0) { humanTruncated = true; return; }
|
|
4126
4129
|
if (buf.length > remaining) { humanChunks.push(buf.subarray(0, remaining)); humanBytes = MAX_CAPTURE_BYTES; humanTruncated = true; }
|
|
4127
4130
|
else { humanChunks.push(buf); humanBytes += buf.length; }
|
|
4128
4131
|
};
|
|
4129
4132
|
|
|
4133
|
+
// Emit a human-meaningful text chunk on the SAME lanes the pipe/pty paths
|
|
4134
|
+
// use: the relay tap (framed + jobKey-tagged by the caller) and the local
|
|
4135
|
+
// --stream spy tee. Byte-capped like the raw captures. This is the minimal-
|
|
4136
|
+
// mode text path — used for stderr, and as the fallback for any session/update
|
|
4137
|
+
// that has no typed nwfTranscriptEvent mapping (or when the relay exposes no
|
|
4138
|
+
// typed publish seam).
|
|
4139
|
+
const emitHuman = (text) => {
|
|
4140
|
+
if (!text) return;
|
|
4141
|
+
if (relayTap && typeof relayTap.onData === 'function') {
|
|
4142
|
+
try { relayTap.onData(text); } catch { /* relay best-effort */ }
|
|
4143
|
+
}
|
|
4144
|
+
captureHuman(text);
|
|
4145
|
+
};
|
|
4146
|
+
|
|
4130
4147
|
const finish = (result) => {
|
|
4131
4148
|
if (settled) return;
|
|
4132
4149
|
settled = true;
|
|
@@ -4200,25 +4217,30 @@ function spawnCaptureAcp({ command, args = [], cwd, env, stdinData, timeoutMs, i
|
|
|
4200
4217
|
}
|
|
4201
4218
|
};
|
|
4202
4219
|
|
|
4203
|
-
//
|
|
4204
|
-
//
|
|
4220
|
+
// Extract plain text from an ACP content value (string, {type,text}, or an
|
|
4221
|
+
// array of content blocks). Shared by the typed-envelope mapper and the
|
|
4222
|
+
// human-text describer so both agree on what the "text" of an update is.
|
|
4223
|
+
const acpTextOf = (content) => {
|
|
4224
|
+
if (content == null) return '';
|
|
4225
|
+
if (typeof content === 'string') return content;
|
|
4226
|
+
if (Array.isArray(content)) return content.map(acpTextOf).join('');
|
|
4227
|
+
if (typeof content === 'object') return typeof content.text === 'string' ? content.text : '';
|
|
4228
|
+
return '';
|
|
4229
|
+
};
|
|
4230
|
+
|
|
4231
|
+
// Serialise an ACP session/update into a short human-readable line. This is
|
|
4232
|
+
// the minimal-mode TEXT for the existing cockpit lane (the fallback), not a
|
|
4233
|
+
// typed envelope.
|
|
4205
4234
|
const describeUpdate = (update) => {
|
|
4206
4235
|
if (!update || typeof update !== 'object') return '';
|
|
4207
4236
|
const kind = update.sessionUpdate || update.type || 'update';
|
|
4208
|
-
const textOf = (content) => {
|
|
4209
|
-
if (content == null) return '';
|
|
4210
|
-
if (typeof content === 'string') return content;
|
|
4211
|
-
if (Array.isArray(content)) return content.map(textOf).join('');
|
|
4212
|
-
if (typeof content === 'object') return typeof content.text === 'string' ? content.text : '';
|
|
4213
|
-
return '';
|
|
4214
|
-
};
|
|
4215
4237
|
switch (kind) {
|
|
4216
4238
|
case 'agent_message_chunk':
|
|
4217
|
-
return
|
|
4239
|
+
return acpTextOf(update.content);
|
|
4218
4240
|
case 'agent_thought_chunk':
|
|
4219
|
-
return `\u{1F4AD} ${
|
|
4241
|
+
return `\u{1F4AD} ${acpTextOf(update.content)}`;
|
|
4220
4242
|
case 'user_message_chunk':
|
|
4221
|
-
return
|
|
4243
|
+
return acpTextOf(update.content);
|
|
4222
4244
|
case 'tool_call': {
|
|
4223
4245
|
const title = update.title || update.toolCallId || 'tool';
|
|
4224
4246
|
return `\u2699 [tool: ${title}${update.status ? ` — ${update.status}` : ''}]\n`;
|
|
@@ -4234,6 +4256,80 @@ function spawnCaptureAcp({ command, args = [], cwd, env, stdinData, timeoutMs, i
|
|
|
4234
4256
|
}
|
|
4235
4257
|
};
|
|
4236
4258
|
|
|
4259
|
+
// #110 step 2: map an ACP session/update to a typed `nwfTranscriptEvent`
|
|
4260
|
+
// envelope — the rich cockpit wire format (the existing downstream
|
|
4261
|
+
// derive+render consumes it). Returns null for an update kind we don't model,
|
|
4262
|
+
// so the caller falls back to the minimal human-text path (nothing dropped,
|
|
4263
|
+
// no regression vs step 1). The `text` field carries the same plain text the
|
|
4264
|
+
// fallback would relay, so a lightweight consumer can still render it.
|
|
4265
|
+
const TRANSCRIPT_EVENT_TYPE = 'nwfTranscriptEvent';
|
|
4266
|
+
const TRANSCRIPT_EVENT_VERSION = 1;
|
|
4267
|
+
const mapTranscriptEnvelope = (update) => {
|
|
4268
|
+
if (!update || typeof update !== 'object') return null;
|
|
4269
|
+
const kind = update.sessionUpdate || update.type;
|
|
4270
|
+
if (!kind) return null;
|
|
4271
|
+
const base = { type: TRANSCRIPT_EVENT_TYPE, v: TRANSCRIPT_EVENT_VERSION, ts: Date.now() };
|
|
4272
|
+
// Optional fields stay `undefined` (JSON encoding omits them) rather than
|
|
4273
|
+
// becoming explicit `null`s, and `??` preserves empty strings — so
|
|
4274
|
+
// consumers see omitted/optional strings, not coerced nulls. `status`
|
|
4275
|
+
// falls through to the kind's default only when genuinely absent.
|
|
4276
|
+
const toolOf = (u, defaultStatus) => ({
|
|
4277
|
+
id: u.toolCallId ?? undefined,
|
|
4278
|
+
title: u.title ?? undefined,
|
|
4279
|
+
status: u.status ?? defaultStatus ?? undefined,
|
|
4280
|
+
kind: u.kind ?? undefined,
|
|
4281
|
+
});
|
|
4282
|
+
switch (kind) {
|
|
4283
|
+
case 'agent_message_chunk':
|
|
4284
|
+
return { ...base, kind: 'message', role: 'agent', text: acpTextOf(update.content) };
|
|
4285
|
+
case 'agent_thought_chunk':
|
|
4286
|
+
return { ...base, kind: 'thought', role: 'agent', text: acpTextOf(update.content) };
|
|
4287
|
+
case 'user_message_chunk':
|
|
4288
|
+
return { ...base, kind: 'message', role: 'user', text: acpTextOf(update.content) };
|
|
4289
|
+
case 'tool_call':
|
|
4290
|
+
// `text` mirrors the fallback's plain text so the typed envelope stays
|
|
4291
|
+
// self-contained for lightweight renderers (matches the stated contract).
|
|
4292
|
+
return { ...base, kind: 'tool_call', text: describeUpdate(update), tool: toolOf(update, 'pending') };
|
|
4293
|
+
case 'tool_call_update':
|
|
4294
|
+
return { ...base, kind: 'tool_call_update', text: describeUpdate(update), tool: toolOf(update, undefined) };
|
|
4295
|
+
case 'plan':
|
|
4296
|
+
// Carry the actual plan entries (rich cockpit renders them), not a
|
|
4297
|
+
// count — an absent/malformed payload stays `undefined` (omitted).
|
|
4298
|
+
return { ...base, kind: 'plan', entries: Array.isArray(update.entries) ? update.entries : undefined };
|
|
4299
|
+
default:
|
|
4300
|
+
// Unmodelled kind → no typed envelope; caller uses the text fallback.
|
|
4301
|
+
return null;
|
|
4302
|
+
}
|
|
4303
|
+
};
|
|
4304
|
+
|
|
4305
|
+
// Publish a session/update: prefer the typed nwfTranscriptEvent envelope on
|
|
4306
|
+
// the relay's typed publish seam; fall back to the minimal human-text path
|
|
4307
|
+
// when the update has no typed mapping OR the relay exposes no typed seam, so
|
|
4308
|
+
// nothing is ever dropped (no regression vs minimal mode).
|
|
4309
|
+
const emitTranscript = (update) => {
|
|
4310
|
+
const env = mapTranscriptEnvelope(update);
|
|
4311
|
+
if (env && relayTap && typeof relayTap.relayEnvelope === 'function') {
|
|
4312
|
+
// Only skip the text fallback when the typed publish ACTUALLY succeeded.
|
|
4313
|
+
// If the seam throws (a downstream tap implementation, not just the
|
|
4314
|
+
// built-in best-effort stringify guard), the envelope never reached the
|
|
4315
|
+
// relay lane — so we must fall through to the text path or that update
|
|
4316
|
+
// would be silently dropped, breaking the "nothing is ever dropped"
|
|
4317
|
+
// guarantee.
|
|
4318
|
+
let published = false;
|
|
4319
|
+
try { relayTap.relayEnvelope(env); published = true; } catch { /* relay best-effort */ }
|
|
4320
|
+
if (published) {
|
|
4321
|
+
// Mirror the human text locally (spy tee + captured stdout) so the
|
|
4322
|
+
// result envelope and --stream spy are unchanged — without re-emitting
|
|
4323
|
+
// raw text onto the relay lane, which now carries the typed envelope.
|
|
4324
|
+
captureHuman(describeUpdate(update));
|
|
4325
|
+
return;
|
|
4326
|
+
}
|
|
4327
|
+
// Typed publish threw → fall through to the text lane below.
|
|
4328
|
+
}
|
|
4329
|
+
// Fallback: minimal text-chunk path (relay text + spy tee + capture).
|
|
4330
|
+
emitHuman(describeUpdate(update));
|
|
4331
|
+
};
|
|
4332
|
+
|
|
4237
4333
|
const handleMessage = (msg) => {
|
|
4238
4334
|
if (!msg || typeof msg !== 'object') return;
|
|
4239
4335
|
// A response to one of OUR requests.
|
|
@@ -4248,7 +4344,7 @@ function spawnCaptureAcp({ command, args = [], cwd, env, stdinData, timeoutMs, i
|
|
|
4248
4344
|
}
|
|
4249
4345
|
// A request or notification FROM the agent.
|
|
4250
4346
|
if (typeof msg.method === 'string') {
|
|
4251
|
-
if (msg.method === 'session/update') {
|
|
4347
|
+
if (msg.method === 'session/update') { emitTranscript(msg.params?.update); return; }
|
|
4252
4348
|
if (msg.method === 'session/request_permission') {
|
|
4253
4349
|
if (msg.id !== undefined) handlePermission(msg.id, msg.params);
|
|
4254
4350
|
return;
|
|
@@ -4590,6 +4686,18 @@ function runAgentJob(profile, job, opts = {}) {
|
|
|
4590
4686
|
const relayTap = relaySession
|
|
4591
4687
|
? {
|
|
4592
4688
|
onData: (buf) => relaySession.relay(buf),
|
|
4689
|
+
// #110 step 2: typed transcript publish seam. The ACP producer maps each
|
|
4690
|
+
// session/update to an `nwfTranscriptEvent` envelope and publishes it
|
|
4691
|
+
// here; we JSON-encode it (newline-delimited) onto the SAME relay lane —
|
|
4692
|
+
// the raw relay TRANSPORT (ring/QoS/offsets/jobKey routing) is unchanged,
|
|
4693
|
+
// still `relaySession.relay(text)`. Consumers (cockpit derive+render)
|
|
4694
|
+
// parse the envelope; unmapped updates fall back to the `onData` text path
|
|
4695
|
+
// so nothing is dropped (no regression vs the minimal-mode floor).
|
|
4696
|
+
// Best-effort: a bad envelope (circular refs / BigInt making
|
|
4697
|
+
// JSON.stringify throw) must never crash the worker, so swallow here.
|
|
4698
|
+
relayEnvelope: (env) => {
|
|
4699
|
+
try { relaySession.relay(`${JSON.stringify(env)}\n`); } catch { /* relay best-effort */ }
|
|
4700
|
+
},
|
|
4593
4701
|
attachSteer: (write) => relaySession.attachSteer(write),
|
|
4594
4702
|
}
|
|
4595
4703
|
: null;
|
package/nanobpmn-binary.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c8ctl-plugin-nano",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.42.1",
|
|
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.
|
|
61
|
-
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.
|
|
62
|
-
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.
|
|
63
|
-
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.
|
|
64
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.
|
|
65
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.
|
|
66
|
-
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.
|
|
60
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.42.1",
|
|
61
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.42.1",
|
|
62
|
+
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.42.1",
|
|
63
|
+
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.42.1",
|
|
64
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.42.1",
|
|
65
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.42.1",
|
|
66
|
+
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.42.1"
|
|
67
67
|
}
|
|
68
68
|
}
|