@waiaas/daemon 2.4.0 → 2.5.0-rc.1

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.
Files changed (36) hide show
  1. package/dist/api/routes/admin.d.ts.map +1 -1
  2. package/dist/api/routes/admin.js +146 -233
  3. package/dist/api/routes/admin.js.map +1 -1
  4. package/dist/api/routes/connect-info.d.ts +2 -0
  5. package/dist/api/routes/connect-info.d.ts.map +1 -1
  6. package/dist/api/routes/connect-info.js +31 -19
  7. package/dist/api/routes/connect-info.js.map +1 -1
  8. package/dist/api/routes/openapi-schemas.d.ts +24 -0
  9. package/dist/api/routes/openapi-schemas.d.ts.map +1 -1
  10. package/dist/api/routes/openapi-schemas.js +10 -0
  11. package/dist/api/routes/openapi-schemas.js.map +1 -1
  12. package/dist/api/routes/policies.d.ts +2 -1
  13. package/dist/api/routes/policies.d.ts.map +1 -1
  14. package/dist/api/routes/policies.js +24 -10
  15. package/dist/api/routes/policies.js.map +1 -1
  16. package/dist/api/routes/sessions.d.ts.map +1 -1
  17. package/dist/api/routes/sessions.js +3 -0
  18. package/dist/api/routes/sessions.js.map +1 -1
  19. package/dist/api/server.d.ts.map +1 -1
  20. package/dist/api/server.js +47 -2
  21. package/dist/api/server.js.map +1 -1
  22. package/dist/infrastructure/database/migrate.d.ts +1 -1
  23. package/dist/infrastructure/database/migrate.d.ts.map +1 -1
  24. package/dist/infrastructure/database/migrate.js +14 -3
  25. package/dist/infrastructure/database/migrate.js.map +1 -1
  26. package/dist/infrastructure/database/schema.d.ts +17 -0
  27. package/dist/infrastructure/database/schema.d.ts.map +1 -1
  28. package/dist/infrastructure/database/schema.js +1 -0
  29. package/dist/infrastructure/database/schema.js.map +1 -1
  30. package/dist/lifecycle/daemon.d.ts.map +1 -1
  31. package/dist/lifecycle/daemon.js +36 -14
  32. package/dist/lifecycle/daemon.js.map +1 -1
  33. package/package.json +4 -4
  34. package/public/admin/assets/index-BLLOYSZp.js +1 -0
  35. package/public/admin/index.html +1 -1
  36. package/public/admin/assets/index-i7xhksGh.js +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../../src/api/routes/admin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAEhE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,KAAK,EAAE,QAAQ,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAKjE,OAAO,KAAK,EAAyE,YAAY,EAAE,iBAAiB,EAAgB,MAAM,cAAc,CAAC;AAEzJ,OAAO,KAAK,EAAE,gBAAgB,EAAc,MAAM,gDAAgD,CAAC;AAInG,OAAO,KAAK,KAAK,MAAM,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6CAA6C,CAAC;AACvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAE9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAExE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8CAA8C,CAAC;AAChF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yDAAyD,CAAC;AACtG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uDAAuD,CAAC;AA8BjG,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,qBAAqB,CAAC,OAAO,MAAM,CAAC,CAAC;IACzC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,kBAAkB,EAAE,MAAM,eAAe,CAAC;IAC1C,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAClE,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,kBAAkB,CAAC,EAAE,YAAY,CAAC,eAAe,CAAC,CAAC;IACnD,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACpD,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,YAAY,CAAC,EAAE;QAAE,yBAAyB,EAAE,OAAO,CAAC;QAAC,wBAAwB,EAAE,MAAM,CAAA;KAAE,CAAC;IACxF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;IAChD,gBAAgB,CAAC,EAAE,iBAAiB,CAAC;IACrC,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mBAAmB,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAClD;AAkmBD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,cAAc,GAAG,WAAW,CA0wC7D"}
