c8ctl-plugin-nano 1.33.2 → 1.34.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.
Files changed (3) hide show
  1. package/README.md +25 -10
  2. package/c8ctl-plugin.js +48 -10
  3. package/package.json +8 -8
package/README.md CHANGED
@@ -274,24 +274,39 @@ its agents' terminals to an operator's cockpit. This rides the app's agentic
274
274
  channel (ADR 0056), served **same-port** on the app's own HTTP base URL at path
275
275
  **`/agentic`** — not a sidecar, so there's no extra port to open.
276
276
 
277
- **Connecting.** Enrolment is opt-in: a worker only connects when it has both an
278
- ADR 0028 **identity token** and a **capability credential** (the same `?token=…`
279
- pattern the blackboard uses). Point it at the app and hand it the two secrets via
280
- env (or persisted config); the channel URL defaults to the configured nano URL:
277
+ **Connecting on by default (local-first).** Nano is designed for local use, so
278
+ visibility is **on by default**. Run a worker against a local app and it appears
279
+ live with **zero configuration** it joins the channel with a well-known
280
+ localhost token in **LOCAL mode** (no credential). The worker presents this
281
+ token to whatever `NANO_AGENTIC_URL` you point it at; the same-machine
282
+ restriction is enforced by the **hub**, which only honours the well-known LOCAL
283
+ token for local/loopback connections:
281
284
 
282
285
  ```bash
283
- # Enrol this worker on the app's same-port /agentic channel
286
+ # LOCAL mode (default): appears live with no secrets
284
287
  export NANO_AGENTIC_URL=http://localhost:8080 # app base URL; channel is served at /agentic
288
+ c8ctl nano work reviewer
289
+ # agentic channel (local): announcing presence as ‹worker› on ws://localhost:8080/agentic
290
+ ```
291
+
292
+ **Secure mode (opt-in).** For a shared/remote deployment, enrol the worker with an
293
+ ADR 0028 **identity token** and a **capability credential** (the same `?token=…`
294
+ pattern the blackboard uses). Setting either switches the worker into SECURE mode,
295
+ which requires **both** (fail closed if only one is set):
296
+
297
+ ```bash
298
+ # SECURE mode: real enrolment
299
+ export NANO_AGENTIC_URL=http://localhost:8080
285
300
  export NANO_AGENTIC_TOKEN=<identity-token> # ADR 0028 identity
286
301
  export NANO_AGENTIC_CREDENTIAL=<capability-cred> # capability credential
287
302
  c8ctl nano work reviewer
288
- # agentic channel: announcing presence as ‹worker› on ws://localhost:8080/agentic
303
+ # agentic channel (secure): announcing presence as ‹worker› on ws://localhost:8080/agentic
289
304
  ```
290
305
 
291
- Without both secrets the worker runs **exactly as before, off the channel** no
292
- visibility, no relay, nothing else changes. A valid identity + capability
293
- connects; an invalid identity is rejected (unauthorized) and a missing capability
294
- is rejected (forbidden).
306
+ To opt out entirely, set `NANO_AGENTIC=off` (or persisted `agentic: false`)the
307
+ worker then runs with **no visibility, no relay, nothing else changed**. In secure
308
+ mode a valid identity + capability connects; an invalid identity is rejected
309
+ (unauthorized) and a missing capability is rejected (forbidden).
295
310
 
296
311
  **How presence appears.** On connect the worker **announces** its identity, its
297
312
  `host`, and the set of `jobKeys` it is currently running, then **heartbeats** to
package/c8ctl-plugin.js CHANGED
@@ -135,6 +135,16 @@ const SUPERVISOR_STATE_FILE = 'supervisor.json';
135
135
  const PROCESSOS_DEFAULT_PORT = 8090;
136
136
  const DEFAULT_NANO_URL = 'http://localhost:8080';
137
137
 
138
+ // The well-known identity token used for LOCAL agentic visibility (security opt-in). Nano is
139
+ // local-first: on the operator's own machine a `nano work` worker joins the visibility channel with
140
+ // zero configuration, so it presents this constant, well-known localhost token — NOT a secret. The
141
+ // worker does not enforce any loopback restriction itself (it presents this token to whatever
142
+ // NANO_AGENTIC_URL is configured); same-machine gating is enforced by the hub, which only honours
143
+ // this well-known token for local/loopback connections. Kept in lock-step with the hub constant in
144
+ // nanobpm/nano-workforce (`app/agentic/channel.ts` LOCAL_AGENTIC_TOKEN). In secure mode (a real
145
+ // NANO_AGENTIC_TOKEN + NANO_AGENTIC_CREDENTIAL) this is never used.
146
+ const LOCAL_AGENTIC_TOKEN = 'nano-local';
147
+
138
148
  // Passive update notifier (npm-style): refresh the latest published version
