c8ctl-plugin-nano 1.39.0 → 1.39.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/c8ctl-plugin.js +158 -58
- package/package.json +8 -8
package/c8ctl-plugin.js
CHANGED
|
@@ -2458,18 +2458,12 @@ function resolveBrokerRestConfig(env = process.env, opts = {}) {
|
|
|
2458
2458
|
// exposes no usable restAddress. The token same-origin gate lives in
|
|
2459
2459
|
// resolveBrokerRestConfig (re-run against the profile base), never duplicated.
|
|
2460
2460
|
function resolveAutoRestConfig(camunda, env = process.env) {
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
} catch {
|
|
2468
|
-
// ignore — degrade to the resolveBrokerRestConfig (localhost) default below
|
|
2469
|
-
}
|
|
2470
|
-
if (profileBase) return resolveBrokerRestConfig(env, { baseUrl: profileBase });
|
|
2471
|
-
}
|
|
2472
|
-
return resolveBrokerRestConfig(env);
|
|
2461
|
+
// Derive the base from the single canonical resolver (explicit override →
|
|
2462
|
+
// profile restAddress → localhost), then run it through resolveBrokerRestConfig
|
|
2463
|
+
// so the token same-origin gate stays single-sourced. resolveWorkerEngineBase
|
|
2464
|
+
// always returns a base, so opts.baseUrl always pins it here.
|
|
2465
|
+
const baseUrl = resolveWorkerEngineBase(camunda, env);
|
|
2466
|
+
return resolveBrokerRestConfig(env, { baseUrl });
|
|
2473
2467
|
}
|
|
2474
2468
|
|
|
2475
2469
|
// ---------------------------------------------------------------------------
|
|
@@ -2647,28 +2641,117 @@ function normalizeRestBase(addr) {
|
|
|
2647
2641
|
return String(addr || '').replace(/\/+$/, '').replace(/\/v2$/i, '');
|
|
2648
2642
|
}
|
|
2649
2643
|
|
|
2644
|
+
// The ONE place that answers "what engine base does this worker use?" — the
|
|
2645
|
+
// single source of truth for the worker's engine-base precedence chain:
|
|
2646
|
+
//
|
|
2647
|
+
// explicit override (NANO_REST_URL → NANO_BASE_URL → persisted cfg.nanoUrl)
|
|
2648
|
+
// → active c8ctl profile restAddress (the client that activates jobs)
|
|
2649
|
+
// → localhost default (DEFAULT_NANO_URL).
|
|
2650
|
+
//
|
|
2651
|
+
// Every worker engine reference derives from this (derivation over duplication):
|
|
2652
|
+
// the `--auto` job-type read (resolveAutoRestConfig, injecting it into the
|
|
2653
|
+
// resolveBrokerRestConfig token gate), the linked-prompt fetch
|
|
2654
|
+
// (resolveLinkedPromptSource), the `supervisor status` engine column
|
|
2655
|
+
// (workerEngine), and the agentic visibility channel base (resolveAgenticConfig).
|
|
2656
|
+
// Before this existed the chain was hand-copied at each site and the agentic copy
|
|
2657
|
+
// silently lost the profile fallback, so a profile-only remote worker degraded to
|
|
2658
|
+
// its own localhost and never enrolled (jwulf/c8ctl-plugin-nano#107, sibling of
|
|
2659
|
+
// #93/#99). Sharing this resolver fixes that by construction and removes the
|
|
2660
|
+
// drift surface. The token same-origin gate stays in resolveBrokerRestConfig,
|
|
2661
|
+
// never duplicated here.
|
|
2662
|
+
function resolveWorkerEngineBase(camunda, env = process.env) {
|
|
2663
|
+
// readConfig() swallows parse/IO errors and returns {} — never throws.
|
|
2664
|
+
const cfg = readConfig() || {};
|
|
2665
|
+
const explicit = env.NANO_REST_URL || env.NANO_BASE_URL || cfg.nanoUrl;
|
|
2666
|
+
if (explicit) return normalizeRestBase(explicit);
|
|
2667
|
+
if (camunda && typeof camunda.getConfig === 'function') {
|
|
2668
|
+
try {
|
|
2669
|
+
const profileBase = normalizeRestBase(camunda.getConfig()?.restAddress);
|
|
2670
|
+
if (profileBase) return profileBase;
|
|
2671
|
+
} catch {
|
|
2672
|
+
// degrade to the localhost default below rather than throw
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
return DEFAULT_NANO_URL;
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
// The engine authority reported in the supervisor activity marker's ENGINE
|
|
2679
|
+
// column (#99): the base the worker actually POLLS JOBS from. The SDK job worker
|
|
2680
|
+
// (`camunda.createJobWorker`) activates jobs against the client's OWN profile
|
|
2681
|
+
// restAddress — which is NOT affected by the explicit NANO_REST_URL / NANO_BASE_URL
|
|
2682
|
+
// / cfg.nanoUrl overrides that resolveWorkerEngineBase prefers for auxiliary REST
|
|
2683
|
+
// reads (linked prompts, `--auto` reads, agentic channel). Reporting an override
|
|
2684
|
+
// there would make the ENGINE column claim an engine the worker is not polling,
|
|
2685
|
+
// violating supervisorEngineCell's "engine this worker polls jobs from" contract.
|
|
2686
|
+
// So prefer the profile restAddress (the polling engine) and fall back to the
|
|
2687
|
+
// canonical resolver only when the client exposes no usable base.
|
|
2688
|
+
function resolveWorkerPollEngineBase(camunda, env = process.env) {
|
|
2689
|
+
if (camunda && typeof camunda.getConfig === 'function') {
|
|
2690
|
+
try {
|
|
2691
|
+
const profileBase = normalizeRestBase(camunda.getConfig()?.restAddress);
|
|
2692
|
+
if (profileBase) return profileBase;
|
|
2693
|
+
} catch {
|
|
2694
|
+
// degrade to the canonical resolver below rather than throw
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
return resolveWorkerEngineBase(camunda, env);
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
// The base URL for a linked-prompt fetch. A linked-resource `resourceKey` is
|
|
2701
|
+
// BROKER-LOCAL: it lives on the engine the SDK client activated this job against
|
|
2702
|
+
// — the polling engine (its OWN profile restAddress). So the prompt fetch must
|
|
2703
|
+
// target that same broker, NOT the NANO_BASE_URL / cfg.nanoUrl auxiliary-read
|
|
2704
|
+
// overrides that resolveWorkerEngineBase prefers. Those overrides steer reads
|
|
2705
|
+
// that are NOT broker-local; honoring them here makes the base drift from the
|
|
2706
|
+
// activation broker so a broker-local resourceKey 404s ("prompt resource N fetch
|
|
2707
|
+
// failed") even while job activation still succeeds. Precedence:
|
|
2708
|
+
//
|
|
2709
|
+
// explicit NANO_REST_URL (same-broker escape hatch, e.g. a caching proxy in
|
|
2710
|
+
// front of the polling engine)
|
|
2711
|
+
// → polling engine base (resolveWorkerPollEngineBase → profile restAddress)
|
|
2712
|
+
// → localhost default.
|
|
2713
|
+
//
|
|
2714
|
+
// NANO_REST_URL is retained as an explicit escape hatch, but NANO_BASE_URL /
|
|
2715
|
+
// cfg.nanoUrl are deliberately NOT — steering the prompt fetch to a different
|
|
2716
|
+
// engine than the one that issued the broker-local resourceKey is the very bug
|
|
2717
|
+
// this resolves.
|
|
2718
|
+
function resolveLinkedPromptBase(camunda, env = process.env) {
|
|
2719
|
+
const explicit = env.NANO_REST_URL;
|
|
2720
|
+
if (explicit) return normalizeRestBase(explicit);
|
|
2721
|
+
return resolveWorkerPollEngineBase(camunda, env);
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2650
2724
|
// Derive the linked-resource fetch base URL + auth headers from the SAME SDK
|
|
2651
2725
|
// client that activated the job. A linked-resource `resourceKey` is broker-local,
|
|
2652
|
-
// so prompt content must be fetched from the broker this worker
|
|
2653
|
-
//
|
|
2654
|
-
//
|
|
2655
|
-
//
|
|
2656
|
-
//
|
|
2726
|
+
// so prompt content must be fetched from the broker this worker POLLS jobs from
|
|
2727
|
+
// (the polling engine — its profile restAddress). The base comes from
|
|
2728
|
+
// resolveLinkedPromptBase (explicit NANO_REST_URL escape hatch → polling engine
|
|
2729
|
+
// base → localhost); it deliberately does NOT follow the NANO_BASE_URL /
|
|
2730
|
+
// cfg.nanoUrl auxiliary-read overrides, since those would point the fetch at an
|
|
2731
|
+
// engine that never issued this broker-local resourceKey (the cause of "prompt
|
|
2732
|
+
// resource N fetch failed"). getAuthHeaders() is guarded so an older or atypical
|
|
2733
|
+
// client runtime degrades to the legacy path rather than throw.
|
|
2734
|
+
//
|
|
2735
|
+
// The base URL is invariant for a worker's lifetime, so callers on the per-job
|
|
2736
|
+
// hot path pass the once-at-startup `resolveLinkedPromptBase` result via
|
|
2737
|
+
// `baseUrl` to skip the synchronous config.json read (existsSync + readFileSync)
|
|
2738
|
+
// that this resolver would otherwise repeat on every job; only the auth headers
|
|
2739
|
+
// (which the client may rotate) are resolved per call. When `baseUrl` is omitted
|
|
2740
|
+
// it falls back to computing the base itself, so standalone callers and tests
|
|
2741
|
+
// keep the single-argument behaviour.
|
|
2657
2742
|
//
|
|
2658
2743
|
// TODO: once c8ctl bumps @camunda8/orchestration-cluster-api to v10 (10.0.0-alpha
|
|
2659
2744
|
// exposes the typed camunda.getResourceContentBinary({resourceKey}) → Blob), drop
|
|
2660
2745
|
// this raw /content/binary fetch and call that method directly. The pinned ^9.1.0
|
|
2661
2746
|
// SDK only exposes the deprecated getResourceContent, which 406s for generic
|
|
2662
2747
|
// (Markdown) prompt resources — see camunda/orchestration-cluster-api-js.
|
|
2663
|
-
async function resolveLinkedPromptSource(camunda, env = process.env) {
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
}
|
|
2671
|
-
}
|
|
2748
|
+
async function resolveLinkedPromptSource(camunda, env = process.env, { baseUrl: preResolvedBase } = {}) {
|
|
2749
|
+
// The fetch base follows resolveLinkedPromptBase (explicit NANO_REST_URL escape
|
|
2750
|
+
// hatch → polling engine base → localhost) so it tracks the broker the job was
|
|
2751
|
+
// activated against — the resourceKey is broker-local. A caller-supplied
|
|
2752
|
+
// pre-resolved base (already normalized at worker startup) is reused verbatim to
|
|
2753
|
+
// avoid a per-job config.json read.
|
|
2754
|
+
const baseUrl = preResolvedBase ?? resolveLinkedPromptBase(camunda, env);
|
|
2672
2755
|
let authHeaders;
|
|
2673
2756
|
if (env.NANO_REST_TOKEN) {
|
|
2674
2757
|
authHeaders = { Authorization: `Bearer ${env.NANO_REST_TOKEN}` };
|
|
@@ -4083,13 +4166,19 @@ function buildResultEnvelope(result, { sandbox, image, git, result: agentResult,
|
|
|
4083
4166
|
* secret is ignored (LOCAL mode).
|
|
4084
4167
|
* - OFF: NANO_AGENTIC=off (or 0/false/no), or persisted `agentic:false`.
|
|
4085
4168
|
*
|
|
4086
|
-
* Env wins over persisted config;
|
|
4087
|
-
* URL
|
|
4169
|
+
* Env wins over persisted config; when no explicit agentic target is set the base
|
|
4170
|
+
* URL defers to the shared resolveWorkerEngineBase (explicit engine override →
|
|
4171
|
+
* active c8ctl profile restAddress → localhost default), so a profile-only remote
|
|
4172
|
+
* worker discovers the agentic channel of the engine its jobs actually run on
|
|
4173
|
+
* rather than its own loopback (jwulf/c8ctl-plugin-nano#107). It is never empty.
|
|
4088
4174
|
* Returns `null` only when disabled (the off-switch).
|
|
4089
4175
|
*
|
|
4176
|
+
* @param {object} [camunda] the SDK client that activates jobs — its
|
|
4177
|
+
* getConfig().restAddress supplies the profile engine base when no explicit
|
|
4178
|
+
* agentic/engine override is set. Threaded from the `work` call site.
|
|
4090
4179
|
* @returns {{ url: string, token: string, credential: string, bufferCapacity: number, secure: boolean, explicitUrl: boolean } | null}
|
|
4091
4180
|
*/
|
|
4092
|
-
function resolveAgenticConfig() {
|
|
4181
|
+
function resolveAgenticConfig(camunda) {
|
|
4093
4182
|
const cfg = readConfig();
|
|
4094
4183
|
// Explicit off-switch (env wins). Lets an operator fully opt out of visibility.
|
|
4095
4184
|
const offSetting = process.env.NANO_AGENTIC
|
|
@@ -4098,14 +4187,14 @@ function resolveAgenticConfig() {
|
|
|
4098
4187
|
|
|
4099
4188
|
// An explicit agentic target (env NANO_AGENTIC_URL or persisted `agenticUrl`)
|
|
4100
4189
|
// is used verbatim and short-circuits hub auto-discovery (#75). When neither is
|
|
4101
|
-
// set the
|
|
4102
|
-
//
|
|
4190
|
+
// set the base defers to the shared worker-engine resolver (explicit engine
|
|
4191
|
+
// override → profile restAddress → localhost default) — NOT a re-inlined
|
|
4192
|
+
// nanoUrl → NANO_BASE_URL → default chain — so agentic discovery targets the
|
|
4193
|
+
// same engine the worker's jobs run on (jwulf/c8ctl-plugin-nano#107).
|
|
4103
4194
|
const explicitUrl = !!(process.env.NANO_AGENTIC_URL || cfg.agenticUrl);
|
|
4104
4195
|
const url = process.env.NANO_AGENTIC_URL
|
|
4105
4196
|
|| cfg.agenticUrl
|
|
4106
|
-
||
|
|
4107
|
-
|| process.env.NANO_BASE_URL
|
|
4108
|
-
|| DEFAULT_NANO_URL;
|
|
4197
|
+
|| resolveWorkerEngineBase(camunda);
|
|
4109
4198
|
// SECURE-mode shared secret. Named NANO_AGENTIC_SECRET to match the server's env
|
|
4110
4199
|
// var EXACTLY (Tab A → Slot A): set the same name + value on the server and every
|
|
4111
4200
|
// worker box. The worker presents it as its identity token; the hub verifies it
|
|
@@ -4349,11 +4438,11 @@ async function discoverAgenticHubs(engineBaseUrl, {
|
|
|
4349
4438
|
* no projects API / not a nano engine, or discovery error/timeout). The
|
|
4350
4439
|
* worker continues doing real work with no channel.
|
|
4351
4440
|
*
|
|
4352
|
-
* @param {{ fetchImpl?: Function, wsProbe?: Function, timeoutMs?: number }} [opts]
|
|
4441
|
+
* @param {{ camunda?: object, fetchImpl?: Function, wsProbe?: Function, timeoutMs?: number }} [opts]
|
|
4353
4442
|
* @returns {Promise<{ status: string, config?: object, message?: string, candidates?: Array }>}
|
|
4354
4443
|
*/
|
|
4355
|
-
async function resolveAgenticTarget(opts = {}) {
|
|
4356
|
-
const base = resolveAgenticConfig();
|
|
4444
|
+
async function resolveAgenticTarget({ camunda, ...opts } = {}) {
|
|
4445
|
+
const base = resolveAgenticConfig(camunda);
|
|
4357
4446
|
if (!base) return { status: 'off' };
|
|
4358
4447
|
// Explicit target wins verbatim and skips discovery entirely.
|
|
4359
4448
|
if (base.explicitUrl) return { status: 'connect', config: base };
|
|
@@ -4774,24 +4863,30 @@ async function workAgent(req, flags) {
|
|
|
4774
4863
|
installParentDeathWatchdog({ parentPid: Number.isInteger(daemonPid) ? daemonPid : undefined });
|
|
4775
4864
|
}
|
|
4776
4865
|
const activeJobs = new Map(); // jobKey -> { type, since (ms epoch) }
|
|
4777
|
-
// Which engine this worker polls jobs from
|
|
4778
|
-
//
|
|
4779
|
-
//
|
|
4780
|
-
//
|
|
4781
|
-
//
|
|
4782
|
-
//
|
|
4783
|
-
//
|
|
4784
|
-
//
|
|
4785
|
-
|
|
4786
|
-
//
|
|
4787
|
-
//
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4866
|
+
// Which engine this worker polls jobs from, surfaced to `supervisor status` via
|
|
4867
|
+
// the activity marker (#99). Derived from resolveWorkerPollEngineBase — the SDK
|
|
4868
|
+
// client's OWN profile restAddress (the base createJobWorker actually activates
|
|
4869
|
+
// against), NOT the NANO_* / cfg.nanoUrl override that resolveWorkerEngineBase
|
|
4870
|
+
// prefers for auxiliary REST reads. That keeps the ENGINE column honest: it
|
|
4871
|
+
// reports the engine the worker truly polls, never an override the job worker
|
|
4872
|
+
// ignores. Falls back to the canonical resolver only when the client exposes no
|
|
4873
|
+
// usable profile base.
|
|
4874
|
+
const workerEngine = resolveWorkerPollEngineBase(camunda);
|
|
4875
|
+
// Base URL for the per-job linked-prompt fetch (issue #63). This follows
|
|
4876
|
+
// resolveLinkedPromptBase — the polling engine (profile restAddress the SDK
|
|
4877
|
+
// activates jobs against), since the linked resourceKey is BROKER-LOCAL and must
|
|
4878
|
+
// be fetched from the broker that issued it — with NANO_REST_URL as the only
|
|
4879
|
+
// explicit escape hatch. It deliberately does NOT follow the NANO_BASE_URL /
|
|
4880
|
+
// cfg.nanoUrl auxiliary-read overrides resolveWorkerEngineBase prefers: pointing
|
|
4881
|
+
// the prompt fetch at a different engine than the activation broker would 404 a
|
|
4882
|
+
// broker-local resourceKey while job activation still succeeds. Computed ONCE at
|
|
4883
|
+
// startup so the per-job hot path skips a config.json read. It coincides with
|
|
4884
|
+
// `workerEngine` (the polling engine) unless NANO_REST_URL is set.
|
|
4885
|
+
const linkedPromptBase = resolveLinkedPromptBase(camunda);
|
|
4886
|
+
// The live agentic-visibility channel status, also surfaced to `supervisor
|
|
4887
|
+
// status` via the activity marker (#99). `agenticState` starts 'starting' and
|
|
4888
|
+
// is updated once the channel target is resolved and again on each
|
|
4889
|
+
// connect/disconnect below.
|
|
4795
4890
|
let agenticState = { status: 'starting' };
|
|
4796
4891
|
const writeActivity = () => {
|
|
4797
4892
|
if (!activityFile) return;
|
|
@@ -4843,7 +4938,7 @@ async function workAgent(req, flags) {
|
|
|
4843
4938
|
// worker joins with the well-known LOCAL token and no credential; SECURE mode
|
|
4844
4939
|
// (NANO_AGENTIC_SECRET) sends a real per-peer shared secret as the identity;
|
|
4845
4940
|
// NANO_AGENTIC=off disables it (see resolveAgenticConfig).
|
|
4846
|
-
const agenticTarget = await resolveAgenticTarget({ logger });
|
|
4941
|
+
const agenticTarget = await resolveAgenticTarget({ camunda, logger });
|
|
4847
4942
|
let agenticCfg = null;
|
|
4848
4943
|
// buildAgenticUrl can throw on a malformed/unsupported explicit NANO_AGENTIC_URL.
|
|
4849
4944
|
// This is only the display URL for the activity marker, so compute it
|
|
@@ -5012,8 +5107,10 @@ async function workAgent(req, flags) {
|
|
|
5012
5107
|
try {
|
|
5013
5108
|
// Fetch the prompt from the broker the SDK client is connected to,
|
|
5014
5109
|
// deriving base URL + auth from that client (not restConfig, whose base
|
|
5015
|
-
// defaults to localhost) — the resourceKey is broker-local.
|
|
5016
|
-
|
|
5110
|
+
// defaults to localhost) — the resourceKey is broker-local. The base is
|
|
5111
|
+
// invariant, so reuse the once-at-startup linkedPromptBase and let the
|
|
5112
|
+
// resolver only compute per-job auth headers (no per-job config.json read).
|
|
5113
|
+
const promptSource = await resolveLinkedPromptSource(camunda, process.env, { baseUrl: linkedPromptBase });
|
|
5017
5114
|
const linked = await resolveLinkedPrompt(job.customHeaders ?? {}, {
|
|
5018
5115
|
baseUrl: promptSource.baseUrl || restConfig.baseUrl,
|
|
5019
5116
|
authHeaders: promptSource.authHeaders,
|
|
@@ -8852,6 +8949,9 @@ export {
|
|
|
8852
8949
|
pickLinkedResource,
|
|
8853
8950
|
resolveBrokerRestConfig,
|
|
8854
8951
|
resolveAutoRestConfig,
|
|
8952
|
+
resolveWorkerEngineBase,
|
|
8953
|
+
resolveWorkerPollEngineBase,
|
|
8954
|
+
resolveLinkedPromptBase,
|
|
8855
8955
|
resourceContentUrl,
|
|
8856
8956
|
fetchLinkedResourceContent,
|
|
8857
8957
|
resolveLinkedPrompt,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c8ctl-plugin-nano",
|
|
3
|
-
"version": "1.39.
|
|
3
|
+
"version": "1.39.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.39.
|
|
61
|
-
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.39.
|
|
62
|
-
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.39.
|
|
63
|
-
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.39.
|
|
64
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.39.
|
|
65
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.39.
|
|
66
|
-
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.39.
|
|
60
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.39.1",
|
|
61
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.39.1",
|
|
62
|
+
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.39.1",
|
|
63
|
+
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.39.1",
|
|
64
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.39.1",
|
|
65
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.39.1",
|
|
66
|
+
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.39.1"
|
|
67
67
|
}
|
|
68
68
|
}
|