@waiaas/daemon 2.10.0 → 2.10.1-rc
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/api/helpers/resolve-chain-id.d.ts +2 -0
- package/dist/api/helpers/resolve-chain-id.d.ts.map +1 -0
- package/dist/api/helpers/resolve-chain-id.js +21 -0
- package/dist/api/helpers/resolve-chain-id.js.map +1 -0
- package/dist/api/routes/actions.d.ts.map +1 -1
- package/dist/api/routes/actions.js +1 -20
- package/dist/api/routes/actions.js.map +1 -1
- package/dist/api/routes/admin-actions.d.ts.map +1 -1
- package/dist/api/routes/admin-actions.js +1 -19
- package/dist/api/routes/admin-actions.js.map +1 -1
- package/dist/api/routes/admin-auth.d.ts +9 -0
- package/dist/api/routes/admin-auth.d.ts.map +1 -0
- package/dist/api/routes/admin-auth.js +453 -0
- package/dist/api/routes/admin-auth.js.map +1 -0
- package/dist/api/routes/admin-monitoring.d.ts +10 -0
- package/dist/api/routes/admin-monitoring.d.ts.map +1 -0
- package/dist/api/routes/admin-monitoring.js +788 -0
- package/dist/api/routes/admin-monitoring.js.map +1 -0
- package/dist/api/routes/admin-notifications.d.ts +9 -0
- package/dist/api/routes/admin-notifications.d.ts.map +1 -0
- package/dist/api/routes/admin-notifications.js +210 -0
- package/dist/api/routes/admin-notifications.js.map +1 -0
- package/dist/api/routes/admin-settings.d.ts +9 -0
- package/dist/api/routes/admin-settings.d.ts.map +1 -0
- package/dist/api/routes/admin-settings.js +433 -0
- package/dist/api/routes/admin-settings.js.map +1 -0
- package/dist/api/routes/admin-wallets.d.ts +16 -0
- package/dist/api/routes/admin-wallets.d.ts.map +1 -0
- package/dist/api/routes/admin-wallets.js +582 -0
- package/dist/api/routes/admin-wallets.js.map +1 -0
- package/dist/api/routes/admin.d.ts +8 -26
- package/dist/api/routes/admin.d.ts.map +1 -1
- package/dist/api/routes/admin.js +20 -2536
- package/dist/api/routes/admin.js.map +1 -1
- package/dist/api/routes/erc8004.d.ts.map +1 -1
- package/dist/api/routes/erc8004.js +0 -1
- package/dist/api/routes/erc8004.js.map +1 -1
- package/dist/api/routes/mcp.d.ts +2 -0
- package/dist/api/routes/mcp.d.ts.map +1 -1
- package/dist/api/routes/mcp.js +2 -1
- package/dist/api/routes/mcp.js.map +1 -1
- package/dist/api/routes/nft-approvals.js +4 -4
- package/dist/api/routes/nft-approvals.js.map +1 -1
- package/dist/api/routes/sessions.d.ts +2 -0
- package/dist/api/routes/sessions.d.ts.map +1 -1
- package/dist/api/routes/sessions.js +5 -3
- package/dist/api/routes/sessions.js.map +1 -1
- package/dist/api/routes/wallets.d.ts.map +1 -1
- package/dist/api/routes/wallets.js +3 -3
- package/dist/api/routes/wallets.js.map +1 -1
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +2 -0
- package/dist/api/server.js.map +1 -1
- package/dist/constants.d.ts +17 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +17 -0
- package/dist/constants.js.map +1 -0
- package/dist/infrastructure/nft/nft-indexer-client.d.ts.map +1 -1
- package/dist/infrastructure/nft/nft-indexer-client.js +3 -3
- package/dist/infrastructure/nft/nft-indexer-client.js.map +1 -1
- package/dist/infrastructure/oracle/coingecko-forex.d.ts.map +1 -1
- package/dist/infrastructure/oracle/coingecko-forex.js +2 -3
- package/dist/infrastructure/oracle/coingecko-forex.js.map +1 -1
- package/dist/infrastructure/oracle/coingecko-oracle.d.ts.map +1 -1
- package/dist/infrastructure/oracle/coingecko-oracle.js +2 -3
- package/dist/infrastructure/oracle/coingecko-oracle.js.map +1 -1
- package/dist/infrastructure/oracle/pyth-oracle.d.ts +0 -1
- package/dist/infrastructure/oracle/pyth-oracle.d.ts.map +1 -1
- package/dist/infrastructure/oracle/pyth-oracle.js +3 -3
- package/dist/infrastructure/oracle/pyth-oracle.js.map +1 -1
- package/dist/lifecycle/workers.d.ts.map +1 -1
- package/dist/lifecycle/workers.js +2 -1
- package/dist/lifecycle/workers.js.map +1 -1
- package/dist/pipeline/dry-run.d.ts.map +1 -1
- package/dist/pipeline/dry-run.js +4 -3
- package/dist/pipeline/dry-run.js.map +1 -1
- package/dist/pipeline/stages.d.ts.map +1 -1
- package/dist/pipeline/stages.js +4 -3
- package/dist/pipeline/stages.js.map +1 -1
- package/dist/services/signing-sdk/channels/ntfy-signing-channel.js +2 -2
- package/dist/services/signing-sdk/channels/ntfy-signing-channel.js.map +1 -1
- package/dist/services/signing-sdk/channels/telegram-signing-channel.js +1 -1
- package/dist/services/signing-sdk/channels/telegram-signing-channel.js.map +1 -1
- package/dist/services/signing-sdk/channels/wallet-notification-channel.js +2 -2
- package/dist/services/signing-sdk/channels/wallet-notification-channel.js.map +1 -1
- package/dist/services/signing-sdk/sign-request-builder.d.ts +2 -0
- package/dist/services/signing-sdk/sign-request-builder.d.ts.map +1 -1
- package/dist/services/signing-sdk/sign-request-builder.js +20 -3
- package/dist/services/signing-sdk/sign-request-builder.js.map +1 -1
- package/dist/services/signing-sdk/sign-response-handler.js +4 -4
- package/dist/services/signing-sdk/sign-response-handler.js.map +1 -1
- package/package.json +5 -5
- package/public/admin/assets/index-By5VUJ-B.js +3 -0
- package/public/admin/index.html +1 -1
- package/public/admin/assets/index--wQVT9Dz.js +0 -3
|
@@ -0,0 +1,788 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Monitoring route handlers: transactions, incoming, agent-prompt, session-reissue,
|
|
3
|
+
* tx-cancel, tx-reject, backup, stats, autostop.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from admin.ts for maintainability.
|
|
6
|
+
*/
|
|
7
|
+
import { createRoute, z } from '@hono/zod-openapi';
|
|
8
|
+
import { sql, desc, eq, and, isNull, gt, count as drizzleCount } from 'drizzle-orm';
|
|
9
|
+
import { createHash } from 'node:crypto';
|
|
10
|
+
import { WAIaaSError, getNetworksForEnvironment } from '@waiaas/core';
|
|
11
|
+
import { wallets, sessions, sessionWallets, policies, transactions, incomingTransactions } from '../../infrastructure/database/schema.js';
|
|
12
|
+
import { generateId } from '../../infrastructure/database/id.js';
|
|
13
|
+
import { buildConnectInfoPrompt } from './connect-info.js';
|
|
14
|
+
import { AgentPromptRequestSchema, AgentPromptResponseSchema, SessionReissueResponseSchema, BackupInfoResponseSchema, BackupListResponseSchema, ErrorResponseSchema, buildErrorResponses, } from './openapi-schemas.js';
|
|
15
|
+
import { formatTxAmount } from './admin-wallets.js';
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Route definitions (top-level)
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
const adminTransactionsQuerySchema = z.object({
|
|
20
|
+
offset: z.coerce.number().int().min(0).default(0).optional(),
|
|
21
|
+
limit: z.coerce.number().int().min(1).max(100).default(20).optional(),
|
|
22
|
+
wallet_id: z.string().uuid().optional(),
|
|
23
|
+
type: z.string().optional(),
|
|
24
|
+
status: z.string().optional(),
|
|
25
|
+
network: z.string().optional(),
|
|
26
|
+
since: z.coerce.number().optional(),
|
|
27
|
+
until: z.coerce.number().optional(),
|
|
28
|
+
search: z.string().optional(),
|
|
29
|
+
});
|
|
30
|
+
const adminTransactionsRoute = createRoute({
|
|
31
|
+
method: 'get',
|
|
32
|
+
path: '/admin/transactions',
|
|
33
|
+
tags: ['Admin'],
|
|
34
|
+
summary: 'List cross-wallet transactions with filters and pagination',
|
|
35
|
+
request: {
|
|
36
|
+
query: adminTransactionsQuerySchema,
|
|
37
|
+
},
|
|
38
|
+
responses: {
|
|
39
|
+
200: {
|
|
40
|
+
description: 'Paginated cross-wallet transaction list',
|
|
41
|
+
content: {
|
|
42
|
+
'application/json': {
|
|
43
|
+
schema: z.object({
|
|
44
|
+
items: z.array(z.object({
|
|
45
|
+
id: z.string(),
|
|
46
|
+
walletId: z.string(),
|
|
47
|
+
walletName: z.string().nullable(),
|
|
48
|
+
type: z.string(),
|
|
49
|
+
status: z.string(),
|
|
50
|
+
tier: z.string().nullable(),
|
|
51
|
+
toAddress: z.string().nullable(),
|
|
52
|
+
amount: z.string().nullable(),
|
|
53
|
+
amountUsd: z.number().nullable(),
|
|
54
|
+
network: z.string().nullable(),
|
|
55
|
+
txHash: z.string().nullable(),
|
|
56
|
+
chain: z.string(),
|
|
57
|
+
createdAt: z.number().nullable(),
|
|
58
|
+
})),
|
|
59
|
+
total: z.number().int(),
|
|
60
|
+
offset: z.number().int(),
|
|
61
|
+
limit: z.number().int(),
|
|
62
|
+
}),
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
const adminIncomingQuerySchema = z.object({
|
|
69
|
+
offset: z.coerce.number().int().min(0).default(0).optional(),
|
|
70
|
+
limit: z.coerce.number().int().min(1).max(100).default(20).optional(),
|
|
71
|
+
wallet_id: z.string().uuid().optional(),
|
|
72
|
+
chain: z.string().optional(),
|
|
73
|
+
status: z.string().optional(),
|
|
74
|
+
suspicious: z.enum(['true', 'false']).optional(),
|
|
75
|
+
});
|
|
76
|
+
const adminIncomingRoute = createRoute({
|
|
77
|
+
method: 'get',
|
|
78
|
+
path: '/admin/incoming',
|
|
79
|
+
tags: ['Admin'],
|
|
80
|
+
summary: 'List cross-wallet incoming transactions with filters and pagination',
|
|
81
|
+
request: {
|
|
82
|
+
query: adminIncomingQuerySchema,
|
|
83
|
+
},
|
|
84
|
+
responses: {
|
|
85
|
+
200: {
|
|
86
|
+
description: 'Paginated cross-wallet incoming transaction list',
|
|
87
|
+
content: {
|
|
88
|
+
'application/json': {
|
|
89
|
+
schema: z.object({
|
|
90
|
+
items: z.array(z.object({
|
|
91
|
+
id: z.string(),
|
|
92
|
+
txHash: z.string(),
|
|
93
|
+
walletId: z.string(),
|
|
94
|
+
walletName: z.string().nullable(),
|
|
95
|
+
fromAddress: z.string(),
|
|
96
|
+
amount: z.string(),
|
|
97
|
+
tokenAddress: z.string().nullable(),
|
|
98
|
+
chain: z.string(),
|
|
99
|
+
network: z.string(),
|
|
100
|
+
status: z.string(),
|
|
101
|
+
blockNumber: z.number().nullable(),
|
|
102
|
+
detectedAt: z.number().nullable(),
|
|
103
|
+
confirmedAt: z.number().nullable(),
|
|
104
|
+
suspicious: z.boolean(),
|
|
105
|
+
})),
|
|
106
|
+
total: z.number().int(),
|
|
107
|
+
offset: z.number().int(),
|
|
108
|
+
limit: z.number().int(),
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Register handlers
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
export function registerAdminMonitoringRoutes(router, deps) {
|
|
119
|
+
// GET /admin/transactions (cross-wallet transaction list)
|
|
120
|
+
router.openapi(adminTransactionsRoute, async (c) => {
|
|
121
|
+
const query = c.req.valid('query');
|
|
122
|
+
const offset = query.offset ?? 0;
|
|
123
|
+
const limit = query.limit ?? 20;
|
|
124
|
+
// Build WHERE conditions
|
|
125
|
+
const conditions = [];
|
|
126
|
+
if (query.wallet_id) {
|
|
127
|
+
conditions.push(eq(transactions.walletId, query.wallet_id));
|
|
128
|
+
}
|
|
129
|
+
if (query.type) {
|
|
130
|
+
conditions.push(eq(transactions.type, query.type));
|
|
131
|
+
}
|
|
132
|
+
if (query.status) {
|
|
133
|
+
conditions.push(eq(transactions.status, query.status));
|
|
134
|
+
}
|
|
135
|
+
if (query.network) {
|
|
136
|
+
conditions.push(eq(transactions.network, query.network));
|
|
137
|
+
}
|
|
138
|
+
if (query.since !== undefined) {
|
|
139
|
+
conditions.push(sql `${transactions.createdAt} >= ${query.since}`);
|
|
140
|
+
}
|
|
141
|
+
if (query.until !== undefined) {
|
|
142
|
+
conditions.push(sql `${transactions.createdAt} <= ${query.until}`);
|
|
143
|
+
}
|
|
144
|
+
if (query.search) {
|
|
145
|
+
const pattern = `%${query.search}%`;
|
|
146
|
+
conditions.push(sql `(${transactions.txHash} LIKE ${pattern} OR ${transactions.toAddress} LIKE ${pattern})`);
|
|
147
|
+
}
|
|
148
|
+
const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
|
|
149
|
+
// Count total
|
|
150
|
+
const totalResult = deps.db
|
|
151
|
+
.select({ count: drizzleCount() })
|
|
152
|
+
.from(transactions)
|
|
153
|
+
.where(whereClause)
|
|
154
|
+
.get();
|
|
155
|
+
const total = totalResult?.count ?? 0;
|
|
156
|
+
// Query with JOIN for walletName
|
|
157
|
+
const rows = deps.db
|
|
158
|
+
.select({
|
|
159
|
+
id: transactions.id,
|
|
160
|
+
walletId: transactions.walletId,
|
|
161
|
+
walletName: wallets.name,
|
|
162
|
+
type: transactions.type,
|
|
163
|
+
status: transactions.status,
|
|
164
|
+
tier: transactions.tier,
|
|
165
|
+
toAddress: transactions.toAddress,
|
|
166
|
+
amount: transactions.amount,
|
|
167
|
+
amountUsd: transactions.amountUsd,
|
|
168
|
+
network: transactions.network,
|
|
169
|
+
txHash: transactions.txHash,
|
|
170
|
+
chain: transactions.chain,
|
|
171
|
+
createdAt: transactions.createdAt,
|
|
172
|
+
tokenMint: transactions.tokenMint,
|
|
173
|
+
contractAddress: transactions.contractAddress,
|
|
174
|
+
})
|
|
175
|
+
.from(transactions)
|
|
176
|
+
.leftJoin(wallets, eq(transactions.walletId, wallets.id))
|
|
177
|
+
.where(whereClause)
|
|
178
|
+
.orderBy(desc(transactions.createdAt))
|
|
179
|
+
.offset(offset)
|
|
180
|
+
.limit(limit)
|
|
181
|
+
.all();
|
|
182
|
+
const items = rows.map((row) => {
|
|
183
|
+
const tokenAddr = row.tokenMint ?? row.contractAddress ?? null;
|
|
184
|
+
return {
|
|
185
|
+
id: row.id,
|
|
186
|
+
walletId: row.walletId,
|
|
187
|
+
walletName: row.walletName ?? null,
|
|
188
|
+
type: row.type,
|
|
189
|
+
status: row.status,
|
|
190
|
+
tier: row.tier ?? null,
|
|
191
|
+
toAddress: row.toAddress ?? null,
|
|
192
|
+
amount: row.amount ?? null,
|
|
193
|
+
formattedAmount: formatTxAmount(row.amount ?? null, row.chain, row.network ?? null, tokenAddr, deps.db),
|
|
194
|
+
amountUsd: row.amountUsd ?? null,
|
|
195
|
+
network: row.network ?? null,
|
|
196
|
+
txHash: row.txHash ?? null,
|
|
197
|
+
chain: row.chain,
|
|
198
|
+
createdAt: row.createdAt instanceof Date
|
|
199
|
+
? Math.floor(row.createdAt.getTime() / 1000)
|
|
200
|
+
: (typeof row.createdAt === 'number' ? row.createdAt : null),
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
return c.json({ items, total, offset, limit }, 200);
|
|
204
|
+
});
|
|
205
|
+
// GET /admin/incoming (cross-wallet incoming transaction list)
|
|
206
|
+
router.openapi(adminIncomingRoute, async (c) => {
|
|
207
|
+
const query = c.req.valid('query');
|
|
208
|
+
const offset = query.offset ?? 0;
|
|
209
|
+
const limit = query.limit ?? 20;
|
|
210
|
+
// Build WHERE conditions (no default status filter -- admin sees all)
|
|
211
|
+
const conditions = [];
|
|
212
|
+
if (query.wallet_id) {
|
|
213
|
+
conditions.push(eq(incomingTransactions.walletId, query.wallet_id));
|
|
214
|
+
}
|
|
215
|
+
if (query.chain) {
|
|
216
|
+
conditions.push(eq(incomingTransactions.chain, query.chain));
|
|
217
|
+
}
|
|
218
|
+
if (query.status) {
|
|
219
|
+
conditions.push(eq(incomingTransactions.status, query.status));
|
|
220
|
+
}
|
|
221
|
+
if (query.suspicious !== undefined) {
|
|
222
|
+
conditions.push(eq(incomingTransactions.isSuspicious, query.suspicious === 'true'));
|
|
223
|
+
}
|
|
224
|
+
const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
|
|
225
|
+
// Count total
|
|
226
|
+
const totalResult = deps.db
|
|
227
|
+
.select({ count: drizzleCount() })
|
|
228
|
+
.from(incomingTransactions)
|
|
229
|
+
.where(whereClause)
|
|
230
|
+
.get();
|
|
231
|
+
const total = totalResult?.count ?? 0;
|
|
232
|
+
// Query with JOIN for walletName
|
|
233
|
+
const rows = deps.db
|
|
234
|
+
.select({
|
|
235
|
+
id: incomingTransactions.id,
|
|
236
|
+
txHash: incomingTransactions.txHash,
|
|
237
|
+
walletId: incomingTransactions.walletId,
|
|
238
|
+
walletName: wallets.name,
|
|
239
|
+
fromAddress: incomingTransactions.fromAddress,
|
|
240
|
+
amount: incomingTransactions.amount,
|
|
241
|
+
tokenAddress: incomingTransactions.tokenAddress,
|
|
242
|
+
chain: incomingTransactions.chain,
|
|
243
|
+
network: incomingTransactions.network,
|
|
244
|
+
status: incomingTransactions.status,
|
|
245
|
+
blockNumber: incomingTransactions.blockNumber,
|
|
246
|
+
detectedAt: incomingTransactions.detectedAt,
|
|
247
|
+
confirmedAt: incomingTransactions.confirmedAt,
|
|
248
|
+
isSuspicious: incomingTransactions.isSuspicious,
|
|
249
|
+
})
|
|
250
|
+
.from(incomingTransactions)
|
|
251
|
+
.leftJoin(wallets, eq(incomingTransactions.walletId, wallets.id))
|
|
252
|
+
.where(whereClause)
|
|
253
|
+
.orderBy(desc(incomingTransactions.detectedAt))
|
|
254
|
+
.offset(offset)
|
|
255
|
+
.limit(limit)
|
|
256
|
+
.all();
|
|
257
|
+
const items = rows.map((row) => ({
|
|
258
|
+
id: row.id,
|
|
259
|
+
txHash: row.txHash,
|
|
260
|
+
walletId: row.walletId,
|
|
261
|
+
walletName: row.walletName ?? null,
|
|
262
|
+
fromAddress: row.fromAddress,
|
|
263
|
+
amount: row.amount,
|
|
264
|
+
formattedAmount: formatTxAmount(row.amount, row.chain, row.network, row.tokenAddress ?? null, deps.db),
|
|
265
|
+
tokenAddress: row.tokenAddress ?? null,
|
|
266
|
+
chain: row.chain,
|
|
267
|
+
network: row.network,
|
|
268
|
+
status: row.status,
|
|
269
|
+
blockNumber: row.blockNumber ?? null,
|
|
270
|
+
detectedAt: row.detectedAt instanceof Date
|
|
271
|
+
? Math.floor(row.detectedAt.getTime() / 1000)
|
|
272
|
+
: (typeof row.detectedAt === 'number' ? row.detectedAt : null),
|
|
273
|
+
confirmedAt: row.confirmedAt instanceof Date
|
|
274
|
+
? Math.floor(row.confirmedAt.getTime() / 1000)
|
|
275
|
+
: (typeof row.confirmedAt === 'number' ? row.confirmedAt : null),
|
|
276
|
+
suspicious: row.isSuspicious ?? false,
|
|
277
|
+
}));
|
|
278
|
+
return c.json({ items, total, offset, limit }, 200);
|
|
279
|
+
});
|
|
280
|
+
// POST /admin/agent-prompt -- Generate agent connection prompt
|
|
281
|
+
const agentPromptRoute = createRoute({
|
|
282
|
+
method: 'post',
|
|
283
|
+
path: '/admin/agent-prompt',
|
|
284
|
+
tags: ['Admin'],
|
|
285
|
+
summary: 'Generate agent connection prompt (magic word)',
|
|
286
|
+
request: {
|
|
287
|
+
body: {
|
|
288
|
+
content: { 'application/json': { schema: AgentPromptRequestSchema } },
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
responses: {
|
|
292
|
+
201: {
|
|
293
|
+
description: 'Agent prompt generated',
|
|
294
|
+
content: { 'application/json': { schema: AgentPromptResponseSchema } },
|
|
295
|
+
},
|
|
296
|
+
...buildErrorResponses(['ADAPTER_NOT_AVAILABLE']),
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
router.openapi(agentPromptRoute, async (c) => {
|
|
300
|
+
if (!deps.jwtSecretManager || !deps.daemonConfig) {
|
|
301
|
+
throw new WAIaaSError('ADAPTER_NOT_AVAILABLE', { message: 'JWT signing not available' });
|
|
302
|
+
}
|
|
303
|
+
const body = c.req.valid('json');
|
|
304
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
305
|
+
// v29.9: per-session TTL; omit = unlimited
|
|
306
|
+
const ttl = body.ttl; // undefined = unlimited session
|
|
307
|
+
const expiresAt = ttl !== undefined ? nowSec + ttl : 0; // 0 = unlimited
|
|
308
|
+
// Get target wallets (with environment for prompt builder)
|
|
309
|
+
let targetWallets;
|
|
310
|
+
if (body.walletIds && body.walletIds.length > 0) {
|
|
311
|
+
targetWallets = body.walletIds
|
|
312
|
+
.map((wid) => deps.db.select().from(wallets).where(eq(wallets.id, wid)).get())
|
|
313
|
+
.filter((w) => w != null && w.status === 'ACTIVE')
|
|
314
|
+
.map((w) => ({ id: w.id, name: w.name, chain: w.chain, environment: w.environment, publicKey: w.publicKey }));
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
targetWallets = deps.db
|
|
318
|
+
.select()
|
|
319
|
+
.from(wallets)
|
|
320
|
+
.where(eq(wallets.status, 'ACTIVE'))
|
|
321
|
+
.all()
|
|
322
|
+
.map((w) => ({ id: w.id, name: w.name, chain: w.chain, environment: w.environment, publicKey: w.publicKey }));
|
|
323
|
+
}
|
|
324
|
+
if (targetWallets.length === 0) {
|
|
325
|
+
return c.json({ prompt: '', walletCount: 0, sessionsCreated: 0, sessionReused: false, expiresAt }, 201);
|
|
326
|
+
}
|
|
327
|
+
// Try to reuse an existing valid session covering all target wallets
|
|
328
|
+
const defaultWallet = targetWallets[0];
|
|
329
|
+
const targetWalletIds = targetWallets.map((w) => w.id);
|
|
330
|
+
let sessionId;
|
|
331
|
+
let sessionReused = false;
|
|
332
|
+
let sessionsCreated = 1;
|
|
333
|
+
let actualExpiresAt = expiresAt;
|
|
334
|
+
// Find active sessions that cover all target wallets
|
|
335
|
+
const candidateSessions = deps.db
|
|
336
|
+
.select({
|
|
337
|
+
id: sessions.id,
|
|
338
|
+
expiresAt: sessions.expiresAt,
|
|
339
|
+
})
|
|
340
|
+
.from(sessions)
|
|
341
|
+
.where(and(isNull(sessions.revokedAt), ttl !== undefined
|
|
342
|
+
? gt(sessions.expiresAt, new Date((nowSec + Math.max(Math.floor(ttl * 0.1), 3600)) * 1000))
|
|
343
|
+
: sql `(${sessions.expiresAt} = 0 OR ${sessions.expiresAt} > ${nowSec})`))
|
|
344
|
+
.all();
|
|
345
|
+
let reusableSessionId = null;
|
|
346
|
+
let reusableExpiresAt = 0;
|
|
347
|
+
for (const candidate of candidateSessions) {
|
|
348
|
+
// Count how many of our target wallets are linked to this session
|
|
349
|
+
const linkedCount = deps.db
|
|
350
|
+
.select({ cnt: drizzleCount() })
|
|
351
|
+
.from(sessionWallets)
|
|
352
|
+
.where(and(eq(sessionWallets.sessionId, candidate.id), sql `${sessionWallets.walletId} IN (${sql.join(targetWalletIds.map((id) => sql `${id}`), sql `, `)})`))
|
|
353
|
+
.get();
|
|
354
|
+
if (linkedCount && linkedCount.cnt === targetWalletIds.length) {
|
|
355
|
+
reusableSessionId = candidate.id;
|
|
356
|
+
reusableExpiresAt = candidate.expiresAt instanceof Date
|
|
357
|
+
? Math.floor(candidate.expiresAt.getTime() / 1000)
|
|
358
|
+
: candidate.expiresAt;
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (reusableSessionId) {
|
|
363
|
+
// Reuse existing session
|
|
364
|
+
sessionId = reusableSessionId;
|
|
365
|
+
sessionReused = true;
|
|
366
|
+
sessionsCreated = 0;
|
|
367
|
+
actualExpiresAt = reusableExpiresAt;
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
// Create a new multi-wallet session
|
|
371
|
+
sessionId = generateId();
|
|
372
|
+
deps.db.insert(sessions).values({
|
|
373
|
+
id: sessionId,
|
|
374
|
+
tokenHash: '',
|
|
375
|
+
expiresAt: new Date(expiresAt * 1000),
|
|
376
|
+
absoluteExpiresAt: new Date(0), // unlimited
|
|
377
|
+
createdAt: new Date(nowSec * 1000),
|
|
378
|
+
renewalCount: 0,
|
|
379
|
+
maxRenewals: 0, // unlimited
|
|
380
|
+
constraints: null,
|
|
381
|
+
source: 'api',
|
|
382
|
+
}).run();
|
|
383
|
+
// Insert N rows into session_wallets
|
|
384
|
+
for (let i = 0; i < targetWallets.length; i++) {
|
|
385
|
+
const w = targetWallets[i];
|
|
386
|
+
deps.db.insert(sessionWallets).values({
|
|
387
|
+
sessionId,
|
|
388
|
+
walletId: w.id,
|
|
389
|
+
createdAt: new Date(nowSec * 1000),
|
|
390
|
+
}).run();
|
|
391
|
+
}
|
|
392
|
+
void deps.notificationService?.notify('SESSION_CREATED', defaultWallet.id, { sessionId });
|
|
393
|
+
}
|
|
394
|
+
// Sign JWT
|
|
395
|
+
const jwtPayload = {
|
|
396
|
+
sub: sessionId,
|
|
397
|
+
iat: nowSec,
|
|
398
|
+
exp: actualExpiresAt > 0 ? actualExpiresAt : undefined,
|
|
399
|
+
};
|
|
400
|
+
const token = await deps.jwtSecretManager.signToken(jwtPayload);
|
|
401
|
+
if (!sessionReused) {
|
|
402
|
+
const tokenHash = createHash('sha256').update(token).digest('hex');
|
|
403
|
+
deps.db.update(sessions).set({ tokenHash }).where(eq(sessions.id, sessionId)).run();
|
|
404
|
+
}
|
|
405
|
+
// Query per-wallet policies for prompt builder
|
|
406
|
+
const promptWallets = targetWallets.map((w) => {
|
|
407
|
+
const walletPolicies = deps.db
|
|
408
|
+
.select({ type: policies.type })
|
|
409
|
+
.from(policies)
|
|
410
|
+
.where(and(eq(policies.walletId, w.id), eq(policies.enabled, true)))
|
|
411
|
+
.all();
|
|
412
|
+
const networks = getNetworksForEnvironment(w.chain, w.environment);
|
|
413
|
+
return {
|
|
414
|
+
id: w.id,
|
|
415
|
+
name: w.name,
|
|
416
|
+
chain: w.chain,
|
|
417
|
+
environment: w.environment,
|
|
418
|
+
address: w.publicKey,
|
|
419
|
+
networks: networks.map((n) => n),
|
|
420
|
+
policies: walletPolicies,
|
|
421
|
+
};
|
|
422
|
+
});
|
|
423
|
+
// Compute capabilities dynamically (same logic as connect-info)
|
|
424
|
+
const capabilities = ['transfer', 'token_transfer', 'balance', 'assets'];
|
|
425
|
+
if (deps.settingsService) {
|
|
426
|
+
try {
|
|
427
|
+
if (deps.settingsService.get('signing_sdk.enabled') === 'true') {
|
|
428
|
+
capabilities.push('sign');
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
// Setting key not found -- signing not available
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (deps.settingsService && deps.actionProviderRegistry) {
|
|
436
|
+
try {
|
|
437
|
+
const providers = deps.actionProviderRegistry.listProviders();
|
|
438
|
+
const hasAnyKey = providers.some((p) => p.requiresApiKey && deps.settingsService.hasApiKey(p.name));
|
|
439
|
+
if (hasAnyKey) {
|
|
440
|
+
capabilities.push('actions');
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
// Settings service not available
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (deps.daemonConfig?.x402?.enabled === true) {
|
|
448
|
+
capabilities.push('x402');
|
|
449
|
+
}
|
|
450
|
+
// Read default-deny toggles
|
|
451
|
+
const defaultDeny = {
|
|
452
|
+
tokenTransfers: deps.settingsService?.get('policy.default_deny_tokens') !== 'false',
|
|
453
|
+
contractCalls: deps.settingsService?.get('policy.default_deny_contracts') !== 'false',
|
|
454
|
+
tokenApprovals: deps.settingsService?.get('policy.default_deny_spenders') !== 'false',
|
|
455
|
+
x402Domains: deps.settingsService?.get('policy.default_deny_x402_domains') !== 'false',
|
|
456
|
+
};
|
|
457
|
+
// Build prompt using shared prompt builder
|
|
458
|
+
const host = c.req.header('Host') ?? 'localhost:3100';
|
|
459
|
+
const protocol = c.req.header('X-Forwarded-Proto') ?? 'http';
|
|
460
|
+
const baseUrl = `${protocol}://${host}`;
|
|
461
|
+
const prompt = buildConnectInfoPrompt({
|
|
462
|
+
wallets: promptWallets,
|
|
463
|
+
capabilities,
|
|
464
|
+
defaultDeny,
|
|
465
|
+
baseUrl,
|
|
466
|
+
version: deps.version,
|
|
467
|
+
});
|
|
468
|
+
// Append session token so the agent can start using it immediately
|
|
469
|
+
const fullPrompt = `${prompt}\n\nSession Token: ${token}\nSession ID: ${sessionId}`;
|
|
470
|
+
return c.json({
|
|
471
|
+
prompt: fullPrompt,
|
|
472
|
+
walletCount: targetWallets.length,
|
|
473
|
+
sessionsCreated,
|
|
474
|
+
sessionReused,
|
|
475
|
+
expiresAt: actualExpiresAt,
|
|
476
|
+
}, 201);
|
|
477
|
+
});
|
|
478
|
+
// POST /admin/sessions/:id/reissue -- Reissue session token
|
|
479
|
+
const sessionReissueRoute = createRoute({
|
|
480
|
+
method: 'post',
|
|
481
|
+
path: '/admin/sessions/{id}/reissue',
|
|
482
|
+
tags: ['Admin'],
|
|
483
|
+
summary: 'Reissue session token (re-sign JWT for existing session)',
|
|
484
|
+
request: {
|
|
485
|
+
params: z.object({ id: z.string().uuid() }),
|
|
486
|
+
},
|
|
487
|
+
responses: {
|
|
488
|
+
200: {
|
|
489
|
+
description: 'Token reissued',
|
|
490
|
+
content: { 'application/json': { schema: SessionReissueResponseSchema } },
|
|
491
|
+
},
|
|
492
|
+
...buildErrorResponses(['SESSION_NOT_FOUND', 'SESSION_REVOKED']),
|
|
493
|
+
},
|
|
494
|
+
});
|
|
495
|
+
router.openapi(sessionReissueRoute, async (c) => {
|
|
496
|
+
if (!deps.jwtSecretManager) {
|
|
497
|
+
throw new WAIaaSError('ADAPTER_NOT_AVAILABLE', { message: 'JWT signing not available' });
|
|
498
|
+
}
|
|
499
|
+
const { id: sessionId } = c.req.valid('param');
|
|
500
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
501
|
+
// Find session
|
|
502
|
+
const session = deps.db
|
|
503
|
+
.select()
|
|
504
|
+
.from(sessions)
|
|
505
|
+
.where(eq(sessions.id, sessionId))
|
|
506
|
+
.get();
|
|
507
|
+
if (!session) {
|
|
508
|
+
throw new WAIaaSError('SESSION_NOT_FOUND');
|
|
509
|
+
}
|
|
510
|
+
if (session.revokedAt) {
|
|
511
|
+
throw new WAIaaSError('SESSION_REVOKED');
|
|
512
|
+
}
|
|
513
|
+
const expiresAtSec = session.expiresAt instanceof Date
|
|
514
|
+
? Math.floor(session.expiresAt.getTime() / 1000)
|
|
515
|
+
: session.expiresAt;
|
|
516
|
+
if (expiresAtSec > 0 && expiresAtSec <= nowSec) {
|
|
517
|
+
throw new WAIaaSError('SESSION_NOT_FOUND', { message: 'Session expired' });
|
|
518
|
+
}
|
|
519
|
+
// Re-sign JWT (no wallet claim needed -- walletId resolved at request time)
|
|
520
|
+
const jwtPayload = {
|
|
521
|
+
sub: sessionId,
|
|
522
|
+
iat: nowSec,
|
|
523
|
+
...(expiresAtSec > 0 ? { exp: expiresAtSec } : {}),
|
|
524
|
+
};
|
|
525
|
+
const token = await deps.jwtSecretManager.signToken(jwtPayload);
|
|
526
|
+
// Increment token_issued_count
|
|
527
|
+
const newCount = (session.tokenIssuedCount ?? 1) + 1;
|
|
528
|
+
deps.db.update(sessions)
|
|
529
|
+
.set({ tokenIssuedCount: newCount })
|
|
530
|
+
.where(eq(sessions.id, sessionId))
|
|
531
|
+
.run();
|
|
532
|
+
return c.json({
|
|
533
|
+
token,
|
|
534
|
+
sessionId,
|
|
535
|
+
tokenIssuedCount: newCount,
|
|
536
|
+
expiresAt: expiresAtSec,
|
|
537
|
+
}, 200);
|
|
538
|
+
});
|
|
539
|
+
// POST /admin/transactions/:id/cancel -- Cancel a QUEUED (DELAY) transaction
|
|
540
|
+
const adminTxCancelRoute = createRoute({
|
|
541
|
+
method: 'post',
|
|
542
|
+
path: '/admin/transactions/{id}/cancel',
|
|
543
|
+
tags: ['Admin'],
|
|
544
|
+
summary: 'Cancel a delayed (QUEUED) transaction',
|
|
545
|
+
request: {
|
|
546
|
+
params: z.object({ id: z.string().uuid() }),
|
|
547
|
+
},
|
|
548
|
+
responses: {
|
|
549
|
+
200: {
|
|
550
|
+
description: 'Transaction cancelled',
|
|
551
|
+
content: {
|
|
552
|
+
'application/json': {
|
|
553
|
+
schema: z.object({
|
|
554
|
+
id: z.string(),
|
|
555
|
+
status: z.literal('CANCELLED'),
|
|
556
|
+
}),
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
...buildErrorResponses(['TX_NOT_FOUND']),
|
|
561
|
+
},
|
|
562
|
+
});
|
|
563
|
+
router.openapi(adminTxCancelRoute, async (c) => {
|
|
564
|
+
const { id: txId } = c.req.valid('param');
|
|
565
|
+
if (!deps.delayQueue) {
|
|
566
|
+
throw new WAIaaSError('ADAPTER_NOT_AVAILABLE', {
|
|
567
|
+
message: 'Delay queue not available',
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
deps.delayQueue.cancelDelay(txId);
|
|
571
|
+
return c.json({ id: txId, status: 'CANCELLED' }, 200);
|
|
572
|
+
});
|
|
573
|
+
// POST /admin/transactions/:id/reject -- Reject a pending (APPROVAL) transaction
|
|
574
|
+
const adminTxRejectRoute = createRoute({
|
|
575
|
+
method: 'post',
|
|
576
|
+
path: '/admin/transactions/{id}/reject',
|
|
577
|
+
tags: ['Admin'],
|
|
578
|
+
summary: 'Reject a pending approval transaction',
|
|
579
|
+
request: {
|
|
580
|
+
params: z.object({ id: z.string().uuid() }),
|
|
581
|
+
},
|
|
582
|
+
responses: {
|
|
583
|
+
200: {
|
|
584
|
+
description: 'Transaction rejected',
|
|
585
|
+
content: {
|
|
586
|
+
'application/json': {
|
|
587
|
+
schema: z.object({
|
|
588
|
+
id: z.string(),
|
|
589
|
+
status: z.literal('CANCELLED'),
|
|
590
|
+
rejectedAt: z.number(),
|
|
591
|
+
}),
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
},
|
|
595
|
+
...buildErrorResponses(['TX_NOT_FOUND']),
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
router.openapi(adminTxRejectRoute, async (c) => {
|
|
599
|
+
const { id: txId } = c.req.valid('param');
|
|
600
|
+
if (!deps.approvalWorkflow) {
|
|
601
|
+
throw new WAIaaSError('ADAPTER_NOT_AVAILABLE', {
|
|
602
|
+
message: 'Approval workflow not available',
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
const result = deps.approvalWorkflow.reject(txId);
|
|
606
|
+
return c.json({
|
|
607
|
+
id: txId,
|
|
608
|
+
status: 'CANCELLED',
|
|
609
|
+
rejectedAt: result.rejectedAt,
|
|
610
|
+
}, 200);
|
|
611
|
+
});
|
|
612
|
+
// POST /admin/backup (create encrypted backup)
|
|
613
|
+
const createBackupRoute = createRoute({
|
|
614
|
+
method: 'post',
|
|
615
|
+
path: '/admin/backup',
|
|
616
|
+
tags: ['Admin'],
|
|
617
|
+
summary: 'Create an encrypted backup',
|
|
618
|
+
responses: {
|
|
619
|
+
200: {
|
|
620
|
+
description: 'Backup created successfully',
|
|
621
|
+
content: { 'application/json': { schema: BackupInfoResponseSchema } },
|
|
622
|
+
},
|
|
623
|
+
401: {
|
|
624
|
+
description: 'Master password not available',
|
|
625
|
+
content: { 'application/json': { schema: ErrorResponseSchema } },
|
|
626
|
+
},
|
|
627
|
+
501: {
|
|
628
|
+
description: 'Backup service not configured',
|
|
629
|
+
content: { 'application/json': { schema: ErrorResponseSchema } },
|
|
630
|
+
},
|
|
631
|
+
...buildErrorResponses(['INVALID_MASTER_PASSWORD']),
|
|
632
|
+
},
|
|
633
|
+
});
|
|
634
|
+
router.openapi(createBackupRoute, async (c) => {
|
|
635
|
+
if (!deps.encryptedBackupService) {
|
|
636
|
+
return c.json({ code: 'NOT_CONFIGURED', message: 'Backup service not configured', retryable: false }, 501);
|
|
637
|
+
}
|
|
638
|
+
if (!deps.passwordRef?.password) {
|
|
639
|
+
return c.json({ code: 'INVALID_MASTER_PASSWORD', message: 'Master password not available', retryable: false }, 401);
|
|
640
|
+
}
|
|
641
|
+
const info = await deps.encryptedBackupService.createBackup(deps.passwordRef.password);
|
|
642
|
+
return c.json(info, 200);
|
|
643
|
+
});
|
|
644
|
+
// GET /admin/backups (list backups)
|
|
645
|
+
const listBackupsRoute = createRoute({
|
|
646
|
+
method: 'get',
|
|
647
|
+
path: '/admin/backups',
|
|
648
|
+
tags: ['Admin'],
|
|
649
|
+
summary: 'List available backups',
|
|
650
|
+
responses: {
|
|
651
|
+
200: {
|
|
652
|
+
description: 'Backup list',
|
|
653
|
+
content: { 'application/json': { schema: BackupListResponseSchema } },
|
|
654
|
+
},
|
|
655
|
+
501: {
|
|
656
|
+
description: 'Backup service not configured',
|
|
657
|
+
content: { 'application/json': { schema: ErrorResponseSchema } },
|
|
658
|
+
},
|
|
659
|
+
},
|
|
660
|
+
});
|
|
661
|
+
router.openapi(listBackupsRoute, async (c) => {
|
|
662
|
+
if (!deps.encryptedBackupService) {
|
|
663
|
+
return c.json({ code: 'NOT_CONFIGURED', message: 'Backup service not configured', retryable: false }, 501);
|
|
664
|
+
}
|
|
665
|
+
const backups = deps.encryptedBackupService.listBackups();
|
|
666
|
+
const retentionCount = deps.daemonConfig?.backup?.retention_count ?? 7;
|
|
667
|
+
return c.json({ backups, total: backups.length, retention_count: retentionCount }, 200);
|
|
668
|
+
});
|
|
669
|
+
// GET /admin/stats -- 7-category operational statistics (STAT-01)
|
|
670
|
+
const adminStatsRoute = createRoute({
|
|
671
|
+
method: 'get',
|
|
672
|
+
path: '/admin/stats',
|
|
673
|
+
tags: ['Admin'],
|
|
674
|
+
summary: 'Get operational statistics',
|
|
675
|
+
responses: {
|
|
676
|
+
200: {
|
|
677
|
+
description: 'Operational statistics (7 categories)',
|
|
678
|
+
content: { 'application/json': { schema: z.any() } },
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
});
|
|
682
|
+
router.openapi(adminStatsRoute, async (c) => {
|
|
683
|
+
if (!deps.adminStatsService) {
|
|
684
|
+
throw new WAIaaSError('STATS_NOT_CONFIGURED');
|
|
685
|
+
}
|
|
686
|
+
const stats = deps.adminStatsService.getStats();
|
|
687
|
+
return c.json(stats, 200);
|
|
688
|
+
});
|
|
689
|
+
// GET /admin/autostop/rules -- List AutoStop rules with status (PLUG-03)
|
|
690
|
+
const autostopRulesRoute = createRoute({
|
|
691
|
+
method: 'get',
|
|
692
|
+
path: '/admin/autostop/rules',
|
|
693
|
+
tags: ['Admin'],
|
|
694
|
+
summary: 'List AutoStop rules with status',
|
|
695
|
+
responses: {
|
|
696
|
+
200: {
|
|
697
|
+
description: 'AutoStop rules list',
|
|
698
|
+
content: { 'application/json': { schema: z.any() } },
|
|
699
|
+
},
|
|
700
|
+
},
|
|
701
|
+
});
|
|
702
|
+
router.openapi(autostopRulesRoute, async (c) => {
|
|
703
|
+
if (!deps.autoStopService) {
|
|
704
|
+
return c.json({ globalEnabled: false, rules: [] }, 200);
|
|
705
|
+
}
|
|
706
|
+
const status = deps.autoStopService.getStatus();
|
|
707
|
+
const registry = deps.autoStopService.registry;
|
|
708
|
+
const rules = registry.getRules().map((r) => {
|
|
709
|
+
const ruleStatus = r.getStatus();
|
|
710
|
+
return {
|
|
711
|
+
id: r.id,
|
|
712
|
+
displayName: r.displayName,
|
|
713
|
+
description: r.description,
|
|
714
|
+
enabled: r.enabled,
|
|
715
|
+
subscribedEvents: r.subscribedEvents,
|
|
716
|
+
config: ruleStatus.config,
|
|
717
|
+
state: ruleStatus.state,
|
|
718
|
+
};
|
|
719
|
+
});
|
|
720
|
+
return c.json({ globalEnabled: status.enabled, rules }, 200);
|
|
721
|
+
});
|
|
722
|
+
// PUT /admin/autostop/rules/:id -- Update AutoStop rule (PLUG-03)
|
|
723
|
+
const autostopRuleUpdateRoute = createRoute({
|
|
724
|
+
method: 'put',
|
|
725
|
+
path: '/admin/autostop/rules/{id}',
|
|
726
|
+
tags: ['Admin'],
|
|
727
|
+
summary: 'Update AutoStop rule enabled/config',
|
|
728
|
+
request: {
|
|
729
|
+
params: z.object({ id: z.string() }),
|
|
730
|
+
body: {
|
|
731
|
+
content: {
|
|
732
|
+
'application/json': {
|
|
733
|
+
schema: z.object({
|
|
734
|
+
enabled: z.boolean().optional(),
|
|
735
|
+
config: z.record(z.unknown()).optional(),
|
|
736
|
+
}),
|
|
737
|
+
},
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
},
|
|
741
|
+
responses: {
|
|
742
|
+
200: {
|
|
743
|
+
description: 'Rule updated',
|
|
744
|
+
content: { 'application/json': { schema: z.any() } },
|
|
745
|
+
},
|
|
746
|
+
404: {
|
|
747
|
+
description: 'Rule not found',
|
|
748
|
+
content: { 'application/json': { schema: z.any() } },
|
|
749
|
+
},
|
|
750
|
+
},
|
|
751
|
+
});
|
|
752
|
+
router.openapi(autostopRuleUpdateRoute, async (c) => {
|
|
753
|
+
if (!deps.autoStopService) {
|
|
754
|
+
throw new WAIaaSError('RULE_NOT_FOUND');
|
|
755
|
+
}
|
|
756
|
+
const { id } = c.req.valid('param');
|
|
757
|
+
const body = c.req.valid('json');
|
|
758
|
+
const registry = deps.autoStopService.registry;
|
|
759
|
+
const rule = registry.getRule(id);
|
|
760
|
+
if (!rule) {
|
|
761
|
+
throw new WAIaaSError('RULE_NOT_FOUND');
|
|
762
|
+
}
|
|
763
|
+
// Update enabled state
|
|
764
|
+
if (body.enabled !== undefined) {
|
|
765
|
+
registry.setEnabled(id, body.enabled);
|
|
766
|
+
// Persist to Admin Settings
|
|
767
|
+
if (deps.settingsService) {
|
|
768
|
+
deps.settingsService.set(`autostop.rule.${id}.enabled`, String(body.enabled));
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
// Update config
|
|
772
|
+
if (body.config) {
|
|
773
|
+
rule.updateConfig(body.config);
|
|
774
|
+
}
|
|
775
|
+
// Return updated rule info
|
|
776
|
+
const ruleStatus = rule.getStatus();
|
|
777
|
+
return c.json({
|
|
778
|
+
id: rule.id,
|
|
779
|
+
displayName: rule.displayName,
|
|
780
|
+
description: rule.description,
|
|
781
|
+
enabled: rule.enabled,
|
|
782
|
+
subscribedEvents: rule.subscribedEvents,
|
|
783
|
+
config: ruleStatus.config,
|
|
784
|
+
state: ruleStatus.state,
|
|
785
|
+
}, 200);
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
//# sourceMappingURL=admin-monitoring.js.map
|