@agentguard-run/burn 0.2.7 → 0.3.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 +14 -0
- package/README.md +78 -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 +116 -25
- package/dist/src/defaults.d.ts +1 -1
- package/dist/src/defaults.js +4 -1
- package/dist/src/frames.d.ts +42 -0
- package/dist/src/frames.js +108 -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 +12 -1
- 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/live-panel.d.ts +42 -0
- package/dist/src/live-panel.js +283 -0
- package/dist/src/policy.d.ts +3 -1
- package/dist/src/policy.js +49 -3
- 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 +102 -0
- package/dist/src/render-recording.d.ts +15 -0
- package/dist/src/render-recording.js +200 -0
- package/dist/src/replay/render.js +5 -0
- package/dist/src/types.d.ts +6 -0
- package/docs/LIVE_PANEL_2026_09.md +55 -0
- package/docs/assets/burn-live-sep19-stop-1920.png +0 -0
- package/docs/assets/burn-live-sep19-stop-390.png +0 -0
- package/docs/assets/burn-live-sep19.evidence.json +288 -0
- package/docs/assets/burn-live-sep19.gif +0 -0
- package/docs/assets/burn-live-sep19.jsonl +45 -0
- package/docs/assets/burn-live-sep19.mp4 +0 -0
- package/docs/assets/burn-live-sep19.mp4.provenance.json +292 -0
- package/docs/assets/burn-live-sep19.observations.jsonl +45 -0
- package/docs/assets/burn-live-sep19.receipt.json +38 -0
- package/docs/burn-idle-audit.md +71 -0
- package/docs/burn-render.md +37 -0
- package/fixtures/live-panel-104x35.txt +35 -0
- package/fixtures/live-panel-80x24.txt +24 -0
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.1 (2026-09-20)
|
|
4
|
+
|
|
5
|
+
- Add the live session screen: block-digit counters for spawns, tokens in window and replay share, a spawn-rate bar against the policy ceiling, the last decisions with verdict chips, and a two-second STOP hold, all through the canvas. The same screen renders recorded runs.
|
|
6
|
+
- Ship the September 19 recorded run as docs/assets/burn-live-sep19 with an mp4, gif, evidence and provenance files.
|
|
7
|
+
|
|
8
|
+
## 0.3.0 (2026-09-20)
|
|
9
|
+
|
|
10
|
+
- 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.
|
|
11
|
+
- 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.
|
|
12
|
+
- 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.
|
|
13
|
+
- 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.
|
|
14
|
+
- 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.
|
|
15
|
+
- Validation: 248 tests pass with no skips, including the 240-process stress test: 40 admitted, 200 denied, 240 signed receipts and zero lock failures.
|
|
16
|
+
|
|
3
17
|
## 0.2.7 (2026-09-19)
|
|
4
18
|
|
|
5
19
|
- Fix the STOP badge padding so every card row is 66 visible characters wide. Preserve the card wording.
|
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,7 +361,35 @@ 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.
|
|
317
386
|
No telemetry. No provider-quota guesses. Account forecasts use only observed host percentages, never an invented allowance or cache weighting.
|
|
387
|
+
|
|
388
|
+
### Local live panel
|
|
389
|
+
|
|
390
|
+
`agentguard-burn live` displays spawn and token counts within the active window,
|
|
391
|
+
session cache-read share, the policy rate bar and the last eight local decisions.
|
|
392
|
+
Use `--session ID`, `--once`, `--record run.jsonl`, or `--replay run.jsonl`.
|
|
393
|
+
STOP reasons hold for two seconds while observations continue. The display is
|
|
394
|
+
local and does not affect admission. Missing observations remain unknown.
|
|
395
|
+
See [the live panel and recorded September 19 evidence](docs/LIVE_PANEL_2026_09.md).
|
|
@@ -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,46 @@ 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 'live': {
|
|
130
|
+
const { runLivePanel } = await Promise.resolve().then(() => __importStar(require('./live-panel')));
|
|
131
|
+
return runLivePanel(HOME, rawRest);
|
|
132
|
+
}
|
|
133
|
+
case 'ps':
|
|
134
|
+
case 'reap':
|
|
135
|
+
return (0, cli_1.runIdleCommand)(command, rawRest, HOME);
|
|
136
|
+
case 'render': {
|
|
137
|
+
if (process.platform === 'win32') {
|
|
138
|
+
process.stdout.write('not supported on this platform yet\n');
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
141
|
+
const source = rest[0], mp4 = flag('--mp4'), gif = flag('--gif');
|
|
142
|
+
if (!source || source.startsWith('--') || !mp4 || mp4.startsWith('--') || (has('--gif') && (!gif || gif.startsWith('--'))))
|
|
143
|
+
throw new Error('Usage: agentguard-burn render <run.jsonl> --mp4 <out.mp4> [--gif <out.gif>]');
|
|
144
|
+
const { renderRecording } = await Promise.resolve().then(() => __importStar(require('./render-recording')));
|
|
145
|
+
const result = await renderRecording(source, { mp4, gif });
|
|
146
|
+
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`);
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
85
149
|
case 'blocks': {
|
|
86
150
|
const session = rest.find(value => value !== 'json' && value !== '--json');
|
|
87
151
|
const report = (0, blocks_1.blocksReport)(HOME, session, { rates: (0, live_1.readPricing)(HOME, (0, pre_tool_use_1.loadPolicy)(HOME)) });
|
|
88
|
-
|
|
152
|
+
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
153
|
return 0;
|
|
90
154
|
}
|
|
91
155
|
case 'why': {
|
|
92
156
|
const location = (0, sessions_1.selectInsightTranscript)(rest.find(value => value !== 'json'));
|
|
93
157
|
const transcript = (0, transcript_1.readInsightSession)(location.path, { host: location.host, sessionId: location.sessionId });
|
|
94
|
-
|
|
158
|
+
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
159
|
return 0;
|
|
96
160
|
}
|
|
97
161
|
case 'rewrites': {
|
|
@@ -99,11 +163,12 @@ async function main(argv) {
|
|
|
99
163
|
const locations = session === 'all' ? (0, sessions_1.discoverInsightTranscripts)() : [(0, sessions_1.selectInsightTranscript)(session)];
|
|
100
164
|
const transcripts = locations.map(location => (0, transcript_1.readInsightTranscript)(location.path, { host: location.host, sessionId: location.sessionId }));
|
|
101
165
|
const rates = (0, live_1.readPricing)(HOME, (0, pre_tool_use_1.loadPolicy)(HOME));
|
|
102
|
-
|
|
166
|
+
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
167
|
return 0;
|
|
104
168
|
}
|
|
105
169
|
case 'pricing': {
|
|
106
|
-
|
|
170
|
+
const rates = (0, live_1.readPricing)(HOME, (0, pre_tool_use_1.loadPolicy)(HOME));
|
|
171
|
+
show({ kind: 'command', command, values: { rates: { ...pricing_1.MODEL_PRICING, ...rates } } }, (0, render_2.renderPricing)(rates) + '\n');
|
|
107
172
|
return 0;
|
|
108
173
|
}
|
|
109
174
|
case 'statusline':
|
|
@@ -112,7 +177,7 @@ async function main(argv) {
|
|
|
112
177
|
const payload = raw && typeof raw === 'object' ? raw : {};
|
|
113
178
|
const location = (0, sessions_1.selectInsightTranscript)(payload.transcript_path || rest[0] || payload.session_id);
|
|
114
179
|
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
|
-
|
|
180
|
+
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
181
|
return observation.snapshot ? 0 : 1;
|
|
117
182
|
}
|
|
118
183
|
case 'hook': {
|
|
@@ -124,6 +189,11 @@ async function main(argv) {
|
|
|
124
189
|
catch {
|
|
125
190
|
// Unparseable payload: allow. We never break the host over our own bug.
|
|
126
191
|
}
|
|
192
|
+
if (input?.hook_event_name === 'SessionStart') {
|
|
193
|
+
const warning = await (0, cli_1.prepareSessionStart)(input, HOME);
|
|
194
|
+
process.stdout.write(JSON.stringify(warning ? { continue: true, suppressOutput: false, systemMessage: warning } : { continue: true, suppressOutput: true }));
|
|
195
|
+
return 0;
|
|
196
|
+
}
|
|
127
197
|
process.stdout.write(JSON.stringify((0, pre_tool_use_1.handlePreToolUse)(input, HOME)));
|
|
128
198
|
return 0;
|
|
129
199
|
}
|
|
@@ -132,12 +202,7 @@ async function main(argv) {
|
|
|
132
202
|
const paths = rest.filter((a) => a.endsWith('.jsonl'));
|
|
133
203
|
const targets = paths.length ? paths : (0, simulate_1.discoverTranscripts)();
|
|
134
204
|
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
|
-
}
|
|
205
|
+
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
206
|
return 0;
|
|
142
207
|
}
|
|
143
208
|
case 'calibrate': {
|
|
@@ -145,7 +210,7 @@ async function main(argv) {
|
|
|
145
210
|
const result = (0, calibrate_1.calibrate)(summary.sessions);
|
|
146
211
|
savePolicy(result.policy);
|
|
147
212
|
const t = result.policy.thresholds;
|
|
148
|
-
|
|
213
|
+
show({ kind: 'command', command, values: { ...result } }, [
|
|
149
214
|
`Calibrated from ${result.sessionsUsed} of ${result.sessionsTotal} sessions (${result.sessionsExcluded} excluded as pathological).`,
|
|
150
215
|
` raw p90 ${(result.rawWarnP90 / 1e9).toFixed(2)}B raw p99 ${(result.rawStopP99 / 1e9).toFixed(2)}B`,
|
|
151
216
|
` sustained WARN ${(t.sustained.warnTokens / 1e9).toFixed(2)}B STOP ${(t.sustained.stopTokens / 1e9).toFixed(2)}B`,
|
|
@@ -163,7 +228,7 @@ async function main(argv) {
|
|
|
163
228
|
const gateway = new gateway_1.Gateway(HOME, { sign: false });
|
|
164
229
|
const lc = policy.thresholds.localCompute;
|
|
165
230
|
const override = (0, override_1.readOverride)(HOME);
|
|
166
|
-
|
|
231
|
+
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
232
|
`mode: ${policy.mode}${policy.mode === 'enforce' ? ' (the next STOP blocks)' : ' (recording only; nothing is blocked)'}`,
|
|
168
233
|
`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
234
|
`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 +271,7 @@ async function main(argv) {
|
|
|
206
271
|
process.stderr.write(`init: refusing to touch ${(0, install_1.configPath)(target)}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
207
272
|
return 1;
|
|
208
273
|
}
|
|
209
|
-
|
|
274
|
+
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
275
|
` ${result.command}\n` +
|
|
211
276
|
(label ? `${label}\n` : '') +
|
|
212
277
|
tail +
|
|
@@ -214,7 +279,7 @@ async function main(argv) {
|
|
|
214
279
|
return 0;
|
|
215
280
|
}
|
|
216
281
|
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
|
-
|
|
282
|
+
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
283
|
(label ? `${label}\n` : '') +
|
|
219
284
|
tail +
|
|
220
285
|
(target === 'claude' ? `Other hosts: agentguard-burn init cursor | init codex | proxy --upstream http://127.0.0.1:11434 --host ollama\n` : ''));
|
|
@@ -226,8 +291,14 @@ async function main(argv) {
|
|
|
226
291
|
return 0;
|
|
227
292
|
}
|
|
228
293
|
case 'codex-hook': {
|
|
294
|
+
const input = readStdinJson();
|
|
295
|
+
if (input && typeof input === 'object' && input.hook_event_name === 'SessionStart') {
|
|
296
|
+
const warning = await (0, cli_1.prepareSessionStart)(input, HOME);
|
|
297
|
+
process.stdout.write(JSON.stringify(warning ? { systemMessage: warning } : {}));
|
|
298
|
+
return 0;
|
|
299
|
+
}
|
|
229
300
|
const gateway = new gateway_1.Gateway(HOME);
|
|
230
|
-
process.stdout.write(JSON.stringify((0, codex_1.handleCodexHook)(
|
|
301
|
+
process.stdout.write(JSON.stringify((0, codex_1.handleCodexHook)(input, gateway)));
|
|
231
302
|
return 0;
|
|
232
303
|
}
|
|
233
304
|
case 'proxy': {
|
|
@@ -251,6 +322,7 @@ async function main(argv) {
|
|
|
251
322
|
log: (l) => process.stderr.write(`${l}\n`),
|
|
252
323
|
});
|
|
253
324
|
const policy = (0, pre_tool_use_1.loadPolicy)(HOME);
|
|
325
|
+
(0, recording_1.recordFrame)({ kind: 'command', command, values: { listening: running.address.origin, host, mode: policy.mode } });
|
|
254
326
|
process.stderr.write(`agentguard-burn proxy ${running.address.origin} -> ${upstreamRaw} (${host}, ${policy.mode} mode)\n` +
|
|
255
327
|
`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
328
|
`Ctrl-C to stop.\n`);
|
|
@@ -263,7 +335,7 @@ async function main(argv) {
|
|
|
263
335
|
}
|
|
264
336
|
case 'conformance': {
|
|
265
337
|
const result = await (0, conformance_1.runConformance)();
|
|
266
|
-
|
|
338
|
+
show({ kind: 'command', command, values: { ok: result.ok, checks: result.checks } }, result.text + '\n');
|
|
267
339
|
return result.ok ? 0 : 1;
|
|
268
340
|
}
|
|
269
341
|
case 'enforce': {
|
|
@@ -274,18 +346,18 @@ async function main(argv) {
|
|
|
274
346
|
return 2;
|
|
275
347
|
}
|
|
276
348
|
savePolicy({ ...policy, mode: 'enforce' });
|
|
277
|
-
|
|
349
|
+
show({ kind: 'command', command, values: { mode: 'enforce' } }, 'Enforcement ON. The next STOP will block the spawn.\n');
|
|
278
350
|
return 0;
|
|
279
351
|
}
|
|
280
352
|
case 'shadow': {
|
|
281
353
|
savePolicy({ ...(0, pre_tool_use_1.loadPolicy)(HOME), mode: 'shadow' });
|
|
282
|
-
|
|
354
|
+
show({ kind: 'command', command, values: { mode: 'shadow' } }, 'Back to shadow mode. Decisions are recorded, nothing is blocked.\n');
|
|
283
355
|
return 0;
|
|
284
356
|
}
|
|
285
357
|
case 'resume': {
|
|
286
358
|
if (has('--clear')) {
|
|
287
359
|
(0, override_1.clearOverride)(HOME);
|
|
288
|
-
|
|
360
|
+
show({ kind: 'command', command, values: { override: false } }, 'Override cleared. The next STOP blocks.\n');
|
|
289
361
|
return 0;
|
|
290
362
|
}
|
|
291
363
|
const reason = flag('--reason');
|
|
@@ -296,13 +368,18 @@ async function main(argv) {
|
|
|
296
368
|
const once = has('--once');
|
|
297
369
|
const now = Date.now();
|
|
298
370
|
(0, override_1.writeOverride)(HOME, once ? { at: now, once: true, reason } : { at: now, once: false, reason, until: now + override_1.OVERRIDE_WINDOW_MS });
|
|
299
|
-
|
|
371
|
+
show({ kind: 'command', command, values: { once, at: now, until: once ? null : now + override_1.OVERRIDE_WINDOW_MS } }, once
|
|
300
372
|
? `Override recorded for the next STOP only, on any host: "${reason}"\n`
|
|
301
373
|
: `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
374
|
return 0;
|
|
303
375
|
}
|
|
304
376
|
default:
|
|
305
|
-
|
|
377
|
+
show({ kind: 'command', command: 'help', values: { commands: ['ps', 'reap', 'live', 'render', 'blocks', 'why', 'rewrites', 'pace', 'statusline', 'pricing', 'replay', 'calibrate', 'status', 'init', 'enforce', 'shadow', 'resume', 'proxy', 'conformance'] } }, 'agentguard-burn <ps|reap|live|render|blocks|why|rewrites|pace|statusline|pricing|replay|calibrate|status|init|enforce|shadow|resume|proxy|conformance>\n' +
|
|
378
|
+
' ps [--json] local agent processes, held memory and workspace disk\n' +
|
|
379
|
+
' render <run.jsonl> --mp4 <out.mp4> [--gif <out.gif>] local recorded-run export\n' +
|
|
380
|
+
' live [--session ID] [--once] [--replay run.jsonl] local instrument panel\n' +
|
|
381
|
+
' --record <file.jsonl> append structured frames for any command; --no-color keeps plain output\n' +
|
|
382
|
+
' reap numbered interactive SIGTERM, never delete or force-kill\n' +
|
|
306
383
|
' blocks [session|all] stored WARN/STOP receipts and that session\'s child usage\n' +
|
|
307
384
|
' why [session] token attribution and API list-price equivalents\n' +
|
|
308
385
|
' rewrites [session|all] full-prefix cache writes by cause\n' +
|
|
@@ -322,7 +399,21 @@ async function main(argv) {
|
|
|
322
399
|
return command ? 64 : 0;
|
|
323
400
|
}
|
|
324
401
|
}
|
|
325
|
-
|
|
402
|
+
async function run() {
|
|
403
|
+
const parsed = (0, recording_1.recordingArguments)(process.argv.slice(2));
|
|
404
|
+
(0, recording_1.configureRecording)(parsed.file);
|
|
405
|
+
try {
|
|
406
|
+
const code = await main(parsed.args);
|
|
407
|
+
if (!(0, recording_1.recordedFrameCount)())
|
|
408
|
+
(0, recording_1.recordFrame)({ kind: 'command', command: parsed.args[0] ?? 'help', values: { exitCode: code } });
|
|
409
|
+
return code;
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
(0, recording_1.recordFrame)({ kind: 'command', command: parsed.args[0] ?? 'help', values: { exitCode: 4, failed: true } });
|
|
413
|
+
throw error;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
run().then((code) => process.exit(code), (error) => {
|
|
326
417
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
327
418
|
process.exit(4);
|
|
328
419
|
});
|
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
|