govyn 0.0.1 → 0.2.5
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/LICENSE +21 -0
- package/README.md +263 -1
- package/configs/multi-provider.yaml +68 -0
- package/configs/openai-only.yaml +45 -0
- package/configs/team-setup.yaml +88 -0
- package/dist/action-logger.d.ts +128 -0
- package/dist/action-logger.js +356 -0
- package/dist/action-logger.js.map +1 -0
- package/dist/admin-cli.d.ts +2 -0
- package/dist/admin-cli.js +36 -0
- package/dist/admin-cli.js.map +1 -0
- package/dist/agents.d.ts +23 -0
- package/dist/agents.js +59 -0
- package/dist/agents.js.map +1 -0
- package/dist/alert-api.d.ts +14 -0
- package/dist/alert-api.js +355 -0
- package/dist/alert-api.js.map +1 -0
- package/dist/alert-manager.d.ts +77 -0
- package/dist/alert-manager.js +267 -0
- package/dist/alert-manager.js.map +1 -0
- package/dist/approval-api.d.ts +19 -0
- package/dist/approval-api.js +82 -0
- package/dist/approval-api.js.map +1 -0
- package/dist/approval-timeout.d.ts +29 -0
- package/dist/approval-timeout.js +45 -0
- package/dist/approval-timeout.js.map +1 -0
- package/dist/approval.d.ts +78 -0
- package/dist/approval.js +101 -0
- package/dist/approval.js.map +1 -0
- package/dist/auth.d.ts +47 -0
- package/dist/auth.js +335 -0
- package/dist/auth.js.map +1 -0
- package/dist/budget-api.d.ts +20 -0
- package/dist/budget-api.js +85 -0
- package/dist/budget-api.js.map +1 -0
- package/dist/budget-enforcer.d.ts +102 -0
- package/dist/budget-enforcer.js +294 -0
- package/dist/budget-enforcer.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +200 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.js +267 -0
- package/dist/config.js.map +1 -0
- package/dist/cost-aggregator.d.ts +69 -0
- package/dist/cost-aggregator.js +305 -0
- package/dist/cost-aggregator.js.map +1 -0
- package/dist/cost-api.d.ts +29 -0
- package/dist/cost-api.js +128 -0
- package/dist/cost-api.js.map +1 -0
- package/dist/database-url.d.ts +6 -0
- package/dist/database-url.js +47 -0
- package/dist/database-url.js.map +1 -0
- package/dist/db-retention.d.ts +53 -0
- package/dist/db-retention.js +82 -0
- package/dist/db-retention.js.map +1 -0
- package/dist/db-schema.d.ts +17 -0
- package/dist/db-schema.js +167 -0
- package/dist/db-schema.js.map +1 -0
- package/dist/db-writer.d.ts +55 -0
- package/dist/db-writer.js +115 -0
- package/dist/db-writer.js.map +1 -0
- package/dist/db.d.ts +33 -0
- package/dist/db.js +78 -0
- package/dist/db.js.map +1 -0
- package/dist/events.d.ts +77 -0
- package/dist/events.js +12 -0
- package/dist/events.js.map +1 -0
- package/dist/health.d.ts +14 -0
- package/dist/health.js +49 -0
- package/dist/health.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/init-wizard.d.ts +12 -0
- package/dist/init-wizard.js +206 -0
- package/dist/init-wizard.js.map +1 -0
- package/dist/log-api.d.ts +20 -0
- package/dist/log-api.js +371 -0
- package/dist/log-api.js.map +1 -0
- package/dist/log-rotator.d.ts +55 -0
- package/dist/log-rotator.js +157 -0
- package/dist/log-rotator.js.map +1 -0
- package/dist/loop-detector.d.ts +71 -0
- package/dist/loop-detector.js +122 -0
- package/dist/loop-detector.js.map +1 -0
- package/dist/persistence-types.d.ts +165 -0
- package/dist/persistence-types.js +2 -0
- package/dist/persistence-types.js.map +1 -0
- package/dist/persistence.d.ts +185 -0
- package/dist/persistence.js +785 -0
- package/dist/persistence.js.map +1 -0
- package/dist/policy-api.d.ts +25 -0
- package/dist/policy-api.js +347 -0
- package/dist/policy-api.js.map +1 -0
- package/dist/policy-engine.d.ts +76 -0
- package/dist/policy-engine.js +835 -0
- package/dist/policy-engine.js.map +1 -0
- package/dist/policy-file.d.ts +10 -0
- package/dist/policy-file.js +52 -0
- package/dist/policy-file.js.map +1 -0
- package/dist/policy-parser.d.ts +21 -0
- package/dist/policy-parser.js +560 -0
- package/dist/policy-parser.js.map +1 -0
- package/dist/policy-types.d.ts +216 -0
- package/dist/policy-types.js +8 -0
- package/dist/policy-types.js.map +1 -0
- package/dist/policy-watcher.d.ts +54 -0
- package/dist/policy-watcher.js +116 -0
- package/dist/policy-watcher.js.map +1 -0
- package/dist/pricing.d.ts +69 -0
- package/dist/pricing.js +93 -0
- package/dist/pricing.js.map +1 -0
- package/dist/prompt.d.ts +6 -0
- package/dist/prompt.js +47 -0
- package/dist/prompt.js.map +1 -0
- package/dist/providers/anthropic.d.ts +18 -0
- package/dist/providers/anthropic.js +61 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/custom.d.ts +19 -0
- package/dist/providers/custom.js +54 -0
- package/dist/providers/custom.js.map +1 -0
- package/dist/providers/openai.d.ts +17 -0
- package/dist/providers/openai.js +48 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/proxy.d.ts +57 -0
- package/dist/proxy.js +477 -0
- package/dist/proxy.js.map +1 -0
- package/dist/router.d.ts +23 -0
- package/dist/router.js +89 -0
- package/dist/router.js.map +1 -0
- package/dist/runtime.d.ts +1 -0
- package/dist/runtime.js +139 -0
- package/dist/runtime.js.map +1 -0
- package/dist/security.d.ts +64 -0
- package/dist/security.js +422 -0
- package/dist/security.js.map +1 -0
- package/dist/server.d.ts +33 -0
- package/dist/server.js +1147 -0
- package/dist/server.js.map +1 -0
- package/dist/sqlite-schema.d.ts +6 -0
- package/dist/sqlite-schema.js +134 -0
- package/dist/sqlite-schema.js.map +1 -0
- package/dist/streaming.d.ts +24 -0
- package/dist/streaming.js +63 -0
- package/dist/streaming.js.map +1 -0
- package/dist/tokens.d.ts +45 -0
- package/dist/tokens.js +237 -0
- package/dist/tokens.js.map +1 -0
- package/dist/types.d.ts +344 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +66 -2
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alert manager for the Govyn proxy.
|
|
3
|
+
*
|
|
4
|
+
* Subscribes to govynEvents and evaluates incoming events against configured
|
|
5
|
+
* alert rules. When a rule matches, delivers a webhook POST to the configured
|
|
6
|
+
* URL and records the alert in the alert_history table.
|
|
7
|
+
*
|
|
8
|
+
* Supports two rule types:
|
|
9
|
+
* - budget_threshold: fires on budget_warning/budget_exceeded events
|
|
10
|
+
* - policy_trigger: fires on policy_denied events
|
|
11
|
+
*/
|
|
12
|
+
import { govynEvents } from './events.js';
|
|
13
|
+
import { deliverWebhookJson, resolveWebhookTarget } from './security.js';
|
|
14
|
+
import { adaptAlertStore } from './persistence.js';
|
|
15
|
+
export class AlertManager {
|
|
16
|
+
store;
|
|
17
|
+
rules = [];
|
|
18
|
+
cooldownCache = new Map();
|
|
19
|
+
eventHandler = null;
|
|
20
|
+
constructor(storeOrSql) {
|
|
21
|
+
this.store = adaptAlertStore(storeOrSql);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Load rules from DB and subscribe to govynEvents.
|
|
25
|
+
*/
|
|
26
|
+
async start() {
|
|
27
|
+
await this.reloadRules();
|
|
28
|
+
this.eventHandler = (event) => {
|
|
29
|
+
this.handleEvent(event).catch((err) => {
|
|
30
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
31
|
+
console.error('[alert-manager] Error handling event:', msg);
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
govynEvents.on('event', this.eventHandler);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Unsubscribe from events.
|
|
38
|
+
*/
|
|
39
|
+
stop() {
|
|
40
|
+
if (this.eventHandler) {
|
|
41
|
+
govynEvents.removeListener('event', this.eventHandler);
|
|
42
|
+
this.eventHandler = null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Reload all rules from the database.
|
|
47
|
+
*/
|
|
48
|
+
async reloadRules() {
|
|
49
|
+
this.rules = await this.store.listRules();
|
|
50
|
+
// Populate cooldown cache from loaded rules
|
|
51
|
+
for (const rule of this.rules) {
|
|
52
|
+
if (rule.lastFiredAt) {
|
|
53
|
+
this.cooldownCache.set(rule.id, rule.lastFiredAt);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Handle an incoming event by evaluating all enabled rules.
|
|
59
|
+
*/
|
|
60
|
+
async handleEvent(event) {
|
|
61
|
+
for (const rule of this.rules) {
|
|
62
|
+
if (!rule.enabled)
|
|
63
|
+
continue;
|
|
64
|
+
if (this.isInCooldown(rule))
|
|
65
|
+
continue;
|
|
66
|
+
if (this.evaluateRule(rule, event)) {
|
|
67
|
+
await this.fireAlert(rule, event);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Evaluate whether a rule matches the given event.
|
|
73
|
+
*/
|
|
74
|
+
evaluateRule(rule, event) {
|
|
75
|
+
if (rule.type === 'budget_threshold') {
|
|
76
|
+
return this.evaluateBudgetThreshold(rule, event);
|
|
77
|
+
}
|
|
78
|
+
if (rule.type === 'policy_trigger') {
|
|
79
|
+
return this.evaluatePolicyTrigger(rule, event);
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check if a rule is within its cooldown window.
|
|
85
|
+
*/
|
|
86
|
+
isInCooldown(rule) {
|
|
87
|
+
const lastFired = this.cooldownCache.get(rule.id) ?? rule.lastFiredAt;
|
|
88
|
+
if (!lastFired)
|
|
89
|
+
return false;
|
|
90
|
+
const cooldownMs = rule.cooldownMinutes * 60 * 1000;
|
|
91
|
+
const cooldownEndsAt = new Date(lastFired.getTime() + cooldownMs);
|
|
92
|
+
return Date.now() < cooldownEndsAt.getTime();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Fire an alert: deliver webhook, record history, update cooldown.
|
|
96
|
+
*/
|
|
97
|
+
async fireAlert(rule, event) {
|
|
98
|
+
const firedAt = new Date().toISOString();
|
|
99
|
+
let webhookStatus = null;
|
|
100
|
+
let webhookError = null;
|
|
101
|
+
// Build webhook payload
|
|
102
|
+
const payload = {
|
|
103
|
+
alert: {
|
|
104
|
+
rule_id: rule.id,
|
|
105
|
+
rule_name: rule.name,
|
|
106
|
+
rule_type: rule.type,
|
|
107
|
+
},
|
|
108
|
+
event: this.serializeEvent(event),
|
|
109
|
+
fired_at: firedAt,
|
|
110
|
+
source: 'govyn',
|
|
111
|
+
};
|
|
112
|
+
// Send webhook
|
|
113
|
+
const webhookTarget = await resolveWebhookTarget(rule.webhookUrl);
|
|
114
|
+
if (!webhookTarget.ok) {
|
|
115
|
+
webhookError = webhookTarget.error;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
try {
|
|
119
|
+
const response = await deliverWebhookJson(webhookTarget.target, payload);
|
|
120
|
+
webhookStatus = response.status;
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
webhookError = err instanceof Error ? err.message : String(err);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Record in alert_history (even on webhook failure)
|
|
127
|
+
try {
|
|
128
|
+
await this.store.recordAlertHistory({
|
|
129
|
+
ruleId: rule.id,
|
|
130
|
+
ruleName: rule.name,
|
|
131
|
+
ruleType: rule.type,
|
|
132
|
+
eventType: event.type,
|
|
133
|
+
eventPayload: this.serializeEvent(event),
|
|
134
|
+
webhookUrl: rule.webhookUrl,
|
|
135
|
+
webhookStatus,
|
|
136
|
+
webhookError,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
141
|
+
console.error('[alert-manager] Failed to record alert history:', msg);
|
|
142
|
+
}
|
|
143
|
+
// Update last_fired_at
|
|
144
|
+
try {
|
|
145
|
+
await this.store.touchRuleLastFired(rule.id, new Date(firedAt));
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
149
|
+
console.error('[alert-manager] Failed to update last_fired_at:', msg);
|
|
150
|
+
}
|
|
151
|
+
// Update in-memory cooldown cache
|
|
152
|
+
this.cooldownCache.set(rule.id, new Date());
|
|
153
|
+
// Emit alert_fired event
|
|
154
|
+
govynEvents.emit('event', {
|
|
155
|
+
type: 'alert_fired',
|
|
156
|
+
ruleId: rule.id,
|
|
157
|
+
ruleName: rule.name,
|
|
158
|
+
ruleType: rule.type,
|
|
159
|
+
webhookUrl: rule.webhookUrl,
|
|
160
|
+
webhookStatus,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
async listRules() {
|
|
164
|
+
return this.store.listRules();
|
|
165
|
+
}
|
|
166
|
+
async createRule(input) {
|
|
167
|
+
const rule = await this.store.createRule(input);
|
|
168
|
+
await this.reloadRules();
|
|
169
|
+
return rule;
|
|
170
|
+
}
|
|
171
|
+
async updateRule(input) {
|
|
172
|
+
const rule = await this.store.updateRule(input);
|
|
173
|
+
if (rule) {
|
|
174
|
+
await this.reloadRules();
|
|
175
|
+
}
|
|
176
|
+
return rule;
|
|
177
|
+
}
|
|
178
|
+
async deleteRule(id) {
|
|
179
|
+
const deleted = await this.store.deleteRule(id);
|
|
180
|
+
if (deleted) {
|
|
181
|
+
await this.reloadRules();
|
|
182
|
+
}
|
|
183
|
+
return deleted;
|
|
184
|
+
}
|
|
185
|
+
async listHistory(limit, offset, ruleId) {
|
|
186
|
+
return this.store.listHistory(limit, offset, ruleId);
|
|
187
|
+
}
|
|
188
|
+
// ---- Private helpers ----
|
|
189
|
+
evaluateBudgetThreshold(rule, event) {
|
|
190
|
+
const config = rule.config;
|
|
191
|
+
if (event.type === 'budget_warning') {
|
|
192
|
+
// Check agent match
|
|
193
|
+
if (config.agent_id !== '*' && config.agent_id !== event.agentId)
|
|
194
|
+
return false;
|
|
195
|
+
// Check metric matches limitPeriod
|
|
196
|
+
if (config.metric !== event.limitPeriod)
|
|
197
|
+
return false;
|
|
198
|
+
// Check threshold
|
|
199
|
+
return event.percentUsed >= config.threshold_percent;
|
|
200
|
+
}
|
|
201
|
+
if (event.type === 'budget_exceeded') {
|
|
202
|
+
// Check agent match
|
|
203
|
+
if (config.agent_id !== '*' && config.agent_id !== event.agentId)
|
|
204
|
+
return false;
|
|
205
|
+
// budget_exceeded always exceeds any threshold
|
|
206
|
+
// Check metric by inspecting the code field
|
|
207
|
+
const isDaily = event.code.includes('daily');
|
|
208
|
+
if (config.metric === 'daily' && !isDaily)
|
|
209
|
+
return false;
|
|
210
|
+
if (config.metric === 'monthly' && isDaily)
|
|
211
|
+
return false;
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
evaluatePolicyTrigger(rule, event) {
|
|
217
|
+
if (event.type !== 'policy_denied')
|
|
218
|
+
return false;
|
|
219
|
+
const config = rule.config;
|
|
220
|
+
// Check agent match
|
|
221
|
+
if (config.agent_id !== '*' && config.agent_id !== event.agentId)
|
|
222
|
+
return false;
|
|
223
|
+
// Check policy name match
|
|
224
|
+
if (config.policy_name !== '*' && config.policy_name !== event.policyName)
|
|
225
|
+
return false;
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Convert a GovynEvent to a plain object for webhook payload / DB storage.
|
|
230
|
+
* Converts camelCase event fields to snake_case for external consumption.
|
|
231
|
+
*/
|
|
232
|
+
serializeEvent(event) {
|
|
233
|
+
const result = { type: event.type };
|
|
234
|
+
// Convert each event type's fields to snake_case
|
|
235
|
+
if (event.type === 'budget_warning') {
|
|
236
|
+
result.agent_id = event.agentId;
|
|
237
|
+
result.percent_used = event.percentUsed;
|
|
238
|
+
result.current_spend = event.currentSpend;
|
|
239
|
+
result.limit = event.limit;
|
|
240
|
+
result.resets_at = event.resetsAt;
|
|
241
|
+
result.limit_period = event.limitPeriod;
|
|
242
|
+
}
|
|
243
|
+
else if (event.type === 'budget_exceeded') {
|
|
244
|
+
result.agent_id = event.agentId;
|
|
245
|
+
result.code = event.code;
|
|
246
|
+
result.limit_amount = event.limitAmount;
|
|
247
|
+
result.current_spend = event.currentSpend;
|
|
248
|
+
result.reset_time = event.resetTime;
|
|
249
|
+
}
|
|
250
|
+
else if (event.type === 'policy_denied') {
|
|
251
|
+
result.agent_id = event.agentId;
|
|
252
|
+
result.provider = event.provider;
|
|
253
|
+
result.path = event.path;
|
|
254
|
+
result.policy_name = event.policyName;
|
|
255
|
+
result.policy_type = event.policyType;
|
|
256
|
+
result.reason = event.reason;
|
|
257
|
+
result.evaluation_time_ms = event.evaluationTimeMs;
|
|
258
|
+
result.allowed = event.allowed;
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
// For other event types, spread all fields
|
|
262
|
+
Object.assign(result, event);
|
|
263
|
+
}
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=alert-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alert-manager.js","sourceRoot":"","sources":["../src/alert-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approval list API handler for the Govyn proxy.
|
|
3
|
+
*
|
|
4
|
+
* Handles GET /api/approvals — returns a paginated, filtered list of
|
|
5
|
+
* approval requests from the database. Complements the existing
|
|
6
|
+
* per-request approve/deny/poll endpoints in server.ts.
|
|
7
|
+
*/
|
|
8
|
+
import type http from 'node:http';
|
|
9
|
+
import type { ApprovalManager } from './approval.js';
|
|
10
|
+
/**
|
|
11
|
+
* Handle GET /api/approvals with status filtering and pagination.
|
|
12
|
+
*
|
|
13
|
+
* Query parameters:
|
|
14
|
+
* - status: 'pending' | 'approved' | 'denied' | 'denied_timeout' | 'all' (default 'all'), supports comma-separated
|
|
15
|
+
* - limit: 1-200 (default 50)
|
|
16
|
+
* - offset: >= 0 (default 0)
|
|
17
|
+
* - agent_id: optional exact match filter
|
|
18
|
+
*/
|
|
19
|
+
export declare function handleApprovalApi(req: http.IncomingMessage, res: http.ServerResponse, approvalManager: ApprovalManager): void;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approval list API handler for the Govyn proxy.
|
|
3
|
+
*
|
|
4
|
+
* Handles GET /api/approvals — returns a paginated, filtered list of
|
|
5
|
+
* approval requests from the database. Complements the existing
|
|
6
|
+
* per-request approve/deny/poll endpoints in server.ts.
|
|
7
|
+
*/
|
|
8
|
+
const VALID_STATUSES = new Set(['pending', 'approved', 'denied', 'denied_timeout']);
|
|
9
|
+
const DEFAULT_LIMIT = 50;
|
|
10
|
+
const MAX_LIMIT = 200;
|
|
11
|
+
/**
|
|
12
|
+
* Handle GET /api/approvals with status filtering and pagination.
|
|
13
|
+
*
|
|
14
|
+
* Query parameters:
|
|
15
|
+
* - status: 'pending' | 'approved' | 'denied' | 'denied_timeout' | 'all' (default 'all'), supports comma-separated
|
|
16
|
+
* - limit: 1-200 (default 50)
|
|
17
|
+
* - offset: >= 0 (default 0)
|
|
18
|
+
* - agent_id: optional exact match filter
|
|
19
|
+
*/
|
|
20
|
+
export function handleApprovalApi(req, res, approvalManager) {
|
|
21
|
+
const parsed = new URL(req.url, 'http://localhost');
|
|
22
|
+
const params = parsed.searchParams;
|
|
23
|
+
// Parse and validate status filter
|
|
24
|
+
const statusParam = params.get('status') ?? 'all';
|
|
25
|
+
let statusFilters = [];
|
|
26
|
+
if (statusParam !== 'all') {
|
|
27
|
+
const parts = statusParam.split(',').map((s) => s.trim()).filter(Boolean);
|
|
28
|
+
for (const part of parts) {
|
|
29
|
+
if (!VALID_STATUSES.has(part)) {
|
|
30
|
+
sendError(res, 400, `Invalid status value: "${part}". Valid values: pending, approved, denied, denied_timeout, all`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
statusFilters = parts;
|
|
35
|
+
}
|
|
36
|
+
// Parse and validate limit
|
|
37
|
+
const limitStr = params.get('limit');
|
|
38
|
+
let limit = DEFAULT_LIMIT;
|
|
39
|
+
if (limitStr !== null) {
|
|
40
|
+
limit = parseInt(limitStr, 10);
|
|
41
|
+
if (isNaN(limit) || limit < 1 || limit > MAX_LIMIT) {
|
|
42
|
+
sendError(res, 400, `Invalid limit: must be between 1 and ${MAX_LIMIT}`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Parse and validate offset
|
|
47
|
+
const offsetStr = params.get('offset');
|
|
48
|
+
let offset = 0;
|
|
49
|
+
if (offsetStr !== null) {
|
|
50
|
+
offset = parseInt(offsetStr, 10);
|
|
51
|
+
if (isNaN(offset) || offset < 0) {
|
|
52
|
+
sendError(res, 400, 'Invalid offset: must be >= 0');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Optional agent_id filter
|
|
57
|
+
const agentId = params.get('agent_id');
|
|
58
|
+
// Build and execute query
|
|
59
|
+
approvalManager.listApprovals(statusFilters, limit, offset, agentId)
|
|
60
|
+
.then((result) => {
|
|
61
|
+
const body = JSON.stringify(result);
|
|
62
|
+
res.writeHead(200, {
|
|
63
|
+
'content-type': 'application/json',
|
|
64
|
+
'content-length': Buffer.byteLength(body).toString(),
|
|
65
|
+
});
|
|
66
|
+
res.end(body);
|
|
67
|
+
})
|
|
68
|
+
.catch((err) => {
|
|
69
|
+
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
70
|
+
console.error('[approval-api] Database error:', message);
|
|
71
|
+
sendError(res, 500, 'Failed to fetch approvals');
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function sendError(res, statusCode, message) {
|
|
75
|
+
const body = JSON.stringify({ error: { message, code: statusCode === 400 ? 'invalid_request' : 'internal_error' } });
|
|
76
|
+
res.writeHead(statusCode, {
|
|
77
|
+
'content-type': 'application/json',
|
|
78
|
+
'content-length': Buffer.byteLength(body).toString(),
|
|
79
|
+
});
|
|
80
|
+
res.end(body);
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=approval-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-api.js","sourceRoot":"","sources":["../src/approval-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background job that auto-denies expired approval requests.
|
|
3
|
+
*
|
|
4
|
+
* Runs on a configurable interval (default 30 seconds) to mark pending
|
|
5
|
+
* approval requests that have passed their expires_at timestamp as
|
|
6
|
+
* 'denied_timeout'. This is distinct from 'denied' — agents can
|
|
7
|
+
* programmatically differentiate between human denial and timeout expiry.
|
|
8
|
+
*/
|
|
9
|
+
import type postgres from 'postgres';
|
|
10
|
+
import type { ApprovalStore } from './persistence-types.js';
|
|
11
|
+
export declare class ApprovalTimeoutChecker {
|
|
12
|
+
private interval;
|
|
13
|
+
private readonly store;
|
|
14
|
+
constructor(storeOrSql: ApprovalStore | postgres.Sql);
|
|
15
|
+
/**
|
|
16
|
+
* Start checking for expired approvals every 30 seconds.
|
|
17
|
+
* The interval is unref'd so it doesn't keep the process alive.
|
|
18
|
+
*/
|
|
19
|
+
start(): void;
|
|
20
|
+
/**
|
|
21
|
+
* Mark all expired pending approvals as denied_timeout.
|
|
22
|
+
* Returns the number of records updated.
|
|
23
|
+
*/
|
|
24
|
+
expireTimedOut(): Promise<number>;
|
|
25
|
+
/**
|
|
26
|
+
* Stop the interval checker.
|
|
27
|
+
*/
|
|
28
|
+
stop(): void;
|
|
29
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background job that auto-denies expired approval requests.
|
|
3
|
+
*
|
|
4
|
+
* Runs on a configurable interval (default 30 seconds) to mark pending
|
|
5
|
+
* approval requests that have passed their expires_at timestamp as
|
|
6
|
+
* 'denied_timeout'. This is distinct from 'denied' — agents can
|
|
7
|
+
* programmatically differentiate between human denial and timeout expiry.
|
|
8
|
+
*/
|
|
9
|
+
import { adaptApprovalStore } from './persistence.js';
|
|
10
|
+
export class ApprovalTimeoutChecker {
|
|
11
|
+
interval = null;
|
|
12
|
+
store;
|
|
13
|
+
constructor(storeOrSql) {
|
|
14
|
+
this.store = adaptApprovalStore(storeOrSql);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Start checking for expired approvals every 30 seconds.
|
|
18
|
+
* The interval is unref'd so it doesn't keep the process alive.
|
|
19
|
+
*/
|
|
20
|
+
start() {
|
|
21
|
+
this.interval = setInterval(() => this.expireTimedOut(), 30_000);
|
|
22
|
+
this.interval.unref();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Mark all expired pending approvals as denied_timeout.
|
|
26
|
+
* Returns the number of records updated.
|
|
27
|
+
*/
|
|
28
|
+
async expireTimedOut() {
|
|
29
|
+
const count = await this.store.expireTimedOutApprovals();
|
|
30
|
+
if (count > 0) {
|
|
31
|
+
console.log(`[govyn] Auto-denied ${count} expired approval request(s)`);
|
|
32
|
+
}
|
|
33
|
+
return count;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Stop the interval checker.
|
|
37
|
+
*/
|
|
38
|
+
stop() {
|
|
39
|
+
if (this.interval) {
|
|
40
|
+
clearInterval(this.interval);
|
|
41
|
+
this.interval = null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=approval-timeout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-timeout.js","sourceRoot":"","sources":["../src/approval-timeout.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,MAAM,OAAO,sBAAsB;IACzB,QAAQ,GAA0C,IAAI,CAAC;IAC9C,KAAK,CAAgB;IAEtC,YAAY,UAAwC;QAClD,IAAI,CAAC,KAAK,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAC9C,CAAC;IAED;;;OAGG;IACH,KAAK;QACH,IAAI,CAAC,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,MAAM,CAAC,CAAC;QACjE,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,cAAc;QAClB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,uBAAuB,EAAE,CAAC;QACzD,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,OAAO,CAAC,GAAG,CAAC,uBAAuB,KAAK,8BAA8B,CAAC,CAAC;QAC1E,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;OAEG;IACH,IAAI;QACF,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC7B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACvB,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approval queue manager for the Govyn proxy.
|
|
3
|
+
*
|
|
4
|
+
* When a require_approval policy flags a request, the proxy creates an
|
|
5
|
+
* approval request in the database. Agents poll for the decision status.
|
|
6
|
+
* Once approved, the agent re-sends with a single-use approval token.
|
|
7
|
+
*
|
|
8
|
+
* Phase 14 (Approval Queue UI) will build a dashboard on top of these APIs.
|
|
9
|
+
* Until then, approvals can be resolved via direct DB updates or API calls.
|
|
10
|
+
*/
|
|
11
|
+
import type postgres from 'postgres';
|
|
12
|
+
import type { ApprovalStore } from './persistence-types.js';
|
|
13
|
+
export declare function hashApprovalRequest(body: string | Buffer | undefined): string;
|
|
14
|
+
/**
|
|
15
|
+
* Generate a truncated summary of a request body for approval metadata.
|
|
16
|
+
* Extracts the first user message content or body content, truncated to maxLength chars.
|
|
17
|
+
*/
|
|
18
|
+
export declare function generateRequestSummary(body: string | undefined, maxLength?: number): string;
|
|
19
|
+
export declare class ApprovalManager {
|
|
20
|
+
private readonly store;
|
|
21
|
+
constructor(storeOrSql: ApprovalStore | postgres.Sql);
|
|
22
|
+
/**
|
|
23
|
+
* Create a new approval request in the database.
|
|
24
|
+
* Returns the approval request ID, polling URL, and expiration time.
|
|
25
|
+
*/
|
|
26
|
+
createApprovalRequest(params: {
|
|
27
|
+
agentId: string;
|
|
28
|
+
provider: string;
|
|
29
|
+
model?: string;
|
|
30
|
+
targetPath: string;
|
|
31
|
+
policyName: string;
|
|
32
|
+
policyRule?: string;
|
|
33
|
+
estimatedCost?: number;
|
|
34
|
+
requestSummary: string;
|
|
35
|
+
requestHash: string;
|
|
36
|
+
requestPayload?: unknown;
|
|
37
|
+
timeoutSeconds: number;
|
|
38
|
+
}): Promise<{
|
|
39
|
+
id: string;
|
|
40
|
+
pollingUrl: string;
|
|
41
|
+
expiresAt: string;
|
|
42
|
+
}>;
|
|
43
|
+
/**
|
|
44
|
+
* Get approval status for polling.
|
|
45
|
+
* Returns status and approval token (if approved), or null if not found.
|
|
46
|
+
*/
|
|
47
|
+
getApprovalStatus(id: string): Promise<{
|
|
48
|
+
id: string;
|
|
49
|
+
status: 'pending' | 'approved' | 'denied' | 'denied_timeout';
|
|
50
|
+
approvalToken?: string;
|
|
51
|
+
decidedAt?: string;
|
|
52
|
+
expiresAt: string;
|
|
53
|
+
} | null>;
|
|
54
|
+
/**
|
|
55
|
+
* Validate an approval token for re-send flow.
|
|
56
|
+
* Atomically finds the token and marks it as used in one query.
|
|
57
|
+
* Returns the original request context if valid, null if invalid/expired/used.
|
|
58
|
+
*/
|
|
59
|
+
validateAndConsumeToken(token: string, expected: {
|
|
60
|
+
agentId: string;
|
|
61
|
+
targetPath: string;
|
|
62
|
+
requestHash: string;
|
|
63
|
+
}): Promise<{
|
|
64
|
+
policyName: string;
|
|
65
|
+
} | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Approve a request. Generates a single-use UUID approval token.
|
|
68
|
+
* Returns true if the request was pending and is now approved.
|
|
69
|
+
*/
|
|
70
|
+
approveRequest(id: string, decidedBy: string, notes?: string): Promise<boolean>;
|
|
71
|
+
/**
|
|
72
|
+
* Deny a request. No token is generated.
|
|
73
|
+
* Returns true if the request was pending and is now denied.
|
|
74
|
+
*/
|
|
75
|
+
denyRequest(id: string, decidedBy: string, notes?: string): Promise<boolean>;
|
|
76
|
+
listApprovals(statusFilters: string[], limit: number, offset: number, agentId: string | null): Promise<import("./persistence-types.js").ApprovalListResult>;
|
|
77
|
+
expireTimedOutApprovals(now?: Date): Promise<number>;
|
|
78
|
+
}
|
package/dist/approval.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approval queue manager for the Govyn proxy.
|
|
3
|
+
*
|
|
4
|
+
* When a require_approval policy flags a request, the proxy creates an
|
|
5
|
+
* approval request in the database. Agents poll for the decision status.
|
|
6
|
+
* Once approved, the agent re-sends with a single-use approval token.
|
|
7
|
+
*
|
|
8
|
+
* Phase 14 (Approval Queue UI) will build a dashboard on top of these APIs.
|
|
9
|
+
* Until then, approvals can be resolved via direct DB updates or API calls.
|
|
10
|
+
*/
|
|
11
|
+
import * as crypto from 'node:crypto';
|
|
12
|
+
import { adaptApprovalStore } from './persistence.js';
|
|
13
|
+
export function hashApprovalRequest(body) {
|
|
14
|
+
const data = Buffer.isBuffer(body)
|
|
15
|
+
? body
|
|
16
|
+
: Buffer.from(body ?? '', 'utf8');
|
|
17
|
+
return crypto.createHash('sha256').update(data).digest('hex');
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Generate a truncated summary of a request body for approval metadata.
|
|
21
|
+
* Extracts the first user message content or body content, truncated to maxLength chars.
|
|
22
|
+
*/
|
|
23
|
+
export function generateRequestSummary(body, maxLength = 500) {
|
|
24
|
+
if (!body)
|
|
25
|
+
return '(empty request)';
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(body);
|
|
28
|
+
// Try to extract user message content (OpenAI/Anthropic format)
|
|
29
|
+
if (Array.isArray(parsed.messages)) {
|
|
30
|
+
for (let i = parsed.messages.length - 1; i >= 0; i--) {
|
|
31
|
+
const msg = parsed.messages[i];
|
|
32
|
+
if (msg?.role === 'user' && typeof msg.content === 'string') {
|
|
33
|
+
const content = msg.content;
|
|
34
|
+
if (content.length <= maxLength)
|
|
35
|
+
return content;
|
|
36
|
+
return content.slice(0, maxLength) + '...';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Fall back to stringified body
|
|
41
|
+
const str = JSON.stringify(parsed);
|
|
42
|
+
if (str.length <= maxLength)
|
|
43
|
+
return str;
|
|
44
|
+
return str.slice(0, maxLength) + '...';
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Non-JSON body — truncate raw text
|
|
48
|
+
if (body.length <= maxLength)
|
|
49
|
+
return body;
|
|
50
|
+
return body.slice(0, maxLength) + '...';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export class ApprovalManager {
|
|
54
|
+
store;
|
|
55
|
+
constructor(storeOrSql) {
|
|
56
|
+
this.store = adaptApprovalStore(storeOrSql);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Create a new approval request in the database.
|
|
60
|
+
* Returns the approval request ID, polling URL, and expiration time.
|
|
61
|
+
*/
|
|
62
|
+
async createApprovalRequest(params) {
|
|
63
|
+
return this.store.createApprovalRequest(params);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get approval status for polling.
|
|
67
|
+
* Returns status and approval token (if approved), or null if not found.
|
|
68
|
+
*/
|
|
69
|
+
async getApprovalStatus(id) {
|
|
70
|
+
return this.store.getApprovalStatus(id);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Validate an approval token for re-send flow.
|
|
74
|
+
* Atomically finds the token and marks it as used in one query.
|
|
75
|
+
* Returns the original request context if valid, null if invalid/expired/used.
|
|
76
|
+
*/
|
|
77
|
+
async validateAndConsumeToken(token, expected) {
|
|
78
|
+
return this.store.validateAndConsumeToken(token, expected);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Approve a request. Generates a single-use UUID approval token.
|
|
82
|
+
* Returns true if the request was pending and is now approved.
|
|
83
|
+
*/
|
|
84
|
+
async approveRequest(id, decidedBy, notes) {
|
|
85
|
+
return this.store.approveRequest(id, decidedBy, notes);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Deny a request. No token is generated.
|
|
89
|
+
* Returns true if the request was pending and is now denied.
|
|
90
|
+
*/
|
|
91
|
+
async denyRequest(id, decidedBy, notes) {
|
|
92
|
+
return this.store.denyRequest(id, decidedBy, notes);
|
|
93
|
+
}
|
|
94
|
+
async listApprovals(statusFilters, limit, offset, agentId) {
|
|
95
|
+
return this.store.listApprovals(statusFilters, limit, offset, agentId);
|
|
96
|
+
}
|
|
97
|
+
async expireTimedOutApprovals(now) {
|
|
98
|
+
return this.store.expireTimedOutApprovals(now);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=approval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval.js","sourceRoot":"","sources":["../src/approval.ts"],"names":[],"mappings":"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"}
|