1
+ {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../../src/api/routes/admin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAEhE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,KAAK,EAAE,QAAQ,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGjE,OAAO,KAAK,EAAyE,YAAY,EAAE,iBAAiB,EAAgB,MAAM,cAAc,CAAC;AAEzJ,OAAO,KAAK,EAAE,gBAAgB,EAAc,MAAM,gDAAgD,CAAC;AAInG,OAAO,KAAK,KAAK,MAAM,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6CAA6C,CAAC;AACvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAE9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAExE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8CAA8C,CAAC;AAChF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yDAAyD,CAAC;AACtG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uDAAuD,CAAC;AA+BjG,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,qBAAqB,CAAC,OAAO,MAAM,CAAC,CAAC;IACzC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,kBAAkB,EAAE,MAAM,eAAe,CAAC;IAC1C,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAClE,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,kBAAkB,CAAC,EAAE,YAAY,CAAC,eAAe,CAAC,CAAC;IACnD,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACpD,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,YAAY,CAAC,EAAE;QAAE,yBAAyB,EAAE,OAAO,CAAC;QAAC,wBAAwB,EAAE,MAAM,CAAA;KAAE,CAAC;IACxF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;IAChD,gBAAgB,CAAC,EAAE,iBAAiB,CAAC;IACrC,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mBAAmB,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAClD;AAkhBD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,cAAc,GAAG,WAAW,CA+xC7D"}
@@ -26,10 +26,8 @@
26
26
  * @see docs/36-killswitch-evm-freeze.md
27
27
  */
28
28
  import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi';
29
- import { sql, desc, eq, and, count as drizzleCount } from 'drizzle-orm';
29
+ import { sql, desc, eq, and, isNull, gt, count as drizzleCount } from 'drizzle-orm';
30
30
  import { createHash } from 'node:crypto';
31
- import { writeFile, rename, mkdir } from 'node:fs/promises';
32
- import { join, dirname } from 'node:path';
33
31
  import { WAIaaSError, getDefaultNetwork, getNetworksForEnvironment } from '@waiaas/core';
34
32
  import { CurrencyCodeSchema, formatRatePreview } from '@waiaas/core';
35
33
  import { wallets, sessions, sessionWallets, notificationLogs, policies, transactions } from '../../infrastructure/database/schema.js';
@@ -37,7 +35,7 @@ import { generateId } from '../../infrastructure/database/id.js';
37
35
  import { buildConnectInfoPrompt } from './connect-info.js';
38
36
  import { getSettingDefinition } from '../../infrastructure/settings/index.js';
39
37
  import { resolveRpcUrl } from '../../infrastructure/adapter-pool.js';
40
- import { AdminStatusResponseSchema, KillSwitchResponseSchema, KillSwitchActivateResponseSchema, KillSwitchEscalateResponseSchema, RecoverResponseSchema, KillSwitchRecoverRequestSchema, ShutdownResponseSchema, RotateSecretResponseSchema, NotificationStatusResponseSchema, NotificationTestRequestSchema, NotificationTestResponseSchema, NotificationLogResponseSchema, SettingsResponseSchema, SettingsUpdateRequestSchema, SettingsUpdateResponseSchema, TestRpcRequestSchema, TestRpcResponseSchema, OracleStatusResponseSchema, AgentPromptRequestSchema, AgentPromptResponseSchema, buildErrorResponses, openApiValidationHook, } from './openapi-schemas.js';
38
+ import { AdminStatusResponseSchema, KillSwitchResponseSchema, KillSwitchActivateResponseSchema, KillSwitchEscalateResponseSchema, RecoverResponseSchema, KillSwitchRecoverRequestSchema, ShutdownResponseSchema, RotateSecretResponseSchema, NotificationStatusResponseSchema, NotificationTestRequestSchema, NotificationTestResponseSchema, NotificationLogResponseSchema, SettingsResponseSchema, SettingsUpdateRequestSchema, SettingsUpdateResponseSchema, TestRpcRequestSchema, TestRpcResponseSchema, OracleStatusResponseSchema, AgentPromptRequestSchema, AgentPromptResponseSchema, SessionReissueResponseSchema, buildErrorResponses, openApiValidationHook, } from './openapi-schemas.js';
41
39
  // ---------------------------------------------------------------------------
42
40
  // Route definitions
43
41
  // ---------------------------------------------------------------------------
@@ -435,82 +433,6 @@ const adminWalletBalanceRoute = createRoute({
435
433
  },
436
434
  });
