@agentguard-run/burn 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +93 -0
- package/README.md +145 -6
- package/dist/src/adapters/codex.d.ts +48 -0
- package/dist/src/adapters/codex.js +197 -0
- package/dist/src/adapters/cursor.d.ts +35 -0
- package/dist/src/adapters/cursor.js +135 -0
- package/dist/src/adapters/raw-api.d.ts +76 -0
- package/dist/src/adapters/raw-api.js +130 -0
- package/dist/src/cli.d.ts +7 -3
- package/dist/src/cli.js +141 -17
- package/dist/src/conformance.d.ts +26 -0
- package/dist/src/conformance.js +261 -0
- package/dist/src/defaults.d.ts +11 -0
- package/dist/src/defaults.js +16 -1
- package/dist/src/detectors/local-compute.d.ts +19 -0
- package/dist/src/detectors/local-compute.js +66 -0
- package/dist/src/events.d.ts +94 -0
- package/dist/src/events.js +47 -0
- package/dist/src/gateway.d.ts +141 -0
- package/dist/src/gateway.js +536 -0
- package/dist/src/hook/pre-tool-use.d.ts +25 -1
- package/dist/src/hook/pre-tool-use.js +64 -16
- package/dist/src/index.d.ts +19 -4
- package/dist/src/index.js +57 -1
- package/dist/src/install.d.ts +29 -0
- package/dist/src/install.js +145 -0
- package/dist/src/override.d.ts +32 -0
- package/dist/src/override.js +72 -0
- package/dist/src/proxy/server.d.ts +45 -0
- package/dist/src/proxy/server.js +169 -0
- package/dist/src/proxy/usage-observer.d.ts +40 -0
- package/dist/src/proxy/usage-observer.js +128 -0
- package/dist/src/receipt.d.ts +61 -0
- package/dist/src/receipt.js +98 -0
- package/dist/src/replay/render.d.ts +1 -0
- package/dist/src/replay/render.js +2 -1
- package/dist/src/state/reservations.d.ts +115 -11
- package/dist/src/state/reservations.js +293 -59
- package/dist/src/state/session.d.ts +6 -0
- package/dist/src/state/session.js +17 -0
- package/dist/src/status.d.ts +19 -0
- package/dist/src/status.js +112 -0
- package/dist/src/types.d.ts +14 -1
- package/fixtures/codex-0.151.0-pretooluse.json +49 -0
- package/package.json +34 -6
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* as little as possible.
|
|
16
16
|
*/
|
|
17
17
|
import { type ReaderCursor } from '../history/claude-transcript';
|
|
18
|
-
import type { Policy, SessionState } from '../types';
|
|
18
|
+
import type { BurnReport, Policy, SessionState } from '../types';
|
|
19
19
|
export interface HookInput {
|
|
20
20
|
session_id?: string;
|
|
21
21
|
transcript_path?: string;
|
|
@@ -35,12 +35,36 @@ export interface HookOutput {
|
|
|
35
35
|
permissionDecisionReason: string;
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
+
export interface PersistedSession {
|
|
39
|
+
cursor: {
|
|
40
|
+
offset: number;
|
|
41
|
+
size: number;
|
|
42
|
+
malformedLines: number;
|
|
43
|
+
depthByUuid: [string, number][];
|
|
44
|
+
};
|
|
45
|
+
state: Omit<SessionState, 'tokensByActiveMinute' | 'spawnsByActiveMinute' | 'surfaceReaders'> & {
|
|
46
|
+
tokensByActiveMinute: [number, number][];
|
|
47
|
+
spawnsByActiveMinute: [number, number][];
|
|
48
|
+
surfaceReaders: [string, number[]][];
|
|
49
|
+
};
|
|
50
|
+
/** Signature of the last finding set the user was told about. */
|
|
51
|
+
notified?: string;
|
|
52
|
+
}
|
|
53
|
+
/** Inflate a persisted Claude Code session. Shared with status. */
|
|
54
|
+
export declare function inflateHookSession(raw: PersistedSession): SessionState;
|
|
55
|
+
/**
|
|
56
|
+
* Tell the user once per change, not once per spawn. Eighteen identical
|
|
57
|
+
* "WARN: 30 spawns" banners in a row train the user to stop reading them,
|
|
58
|
+
* and the one that matters is the nineteenth.
|
|
59
|
+
*/
|
|
60
|
+
export declare function findingSignature(report: BurnReport): string;
|
|
38
61
|
export declare function loadPolicy(home: string): Policy;
|
|
39
62
|
/** Refresh session state from the transcript. Cheap: only new bytes are read. */
|
|
40
63
|
export declare function refreshSession(home: string, sessionId: string, transcriptPath: string): {
|
|
41
64
|
cursor: ReaderCursor;
|
|
42
65
|
state: SessionState;
|
|
43
66
|
newSpawns: number;
|
|
67
|
+
notified: string;
|
|
44
68
|
};
|
|
45
69
|
export declare function handlePreToolUse(input: HookInput, home: string, now?: number): HookOutput;
|
|
46
70
|
/** The settings.json fragment users paste in. Only PreToolUse can block. */
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* as little as possible.
|
|
17
17
|
*/
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.inflateHookSession = inflateHookSession;
|
|
20
|
+
exports.findingSignature = findingSignature;
|
|
19
21
|
exports.loadPolicy = loadPolicy;
|
|
20
22
|
exports.refreshSession = refreshSession;
|
|
21
23
|
exports.handlePreToolUse = handlePreToolUse;
|
|
@@ -27,28 +29,32 @@ const claude_transcript_1 = require("../history/claude-transcript");
|
|
|
27
29
|
const reservations_1 = require("../state/reservations");
|
|
28
30
|
const session_1 = require("../state/session");
|
|
29
31
|
const defaults_1 = require("../defaults");
|
|
32
|
+
const override_1 = require("../override");
|
|
30
33
|
const render_1 = require("../replay/render");
|
|
31
34
|
const SPAWN_TOOLS = new Set(['Agent', 'Task']);
|
|
32
35
|
function sessionFile(home, sessionId) {
|
|
33
36
|
return (0, node_path_1.join)(home, 'sessions', `${sessionId.replace(/[^a-zA-Z0-9_-]/g, '_')}.json`);
|
|
34
37
|
}
|
|
38
|
+
/** Inflate a persisted Claude Code session. Shared with status. */
|
|
39
|
+
function inflateHookSession(raw) {
|
|
40
|
+
return {
|
|
41
|
+
...raw.state,
|
|
42
|
+
tokensByActiveMinute: new Map(raw.state.tokensByActiveMinute),
|
|
43
|
+
spawnsByActiveMinute: new Map(raw.state.spawnsByActiveMinute),
|
|
44
|
+
surfaceReaders: new Map(raw.state.surfaceReaders.map(([k, v]) => [k, new Set(v)])),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
35
47
|
function loadSession(home, sessionId, firstEventAt) {
|
|
36
48
|
try {
|
|
37
49
|
const raw = JSON.parse((0, node_fs_1.readFileSync)(sessionFile(home, sessionId), 'utf8'));
|
|
38
50
|
const cursor = { ...raw.cursor, depthByUuid: new Map(raw.cursor.depthByUuid) };
|
|
39
|
-
|
|
40
|
-
...raw.state,
|
|
41
|
-
tokensByActiveMinute: new Map(raw.state.tokensByActiveMinute),
|
|
42
|
-
spawnsByActiveMinute: new Map(raw.state.spawnsByActiveMinute),
|
|
43
|
-
surfaceReaders: new Map(raw.state.surfaceReaders.map(([k, v]) => [k, new Set(v)])),
|
|
44
|
-
};
|
|
45
|
-
return { cursor, state };
|
|
51
|
+
return { cursor, state: inflateHookSession(raw), notified: raw.notified ?? '' };
|
|
46
52
|
}
|
|
47
53
|
catch {
|
|
48
|
-
return { cursor: (0, claude_transcript_1.newCursor)(), state: (0, session_1.newSessionState)(sessionId, firstEventAt) };
|
|
54
|
+
return { cursor: (0, claude_transcript_1.newCursor)(), state: (0, session_1.newSessionState)(sessionId, firstEventAt), notified: '' };
|
|
49
55
|
}
|
|
50
56
|
}
|
|
51
|
-
function saveSession(home, cursor, state) {
|
|
57
|
+
function saveSession(home, cursor, state, notified) {
|
|
52
58
|
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(home, 'sessions'), { recursive: true, mode: 0o700 });
|
|
53
59
|
const persisted = {
|
|
54
60
|
cursor: { offset: cursor.offset, size: cursor.size, malformedLines: cursor.malformedLines, depthByUuid: [...cursor.depthByUuid] },
|
|
@@ -58,12 +64,24 @@ function saveSession(home, cursor, state) {
|
|
|
58
64
|
spawnsByActiveMinute: [...state.spawnsByActiveMinute],
|
|
59
65
|
surfaceReaders: [...state.surfaceReaders].map(([k, v]) => [k, [...v]]),
|
|
60
66
|
},
|
|
67
|
+
notified,
|
|
61
68
|
};
|
|
62
69
|
const file = sessionFile(home, state.sessionId);
|
|
63
70
|
(0, node_fs_1.writeFileSync)(`${file}.tmp`, JSON.stringify(persisted), { mode: 0o600 });
|
|
64
71
|
// rename is atomic; a concurrent reader never sees a half-written file.
|
|
65
72
|
require('node:fs').renameSync(`${file}.tmp`, file);
|
|
66
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Tell the user once per change, not once per spawn. Eighteen identical
|
|
76
|
+
* "WARN: 30 spawns" banners in a row train the user to stop reading them,
|
|
77
|
+
* and the one that matters is the nineteenth.
|
|
78
|
+
*/
|
|
79
|
+
function findingSignature(report) {
|
|
80
|
+
return report.findings
|
|
81
|
+
.map((f) => `${f.detector}:${f.verdict}`)
|
|
82
|
+
.sort()
|
|
83
|
+
.join('|');
|
|
84
|
+
}
|
|
67
85
|
function loadPolicy(home) {
|
|
68
86
|
try {
|
|
69
87
|
const parsed = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(home, 'burn-policy.json'), 'utf8'));
|
|
@@ -81,15 +99,27 @@ function recordDecision(home, entry) {
|
|
|
81
99
|
}
|
|
82
100
|
/** Refresh session state from the transcript. Cheap: only new bytes are read. */
|
|
83
101
|
function refreshSession(home, sessionId, transcriptPath) {
|
|
84
|
-
const { cursor, state } = loadSession(home, sessionId, Date.now());
|
|
102
|
+
const { cursor, state, notified } = loadSession(home, sessionId, Date.now());
|
|
85
103
|
const before = state.spawnCount;
|
|
86
104
|
const events = (0, claude_transcript_1.readIncremental)(transcriptPath, cursor);
|
|
87
105
|
for (const event of events)
|
|
88
106
|
(0, session_1.applyEvent)(state, event);
|
|
89
107
|
if (events.length > 0 && state.startedAt > events[0].at)
|
|
90
108
|
state.startedAt = events[0].at;
|
|
91
|
-
saveSession(home, cursor, state);
|
|
92
|
-
return { cursor, state, newSpawns: state.spawnCount - before };
|
|
109
|
+
saveSession(home, cursor, state, notified);
|
|
110
|
+
return { cursor, state, newSpawns: state.spawnCount - before, notified };
|
|
111
|
+
}
|
|
112
|
+
function rememberNotified(home, sessionId, signature) {
|
|
113
|
+
try {
|
|
114
|
+
const raw = JSON.parse((0, node_fs_1.readFileSync)(sessionFile(home, sessionId), 'utf8'));
|
|
115
|
+
raw.notified = signature;
|
|
116
|
+
const file = sessionFile(home, sessionId);
|
|
117
|
+
(0, node_fs_1.writeFileSync)(`${file}.tmp`, JSON.stringify(raw), { mode: 0o600 });
|
|
118
|
+
require('node:fs').renameSync(`${file}.tmp`, file);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
/* best effort; the worst case is one repeated banner */
|
|
122
|
+
}
|
|
93
123
|
}
|
|
94
124
|
function handlePreToolUse(input, home, now = Date.now()) {
|
|
95
125
|
const toolName = input.tool_name ?? '';
|
|
@@ -100,8 +130,11 @@ function handlePreToolUse(input, home, now = Date.now()) {
|
|
|
100
130
|
const store = new reservations_1.ReservationStore(home);
|
|
101
131
|
let report;
|
|
102
132
|
let reservation;
|
|
133
|
+
let notified = '';
|
|
103
134
|
try {
|
|
104
|
-
const
|
|
135
|
+
const refreshed = refreshSession(home, input.session_id, input.transcript_path);
|
|
136
|
+
const { state, newSpawns } = refreshed;
|
|
137
|
+
notified = refreshed.notified;
|
|
105
138
|
if (newSpawns > 0)
|
|
106
139
|
store.reconcile(input.session_id, state.spawnCount, state.spawnCount - newSpawns);
|
|
107
140
|
// Depth of the proposed child: the issuing agent's depth plus one. A hook
|
|
@@ -124,26 +157,41 @@ function handlePreToolUse(input, home, now = Date.now()) {
|
|
|
124
157
|
}
|
|
125
158
|
const shouldDeny = report.verdict === 'STOP' || !reservation.allowed;
|
|
126
159
|
const reason = shouldDeny ? buildDenyReason(report, reservation) : '';
|
|
160
|
+
// The audited override: only consulted when a block is about to happen.
|
|
161
|
+
const override = shouldDeny && policy.mode === 'enforce' ? (0, override_1.consumeOverride)(home, now) : null;
|
|
127
162
|
recordDecision(home, {
|
|
128
163
|
at: now,
|
|
129
164
|
sessionId: input.session_id,
|
|
130
165
|
toolUseId: input.tool_use_id ?? null,
|
|
131
166
|
verdict: report.verdict,
|
|
132
167
|
wouldDeny: shouldDeny,
|
|
133
|
-
enforced: policy.mode === 'enforce' && shouldDeny,
|
|
168
|
+
enforced: policy.mode === 'enforce' && shouldDeny && !override,
|
|
169
|
+
overridden: override ? { once: override.once, reason: override.reason } : undefined,
|
|
134
170
|
mode: policy.mode,
|
|
135
171
|
findings: report.findings.map((f) => ({ detector: f.detector, verdict: f.verdict, observed: f.observed, threshold: f.threshold })),
|
|
136
172
|
effectiveSpawns: reservation.effectiveSpawns,
|
|
137
173
|
totals: report.totals,
|
|
138
174
|
});
|
|
139
|
-
if (shouldDeny && policy.mode === 'enforce')
|
|
140
|
-
|
|
175
|
+
if (shouldDeny && policy.mode === 'enforce') {
|
|
176
|
+
if (!override)
|
|
177
|
+
return deny(reason);
|
|
178
|
+
return {
|
|
179
|
+
continue: true,
|
|
180
|
+
systemMessage: `AgentGuard STOP overridden${override.once ? ' once' : ''} ("${override.reason}"): ${report.findings[0]?.summary ?? ''}`,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
141
183
|
if (report.verdict !== 'OK') {
|
|
184
|
+
const signature = findingSignature(report);
|
|
185
|
+
if (signature === notified)
|
|
186
|
+
return { continue: true, suppressOutput: true };
|
|
187
|
+
rememberNotified(home, input.session_id, signature);
|
|
142
188
|
return {
|
|
143
189
|
continue: true,
|
|
144
190
|
systemMessage: `AgentGuard ${report.verdict}${policy.mode === 'shadow' && shouldDeny ? ' (shadow: would have blocked)' : ''}: ${report.findings[0]?.summary ?? ''}`,
|
|
145
191
|
};
|
|
146
192
|
}
|
|
193
|
+
if (notified)
|
|
194
|
+
rememberNotified(home, input.session_id, '');
|
|
147
195
|
return { continue: true, suppressOutput: true };
|
|
148
196
|
}
|
|
149
197
|
function buildDenyReason(report, _reservation) {
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
export * from './types';
|
|
2
|
-
export { DEFAULT_POLICY, DEFAULT_THRESHOLDS } from './defaults';
|
|
2
|
+
export { DEFAULT_POLICY, DEFAULT_THRESHOLDS, DEFAULT_LOCAL_COMPUTE, CALL_RESERVATION_TTL_MS } from './defaults';
|
|
3
3
|
export { evaluate, fmt } from './detectors/evaluate';
|
|
4
|
+
export { evaluateLocalCompute } from './detectors/local-compute';
|
|
4
5
|
export { readIncremental, readAll, newCursor, normaliseLine, type ReaderCursor } from './history/claude-transcript';
|
|
5
|
-
export { applyEvent, newSessionState, windowSum, medianCompletedMinute, cacheReadRatio } from './state/session';
|
|
6
|
-
export { ReservationStore, RESERVATION_TTL_MS } from './state/reservations';
|
|
6
|
+
export { applyEvent, applyCorrection, newSessionState, windowSum, medianCompletedMinute, cacheReadRatio } from './state/session';
|
|
7
|
+
export { ReservationStore, Transaction, RESERVATION_TTL_MS, type Reservation, type CallReservation, type ComputeSnapshot } from './state/reservations';
|
|
7
8
|
export { replayAll, replaySession, replayEvents, discoverTranscripts, type ReplaySummary, type SessionReplay } from './replay/simulate';
|
|
8
|
-
export { renderReplay } from './replay/render';
|
|
9
|
+
export { renderReplay, renderStop } from './replay/render';
|
|
9
10
|
export { calibrate, type CalibrationResult } from './calibrate';
|
|
10
11
|
export { handlePreToolUse, refreshSession, loadPolicy, settingsSnippet, type HookInput, type HookOutput } from './hook/pre-tool-use';
|
|
12
|
+
export * from './events';
|
|
13
|
+
export { Gateway, mergeCapabilities, type Decision, type GatewaySessionView, type UsageCoverage, type GatewayOptions } from './gateway';
|
|
14
|
+
export { ReceiptSigner, verifyReceipt, receiptDigest, canonical, sha256, type ReceiptPayload, type SignedReceipt } from './receipt';
|
|
15
|
+
export { createRawApiGuard, BurnStopError, type RawApiGuard, type RawGuardOptions } from './adapters/raw-api';
|
|
16
|
+
export { handleCursorHook, cursorHooksSnippet, type CursorHookOutput } from './adapters/cursor';
|
|
17
|
+
export { handleCodexHook, codexHooksSnippet, parseCodexTranscript, readCodexTranscriptUsage, CODEX_FORBIDDEN_FIELDS, type CodexHookOutput } from './adapters/codex';
|
|
18
|
+
export { startProxy, profileFor, type ProxyOptions, type ProxyHost, type RunningProxy } from './proxy/server';
|
|
19
|
+
export { UsageObserver, type ProxyProfile, type ObservedUsage } from './proxy/usage-observer';
|
|
20
|
+
export { renderMachineStatus } from './status';
|
|
21
|
+
export { runConformance, type ConformanceResult } from './conformance';
|
|
22
|
+
export { writeOverride, readOverride, consumeOverride, clearOverride, OVERRIDE_WINDOW_MS, type Override } from './override';
|
|
23
|
+
export { install, health, configPath, type InstallHost, type InstallResult, type HostHealth } from './install';
|
|
24
|
+
export { readHookSessions, renderHostHealth } from './status';
|
|
25
|
+
export { findingSignature, inflateHookSession, type PersistedSession } from './hook/pre-tool-use';
|
package/dist/src/index.js
CHANGED
|
@@ -14,14 +14,19 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.settingsSnippet = exports.loadPolicy = exports.refreshSession = exports.handlePreToolUse = exports.calibrate = exports.renderReplay = exports.discoverTranscripts = exports.replayEvents = exports.replaySession = exports.replayAll = exports.RESERVATION_TTL_MS = exports.ReservationStore = exports.cacheReadRatio = exports.medianCompletedMinute = exports.windowSum = exports.newSessionState = exports.applyEvent = exports.normaliseLine = exports.newCursor = exports.readAll = exports.readIncremental = exports.fmt = exports.evaluate = exports.DEFAULT_THRESHOLDS = exports.DEFAULT_POLICY = void 0;
|
|
17
|
+
exports.UsageObserver = exports.profileFor = exports.startProxy = exports.CODEX_FORBIDDEN_FIELDS = exports.readCodexTranscriptUsage = exports.parseCodexTranscript = exports.codexHooksSnippet = exports.handleCodexHook = exports.cursorHooksSnippet = exports.handleCursorHook = exports.BurnStopError = exports.createRawApiGuard = exports.sha256 = exports.canonical = exports.receiptDigest = exports.verifyReceipt = exports.ReceiptSigner = exports.mergeCapabilities = exports.Gateway = exports.settingsSnippet = exports.loadPolicy = exports.refreshSession = exports.handlePreToolUse = exports.calibrate = exports.renderStop = exports.renderReplay = exports.discoverTranscripts = exports.replayEvents = exports.replaySession = exports.replayAll = exports.RESERVATION_TTL_MS = exports.Transaction = exports.ReservationStore = exports.cacheReadRatio = exports.medianCompletedMinute = exports.windowSum = exports.newSessionState = exports.applyCorrection = exports.applyEvent = exports.normaliseLine = exports.newCursor = exports.readAll = exports.readIncremental = exports.evaluateLocalCompute = exports.fmt = exports.evaluate = exports.CALL_RESERVATION_TTL_MS = exports.DEFAULT_LOCAL_COMPUTE = exports.DEFAULT_THRESHOLDS = exports.DEFAULT_POLICY = void 0;
|
|
18
|
+
exports.inflateHookSession = exports.findingSignature = exports.renderHostHealth = exports.readHookSessions = exports.configPath = exports.health = exports.install = exports.OVERRIDE_WINDOW_MS = exports.clearOverride = exports.consumeOverride = exports.readOverride = exports.writeOverride = exports.runConformance = exports.renderMachineStatus = void 0;
|
|
18
19
|
__exportStar(require("./types"), exports);
|
|
19
20
|
var defaults_1 = require("./defaults");
|
|
20
21
|
Object.defineProperty(exports, "DEFAULT_POLICY", { enumerable: true, get: function () { return defaults_1.DEFAULT_POLICY; } });
|
|
21
22
|
Object.defineProperty(exports, "DEFAULT_THRESHOLDS", { enumerable: true, get: function () { return defaults_1.DEFAULT_THRESHOLDS; } });
|
|
23
|
+
Object.defineProperty(exports, "DEFAULT_LOCAL_COMPUTE", { enumerable: true, get: function () { return defaults_1.DEFAULT_LOCAL_COMPUTE; } });
|
|
24
|
+
Object.defineProperty(exports, "CALL_RESERVATION_TTL_MS", { enumerable: true, get: function () { return defaults_1.CALL_RESERVATION_TTL_MS; } });
|
|
22
25
|
var evaluate_1 = require("./detectors/evaluate");
|
|
23
26
|
Object.defineProperty(exports, "evaluate", { enumerable: true, get: function () { return evaluate_1.evaluate; } });
|
|
24
27
|
Object.defineProperty(exports, "fmt", { enumerable: true, get: function () { return evaluate_1.fmt; } });
|
|
28
|
+
var local_compute_1 = require("./detectors/local-compute");
|
|
29
|
+
Object.defineProperty(exports, "evaluateLocalCompute", { enumerable: true, get: function () { return local_compute_1.evaluateLocalCompute; } });
|
|
25
30
|
var claude_transcript_1 = require("./history/claude-transcript");
|
|
26
31
|
Object.defineProperty(exports, "readIncremental", { enumerable: true, get: function () { return claude_transcript_1.readIncremental; } });
|
|
27
32
|
Object.defineProperty(exports, "readAll", { enumerable: true, get: function () { return claude_transcript_1.readAll; } });
|
|
@@ -29,12 +34,14 @@ Object.defineProperty(exports, "newCursor", { enumerable: true, get: function ()
|
|
|
29
34
|
Object.defineProperty(exports, "normaliseLine", { enumerable: true, get: function () { return claude_transcript_1.normaliseLine; } });
|
|
30
35
|
var session_1 = require("./state/session");
|
|
31
36
|
Object.defineProperty(exports, "applyEvent", { enumerable: true, get: function () { return session_1.applyEvent; } });
|
|
37
|
+
Object.defineProperty(exports, "applyCorrection", { enumerable: true, get: function () { return session_1.applyCorrection; } });
|
|
32
38
|
Object.defineProperty(exports, "newSessionState", { enumerable: true, get: function () { return session_1.newSessionState; } });
|
|
33
39
|
Object.defineProperty(exports, "windowSum", { enumerable: true, get: function () { return session_1.windowSum; } });
|
|
34
40
|
Object.defineProperty(exports, "medianCompletedMinute", { enumerable: true, get: function () { return session_1.medianCompletedMinute; } });
|
|
35
41
|
Object.defineProperty(exports, "cacheReadRatio", { enumerable: true, get: function () { return session_1.cacheReadRatio; } });
|
|
36
42
|
var reservations_1 = require("./state/reservations");
|
|
37
43
|
Object.defineProperty(exports, "ReservationStore", { enumerable: true, get: function () { return reservations_1.ReservationStore; } });
|
|
44
|
+
Object.defineProperty(exports, "Transaction", { enumerable: true, get: function () { return reservations_1.Transaction; } });
|
|
38
45
|
Object.defineProperty(exports, "RESERVATION_TTL_MS", { enumerable: true, get: function () { return reservations_1.RESERVATION_TTL_MS; } });
|
|
39
46
|
var simulate_1 = require("./replay/simulate");
|
|
40
47
|
Object.defineProperty(exports, "replayAll", { enumerable: true, get: function () { return simulate_1.replayAll; } });
|
|
@@ -43,6 +50,7 @@ Object.defineProperty(exports, "replayEvents", { enumerable: true, get: function
|
|
|
43
50
|
Object.defineProperty(exports, "discoverTranscripts", { enumerable: true, get: function () { return simulate_1.discoverTranscripts; } });
|
|
44
51
|
var render_1 = require("./replay/render");
|
|
45
52
|
Object.defineProperty(exports, "renderReplay", { enumerable: true, get: function () { return render_1.renderReplay; } });
|
|
53
|
+
Object.defineProperty(exports, "renderStop", { enumerable: true, get: function () { return render_1.renderStop; } });
|
|
46
54
|
var calibrate_1 = require("./calibrate");
|
|
47
55
|
Object.defineProperty(exports, "calibrate", { enumerable: true, get: function () { return calibrate_1.calibrate; } });
|
|
48
56
|
var pre_tool_use_1 = require("./hook/pre-tool-use");
|
|
@@ -50,3 +58,51 @@ Object.defineProperty(exports, "handlePreToolUse", { enumerable: true, get: func
|
|
|
50
58
|
Object.defineProperty(exports, "refreshSession", { enumerable: true, get: function () { return pre_tool_use_1.refreshSession; } });
|
|
51
59
|
Object.defineProperty(exports, "loadPolicy", { enumerable: true, get: function () { return pre_tool_use_1.loadPolicy; } });
|
|
52
60
|
Object.defineProperty(exports, "settingsSnippet", { enumerable: true, get: function () { return pre_tool_use_1.settingsSnippet; } });
|
|
61
|
+
// Cross-tool layer (0.2.0)
|
|
62
|
+
__exportStar(require("./events"), exports);
|
|
63
|
+
var gateway_1 = require("./gateway");
|
|
64
|
+
Object.defineProperty(exports, "Gateway", { enumerable: true, get: function () { return gateway_1.Gateway; } });
|
|
65
|
+
Object.defineProperty(exports, "mergeCapabilities", { enumerable: true, get: function () { return gateway_1.mergeCapabilities; } });
|
|
66
|
+
var receipt_1 = require("./receipt");
|
|
67
|
+
Object.defineProperty(exports, "ReceiptSigner", { enumerable: true, get: function () { return receipt_1.ReceiptSigner; } });
|
|
68
|
+
Object.defineProperty(exports, "verifyReceipt", { enumerable: true, get: function () { return receipt_1.verifyReceipt; } });
|
|
69
|
+
Object.defineProperty(exports, "receiptDigest", { enumerable: true, get: function () { return receipt_1.receiptDigest; } });
|
|
70
|
+
Object.defineProperty(exports, "canonical", { enumerable: true, get: function () { return receipt_1.canonical; } });
|
|
71
|
+
Object.defineProperty(exports, "sha256", { enumerable: true, get: function () { return receipt_1.sha256; } });
|
|
72
|
+
var raw_api_1 = require("./adapters/raw-api");
|
|
73
|
+
Object.defineProperty(exports, "createRawApiGuard", { enumerable: true, get: function () { return raw_api_1.createRawApiGuard; } });
|
|
74
|
+
Object.defineProperty(exports, "BurnStopError", { enumerable: true, get: function () { return raw_api_1.BurnStopError; } });
|
|
75
|
+
var cursor_1 = require("./adapters/cursor");
|
|
76
|
+
Object.defineProperty(exports, "handleCursorHook", { enumerable: true, get: function () { return cursor_1.handleCursorHook; } });
|
|
77
|
+
Object.defineProperty(exports, "cursorHooksSnippet", { enumerable: true, get: function () { return cursor_1.cursorHooksSnippet; } });
|
|
78
|
+
var codex_1 = require("./adapters/codex");
|
|
79
|
+
Object.defineProperty(exports, "handleCodexHook", { enumerable: true, get: function () { return codex_1.handleCodexHook; } });
|
|
80
|
+
Object.defineProperty(exports, "codexHooksSnippet", { enumerable: true, get: function () { return codex_1.codexHooksSnippet; } });
|
|
81
|
+
Object.defineProperty(exports, "parseCodexTranscript", { enumerable: true, get: function () { return codex_1.parseCodexTranscript; } });
|
|
82
|
+
Object.defineProperty(exports, "readCodexTranscriptUsage", { enumerable: true, get: function () { return codex_1.readCodexTranscriptUsage; } });
|
|
83
|
+
Object.defineProperty(exports, "CODEX_FORBIDDEN_FIELDS", { enumerable: true, get: function () { return codex_1.CODEX_FORBIDDEN_FIELDS; } });
|
|
84
|
+
var server_1 = require("./proxy/server");
|
|
85
|
+
Object.defineProperty(exports, "startProxy", { enumerable: true, get: function () { return server_1.startProxy; } });
|
|
86
|
+
Object.defineProperty(exports, "profileFor", { enumerable: true, get: function () { return server_1.profileFor; } });
|
|
87
|
+
var usage_observer_1 = require("./proxy/usage-observer");
|
|
88
|
+
Object.defineProperty(exports, "UsageObserver", { enumerable: true, get: function () { return usage_observer_1.UsageObserver; } });
|
|
89
|
+
var status_1 = require("./status");
|
|
90
|
+
Object.defineProperty(exports, "renderMachineStatus", { enumerable: true, get: function () { return status_1.renderMachineStatus; } });
|
|
91
|
+
var conformance_1 = require("./conformance");
|
|
92
|
+
Object.defineProperty(exports, "runConformance", { enumerable: true, get: function () { return conformance_1.runConformance; } });
|
|
93
|
+
var override_1 = require("./override");
|
|
94
|
+
Object.defineProperty(exports, "writeOverride", { enumerable: true, get: function () { return override_1.writeOverride; } });
|
|
95
|
+
Object.defineProperty(exports, "readOverride", { enumerable: true, get: function () { return override_1.readOverride; } });
|
|
96
|
+
Object.defineProperty(exports, "consumeOverride", { enumerable: true, get: function () { return override_1.consumeOverride; } });
|
|
97
|
+
Object.defineProperty(exports, "clearOverride", { enumerable: true, get: function () { return override_1.clearOverride; } });
|
|
98
|
+
Object.defineProperty(exports, "OVERRIDE_WINDOW_MS", { enumerable: true, get: function () { return override_1.OVERRIDE_WINDOW_MS; } });
|
|
99
|
+
var install_1 = require("./install");
|
|
100
|
+
Object.defineProperty(exports, "install", { enumerable: true, get: function () { return install_1.install; } });
|
|
101
|
+
Object.defineProperty(exports, "health", { enumerable: true, get: function () { return install_1.health; } });
|
|
102
|
+
Object.defineProperty(exports, "configPath", { enumerable: true, get: function () { return install_1.configPath; } });
|
|
103
|
+
var status_2 = require("./status");
|
|
104
|
+
Object.defineProperty(exports, "readHookSessions", { enumerable: true, get: function () { return status_2.readHookSessions; } });
|
|
105
|
+
Object.defineProperty(exports, "renderHostHealth", { enumerable: true, get: function () { return status_2.renderHostHealth; } });
|
|
106
|
+
var pre_tool_use_2 = require("./hook/pre-tool-use");
|
|
107
|
+
Object.defineProperty(exports, "findingSignature", { enumerable: true, get: function () { return pre_tool_use_2.findingSignature; } });
|
|
108
|
+
Object.defineProperty(exports, "inflateHookSession", { enumerable: true, get: function () { return pre_tool_use_2.inflateHookSession; } });
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installing the hook into each host's config, and checking it is there.
|
|
3
|
+
*
|
|
4
|
+
* Merging into a config file the user also edits by hand is the one place
|
|
5
|
+
* this tool can do damage, so: always a timestamped backup first, always a
|
|
6
|
+
* parse-before-write, always an atomic rename, and the AgentGuard entry is
|
|
7
|
+
* found by its command (any command mentioning agentguard-burn), so a
|
|
8
|
+
* re-run upgrades the path in place instead of stacking duplicates.
|
|
9
|
+
*/
|
|
10
|
+
export type InstallHost = 'claude' | 'cursor' | 'codex';
|
|
11
|
+
export interface InstallResult {
|
|
12
|
+
file: string;
|
|
13
|
+
backup: string | null;
|
|
14
|
+
changed: boolean;
|
|
15
|
+
command: string;
|
|
16
|
+
}
|
|
17
|
+
export interface HostHealth {
|
|
18
|
+
host: InstallHost;
|
|
19
|
+
file: string;
|
|
20
|
+
installed: boolean;
|
|
21
|
+
command: string | null;
|
|
22
|
+
/** The script the command points at exists on disk. */
|
|
23
|
+
commandExists: boolean;
|
|
24
|
+
/** The host itself is present (its config dir or binary). */
|
|
25
|
+
hostPresent: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare function configPath(host: InstallHost, home?: string): string;
|
|
28
|
+
export declare function install(host: InstallHost, cliPath: string, home?: string): InstallResult;
|
|
29
|
+
export declare function health(host: InstallHost, home?: string): HostHealth;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Installing the hook into each host's config, and checking it is there.
|
|
4
|
+
*
|
|
5
|
+
* Merging into a config file the user also edits by hand is the one place
|
|
6
|
+
* this tool can do damage, so: always a timestamped backup first, always a
|
|
7
|
+
* parse-before-write, always an atomic rename, and the AgentGuard entry is
|
|
8
|
+
* found by its command (any command mentioning agentguard-burn), so a
|
|
9
|
+
* re-run upgrades the path in place instead of stacking duplicates.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.configPath = configPath;
|
|
13
|
+
exports.install = install;
|
|
14
|
+
exports.health = health;
|
|
15
|
+
const node_fs_1 = require("node:fs");
|
|
16
|
+
const node_os_1 = require("node:os");
|
|
17
|
+
const node_path_1 = require("node:path");
|
|
18
|
+
const MARK = /agentguard-burn|agentguard-run\/burn/;
|
|
19
|
+
function configPath(host, home = (0, node_os_1.homedir)()) {
|
|
20
|
+
return host === 'claude' ? (0, node_path_1.join)(home, '.claude', 'settings.json') : host === 'cursor' ? (0, node_path_1.join)(home, '.cursor', 'hooks.json') : (0, node_path_1.join)(home, '.codex', 'hooks.json');
|
|
21
|
+
}
|
|
22
|
+
function readJson(file) {
|
|
23
|
+
if (!(0, node_fs_1.existsSync)(file))
|
|
24
|
+
return {};
|
|
25
|
+
const text = (0, node_fs_1.readFileSync)(file, 'utf8');
|
|
26
|
+
if (!text.trim())
|
|
27
|
+
return {};
|
|
28
|
+
const parsed = JSON.parse(text); // throws on a corrupt file: we refuse to overwrite what we cannot read
|
|
29
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
30
|
+
throw new Error(`${file} is not a JSON object`);
|
|
31
|
+
return parsed;
|
|
32
|
+
}
|
|
33
|
+
function writeJson(file, value) {
|
|
34
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(file, '..'), { recursive: true });
|
|
35
|
+
let backup = null;
|
|
36
|
+
if ((0, node_fs_1.existsSync)(file)) {
|
|
37
|
+
backup = `${file}.bak-${new Date().toISOString().replace(/[:.]/g, '').slice(0, 15)}`;
|
|
38
|
+
(0, node_fs_1.copyFileSync)(file, backup);
|
|
39
|
+
}
|
|
40
|
+
const tmp = `${file}.agentguard.tmp`;
|
|
41
|
+
(0, node_fs_1.writeFileSync)(tmp, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
42
|
+
(0, node_fs_1.renameSync)(tmp, file);
|
|
43
|
+
return backup;
|
|
44
|
+
}
|
|
45
|
+
/** Claude Code and Codex share the { hooks: { Event: [ { matcher, hooks: [ {command} ] } ] } } shape. */
|
|
46
|
+
function mergeMatcherStyle(cfg, event, matcher, command, timeout) {
|
|
47
|
+
const hooks = (cfg.hooks ??= {});
|
|
48
|
+
const list = (hooks[event] ??= []);
|
|
49
|
+
let changed = false;
|
|
50
|
+
let found = false;
|
|
51
|
+
for (const m of list) {
|
|
52
|
+
for (const h of m.hooks ?? []) {
|
|
53
|
+
if (typeof h.command === 'string' && MARK.test(h.command)) {
|
|
54
|
+
found = true;
|
|
55
|
+
if (h.command !== command || m.matcher !== matcher || h.timeout !== timeout) {
|
|
56
|
+
h.command = command;
|
|
57
|
+
h.timeout = timeout;
|
|
58
|
+
m.matcher = matcher;
|
|
59
|
+
changed = true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!found) {
|
|
65
|
+
list.push({ matcher, hooks: [{ type: 'command', command, timeout }] });
|
|
66
|
+
changed = true;
|
|
67
|
+
}
|
|
68
|
+
return changed;
|
|
69
|
+
}
|
|
70
|
+
/** Cursor: { version: 1, hooks: { subagentStart: [ {command, timeout, failClosed} ] } } */
|
|
71
|
+
function mergeCursor(cfg, command) {
|
|
72
|
+
let changed = false;
|
|
73
|
+
if (cfg.version !== 1) {
|
|
74
|
+
cfg.version = 1;
|
|
75
|
+
changed = true;
|
|
76
|
+
}
|
|
77
|
+
const hooks = (cfg.hooks ??= {});
|
|
78
|
+
const want = {
|
|
79
|
+
subagentStart: { command, timeout: 5, failClosed: true },
|
|
80
|
+
subagentStop: { command, timeout: 5 },
|
|
81
|
+
sessionEnd: { command, timeout: 3 },
|
|
82
|
+
};
|
|
83
|
+
for (const [event, entry] of Object.entries(want)) {
|
|
84
|
+
const list = (hooks[event] ??= []);
|
|
85
|
+
const mine = list.find((h) => typeof h.command === 'string' && MARK.test(h.command));
|
|
86
|
+
if (!mine) {
|
|
87
|
+
list.push(entry);
|
|
88
|
+
changed = true;
|
|
89
|
+
}
|
|
90
|
+
else if (JSON.stringify(mine) !== JSON.stringify({ ...mine, ...entry })) {
|
|
91
|
+
Object.assign(mine, entry);
|
|
92
|
+
changed = true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return changed;
|
|
96
|
+
}
|
|
97
|
+
function install(host, cliPath, home = (0, node_os_1.homedir)()) {
|
|
98
|
+
const file = configPath(host, home);
|
|
99
|
+
const cfg = readJson(file);
|
|
100
|
+
let command;
|
|
101
|
+
let changed;
|
|
102
|
+
if (host === 'claude') {
|
|
103
|
+
command = `node ${cliPath} hook`;
|
|
104
|
+
changed = mergeMatcherStyle(cfg, 'PreToolUse', '^(Agent|Task)$', command, 5);
|
|
105
|
+
}
|
|
106
|
+
else if (host === 'codex') {
|
|
107
|
+
command = `node ${cliPath} codex-hook`;
|
|
108
|
+
changed = mergeMatcherStyle(cfg, 'PreToolUse', '^(Agent|spawn_agent)$', command, 5);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
command = `node ${cliPath} cursor-hook`;
|
|
112
|
+
changed = mergeCursor(cfg, command);
|
|
113
|
+
}
|
|
114
|
+
const backup = changed ? writeJson(file, cfg) : null;
|
|
115
|
+
return { file, backup, changed, command };
|
|
116
|
+
}
|
|
117
|
+
function findCommand(cfg) {
|
|
118
|
+
const hooks = cfg.hooks;
|
|
119
|
+
if (!hooks || typeof hooks !== 'object')
|
|
120
|
+
return null;
|
|
121
|
+
for (const list of Object.values(hooks)) {
|
|
122
|
+
if (!Array.isArray(list))
|
|
123
|
+
continue;
|
|
124
|
+
for (const item of list) {
|
|
125
|
+
const entries = Array.isArray(item.hooks) ? item.hooks : [item];
|
|
126
|
+
for (const h of entries)
|
|
127
|
+
if (typeof h.command === 'string' && MARK.test(h.command))
|
|
128
|
+
return h.command;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
function health(host, home = (0, node_os_1.homedir)()) {
|
|
134
|
+
const file = configPath(host, home);
|
|
135
|
+
let command = null;
|
|
136
|
+
try {
|
|
137
|
+
command = findCommand(readJson(file));
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
command = null;
|
|
141
|
+
}
|
|
142
|
+
const script = command?.match(/^node (\S+)/)?.[1] ?? null;
|
|
143
|
+
const hostPresent = host === 'claude' ? (0, node_fs_1.existsSync)((0, node_path_1.join)(home, '.claude')) : host === 'cursor' ? (0, node_fs_1.existsSync)((0, node_path_1.join)(home, '.cursor')) : (0, node_fs_1.existsSync)((0, node_path_1.join)(home, '.codex'));
|
|
144
|
+
return { host, file, installed: command !== null, command, commandExists: script !== null && (0, node_fs_1.existsSync)(script), hostPresent };
|
|
145
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The audited override.
|
|
3
|
+
*
|
|
4
|
+
* The STOP box says "override once: agentguard-burn resume --once". That
|
|
5
|
+
* has to be real, and it has to be single-use across every host on the
|
|
6
|
+
* machine: two hooks that both see the file must not both let a spawn
|
|
7
|
+
* through. Consumption is a rename, which is atomic; the loser of the race
|
|
8
|
+
* finds nothing to rename and blocks.
|
|
9
|
+
*
|
|
10
|
+
* Without --once the override is a window, 15 minutes by default, for the
|
|
11
|
+
* case where the user has decided the session is fine and does not want to
|
|
12
|
+
* type the command forty times. Every overridden STOP is written to the
|
|
13
|
+
* decisions ledger with the reason, so "why did this get through" always
|
|
14
|
+
* has an answer.
|
|
15
|
+
*/
|
|
16
|
+
export interface Override {
|
|
17
|
+
at: number;
|
|
18
|
+
once: boolean;
|
|
19
|
+
reason: string;
|
|
20
|
+
/** Absent for --once. */
|
|
21
|
+
until?: number;
|
|
22
|
+
}
|
|
23
|
+
export declare const OVERRIDE_WINDOW_MS: number;
|
|
24
|
+
export declare function writeOverride(home: string, o: Override): void;
|
|
25
|
+
export declare function readOverride(home: string, now?: number): Override | null;
|
|
26
|
+
/**
|
|
27
|
+
* Called only when a STOP is about to be enforced. Returns the override to
|
|
28
|
+
* apply, or null. A --once override is consumed here and cannot be used
|
|
29
|
+
* twice, however many processes ask at the same moment.
|
|
30
|
+
*/
|
|
31
|
+
export declare function consumeOverride(home: string, now?: number): Override | null;
|
|
32
|
+
export declare function clearOverride(home: string): void;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The audited override.
|
|
4
|
+
*
|
|
5
|
+
* The STOP box says "override once: agentguard-burn resume --once". That
|
|
6
|
+
* has to be real, and it has to be single-use across every host on the
|
|
7
|
+
* machine: two hooks that both see the file must not both let a spawn
|
|
8
|
+
* through. Consumption is a rename, which is atomic; the loser of the race
|
|
9
|
+
* finds nothing to rename and blocks.
|
|
10
|
+
*
|
|
11
|
+
* Without --once the override is a window, 15 minutes by default, for the
|
|
12
|
+
* case where the user has decided the session is fine and does not want to
|
|
13
|
+
* type the command forty times. Every overridden STOP is written to the
|
|
14
|
+
* decisions ledger with the reason, so "why did this get through" always
|
|
15
|
+
* has an answer.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.OVERRIDE_WINDOW_MS = void 0;
|
|
19
|
+
exports.writeOverride = writeOverride;
|
|
20
|
+
exports.readOverride = readOverride;
|
|
21
|
+
exports.consumeOverride = consumeOverride;
|
|
22
|
+
exports.clearOverride = clearOverride;
|
|
23
|
+
const node_fs_1 = require("node:fs");
|
|
24
|
+
const node_path_1 = require("node:path");
|
|
25
|
+
exports.OVERRIDE_WINDOW_MS = 15 * 60 * 1000;
|
|
26
|
+
function file(home) {
|
|
27
|
+
return (0, node_path_1.join)(home, 'override.json');
|
|
28
|
+
}
|
|
29
|
+
function writeOverride(home, o) {
|
|
30
|
+
(0, node_fs_1.mkdirSync)(home, { recursive: true, mode: 0o700 });
|
|
31
|
+
(0, node_fs_1.writeFileSync)(file(home), JSON.stringify(o), { mode: 0o600 });
|
|
32
|
+
}
|
|
33
|
+
function readOverride(home, now = Date.now()) {
|
|
34
|
+
try {
|
|
35
|
+
const o = JSON.parse((0, node_fs_1.readFileSync)(file(home), 'utf8'));
|
|
36
|
+
if (typeof o.reason !== 'string' || typeof o.at !== 'number')
|
|
37
|
+
return null;
|
|
38
|
+
if (!o.once && (o.until === undefined || o.until < now))
|
|
39
|
+
return null;
|
|
40
|
+
return o;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Called only when a STOP is about to be enforced. Returns the override to
|
|
48
|
+
* apply, or null. A --once override is consumed here and cannot be used
|
|
49
|
+
* twice, however many processes ask at the same moment.
|
|
50
|
+
*/
|
|
51
|
+
function consumeOverride(home, now = Date.now()) {
|
|
52
|
+
const o = readOverride(home, now);
|
|
53
|
+
if (!o) {
|
|
54
|
+
if ((0, node_fs_1.existsSync)(file(home)))
|
|
55
|
+
(0, node_fs_1.rmSync)(file(home), { force: true }); // expired or malformed
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
if (!o.once)
|
|
59
|
+
return o;
|
|
60
|
+
const consumed = `${file(home)}.used.${process.pid}.${now.toString(36)}`;
|
|
61
|
+
try {
|
|
62
|
+
(0, node_fs_1.renameSync)(file(home), consumed);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null; // a sibling consumed it first
|
|
66
|
+
}
|
|
67
|
+
(0, node_fs_1.rmSync)(consumed, { force: true });
|
|
68
|
+
return o;
|
|
69
|
+
}
|
|
70
|
+
function clearOverride(home) {
|
|
71
|
+
(0, node_fs_1.rmSync)(file(home), { force: true });
|
|
72
|
+
}
|