@agentguard-run/burn 0.1.1 → 0.2.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/CHANGELOG.md +93 -0
- package/README.md +145 -6
- package/dist/src/adapters/codex.d.ts +48 -0
- package/dist/src/adapters/codex.js +197 -0
- package/dist/src/adapters/cursor.d.ts +35 -0
- package/dist/src/adapters/cursor.js +135 -0
- package/dist/src/adapters/raw-api.d.ts +76 -0
- package/dist/src/adapters/raw-api.js +130 -0
- package/dist/src/cli.d.ts +7 -3
- package/dist/src/cli.js +141 -17
- package/dist/src/conformance.d.ts +26 -0
- package/dist/src/conformance.js +261 -0
- package/dist/src/defaults.d.ts +11 -0
- package/dist/src/defaults.js +16 -1
- package/dist/src/detectors/local-compute.d.ts +19 -0
- package/dist/src/detectors/local-compute.js +66 -0
- package/dist/src/events.d.ts +94 -0
- package/dist/src/events.js +47 -0
- package/dist/src/gateway.d.ts +141 -0
- package/dist/src/gateway.js +536 -0
- package/dist/src/hook/pre-tool-use.d.ts +25 -1
- package/dist/src/hook/pre-tool-use.js +64 -16
- package/dist/src/index.d.ts +19 -4
- package/dist/src/index.js +57 -1
- package/dist/src/install.d.ts +29 -0
- package/dist/src/install.js +145 -0
- package/dist/src/override.d.ts +32 -0
- package/dist/src/override.js +72 -0
- package/dist/src/proxy/server.d.ts +45 -0
- package/dist/src/proxy/server.js +169 -0
- package/dist/src/proxy/usage-observer.d.ts +40 -0
- package/dist/src/proxy/usage-observer.js +128 -0
- package/dist/src/receipt.d.ts +61 -0
- package/dist/src/receipt.js +98 -0
- package/dist/src/replay/render.d.ts +1 -0
- package/dist/src/replay/render.js +2 -1
- package/dist/src/state/reservations.d.ts +115 -11
- package/dist/src/state/reservations.js +293 -59
- package/dist/src/state/session.d.ts +6 -0
- package/dist/src/state/session.js +17 -0
- package/dist/src/status.d.ts +19 -0
- package/dist/src/status.js +112 -0
- package/dist/src/types.d.ts +14 -1
- package/fixtures/codex-0.151.0-pretooluse.json +49 -0
- package/package.json +34 -6
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* One status for the whole machine.
|
|
4
|
+
*
|
|
5
|
+
* Every host's sessions in one table, with coverage stated per plane, so an
|
|
6
|
+
* OK never masquerades as full visibility. A Cursor session shows
|
|
7
|
+
* `usage:n/a`; a proxy session shows `spawns:n/a`; a session fed by both
|
|
8
|
+
* middleware and the proxy shows full coverage, because it has it.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.readHookSessions = readHookSessions;
|
|
12
|
+
exports.renderHostHealth = renderHostHealth;
|
|
13
|
+
exports.renderMachineStatus = renderMachineStatus;
|
|
14
|
+
const node_fs_1 = require("node:fs");
|
|
15
|
+
const node_path_1 = require("node:path");
|
|
16
|
+
const evaluate_1 = require("./detectors/evaluate");
|
|
17
|
+
const events_1 = require("./events");
|
|
18
|
+
const pre_tool_use_1 = require("./hook/pre-tool-use");
|
|
19
|
+
const LIVE_WINDOW_MS = 30 * 60 * 1000;
|
|
20
|
+
/**
|
|
21
|
+
* Claude Code sessions live in the hook's own files (they carry a transcript
|
|
22
|
+
* cursor the gateway sessions do not). Read them into the same view so the
|
|
23
|
+
* table shows every host, not just the new ones.
|
|
24
|
+
*/
|
|
25
|
+
function readHookSessions(home) {
|
|
26
|
+
const dir = (0, node_path_1.join)(home, 'sessions');
|
|
27
|
+
const out = [];
|
|
28
|
+
let names = [];
|
|
29
|
+
try {
|
|
30
|
+
names = (0, node_fs_1.readdirSync)(dir).filter((n) => n.endsWith('.json') && !n.startsWith('gw-'));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
for (const name of names) {
|
|
36
|
+
try {
|
|
37
|
+
const raw = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(dir, name), 'utf8'));
|
|
38
|
+
if (!raw.cursor || !raw.state)
|
|
39
|
+
continue;
|
|
40
|
+
const state = (0, pre_tool_use_1.inflateHookSession)(raw);
|
|
41
|
+
out.push({
|
|
42
|
+
sessionId: state.sessionId,
|
|
43
|
+
hosts: ['claude-code'],
|
|
44
|
+
capabilities: events_1.CAPABILITIES['claude-code'],
|
|
45
|
+
state,
|
|
46
|
+
liveSpawns: 0,
|
|
47
|
+
usage: { authoritative: state.totalTokens > 0 ? 1 : 0, estimated: 0, missing: 0 },
|
|
48
|
+
decisions: 0,
|
|
49
|
+
wouldBlock: 0,
|
|
50
|
+
closedAt: null,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
/* half-written or foreign file: skip */
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
function renderHostHealth(hosts) {
|
|
60
|
+
const lines = ['hosts:'];
|
|
61
|
+
for (const h of hosts) {
|
|
62
|
+
const name = h.host === 'claude' ? 'claude-code' : h.host;
|
|
63
|
+
let state;
|
|
64
|
+
if (!h.installed)
|
|
65
|
+
state = h.hostPresent ? `not installed (agentguard-burn init ${h.host} --write)` : 'not present on this machine';
|
|
66
|
+
else if (!h.commandExists)
|
|
67
|
+
state = `INSTALLED BUT BROKEN: hook command not found on disk, host will fail open (re-run: agentguard-burn init ${h.host} --write)`;
|
|
68
|
+
else
|
|
69
|
+
state = `installed, hook command ok (${h.file.replace(process.env.HOME ?? '', '~')})`;
|
|
70
|
+
lines.push(` ${name.padEnd(13)} ${state}`);
|
|
71
|
+
}
|
|
72
|
+
return lines.join('\n');
|
|
73
|
+
}
|
|
74
|
+
function renderMachineStatus(sessions, compute, now = Date.now()) {
|
|
75
|
+
const live = sessions.filter((s) => s.closedAt === null && now - s.state.lastEventAt <= LIVE_WINDOW_MS).sort((a, b) => b.state.lastEventAt - a.state.lastEventAt);
|
|
76
|
+
const totalTokens = live.reduce((n, s) => n + s.state.totalTokens, 0);
|
|
77
|
+
const lines = [];
|
|
78
|
+
lines.push(`sessions: ${live.length} active in the last 30 min · ${(0, evaluate_1.fmt)(totalTokens)} tokens · ${compute.inFlight} model call(s) in flight · ${Math.round(compute.occupiedMs / 1000)}s occupied in last ${Math.round(compute.windowMs / 60000)} min`);
|
|
79
|
+
if (live.length === 0) {
|
|
80
|
+
lines.push(' (none)');
|
|
81
|
+
return lines.join('\n');
|
|
82
|
+
}
|
|
83
|
+
lines.push(' ' + pad('HOST(S)', 22) + pad('SESSION', 20) + pad('TOKENS', 9) + pad('SPAWNS', 8) + pad('DEPTH', 7) + pad('LAST', 8) + 'COVERAGE');
|
|
84
|
+
for (const s of live) {
|
|
85
|
+
lines.push(' ' +
|
|
86
|
+
pad(s.hosts.join('+'), 22) +
|
|
87
|
+
pad(shortId(s.sessionId), 20) +
|
|
88
|
+
pad((0, evaluate_1.fmt)(s.state.totalTokens), 9) +
|
|
89
|
+
pad(String(s.state.spawnCount), 8) +
|
|
90
|
+
pad(String(s.state.maxDepth), 7) +
|
|
91
|
+
pad(ago(now - s.state.lastEventAt), 8) +
|
|
92
|
+
coverageLine(s));
|
|
93
|
+
}
|
|
94
|
+
return lines.join('\n');
|
|
95
|
+
}
|
|
96
|
+
function ago(ms) {
|
|
97
|
+
const m = Math.floor(ms / 60000);
|
|
98
|
+
return m < 1 ? 'now' : `${m}m ago`;
|
|
99
|
+
}
|
|
100
|
+
function coverageLine(s) {
|
|
101
|
+
const usage = s.usage.missing > 0 ? `usage:partial(${s.usage.missing} missing)` : s.usage.authoritative > 0 ? 'usage:auth' : s.usage.estimated > 0 ? 'usage:est' : `usage:${short(s.capabilities.usage)}`;
|
|
102
|
+
return `spawns:${short(s.capabilities.spawns)} depth:${short(s.capabilities.depth)} ${usage}`;
|
|
103
|
+
}
|
|
104
|
+
function short(c) {
|
|
105
|
+
return c === 'authoritative' ? 'auth' : c === 'estimated' ? 'est' : 'n/a';
|
|
106
|
+
}
|
|
107
|
+
function pad(v, w) {
|
|
108
|
+
return v.length >= w ? `${v.slice(0, w - 1)} ` : v.padEnd(w, ' ');
|
|
109
|
+
}
|
|
110
|
+
function shortId(v) {
|
|
111
|
+
return v.length <= 18 ? v : `${v.slice(0, 8)}…${v.slice(-8)}`;
|
|
112
|
+
}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* competitors, so the policy layer has to sit above all of them.
|
|
9
9
|
*/
|
|
10
10
|
export type Verdict = 'OK' | 'WARN' | 'STOP';
|
|
11
|
-
export type Detector = 'fanout' | 'sustained_burn' | 'burn_debt' | 'spawn_rate' | 'account' | 'duplicate_work';
|
|
11
|
+
export type Detector = 'fanout' | 'sustained_burn' | 'burn_debt' | 'spawn_rate' | 'account' | 'duplicate_work' | 'local_compute';
|
|
12
12
|
/** One normalised observation from a host transcript. */
|
|
13
13
|
export interface BurnEvent {
|
|
14
14
|
/** Unix milliseconds. */
|
|
@@ -114,6 +114,19 @@ export interface Thresholds {
|
|
|
114
114
|
windowActiveMinutes: number;
|
|
115
115
|
warnConcurrentSessions: number;
|
|
116
116
|
};
|
|
117
|
+
/**
|
|
118
|
+
* Local inference plane (Ollama, vLLM, LM Studio). Token dollars are close
|
|
119
|
+
* to meaningless when the GPU is yours; what runs away is concurrency and
|
|
120
|
+
* occupied time. WARN-only by default: no universal STOP is honest for
|
|
121
|
+
* hardware we cannot see. Optional so pre-0.2 policy files stay valid.
|
|
122
|
+
*/
|
|
123
|
+
localCompute?: {
|
|
124
|
+
windowMs: number;
|
|
125
|
+
warnConcurrent: number;
|
|
126
|
+
stopConcurrent: number | null;
|
|
127
|
+
warnOccupiedMs: number | null;
|
|
128
|
+
stopOccupiedMs: number | null;
|
|
129
|
+
};
|
|
117
130
|
}
|
|
118
131
|
export type Mode = 'shadow' | 'enforce';
|
|
119
132
|
export interface Policy {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"captured_from": "codex-cli 0.151.0, 2026-09-03, codex exec with -c hooks.PreToolUse inline; --dangerously-bypass-hook-trust",
|
|
3
|
+
"payloads": [
|
|
4
|
+
{
|
|
5
|
+
"session_id": "01a069d3-5fc8-7470-8c95-1b42e7206746",
|
|
6
|
+
"turn_id": "01a069d3-6018-7523-a26e-eea10123676c",
|
|
7
|
+
"transcript_path": "/Users/example/.codex/sessions/2026/09/03/rollout-example.jsonl",
|
|
8
|
+
"cwd": "/Users/example/project",
|
|
9
|
+
"hook_event_name": "PreToolUse",
|
|
10
|
+
"model": "gpt-5.4-mini",
|
|
11
|
+
"permission_mode": "bypassPermissions",
|
|
12
|
+
"tool_name": "Bash",
|
|
13
|
+
"tool_input": {
|
|
14
|
+
"command": "echo CANARY_OK"
|
|
15
|
+
},
|
|
16
|
+
"tool_use_id": "call_NmxrZob0I2KFC8ChNe2PduGX"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"session_id": "01a069d4-d356-73b3-9440-1c4440319de8",
|
|
20
|
+
"turn_id": "01a069d4-d3b2-7593-85ec-8a0281f51bf1",
|
|
21
|
+
"transcript_path": "/Users/example/.codex/sessions/2026/09/03/rollout-example.jsonl",
|
|
22
|
+
"cwd": "/Users/example/project",
|
|
23
|
+
"hook_event_name": "PreToolUse",
|
|
24
|
+
"model": "gpt-5.4-mini",
|
|
25
|
+
"permission_mode": "bypassPermissions",
|
|
26
|
+
"tool_name": "spawn_agent",
|
|
27
|
+
"tool_input": {
|
|
28
|
+
"message": "<redacted>",
|
|
29
|
+
"fork_context": "<redacted>"
|
|
30
|
+
},
|
|
31
|
+
"tool_use_id": "call_oktUPQ5jcosNPOcGnEaWqRcv"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"session_id": "01a069d4-d356-73b3-9440-1c4440319de8",
|
|
35
|
+
"turn_id": "01a069d4-d3b2-7593-85ec-8a0281f51bf1",
|
|
36
|
+
"transcript_path": "/Users/example/.codex/sessions/2026/09/03/rollout-example.jsonl",
|
|
37
|
+
"cwd": "/Users/example/project",
|
|
38
|
+
"hook_event_name": "PreToolUse",
|
|
39
|
+
"model": "gpt-5.4-mini",
|
|
40
|
+
"permission_mode": "bypassPermissions",
|
|
41
|
+
"tool_name": "multi_agent_v1wait_agent",
|
|
42
|
+
"tool_input": {
|
|
43
|
+
"targets": "<redacted>",
|
|
44
|
+
"timeout_ms": "<redacted>"
|
|
45
|
+
},
|
|
46
|
+
"tool_use_id": "call_RK95A6i4UUhTigIGXF24gMMO"
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentguard-run/burn",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Local runaway-agent circuit breaker for AI coding agents.
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Local runaway-agent circuit breaker for AI coding agents and local model runtimes. One policy across Claude Code, Cursor, Codex, Ollama, vLLM, LM Studio and raw orchestrators: detects fan-out storms and sustained token burn, blocks the next spawn, and proves what happened with content-free signed receipts. Nothing leaves the machine.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"main": "dist/src/index.js",
|
|
8
8
|
"types": "dist/src/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/src/index.d.ts",
|
|
12
|
+
"default": "./dist/src/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./middleware": {
|
|
15
|
+
"types": "./dist/src/adapters/raw-api.d.ts",
|
|
16
|
+
"default": "./dist/src/adapters/raw-api.js"
|
|
17
|
+
},
|
|
18
|
+
"./proxy": {
|
|
19
|
+
"types": "./dist/src/proxy/server.d.ts",
|
|
20
|
+
"default": "./dist/src/proxy/server.js"
|
|
21
|
+
},
|
|
22
|
+
"./cursor": {
|
|
23
|
+
"types": "./dist/src/adapters/cursor.d.ts",
|
|
24
|
+
"default": "./dist/src/adapters/cursor.js"
|
|
25
|
+
},
|
|
26
|
+
"./codex": {
|
|
27
|
+
"types": "./dist/src/adapters/codex.d.ts",
|
|
28
|
+
"default": "./dist/src/adapters/codex.js"
|
|
29
|
+
},
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
9
32
|
"bin": {
|
|
10
33
|
"agentguard-burn": "dist/src/cli.js"
|
|
11
34
|
},
|
|
12
35
|
"files": [
|
|
13
36
|
"dist/src",
|
|
37
|
+
"fixtures",
|
|
14
38
|
"README.md",
|
|
39
|
+
"CHANGELOG.md",
|
|
15
40
|
"LICENSE"
|
|
16
41
|
],
|
|
17
42
|
"engines": {
|
|
@@ -21,11 +46,10 @@
|
|
|
21
46
|
"build": "tsc -p tsconfig.json",
|
|
22
47
|
"test": "tsc -p tsconfig.json && node --test \"dist/tests/**/*.test.js\"",
|
|
23
48
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
"dependencies": {
|
|
27
|
-
"@noble/ed25519": "^3.0.0"
|
|
49
|
+
"conformance": "tsc -p tsconfig.json && node dist/src/cli.js conformance",
|
|
50
|
+
"replay": "node dist/src/cli.js replay"
|
|
28
51
|
},
|
|
52
|
+
"dependencies": {},
|
|
29
53
|
"devDependencies": {
|
|
30
54
|
"@types/node": "^22",
|
|
31
55
|
"typescript": "^5.0.0"
|
|
@@ -33,6 +57,10 @@
|
|
|
33
57
|
"keywords": [
|
|
34
58
|
"agentguard",
|
|
35
59
|
"claude-code",
|
|
60
|
+
"cursor",
|
|
61
|
+
"codex",
|
|
62
|
+
"ollama",
|
|
63
|
+
"vllm",
|
|
36
64
|
"agents",
|
|
37
65
|
"token-budget",
|
|
38
66
|
"circuit-breaker",
|