@rackbops/ac-agent 2.0.0-alpha.59 → 2.0.0-alpha.62

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 +140 -10
  2. package/dist/ac-agent.mjs +9313 -5156
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -264,6 +264,101 @@ race the bind and report a false failure. Wait a moment, then check:
264
264
  curl -k https://127.0.0.1:8788/v1/health
265
265
  ```
266
266
 
267
+ #### A server host that also runs the console (#826)
268
+
269
+ The machine that runs the console in Docker can pair an agent too, so its own toolchain is graded in
270
+ the Agents panel's Environment section like any other machine's; nucbox is set up this way and paired
271
+ (#826, 2026-10-20). It installs the Linux way above, with these differences.
272
+
273
+ **Node 26 from NodeSource's apt repository.** The agent needs Node 26 or newer (`engines.node`, #827;
274
+ see **Install** above), the fleet's one line, and Ubuntu's own `nodejs` is older (22 on Ubuntu 26.04).
275
+ Write NodeSource's key and source by hand rather than piping its setup script to a shell. The key:
276
+
277
+ ```sh
278
+ curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
279
+ | gpg --dearmor | sudo tee /usr/share/keyrings/nodesource.gpg >/dev/null
280
+ sudo chmod 0644 /usr/share/keyrings/nodesource.gpg
281
+ ```
282
+
283
+ Its fingerprint is `6F71 F525 2828 41EE DAF8 51B4 2F59 B5F9 9B1B E0B4`. The source, in deb822 form,
284
+ is `/etc/apt/sources.list.d/nodesource.sources`:
285
+
286
+ ```
287
+ Types: deb
288
+ URIs: https://deb.nodesource.com/node_26.x
289
+ Suites: nodistro
290
+ Components: main
291
+ Architectures: amd64
292
+ Signed-By: /usr/share/keyrings/nodesource.gpg
293
+ ```
294
+
295
+ Two pins make NodeSource's packages win over Ubuntu's: `/etc/apt/preferences.d/nodejs` (below) and
296
+ `/etc/apt/preferences.d/nsolid` (the same, with `Package: nsolid`):
297
+
298
+ ```
299
+ Package: nodejs
300
+ Pin: origin deb.nodesource.com
301
+ Pin-Priority: 600
302
+ ```
303
+
304
+ Then `sudo apt-get update && sudo apt-get install -y nodejs`, which brings `npm` with it. On nucbox
305
+ (2026-10-09) that installed `26.11.1-1nodesource1` with npm 11.20.0 and removed Ubuntu's nodejs 22,
306
+ its `node-*` libraries and `nodejs-doc`, so check first that nothing else on the host runs on the
307
+ system Node. On nucbox nothing else did (its services run in Docker; the plan's read of the box,
308
+ 2026-10-08), so the agent is the system Node's only user.
309
+
310
+ **An empty repos root.** After `sudo loginctl enable-linger "$USER"`:
311
+
312
+ ```sh
313
+ mkdir -p "$HOME/ac-agent-repos"
314
+ npx -y @rackbops/ac-agent install --repos-root "$HOME/ac-agent-repos"
315
+ ```
316
+
317
+ The console's host holds no checkout the console should list or pull, so the repos root is an empty
318
+ directory. `curl -k https://127.0.0.1:8788/v1/health` then answers `{"ok":true, ...,
319
+ "paired":false}`.
320
+
321
+ **The console reaches it at the host's LAN address.** Pair it as in **Pair** below, giving the
322
+ console's form the URL `pair` prints. That is the host's LAN address, not `127.0.0.1`: the console's
323
+ container runs on the same machine, but it calls this agent the way it calls any other, and inside the
324
+ container `127.0.0.1` is the container itself. `pair` and the certificate leave Docker's bridge
325
+ interfaces out (`docker0`, `br-*`, `veth*`; #330) unless they are the machine's only addresses, so the
326
+ address printed is the real NIC's. A host firewall has to admit the agent's port from the console's
327
+ Docker network as well as from the LAN, since the container's traffic to the host arrives from its
328
+ bridge address (not verified on nucbox; pairing is the test). Give the pairing form the host's name in
329
+ the fleet standard (`nucbox`): the Upgrade action finds the standard host by the name the console holds
330
+ for the machine, and the Environment grading by the name the agent reports (its OS hostname, which an
331
+ inventory sidecar's `hostname` maps to the host; see the agents plugin's README, "Which standard host a
332
+ machine is").
333
+
334
+ **The minimal grant set.** Opt the machine in to upgrades (`toolchain_upgrade = true` and the sudoers
335
+ rule, **Operator setup** under `POST /v1/toolchain/upgrade`; read what that rule grants before adding
336
+ it to a production host), restart it (`stop`, then `start`), and in the Grants grid give the `agents`
337
+ plugin `agent.toolchain.upgrade` and nothing else: no `agent.exec.claude`, no `agent.git.*`, no
338
+ `agent.scan.*`. The grading needs no grant at all (`GET /v1/preflight` is control plane, and so is
339
+ `POST /v1/update`, which lets the console update the agent itself). The Upgrade button needs none
340
+ either: it shows on any `behind` row whose tool the standard gives a package for on a Linux server
341
+ (among `git`, `gh`, `node` and `claude`; a `docker` or `cloudflared` row gets one too, and the agent
342
+ runs that upgrade at the next 05:00 America/New_York, #978). The grant is what lets
343
+ a click succeed; without it the agent refuses with `agent.toolchain.upgrade is not granted to agents on
344
+ <machine>`. That granted verb installs a package the standard names: as root, through `sudo apt-get
345
+ install --only-upgrade` and the sudoers rule, or for Claude Code with its own `claude update` as the
346
+ agent's user. No other verb is granted to `agents`: no `agent.exec.*`, `agent.git.*` or `agent.scan.*`.
347
+
348
+ nucbox additionally grants `agent.tunnel.lint` to the `admin` plugin, and nothing else (the owner's Grants
349
+ screenshot, 2026-10-20).
350
+
351
+ The console no longer asks this machine for what it has not been granted (#843):
352
+ - The host's job runner skips a machine that has not granted every capability a job declares: no
353
+ `job_runs` row, no task and no `job failed` notification. The four `scans` jobs declare
354
+ `agent.scan.<kind>`, so each reads `not granted` on the machine's row instead of a failed run.
355
+ - The `repos` plugin's repo-currency check and `provider-artifacts`' hourly `publish.mirror` raise no
356
+ Needs-attention row for a machine that never had the grant; the card reads `not granted`. A grant
357
+ revoked after a good result raises one row (`Repo currency is no longer granted on <machine>`, and
358
+ its mirror-publish counterpart) for 7 days, then nothing.
359
+ - The Admin Security card's tunnel lint reads the console's own grants and does not call this agent;
360
+ it shows `agent.tunnel.lint is not granted to admin on <machine>` as a neutral row, not a warning.
361
+
267
362
  ### Windows: no console window
268
363
 
269
364
  The task's action is `wscript.exe //B "<launch.js>"` against a small generated JScript launcher