437
435
  // ---------------------------------------------------------------------------
438
- // Bulk session/MCP token route definitions
439
- // ---------------------------------------------------------------------------
440
- const BulkResultItemSchema = z.object({
441
- walletId: z.string().uuid(),
442
- walletName: z.string().nullable(),
443
- sessionId: z.string().uuid().optional(),
444
- token: z.string().optional(),
445
- tokenPath: z.string().optional(),
446
- error: z.string().optional(),
447
- });
448
- const adminBulkSessionsRoute = createRoute({
449
- method: 'post',
450
- path: '/admin/sessions/bulk',
451
- tags: ['Admin'],
452
- summary: 'Create sessions for multiple wallets at once',
453
- request: {
454
- body: {
455
- content: {
456
- 'application/json': {
457
- schema: z.object({
458
- walletIds: z.array(z.string().uuid()).min(1).max(50),
459
- ttl: z.number().int().min(300).optional(),
460
- }),
461
- },
462
- },
463
- },
464
- },
465
- responses: {
466
- 200: {
467
- description: 'Bulk session creation results',
468
- content: {
469
- 'application/json': {
470
- schema: z.object({
471
- results: z.array(BulkResultItemSchema),
472
- created: z.number().int(),
473
- failed: z.number().int(),
474
- }),
475
- },
476
- },
477
- },
478
- },
479
- });
480
- const adminBulkMcpTokensRoute = createRoute({
481
- method: 'post',
482
- path: '/admin/mcp/tokens/bulk',
483
- tags: ['Admin'],
484
- summary: 'Create MCP tokens for multiple wallets at once',
485
- request: {
486
- body: {
487
- content: {
488
- 'application/json': {
489
- schema: z.object({
490
- walletIds: z.array(z.string().uuid()).min(1).max(50),
491
- ttl: z.number().int().min(300).optional(),
492
- }),
493
- },
494
- },
495
- },
496
- },
497
- responses: {
498
- 200: {
499
- description: 'Bulk MCP token creation results',
500
- content: {
501
- 'application/json': {
502
- schema: z.object({
503
- results: z.array(BulkResultItemSchema),
504
- created: z.number().int(),
505
- failed: z.number().int(),
506
- claudeDesktopConfig: z.record(z.unknown()),
507
- }),
508
- },
509
- },
510
- },
511
- },
512
- });
513
- // ---------------------------------------------------------------------------
514
436
  // Telegram Users route definitions
515
437
  // ---------------------------------------------------------------------------
