@rackbops/ac-agent 2.0.0-alpha.56 → 2.0.0-alpha.57

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 CHANGED
@@ -907,7 +907,7 @@ kind's jobs, and one holding only `agent.exec.claude` never reads these):
907
907
  | `already-current` | exit 0 and the version is unchanged; winget's `0x8A15002B` (no applicable update) also maps here, and means "no newer version this machine can install", which is not always the same as "current" | for the winget code |
908
908
  | `needs-sudoers` | `apt`: `sudo -n` refused, in sudo 1.9's wording (`a password is required`, `is not in the sudoers file`, `is not allowed to execute`, `is not allowed to run sudo on`, `user <u> may not run sudo on <host>`) or sudo-rs's, the default `sudo` of recent Ubuntu (`interactive authentication is required`, `I'm afraid I can't do that`, `user <u> may not run <command> on <host>`) | the sudoers rule to add, with what it grants |
909
909
  | `needs-elevation` | `winget`: its `0x8A150019` return code (command requires administrator privileges) | the command to paste into an elevated terminal |
910
- | `failed` | any other non-zero exit (the last output line, printable and cut at 300 characters), winget's `0x8A150014` (no installed package with that id), the output cap, a version that cannot be read after an exit 0, a command that could not start | what failed |
910
+ | `failed` | any other non-zero exit (the last output line that is not closing advice such as `Or consider using native installation with: claude install`, printable and cut at 300 characters), winget's `0x8A150014` (no installed package with that id), the output cap, a version that cannot be read after an exit 0, a command that could not start | what failed |
911
911
  | `timed-out` | the 15-minute limit | check the package before retrying: it may be half-installed |
912
912
 
913
913
  The winget return codes are pinned by number from winget's own generated table
@@ -918,7 +918,7 @@ package, or an installer that elevates itself, when its caller is not elevated h
918
918
  here**: it may relaunch the installer through UAC, which puts a consent prompt on the logon user's
919
919
  desktop (the agent is a logon task in that session) that the agent cannot answer; the job then waits
920
920
  until it is answered or the 15-minute limit fires (`timed-out`), and a limited `taskkill` cannot end an
921
- elevated installer. Anything else an installer fails with is `failed`, with its last output line.
921
+ elevated installer. Anything else an installer fails with is `failed`, with its last output line that is not closing advice.
922
922
 
923
923
  **Audit.** Two lines in the agent log per run: `toolchain upgrade started: "<tool>" via <manager>
924
924
  <packageId> (version of <cmd>)` when the job starts (so a run the agent does not survive is on record),
package/dist/ac-agent.mjs CHANGED
@@ -47,7 +47,7 @@ function _mergeNamespaces(n, m) {
47
47
  }
48
48
 
49
49
  var name = "@rackbops/ac-agent";
50
- var version = "2.0.0-alpha.56";
50
+ var version = "2.0.0-alpha.57";
51
51
  var type = "module";
52
52
  var description = "The per-machine host agent (installed with npx @rackbops/ac-agent): an HTTPS health server plus install/status/uninstall for a Windows logon task or a systemd user unit.";
53
53
  var bin = {
@@ -5210,7 +5210,12 @@ function createGrantState(initial, persist) {
5210
5210
  let current = initial;
5211
5211
  return {
5212
5212
  revision: () => current?.revision ?? null,
5213
- allows: (pluginId, capability) => (current?.grants[pluginId] ?? []).includes(capability),
5213
+ // Own keys only (#722): `grants` is parsed from JSON, a plain object, and `constructor` passes
5214
+ // PLUGIN_ID, so a bare `grants[pluginId]` read the inherited `Object` (no `.includes`) and the
5215
+ // guard answered 500 instead of 403.
5216
+ allows: (pluginId, capability) => current !== null && Object.hasOwn(current.grants, pluginId)
5217
+ ? (current.grants[pluginId] ?? []).includes(capability)
5218
+ : false,
5214
5219
  replace: (set) => {
5215
5220
  persist(set);
5216
5221
  current = set;
@@ -66328,7 +66333,8 @@ const WINDOWS = {
66328
66333
  };
66329
66334
  const TIERS = [200_000, 1_000_000];
66330
66335
  function windowFor(model, occupancy) {
66331
- const win = WINDOWS[model];
66336
+ // Own keys only (#722): `model` is text from a transcript, as in 1.x's dict lookup.
66337
+ const win = Object.hasOwn(WINDOWS, model) ? WINDOWS[model] : undefined;
66332
66338
  if (win !== undefined)
66333
66339
  return { window: win, mapped: true };
66334
66340
  for (const tier of TIERS) {
@@ -66738,7 +66744,8 @@ function isPlainObject(v) {
66738
66744
  /** USD-equivalent for one assistant turn, at list API rates. `0` for an unpriced model -- the
66739
66745
  * caller counts its tokens and surfaces the id instead of guessing. */
66740
66746
  function recordCost(model, usage) {
66741
- const rate = PRICES[model];
66747
+ // Own keys only (#722): `model` is text from a transcript, as in 1.x's dict lookup.
66748
+ const rate = Object.hasOwn(PRICES, model) ? PRICES[model] : undefined;
66742
66749
  if (!rate)
66743
66750
  return 0;
66744
66751
  const creation = isPlainObject(usage.cache_creation) ? usage.cache_creation : {};
@@ -66827,7 +66834,7 @@ function scanFile(fileBuffer, mtime, size, stem, parentName, fullPathComponents)
66827
66834
  turns++;
66828
66835
  if (model) {
66829
66836
  models.add(model);
66830
- if (!(model in PRICES))
66837
+ if (!Object.hasOwn(PRICES, model))
66831
66838
  unpriced.add(model);
66832
66839
  }
66833
66840
  cost += recordCost(model, usage);
@@ -75850,12 +75857,18 @@ function sudoersRule(username) {
75850
75857
  function printable(text, max) {
75851
75858
  return text.replace(/[^\x20-\x7e]/g, "?").slice(0, max);
75852
75859
  }
75860
+ /** A closing line that advises rather than reports, e.g. the Claude CLI's "Or consider using native
75861
+ * installation with: claude install" printed after the error that actually failed the run. */
75862
+ const ADVICE_LINE = /^(?:or\s+)?(?:consider|try|hint|tip|see|for more)(?=\s|:|$)/i;
75863
+ /** The command's last line that reports something: trailing advice lines are skipped, so the reason
75864
+ * is the failure and not the hint printed after it. All-advice output falls back to its last line. */
75853
75865
  function lastNonEmptyLine(text) {
75854
75866
  const lines = text
75855
75867
  .split(/\r?\n/)
75856
75868
  .map((line) => line.trim())
75857
75869
  .filter((line) => line !== "");
75858
- return printable(lines.at(-1) ?? "", MAX_REASON_CHARS);
75870
+ const reported = lines.findLast((line) => !ADVICE_LINE.test(line));
75871
+ return printable(reported ?? lines.at(-1) ?? "", MAX_REASON_CHARS);
75859
75872
  }
75860
75873
  function classifyOutcome(input) {
75861
75874
  if (input.limit === "timeout") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rackbops/ac-agent",
3
- "version": "2.0.0-alpha.56",
3
+ "version": "2.0.0-alpha.57",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "The per-machine host agent (installed with npx @rackbops/ac-agent): an HTTPS health server plus install/status/uninstall for a Windows logon task or a systemd user unit.",