139
149
  // from the registry in a detached background process at most once per day, and
140
150
  // surface a one-line "update available" notice at most once per day. Never
@@ -3800,30 +3810,54 @@ function buildResultEnvelope(result, { sandbox, image, git, result: agentResult,
3800
3810
  * HTTP base URL at `/agentic`; the identity token + capability credential follow
3801
3811
  * the blackboard's `?token=…` pattern.
3802
3812
  *
3813
+ * Local-first (security opt-in). Nano is designed for local use, so visibility is
3814
+ * ON BY DEFAULT:
3815
+ * - LOCAL mode (default): no credentials configured — the worker connects with
3816
+ * the well-known localhost token ({@link LOCAL_AGENTIC_TOKEN}) and no
3817
+ * capability credential, so it appears live with zero configuration (the hub's
3818
+ * matching LOCAL mode accepts it).
3819
+ * - SECURE mode: set NANO_AGENTIC_TOKEN + NANO_AGENTIC_CREDENTIAL (or the
3820
+ * persisted `agenticToken`/`agenticCredential`) — an ADR 0028 identity token
3821
+ * AND a capability credential are then sent (enrolment). If only one is set the
3822
+ * config is incomplete and we stay off (fail closed), returning `null`.
3823
+ * - OFF: NANO_AGENTIC=off (or 0/false/no), or persisted `agentic:false`.
3824
+ *
3803
3825
  * Env wins over persisted config; the base URL falls back to the configured nano
3804
- * URL (the app's own port). A worker only connects when BOTH an identity token
3805
- * and a capability credential are present (enrolment) — absent either, it runs
3806
- * exactly as before, off the visibility page. Returns `null` when not enrolled.
3826
+ * URL (the app's own port). Returns `null` only when disabled or half-configured.
3807
3827
  *
3808
- * @returns {{ url: string, token: string, credential: string, bufferCapacity: number } | null}
3828
+ * @returns {{ url: string, token: string, credential: string, bufferCapacity: number, secure: boolean } | null}
3809
3829
  */
3810
3830
  function resolveAgenticConfig() {
3811
3831
  const cfg = readConfig();
3832
+ // Explicit off-switch (env wins). Lets an operator fully opt out of visibility.
3833
+ const offSetting = process.env.NANO_AGENTIC
3834
+ ?? (cfg.agentic === false ? 'off' : cfg.agentic);
3835
+ if (/^(0|off|false|no)$/i.test(String(offSetting ?? ''))) return null;
3836
+
3812
3837
  const url = process.env.NANO_AGENTIC_URL
3813
3838
  || cfg.agenticUrl
3814
3839
  || cfg.nanoUrl
3815
3840
  || process.env.NANO_BASE_URL
3816
3841
  || DEFAULT_NANO_URL;
3842
+ if (!url) return null;
3817
3843
  const token = process.env.NANO_AGENTIC_TOKEN || cfg.agenticToken || '';
3818
3844
  const credential = process.env.NANO_AGENTIC_CREDENTIAL || cfg.agenticCredential || '';
3819
- if (!url || !token || !credential) return null;
3820
3845
  // Outbound hub-down buffer bound (frames). Operator-tunable (C4, #43) so a
3821
3846
  // long expected outage can be given more headroom; resolveBufferCapacity
3822
3847
  // validates it to a positive integer and falls back to the client default.
3823
3848
  const bufferCapacity = resolveBufferCapacity(
3824
3849
  process.env.NANO_AGENTIC_BUFFER_CAPACITY ?? cfg.agenticBufferCapacity,
3825
3850
  );
3826
- return { url, token, credential, bufferCapacity };
3851
+
3852
+ // SECURE mode: any explicit credential configured means the operator opted into
3853
+ // enrolment — require BOTH halves, fail closed if only one is present.
3854
+ if (token || credential) {
3855
+ if (!token || !credential) return null;
3856
+ return { url, token, credential, bufferCapacity, secure: true };
3857
+ }
3858
+
3859
+ // LOCAL mode (default): well-known localhost token, no capability credential.
3860
+ return { url, token: LOCAL_AGENTIC_TOKEN, credential: '', bufferCapacity, secure: false };
3827
3861
  }
3828
3862
 
3829
3863
  /**
@@ -4146,8 +4180,10 @@ async function workAgent(req, flags) {
4146
4180
  // accessors on `workChannel` (relay-lane sink + connect/disconnect/reconnect
4147
4181
  // lifecycle events) rather than opening their own connection.
4148
4182
  //
4149
- // Enrolment is opt-in: without an identity token + capability credential the
4150
- // worker runs exactly as before, off the channel (see resolveAgenticConfig).
4183
+ // Local-first (security opt-in): visibility is ON BY DEFAULT. In LOCAL mode the
4184
+ // worker joins with the well-known localhost token and no credential; SECURE
4185
+ // mode (NANO_AGENTIC_TOKEN + NANO_AGENTIC_CREDENTIAL) sends a real ADR 0028
4186
+ // identity + capability; NANO_AGENTIC=off disables it (see resolveAgenticConfig).
4151
4187
  const agenticCfg = resolveAgenticConfig();
4152
4188
  if (agenticCfg) {
4153
4189
  try {
@@ -4167,7 +4203,8 @@ async function workAgent(req, flags) {
4167
4203
  logger,
4168
4204
  });
4169
4205
  const shown = redactAgenticUrl(buildAgenticUrl(agenticCfg.url, {}));
4170
- logger.info(` agentic channel: announcing presence as ${workerName} on ${shown}`);
4206
+ const mode = agenticCfg.secure ? 'secure' : 'local';
4207
+ logger.info(` agentic channel (${mode}): announcing presence as ${workerName} on ${shown}`);
4171
4208
  } catch (err) {
4172
4209
  // Never let a channel failure stop the worker from doing its actual job.
4173
4210
  workChannel = null;
@@ -4191,7 +4228,7 @@ async function workAgent(req, flags) {
4191
4228
  }
4192
4229
  }
4193
4230
  } else {
4194
- logger.info(' agentic channel: not enrolled (set NANO_AGENTIC_URL + NANO_AGENTIC_TOKEN + NANO_AGENTIC_CREDENTIAL to appear on the visibility page).');
4231
+ logger.info(' agentic channel: disabled either the off-switch is set (NANO_AGENTIC=off or persisted agentic:false), or SECURE mode is half-configured (set BOTH NANO_AGENTIC_TOKEN + NANO_AGENTIC_CREDENTIAL). Clear the off-switch to use default LOCAL visibility.');
4195
4232
  }
4196
4233
 
4197
4234
  // C3 (#42): the role's live-terminal mode — a full PTY (streamed on the relay
@@ -7566,6 +7603,7 @@ function parseProcessosRequest(args, flags) {
7566
7603
  export { resolveBinary, findBinary, launcherEnvMarkers };
7567
7604
  export { setConfig, unsetConfig, readConfig, writeConfig, getConfigFile, SETTING_ALIASES };
7568
7605
  export { buildNpmInvocation };
7606
+ export { resolveAgenticConfig, LOCAL_AGENTIC_TOKEN };
7569
7607
  export { compareSemver, githubRepoSlug, filterReleasesSince, renderReleaseBody };
7570
7608
  export {
7571
7609
  webConsoleUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c8ctl-plugin-nano",
3
- "version": "1.33.2",
3
+ "version": "1.34.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.33.2",
61
- "@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.33.2",
62
- "@nanobpm/c8ctl-plugin-nano-linux-x64": "1.33.2",
63
- "@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.33.2",
64
- "@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.33.2",
65
- "@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.33.2",
66
- "@nanobpm/c8ctl-plugin-nano-win32-x64": "1.33.2"
60
+ "@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.34.0",
61
+ "@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.34.0",
62
+ "@nanobpm/c8ctl-plugin-nano-linux-x64": "1.34.0",
63
+ "@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.34.0",
64
+ "@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.34.0",
65
+ "@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.34.0",
66
+ "@nanobpm/c8ctl-plugin-nano-win32-x64": "1.34.0"
67
67
  }
68
68
  }