@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.
Files changed (45) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +145 -6
  3. package/dist/src/adapters/codex.d.ts +48 -0
  4. package/dist/src/adapters/codex.js +197 -0
  5. package/dist/src/adapters/cursor.d.ts +35 -0
  6. package/dist/src/adapters/cursor.js +135 -0
  7. package/dist/src/adapters/raw-api.d.ts +76 -0
  8. package/dist/src/adapters/raw-api.js +130 -0
  9. package/dist/src/cli.d.ts +7 -3
  10. package/dist/src/cli.js +141 -17
  11. package/dist/src/conformance.d.ts +26 -0
  12. package/dist/src/conformance.js +261 -0
  13. package/dist/src/defaults.d.ts +11 -0
  14. package/dist/src/defaults.js +16 -1
  15. package/dist/src/detectors/local-compute.d.ts +19 -0
  16. package/dist/src/detectors/local-compute.js +66 -0
  17. package/dist/src/events.d.ts +94 -0
  18. package/dist/src/events.js +47 -0
  19. package/dist/src/gateway.d.ts +141 -0
  20. package/dist/src/gateway.js +536 -0
  21. package/dist/src/hook/pre-tool-use.d.ts +25 -1
  22. package/dist/src/hook/pre-tool-use.js +64 -16
  23. package/dist/src/index.d.ts +19 -4
  24. package/dist/src/index.js +57 -1
  25. package/dist/src/install.d.ts +29 -0
  26. package/dist/src/install.js +145 -0
  27. package/dist/src/override.d.ts +32 -0
  28. package/dist/src/override.js +72 -0
  29. package/dist/src/proxy/server.d.ts +45 -0
  30. package/dist/src/proxy/server.js +169 -0
  31. package/dist/src/proxy/usage-observer.d.ts +40 -0
  32. package/dist/src/proxy/usage-observer.js +128 -0
  33. package/dist/src/receipt.d.ts +61 -0
  34. package/dist/src/receipt.js +98 -0
  35. package/dist/src/replay/render.d.ts +1 -0
  36. package/dist/src/replay/render.js +2 -1
  37. package/dist/src/state/reservations.d.ts +115 -11
  38. package/dist/src/state/reservations.js +293 -59
  39. package/dist/src/state/session.d.ts +6 -0
  40. package/dist/src/state/session.js +17 -0
  41. package/dist/src/status.d.ts +19 -0
  42. package/dist/src/status.js +112 -0
  43. package/dist/src/types.d.ts +14 -1
  44. package/fixtures/codex-0.151.0-pretooluse.json +49 -0
  45. 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
+ }
@@ -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.1.1",
4
- "description": "Local runaway-agent circuit breaker for AI coding agents. Detects fan-out storms and sustained token burn, blocks the next spawn, and proves what happened. Nothing leaves the machine.",
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
- "replay": "node dist/cli.js replay"
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",