@@ -353,8 +448,17 @@ attempt never wins a race against a still-valid one. Requires a certificate to a
353
448
  ui.tsx`) and fill in the pairing form with the three printed values plus an optional machine name:
354
449
  URL, Fingerprint, Code, Name. Submitting calls `POST /api/agents` — success adds the machine to the
355
450
  paired list; the console then reaches it, per capability, once an operator grants that capability to
356
- a plugin for it in the same panel's Grants grid (see **Security model** below — nothing runs until a
357
- capability is both declared by the plugin and granted here).
451
+ a plugin for it in the same panel's Grants grid (see **Security model** below — no verb runs until a
452
+ capability is both declared by the plugin and granted here; the control-plane routes, `PUT /v1/grants`,
453
+ `GET /v1/preflight`, and `POST` and `GET /v1/update`, need no grant).
454
+ Every other POST verb answers `413 {ok:false,error:"request body too large"}` above 64 KiB; publish,
455
+ toolchain-upgrade and `exec/gh` (512 KiB, for one 64 KiB `--body`) keep their own larger caps.
456
+
457
+ **Unpairing** (the Agents panel's Unpair, `DELETE /api/agents/:id`) is local to the console: it forgets the
458
+ machine and its grants and makes no call to the agent, which keeps its stored token hash and keeps accepting
459
+ the token. To cut that credential off, run `pair --force` on the machine and pair it again from the console
460
+ (the old token stops working once the new code is consumed), or `uninstall`, which removes the token hash. A
461
+ copy of the console's `/state/ac.db` taken before the unpair still holds the old token in clear.
358
462
 
359
463
  ## Uninstall
360
464
 
@@ -381,7 +485,9 @@ running agent. Both outcomes are written to `agent.log`, as `replaced stale lock
381
485
 