516
438
  const TelegramUserSchema = z.object({
@@ -1393,132 +1315,6 @@ export function adminRoutes(deps) {
1393
1315
  return c.json({ success: true }, 200);
1394
1316
  });
1395
1317
  // ---------------------------------------------------------------------------
1396
- // POST /admin/sessions/bulk — Bulk create sessions
1397
- // ---------------------------------------------------------------------------
1398
- router.openapi(adminBulkSessionsRoute, async (c) => {
1399
- if (!deps.jwtSecretManager || !deps.daemonConfig) {
1400
- throw new WAIaaSError('ADAPTER_NOT_AVAILABLE', { message: 'JWT signing not available' });
1401
- }
1402
- const { walletIds, ttl: reqTtl } = c.req.valid('json');
1403
- const config = deps.daemonConfig;
1404
- const nowSec = Math.floor(Date.now() / 1000);
1405
- const ttl = reqTtl ?? config.security.session_ttl;
1406
- const expiresAt = nowSec + ttl;
1407
- const absoluteExpiresAt = nowSec + config.security.session_absolute_lifetime;
1408
- const results = [];
1409
- for (const walletId of walletIds) {
1410
- try {
1411
- const wallet = deps.db.select().from(wallets).where(eq(wallets.id, walletId)).get();
1412
- if (!wallet) {
1413
- results.push({ walletId, walletName: null, error: 'Wallet not found' });
1414
- continue;
1415
- }
1416
- if (wallet.status === 'TERMINATED') {
1417
- results.push({ walletId, walletName: wallet.name, error: 'Wallet terminated' });
1418
- continue;
1419
- }
1420
- const sessionId = generateId();
1421
- const jwtPayload = { sub: sessionId, wlt: walletId, iat: nowSec, exp: expiresAt };
1422
- const token = await deps.jwtSecretManager.signToken(jwtPayload);
1423
- const tokenHash = createHash('sha256').update(token).digest('hex');
1424
- deps.db.insert(sessions).values({
1425
- id: sessionId, tokenHash,
1426
- expiresAt: new Date(expiresAt * 1000),
1427
- absoluteExpiresAt: new Date(absoluteExpiresAt * 1000),
1428
- createdAt: new Date(nowSec * 1000),
1429
- renewalCount: 0, maxRenewals: config.security.session_max_renewals,
1430
- constraints: null, source: 'api',
1431
- }).run();
1432
- deps.db.insert(sessionWallets).values({
1433
- sessionId, walletId, isDefault: true, createdAt: new Date(nowSec * 1000),
1434
- }).run();
1435
- void deps.notificationService?.notify('SESSION_CREATED', walletId, { sessionId });
1436
- results.push({ walletId, walletName: wallet.name, sessionId, token });
1437
- }
1438
- catch (err) {
1439
- const msg = err instanceof Error ? err.message : String(err);
1440
- results.push({ walletId, walletName: null, error: msg });
1441
- }
1442
- }
1443
- const created = results.filter((r) => !r.error).length;
1444
- return c.json({ results, created, failed: results.length - created }, 200);
1445
- });
1446
- // ---------------------------------------------------------------------------
1447
- // POST /admin/mcp/tokens/bulk — Bulk create MCP tokens
1448
- // ---------------------------------------------------------------------------
1449
- router.openapi(adminBulkMcpTokensRoute, async (c) => {
1450
- if (!deps.jwtSecretManager || !deps.daemonConfig || !deps.dataDir) {
1451
- throw new WAIaaSError('ADAPTER_NOT_AVAILABLE', { message: 'JWT signing not available' });
1452
- }
1453
- const { walletIds, ttl: reqTtl } = c.req.valid('json');
1454
- const config = deps.daemonConfig;
1455
- const nowSec = Math.floor(Date.now() / 1000);
1456
- const ttl = reqTtl ?? config.security.session_ttl;
1457
- const expiresAt = nowSec + ttl;
1458
- const absoluteExpiresAt = nowSec + config.security.session_absolute_lifetime;
1459
- const baseUrl = `http://127.0.0.1:${config.daemon.port}`;
1460
- const results = [];
1461
- const claudeDesktopConfig = {};
1462
- const toSlug = (name) => {
1463
- const slug = name.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-{2,}/g, '-').replace(/^-|-$/g, '');
1464
- return slug || 'wallet';
1465
- };
1466
- for (const walletId of walletIds) {
1467
- try {
1468
- const wallet = deps.db.select().from(wallets).where(eq(wallets.id, walletId)).get();
1469
- if (!wallet) {
1470
- results.push({ walletId, walletName: null, error: 'Wallet not found' });
1471
- continue;
1472
- }
1473
- if (wallet.status === 'TERMINATED') {
1474
- results.push({ walletId, walletName: wallet.name, error: 'Wallet terminated' });
1475
- continue;
1476
- }
1477
- const sessionId = generateId();
1478
- const jwtPayload = { sub: sessionId, wlt: walletId, iat: nowSec, exp: expiresAt };
1479
- const token = await deps.jwtSecretManager.signToken(jwtPayload);
1480
- const tokenHash = createHash('sha256').update(token).digest('hex');
1481
- deps.db.insert(sessions).values({
1482
- id: sessionId, tokenHash,
1483
- expiresAt: new Date(expiresAt * 1000),
1484
- absoluteExpiresAt: new Date(absoluteExpiresAt * 1000),
1485
- createdAt: new Date(nowSec * 1000),
1486
- renewalCount: 0, maxRenewals: config.security.session_max_renewals,
1487
- constraints: null, source: 'mcp',
1488
- }).run();
1489
- deps.db.insert(sessionWallets).values({
1490
- sessionId, walletId, isDefault: true, createdAt: new Date(nowSec * 1000),
1491
- }).run();
1492
- // Write token file
1493
- const tokenPath = join(deps.dataDir, 'mcp-tokens', walletId);
1494
- const tmpPath = `${tokenPath}.tmp`;
1495
- await mkdir(dirname(tokenPath), { recursive: true });
1496
- await writeFile(tmpPath, token, 'utf-8');
1497
- await rename(tmpPath, tokenPath);
1498
- // Claude Desktop config snippet
1499
- const slug = toSlug(wallet.name);
1500
- claudeDesktopConfig[`waiaas-${slug}`] = {
1501
- command: 'npx',
1502
- args: ['@waiaas/mcp'],
1503
- env: {
1504
- WAIAAS_DATA_DIR: deps.dataDir,
1505
- WAIAAS_BASE_URL: baseUrl,
1506
- WAIAAS_WALLET_ID: walletId,
1507
- WAIAAS_WALLET_NAME: wallet.name,
1508
- },
1509
- };
1510
- void deps.notificationService?.notify('SESSION_CREATED', walletId, { sessionId });
1511
- results.push({ walletId, walletName: wallet.name, sessionId, tokenPath });
1512
- }
1513
- catch (err) {
1514
- const msg = err instanceof Error ? err.message : String(err);
1515
- results.push({ walletId, walletName: null, error: msg });
1516
- }
1517
- }
1518
- const created = results.filter((r) => !r.error).length;
1519
- return c.json({ results, created, failed: results.length - created, claudeDesktopConfig }, 200);
1520
- });
1521
- // ---------------------------------------------------------------------------
1522
1318
  // POST /admin/agent-prompt — Generate agent connection prompt
