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

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