382
486
  `stop`, `uninstall` and an install's restart kill only the pid that pinned answer names. When nothing
383
487
  answers they kill nothing and remove the stale `agent.pid`, and they print `... nothing was killed` if
384
- that pid belongs to some other live process.
488
+ that pid belongs to some other live process. When the pinned answer has no `pid` (an agent older than
489
+ alpha.59), they refuse and name the port and version instead of killing nothing. An install also
490
+ refuses to start while the port still answers after the stop.
385
491
 
386
492
  ## Config and state directories
387
493
 
@@ -486,8 +592,8 @@ capability, plugin}`. See **Security model** for how a grant reaches the agent i
486
592
  - **git is run by absolute path** (#699). Each of the three requests resolves git once, on the
487
593
  refreshed PATH (the registry's on Windows, the process PATH elsewhere: the search the preflight and
488
594
  the toolchain upgrade use), and every git spawn of that request names the path it found, made
489
- absolute if PATH gave a relative entry (`exec/claude` does the same for `claude`, the other spawn
490
- whose working directory a request chooses). This matters on Windows, where a bare `git` is looked for in the
595
+ absolute if PATH gave a relative entry (`exec/claude` and `exec/gh` resolve their tool on the same refreshed PATH, #716, and `exec/claude`
596
+ also makes its path absolute, the other spawn whose working directory a request chooses). This matters on Windows, where a bare `git` is looked for in the
491
597
  spawn's working directory first (`git.com`, then `git.exe`), and every call here has a repo checkout
492
598
  as its working directory: a `git.exe` that a clone, a branch or a fast-forward put in the repo's root
493
599
  would otherwise run as the agent's user, unattended (the daily `repo.pull` job reaches it). A bare
@@ -601,7 +707,9 @@ capability, plugin}`. See **Security model** for how a grant reaches the agent i
601
707
  (the default) / `acceptEdits` / `bypassPermissions`; `model` is an optional
602
708
  `^[A-Za-z0-9._-]{1,64}$` string. Any other shape is `400` before anything spawns. `claude` runs as
603
709
  `claude -p <prompt> --permission-mode <mode> --add-dir <repo path> [--model <model>]`, `cwd` the
