@gethelio/proxy 0.4.0 → 0.5.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/dist/cli.js +1748 -269
- package/dist/dashboard-assets/assets/index-DZKoV0Vx.css +1 -0
- package/dist/dashboard-assets/assets/{index-CAgnN6wV.js → index-DgywE2WQ.js} +30 -30
- package/dist/dashboard-assets/index.html +2 -2
- package/dist/index.d.ts +415 -11
- package/dist/index.js +1716 -268
- package/package.json +1 -1
- package/dist/dashboard-assets/assets/index-DG3h7Cvn.css +0 -1
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
<meta name="referrer" content="no-referrer" />
|
|
7
7
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
8
8
|
<title>Helio Dashboard</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-DgywE2WQ.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DZKoV0Vx.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<div id="root"></div>
|
package/dist/index.d.ts
CHANGED
|
@@ -36,6 +36,12 @@ declare const policiesSchema: z.ZodObject<{
|
|
|
36
36
|
regex: z.ZodOptional<z.ZodString>;
|
|
37
37
|
}, z.core.$strict>>>;
|
|
38
38
|
environment: z.ZodOptional<z.ZodString>;
|
|
39
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
40
|
+
eq: z.ZodOptional<z.ZodString>;
|
|
41
|
+
neq: z.ZodOptional<z.ZodString>;
|
|
42
|
+
contains: z.ZodOptional<z.ZodString>;
|
|
43
|
+
regex: z.ZodOptional<z.ZodString>;
|
|
44
|
+
}, z.core.$strict>]>>>;
|
|
39
45
|
}, z.core.$strict>;
|
|
40
46
|
action: z.ZodEnum<{
|
|
41
47
|
allow: "allow";
|
|
@@ -63,6 +69,7 @@ declare const policiesSchema: z.ZodObject<{
|
|
|
63
69
|
tool: "tool";
|
|
64
70
|
agent: "agent";
|
|
65
71
|
session: "session";
|
|
72
|
+
sender_id: "sender_id";
|
|
66
73
|
}>>;
|
|
67
74
|
max_spend: z.ZodOptional<z.ZodObject<{
|
|
68
75
|
field: z.ZodString;
|
|
@@ -73,6 +80,7 @@ declare const policiesSchema: z.ZodObject<{
|
|
|
73
80
|
tool: "tool";
|
|
74
81
|
agent: "agent";
|
|
75
82
|
session: "session";
|
|
83
|
+
sender_id: "sender_id";
|
|
76
84
|
}>>;
|
|
77
85
|
}, z.core.$strict>>;
|
|
78
86
|
}, z.core.$strict>>;
|
|
@@ -81,6 +89,33 @@ declare const policiesSchema: z.ZodObject<{
|
|
|
81
89
|
suggestion: z.ZodOptional<z.ZodString>;
|
|
82
90
|
}, z.core.$strict>>;
|
|
83
91
|
}, z.core.$strict>>>;
|
|
92
|
+
install: z.ZodOptional<z.ZodObject<{
|
|
93
|
+
default: z.ZodDefault<z.ZodEnum<{
|
|
94
|
+
allow: "allow";
|
|
95
|
+
deny: "deny";
|
|
96
|
+
}>>;
|
|
97
|
+
rules: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
98
|
+
name: z.ZodOptional<z.ZodString>;
|
|
99
|
+
match: z.ZodObject<{
|
|
100
|
+
name: z.ZodOptional<z.ZodString>;
|
|
101
|
+
source: z.ZodOptional<z.ZodString>;
|
|
102
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
103
|
+
eq: z.ZodOptional<z.ZodString>;
|
|
104
|
+
neq: z.ZodOptional<z.ZodString>;
|
|
105
|
+
contains: z.ZodOptional<z.ZodString>;
|
|
106
|
+
regex: z.ZodOptional<z.ZodString>;
|
|
107
|
+
}, z.core.$strict>]>>>;
|
|
108
|
+
}, z.core.$strict>;
|
|
109
|
+
action: z.ZodEnum<{
|
|
110
|
+
allow: "allow";
|
|
111
|
+
deny_install: "deny_install";
|
|
112
|
+
}>;
|
|
113
|
+
feedback: z.ZodOptional<z.ZodObject<{
|
|
114
|
+
message: z.ZodString;
|
|
115
|
+
suggestion: z.ZodOptional<z.ZodString>;
|
|
116
|
+
}, z.core.$strict>>;
|
|
117
|
+
}, z.core.$strict>>>;
|
|
118
|
+
}, z.core.$strict>>;
|
|
84
119
|
on_tool_drift: z.ZodOptional<z.ZodEnum<{
|
|
85
120
|
require_approval: "require_approval";
|
|
86
121
|
log: "log";
|
|
@@ -164,6 +199,12 @@ declare const helioConfigSchema: z.ZodObject<{
|
|
|
164
199
|
regex: z.ZodOptional<z.ZodString>;
|
|
165
200
|
}, z.core.$strict>>>;
|
|
166
201
|
environment: z.ZodOptional<z.ZodString>;
|
|
202
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
203
|
+
eq: z.ZodOptional<z.ZodString>;
|
|
204
|
+
neq: z.ZodOptional<z.ZodString>;
|
|
205
|
+
contains: z.ZodOptional<z.ZodString>;
|
|
206
|
+
regex: z.ZodOptional<z.ZodString>;
|
|
207
|
+
}, z.core.$strict>]>>>;
|
|
167
208
|
}, z.core.$strict>;
|
|
168
209
|
action: z.ZodEnum<{
|
|
169
210
|
allow: "allow";
|
|
@@ -191,6 +232,7 @@ declare const helioConfigSchema: z.ZodObject<{
|
|
|
191
232
|
tool: "tool";
|
|
192
233
|
agent: "agent";
|
|
193
234
|
session: "session";
|
|
235
|
+
sender_id: "sender_id";
|
|
194
236
|
}>>;
|
|
195
237
|
max_spend: z.ZodOptional<z.ZodObject<{
|
|
196
238
|
field: z.ZodString;
|
|
@@ -201,6 +243,7 @@ declare const helioConfigSchema: z.ZodObject<{
|
|
|
201
243
|
tool: "tool";
|
|
202
244
|
agent: "agent";
|
|
203
245
|
session: "session";
|
|
246
|
+
sender_id: "sender_id";
|
|
204
247
|
}>>;
|
|
205
248
|
}, z.core.$strict>>;
|
|
206
249
|
}, z.core.$strict>>;
|
|
@@ -209,6 +252,33 @@ declare const helioConfigSchema: z.ZodObject<{
|
|
|
209
252
|
suggestion: z.ZodOptional<z.ZodString>;
|
|
210
253
|
}, z.core.$strict>>;
|
|
211
254
|
}, z.core.$strict>>>;
|
|
255
|
+
install: z.ZodOptional<z.ZodObject<{
|
|
256
|
+
default: z.ZodDefault<z.ZodEnum<{
|
|
257
|
+
allow: "allow";
|
|
258
|
+
deny: "deny";
|
|
259
|
+
}>>;
|
|
260
|
+
rules: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
261
|
+
name: z.ZodOptional<z.ZodString>;
|
|
262
|
+
match: z.ZodObject<{
|
|
263
|
+
name: z.ZodOptional<z.ZodString>;
|
|
264
|
+
source: z.ZodOptional<z.ZodString>;
|
|
265
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
266
|
+
eq: z.ZodOptional<z.ZodString>;
|
|
267
|
+
neq: z.ZodOptional<z.ZodString>;
|
|
268
|
+
contains: z.ZodOptional<z.ZodString>;
|
|
269
|
+
regex: z.ZodOptional<z.ZodString>;
|
|
270
|
+
}, z.core.$strict>]>>>;
|
|
271
|
+
}, z.core.$strict>;
|
|
272
|
+
action: z.ZodEnum<{
|
|
273
|
+
allow: "allow";
|
|
274
|
+
deny_install: "deny_install";
|
|
275
|
+
}>;
|
|
276
|
+
feedback: z.ZodOptional<z.ZodObject<{
|
|
277
|
+
message: z.ZodString;
|
|
278
|
+
suggestion: z.ZodOptional<z.ZodString>;
|
|
279
|
+
}, z.core.$strict>>;
|
|
280
|
+
}, z.core.$strict>>>;
|
|
281
|
+
}, z.core.$strict>>;
|
|
212
282
|
on_tool_drift: z.ZodOptional<z.ZodEnum<{
|
|
213
283
|
require_approval: "require_approval";
|
|
214
284
|
log: "log";
|
|
@@ -250,6 +320,7 @@ declare const helioConfigSchema: z.ZodObject<{
|
|
|
250
320
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
251
321
|
port: z.ZodDefault<z.ZodNumber>;
|
|
252
322
|
host: z.ZodDefault<z.ZodString>;
|
|
323
|
+
evaluation_ttl: z.ZodDefault<z.ZodString>;
|
|
253
324
|
}, z.core.$strip>>;
|
|
254
325
|
}, z.core.$strip>;
|
|
255
326
|
/** Fully validated and defaulted Helio configuration. */
|
|
@@ -313,6 +384,22 @@ interface InputCondition {
|
|
|
313
384
|
/** Pre-compiled RegExp when operator is 'regex'. */
|
|
314
385
|
readonly regex?: RegExp;
|
|
315
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* A single flattened metadata condition (issue #13 — `match.metadata.*`).
|
|
389
|
+
*
|
|
390
|
+
* Mirrors InputCondition but matches a flat top-level key of the adapter-supplied
|
|
391
|
+
* context object (no JSONPath traversal), and is restricted to the string-friendly
|
|
392
|
+
* operator subset — metadata values (channel_id, sender_id, …) are strings, so the
|
|
393
|
+
* numeric comparators are deliberately excluded.
|
|
394
|
+
*/
|
|
395
|
+
interface MetadataCondition {
|
|
396
|
+
/** The metadata key to read (e.g. "channel_id", "sender_id", or virtual "agent_id"). */
|
|
397
|
+
readonly key: string;
|
|
398
|
+
readonly operator: 'eq' | 'neq' | 'contains' | 'regex';
|
|
399
|
+
readonly value: unknown;
|
|
400
|
+
/** Pre-compiled RegExp when operator is 'regex'. */
|
|
401
|
+
readonly regex?: RegExp;
|
|
402
|
+
}
|
|
316
403
|
/** Compiled match block for a policy rule. */
|
|
317
404
|
interface CompiledMatch {
|
|
318
405
|
readonly tool?: ToolMatcher;
|
|
@@ -320,6 +407,8 @@ interface CompiledMatch {
|
|
|
320
407
|
/** Flattened list of input conditions (one entry per path+operator pair). */
|
|
321
408
|
readonly input?: readonly InputCondition[];
|
|
322
409
|
readonly environment?: string;
|
|
410
|
+
/** Flattened list of metadata conditions (one entry per key+operator pair). */
|
|
411
|
+
readonly metadata?: readonly MetadataCondition[];
|
|
323
412
|
}
|
|
324
413
|
/** Compiled approval configuration with durations as milliseconds. */
|
|
325
414
|
interface CompiledApproval {
|
|
@@ -334,13 +423,13 @@ interface CompiledSpendLimit {
|
|
|
334
423
|
readonly limit: number;
|
|
335
424
|
readonly currency: string;
|
|
336
425
|
readonly windowMs: number;
|
|
337
|
-
readonly key?: 'tool' | 'agent' | 'session';
|
|
426
|
+
readonly key?: 'tool' | 'agent' | 'session' | 'sender_id';
|
|
338
427
|
}
|
|
339
428
|
/** Compiled rate/spend limit configuration. */
|
|
340
429
|
interface CompiledLimits {
|
|
341
430
|
readonly maxCalls?: number;
|
|
342
431
|
readonly windowMs?: number;
|
|
343
|
-
readonly key?: 'tool' | 'agent' | 'session';
|
|
432
|
+
readonly key?: 'tool' | 'agent' | 'session' | 'sender_id';
|
|
344
433
|
readonly maxSpend?: CompiledSpendLimit;
|
|
345
434
|
}
|
|
346
435
|
/** Policy action types. */
|
|
@@ -371,6 +460,31 @@ interface CompiledPolicyRule {
|
|
|
371
460
|
readonly suggestion?: string;
|
|
372
461
|
};
|
|
373
462
|
}
|
|
463
|
+
/** Compiled match block for an install-time rule (issue #13). */
|
|
464
|
+
interface CompiledInstallMatch {
|
|
465
|
+
/** Glob matcher on the package name. */
|
|
466
|
+
readonly name?: ToolMatcher;
|
|
467
|
+
/** Exact ecosystem/source match (npm | pip | …). */
|
|
468
|
+
readonly source?: string;
|
|
469
|
+
/** Flattened metadata conditions (sender/channel-gated installs). */
|
|
470
|
+
readonly metadata?: readonly MetadataCondition[];
|
|
471
|
+
}
|
|
472
|
+
/** A compiled install-time rule. */
|
|
473
|
+
interface CompiledInstallRule {
|
|
474
|
+
readonly index: number;
|
|
475
|
+
readonly name?: string;
|
|
476
|
+
readonly match: CompiledInstallMatch;
|
|
477
|
+
readonly action: 'deny_install' | 'allow';
|
|
478
|
+
readonly feedback?: {
|
|
479
|
+
readonly message: string;
|
|
480
|
+
readonly suggestion?: string;
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
/** Compiled install-time policy (issue #13). */
|
|
484
|
+
interface CompiledInstallPolicy {
|
|
485
|
+
readonly defaultAction: 'allow' | 'deny';
|
|
486
|
+
readonly rules: readonly CompiledInstallRule[];
|
|
487
|
+
}
|
|
374
488
|
/** Top-level compiled policy — what the engine consumes. */
|
|
375
489
|
interface CompiledPolicy {
|
|
376
490
|
readonly defaultAction: 'allow' | 'deny';
|
|
@@ -382,6 +496,8 @@ interface CompiledPolicy {
|
|
|
382
496
|
*/
|
|
383
497
|
readonly onToolDrift?: 'block' | 'require_approval' | 'log';
|
|
384
498
|
readonly rules: readonly CompiledPolicyRule[];
|
|
499
|
+
/** Install-time policy (issue #13 — deny_install). Undefined ⇒ observational. */
|
|
500
|
+
readonly install?: CompiledInstallPolicy;
|
|
385
501
|
}
|
|
386
502
|
/** A non-fatal warning produced during policy compilation. */
|
|
387
503
|
interface PolicyParseWarning {
|
|
@@ -421,6 +537,13 @@ interface MatchContext {
|
|
|
421
537
|
readonly toolArguments?: Readonly<Record<string, unknown>>;
|
|
422
538
|
/** The configured environment label (e.g. "production", "staging"). */
|
|
423
539
|
readonly environment?: string;
|
|
540
|
+
/**
|
|
541
|
+
* Adapter-supplied context for `match.metadata.*` (issue #13). Present only on
|
|
542
|
+
* the sideband (host-enforced) path; always absent on the MCP path, so metadata
|
|
543
|
+
* rules are inert there by construction. The virtual `agent_id` key (from the
|
|
544
|
+
* request column) is merged in by the decision pipeline, not stored here twice.
|
|
545
|
+
*/
|
|
546
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
424
547
|
}
|
|
425
548
|
|
|
426
549
|
/** A JSON-RPC 2.0 request object. */
|
|
@@ -737,6 +860,27 @@ interface AuditRecord {
|
|
|
737
860
|
readonly flagged_destructive: boolean;
|
|
738
861
|
/** Whether this record was produced in dry-run mode. */
|
|
739
862
|
readonly dry_run: boolean;
|
|
863
|
+
/**
|
|
864
|
+
* Record category discriminator (issues #12/#16). `'tool_call'` is the
|
|
865
|
+
* default for governed tool calls; `'drift_event'` for tool-definition drift
|
|
866
|
+
* records; `'install_scan'` for sideband install evaluations; and
|
|
867
|
+
* `'evaluation_expired'` for sideband evaluations whose `/audit` never
|
|
868
|
+
* arrived (the bypass/tamper signal — block_reason stays null so they do not
|
|
869
|
+
* count as enforcement blocks).
|
|
870
|
+
*/
|
|
871
|
+
readonly record_kind: 'tool_call' | 'drift_event' | 'install_scan' | 'evaluation_expired';
|
|
872
|
+
/**
|
|
873
|
+
* Enforcement origin: `'mcp'` for the proxy path, or an adapter-supplied
|
|
874
|
+
* origin string (e.g. `'openclaw'`) for sideband-governed calls. Surfaces
|
|
875
|
+
* the enforcement-grade ladder (structural vs host-enforced) per record.
|
|
876
|
+
*/
|
|
877
|
+
readonly origin: string;
|
|
878
|
+
/**
|
|
879
|
+
* Adapter-supplied context object (reserved keys: `channel_id`, `sender_id`,
|
|
880
|
+
* `sender_name`, `conversation_id`). Null for MCP-origin records. Backs
|
|
881
|
+
* `match.metadata.*` (#13) and the dashboard metadata columns (#16).
|
|
882
|
+
*/
|
|
883
|
+
readonly metadata: Record<string, unknown> | null;
|
|
740
884
|
/** ISO 8601 timestamp of when the record was persisted. */
|
|
741
885
|
readonly created_at: string;
|
|
742
886
|
}
|
|
@@ -762,6 +906,14 @@ interface AuditQueryFilters {
|
|
|
762
906
|
readonly flagged_destructive?: boolean;
|
|
763
907
|
/** Include only dry-run records (true) or non-dry-run records (false). */
|
|
764
908
|
readonly dry_run?: boolean;
|
|
909
|
+
/** Filter by record kind (tool_call / drift_event / install_scan / evaluation_expired). */
|
|
910
|
+
readonly record_kind?: string;
|
|
911
|
+
/** Filter by enforcement origin (e.g. 'mcp', 'openclaw'). */
|
|
912
|
+
readonly origin?: string;
|
|
913
|
+
/** Filter by metadata.channel_id (adapter-supplied; JSON-extracted, substring match). */
|
|
914
|
+
readonly channel_id?: string;
|
|
915
|
+
/** Filter by metadata.sender_id (adapter-supplied; JSON-extracted, substring match). */
|
|
916
|
+
readonly sender_id?: string;
|
|
765
917
|
/** Include only records where upstream HTTP status is >= this value. */
|
|
766
918
|
readonly upstream_status_min?: number;
|
|
767
919
|
/** Include only records where upstream HTTP status is <= this value. */
|
|
@@ -932,7 +1084,7 @@ declare class AuditWriter {
|
|
|
932
1084
|
* is scheduled. This keeps request-path latency bounded even under bursty
|
|
933
1085
|
* write load.
|
|
934
1086
|
*/
|
|
935
|
-
push(record: Omit<AuditRecord, 'id' | 'created_at'
|
|
1087
|
+
push(record: Omit<AuditRecord, 'id' | 'created_at'>, id?: string): void;
|
|
936
1088
|
/**
|
|
937
1089
|
* Push a record and schedule a high-priority async flush.
|
|
938
1090
|
*
|
|
@@ -941,7 +1093,7 @@ declare class AuditWriter {
|
|
|
941
1093
|
* A fatal-process crash still invokes the crash-drain hook, which calls
|
|
942
1094
|
* `flush()` synchronously before exit.
|
|
943
1095
|
*/
|
|
944
|
-
pushImmediate(record: Omit<AuditRecord, 'id' | 'created_at'
|
|
1096
|
+
pushImmediate(record: Omit<AuditRecord, 'id' | 'created_at'>, id?: string): void;
|
|
945
1097
|
/**
|
|
946
1098
|
* Schedule a flush on the next tick, coalescing multiple calls into one.
|
|
947
1099
|
*/
|
|
@@ -1125,7 +1277,14 @@ declare class EvidenceStore {
|
|
|
1125
1277
|
}
|
|
1126
1278
|
|
|
1127
1279
|
/** Possible states of an approval ticket. */
|
|
1128
|
-
type ApprovalStatus = 'pending' | 'approved' | 'denied' | 'timeout' | 'break_glass' | 'client_disconnected' | 'shutdown_cancelled'
|
|
1280
|
+
type ApprovalStatus = 'pending' | 'approved' | 'denied' | 'timeout' | 'break_glass' | 'client_disconnected' | 'shutdown_cancelled'
|
|
1281
|
+
/**
|
|
1282
|
+
* The approver's native UI cancelled the request (e.g. an OpenClaw
|
|
1283
|
+
* `/approve` dialog dismissed without a decision). Distinct from
|
|
1284
|
+
* `client_disconnected`, which the router reserves for the requesting agent
|
|
1285
|
+
* aborting the held MCP request. Sideband (native) approvals only. (#12.)
|
|
1286
|
+
*/
|
|
1287
|
+
| 'cancelled';
|
|
1129
1288
|
/** A failed attempt to deliver an approval notification. */
|
|
1130
1289
|
interface ApprovalNotificationFailure {
|
|
1131
1290
|
readonly channel: string;
|
|
@@ -1248,7 +1407,7 @@ declare class ApprovalQueue {
|
|
|
1248
1407
|
* @returns `true` if the ticket was resolved, `false` if not found or
|
|
1249
1408
|
* already resolved (no double-resolution).
|
|
1250
1409
|
*/
|
|
1251
|
-
resolve(id: string, status: 'approved' | 'denied' | 'timeout' | 'break_glass' | 'client_disconnected' | 'shutdown_cancelled', resolvedBy?: string, options?: {
|
|
1410
|
+
resolve(id: string, status: 'approved' | 'denied' | 'timeout' | 'break_glass' | 'client_disconnected' | 'shutdown_cancelled' | 'cancelled', resolvedBy?: string, options?: {
|
|
1252
1411
|
denial_reason?: string;
|
|
1253
1412
|
break_glass_reason?: string;
|
|
1254
1413
|
}): boolean;
|
|
@@ -1291,6 +1450,19 @@ interface ApprovalSubmitParams {
|
|
|
1291
1450
|
readonly matched_rule: CompiledPolicyRule | undefined;
|
|
1292
1451
|
readonly session_id: string | null;
|
|
1293
1452
|
}
|
|
1453
|
+
/** Resolution statuses a native (sideband) ticket can be moved to. */
|
|
1454
|
+
type NativeResolution = 'approved' | 'denied' | 'timeout' | 'cancelled';
|
|
1455
|
+
/** Parameters for creating a native (adapter-owned) approval ticket. */
|
|
1456
|
+
interface NativeTicketParams {
|
|
1457
|
+
readonly tool_name: string;
|
|
1458
|
+
readonly tool_input: Record<string, unknown>;
|
|
1459
|
+
readonly matched_rule: CompiledPolicyRule | undefined;
|
|
1460
|
+
readonly session_id: string | null;
|
|
1461
|
+
/** Adapter origin (e.g. "openclaw"); stored as channel_name `native:<origin>`. */
|
|
1462
|
+
readonly origin: string;
|
|
1463
|
+
/** Ticket timeout in ms (rule timeout, else the router default). */
|
|
1464
|
+
readonly timeout_ms?: number;
|
|
1465
|
+
}
|
|
1294
1466
|
declare class ApprovalRouter {
|
|
1295
1467
|
private readonly defaultTimeoutMs;
|
|
1296
1468
|
readonly defaultOnTimeout: 'allow' | 'deny';
|
|
@@ -1311,6 +1483,36 @@ declare class ApprovalRouter {
|
|
|
1311
1483
|
* is held open.
|
|
1312
1484
|
*/
|
|
1313
1485
|
submit(params: ApprovalSubmitParams, abortSignal?: AbortSignal): Promise<ApprovalOutcome>;
|
|
1486
|
+
/**
|
|
1487
|
+
* Create a native (adapter-owned) approval ticket without holding a Promise.
|
|
1488
|
+
*
|
|
1489
|
+
* Used by the sideband governance path (issue #12, D10): when `/evaluate`
|
|
1490
|
+
* yields `require_approval`, the adapter runs the approval in its own UI
|
|
1491
|
+
* (e.g. OpenClaw's Telegram dialog), so Helio must NOT block, start
|
|
1492
|
+
* timeout/escalation timers, or notify a channel — doing so would
|
|
1493
|
+
* double-notify. We still create the queue ticket and fire `onSubmit` so the
|
|
1494
|
+
* dashboard's `approval_requested` SSE event flows and the ticket is visible.
|
|
1495
|
+
*
|
|
1496
|
+
* The ticket's `channel_name` is `native:<origin>`, which marks it as
|
|
1497
|
+
* adapter-owned: the dashboard approve/deny endpoints refuse it (it can only
|
|
1498
|
+
* be resolved through the adapter), and {@link resolveNativeTicket} is the
|
|
1499
|
+
* resolution path.
|
|
1500
|
+
*/
|
|
1501
|
+
createNativeTicket(params: NativeTicketParams): ApprovalTicket;
|
|
1502
|
+
/**
|
|
1503
|
+
* Resolve a native ticket created by {@link createNativeTicket}.
|
|
1504
|
+
*
|
|
1505
|
+
* Resolves the queue ticket and fires `onResolve` (→ `approval_resolved`
|
|
1506
|
+
* SSE), with no held Promise to settle. Refuses tickets that have a pending
|
|
1507
|
+
* router Promise (those are MCP-path tickets; resolving them here would leave
|
|
1508
|
+
* the held request hanging) and tickets that are not `native:`-prefixed.
|
|
1509
|
+
*
|
|
1510
|
+
* @returns `true` if resolved, `false` if not found, already resolved, not a
|
|
1511
|
+
* native ticket, or router-managed.
|
|
1512
|
+
*/
|
|
1513
|
+
resolveNativeTicket(ticketId: string, status: NativeResolution, resolvedBy?: string, options?: {
|
|
1514
|
+
denial_reason?: string;
|
|
1515
|
+
}): boolean;
|
|
1314
1516
|
/**
|
|
1315
1517
|
* Approve a pending ticket. Resolves the held Promise so the governed
|
|
1316
1518
|
* forwarder can forward the request upstream.
|
|
@@ -1335,6 +1537,8 @@ declare class ApprovalRouter {
|
|
|
1335
1537
|
* already resolved.
|
|
1336
1538
|
*/
|
|
1337
1539
|
breakGlass(ticketId: string, resolvedBy: string, reason: string): boolean;
|
|
1540
|
+
/** Look up a ticket by id (delegates to the queue). */
|
|
1541
|
+
getTicket(ticketId: string): ApprovalTicket | undefined;
|
|
1338
1542
|
/** Clean up all pending timers and resolve all pending promises. */
|
|
1339
1543
|
close(): void;
|
|
1340
1544
|
private finalizePending;
|
|
@@ -1405,6 +1609,21 @@ declare class RateLimiter {
|
|
|
1405
1609
|
* - At/over limit: does NOT record (blocked calls don't consume a slot)
|
|
1406
1610
|
*/
|
|
1407
1611
|
check(params: RateLimitCheckParams): RateLimitResult;
|
|
1612
|
+
/**
|
|
1613
|
+
* Unconditionally record a call against the rate limit.
|
|
1614
|
+
*
|
|
1615
|
+
* Unlike check(), this always appends the timestamp — even when the bucket
|
|
1616
|
+
* is already at/over the limit — because the call it represents has already
|
|
1617
|
+
* executed. The sideband splits decision from execution: /evaluate peeks
|
|
1618
|
+
* (non-destructive), and /audit calls record() once the external call ran,
|
|
1619
|
+
* so refusing to record at the limit (as check() does) would let real calls
|
|
1620
|
+
* escape accounting and under-count subsequent peeks. (issue #12, D3.)
|
|
1621
|
+
*
|
|
1622
|
+
* Warnings fire only while the post-append count stays within the limit —
|
|
1623
|
+
* exact parity with check(), which never warns on its over-limit path — so a
|
|
1624
|
+
* burst of over-limit audits cannot flood the dashboard's limit_warning feed.
|
|
1625
|
+
*/
|
|
1626
|
+
record(params: RateLimitCheckParams): RateLimitResult;
|
|
1408
1627
|
/**
|
|
1409
1628
|
* Check the rate limit without recording the call (non-destructive).
|
|
1410
1629
|
*
|
|
@@ -1514,6 +1733,20 @@ declare class SpendLimiter {
|
|
|
1514
1733
|
* - Would exceed: does NOT record (rejected spends don't consume budget)
|
|
1515
1734
|
*/
|
|
1516
1735
|
check(params: SpendLimitCheckParams): SpendLimitResult;
|
|
1736
|
+
/**
|
|
1737
|
+
* Unconditionally record a spend against the limit.
|
|
1738
|
+
*
|
|
1739
|
+
* Unlike check(), this always appends the amount — even when it pushes the
|
|
1740
|
+
* window past the limit — because the spend it represents has already been
|
|
1741
|
+
* incurred. The sideband peeks at /evaluate and commits here at /audit once
|
|
1742
|
+
* the external call ran (issue #12, D3).
|
|
1743
|
+
*
|
|
1744
|
+
* Throws on a negative or non-finite amount: such amounts are rejected at
|
|
1745
|
+
* /evaluate, so one reaching record() is a logic bug we surface loudly rather
|
|
1746
|
+
* than silently corrupt the sliding-window sum. Warnings fire only while the
|
|
1747
|
+
* post-append spend stays within the limit (parity with check()).
|
|
1748
|
+
*/
|
|
1749
|
+
record(params: SpendLimitCheckParams): SpendLimitResult;
|
|
1517
1750
|
/**
|
|
1518
1751
|
* Check the spend limit without recording the spend (non-destructive).
|
|
1519
1752
|
*
|
|
@@ -1602,6 +1835,7 @@ declare class GovernedForwarder implements McpForwarder {
|
|
|
1602
1835
|
private readonly spendLimiter;
|
|
1603
1836
|
private readonly annotationCache;
|
|
1604
1837
|
private agentKeyWarned;
|
|
1838
|
+
private senderKeyWarned;
|
|
1605
1839
|
constructor(inner: McpForwarder, policy: CompiledPolicy, options?: GovernedForwarderOptions);
|
|
1606
1840
|
/**
|
|
1607
1841
|
* Swap the compiled policy atomically and reconcile limit bucket state
|
|
@@ -1665,15 +1899,169 @@ declare class GovernedForwarder implements McpForwarder {
|
|
|
1665
1899
|
private makeClientDisconnectedBlockResult;
|
|
1666
1900
|
}
|
|
1667
1901
|
|
|
1902
|
+
/** The outcome vocabulary adapters branch on — never internal rule actions. */
|
|
1903
|
+
type WireDecision = 'allow' | 'deny' | 'require_approval' | 'rate_limited' | 'spend_limited' | 'dry_run';
|
|
1904
|
+
/** Tool definition carried by /evaluate (optional; enables the drift guard). */
|
|
1905
|
+
interface WireToolDefinition {
|
|
1906
|
+
readonly name: string;
|
|
1907
|
+
readonly description?: string;
|
|
1908
|
+
readonly input_schema?: unknown;
|
|
1909
|
+
readonly output_schema?: unknown;
|
|
1910
|
+
readonly title?: string;
|
|
1911
|
+
readonly annotations?: Record<string, unknown>;
|
|
1912
|
+
}
|
|
1913
|
+
interface EvaluateInput {
|
|
1914
|
+
readonly origin: string;
|
|
1915
|
+
readonly adapter_version?: string;
|
|
1916
|
+
readonly agent_id: string | null;
|
|
1917
|
+
readonly session_id: string | null;
|
|
1918
|
+
readonly tool: WireToolDefinition;
|
|
1919
|
+
readonly arguments: Record<string, unknown> | undefined;
|
|
1920
|
+
readonly metadata: Record<string, unknown> | null;
|
|
1921
|
+
}
|
|
1922
|
+
interface InstallScanInput {
|
|
1923
|
+
readonly origin: string;
|
|
1924
|
+
readonly agent_id: string | null;
|
|
1925
|
+
readonly session_id: string | null;
|
|
1926
|
+
readonly package: {
|
|
1927
|
+
readonly name: string;
|
|
1928
|
+
readonly version?: string;
|
|
1929
|
+
readonly source?: string;
|
|
1930
|
+
readonly spec?: string;
|
|
1931
|
+
readonly url?: string;
|
|
1932
|
+
};
|
|
1933
|
+
readonly metadata: Record<string, unknown> | null;
|
|
1934
|
+
}
|
|
1935
|
+
interface AuditInput {
|
|
1936
|
+
readonly evaluation_id: string;
|
|
1937
|
+
readonly status: 'success' | 'error' | 'not_executed';
|
|
1938
|
+
readonly error?: string;
|
|
1939
|
+
readonly duration_ms?: number;
|
|
1940
|
+
readonly result?: unknown;
|
|
1941
|
+
readonly actual_amount?: number;
|
|
1942
|
+
}
|
|
1943
|
+
interface ResolveApprovalInput {
|
|
1944
|
+
readonly resolution: 'approved' | 'denied' | 'timeout' | 'cancelled';
|
|
1945
|
+
readonly resolved_by?: string;
|
|
1946
|
+
readonly reason?: string;
|
|
1947
|
+
readonly scope?: 'once' | 'always';
|
|
1948
|
+
}
|
|
1949
|
+
/** A service result: HTTP status + JSON body, adapted verbatim by the route. */
|
|
1950
|
+
interface ServiceResult {
|
|
1951
|
+
readonly status: number;
|
|
1952
|
+
readonly body: Record<string, unknown>;
|
|
1953
|
+
}
|
|
1954
|
+
interface GovernanceServiceOptions {
|
|
1955
|
+
readonly policy: CompiledPolicy;
|
|
1956
|
+
readonly environment?: string;
|
|
1957
|
+
readonly evidenceStore?: EvidenceStore;
|
|
1958
|
+
readonly approvalRouter?: ApprovalRouter;
|
|
1959
|
+
readonly rateLimiter?: RateLimiter;
|
|
1960
|
+
readonly spendLimiter?: SpendLimiter;
|
|
1961
|
+
readonly auditWriter?: AuditWriter;
|
|
1962
|
+
/** Default approval timeout (ms) when a rule sets none. */
|
|
1963
|
+
readonly approvalTimeoutMs?: number;
|
|
1964
|
+
/** Pending-evaluation TTL (ms). Default 10 minutes. */
|
|
1965
|
+
readonly ttlMs?: number;
|
|
1966
|
+
/** Clock for testable time. Defaults to Date.now. */
|
|
1967
|
+
readonly now?: () => number;
|
|
1968
|
+
/** Sweep interval (ms). 0 disables the periodic GC backstop. Default 30s. */
|
|
1969
|
+
readonly sweepIntervalMs?: number;
|
|
1970
|
+
/** Max pending evaluations (count). Default 10,000. Overridable for tests. */
|
|
1971
|
+
readonly maxPending?: number;
|
|
1972
|
+
/** Max pending-evaluation footprint (serialized bytes). Default 64 MiB. */
|
|
1973
|
+
readonly maxPendingBytes?: number;
|
|
1974
|
+
/** Max distinct sender_id limit keys (issue #13). Default 50,000. Overridable for tests. */
|
|
1975
|
+
readonly maxSenderKeys?: number;
|
|
1976
|
+
}
|
|
1977
|
+
declare class GovernanceService {
|
|
1978
|
+
private policy;
|
|
1979
|
+
private readonly environment;
|
|
1980
|
+
private readonly evidenceStore;
|
|
1981
|
+
private readonly approvalRouter;
|
|
1982
|
+
private readonly rateLimiter;
|
|
1983
|
+
private readonly spendLimiter;
|
|
1984
|
+
private readonly auditWriter;
|
|
1985
|
+
private readonly approvalTimeoutMs;
|
|
1986
|
+
private readonly ttlMs;
|
|
1987
|
+
private readonly now;
|
|
1988
|
+
private readonly maxPending;
|
|
1989
|
+
private readonly maxPendingBytes;
|
|
1990
|
+
private readonly maxSenderKeys;
|
|
1991
|
+
/** Distinct sender_id limit keys with live state (reservation registry, issue #13). */
|
|
1992
|
+
private readonly senderKeys;
|
|
1993
|
+
private readonly pending;
|
|
1994
|
+
private readonly tombstones;
|
|
1995
|
+
private readonly caches;
|
|
1996
|
+
/** Native approval ticket id → its pending evaluation id, for on-access
|
|
1997
|
+
* deadline enforcement on the resolve path. */
|
|
1998
|
+
private readonly ticketToEvaluation;
|
|
1999
|
+
private pendingBytes;
|
|
2000
|
+
private sweepTimer;
|
|
2001
|
+
private closed;
|
|
2002
|
+
constructor(options: GovernanceServiceOptions);
|
|
2003
|
+
/** Swap the compiled policy on hot-reload (mirrors GovernedForwarder). */
|
|
2004
|
+
updatePolicy(policy: CompiledPolicy): void;
|
|
2005
|
+
evaluate(req: EvaluateInput): ServiceResult;
|
|
2006
|
+
audit(req: AuditInput, payloadHash: string): ServiceResult;
|
|
2007
|
+
installScan(req: InstallScanInput): ServiceResult;
|
|
2008
|
+
/** First-match-wins evaluation of the compiled install policy (issue #13). */
|
|
2009
|
+
private evaluateInstall;
|
|
2010
|
+
resolveApproval(ticketId: string, req: ResolveApprovalInput): ServiceResult;
|
|
2011
|
+
sweep(): void;
|
|
2012
|
+
/**
|
|
2013
|
+
* Reserve a cardinality slot for a sender-keyed limit (issue #13).
|
|
2014
|
+
*
|
|
2015
|
+
* Only `sender:*` keys are gated — tool/session families are bounded by upstream
|
|
2016
|
+
* cardinality, and the MCP path never reaches here, so structural traffic cannot
|
|
2017
|
+
* be starved. A key already backed by live state (registry or a live limiter
|
|
2018
|
+
* bucket) costs no new slot. At capacity we lazily prune dead keys before failing
|
|
2019
|
+
* closed, so an emptied bucket frees its slot without waiting for the sweep.
|
|
2020
|
+
*/
|
|
2021
|
+
private reserveSenderKey;
|
|
2022
|
+
/** Drop registry keys with no pending evaluation AND no live limiter bucket. */
|
|
2023
|
+
private pruneSenderKeys;
|
|
2024
|
+
/**
|
|
2025
|
+
* Whether either limiter still holds a live bucket for `key`. Uses the public
|
|
2026
|
+
* `getKeyState()` — never the limiters' private maps — and its lazy eviction of
|
|
2027
|
+
* an emptied bucket IS the prune-on-touch mechanism.
|
|
2028
|
+
*/
|
|
2029
|
+
private hasLiveBucket;
|
|
2030
|
+
close(): void;
|
|
2031
|
+
/** Apply crossed deadlines to one pending entry. Returns its post-state. */
|
|
2032
|
+
private enforceDeadlines;
|
|
2033
|
+
private cacheFor;
|
|
2034
|
+
private discardPending;
|
|
2035
|
+
private getTicketStatus;
|
|
2036
|
+
private planRate;
|
|
2037
|
+
private planSpend;
|
|
2038
|
+
/** Commit a limit plan at /audit time and return the evidence_chain block. */
|
|
2039
|
+
private commitLimit;
|
|
2040
|
+
private writeAudit;
|
|
2041
|
+
private assertApprovalRouter;
|
|
2042
|
+
}
|
|
2043
|
+
|
|
1668
2044
|
/** Options for constructing the SDK sideband Hono app. */
|
|
1669
2045
|
interface SidebandAppOptions {
|
|
1670
2046
|
/**
|
|
1671
|
-
* Bearer token
|
|
1672
|
-
*
|
|
1673
|
-
*
|
|
1674
|
-
* explicitly disabled the per-boot token via env.
|
|
2047
|
+
* Bearer token for the evidence/context/session routes (the SDK scope).
|
|
2048
|
+
* When omitted or empty, those routes run open — useful for local
|
|
2049
|
+
* development when the operator has disabled the per-boot token via env.
|
|
1675
2050
|
*/
|
|
1676
2051
|
readonly token?: string;
|
|
2052
|
+
/**
|
|
2053
|
+
* Bearer token for the governance routes (the adapter scope, issue #12/F6).
|
|
2054
|
+
* Distinct from `token` so an SDK client cannot drive policy decisions and
|
|
2055
|
+
* an adapter cannot write evidence it was not granted. When omitted, the
|
|
2056
|
+
* governance routes run open (and only mount if `governance` is provided).
|
|
2057
|
+
*/
|
|
2058
|
+
readonly adapterToken?: string;
|
|
2059
|
+
/**
|
|
2060
|
+
* The governance service backing `/evaluate`, `/audit`, `/install-scan`, and
|
|
2061
|
+
* `/approval/:id/resolve`. When omitted, those routes return 503
|
|
2062
|
+
* `governance_unavailable` (evidence-only deployments, and existing tests).
|
|
2063
|
+
*/
|
|
2064
|
+
readonly governance?: GovernanceService;
|
|
1677
2065
|
}
|
|
1678
2066
|
/**
|
|
1679
2067
|
* Create a Hono app for the SDK sideband API.
|
|
@@ -1698,6 +2086,20 @@ interface SidebandAppOptions {
|
|
|
1698
2086
|
*/
|
|
1699
2087
|
declare function createSidebandApp(store: EvidenceStore, options?: SidebandAppOptions): Hono;
|
|
1700
2088
|
|
|
2089
|
+
/**
|
|
2090
|
+
* Error thrown when the GovernanceService is wired in a way that could fail
|
|
2091
|
+
* open — for example, an approval-capable policy with no ApprovalRouter to
|
|
2092
|
+
* route `require_approval` decisions through (issue #12). Surfaced at
|
|
2093
|
+
* construction and on hot-reload so a misconfiguration crashes loudly rather
|
|
2094
|
+
* than silently degrading an approval into an unenforced allow.
|
|
2095
|
+
*
|
|
2096
|
+
* Direct embedders of GovernanceService can catch this distinctly; the bundled
|
|
2097
|
+
* CLI always provides a router, so it never fires in the shipped path.
|
|
2098
|
+
*/
|
|
2099
|
+
declare class GovernanceConfigError extends Error {
|
|
2100
|
+
constructor(message: string);
|
|
2101
|
+
}
|
|
2102
|
+
|
|
1701
2103
|
/** @internal Exported for testing only. */
|
|
1702
2104
|
declare class QueueChannel implements ApprovalChannel {
|
|
1703
2105
|
readonly type = "dashboard";
|
|
@@ -1834,6 +2236,8 @@ interface ActionEvent {
|
|
|
1834
2236
|
readonly dry_run: boolean;
|
|
1835
2237
|
readonly matched_rule: string | null;
|
|
1836
2238
|
readonly matched_rule_index: number | null;
|
|
2239
|
+
readonly record_kind: AuditRecord['record_kind'];
|
|
2240
|
+
readonly origin: string;
|
|
1837
2241
|
}
|
|
1838
2242
|
/** Payload for an approval_requested event. */
|
|
1839
2243
|
interface ApprovalRequestedEvent {
|
|
@@ -1928,4 +2332,4 @@ interface DashboardAppOptions {
|
|
|
1928
2332
|
*/
|
|
1929
2333
|
declare function createDashboardApp(deps: DashboardAppDeps, options?: DashboardAppOptions): Hono;
|
|
1930
2334
|
|
|
1931
|
-
export { type ApprovalAppOptions, type ApprovalChannel, type ApprovalOutcome, ApprovalQueue, type ApprovalQueueOptions, ApprovalRouter, type ApprovalRouterOptions, type ApprovalStatus, type ApprovalTicket, type AuditAggregateStats, type AuditListResult, type AuditPaginationOptions, type AuditQueryFilters, type AuditRecord, AuditStore, type AuditStoreOptions, type AuditTimeBucket, AuditWriter, type AuditWriterOptions, type CompilePoliciesResult, type CompiledPolicy, type CompiledPolicyRule, ConfigError, type CreateAppOptions, type DashboardAppDeps, type DashboardAppOptions, DashboardEventBus, type DashboardEventType, type DashboardEvents, type EvidenceEntry, EvidenceStore, type EvidenceStoreOptions, GovernedForwarder, type GovernedForwarderOptions, type HelioConfig, type MatchContext, type PolicyDecision, PolicyParseError, QueueChannel, type RateLimitCheckParams, type RateLimitKeyState, type RateLimitResult, RateLimiter, type RateLimiterOptions, type ServerHandle, type SessionState, type SlackActionAppOptions, SlackChannel, type SlackChannelOptions, type SpendLimitCheckParams, type SpendLimitKeyState, type SpendLimitResult, SpendLimiter, type SpendLimiterOptions, SseUpstreamForwarder, type SseUpstreamForwarderOptions, StdioForwarder, type StdioForwarderOptions, StreamableHttpForwarder, type StreamableHttpForwarderOptions, UpstreamForwarder, type UpstreamForwarderOptions, VERSION, WebhookChannel, type WebhookChannelOptions, compilePolicies, createApp, createApprovalApp, createChannels, createDashboardApp, createSidebandApp, createSlackActionApp, evaluatePolicy, loadConfig, matchRule, startServer, startSidebandServer };
|
|
2335
|
+
export { type ApprovalAppOptions, type ApprovalChannel, type ApprovalOutcome, ApprovalQueue, type ApprovalQueueOptions, ApprovalRouter, type ApprovalRouterOptions, type ApprovalStatus, type ApprovalTicket, type AuditAggregateStats, type AuditInput, type AuditListResult, type AuditPaginationOptions, type AuditQueryFilters, type AuditRecord, AuditStore, type AuditStoreOptions, type AuditTimeBucket, AuditWriter, type AuditWriterOptions, type CompilePoliciesResult, type CompiledPolicy, type CompiledPolicyRule, ConfigError, type CreateAppOptions, type DashboardAppDeps, type DashboardAppOptions, DashboardEventBus, type DashboardEventType, type DashboardEvents, type EvaluateInput, type EvidenceEntry, EvidenceStore, type EvidenceStoreOptions, GovernanceConfigError, GovernanceService, type GovernanceServiceOptions, GovernedForwarder, type GovernedForwarderOptions, type HelioConfig, type InstallScanInput, type MatchContext, type PolicyDecision, PolicyParseError, QueueChannel, type RateLimitCheckParams, type RateLimitKeyState, type RateLimitResult, RateLimiter, type RateLimiterOptions, type ResolveApprovalInput, type ServerHandle, type SessionState, type SlackActionAppOptions, SlackChannel, type SlackChannelOptions, type SpendLimitCheckParams, type SpendLimitKeyState, type SpendLimitResult, SpendLimiter, type SpendLimiterOptions, SseUpstreamForwarder, type SseUpstreamForwarderOptions, StdioForwarder, type StdioForwarderOptions, StreamableHttpForwarder, type StreamableHttpForwarderOptions, UpstreamForwarder, type UpstreamForwarderOptions, VERSION, WebhookChannel, type WebhookChannelOptions, type WireDecision, compilePolicies, createApp, createApprovalApp, createChannels, createDashboardApp, createSidebandApp, createSlackActionApp, evaluatePolicy, loadConfig, matchRule, startServer, startSidebandServer };
|