agent-control-plane-core 0.11.0 → 0.11.1

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
@@ -170,8 +170,9 @@ emit(claudeAdapter.render(verdict, event));
170
170
  `agent-control-plane-core/runtime` publishes the rest of the plumbing the four
171
171
  hook entries share. `runHookEntry(adapter, judge)` is a whole entry: each
172
172
  `bin/<host>-hook.mjs` is that one call. `renderHookResponse` runs parse → judge
173
- → render, and returns the failure response THAT host expects when the payload
174
- does not parse. `emit`
173
+ → `renderEscalated`, so a judge's `ask` on a host with no ask tier blocks a
174
+ vetoable call instead of running it. It returns the failure response THAT host
175
+ expects when the payload does not parse. `emit`
175
176
  drains the whole body before it exits, so a large deny body is never truncated
176
177
  on a non-blocking pipe. The entries ship as `acp-claude-hook`, `acp-codex-hook`,
177
178
  `acp-amp-hook` and `acp-gemini-hook`. Codex's hook stdin carries no version, so
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-control-plane-core",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Vendor-neutral control-plane contract for coding agents: a normalized ToolCallEvent/Verdict schema with per-agent adapters (Claude Code, Codex, Amp, Gemini CLI) and a conformance suite.",
5
5
  "type": "module",
6
6
  "repository": {
package/src/runtime.mjs CHANGED
@@ -13,7 +13,12 @@
13
13
  * `process.exit` (see {@link writeAllSync}).
14
14
  */
15
15
  import { writeSync } from "node:fs";
16
- import { Decision, gatedResponse, sanitizeVerdict } from "./control-plane.mjs";
16
+ import {
17
+ Decision,
18
+ gatedResponse,
19
+ renderEscalated,
20
+ sanitizeVerdict,
21
+ } from "./control-plane.mjs";
17
22
 
18
23
  /**
19
24
  * Read all of stdin to a string.
@@ -63,7 +68,9 @@ export function demoJudge(event) {
63
68
  * A judge that THROWS is not a malformed payload: the call parsed, and the judge
64
69
  * failed to answer. That takes the same path as a judge that answers garbage —
65
70
  * the clamp's `ask` (see below) — so a crashing guardrail puts the call in front
66
- * of a human instead of taking an exit-0 fail-safe that runs it.
71
+ * of a human instead of taking an exit-0 fail-safe that runs it. The verdict
72
+ * renders through `renderEscalated`, so on a host with no ask tier that ask
73
+ * blocks a vetoable call rather than running it.
67
74
  *
68
75
  * Every error is written to STDERR (never stdout, which belongs to the host
69
76
  * transport) so a regression that silently disables enforcement leaves a
@@ -107,8 +114,9 @@ export function renderHookResponse(
107
114
  report("hook judge error", err);
108
115
  }
109
116
  // A host-supplied `onFailure` that BLOCKS (see {@link failurePosture}) wins
110
- // over the clamp's ask: Codex and Gemini cannot enforce an ask, so the
111
- // clamp would run a call no judge approved.
117
+ // over the clamp's ask. `renderEscalated` keeps an ask on a host that
118
+ // honours one (Claude, Amp), so without this a fail-closed entry would ask
119
+ // about a call no judge approved instead of blocking it.
112
120
  if (onFailure.enforced && !isDecision(answer)) return onFailure;
113
121
  // A judge's answer is clamped, not trusted. `normalizeVerdict` (inside
114
122
  // every adapter's render) THROWS on a decision outside allow/deny/ask, and
@@ -118,7 +126,7 @@ export function renderHookResponse(
118
126
  // in front of a human. Prose is passed through: scrubbing monitor-authored
119
127
  // text needs a scrubber this transport module has no business choosing.
120
128
  const verdict = sanitizeVerdict(answer, (text) => text);
121
- return adapter.render(verdict, event);
129
+ return renderEscalated(adapter, verdict, event);
122
130
  } catch (err) {
123
131
  report("hook pipeline error", err);
124
132
  return onFailure;
@@ -22,7 +22,9 @@ export function demoJudge(event: import("./control-plane.mjs").ToolCallEvent): i
22
22
  * A judge that THROWS is not a malformed payload: the call parsed, and the judge
23
23
  * failed to answer. That takes the same path as a judge that answers garbage —
24
24
  * the clamp's `ask` (see below) — so a crashing guardrail puts the call in front
25
- * of a human instead of taking an exit-0 fail-safe that runs it.
25
+ * of a human instead of taking an exit-0 fail-safe that runs it. The verdict
26
+ * renders through `renderEscalated`, so on a host with no ask tier that ask
27
+ * blocks a vetoable call rather than running it.
26
28
  *
27
29
  * Every error is written to STDERR (never stdout, which belongs to the host
28
30
  * transport) so a regression that silently disables enforcement leaves a