604
- resolved repo. **At most 4 `claude` jobs run at once** — a 5th concurrent request is
710
+ resolved repo. `claude` is looked up on the refreshed PATH (the registry's on Windows, the process PATH
711
+ elsewhere), and the job runs with that PATH, so a `claude` installed after the agent started is found
712
+ without a restart (#716). **At most 4 `claude` jobs run at once** — a 5th concurrent request is
605
713
  `429 {ok: false, error: "too many running jobs"}`, checked before spawning, not after. Only `claude`
606
714
  jobs count: a running toolchain upgrade (#675) neither uses up a slot nor is blocked by a claude job.
607
715
  - **`GET /v1/jobs`** (`agent.exec.claude`) → `{ok: true, jobs: [view...]}`, newest first.
@@ -632,8 +740,8 @@ capability, plugin}`. See **Security model** for how a grant reaches the agent i
632
740
  be longer, up to 64 KiB (24 KiB on Windows, where the command line is limited) and contain `\n`/`\t`
633
741
  (a PR/issue comment body); any other flag's value, `--search=<text>` included, is held to the 4 KiB and
634
742
  no-control-character rule.
635
- - **The `gh` child gets an allowlisted environment, not the agent's** (#714): `PATH`, the per-OS
636
- profile/temp/locale keys, proxy and `SSL_CERT_*` keys, `GH_HOST` and `GH_CONFIG_DIR`, plus
743
+ - **The `gh` child gets an allowlisted environment, not the agent's** (#714): `PATH` (the refreshed PATH `gh` was
744
+ looked up on, #716), the per-OS profile/temp/locale keys, proxy and `SSL_CERT_*` keys, `GH_HOST` and `GH_CONFIG_DIR`, plus
637
745
  `GH_PROMPT_DISABLED`, `GH_NO_UPDATE_NOTIFIER` and `NO_COLOR`. `GH_TOKEN`, `GITHUB_TOKEN`,
638
746
  `GH_ENTERPRISE_TOKEN` and `GITHUB_ENTERPRISE_TOKEN` are deliberately left out, so `gh` takes its
639
747
  credential from `gh auth login` on the machine. A `--jq` program that names the environment
@@ -1106,6 +1214,26 @@ query` (Windows) or `xdg-mime query` (Linux) is ever run.
1106
1214
  restarted agent holds no answer until its first conclusive detection, so a probe landing in that
1107
1215
  window stores "unknown".
1108
1216
 
1217
+ ## Machine identity (#914)
1218
+
1219
+ The console is moving to treat a machine as its **OS machine id** (#914): a changed id is a different
1220
+ machine. This agent reports the id so the console can recognise a re-pair from the same machine; what the
1221
+ console does with it is a separate change (AM5, #914), not part of this one. The agent reads the id once,
1222
+ at `serve` start:
1223
+
1224
+ - **Linux:** the first non-empty line of `/etc/machine-id`, else of `/var/lib/dbus/machine-id`, if it is 32
1225
+ hex characters (a first-boot `uninitialized` placeholder is not an id).
1226
+ - **Windows:** `MachineGuid` from `reg.exe query HKLM\SOFTWARE\Microsoft\Cryptography /v MachineGuid`
1227
+ (readable without elevation; 5 s limit), if it is a GUID.
1228
+ - **Anything else, or any failure:** no id; the agent reports none and the console decides by certificate
1229
+ as before.
1230
+
1231
+ **Only a hash leaves the machine**: HMAC-SHA256 of the lower-cased id under the key
1232
+ `ac-agent/os-machine-id/v1`, as 64 lowercase hex characters, in the field `osMachineId`. It is answered by
1233
+ `POST /v1/pair` and, to a caller with a valid bearer token only, by `GET /v1/health`; an unauthenticated
1234
+ health caller never sees it. The field is omitted when there is no id. `apiVersion` is unchanged (additive
1235
+ field; an older console ignores it).
1236
+
1109
1237
  ## Preflight
1110
1238
 
1111
1239
  **`GET /v1/preflight`** (#55): control plane, like `PUT /v1/grants` -- bearer-gated, no capability,
@@ -1153,7 +1281,9 @@ free text (a `broken` row's last non-empty output line, `unauthenticated`'s
1153
1281
  `System32` path) so a tool installed after the agent's logon task started is still seen; on
1154
1282
  Linux the process PATH plus every version-manager bin directory (`fnm`/`nvm`/`volta`/`asdf`) is
1155
1283
  searched as a fallback, so a tool invisible to a bare `which` reads `off-path`, never `missing`.
1156
- `searchPath` on the report names which: `"registry"` or `"process"`.
1284
+ `searchPath` on the report names which: `"registry"` or `"process"`. `exec/claude`, `exec/gh`, the repo
1285
+ verbs and the toolchain upgrade search the same PATH (#716, #699, #675), so a row the panel reads as
1286
+ `ok` is a tool those verbs find.
1157
1287
  - **The whole check answers in a few seconds.** The seven rows run concurrently; the worst case is
1158
1288
  `gh`'s `--version` answering just under 15 s and then `auth status` timing out at 15 s too --
1159
1289
  under the console's own 100 s edge-request budget behind Cloudflare Access.
@@ -1235,7 +1365,7 @@ Every refusal, the verified hand-off, and a failed hand-off are logged as
1235
1365
  10-year validity; SAN covers the machine's hostname, the machine's LAN-preferred IPv4 addresses
1236
1366
  (virtual and tunnel interfaces skipped unless nothing else exists), and `127.0.0.1`).
1237
1367
  - **`GET /v1/health` and `POST /v1/pair` are the only unauthenticated routes.** `/v1/health` answers
1238
- `{ok, version, apiVersion, machine, paired, capabilities, grantsRevision, claudeDesktop?}` — enough
1368
+ `{ok, version, apiVersion, machine, paired, capabilities, grantsRevision, claudeDesktop?}` (plus `osMachineId` for a valid bearer only, #914; see **Machine identity**) — enough
1239
1369
  for a caller to discover what it's talking to before pairing, nothing that requires trust.
1240
1370
  `grantsRevision` is `null` before this agent has ever received a `PUT /v1/grants` (or if
1241
1371
  `grants.json` went missing). `claudeDesktop` (present only once the agent holds a conclusive