@agentguard-run/burn 0.2.6 → 0.3.0
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 +15 -0
- package/README.md +69 -0
- package/dist/src/adapters/codex.js +6 -0
- package/dist/src/canvas.d.ts +36 -0
- package/dist/src/canvas.js +132 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +111 -25
- package/dist/src/defaults.d.ts +1 -1
- package/dist/src/defaults.js +4 -1
- package/dist/src/frames.d.ts +38 -0
- package/dist/src/frames.js +98 -0
- package/dist/src/gateway.js +3 -0
- package/dist/src/hook/pre-tool-use.d.ts +1 -0
- package/dist/src/hook/pre-tool-use.js +15 -2
- package/dist/src/idle/cache.d.ts +8 -0
- package/dist/src/idle/cache.js +70 -0
- package/dist/src/idle/classify.d.ts +13 -0
- package/dist/src/idle/classify.js +133 -0
- package/dist/src/idle/cli.d.ts +2 -0
- package/dist/src/idle/cli.js +72 -0
- package/dist/src/idle/collect.d.ts +38 -0
- package/dist/src/idle/collect.js +543 -0
- package/dist/src/idle/hook.d.ts +17 -0
- package/dist/src/idle/hook.js +61 -0
- package/dist/src/idle/platform.d.ts +25 -0
- package/dist/src/idle/platform.js +154 -0
- package/dist/src/idle/reap.d.ts +41 -0
- package/dist/src/idle/reap.js +262 -0
- package/dist/src/idle/render.d.ts +10 -0
- package/dist/src/idle/render.js +108 -0
- package/dist/src/idle/types.d.ts +56 -0
- package/dist/src/idle/types.js +8 -0
- package/dist/src/install.js +4 -2
- package/dist/src/policy.js +47 -1
- package/dist/src/presentation.d.ts +5 -0
- package/dist/src/presentation.js +23 -0
- package/dist/src/recording.d.ts +22 -0
- package/dist/src/recording.js +80 -0
- package/dist/src/render-recording.d.ts +13 -0
- package/dist/src/render-recording.js +193 -0
- package/dist/src/replay/render.js +17 -12
- package/dist/src/types.d.ts +6 -0
- package/docs/burn-idle-audit.md +71 -0
- package/docs/burn-render.md +37 -0
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 (2026-09-20)
|
|
4
|
+
|
|
5
|
+
- Add local ps and JSON audits for Claude Code and Codex processes, leftover plugin daemons, automation browsers, workspace disk and current swap. Report held memory and cost exposure with explicit unknown evidence.
|
|
6
|
+
- Add interactive reap with typed row selection, SIGTERM only, a bounded wait and a result for each selection. Never delete workspaces, force-kill processes or close reap itself or its ancestors. Refuse recent terminal activity, dirty or unverified working directories and changed process identities.
|
|
7
|
+
- Add configurable idle-session, browser and orphan-workspace WARN thresholds, including team threshold files. SessionStart uses a five-minute metadata cache and a bounded advisory check that skips on timeout and never blocks.
|
|
8
|
+
- Keep existing plain STOP and WARN hook cards and no-color command bytes unchanged. Add a fixed-grid colored Canvas for terminal screens, with clipped cells, three-row block digits, bars and smaller-terminal layouts.
|
|
9
|
+
- Add opt-in timestamped structured frame recording for commands and offline recorded-run MP4/GIF rendering, local system fonts, numbered PNG fallback and SHA256 provenance. No audit or rendering data leaves the machine.
|
|
10
|
+
- Validation: 248 tests pass with no skips, including the 240-process stress test: 40 admitted, 200 denied, 240 signed receipts and zero lock failures.
|
|
11
|
+
|
|
12
|
+
## 0.2.7 (2026-09-19)
|
|
13
|
+
|
|
14
|
+
- Fix the STOP badge padding so every card row is 66 visible characters wide. Preserve the card wording.
|
|
15
|
+
- Use light box-drawing glyphs for the STOP frame after checking a real Claude Code hook denial. Start the frame below the host Error prefix so its edges align.
|
|
16
|
+
- Test both spawn and model-call cards with ANSI colour enabled and disabled, including wrapped advice.
|
|
17
|
+
|
|
3
18
|
## 0.2.6 (2026-09-19)
|
|
4
19
|
|
|
5
20
|
- Fix inflated enforcement usage: repeated assistant content blocks and copied child histories now share the same provider response identity as `why`. Persist only usage deltas; rebuild old counters once without replacing their receipt chain head.
|
package/README.md
CHANGED
|
@@ -16,6 +16,56 @@ The two init commands print hook configuration for review. Merge the relevant sn
|
|
|
16
16
|
|
|
17
17
|
For Claude Code, configure its `statusLine` command as `agentguard-burn statusline`. It receives the host JSON on stdin. Codex 0.154.0 has no external status command slot. Use `agentguard-burn statusline SESSION_ID` in a companion terminal and the native Codex limit indicators. See [host fields and setup](docs/USAGE_AND_PRICING.md#what-the-hosts-expose).
|
|
18
18
|
|
|
19
|
+
## Local idle audit
|
|
20
|
+
|
|
21
|
+
Burn 0.3.0 reports held memory and disk cost exposure on this machine:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
agentguard-burn ps
|
|
25
|
+
agentguard-burn ps --json
|
|
26
|
+
agentguard-burn reap
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`ps` lists Claude Code and Codex sessions with their Node children, leftover plugin daemons, automation-owned Chrome browsers, and candidate workspaces. Process rows are ranked by resident memory, then workspace rows by disk size. Normal Chrome profiles are excluded. Browser rows show their process count, total resident memory and available window count. Unknown values are explicit. RSS can count shared memory more than once; it is not a bill.
|
|
30
|
+
|
|
31
|
+
Session idle time uses the controlling terminal's access time when readable, otherwise transcript file-write metadata. The table labels the method. A transcript fallback can warn but cannot prove that a terminal has been inactive for 24 hours. Workspace rows show size, last modification, dirty status and open-handle evidence. No transcript content or process arguments are printed or uploaded. Swap is reported only: swap is only released by the operating system on reboot. Burn does not touch it.
|
|
32
|
+
|
|
33
|
+
`reap` prints the same numbered audit, then requires an interactive terminal and numbers typed at the prompt. There is no `--yes` flag and piped confirmation is refused. It sends only SIGTERM to the selected eligible processes, waits, and reports whether they exited. It never sends SIGKILL and never deletes files or directories.
|
|
34
|
+
|
|
35
|
+
Reap refuses:
|
|
36
|
+
|
|
37
|
+
- Any session with terminal input in the last 24 hours, or unavailable terminal-idle evidence. Changing a warning threshold does not change this safety limit.
|
|
38
|
+
- Any process whose working directory has modified or untracked files, or whose directory safety cannot be verified.
|
|
39
|
+
- Every workspace, including any workspace with open file handles. Workspace rows are information only.
|
|
40
|
+
- The process running reap and all its ancestors, including a selected group containing one of them.
|
|
41
|
+
- A process whose identity, ownership or safety evidence changed while confirmation was pending. New or replaced PIDs are never added to the confirmed selection.
|
|
42
|
+
|
|
43
|
+
It rechecks the selected processes before signaling. Permission errors and processes that ignore SIGTERM are reported. They are not force-closed.
|
|
44
|
+
|
|
45
|
+
Add these fields under `thresholds` in the existing `~/.agentguard/burn-policy.json` (or `AGENTGUARD_HOME/burn-policy.json`):
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"thresholds": {
|
|
50
|
+
"idle_session_warn_hours": 24,
|
|
51
|
+
"idle_browser_warn_minutes": 60,
|
|
52
|
+
"orphan_workspace_warn_days": 2
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Merge the fields into your existing policy. Missing or invalid values use the shipped defaults. A local `teamPolicyFile` can point to a JSON file containing `thresholds`; relative paths resolve from the Burn home. Team threshold values take precedence over local threshold values, including these three audit settings. Neither policy loading nor the audit rewrites your policy.
|
|
58
|
+
|
|
59
|
+
The Claude Code and Codex hook snippets now include SessionStart. Review the new `agentguard-burn init claude` or `agentguard-burn init codex` output to update existing installations. At session start, a fresh five-minute local audit cache is checked. A cache miss gets a bounded scan with a 150 ms budget; timeout or unavailable evidence skips the advisory. A warning is one line, never a table and never a deny:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
N idle agent sessions holding X GB, run agentguard-burn ps
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The line is WARN-only in both shadow and enforce modes. If only browser or workspace rows cross their thresholds, the session count can be zero; run `ps` for those details. The full `ps` command refreshes the cache. All collection and caching stays local.
|
|
66
|
+
|
|
67
|
+
macOS uses local process, file-handle and swap metadata. Linux uses `/proc` and available local tools, and says what could not be read. Windows prints `not supported on this platform yet` and exits successfully. See the [docs page draft](docs/burn-idle-audit.md) for the audit boundary and limitations.
|
|
68
|
+
|
|
19
69
|
## Why this session
|
|
20
70
|
|
|
21
71
|
```sh
|
|
@@ -311,6 +361,25 @@ Both token limits default to `null`. Closed gateway sessions are excluded;
|
|
|
311
361
|
hook and gateway session files are both read. WARN never blocks. STOP records
|
|
312
362
|
a would-block decision in shadow and blocks new work only in enforce mode.
|
|
313
363
|
|
|
364
|
+
## Terminal canvas and local recordings
|
|
365
|
+
|
|
366
|
+
Interactive screens use a fixed 104 by 35 cell canvas, with mint for good, slate for neutral, amber for WARN and red for STOP. Smaller terminals drop side detail and paginate rows without wrapping. Use --no-color for the existing plain-text commands and hook cards. Hook JSON and the one-line statusline remain host protocols, with no canvas control sequences inserted.
|
|
367
|
+
|
|
368
|
+
Any command accepts an explicit local recording path:
|
|
369
|
+
|
|
370
|
+
```sh
|
|
371
|
+
agentguard-burn ps --record ./idle-run.jsonl
|
|
372
|
+
agentguard-burn status --no-color --record ./status-run.jsonl
|
|
373
|
+
agentguard-burn replay --record ./history-run.jsonl
|
|
374
|
+
agentguard-burn render ./idle-run.jsonl --mp4 ./idle-run.mp4 --gif ./idle-run.gif
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Each JSONL frame contains a wall-clock timestamp and structured observations, never rendered terminal text. Audit recordings omit raw process arguments and the full process table. They can contain local paths, PIDs, identifiers and resource measurements, so review them before sharing. Recording is opt-in and stays on disk. A recording failure prints a notice without changing a hook decision or cleanup result. Appended recordings must have nondecreasing timestamps.
|
|
378
|
+
|
|
379
|
+
The render command draws recorded Canvas cells at 1920 by 1080 using installed Menlo on macOS or DejaVu Sans Mono on Linux. The title says RECORDED RUN and the footer says recorded run. It preserves frame timestamp intervals and holds the final frame for one second. ffmpeg encodes the MP4 and optional GIF; without it, numbered PNG files and a reproducible ffmpeg command remain. No fonts or assets are fetched. The native PNG dependency loads only for render, never during session-start checks.
|
|
380
|
+
|
|
381
|
+
A provenance JSON beside the requested MP4 records the source recording's SHA256, renderer version, render time, font and frame times. Existing outputs are refused. The source recording stays unchanged. See [the render draft](docs/burn-render.md) for the format and limits.
|
|
382
|
+
|
|
314
383
|
## What it never does
|
|
315
384
|
|
|
316
385
|
No prompts, responses, file contents, or tool inputs are persisted or rendered.
|
|
@@ -34,6 +34,7 @@ const node_fs_1 = require("node:fs");
|
|
|
34
34
|
const events_1 = require("../events");
|
|
35
35
|
const render_1 = require("../replay/render");
|
|
36
36
|
const live_1 = require("../insights/live");
|
|
37
|
+
const hook_1 = require("../idle/hook");
|
|
37
38
|
const pre_tool_use_1 = require("../hook/pre-tool-use");
|
|
38
39
|
/** Fields this renderer must never emit. Codex rejects the whole object if they appear. */
|
|
39
40
|
exports.CODEX_FORBIDDEN_FIELDS = ['continue', 'stopReason', 'suppressOutput', 'decision', 'reason'];
|
|
@@ -43,6 +44,10 @@ function handleCodexHook(raw, gateway, now = Date.now()) {
|
|
|
43
44
|
const parsed = parse(raw);
|
|
44
45
|
if (!parsed)
|
|
45
46
|
return {};
|
|
47
|
+
if (parsed.event === 'SessionStart') {
|
|
48
|
+
const warning = (0, hook_1.sessionStartWarning)(gateway.dataDirectory, (0, pre_tool_use_1.loadPolicy)(gateway.dataDirectory), now);
|
|
49
|
+
return warning ? { systemMessage: warning } : {};
|
|
50
|
+
}
|
|
46
51
|
const observation = (0, live_1.observeTool)(gateway.dataDirectory, { session_id: parsed.sessionId, transcript_path: parsed.transcriptPath, tool_name: parsed.toolName }, 'codex', (0, pre_tool_use_1.loadPolicy)(gateway.dataDirectory), now);
|
|
47
52
|
const output = parsed.event === 'PreToolUse' && SPAWN_TOOLS.has(parsed.toolName) ? handleCodexSpawnHook(raw, gateway, now) : {};
|
|
48
53
|
return observation.messages.length ? { ...output, systemMessage: [output.systemMessage, ...observation.messages].filter(Boolean).join('\n') } : output;
|
|
@@ -200,6 +205,7 @@ function walk(value, path) {
|
|
|
200
205
|
function codexHooksSnippet(command) {
|
|
201
206
|
return {
|
|
202
207
|
hooks: {
|
|
208
|
+
SessionStart: [{ matcher: '.*', hooks: [{ type: 'command', command, timeout: 1 }] }],
|
|
203
209
|
PreToolUse: [{ matcher: '.*', hooks: [{ type: 'command', command, timeout: 15 }] }],
|
|
204
210
|
},
|
|
205
211
|
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type CanvasColor = 'mint' | 'slate' | 'amber' | 'red' | 'white' | 'none';
|
|
2
|
+
export type CanvasColorMode = 'truecolor' | '256' | 'none';
|
|
3
|
+
export interface CanvasCell {
|
|
4
|
+
char: string;
|
|
5
|
+
color: CanvasColor;
|
|
6
|
+
}
|
|
7
|
+
export interface CanvasSnapshot {
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
cells: CanvasCell[][];
|
|
11
|
+
}
|
|
12
|
+
export declare const CANVAS_PALETTE: Record<Exclude<CanvasColor, 'none'>, {
|
|
13
|
+
hex: string;
|
|
14
|
+
rgb: [number, number, number];
|
|
15
|
+
ansi256: number;
|
|
16
|
+
}>;
|
|
17
|
+
export declare class Canvas {
|
|
18
|
+
readonly width: number;
|
|
19
|
+
readonly height: number;
|
|
20
|
+
readonly colorMode: CanvasColorMode;
|
|
21
|
+
private readonly cells;
|
|
22
|
+
constructor(options?: {
|
|
23
|
+
width?: number;
|
|
24
|
+
height?: number;
|
|
25
|
+
colour?: boolean;
|
|
26
|
+
colorMode?: CanvasColorMode;
|
|
27
|
+
});
|
|
28
|
+
put(row: number, col: number, text: string, color?: CanvasColor): this;
|
|
29
|
+
digits(row: number, col: number, value: string | number, color?: CanvasColor): this;
|
|
30
|
+
bar(row: number, col: number, width: number, fraction: number, color?: CanvasColor): this;
|
|
31
|
+
rule(row: number, col: number, width?: number): this;
|
|
32
|
+
header(left?: string, right?: string): this;
|
|
33
|
+
footer(hints: string, clock: string): this;
|
|
34
|
+
toJSON(): CanvasSnapshot;
|
|
35
|
+
render(): string;
|
|
36
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Canvas = exports.CANVAS_PALETTE = void 0;
|
|
4
|
+
/** A clipped grid of terminal cells. Rendering adds only palette-owned ANSI. */
|
|
5
|
+
const node_util_1 = require("node:util");
|
|
6
|
+
exports.CANVAS_PALETTE = {
|
|
7
|
+
mint: { hex: '#2ABCB4', rgb: [42, 188, 180], ansi256: 37 },
|
|
8
|
+
slate: { hex: '#64748B', rgb: [100, 116, 139], ansi256: 66 },
|
|
9
|
+
amber: { hex: '#F59E0B', rgb: [245, 158, 11], ansi256: 208 },
|
|
10
|
+
red: { hex: '#EF4444', rgb: [239, 68, 68], ansi256: 203 },
|
|
11
|
+
white: { hex: '#E2E8F0', rgb: [226, 232, 240], ansi256: 254 },
|
|
12
|
+
};
|
|
13
|
+
const GLYPHS = {
|
|
14
|
+
'0': ['█▀█', '█ █', '█▄█'], '1': [' ▀█', ' █', ' █'],
|
|
15
|
+
'2': ['▀▀█', '▄▀▀', '█▄▄'], '3': ['▀▀█', ' ▀█', '▄▄█'],
|
|
16
|
+
'4': ['█ █', '▀▀█', ' █'], '5': ['█▀▀', '▀▀█', '▄▄█'],
|
|
17
|
+
'6': ['█▀▀', '█▀█', '█▄█'], '7': ['▀▀█', ' █', ' █'],
|
|
18
|
+
'8': ['█▀█', '█▀█', '█▄█'], '9': ['█▀█', '▀▀█', '▄▄█'],
|
|
19
|
+
'.': [' ', ' ', '▄'], '-': [' ', '▀▀▀', ' '],
|
|
20
|
+
};
|
|
21
|
+
const colors = new Set(['mint', 'slate', 'amber', 'red', 'white', 'none']);
|
|
22
|
+
function dimension(value, fallback, limit) {
|
|
23
|
+
return Math.max(1, Math.min(limit, Math.floor(typeof value === 'number' && Number.isFinite(value) ? value : fallback)));
|
|
24
|
+
}
|
|
25
|
+
function wide(code) {
|
|
26
|
+
return code >= 0x1100 && (code <= 0x115f || code === 0x2329 || code === 0x232a
|
|
27
|
+
|| (code >= 0x2e80 && code <= 0xa4cf && code !== 0x303f) || (code >= 0xac00 && code <= 0xd7a3)
|
|
28
|
+
|| (code >= 0xf900 && code <= 0xfaff) || (code >= 0xfe10 && code <= 0xfe19)
|
|
29
|
+
|| (code >= 0xfe30 && code <= 0xfe6f) || (code >= 0xff00 && code <= 0xff60)
|
|
30
|
+
|| (code >= 0xffe0 && code <= 0xffe6) || (code >= 0x1f000 && code <= 0x1faff)
|
|
31
|
+
|| (code >= 0x20000 && code <= 0x3fffd));
|
|
32
|
+
}
|
|
33
|
+
/** Keep one printable column per cell; preserve bullets and box-drawing rules. */
|
|
34
|
+
function characters(text) {
|
|
35
|
+
return Array.from((0, node_util_1.stripVTControlCharacters)(String(text)).replace(/[\r\n\t]/g, ' ').replace(/[\p{Cc}\p{Cf}\p{M}]/gu, ''))
|
|
36
|
+
.map(char => wide(char.codePointAt(0)) ? '?' : char);
|
|
37
|
+
}
|
|
38
|
+
class Canvas {
|
|
39
|
+
width;
|
|
40
|
+
height;
|
|
41
|
+
colorMode;
|
|
42
|
+
cells;
|
|
43
|
+
constructor(options = {}) {
|
|
44
|
+
this.width = dimension(options.width, Math.min(104, process.stdout.columns || 104), 500);
|
|
45
|
+
this.height = dimension(options.height, Math.min(35, process.stdout.rows || 35), 200);
|
|
46
|
+
this.colorMode = options.colour === false ? 'none' : options.colorMode
|
|
47
|
+
?? (options.colour !== true && process.env.NO_COLOR !== undefined ? 'none' : /^(truecolor|24bit)$/i.test(process.env.COLORTERM || '') ? 'truecolor' : '256');
|
|
48
|
+
this.cells = Array.from({ length: this.height }, () => Array.from({ length: this.width }, () => ({ char: ' ', color: 'none' })));
|
|
49
|
+
}
|
|
50
|
+
put(row, col, text, color = 'white') {
|
|
51
|
+
if (!Number.isFinite(row) || !Number.isFinite(col))
|
|
52
|
+
return this;
|
|
53
|
+
row = Math.floor(row);
|
|
54
|
+
col = Math.floor(col);
|
|
55
|
+
if (row < 0 || row >= this.height || col >= this.width)
|
|
56
|
+
return this;
|
|
57
|
+
const selected = colors.has(color) ? color : 'none';
|
|
58
|
+
for (const [index, char] of characters(text).entries()) {
|
|
59
|
+
const x = col + index;
|
|
60
|
+
if (x >= this.width)
|
|
61
|
+
break;
|
|
62
|
+
if (x >= 0)
|
|
63
|
+
this.cells[row][x] = { char, color: selected };
|
|
64
|
+
}
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
digits(row, col, value, color = 'mint') {
|
|
68
|
+
let x = col;
|
|
69
|
+
for (const char of characters(String(value))) {
|
|
70
|
+
const glyph = GLYPHS[char] ?? [' ', ' ', ' '];
|
|
71
|
+
for (let y = 0; y < 3; y++)
|
|
72
|
+
this.put(row + y, x, glyph[y], color);
|
|
73
|
+
x += glyph[0].length + 1;
|
|
74
|
+
if (x >= this.width)
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
bar(row, col, width, fraction, color = 'mint') {
|
|
80
|
+
if (!Number.isFinite(width) || width <= 0)
|
|
81
|
+
return this;
|
|
82
|
+
const length = Math.min(500, Math.floor(width));
|
|
83
|
+
const ratio = Number.isFinite(fraction) ? Math.max(0, Math.min(1, fraction)) : 0;
|
|
84
|
+
const filled = Math.round(length * ratio);
|
|
85
|
+
return this.put(row, col, '█'.repeat(filled) + '░'.repeat(length - filled), color);
|
|
86
|
+
}
|
|
87
|
+
rule(row, col, width = this.width - col) {
|
|
88
|
+
if (!Number.isFinite(width) || width <= 0)
|
|
89
|
+
return this;
|
|
90
|
+
return this.put(row, col, '─'.repeat(Math.min(500, Math.floor(width))), 'slate');
|
|
91
|
+
}
|
|
92
|
+
header(left = 'AGENTGUARD / BURN', right = 'LIVE') {
|
|
93
|
+
const rightChars = characters(right).slice(0, this.width);
|
|
94
|
+
const start = this.width - rightChars.length;
|
|
95
|
+
this.put(0, 0, characters(left).slice(0, Math.max(0, start - (rightChars.length ? 2 : 0))).join(''), 'mint');
|
|
96
|
+
this.put(0, start, rightChars.join(''), 'slate');
|
|
97
|
+
if (this.height > 1)
|
|
98
|
+
this.rule(1, 0);
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
footer(hints, clock) {
|
|
102
|
+
if (this.height > 1)
|
|
103
|
+
this.rule(this.height - 2, 0);
|
|
104
|
+
const clockChars = characters(clock).slice(0, this.width), start = this.width - clockChars.length;
|
|
105
|
+
this.put(this.height - 1, 0, characters(hints).slice(0, Math.max(0, start - (clockChars.length ? 2 : 0))).join(''), 'slate');
|
|
106
|
+
return this.put(this.height - 1, start, clockChars.join(''), 'white');
|
|
107
|
+
}
|
|
108
|
+
toJSON() {
|
|
109
|
+
return { width: this.width, height: this.height, cells: this.cells.map(row => row.map(cell => ({ ...cell }))) };
|
|
110
|
+
}
|
|
111
|
+
render() {
|
|
112
|
+
return this.cells.map(row => {
|
|
113
|
+
if (this.colorMode === 'none')
|
|
114
|
+
return row.map(cell => cell.char).join('');
|
|
115
|
+
let current = 'none', text = '';
|
|
116
|
+
for (const cell of row) {
|
|
117
|
+
if (cell.color !== current) {
|
|
118
|
+
current = cell.color;
|
|
119
|
+
if (current === 'none')
|
|
120
|
+
text += '\x1b[0m';
|
|
121
|
+
else {
|
|
122
|
+
const color = exports.CANVAS_PALETTE[current];
|
|
123
|
+
text += this.colorMode === 'truecolor' ? `\x1b[38;2;${color.rgb.join(';')}m` : `\x1b[38;5;${color.ansi256}m`;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
text += cell.char;
|
|
127
|
+
}
|
|
128
|
+
return text + (current !== 'none' ? '\x1b[0m' : '');
|
|
129
|
+
}).join('\n');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
exports.Canvas = Canvas;
|
package/dist/src/cli.d.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* agentguard-burn
|
|
4
4
|
*
|
|
5
|
+
* ps local agent processes, held memory and orphan workspace disk
|
|
6
|
+
* reap interactively SIGTERM selected safe processes, never delete
|
|
5
7
|
* replay what enforcement would have stopped, on your history
|
|
6
8
|
* calibrate fit thresholds to your own usage, write shadow policy
|
|
7
9
|
* status mode, shadow decisions, eligibility, every host's sessions
|
package/dist/src/cli.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* agentguard-burn
|
|
5
5
|
*
|
|
6
|
+
* ps local agent processes, held memory and orphan workspace disk
|
|
7
|
+
* reap interactively SIGTERM selected safe processes, never delete
|
|
6
8
|
* replay what enforcement would have stopped, on your history
|
|
7
9
|
* calibrate fit thresholds to your own usage, write shadow policy
|
|
8
10
|
* status mode, shadow decisions, eligibility, every host's sessions
|
|
@@ -16,7 +18,42 @@
|
|
|
16
18
|
* cursor-hook (internal) Cursor hook entry point
|
|
17
19
|
* codex-hook (internal) Codex hook entry point
|
|
18
20
|
*/
|
|
21
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
24
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
25
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
26
|
+
}
|
|
27
|
+
Object.defineProperty(o, k2, desc);
|
|
28
|
+
}) : (function(o, m, k, k2) {
|
|
29
|
+
if (k2 === undefined) k2 = k;
|
|
30
|
+
o[k2] = m[k];
|
|
31
|
+
}));
|
|
32
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
33
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
34
|
+
}) : function(o, v) {
|
|
35
|
+
o["default"] = v;
|
|
36
|
+
});
|
|
37
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
38
|
+
var ownKeys = function(o) {
|
|
39
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
40
|
+
var ar = [];
|
|
41
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
42
|
+
return ar;
|
|
43
|
+
};
|
|
44
|
+
return ownKeys(o);
|
|
45
|
+
};
|
|
46
|
+
return function (mod) {
|
|
47
|
+
if (mod && mod.__esModule) return mod;
|
|
48
|
+
var result = {};
|
|
49
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
50
|
+
__setModuleDefault(result, mod);
|
|
51
|
+
return result;
|
|
52
|
+
};
|
|
53
|
+
})();
|
|
19
54
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
55
|
+
const presentation_1 = require("./presentation");
|
|
56
|
+
const recording_1 = require("./recording");
|
|
20
57
|
const node_fs_1 = require("node:fs");
|
|
21
58
|
const node_os_1 = require("node:os");
|
|
22
59
|
const node_path_1 = require("node:path");
|
|
@@ -37,8 +74,12 @@ const eligibility_1 = require("./eligibility");
|
|
|
37
74
|
const sessions_1 = require("./insights/sessions");
|
|
38
75
|
const transcript_1 = require("./insights/transcript");
|
|
39
76
|
const live_1 = require("./insights/live");
|
|
77
|
+
const attribution_1 = require("./insights/attribution");
|
|
78
|
+
const pricing_1 = require("./insights/pricing");
|
|
79
|
+
const transcript_2 = require("./insights/transcript");
|
|
40
80
|
const render_2 = require("./insights/render");
|
|
41
81
|
const blocks_1 = require("./insights/blocks");
|
|
82
|
+
const cli_1 = require("./idle/cli");
|
|
42
83
|
const HOME = process.env.AGENTGUARD_HOME ?? (0, node_path_1.join)((0, node_os_1.homedir)(), '.agentguard');
|
|
43
84
|
const PROXY_HOSTS = ['ollama', 'vllm', 'lm-studio', 'openai-compatible'];
|
|
44
85
|
function readStdinJson() {
|
|
@@ -75,23 +116,42 @@ function shadowEligibility() {
|
|
|
75
116
|
return (0, eligibility_1.computeShadowEligibility)(readDecisions());
|
|
76
117
|
}
|
|
77
118
|
async function main(argv) {
|
|
78
|
-
const [command, ...
|
|
119
|
+
const [command, ...rawRest] = argv;
|
|
120
|
+
const noColor = rawRest.includes('--no-color');
|
|
121
|
+
const rest = rawRest.filter(value => value !== '--no-color');
|
|
122
|
+
const show = (data, plain, protocol = false) => (0, presentation_1.present)(data, plain, { colour: Boolean(process.stdout.isTTY) && !noColor && process.env.NO_COLOR === undefined, protocol });
|
|
79
123
|
const flag = (name) => {
|
|
80
124
|
const i = rest.indexOf(name);
|
|
81
125
|
return i >= 0 ? rest[i + 1] : undefined;
|
|
82
126
|
};
|
|
83
|
-
const has = (name) =>
|
|
127
|
+
const has = (name) => rawRest.includes(name);
|
|
84
128
|
switch (command) {
|
|
129
|
+
case 'ps':
|
|
130
|
+
case 'reap':
|
|
131
|
+
return (0, cli_1.runIdleCommand)(command, rawRest, HOME);
|
|
132
|
+
case 'render': {
|
|
133
|
+
if (process.platform === 'win32') {
|
|
134
|
+
process.stdout.write('not supported on this platform yet\n');
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
const source = rest[0], mp4 = flag('--mp4'), gif = flag('--gif');
|
|
138
|
+
if (!source || source.startsWith('--') || !mp4 || mp4.startsWith('--') || (has('--gif') && (!gif || gif.startsWith('--'))))
|
|
139
|
+
throw new Error('Usage: agentguard-burn render <run.jsonl> --mp4 <out.mp4> [--gif <out.gif>]');
|
|
140
|
+
const { renderRecording } = await Promise.resolve().then(() => __importStar(require('./render-recording')));
|
|
141
|
+
const result = await renderRecording(source, { mp4, gif });
|
|
142
|
+
show({ kind: 'command', command: 'render', values: { ...result } }, `PNG frames: ${result.framesDir}\n${result.mp4 ? `MP4: ${result.mp4}\n` : `ffmpeg unavailable. Run: ${result.ffmpegCommand}\n`}${result.gif ? `GIF: ${result.gif}\n` : ''}Provenance: ${result.provenancePath}\n`);
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
85
145
|
case 'blocks': {
|
|
86
146
|
const session = rest.find(value => value !== 'json' && value !== '--json');
|
|
87
147
|
const report = (0, blocks_1.blocksReport)(HOME, session, { rates: (0, live_1.readPricing)(HOME, (0, pre_tool_use_1.loadPolicy)(HOME)) });
|
|
88
|
-
|
|
148
|
+
show({ kind: 'command', command, values: { ...report } }, (rest.includes('json') || has('--json') ? JSON.stringify(report, null, 2) : (0, blocks_1.renderBlocks)(report)) + '\n', rest.includes('json') || has('--json'));
|
|
89
149
|
return 0;
|
|
90
150
|
}
|
|
91
151
|
case 'why': {
|
|
92
152
|
const location = (0, sessions_1.selectInsightTranscript)(rest.find(value => value !== 'json'));
|
|
93
153
|
const transcript = (0, transcript_1.readInsightSession)(location.path, { host: location.host, sessionId: location.sessionId });
|
|
94
|
-
|
|
154
|
+
show({ kind: 'command', command, values: { session: transcript.sessionId, observations: transcript.observations, diagnostics: transcript.diagnostics, attribution: (0, attribution_1.attributeTurns)((0, transcript_2.deduplicateTurns)(transcript.turns)).buckets, prices: (0, transcript_2.deduplicateTurns)(transcript.turns).map(turn => (0, pricing_1.priceTurn)(turn, (0, live_1.readPricing)(HOME, (0, pre_tool_use_1.loadPolicy)(HOME)))) } }, rest.includes('json') ? JSON.stringify(transcript, null, 2) + '\n' : (0, render_2.renderWhy)(transcript, (0, live_1.readPricing)(HOME, (0, pre_tool_use_1.loadPolicy)(HOME))) + '\n', rest.includes('json'));
|
|
95
155
|
return 0;
|
|
96
156
|
}
|
|
97
157
|
case 'rewrites': {
|
|
@@ -99,11 +159,12 @@ async function main(argv) {
|
|
|
99
159
|
const locations = session === 'all' ? (0, sessions_1.discoverInsightTranscripts)() : [(0, sessions_1.selectInsightTranscript)(session)];
|
|
100
160
|
const transcripts = locations.map(location => (0, transcript_1.readInsightTranscript)(location.path, { host: location.host, sessionId: location.sessionId }));
|
|
101
161
|
const rates = (0, live_1.readPricing)(HOME, (0, pre_tool_use_1.loadPolicy)(HOME));
|
|
102
|
-
|
|
162
|
+
show({ kind: 'command', command, values: (0, render_2.rewriteReport)(transcripts, rates) }, rest.includes('json') ? JSON.stringify((0, render_2.rewriteReport)(transcripts, rates), null, 2) + '\n' : (0, render_2.renderRewrites)(transcripts, rates) + '\n', rest.includes('json'));
|
|
103
163
|
return 0;
|
|
104
164
|
}
|
|
105
165
|
case 'pricing': {
|
|
106
|
-
|
|
166
|
+
const rates = (0, live_1.readPricing)(HOME, (0, pre_tool_use_1.loadPolicy)(HOME));
|
|
167
|
+
show({ kind: 'command', command, values: { rates: { ...pricing_1.MODEL_PRICING, ...rates } } }, (0, render_2.renderPricing)(rates) + '\n');
|
|
107
168
|
return 0;
|
|
108
169
|
}
|
|
109
170
|
case 'statusline':
|
|
@@ -112,7 +173,7 @@ async function main(argv) {
|
|
|
112
173
|
const payload = raw && typeof raw === 'object' ? raw : {};
|
|
113
174
|
const location = (0, sessions_1.selectInsightTranscript)(payload.transcript_path || rest[0] || payload.session_id);
|
|
114
175
|
const observation = (0, live_1.observeTool)(HOME, { ...payload, session_id: payload.session_id || location.sessionId, transcript_path: location.path }, location.host, (0, pre_tool_use_1.loadPolicy)(HOME));
|
|
115
|
-
|
|
176
|
+
show({ kind: 'command', command, values: { snapshot: observation.snapshot ? { host: observation.snapshot.host, session: observation.snapshot.sessionId, pace: observation.snapshot.pace, forecast: observation.snapshot.forecast, lastTurnTokens: observation.snapshot.lastTurnTokens, windows: observation.snapshot.windows } : null } }, (observation.snapshot ? (0, live_1.renderStatusLine)(observation.snapshot) : 'AgentGuard pace unavailable: local usage metadata is not readable.') + '\n', command === 'statusline');
|
|
116
177
|
return observation.snapshot ? 0 : 1;
|
|
117
178
|
}
|
|
118
179
|
case 'hook': {
|
|
@@ -124,6 +185,11 @@ async function main(argv) {
|
|
|
124
185
|
catch {
|
|
125
186
|
// Unparseable payload: allow. We never break the host over our own bug.
|
|
126
187
|
}
|
|
188
|
+
if (input?.hook_event_name === 'SessionStart') {
|
|
189
|
+
const warning = await (0, cli_1.prepareSessionStart)(input, HOME);
|
|
190
|
+
process.stdout.write(JSON.stringify(warning ? { continue: true, suppressOutput: false, systemMessage: warning } : { continue: true, suppressOutput: true }));
|
|
191
|
+
return 0;
|
|
192
|
+
}
|
|
127
193
|
process.stdout.write(JSON.stringify((0, pre_tool_use_1.handlePreToolUse)(input, HOME)));
|
|
128
194
|
return 0;
|
|
129
195
|
}
|
|
@@ -132,12 +198,7 @@ async function main(argv) {
|
|
|
132
198
|
const paths = rest.filter((a) => a.endsWith('.jsonl'));
|
|
133
199
|
const targets = paths.length ? paths : (0, simulate_1.discoverTranscripts)();
|
|
134
200
|
const summary = (0, simulate_1.replayAll)(targets, policy.thresholds, Number(flag('--min-tokens') ?? 0));
|
|
135
|
-
|
|
136
|
-
process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
process.stdout.write(`${(0, render_1.renderReplay)(summary, { colour: !has('--no-color'), top: Number(flag('--top') ?? 8) })}\n`);
|
|
140
|
-
}
|
|
201
|
+
show({ kind: 'replay', summary }, has('--json') ? `${JSON.stringify(summary, null, 2)}\n` : `${(0, render_1.renderReplay)(summary, { colour: false, top: Number(flag('--top') ?? 8) })}\n`, has('--json'));
|
|
141
202
|
return 0;
|
|
142
203
|
}
|
|
143
204
|
case 'calibrate': {
|
|
@@ -145,7 +206,7 @@ async function main(argv) {
|
|
|
145
206
|
const result = (0, calibrate_1.calibrate)(summary.sessions);
|
|
146
207
|
savePolicy(result.policy);
|
|
147
208
|
const t = result.policy.thresholds;
|
|
148
|
-
|
|
209
|
+
show({ kind: 'command', command, values: { ...result } }, [
|
|
149
210
|
`Calibrated from ${result.sessionsUsed} of ${result.sessionsTotal} sessions (${result.sessionsExcluded} excluded as pathological).`,
|
|
150
211
|
` raw p90 ${(result.rawWarnP90 / 1e9).toFixed(2)}B raw p99 ${(result.rawStopP99 / 1e9).toFixed(2)}B`,
|
|
151
212
|
` sustained WARN ${(t.sustained.warnTokens / 1e9).toFixed(2)}B STOP ${(t.sustained.stopTokens / 1e9).toFixed(2)}B`,
|
|
@@ -163,7 +224,7 @@ async function main(argv) {
|
|
|
163
224
|
const gateway = new gateway_1.Gateway(HOME, { sign: false });
|
|
164
225
|
const lc = policy.thresholds.localCompute;
|
|
165
226
|
const override = (0, override_1.readOverride)(HOME);
|
|
166
|
-
|
|
227
|
+
show({ kind: 'command', command, values: { mode: policy.mode, thresholds: policy.thresholds, eligibility: e, override: override ? { active: true, once: override.once, until: override.until } : null, hosts: ['claude', 'cursor', 'codex'].map(h => { const { command: omitted, ...metadata } = (0, install_1.health)(h); return metadata; }), sessions: [...(0, status_1.readHookSessions)(HOME), ...gateway.sessions()].map(row => ({ sessionId: row.sessionId, hosts: row.hosts, tokens: row.state.totalTokens, spawns: row.state.spawnCount, liveSpawns: row.liveSpawns, capabilities: row.capabilities })) } }, [
|
|
167
228
|
`mode: ${policy.mode}${policy.mode === 'enforce' ? ' (the next STOP blocks)' : ' (recording only; nothing is blocked)'}`,
|
|
168
229
|
`thresholds: fan-out lifetime WARN ${policy.thresholds.fanout.warn}; STOP above ${policy.thresholds.fanout.stop} spawns in ${policy.thresholds.fanout.windowActiveMinutes ?? 120} active minutes; depth above ${policy.thresholds.fanout.maxDepth}`,
|
|
169
230
|
`spawn-rate: WARN ${policy.thresholds.spawnRate?.warn ?? 8}; STOP ${policy.thresholds.spawnRate?.stop ?? 16} in ${policy.thresholds.spawnRate?.windowActiveMinutes ?? 15} active minutes; ${policy.thresholds.spawnRate?.enforce === false ? 'shadow only' : 'enforcing in enforce mode'}`,
|
|
@@ -206,7 +267,7 @@ async function main(argv) {
|
|
|
206
267
|
process.stderr.write(`init: refusing to touch ${(0, install_1.configPath)(target)}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
207
268
|
return 1;
|
|
208
269
|
}
|
|
209
|
-
|
|
270
|
+
show({ kind: 'command', command, values: { host: target, changed: result.changed, file: result.file, mode: policy.mode } }, (result.changed ? `Wrote ${result.file}${result.backup ? ` (backup: ${result.backup})` : ''}\n` : `${result.file} already has the current hook; nothing changed.\n`) +
|
|
210
271
|
` ${result.command}\n` +
|
|
211
272
|
(label ? `${label}\n` : '') +
|
|
212
273
|
tail +
|
|
@@ -214,7 +275,7 @@ async function main(argv) {
|
|
|
214
275
|
return 0;
|
|
215
276
|
}
|
|
216
277
|
const snippet = target === 'cursor' ? (0, cursor_1.cursorHooksSnippet)(`node ${cli} cursor-hook`) : target === 'codex' ? (0, codex_1.codexHooksSnippet)(`node ${cli} codex-hook`) : (0, pre_tool_use_1.settingsSnippet)(`node ${cli} hook`);
|
|
217
|
-
|
|
278
|
+
show({ kind: 'command', command, values: { host: target, config: (0, install_1.configPath)(target), snippet, mode: policy.mode } }, `Add this to ${(0, install_1.configPath)(target).replace(process.env.HOME ?? '', '~')} (merge into existing "hooks"), or run: agentguard-burn init ${target} --write\n\n${JSON.stringify(snippet, null, 2)}\n\n` +
|
|
218
279
|
(label ? `${label}\n` : '') +
|
|
219
280
|
tail +
|
|
220
281
|
(target === 'claude' ? `Other hosts: agentguard-burn init cursor | init codex | proxy --upstream http://127.0.0.1:11434 --host ollama\n` : ''));
|
|
@@ -226,8 +287,14 @@ async function main(argv) {
|
|
|
226
287
|
return 0;
|
|
227
288
|
}
|
|
228
289
|
case 'codex-hook': {
|
|
290
|
+
const input = readStdinJson();
|
|
291
|
+
if (input && typeof input === 'object' && input.hook_event_name === 'SessionStart') {
|
|
292
|
+
const warning = await (0, cli_1.prepareSessionStart)(input, HOME);
|
|
293
|
+
process.stdout.write(JSON.stringify(warning ? { systemMessage: warning } : {}));
|
|
294
|
+
return 0;
|
|
295
|
+
}
|
|
229
296
|
const gateway = new gateway_1.Gateway(HOME);
|
|
230
|
-
process.stdout.write(JSON.stringify((0, codex_1.handleCodexHook)(
|
|
297
|
+
process.stdout.write(JSON.stringify((0, codex_1.handleCodexHook)(input, gateway)));
|
|
231
298
|
return 0;
|
|
232
299
|
}
|
|
233
300
|
case 'proxy': {
|
|
@@ -251,6 +318,7 @@ async function main(argv) {
|
|
|
251
318
|
log: (l) => process.stderr.write(`${l}\n`),
|
|
252
319
|
});
|
|
253
320
|
const policy = (0, pre_tool_use_1.loadPolicy)(HOME);
|
|
321
|
+
(0, recording_1.recordFrame)({ kind: 'command', command, values: { listening: running.address.origin, host, mode: policy.mode } });
|
|
254
322
|
process.stderr.write(`agentguard-burn proxy ${running.address.origin} -> ${upstreamRaw} (${host}, ${policy.mode} mode)\n` +
|
|
255
323
|
`Point your agent at ${running.address.origin}. Send x-agentguard-session: <id> so calls join a session; without it each client port is its own low-confidence session.\n` +
|
|
256
324
|
`Ctrl-C to stop.\n`);
|
|
@@ -263,7 +331,7 @@ async function main(argv) {
|
|
|
263
331
|
}
|
|
264
332
|
case 'conformance': {
|
|
265
333
|
const result = await (0, conformance_1.runConformance)();
|
|
266
|
-
|
|
334
|
+
show({ kind: 'command', command, values: { ok: result.ok, checks: result.checks } }, result.text + '\n');
|
|
267
335
|
return result.ok ? 0 : 1;
|
|
268
336
|
}
|
|
269
337
|
case 'enforce': {
|
|
@@ -274,18 +342,18 @@ async function main(argv) {
|
|
|
274
342
|
return 2;
|
|
275
343
|
}
|
|
276
344
|
savePolicy({ ...policy, mode: 'enforce' });
|
|
277
|
-
|
|
345
|
+
show({ kind: 'command', command, values: { mode: 'enforce' } }, 'Enforcement ON. The next STOP will block the spawn.\n');
|
|
278
346
|
return 0;
|
|
279
347
|
}
|
|
280
348
|
case 'shadow': {
|
|
281
349
|
savePolicy({ ...(0, pre_tool_use_1.loadPolicy)(HOME), mode: 'shadow' });
|
|
282
|
-
|
|
350
|
+
show({ kind: 'command', command, values: { mode: 'shadow' } }, 'Back to shadow mode. Decisions are recorded, nothing is blocked.\n');
|
|
283
351
|
return 0;
|
|
284
352
|
}
|
|
285
353
|
case 'resume': {
|
|
286
354
|
if (has('--clear')) {
|
|
287
355
|
(0, override_1.clearOverride)(HOME);
|
|
288
|
-
|
|
356
|
+
show({ kind: 'command', command, values: { override: false } }, 'Override cleared. The next STOP blocks.\n');
|
|
289
357
|
return 0;
|
|
290
358
|
}
|
|
291
359
|
const reason = flag('--reason');
|
|
@@ -296,13 +364,17 @@ async function main(argv) {
|
|
|
296
364
|
const once = has('--once');
|
|
297
365
|
const now = Date.now();
|
|
298
366
|
(0, override_1.writeOverride)(HOME, once ? { at: now, once: true, reason } : { at: now, once: false, reason, until: now + override_1.OVERRIDE_WINDOW_MS });
|
|
299
|
-
|
|
367
|
+
show({ kind: 'command', command, values: { once, at: now, until: once ? null : now + override_1.OVERRIDE_WINDOW_MS } }, once
|
|
300
368
|
? `Override recorded for the next STOP only, on any host: "${reason}"\n`
|
|
301
369
|
: `Override recorded for the next ${override_1.OVERRIDE_WINDOW_MS / 60000} minutes, on any host: "${reason}"\n (agentguard-burn resume --clear to end it early)\n`);
|
|
302
370
|
return 0;
|
|
303
371
|
}
|
|
304
372
|
default:
|
|
305
|
-
|
|
373
|
+
show({ kind: 'command', command: 'help', values: { commands: ['ps', 'reap', 'render', 'blocks', 'why', 'rewrites', 'pace', 'statusline', 'pricing', 'replay', 'calibrate', 'status', 'init', 'enforce', 'shadow', 'resume', 'proxy', 'conformance'] } }, 'agentguard-burn <ps|reap|render|blocks|why|rewrites|pace|statusline|pricing|replay|calibrate|status|init|enforce|shadow|resume|proxy|conformance>\n' +
|
|
374
|
+
' ps [--json] local agent processes, held memory and workspace disk\n' +
|
|
375
|
+
' render <run.jsonl> --mp4 <out.mp4> [--gif <out.gif>] local recorded-run export\n' +
|
|
376
|
+
' --record <file.jsonl> append structured frames for any command; --no-color keeps plain output\n' +
|
|
377
|
+
' reap numbered interactive SIGTERM, never delete or force-kill\n' +
|
|
306
378
|
' blocks [session|all] stored WARN/STOP receipts and that session\'s child usage\n' +
|
|
307
379
|
' why [session] token attribution and API list-price equivalents\n' +
|
|
308
380
|
' rewrites [session|all] full-prefix cache writes by cause\n' +
|
|
@@ -322,7 +394,21 @@ async function main(argv) {
|
|
|
322
394
|
return command ? 64 : 0;
|
|
323
395
|
}
|
|
324
396
|
}
|
|
325
|
-
|
|
397
|
+
async function run() {
|
|
398
|
+
const parsed = (0, recording_1.recordingArguments)(process.argv.slice(2));
|
|
399
|
+
(0, recording_1.configureRecording)(parsed.file);
|
|
400
|
+
try {
|
|
401
|
+
const code = await main(parsed.args);
|
|
402
|
+
if (!(0, recording_1.recordedFrameCount)())
|
|
403
|
+
(0, recording_1.recordFrame)({ kind: 'command', command: parsed.args[0] ?? 'help', values: { exitCode: code } });
|
|
404
|
+
return code;
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
(0, recording_1.recordFrame)({ kind: 'command', command: parsed.args[0] ?? 'help', values: { exitCode: 4, failed: true } });
|
|
408
|
+
throw error;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
run().then((code) => process.exit(code), (error) => {
|
|
326
412
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
327
413
|
process.exit(4);
|
|
328
414
|
});
|
package/dist/src/defaults.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { Policy, Thresholds } from './types';
|
|
|
11
11
|
* Spawn rate 8/16 over 15 active minutes warns/stops by default. Sustained
|
|
12
12
|
* thresholds remain 3.5B/5B pending review of deduplicated calibration.
|
|
13
13
|
* This is a dated replay snapshot and an upper bound, not labelled evidence
|
|
14
|
-
* of false-positive rates or guaranteed
|
|
14
|
+
* of false-positive rates or guaranteed reductions in cost exposure.
|
|
15
15
|
*
|
|
16
16
|
* A previous draft shipped a 50M session budget. Routine sessions on the same
|
|
17
17
|
* machine run 1.4B to 3.5B, so that default would have fired within minutes on
|
package/dist/src/defaults.js
CHANGED
|
@@ -13,7 +13,7 @@ exports.SHADOW_MIN_DAYS = exports.SHADOW_MIN_DECISIONS = exports.ACTIVE_GAP_CAP_
|
|
|
13
13
|
* Spawn rate 8/16 over 15 active minutes warns/stops by default. Sustained
|
|
14
14
|
* thresholds remain 3.5B/5B pending review of deduplicated calibration.
|
|
15
15
|
* This is a dated replay snapshot and an upper bound, not labelled evidence
|
|
16
|
-
* of false-positive rates or guaranteed
|
|
16
|
+
* of false-positive rates or guaranteed reductions in cost exposure.
|
|
17
17
|
*
|
|
18
18
|
* A previous draft shipped a 50M session budget. Routine sessions on the same
|
|
19
19
|
* machine run 1.4B to 3.5B, so that default would have fired within minutes on
|
|
@@ -25,6 +25,9 @@ exports.SHADOW_MIN_DAYS = exports.SHADOW_MIN_DECISIONS = exports.ACTIVE_GAP_CAP_
|
|
|
25
25
|
* absent from these thresholds.
|
|
26
26
|
*/
|
|
27
27
|
exports.DEFAULT_THRESHOLDS = {
|
|
28
|
+
idle_session_warn_hours: 24,
|
|
29
|
+
idle_browser_warn_minutes: 60,
|
|
30
|
+
orphan_workspace_warn_days: 2,
|
|
28
31
|
fanout: { warn: 24, stop: 40, maxDepth: 2, windowActiveMinutes: 120 },
|
|
29
32
|
sustained: { warnTokens: 3_500_000_000, stopTokens: 5_000_000_000 },
|
|
30
33
|
burnDebt: {
|