web-agent-bridge 3.2.0 → 3.4.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/LICENSE +84 -72
- package/README.ar.md +1304 -1152
- package/README.md +298 -1635
- package/bin/agent-runner.js +474 -474
- package/bin/cli.js +237 -138
- package/bin/wab-init.js +223 -0
- package/bin/wab.js +80 -80
- package/examples/azure-dns-wab.js +83 -0
- package/examples/bidi-agent.js +119 -119
- package/examples/cloudflare-wab-dns.js +121 -0
- package/examples/cpanel-wab-dns.js +114 -0
- package/examples/cross-site-agent.js +91 -91
- package/examples/dns-discovery-agent.js +166 -0
- package/examples/gcp-dns-wab.js +76 -0
- package/examples/governance-agent.js +169 -0
- package/examples/mcp-agent.js +94 -94
- package/examples/next-app-router/README.md +44 -44
- package/examples/plesk-wab-dns.js +103 -0
- package/examples/puppeteer-agent.js +108 -108
- package/examples/route53-wab-dns.js +144 -0
- package/examples/saas-dashboard/README.md +55 -55
- package/examples/safe-mode-agent.js +96 -0
- package/examples/shopify-hydrogen/README.md +74 -74
- package/examples/vision-agent.js +171 -171
- package/examples/wab-sign.js +74 -0
- package/examples/wab-verify.js +60 -0
- package/examples/wordpress-elementor/README.md +77 -77
- package/package.json +19 -6
- package/public/.well-known/agent-tools.json +180 -180
- package/public/.well-known/ai-assets.json +59 -59
- package/public/.well-known/security.txt +8 -0
- package/public/.well-known/wab.json +28 -0
- package/public/activate.html +368 -0
- package/public/adoption-metrics.html +188 -0
- package/public/agent-workspace.html +349 -349
- package/public/ai.html +198 -198
- package/public/api.html +413 -412
- package/public/azure-dns-integration.html +289 -0
- package/public/browser.html +486 -486
- package/public/cloudflare-integration.html +380 -0
- package/public/commander-dashboard.html +243 -243
- package/public/cookies.html +210 -210
- package/public/cpanel-integration.html +398 -0
- package/public/css/agent-workspace.css +1713 -1713
- package/public/css/premium.css +317 -317
- package/public/css/styles.css +1263 -1235
- package/public/dashboard.html +707 -706
- package/public/dns.html +436 -0
- package/public/docs.html +588 -587
- package/public/feed.xml +89 -89
- package/public/gcp-dns-integration.html +318 -0
- package/public/growth.html +465 -463
- package/public/index.html +1266 -982
- package/public/integrations.html +556 -0
- package/public/js/activate.js +145 -0
- package/public/js/agent-workspace.js +1740 -1740
- package/public/js/auth-nav.js +65 -31
- package/public/js/auth-redirect.js +12 -12
- package/public/js/cookie-consent.js +56 -56
- package/public/js/dns.js +438 -0
- package/public/js/wab-demo-page.js +721 -721
- package/public/js/ws-client.js +74 -74
- package/public/llms-full.txt +360 -360
- package/public/llms.txt +125 -125
- package/public/login.html +85 -85
- package/public/mesh-dashboard.html +328 -328
- package/public/openapi.json +669 -580
- package/public/phone-shield.html +281 -0
- package/public/plesk-integration.html +375 -0
- package/public/premium-dashboard.html +2489 -2489
- package/public/premium.html +793 -793
- package/public/privacy.html +297 -297
- package/public/provider-onboarding.html +172 -0
- package/public/provider-sandbox.html +134 -0
- package/public/providers.html +359 -0
- package/public/register.html +105 -105
- package/public/registrar-integrations.html +141 -0
- package/public/robots.txt +99 -87
- package/public/route53-integration.html +531 -0
- package/public/script/wab-consent.d.ts +36 -36
- package/public/script/wab-consent.js +104 -104
- package/public/script/wab-schema.js +131 -131
- package/public/script/wab.d.ts +108 -108
- package/public/script/wab.min.js +580 -580
- package/public/security.txt +8 -0
- package/public/shieldqr.html +231 -0
- package/public/sitemap.xml +6 -0
- package/public/terms.html +256 -256
- package/public/wab-trust.html +200 -0
- package/public/wab-vs-protocols.html +210 -0
- package/public/whitepaper.html +449 -0
- package/script/ai-agent-bridge.js +1754 -1754
- package/sdk/README.md +99 -99
- package/sdk/agent-mesh.js +449 -449
- package/sdk/auto-discovery.js +288 -0
- package/sdk/commander.js +262 -262
- package/sdk/governance.js +262 -0
- package/sdk/index.d.ts +464 -464
- package/sdk/index.js +25 -1
- package/sdk/multi-agent.js +318 -318
- package/sdk/package.json +2 -2
- package/sdk/safe-mode.js +221 -0
- package/sdk/safety-shield.js +219 -0
- package/sdk/schema-discovery.js +83 -83
- package/server/adapters/index.js +520 -520
- package/server/config/plans.js +367 -367
- package/server/config/secrets.js +102 -102
- package/server/control-plane/index.js +301 -301
- package/server/data-plane/index.js +354 -354
- package/server/index.js +670 -427
- package/server/llm/index.js +404 -404
- package/server/middleware/adminAuth.js +35 -35
- package/server/middleware/auth.js +50 -50
- package/server/middleware/featureGate.js +88 -88
- package/server/middleware/rateLimits.js +100 -100
- package/server/middleware/sensitiveAction.js +157 -0
- package/server/migrations/001_add_analytics_indexes.sql +7 -7
- package/server/migrations/002_premium_features.sql +418 -418
- package/server/migrations/003_ads_integer_cents.sql +33 -33
- package/server/migrations/004_agent_os.sql +158 -158
- package/server/migrations/005_marketplace_metering.sql +126 -126
- package/server/migrations/007_governance.sql +106 -0
- package/server/migrations/008_plans.sql +144 -0
- package/server/migrations/009_shieldqr.sql +30 -0
- package/server/migrations/010_extended_trust.sql +33 -0
- package/server/models/adapters/index.js +33 -33
- package/server/models/adapters/mysql.js +183 -183
- package/server/models/adapters/postgresql.js +172 -172
- package/server/models/adapters/sqlite.js +7 -7
- package/server/models/db.js +740 -681
- package/server/observability/failure-analysis.js +337 -337
- package/server/observability/index.js +394 -394
- package/server/protocol/capabilities.js +223 -223
- package/server/protocol/index.js +243 -243
- package/server/protocol/schema.js +584 -584
- package/server/registry/certification.js +271 -271
- package/server/registry/index.js +326 -326
- package/server/routes/admin-plans.js +76 -0
- package/server/routes/admin-premium.js +673 -671
- package/server/routes/admin-shieldqr.js +90 -0
- package/server/routes/admin-trust-monitor.js +83 -0
- package/server/routes/admin.js +549 -261
- package/server/routes/ads.js +130 -130
- package/server/routes/agent-workspace.js +540 -540
- package/server/routes/api.js +150 -150
- package/server/routes/auth.js +71 -71
- package/server/routes/billing.js +57 -45
- package/server/routes/commander.js +316 -316
- package/server/routes/demo-showcase.js +332 -332
- package/server/routes/demo-store.js +154 -0
- package/server/routes/discovery.js +2348 -417
- package/server/routes/gateway.js +173 -157
- package/server/routes/governance.js +208 -0
- package/server/routes/license.js +251 -240
- package/server/routes/mesh.js +469 -469
- package/server/routes/noscript.js +543 -543
- package/server/routes/plans.js +33 -0
- package/server/routes/premium-v2.js +686 -686
- package/server/routes/premium.js +724 -724
- package/server/routes/providers.js +650 -0
- package/server/routes/runtime.js +2148 -2147
- package/server/routes/shieldqr.js +88 -0
- package/server/routes/sovereign.js +465 -385
- package/server/routes/universal.js +200 -185
- package/server/routes/wab-api.js +850 -501
- package/server/runtime/container-worker.js +111 -111
- package/server/runtime/container.js +448 -448
- package/server/runtime/distributed-worker.js +362 -362
- package/server/runtime/event-bus.js +210 -210
- package/server/runtime/index.js +253 -253
- package/server/runtime/queue.js +599 -599
- package/server/runtime/replay.js +666 -666
- package/server/runtime/sandbox.js +266 -266
- package/server/runtime/scheduler.js +534 -534
- package/server/runtime/session-engine.js +293 -293
- package/server/runtime/state-manager.js +188 -188
- package/server/security/cross-site-redactor.js +196 -0
- package/server/security/dry-run.js +180 -0
- package/server/security/human-gate-rate-limit.js +147 -0
- package/server/security/human-gate-transports.js +178 -0
- package/server/security/human-gate.js +281 -0
- package/server/security/index.js +368 -368
- package/server/security/intent-engine.js +245 -0
- package/server/security/reward-guard.js +171 -0
- package/server/security/rollback-store.js +239 -0
- package/server/security/token-scope.js +404 -0
- package/server/security/url-policy.js +139 -0
- package/server/services/agent-chat.js +506 -506
- package/server/services/agent-learning.js +601 -575
- package/server/services/agent-memory.js +625 -625
- package/server/services/agent-mesh.js +555 -539
- package/server/services/agent-symphony.js +717 -717
- package/server/services/agent-tasks.js +1807 -1807
- package/server/services/api-key-engine.js +292 -261
- package/server/services/cluster.js +894 -894
- package/server/services/commander.js +738 -738
- package/server/services/edge-compute.js +440 -440
- package/server/services/email.js +233 -204
- package/server/services/governance.js +466 -0
- package/server/services/hosted-runtime.js +205 -205
- package/server/services/lfd.js +635 -635
- package/server/services/local-ai.js +389 -389
- package/server/services/marketplace.js +270 -270
- package/server/services/metering.js +182 -182
- package/server/services/modules/affiliate-intelligence.js +93 -93
- package/server/services/modules/agent-firewall.js +90 -90
- package/server/services/modules/bounty.js +89 -89
- package/server/services/modules/collective-bargaining.js +92 -92
- package/server/services/modules/dark-pattern.js +66 -66
- package/server/services/modules/gov-intelligence.js +45 -45
- package/server/services/modules/neural.js +55 -55
- package/server/services/modules/notary.js +49 -49
- package/server/services/modules/price-time-machine.js +86 -86
- package/server/services/modules/protocol.js +104 -104
- package/server/services/negotiation.js +439 -439
- package/server/services/plans.js +214 -0
- package/server/services/plugins.js +771 -771
- package/server/services/premium.js +1 -1
- package/server/services/price-intelligence.js +566 -566
- package/server/services/price-shield.js +1137 -1137
- package/server/services/provider-clients.js +740 -0
- package/server/services/reputation.js +465 -465
- package/server/services/search-engine.js +357 -357
- package/server/services/security.js +513 -513
- package/server/services/self-healing.js +843 -843
- package/server/services/shieldqr.js +322 -0
- package/server/services/sovereign-shield.js +542 -0
- package/server/services/ssl-inspector.js +42 -0
- package/server/services/ssl-monitor.js +167 -0
- package/server/services/stripe.js +205 -192
- package/server/services/swarm.js +788 -788
- package/server/services/universal-scraper.js +662 -661
- package/server/services/verification.js +481 -481
- package/server/services/vision.js +1163 -1163
- package/server/services/wab-crypto.js +178 -0
- package/server/utils/cache.js +125 -125
- package/server/utils/migrate.js +81 -81
- package/server/utils/safe-fetch.js +228 -0
- package/server/utils/secureFields.js +50 -50
- package/server/ws.js +161 -161
- package/templates/artisan-marketplace.yaml +104 -104
- package/templates/book-price-scout.yaml +98 -98
- package/templates/electronics-price-tracker.yaml +108 -108
- package/templates/flight-deal-hunter.yaml +113 -113
- package/templates/freelancer-direct.yaml +116 -116
- package/templates/grocery-price-compare.yaml +93 -93
- package/templates/hotel-direct-booking.yaml +113 -113
- package/templates/local-services.yaml +98 -98
- package/templates/olive-oil-tunisia.yaml +88 -88
- package/templates/organic-farm-fresh.yaml +101 -101
- package/templates/restaurant-direct.yaml +97 -97
- package/public/score.html +0 -263
- package/server/migrations/006_growth_suite.sql +0 -138
- package/server/routes/growth.js +0 -962
- package/server/services/fairness-engine.js +0 -409
- package/server/services/fairness.js +0 -420
package/server/routes/admin.js
CHANGED
|
@@ -1,261 +1,549 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Admin API Routes
|
|
3
|
-
* Full admin panel backend: users, sites, analytics, Stripe, SMTP, grants
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const express = require('express');
|
|
7
|
-
const router = express.Router();
|
|
8
|
-
const { authenticateAdmin, generateAdminToken } = require('../middleware/adminAuth');
|
|
9
|
-
const { adminLoginLimiter } = require('../middleware/rateLimits');
|
|
10
|
-
const { auditLog, revokeJWT } = require('../services/security');
|
|
11
|
-
const {
|
|
12
|
-
loginAdmin, findAdminById, createAdmin,
|
|
13
|
-
getAllUsers, getAllSites, getAdminStats, getPlatformAnalytics,
|
|
14
|
-
getUserFullDetails, adminUpdateUserTier, adminUpdateSite, adminDeleteUser,
|
|
15
|
-
grantFreeTier, revokeGrant, getActiveGrants,
|
|
16
|
-
getSmtpSettings, updateSmtpSettings, getNotificationLogs,
|
|
17
|
-
getPayments, getPlatformSetting, setPlatformSetting,
|
|
18
|
-
findUserByEmail,
|
|
19
|
-
findSiteById,
|
|
20
|
-
getAnalyticsBySite,
|
|
21
|
-
getAnalyticsTimeline
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
res.json({
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
stats
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
res.json({
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
res.json({
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (!
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
res.json({
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
const
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (
|
|
189
|
-
if (
|
|
190
|
-
if (
|
|
191
|
-
if (
|
|
192
|
-
if (
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Admin API Routes
|
|
3
|
+
* Full admin panel backend: users, sites, analytics, Stripe, SMTP, grants
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const express = require('express');
|
|
7
|
+
const router = express.Router();
|
|
8
|
+
const { authenticateAdmin, generateAdminToken } = require('../middleware/adminAuth');
|
|
9
|
+
const { adminLoginLimiter } = require('../middleware/rateLimits');
|
|
10
|
+
const { auditLog, revokeJWT } = require('../services/security');
|
|
11
|
+
const {
|
|
12
|
+
loginAdmin, findAdminById, createAdmin,
|
|
13
|
+
getAllUsers, getAllSites, getAdminStats, getPlatformAnalytics,
|
|
14
|
+
getUserFullDetails, adminUpdateUserTier, adminUpdateSite, adminDeleteUser,
|
|
15
|
+
grantFreeTier, revokeGrant, getActiveGrants,
|
|
16
|
+
getSmtpSettings, updateSmtpSettings, getNotificationLogs,
|
|
17
|
+
getPayments, getPlatformSetting, setPlatformSetting,
|
|
18
|
+
findUserByEmail,
|
|
19
|
+
findSiteById,
|
|
20
|
+
getAnalyticsBySite,
|
|
21
|
+
getAnalyticsTimeline,
|
|
22
|
+
db
|
|
23
|
+
} = require('../models/db');
|
|
24
|
+
const { sendEmail } = require('../services/email');
|
|
25
|
+
const { createCheckoutSession, createPortalSession, isStripeConfigured, getStripePrices } = require('../services/stripe');
|
|
26
|
+
|
|
27
|
+
// ─── Auth ──────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
router.post('/login', adminLoginLimiter, (req, res) => {
|
|
30
|
+
const { email, password } = req.body;
|
|
31
|
+
if (!email || !password) return res.status(400).json({ error: 'Email and password required' });
|
|
32
|
+
|
|
33
|
+
const admin = loginAdmin({ email, password });
|
|
34
|
+
if (!admin) {
|
|
35
|
+
auditLog({ actorType: 'admin', action: 'admin_login_failed', details: { email }, ip: req.ip, outcome: 'denied', severity: 'warning' });
|
|
36
|
+
return res.status(401).json({ error: 'Invalid credentials' });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const token = generateAdminToken(admin);
|
|
40
|
+
auditLog({ actorType: 'admin', actorId: String(admin.id), action: 'admin_login', ip: req.ip });
|
|
41
|
+
res.json({ admin, token });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
router.post('/logout', authenticateAdmin, (req, res) => {
|
|
45
|
+
if (req._rawToken) {
|
|
46
|
+
revokeJWT(req._rawToken, 'admin_logout');
|
|
47
|
+
auditLog({ actorType: 'admin', actorId: String(req.admin.id), action: 'admin_logout', ip: req.ip });
|
|
48
|
+
}
|
|
49
|
+
res.json({ success: true });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
router.get('/me', authenticateAdmin, (req, res) => {
|
|
53
|
+
const admin = findAdminById(req.admin.id);
|
|
54
|
+
if (!admin) return res.status(404).json({ error: 'Admin not found' });
|
|
55
|
+
res.json({ admin });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ─── Dashboard Stats ──────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
router.get('/stats', authenticateAdmin, (req, res) => {
|
|
61
|
+
const stats = getAdminStats();
|
|
62
|
+
stats.stripeConfigured = isStripeConfigured();
|
|
63
|
+
res.json(stats);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
router.get('/analytics', authenticateAdmin, (req, res) => {
|
|
67
|
+
const days = parseInt(req.query.days) || 30;
|
|
68
|
+
const data = getPlatformAnalytics(days);
|
|
69
|
+
res.json(data);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// ─── Users Management ─────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
router.get('/users', authenticateAdmin, (req, res) => {
|
|
75
|
+
const users = getAllUsers();
|
|
76
|
+
res.json({ users });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
router.get('/users/:id', authenticateAdmin, (req, res) => {
|
|
80
|
+
const user = getUserFullDetails(req.params.id);
|
|
81
|
+
if (!user) return res.status(404).json({ error: 'User not found' });
|
|
82
|
+
res.json({ user });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
router.put('/users/:id/tier', authenticateAdmin, (req, res) => {
|
|
86
|
+
const { tier, siteId } = req.body;
|
|
87
|
+
if (!['free', 'starter', 'pro', 'enterprise'].includes(tier)) {
|
|
88
|
+
return res.status(400).json({ error: 'Invalid tier' });
|
|
89
|
+
}
|
|
90
|
+
adminUpdateUserTier(req.params.id, siteId, tier);
|
|
91
|
+
res.json({ success: true });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
router.delete('/users/:id', authenticateAdmin, (req, res) => {
|
|
95
|
+
adminDeleteUser(req.params.id);
|
|
96
|
+
res.json({ success: true });
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// ─── Sites Management ─────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
router.get('/sites', authenticateAdmin, (req, res) => {
|
|
102
|
+
const sites = getAllSites();
|
|
103
|
+
res.json({ sites });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
router.put('/sites/:id', authenticateAdmin, (req, res) => {
|
|
107
|
+
const { tier, active } = req.body;
|
|
108
|
+
const ok = adminUpdateSite(req.params.id, { tier, active });
|
|
109
|
+
if (!ok) return res.status(404).json({ error: 'Site not found or invalid tier' });
|
|
110
|
+
res.json({ success: true });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
router.get('/sites/:id/analytics', authenticateAdmin, (req, res) => {
|
|
114
|
+
const site = findSiteById.get(req.params.id);
|
|
115
|
+
if (!site) return res.status(404).json({ error: 'Site not found' });
|
|
116
|
+
const days = parseInt(req.query.days, 10) || 30;
|
|
117
|
+
const since = new Date(Date.now() - days * 86400000).toISOString();
|
|
118
|
+
const summary = getAnalyticsBySite.all(site.id, since);
|
|
119
|
+
const timeline = getAnalyticsTimeline.all(site.id, since);
|
|
120
|
+
res.json({
|
|
121
|
+
site: {
|
|
122
|
+
id: site.id,
|
|
123
|
+
name: site.name,
|
|
124
|
+
domain: site.domain,
|
|
125
|
+
tier: site.tier,
|
|
126
|
+
license_key: site.license_key
|
|
127
|
+
},
|
|
128
|
+
summary,
|
|
129
|
+
timeline,
|
|
130
|
+
period: `${days} days`
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// ─── Free Grants ──────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
router.get('/grants', authenticateAdmin, (req, res) => {
|
|
137
|
+
const grants = getActiveGrants();
|
|
138
|
+
res.json({ grants });
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
router.post('/grants', authenticateAdmin, (req, res) => {
|
|
142
|
+
const { userId, siteId, tier, reason, expiresAt } = req.body;
|
|
143
|
+
if (!userId || !tier) return res.status(400).json({ error: 'userId and tier required' });
|
|
144
|
+
if (!['starter', 'pro', 'enterprise'].includes(tier)) return res.status(400).json({ error: 'Invalid tier' });
|
|
145
|
+
|
|
146
|
+
const grant = grantFreeTier({ userId, siteId, tier, reason, grantedBy: req.admin.id, expiresAt });
|
|
147
|
+
|
|
148
|
+
// Send notification email
|
|
149
|
+
const user = getUserFullDetails(userId);
|
|
150
|
+
if (user) {
|
|
151
|
+
sendEmail({
|
|
152
|
+
to: user.email,
|
|
153
|
+
template: 'tier_upgrade',
|
|
154
|
+
data: { name: user.name, tier, reason: reason || 'Complimentary upgrade' },
|
|
155
|
+
userId
|
|
156
|
+
}).catch(() => {});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
res.status(201).json({ grant });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
router.delete('/grants/:id', authenticateAdmin, (req, res) => {
|
|
163
|
+
const ok = revokeGrant(req.params.id);
|
|
164
|
+
if (!ok) return res.status(404).json({ error: 'Grant not found' });
|
|
165
|
+
res.json({ success: true });
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// ─── Stripe Settings ─────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
router.get('/stripe/config', authenticateAdmin, (req, res) => {
|
|
171
|
+
const secretKey = getPlatformSetting('stripe_secret_key');
|
|
172
|
+
const publishableKey = getPlatformSetting('stripe_publishable_key');
|
|
173
|
+
const webhookSecret = getPlatformSetting('stripe_webhook_secret');
|
|
174
|
+
const prices = getStripePrices();
|
|
175
|
+
|
|
176
|
+
res.json({
|
|
177
|
+
configured: isStripeConfigured(),
|
|
178
|
+
hasSecretKey: !!secretKey,
|
|
179
|
+
publishableKey: publishableKey || '',
|
|
180
|
+
webhookSecret: webhookSecret ? '••••' + webhookSecret.slice(-4) : '',
|
|
181
|
+
prices
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
router.put('/stripe/config', authenticateAdmin, (req, res) => {
|
|
186
|
+
const { secretKey, publishableKey, webhookSecret, priceStarter, pricePro, priceEnterprise } = req.body;
|
|
187
|
+
|
|
188
|
+
if (secretKey) setPlatformSetting('stripe_secret_key', secretKey);
|
|
189
|
+
if (publishableKey) setPlatformSetting('stripe_publishable_key', publishableKey);
|
|
190
|
+
if (webhookSecret) setPlatformSetting('stripe_webhook_secret', webhookSecret);
|
|
191
|
+
if (priceStarter) setPlatformSetting('stripe_price_starter', priceStarter);
|
|
192
|
+
if (pricePro) setPlatformSetting('stripe_price_pro', pricePro);
|
|
193
|
+
if (priceEnterprise) setPlatformSetting('stripe_price_enterprise', priceEnterprise);
|
|
194
|
+
|
|
195
|
+
res.json({ success: true });
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// ─── Payments ──────────────────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
router.get('/payments', authenticateAdmin, (req, res) => {
|
|
201
|
+
const limit = parseInt(req.query.limit) || 50;
|
|
202
|
+
const payments = getPayments(limit);
|
|
203
|
+
res.json({ payments });
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// ─── SMTP Settings ────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
router.get('/smtp', authenticateAdmin, (req, res) => {
|
|
209
|
+
const settings = getSmtpSettings();
|
|
210
|
+
// Mask password
|
|
211
|
+
if (settings && settings.password) {
|
|
212
|
+
settings.password = '••••••••';
|
|
213
|
+
}
|
|
214
|
+
res.json({ settings });
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
router.put('/smtp', authenticateAdmin, (req, res) => {
|
|
218
|
+
const { host, port, secure, username, password, fromName, fromEmail, enabled } = req.body;
|
|
219
|
+
if (!host || !username || !fromEmail) {
|
|
220
|
+
return res.status(400).json({ error: 'Host, username, and fromEmail are required' });
|
|
221
|
+
}
|
|
222
|
+
updateSmtpSettings({ host, port, secure, username, password, fromName, fromEmail, enabled });
|
|
223
|
+
res.json({ success: true });
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
router.post('/smtp/test', authenticateAdmin, (req, res) => {
|
|
227
|
+
const { testEmail } = req.body;
|
|
228
|
+
if (!testEmail) return res.status(400).json({ error: 'testEmail required' });
|
|
229
|
+
|
|
230
|
+
sendEmail({
|
|
231
|
+
to: testEmail,
|
|
232
|
+
template: 'welcome',
|
|
233
|
+
data: { name: 'Test User', dashboardUrl: 'https://webagentbridge.com/dashboard' }
|
|
234
|
+
}).then(result => {
|
|
235
|
+
res.json(result);
|
|
236
|
+
}).catch(err => {
|
|
237
|
+
res.status(500).json({ success: false, error: err.message });
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// ─── Notification Logs ────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
router.get('/notifications', authenticateAdmin, (req, res) => {
|
|
244
|
+
const limit = parseInt(req.query.limit) || 100;
|
|
245
|
+
const logs = getNotificationLogs(limit);
|
|
246
|
+
res.json({ logs });
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// ─── Send Custom Notification ─────────────────────────────────────────
|
|
250
|
+
|
|
251
|
+
router.post('/notifications/send', authenticateAdmin, (req, res) => {
|
|
252
|
+
const { userId, email, template, data } = req.body;
|
|
253
|
+
if (!email || !template) return res.status(400).json({ error: 'email and template required' });
|
|
254
|
+
|
|
255
|
+
sendEmail({ to: email, template, data: data || {}, userId }).then(result => {
|
|
256
|
+
res.json(result);
|
|
257
|
+
}).catch(err => {
|
|
258
|
+
res.status(500).json({ success: false, error: err.message });
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// ─── DNS Discovery Oversight ──────────────────────────────────────────
|
|
263
|
+
// Real-data views into discovery_usage_runs / discovery_trust_runs.
|
|
264
|
+
// Tables are auto-created by routes/discovery.js.
|
|
265
|
+
|
|
266
|
+
function tableExists(name) {
|
|
267
|
+
try {
|
|
268
|
+
return !!db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name = ?`).get(name);
|
|
269
|
+
} catch { return false; }
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
router.get('/discovery/stats', authenticateAdmin, (_req, res) => {
|
|
273
|
+
if (!tableExists('discovery_usage_runs')) {
|
|
274
|
+
return res.json({ enabled: false, totals: {}, last7d: [], topDomains: [] });
|
|
275
|
+
}
|
|
276
|
+
const totals = db.prepare(`
|
|
277
|
+
SELECT
|
|
278
|
+
COUNT(*) AS runs,
|
|
279
|
+
COUNT(DISTINCT domain) AS domains,
|
|
280
|
+
SUM(CASE WHEN execution_succeeded = 1 THEN 1 ELSE 0 END) AS successes,
|
|
281
|
+
AVG(value_score) AS avg_score
|
|
282
|
+
FROM discovery_usage_runs
|
|
283
|
+
WHERE created_at >= datetime('now', '-30 days')
|
|
284
|
+
`).get();
|
|
285
|
+
const last7d = db.prepare(`
|
|
286
|
+
SELECT date(created_at) AS day, COUNT(*) AS runs,
|
|
287
|
+
SUM(CASE WHEN execution_succeeded = 1 THEN 1 ELSE 0 END) AS successes
|
|
288
|
+
FROM discovery_usage_runs
|
|
289
|
+
WHERE created_at >= datetime('now', '-7 days')
|
|
290
|
+
GROUP BY day ORDER BY day ASC
|
|
291
|
+
`).all();
|
|
292
|
+
const topDomains = db.prepare(`
|
|
293
|
+
SELECT domain, COUNT(*) AS runs, AVG(value_score) AS score
|
|
294
|
+
FROM discovery_usage_runs
|
|
295
|
+
WHERE created_at >= datetime('now', '-30 days')
|
|
296
|
+
GROUP BY domain ORDER BY runs DESC LIMIT 25
|
|
297
|
+
`).all();
|
|
298
|
+
res.json({ enabled: true, totals, last7d, topDomains });
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
router.get('/discovery/runs', authenticateAdmin, (req, res) => {
|
|
302
|
+
if (!tableExists('discovery_usage_runs')) return res.json({ runs: [] });
|
|
303
|
+
const limit = Math.min(parseInt(req.query.limit, 10) || 100, 500);
|
|
304
|
+
const runs = db.prepare(`
|
|
305
|
+
SELECT id, domain, mode, preferred_use_case, selected_action,
|
|
306
|
+
readiness_ok, execution_attempted, execution_succeeded,
|
|
307
|
+
value_score, end_to_end_ms, created_at
|
|
308
|
+
FROM discovery_usage_runs
|
|
309
|
+
ORDER BY created_at DESC LIMIT ?
|
|
310
|
+
`).all(limit);
|
|
311
|
+
res.json({ runs });
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
router.get('/trust/stats', authenticateAdmin, (_req, res) => {
|
|
315
|
+
if (!tableExists('discovery_trust_runs')) {
|
|
316
|
+
return res.json({ enabled: false, totals: {}, leaderboard: [] });
|
|
317
|
+
}
|
|
318
|
+
const totals = db.prepare(`
|
|
319
|
+
SELECT COUNT(*) AS runs, COUNT(DISTINCT domain) AS domains,
|
|
320
|
+
AVG(score) AS avg_score,
|
|
321
|
+
SUM(sig_valid) AS valid_sigs,
|
|
322
|
+
SUM(signed_manifest) AS signed_manifests,
|
|
323
|
+
SUM(has_pk) AS domains_with_pk
|
|
324
|
+
FROM discovery_trust_runs
|
|
325
|
+
WHERE created_at >= datetime('now', '-30 days')
|
|
326
|
+
`).get();
|
|
327
|
+
const leaderboard = db.prepare(`
|
|
328
|
+
SELECT domain, MAX(score) AS score, MAX(created_at) AS last_run
|
|
329
|
+
FROM discovery_trust_runs
|
|
330
|
+
GROUP BY domain ORDER BY score DESC, last_run DESC LIMIT 25
|
|
331
|
+
`).all();
|
|
332
|
+
res.json({ enabled: true, totals, leaderboard });
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
router.get('/trust/runs', authenticateAdmin, (req, res) => {
|
|
336
|
+
if (!tableExists('discovery_trust_runs')) return res.json({ runs: [] });
|
|
337
|
+
const limit = Math.min(parseInt(req.query.limit, 10) || 100, 500);
|
|
338
|
+
const runs = db.prepare(`
|
|
339
|
+
SELECT id, domain, score, dnssec, has_pk, signed_manifest, sig_valid, https_ok, created_at
|
|
340
|
+
FROM discovery_trust_runs
|
|
341
|
+
ORDER BY created_at DESC LIMIT ?
|
|
342
|
+
`).all(limit);
|
|
343
|
+
res.json({ runs });
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// ─── Providers Oversight (cross-user) ─────────────────────────────────
|
|
347
|
+
|
|
348
|
+
router.get('/providers/stats', authenticateAdmin, (_req, res) => {
|
|
349
|
+
if (!tableExists('provider_accounts')) {
|
|
350
|
+
return res.json({ enabled: false, totals: {}, byProvider: [], recentActions: [] });
|
|
351
|
+
}
|
|
352
|
+
const totals = db.prepare(`
|
|
353
|
+
SELECT COUNT(*) AS accounts,
|
|
354
|
+
SUM(CASE WHEN status='active' THEN 1 ELSE 0 END) AS active,
|
|
355
|
+
SUM(CASE WHEN status='error' THEN 1 ELSE 0 END) AS errored,
|
|
356
|
+
SUM(domains_count) AS domains_under_management
|
|
357
|
+
FROM provider_accounts
|
|
358
|
+
`).get();
|
|
359
|
+
const byProvider = db.prepare(`
|
|
360
|
+
SELECT provider_type, COUNT(*) AS accounts,
|
|
361
|
+
SUM(CASE WHEN status='active' THEN 1 ELSE 0 END) AS active,
|
|
362
|
+
COALESCE(SUM(domains_count), 0) AS domains
|
|
363
|
+
FROM provider_accounts
|
|
364
|
+
GROUP BY provider_type ORDER BY accounts DESC
|
|
365
|
+
`).all();
|
|
366
|
+
const recentActions = tableExists('provider_action_log')
|
|
367
|
+
? db.prepare(`
|
|
368
|
+
SELECT l.id, l.account_id, a.provider_type, l.domain, l.action, l.status,
|
|
369
|
+
l.duration_ms, l.detail, l.created_at
|
|
370
|
+
FROM provider_action_log l
|
|
371
|
+
LEFT JOIN provider_accounts a ON a.id = l.account_id
|
|
372
|
+
ORDER BY l.created_at DESC LIMIT 50
|
|
373
|
+
`).all()
|
|
374
|
+
: [];
|
|
375
|
+
const wabEnabled = tableExists('provider_domains')
|
|
376
|
+
? db.prepare(`SELECT COUNT(*) AS c FROM provider_domains WHERE wab_enabled = 1`).get().c
|
|
377
|
+
: 0;
|
|
378
|
+
res.json({ enabled: true, totals: { ...totals, wab_enabled_domains: wabEnabled }, byProvider, recentActions });
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
router.get('/providers/accounts', authenticateAdmin, (req, res) => {
|
|
382
|
+
if (!tableExists('provider_accounts')) return res.json({ accounts: [] });
|
|
383
|
+
const limit = Math.min(parseInt(req.query.limit, 10) || 200, 1000);
|
|
384
|
+
const rows = db.prepare(`
|
|
385
|
+
SELECT a.id, a.user_id, u.email AS user_email, a.provider_type, a.label,
|
|
386
|
+
a.status, a.last_test_at, a.last_test_ok, a.last_test_error,
|
|
387
|
+
a.last_sync_at, a.domains_count, a.created_at, a.updated_at
|
|
388
|
+
FROM provider_accounts a
|
|
389
|
+
LEFT JOIN users u ON u.id = a.user_id
|
|
390
|
+
ORDER BY a.created_at DESC LIMIT ?
|
|
391
|
+
`).all(limit);
|
|
392
|
+
res.json({ accounts: rows });
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
router.get('/providers/domains', authenticateAdmin, (req, res) => {
|
|
396
|
+
if (!tableExists('provider_domains')) return res.json({ domains: [] });
|
|
397
|
+
const limit = Math.min(parseInt(req.query.limit, 10) || 200, 1000);
|
|
398
|
+
const rows = db.prepare(`
|
|
399
|
+
SELECT d.id, d.account_id, a.provider_type, a.user_id, u.email AS user_email,
|
|
400
|
+
d.domain, d.zone_id, d.wab_enabled, d.wab_record_value,
|
|
401
|
+
d.last_action, d.last_action_at, d.last_action_status, d.last_action_error
|
|
402
|
+
FROM provider_domains d
|
|
403
|
+
LEFT JOIN provider_accounts a ON a.id = d.account_id
|
|
404
|
+
LEFT JOIN users u ON u.id = a.user_id
|
|
405
|
+
ORDER BY d.updated_at DESC LIMIT ?
|
|
406
|
+
`).all(limit);
|
|
407
|
+
res.json({ domains: rows });
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// ─── API Modules: real counts (replaces hardcoded dashboard placeholders) ──
|
|
411
|
+
router.get('/modules/stats', authenticateAdmin, (_req, res) => {
|
|
412
|
+
// Source of truth: server/routes/* + service registry. We treat each
|
|
413
|
+
// top-level route module as one "API module" and return real numbers.
|
|
414
|
+
const modules = [
|
|
415
|
+
{ id: 'auth', label: 'Authentication', openness: 'open' },
|
|
416
|
+
{ id: 'wab', label: 'WAB Core API', openness: 'open' },
|
|
417
|
+
{ id: 'discovery', label: 'DNS Discovery', openness: 'open' },
|
|
418
|
+
{ id: 'sovereign', label: 'Sovereign Mode', openness: 'open' },
|
|
419
|
+
{ id: 'commander', label: 'Commander SDK', openness: 'partial' },
|
|
420
|
+
{ id: 'mesh', label: 'Agent Mesh', openness: 'partial' },
|
|
421
|
+
{ id: 'workspace', label: 'Agent Workspace', openness: 'partial' },
|
|
422
|
+
{ id: 'universal', label: 'Universal Agent', openness: 'partial' },
|
|
423
|
+
{ id: 'gateway', label: 'Gateway (v1)', openness: 'closed' },
|
|
424
|
+
{ id: 'premium', label: 'Premium APIs', openness: 'closed' },
|
|
425
|
+
{ id: 'admin', label: 'Admin APIs', openness: 'closed' },
|
|
426
|
+
{ id: 'providers', label: 'DNS Providers', openness: 'open' },
|
|
427
|
+
];
|
|
428
|
+
const counts = modules.reduce((acc, m) => { acc[m.openness] = (acc[m.openness] || 0) + 1; return acc; }, {});
|
|
429
|
+
res.json({
|
|
430
|
+
total: modules.length,
|
|
431
|
+
open: counts.open || 0,
|
|
432
|
+
partial: counts.partial || 0,
|
|
433
|
+
closed: counts.closed || 0,
|
|
434
|
+
modules,
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// ─── Governance Layer (Phase 20) — admin oversight of all agents ──
|
|
439
|
+
// These endpoints expose the same data as /api/governance/* but authenticate
|
|
440
|
+
// via admin token instead of an agent's own token, so an admin can see and
|
|
441
|
+
// act across every agent on the platform.
|
|
442
|
+
const gov = (() => { try { return require('../services/governance'); } catch { return null; } });
|
|
443
|
+
function ensureGovTables() {
|
|
444
|
+
return tableExists('gov_agents');
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
router.get('/governance/stats', authenticateAdmin, (_req, res) => {
|
|
448
|
+
if (!ensureGovTables()) return res.json({ enabled: false, totals: {} });
|
|
449
|
+
const totals = db.prepare(`
|
|
450
|
+
SELECT
|
|
451
|
+
COUNT(*) AS agents,
|
|
452
|
+
SUM(CASE WHEN status='alive' THEN 1 ELSE 0 END) AS alive,
|
|
453
|
+
SUM(CASE WHEN status='killed' THEN 1 ELSE 0 END) AS killed,
|
|
454
|
+
SUM(CASE WHEN status='suspended' THEN 1 ELSE 0 END) AS suspended
|
|
455
|
+
FROM gov_agents
|
|
456
|
+
`).get() || {};
|
|
457
|
+
const policies = db.prepare(`SELECT COUNT(*) AS c FROM gov_policies`).get().c;
|
|
458
|
+
const audit = db.prepare(`SELECT COUNT(*) AS c FROM gov_audit`).get().c;
|
|
459
|
+
const pending = db.prepare(`SELECT COUNT(*) AS c FROM gov_approvals WHERE status='pending'`).get().c;
|
|
460
|
+
const recentDenies = db.prepare(`
|
|
461
|
+
SELECT id, agent_id, ts, resource, action, decision, reason
|
|
462
|
+
FROM gov_audit WHERE decision='deny' ORDER BY id DESC LIMIT 25
|
|
463
|
+
`).all();
|
|
464
|
+
res.json({ enabled: true, totals: { ...totals, policies, audit_events: audit, pending_approvals: pending }, recentDenies });
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
router.get('/governance/agents', authenticateAdmin, (_req, res) => {
|
|
468
|
+
if (!ensureGovTables()) return res.json({ agents: [] });
|
|
469
|
+
const rows = db.prepare(`
|
|
470
|
+
SELECT a.agent_id, a.owner_id, u.email AS owner_email, a.display_name,
|
|
471
|
+
a.status, a.killed_at, a.killed_reason, a.created_at, a.updated_at,
|
|
472
|
+
(SELECT COUNT(*) FROM gov_policies p WHERE p.agent_id = a.agent_id) AS policies,
|
|
473
|
+
(SELECT COUNT(*) FROM gov_audit g WHERE g.agent_id = a.agent_id) AS audit_events,
|
|
474
|
+
(SELECT COUNT(*) FROM gov_approvals ap WHERE ap.agent_id = a.agent_id AND ap.status='pending') AS pending_approvals
|
|
475
|
+
FROM gov_agents a
|
|
476
|
+
LEFT JOIN users u ON u.id = a.owner_id
|
|
477
|
+
ORDER BY a.created_at DESC
|
|
478
|
+
`).all();
|
|
479
|
+
res.json({ agents: rows });
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
router.get('/governance/agents/:id', authenticateAdmin, (req, res) => {
|
|
483
|
+
if (!ensureGovTables()) return res.status(404).json({ error: 'governance_disabled' });
|
|
484
|
+
const a = db.prepare(`SELECT agent_id, owner_id, display_name, status, killed_at, killed_reason, metadata, created_at, updated_at FROM gov_agents WHERE agent_id = ?`).get(req.params.id);
|
|
485
|
+
if (!a) return res.status(404).json({ error: 'agent_not_found' });
|
|
486
|
+
const policies = db.prepare(`SELECT * FROM gov_policies WHERE agent_id = ? ORDER BY id DESC`).all(req.params.id);
|
|
487
|
+
const approvals = db.prepare(`SELECT * FROM gov_approvals WHERE agent_id = ? AND status='pending' ORDER BY created_at DESC LIMIT 50`).all(req.params.id);
|
|
488
|
+
const audit = db.prepare(`SELECT id, ts, event_type, resource, action, scope, amount, currency, decision, reason FROM gov_audit WHERE agent_id = ? ORDER BY id DESC LIMIT 200`).all(req.params.id);
|
|
489
|
+
res.json({ agent: a, policies, approvals, audit });
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
router.get('/governance/agents/:id/audit/verify', authenticateAdmin, (req, res) => {
|
|
493
|
+
const g = gov(); if (!g) return res.status(503).json({ error: 'governance_unavailable' });
|
|
494
|
+
res.json(g.verifyAuditChain(req.params.id));
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
router.post('/governance/agents/:id/kill', authenticateAdmin, (req, res) => {
|
|
498
|
+
const g = gov(); if (!g) return res.status(503).json({ error: 'governance_unavailable' });
|
|
499
|
+
const reason = (req.body && req.body.reason) || ('admin:' + (req.admin.email || req.admin.id));
|
|
500
|
+
const ok = g.killAgent(req.params.id, reason);
|
|
501
|
+
auditLog({ actorType: 'admin', actorId: String(req.admin.id), action: 'governance_kill', details: { agent_id: req.params.id, reason } });
|
|
502
|
+
res.json({ ok, status: 'killed', reason });
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
router.post('/governance/agents/:id/revive', authenticateAdmin, (req, res) => {
|
|
506
|
+
const g = gov(); if (!g) return res.status(503).json({ error: 'governance_unavailable' });
|
|
507
|
+
const reason = (req.body && req.body.reason) || ('admin:' + (req.admin.email || req.admin.id));
|
|
508
|
+
const ok = g.reviveAgent(req.params.id, reason);
|
|
509
|
+
auditLog({ actorType: 'admin', actorId: String(req.admin.id), action: 'governance_revive', details: { agent_id: req.params.id, reason } });
|
|
510
|
+
res.json({ ok, status: 'alive', reason });
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
router.post('/governance/agents/:id/policies', authenticateAdmin, (req, res) => {
|
|
514
|
+
const g = gov(); if (!g) return res.status(503).json({ error: 'governance_unavailable' });
|
|
515
|
+
const b = req.body || {};
|
|
516
|
+
if (!b.resource || !b.action) return res.status(400).json({ error: 'missing_fields', need: ['resource', 'action'] });
|
|
517
|
+
const r = g.definePolicy({
|
|
518
|
+
agentId: req.params.id, resource: String(b.resource), action: String(b.action),
|
|
519
|
+
scope: b.scope || null, maxAmount: b.max_amount, currency: b.currency,
|
|
520
|
+
dailyCap: b.daily_cap, perCallRate: b.per_call_rate,
|
|
521
|
+
requiresApproval: !!b.requires_approval,
|
|
522
|
+
effect: b.effect === 'deny' ? 'deny' : 'allow',
|
|
523
|
+
expiresAt: b.expires_at || null,
|
|
524
|
+
});
|
|
525
|
+
auditLog({ actorType: 'admin', actorId: String(req.admin.id), action: 'governance_policy_create', details: { agent_id: req.params.id, policy_id: r.id } });
|
|
526
|
+
res.status(201).json({ ok: true, id: r.id });
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
router.delete('/governance/agents/:id/policies/:pid', authenticateAdmin, (req, res) => {
|
|
530
|
+
const g = gov(); if (!g) return res.status(503).json({ error: 'governance_unavailable' });
|
|
531
|
+
const ok = g.deletePolicy(req.params.id, Number(req.params.pid));
|
|
532
|
+
auditLog({ actorType: 'admin', actorId: String(req.admin.id), action: 'governance_policy_delete', details: { agent_id: req.params.id, policy_id: req.params.pid } });
|
|
533
|
+
res.json({ ok });
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
router.post('/governance/approvals/:rid/decide', authenticateAdmin, (req, res) => {
|
|
537
|
+
const g = gov(); if (!g) return res.status(503).json({ error: 'governance_unavailable' });
|
|
538
|
+
const decision = req.body?.decision === 'approved' ? 'approved' : 'rejected';
|
|
539
|
+
const out = g.decideApproval(req.params.rid, {
|
|
540
|
+
decision,
|
|
541
|
+
decidedBy: 'admin:' + req.admin.id,
|
|
542
|
+
note: req.body?.note || null,
|
|
543
|
+
});
|
|
544
|
+
if (!out.ok) return res.status(409).json(out);
|
|
545
|
+
auditLog({ actorType: 'admin', actorId: String(req.admin.id), action: 'governance_approval_decide', details: { request_id: req.params.rid, decision } });
|
|
546
|
+
res.json(out);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
module.exports = router;
|