@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.
- package/README.md +140 -10
- package/dist/ac-agent.mjs +9313 -5156
- 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 —
|
|
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`
|
|
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.
|
|
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
|
|
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
|