@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.
- package/README.md +135 -9
- package/dist/ac-agent.mjs +9241 -5144
- 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 —
|
|
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`
|
|
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.
|
|
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
|
|
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
|