@x402sentinel/x402 0.1.4 → 0.2.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/README.md +136 -79
- package/dist/index.cjs +110 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +110 -71
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,6 +22,92 @@
|
|
|
22
22
|
|
|
23
23
|
**One line to add. Zero config to start.**
|
|
24
24
|
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @x402sentinel/x402
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { sentinel } from "@x402sentinel/x402";
|
|
33
|
+
|
|
34
|
+
const fetch = sentinel(globalThis.fetch);
|
|
35
|
+
|
|
36
|
+
// That's it. Every x402 payment is now tracked.
|
|
37
|
+
// View your data: sentinel.valeocash.com
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### With options (optional)
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
const fetch = sentinel(globalThis.fetch, {
|
|
44
|
+
agentId: "my-agent",
|
|
45
|
+
apiKey: "your-api-key", // from sentinel.valeocash.com/dashboard/settings
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Framework Packages
|
|
52
|
+
|
|
53
|
+
One-liner integrations for popular frameworks:
|
|
54
|
+
|
|
55
|
+
| Package | Install |
|
|
56
|
+
|---------|---------|
|
|
57
|
+
| **Express** | `npm install @x402sentinel/express` |
|
|
58
|
+
| **Next.js** | `npm install @x402sentinel/next` |
|
|
59
|
+
| **LangChain** | `npm install @x402sentinel/langchain` |
|
|
60
|
+
| **Vercel AI SDK** | `npm install @x402sentinel/vercel-ai` |
|
|
61
|
+
|
|
62
|
+
### Express
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import express from "express";
|
|
66
|
+
import { sentinelMiddleware } from "@x402sentinel/express";
|
|
67
|
+
|
|
68
|
+
const app = express();
|
|
69
|
+
app.use(sentinelMiddleware());
|
|
70
|
+
|
|
71
|
+
app.get("/data", async (req, res) => {
|
|
72
|
+
const data = await req.sentinelFetch("https://api.example.com/paid");
|
|
73
|
+
res.json(await data.json());
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Next.js
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// app/api/data/route.ts
|
|
81
|
+
import { withSentinel } from "@x402sentinel/next";
|
|
82
|
+
|
|
83
|
+
export const GET = withSentinel(async (req, sentinelFetch) => {
|
|
84
|
+
const res = await sentinelFetch("https://api.example.com/paid");
|
|
85
|
+
return Response.json(await res.json());
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### LangChain
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { SentinelX402Tool } from "@x402sentinel/langchain";
|
|
93
|
+
|
|
94
|
+
const tools = [new SentinelX402Tool({ agentId: "research-agent" })];
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Vercel AI SDK
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { sentinelX402Tool } from "@x402sentinel/vercel-ai";
|
|
101
|
+
|
|
102
|
+
const result = await generateText({
|
|
103
|
+
model: openai("gpt-4"),
|
|
104
|
+
tools: { x402: sentinelX402Tool({ agentId: "my-agent" }) },
|
|
105
|
+
prompt: "Fetch the latest weather data",
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
25
111
|
## The Problem
|
|
26
112
|
|
|
27
113
|
AI agents are spending real money autonomously. The [x402 protocol](https://github.com/coinbase/x402) enables internet-native payments, but provides no built-in audit trail, budget controls, or compliance tooling.
|
|
@@ -34,7 +120,27 @@ Sentinel fixes this with a single line of code.
|
|
|
34
120
|
|
|
35
121
|
---
|
|
36
122
|
|
|
37
|
-
##
|
|
123
|
+
## Features
|
|
124
|
+
|
|
125
|
+
| Feature | Description |
|
|
126
|
+
|---------|-------------|
|
|
127
|
+
| **Budget Enforcement** | Per-call, hourly, daily, and lifetime spend limits. Blocks before payment. |
|
|
128
|
+
| **Spike Detection** | Flags payments that exceed N x the rolling average |
|
|
129
|
+
| **Audit Trails** | Every payment logged with agent ID, team, endpoint, tx hash, timing |
|
|
130
|
+
| **Cryptographic Receipts** | HMAC-SHA256 signed proof for every payment |
|
|
131
|
+
| **Endpoint Filtering** | Allowlist/blocklist URL patterns |
|
|
132
|
+
| **Approval Workflows** | Require human approval above a threshold |
|
|
133
|
+
| **Storage Backends** | In-memory (default), JSONL file, or remote API |
|
|
134
|
+
| **Dashboard Queries** | Query spend by agent, team, endpoint, time range |
|
|
135
|
+
| **CSV/JSON Export** | Export audit data for compliance reviews |
|
|
136
|
+
| **Zero Config** | Works with no API key. Claim your data later. |
|
|
137
|
+
| **Drop-in Wrapper** | One line change. Remove Sentinel, code works identically. |
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Advanced Configuration
|
|
142
|
+
|
|
143
|
+
For full control, use `wrapWithSentinel` directly:
|
|
38
144
|
|
|
39
145
|
### Before (plain x402)
|
|
40
146
|
|
|
@@ -54,67 +160,34 @@ const response = await fetchWithPayment("https://api.example.com/paid");
|
|
|
54
160
|
```ts
|
|
55
161
|
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
|
|
56
162
|
import { registerExactEvmScheme } from "@x402/evm/exact/client";
|
|
57
|
-
import { wrapWithSentinel, standardPolicy } from "@x402sentinel/x402";
|
|
163
|
+
import { wrapWithSentinel, standardPolicy } from "@x402sentinel/x402";
|
|
58
164
|
|
|
59
165
|
const client = new x402Client();
|
|
60
166
|
registerExactEvmScheme(client, { signer });
|
|
61
167
|
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
|
|
62
168
|
|
|
63
|
-
const secureFetch = wrapWithSentinel(fetchWithPayment, {
|
|
169
|
+
const secureFetch = wrapWithSentinel(fetchWithPayment, {
|
|
64
170
|
agentId: "agent-weather-001",
|
|
65
171
|
budget: standardPolicy(),
|
|
66
172
|
});
|
|
67
173
|
|
|
68
|
-
const response = await secureFetch("https://api.example.com/paid");
|
|
174
|
+
const response = await secureFetch("https://api.example.com/paid");
|
|
69
175
|
```
|
|
70
176
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
### Install
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
npm install @x402sentinel/x402
|
|
77
|
-
# or
|
|
78
|
-
pnpm add @x402sentinel/x402
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## Features
|
|
84
|
-
|
|
85
|
-
| Feature | Description |
|
|
86
|
-
|---------|-------------|
|
|
87
|
-
| **Budget Enforcement** | Per-call, hourly, daily, and lifetime spend limits. Blocks before payment. |
|
|
88
|
-
| **Spike Detection** | Flags payments that exceed N× the rolling average |
|
|
89
|
-
| **Audit Trails** | Every payment logged with agent ID, team, endpoint, tx hash, timing |
|
|
90
|
-
| **Endpoint Filtering** | Allowlist/blocklist URL patterns |
|
|
91
|
-
| **Approval Workflows** | Require human approval above a threshold |
|
|
92
|
-
| **Storage Backends** | In-memory (default), JSONL file, or remote API |
|
|
93
|
-
| **Dashboard Queries** | Query spend by agent, team, endpoint, time range |
|
|
94
|
-
| **CSV/JSON Export** | Export audit data for compliance reviews |
|
|
95
|
-
| **Zero Dependencies** | No runtime deps beyond x402 peer deps |
|
|
96
|
-
| **Drop-in Wrapper** | One line change. Remove Sentinel, code works identically. |
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
## Configuration
|
|
177
|
+
### Full Configuration
|
|
101
178
|
|
|
102
179
|
```ts
|
|
103
180
|
const secureFetch = wrapWithSentinel(fetchWithPayment, {
|
|
104
|
-
// Required
|
|
105
181
|
agentId: "agent-weather-001",
|
|
106
|
-
|
|
107
|
-
// Optional identity
|
|
108
182
|
team: "data-ops",
|
|
109
183
|
humanSponsor: "alice@company.com",
|
|
110
184
|
|
|
111
|
-
// Budget policy
|
|
112
185
|
budget: {
|
|
113
|
-
maxPerCall: "1.00",
|
|
114
|
-
maxPerHour: "25.00",
|
|
115
|
-
maxPerDay: "200.00",
|
|
116
|
-
maxTotal: "10000.00",
|
|
117
|
-
spikeThreshold: 3.0,
|
|
186
|
+
maxPerCall: "1.00",
|
|
187
|
+
maxPerHour: "25.00",
|
|
188
|
+
maxPerDay: "200.00",
|
|
189
|
+
maxTotal: "10000.00",
|
|
190
|
+
spikeThreshold: 3.0,
|
|
118
191
|
allowedEndpoints: ["https://api.trusted.com/*"],
|
|
119
192
|
blockedEndpoints: ["https://api.sketchy.com/*"],
|
|
120
193
|
requireApproval: {
|
|
@@ -123,10 +196,9 @@ const secureFetch = wrapWithSentinel(fetchWithPayment, {
|
|
|
123
196
|
},
|
|
124
197
|
},
|
|
125
198
|
|
|
126
|
-
// Audit settings
|
|
127
199
|
audit: {
|
|
128
200
|
enabled: true,
|
|
129
|
-
storage: new MemoryStorage(),
|
|
201
|
+
storage: new MemoryStorage(),
|
|
130
202
|
redactFields: ["secret_key"],
|
|
131
203
|
enrichment: {
|
|
132
204
|
staticTags: ["production"],
|
|
@@ -136,14 +208,12 @@ const secureFetch = wrapWithSentinel(fetchWithPayment, {
|
|
|
136
208
|
},
|
|
137
209
|
},
|
|
138
210
|
|
|
139
|
-
// Lifecycle hooks
|
|
140
211
|
hooks: {
|
|
141
212
|
afterPayment: async (record) => { /* log to DataDog */ },
|
|
142
213
|
onBudgetExceeded: async (violation) => { /* page on-call */ },
|
|
143
214
|
onAnomaly: async (anomaly) => { /* alert Slack */ },
|
|
144
215
|
},
|
|
145
216
|
|
|
146
|
-
// Custom metadata on every record
|
|
147
217
|
metadata: { environment: "production", cost_center: "ENG-2024" },
|
|
148
218
|
});
|
|
149
219
|
```
|
|
@@ -192,9 +262,8 @@ try {
|
|
|
192
262
|
} catch (err) {
|
|
193
263
|
if (err instanceof SentinelBudgetError) {
|
|
194
264
|
console.log(err.message);
|
|
195
|
-
|
|
196
|
-
console.log(err.violation.
|
|
197
|
-
console.log(err.violation.limit); // "5.00"
|
|
265
|
+
console.log(err.violation.type);
|
|
266
|
+
console.log(err.violation.limit);
|
|
198
267
|
}
|
|
199
268
|
}
|
|
200
269
|
```
|
|
@@ -207,12 +276,12 @@ Every payment produces an `AuditRecord` with:
|
|
|
207
276
|
|
|
208
277
|
```ts
|
|
209
278
|
{
|
|
210
|
-
id: "a1b2c3d4e5f6g7h8",
|
|
279
|
+
id: "a1b2c3d4e5f6g7h8",
|
|
211
280
|
agent_id: "agent-weather-001",
|
|
212
281
|
team: "data-ops",
|
|
213
282
|
human_sponsor: "alice@company.com",
|
|
214
|
-
amount: "0.50",
|
|
215
|
-
amount_raw: "500000",
|
|
283
|
+
amount: "0.50",
|
|
284
|
+
amount_raw: "500000",
|
|
216
285
|
asset: "USDC",
|
|
217
286
|
network: "eip155:8453",
|
|
218
287
|
tx_hash: "0xabc...",
|
|
@@ -239,7 +308,7 @@ const memory = new MemoryStorage(10_000);
|
|
|
239
308
|
// JSONL file — persistent, append-only
|
|
240
309
|
const file = new FileStorage(".valeo/audit.jsonl");
|
|
241
310
|
|
|
242
|
-
// Remote API — batched writes to
|
|
311
|
+
// Remote API — batched writes to sentinel.valeocash.com
|
|
243
312
|
const api = new ApiStorage({ apiKey: "val_..." });
|
|
244
313
|
```
|
|
245
314
|
|
|
@@ -258,7 +327,7 @@ const records = await logger.query({
|
|
|
258
327
|
});
|
|
259
328
|
|
|
260
329
|
const summary = await logger.summarize({ team: "data-ops" });
|
|
261
|
-
console.log(summary.total_spend);
|
|
330
|
+
console.log(summary.total_spend);
|
|
262
331
|
|
|
263
332
|
const csv = await logger.exportCSV();
|
|
264
333
|
```
|
|
@@ -272,17 +341,8 @@ import { SentinelDashboard } from "@x402sentinel/x402/dashboard";
|
|
|
272
341
|
|
|
273
342
|
const dashboard = new SentinelDashboard({ storage: myStorage });
|
|
274
343
|
|
|
275
|
-
|
|
276
|
-
const report = await dashboard.getSpend({
|
|
277
|
-
agentId: "bot-1",
|
|
278
|
-
range: "last_day",
|
|
279
|
-
});
|
|
280
|
-
console.log(report.totalSpend, report.count);
|
|
281
|
-
|
|
282
|
-
// Agent summaries
|
|
344
|
+
const report = await dashboard.getSpend({ agentId: "bot-1", range: "last_day" });
|
|
283
345
|
const agents = await dashboard.getAgents();
|
|
284
|
-
|
|
285
|
-
// Alerts (violations + anomalies)
|
|
286
346
|
const alerts = await dashboard.getAlerts();
|
|
287
347
|
```
|
|
288
348
|
|
|
@@ -290,23 +350,15 @@ Dashboard queries run locally against your storage backend. No remote API requir
|
|
|
290
350
|
|
|
291
351
|
---
|
|
292
352
|
|
|
293
|
-
##
|
|
294
|
-
|
|
295
|
-
Sentinel wraps **any** x402-compatible fetch. It works with:
|
|
296
|
-
|
|
297
|
-
- `@x402/fetch` + `x402Client` (current)
|
|
298
|
-
- `x402-fetch` + viem wallet (legacy)
|
|
299
|
-
- Any function with the `fetch` signature that handles x402 internally
|
|
353
|
+
## API Reference
|
|
300
354
|
|
|
301
|
-
|
|
355
|
+
### `sentinel(fetch, options?): typeof fetch`
|
|
302
356
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
## API Reference
|
|
357
|
+
Zero-config wrapper. Works with no API key. Call `sentinel(globalThis.fetch)` and every x402 payment is tracked.
|
|
306
358
|
|
|
307
359
|
### `wrapWithSentinel(fetch, config): typeof fetch`
|
|
308
360
|
|
|
309
|
-
|
|
361
|
+
Full-config wrapper with budget enforcement, audit trails, hooks, and metadata.
|
|
310
362
|
|
|
311
363
|
### `BudgetManager`
|
|
312
364
|
|
|
@@ -353,11 +405,16 @@ Sentinel is an SDK + Dashboard + API + Docs in a single monorepo:
|
|
|
353
405
|
|
|
354
406
|
```
|
|
355
407
|
├── / SDK (@x402sentinel/x402)
|
|
408
|
+
├── packages/ Framework integrations
|
|
409
|
+
│ ├── express/ @x402sentinel/express
|
|
410
|
+
│ ├── next/ @x402sentinel/next
|
|
411
|
+
│ ├── langchain/ @x402sentinel/langchain
|
|
412
|
+
│ └── vercel-ai/ @x402sentinel/vercel-ai
|
|
356
413
|
├── app/ Next.js 15 application
|
|
357
414
|
│ ├── /login API key authentication
|
|
358
415
|
│ ├── /dashboard Real-time analytics dashboard
|
|
359
416
|
│ ├── /docs Full documentation site
|
|
360
|
-
│ └── /api/v1/* REST API (
|
|
417
|
+
│ └── /api/v1/* REST API (20+ endpoints)
|
|
361
418
|
├── Dockerfile Production Docker image
|
|
362
419
|
└── docker-compose.yml One-command deployment
|
|
363
420
|
```
|
|
@@ -397,7 +454,7 @@ Login with demo API key: `sk_sentinel_demo_000`
|
|
|
397
454
|
- Escrow and pre-commitment flows
|
|
398
455
|
- Treasury management integration
|
|
399
456
|
- On-chain agent identity verification
|
|
400
|
-
- Real-time dashboard at [
|
|
457
|
+
- Real-time dashboard at [sentinel.valeocash.com](https://sentinel.valeocash.com)
|
|
401
458
|
|
|
402
459
|
---
|
|
403
460
|
|
package/dist/index.cjs
CHANGED
|
@@ -924,76 +924,6 @@ function wrapWithSentinel(fetchWithPayment, config) {
|
|
|
924
924
|
};
|
|
925
925
|
return sentinelFetch;
|
|
926
926
|
}
|
|
927
|
-
var FileStorage = class {
|
|
928
|
-
filePath;
|
|
929
|
-
buffer = [];
|
|
930
|
-
flushThreshold;
|
|
931
|
-
flushTimer = null;
|
|
932
|
-
constructor(filePath = ".valeo/audit.jsonl", flushThreshold = 100) {
|
|
933
|
-
this.filePath = filePath;
|
|
934
|
-
this.flushThreshold = flushThreshold;
|
|
935
|
-
this.ensureDir();
|
|
936
|
-
this.startAutoFlush();
|
|
937
|
-
}
|
|
938
|
-
async write(record) {
|
|
939
|
-
this.buffer.push(record);
|
|
940
|
-
if (this.buffer.length >= this.flushThreshold) {
|
|
941
|
-
await this.flush();
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
async query(query) {
|
|
945
|
-
await this.flush();
|
|
946
|
-
const all = this.readAll();
|
|
947
|
-
let results = all.filter((r) => matchesQuery(r, query));
|
|
948
|
-
const offset = query.offset ?? 0;
|
|
949
|
-
const limit = query.limit ?? results.length;
|
|
950
|
-
return results.slice(offset, offset + limit);
|
|
951
|
-
}
|
|
952
|
-
async summarize(query) {
|
|
953
|
-
await this.flush();
|
|
954
|
-
const records = this.readAll().filter((r) => matchesQuery(r, query));
|
|
955
|
-
return buildSummary(records, query);
|
|
956
|
-
}
|
|
957
|
-
async count(query) {
|
|
958
|
-
await this.flush();
|
|
959
|
-
return this.readAll().filter((r) => matchesQuery(r, query)).length;
|
|
960
|
-
}
|
|
961
|
-
async getById(id) {
|
|
962
|
-
await this.flush();
|
|
963
|
-
return this.readAll().find((r) => r.id === id) ?? null;
|
|
964
|
-
}
|
|
965
|
-
/** Write buffered records to disk */
|
|
966
|
-
async flush() {
|
|
967
|
-
if (this.buffer.length === 0) return;
|
|
968
|
-
const lines = this.buffer.map((r) => JSON.stringify(r)).join("\n") + "\n";
|
|
969
|
-
fs.appendFileSync(this.filePath, lines, "utf-8");
|
|
970
|
-
this.buffer = [];
|
|
971
|
-
}
|
|
972
|
-
/** Stop the auto-flush timer (for clean shutdown) */
|
|
973
|
-
destroy() {
|
|
974
|
-
if (this.flushTimer) {
|
|
975
|
-
clearInterval(this.flushTimer);
|
|
976
|
-
this.flushTimer = null;
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
readAll() {
|
|
980
|
-
if (!fs.existsSync(this.filePath)) return [];
|
|
981
|
-
const content = fs.readFileSync(this.filePath, "utf-8");
|
|
982
|
-
return content.split("\n").filter((line) => line.trim().length > 0).map((line) => JSON.parse(line));
|
|
983
|
-
}
|
|
984
|
-
ensureDir() {
|
|
985
|
-
const dir = path.dirname(this.filePath);
|
|
986
|
-
if (!fs.existsSync(dir)) {
|
|
987
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
startAutoFlush() {
|
|
991
|
-
this.flushTimer = setInterval(() => {
|
|
992
|
-
void this.flush();
|
|
993
|
-
}, 5e3);
|
|
994
|
-
this.flushTimer.unref();
|
|
995
|
-
}
|
|
996
|
-
};
|
|
997
927
|
|
|
998
928
|
// src/audit/storage/api.ts
|
|
999
929
|
var ApiStorage = class {
|
|
@@ -1115,6 +1045,115 @@ function sleep(ms) {
|
|
|
1115
1045
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1116
1046
|
}
|
|
1117
1047
|
|
|
1048
|
+
// src/sentinel.ts
|
|
1049
|
+
function sentinel(fetchFn, options) {
|
|
1050
|
+
const agentId = options?.agentId || `agent-${Date.now().toString(36)}`;
|
|
1051
|
+
const apiKey = options?.apiKey || "anonymous";
|
|
1052
|
+
const baseUrl = options?.baseUrl || "https://sentinel.valeocash.com";
|
|
1053
|
+
const wrapped = wrapWithSentinel(fetchFn, {
|
|
1054
|
+
agentId,
|
|
1055
|
+
budget: {
|
|
1056
|
+
maxPerCall: "10.00",
|
|
1057
|
+
maxPerHour: "100.00",
|
|
1058
|
+
maxPerDay: "1000.00"
|
|
1059
|
+
},
|
|
1060
|
+
audit: {
|
|
1061
|
+
storage: new ApiStorage({
|
|
1062
|
+
baseUrl: `${baseUrl}/api/v1`,
|
|
1063
|
+
apiKey
|
|
1064
|
+
})
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
if (apiKey === "anonymous") {
|
|
1068
|
+
let hasLoggedOnce = false;
|
|
1069
|
+
const sentinelFetch = async (input, init) => {
|
|
1070
|
+
const response = await wrapped(input, init);
|
|
1071
|
+
if (!hasLoggedOnce && (response.headers.has("payment-response") || response.headers.has("x-payment-response"))) {
|
|
1072
|
+
hasLoggedOnce = true;
|
|
1073
|
+
console.log(`
|
|
1074
|
+
\u2713 Payment tracked by Sentinel
|
|
1075
|
+
Agent: ${agentId}
|
|
1076
|
+
View: ${baseUrl}/agent/${agentId}
|
|
1077
|
+
Claim your data: ${baseUrl}/claim
|
|
1078
|
+
`);
|
|
1079
|
+
}
|
|
1080
|
+
return response;
|
|
1081
|
+
};
|
|
1082
|
+
return sentinelFetch;
|
|
1083
|
+
}
|
|
1084
|
+
return wrapped;
|
|
1085
|
+
}
|
|
1086
|
+
var FileStorage = class {
|
|
1087
|
+
filePath;
|
|
1088
|
+
buffer = [];
|
|
1089
|
+
flushThreshold;
|
|
1090
|
+
flushTimer = null;
|
|
1091
|
+
constructor(filePath = ".valeo/audit.jsonl", flushThreshold = 100) {
|
|
1092
|
+
this.filePath = filePath;
|
|
1093
|
+
this.flushThreshold = flushThreshold;
|
|
1094
|
+
this.ensureDir();
|
|
1095
|
+
this.startAutoFlush();
|
|
1096
|
+
}
|
|
1097
|
+
async write(record) {
|
|
1098
|
+
this.buffer.push(record);
|
|
1099
|
+
if (this.buffer.length >= this.flushThreshold) {
|
|
1100
|
+
await this.flush();
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
async query(query) {
|
|
1104
|
+
await this.flush();
|
|
1105
|
+
const all = this.readAll();
|
|
1106
|
+
let results = all.filter((r) => matchesQuery(r, query));
|
|
1107
|
+
const offset = query.offset ?? 0;
|
|
1108
|
+
const limit = query.limit ?? results.length;
|
|
1109
|
+
return results.slice(offset, offset + limit);
|
|
1110
|
+
}
|
|
1111
|
+
async summarize(query) {
|
|
1112
|
+
await this.flush();
|
|
1113
|
+
const records = this.readAll().filter((r) => matchesQuery(r, query));
|
|
1114
|
+
return buildSummary(records, query);
|
|
1115
|
+
}
|
|
1116
|
+
async count(query) {
|
|
1117
|
+
await this.flush();
|
|
1118
|
+
return this.readAll().filter((r) => matchesQuery(r, query)).length;
|
|
1119
|
+
}
|
|
1120
|
+
async getById(id) {
|
|
1121
|
+
await this.flush();
|
|
1122
|
+
return this.readAll().find((r) => r.id === id) ?? null;
|
|
1123
|
+
}
|
|
1124
|
+
/** Write buffered records to disk */
|
|
1125
|
+
async flush() {
|
|
1126
|
+
if (this.buffer.length === 0) return;
|
|
1127
|
+
const lines = this.buffer.map((r) => JSON.stringify(r)).join("\n") + "\n";
|
|
1128
|
+
fs.appendFileSync(this.filePath, lines, "utf-8");
|
|
1129
|
+
this.buffer = [];
|
|
1130
|
+
}
|
|
1131
|
+
/** Stop the auto-flush timer (for clean shutdown) */
|
|
1132
|
+
destroy() {
|
|
1133
|
+
if (this.flushTimer) {
|
|
1134
|
+
clearInterval(this.flushTimer);
|
|
1135
|
+
this.flushTimer = null;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
readAll() {
|
|
1139
|
+
if (!fs.existsSync(this.filePath)) return [];
|
|
1140
|
+
const content = fs.readFileSync(this.filePath, "utf-8");
|
|
1141
|
+
return content.split("\n").filter((line) => line.trim().length > 0).map((line) => JSON.parse(line));
|
|
1142
|
+
}
|
|
1143
|
+
ensureDir() {
|
|
1144
|
+
const dir = path.dirname(this.filePath);
|
|
1145
|
+
if (!fs.existsSync(dir)) {
|
|
1146
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
startAutoFlush() {
|
|
1150
|
+
this.flushTimer = setInterval(() => {
|
|
1151
|
+
void this.flush();
|
|
1152
|
+
}, 5e3);
|
|
1153
|
+
this.flushTimer.unref();
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
|
|
1118
1157
|
exports.ApiStorage = ApiStorage;
|
|
1119
1158
|
exports.AuditLogger = AuditLogger;
|
|
1120
1159
|
exports.BudgetManager = BudgetManager;
|
|
@@ -1127,6 +1166,7 @@ exports.SentinelError = SentinelError;
|
|
|
1127
1166
|
exports.conservativePolicy = conservativePolicy;
|
|
1128
1167
|
exports.customPolicy = customPolicy;
|
|
1129
1168
|
exports.liberalPolicy = liberalPolicy;
|
|
1169
|
+
exports.sentinel = sentinel;
|
|
1130
1170
|
exports.standardPolicy = standardPolicy;
|
|
1131
1171
|
exports.unlimitedPolicy = unlimitedPolicy;
|
|
1132
1172
|
exports.validateConfig = validateConfig;
|