1523
1319
  // ---------------------------------------------------------------------------
1524
1320
  const agentPromptRoute = createRoute({
@@ -1566,36 +1362,83 @@ export function adminRoutes(deps) {
1566
1362
  .map((w) => ({ id: w.id, name: w.name, chain: w.chain, environment: w.environment, publicKey: w.publicKey, defaultNetwork: w.defaultNetwork }));
1567
1363
  }
1568
1364
  if (targetWallets.length === 0) {
1569
- return c.json({ prompt: '', walletCount: 0, sessionsCreated: 0, expiresAt }, 201);
1365
+ return c.json({ prompt: '', walletCount: 0, sessionsCreated: 0, sessionReused: false, expiresAt }, 201);
1570
1366
  }
1571
- // Create a single multi-wallet session (one sessionId, one token)
1367
+ // Try to reuse an existing valid session covering all target wallets
1572
1368
  const defaultWallet = targetWallets[0];
1573
- const sessionId = generateId();
1574
- const jwtPayload = { sub: sessionId, wlt: defaultWallet.id, iat: nowSec, exp: expiresAt };
1575
- const token = await deps.jwtSecretManager.signToken(jwtPayload);
1576
- const tokenHash = createHash('sha256').update(token).digest('hex');
1577
- deps.db.insert(sessions).values({
1578
- id: sessionId,
1579
- tokenHash,
1580
- expiresAt: new Date(expiresAt * 1000),
1581
- absoluteExpiresAt: new Date(absoluteExpiresAt * 1000),
1582
- createdAt: new Date(nowSec * 1000),
1583
- renewalCount: 0,
1584
- maxRenewals: config.security.session_max_renewals,
1585
- constraints: null,
1586
- source: 'api',
1587
- }).run();
1588
- // Insert N rows into session_wallets (first wallet is default)
1589
- for (let i = 0; i < targetWallets.length; i++) {
1590
- const w = targetWallets[i];
1591
- deps.db.insert(sessionWallets).values({
1592
- sessionId,
1593
- walletId: w.id,
1594
- isDefault: i === 0,
1369
+ const targetWalletIds = targetWallets.map((w) => w.id);
1370
+ const minRemainingTtl = Math.max(Math.floor(ttl * 0.1), 3600); // 10% of TTL or 1 hour
1371
+ const minExpiresAt = new Date((nowSec + minRemainingTtl) * 1000);
1372
+ let sessionId;
1373
+ let sessionReused = false;
1374
+ let sessionsCreated = 1;
1375
+ let actualExpiresAt = expiresAt;
1376
+ // Find active sessions that cover all target wallets with sufficient TTL
1377
+ const candidateSessions = deps.db
1378
+ .select({
1379
+ id: sessions.id,
1380
+ expiresAt: sessions.expiresAt,
1381
+ })
1382
+ .from(sessions)
1383
+ .where(and(isNull(sessions.revokedAt), gt(sessions.expiresAt, minExpiresAt)))
1384
+ .all();
1385
+ let reusableSessionId = null;
1386
+ let reusableExpiresAt = 0;
1387
+ for (const candidate of candidateSessions) {
1388
+ // Count how many of our target wallets are linked to this session
1389
+ const linkedCount = deps.db
1390
+ .select({ cnt: drizzleCount() })
1391
+ .from(sessionWallets)
1392
+ .where(and(eq(sessionWallets.sessionId, candidate.id), sql `${sessionWallets.walletId} IN (${sql.join(targetWalletIds.map((id) => sql `${id}`), sql `, `)})`))
1393
+ .get();
1394
+ if (linkedCount && linkedCount.cnt === targetWalletIds.length) {
1395
+ reusableSessionId = candidate.id;
1396
+ reusableExpiresAt = candidate.expiresAt instanceof Date
1397
+ ? Math.floor(candidate.expiresAt.getTime() / 1000)
1398
+ : candidate.expiresAt;
1399
+ break;
1400
+ }
1401
+ }
1402
+ if (reusableSessionId) {
1403
+ // Reuse existing session — re-sign JWT with existing session ID
1404
+ sessionId = reusableSessionId;
1405
+ sessionReused = true;
1406
+ sessionsCreated = 0;
1407
+ actualExpiresAt = reusableExpiresAt;
1408
+ }
1409
+ else {
1410
+ // Create a new multi-wallet session
1411
+ sessionId = generateId();
1412
+ deps.db.insert(sessions).values({
1413
+ id: sessionId,
1414
+ tokenHash: '',
1415
+ expiresAt: new Date(expiresAt * 1000),
1416
+ absoluteExpiresAt: new Date(absoluteExpiresAt * 1000),
1595
1417
  createdAt: new Date(nowSec * 1000),
1418
+ renewalCount: 0,
1419
+ maxRenewals: config.security.session_max_renewals,
1420
+ constraints: null,
1421
+ source: 'api',
1596
1422
  }).run();
1423
+ // Insert N rows into session_wallets (first wallet is default)
1424
+ for (let i = 0; i < targetWallets.length; i++) {
1425
+ const w = targetWallets[i];
1426
+ deps.db.insert(sessionWallets).values({
1427
+ sessionId,
1428
+ walletId: w.id,
1429
+ isDefault: i === 0,
1430
+ createdAt: new Date(nowSec * 1000),
1431
+ }).run();
1432
+ }
1433
+ void deps.notificationService?.notify('SESSION_CREATED', defaultWallet.id, { sessionId });
1434
+ }
1435
+ // Sign JWT (new or re-signed for reused session)
1436
+ const jwtPayload = { sub: sessionId, wlt: defaultWallet.id, iat: nowSec, exp: actualExpiresAt };
1437
+ const token = await deps.jwtSecretManager.signToken(jwtPayload);
1438
+ if (!sessionReused) {
1439
+ const tokenHash = createHash('sha256').update(token).digest('hex');
1440
+ deps.db.update(sessions).set({ tokenHash }).where(eq(sessions.id, sessionId)).run();
1597
1441
  }
1598
- void deps.notificationService?.notify('SESSION_CREATED', defaultWallet.id, { sessionId });
1599
1442
  // Query per-wallet policies for prompt builder
1600
1443
  const promptWallets = targetWallets.map((w) => {
1601
1444
  const walletPolicies = deps.db
@@ -1603,12 +1446,15 @@ export function adminRoutes(deps) {
1603
1446
  .from(policies)
1604
1447
  .where(and(eq(policies.walletId, w.id), eq(policies.enabled, true)))
1605
1448
  .all();
1449
+ const networks = getNetworksForEnvironment(w.chain, w.environment);
1606
1450
  return {
1451
+ id: w.id,
1607
1452
  name: w.name,
1608
1453
  chain: w.chain,
1609
1454
  environment: w.environment,
1610
1455
  address: w.publicKey,
1611
1456
  defaultNetwork: w.defaultNetwork,
1457
+ networks: networks.map((n) => n),
1612
1458
  policies: walletPolicies,
1613
1459
  };
1614
1460
  });
@@ -1653,10 +1499,77 @@ export function adminRoutes(deps) {
1653
1499
  return c.json({
1654
1500
  prompt: fullPrompt,
1655
1501
  walletCount: targetWallets.length,
1656
- sessionsCreated: 1,
1657
- expiresAt,
1502
+ sessionsCreated,
1503
+ sessionReused,
1504
+ expiresAt: actualExpiresAt,
1658
1505
  }, 201);
1659
1506
  });
1507
+ // ---------------------------------------------------------------------------
1508
+ // POST /admin/sessions/:id/reissue — Reissue session token
1509
+ // ---------------------------------------------------------------------------
1510
+ const sessionReissueRoute = createRoute({
1511
+ method: 'post',
1512
+ path: '/admin/sessions/{id}/reissue',
1513
+ tags: ['Admin'],
1514
+ summary: 'Reissue session token (re-sign JWT for existing session)',
1515
+ request: {
1516
+ params: z.object({ id: z.string().uuid() }),
1517
+ },
1518
+ responses: {
1519
+ 200: {
1520
+ description: 'Token reissued',
1521
+ content: { 'application/json': { schema: SessionReissueResponseSchema } },
1522
+ },
1523
+ ...buildErrorResponses(['SESSION_NOT_FOUND', 'SESSION_REVOKED']),
1524
+ },
1525
+ });
1526
+ router.openapi(sessionReissueRoute, async (c) => {
1527
+ if (!deps.jwtSecretManager) {
1528
+ throw new WAIaaSError('ADAPTER_NOT_AVAILABLE', { message: 'JWT signing not available' });
1529
+ }
1530
+ const { id: sessionId } = c.req.valid('param');
1531
+ const nowSec = Math.floor(Date.now() / 1000);
1532
+ // Find session
1533
+ const session = deps.db
1534
+ .select()
1535
+ .from(sessions)
1536
+ .where(eq(sessions.id, sessionId))
1537
+ .get();
1538
+ if (!session) {
1539
+ throw new WAIaaSError('SESSION_NOT_FOUND');
1540
+ }
1541
+ if (session.revokedAt) {
1542
+ throw new WAIaaSError('SESSION_REVOKED');
1543
+ }
1544
+ const expiresAtSec = session.expiresAt instanceof Date
1545
+ ? Math.floor(session.expiresAt.getTime() / 1000)
1546
+ : session.expiresAt;
1547
+ if (expiresAtSec <= nowSec) {
1548
+ throw new WAIaaSError('SESSION_NOT_FOUND', { message: 'Session expired' });
1549
+ }
1550
+ // Get default wallet for JWT
1551
+ const defaultSw = deps.db
1552
+ .select({ walletId: sessionWallets.walletId })
1553
+ .from(sessionWallets)
1554
+ .where(and(eq(sessionWallets.sessionId, sessionId), eq(sessionWallets.isDefault, true)))
1555
+ .get();
1556
+ const defaultWalletId = defaultSw?.walletId ?? '';
1557
+ // Re-sign JWT
1558
+ const jwtPayload = { sub: sessionId, wlt: defaultWalletId, iat: nowSec, exp: expiresAtSec };
1559
+ const token = await deps.jwtSecretManager.signToken(jwtPayload);
1560
+ // Increment token_issued_count
1561
+ const newCount = (session.tokenIssuedCount ?? 1) + 1;
1562
+ deps.db.update(sessions)
1563
+ .set({ tokenIssuedCount: newCount })
1564
+ .where(eq(sessions.id, sessionId))
1565
+ .run();
1566
+ return c.json({
1567
+ token,
1568
+ sessionId,
1569
+ tokenIssuedCount: newCount,
1570
+ expiresAt: expiresAtSec,
1571
+ }, 200);
1572
+ });
1660
1573
  return router;
1661
1574
  }
1662
1575
  //# sourceMappingURL=admin.js.map