browser-debug-mcp-bridge 1.12.0 → 1.13.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/README.md +1 -1
- package/apps/mcp-server/dist/db/migrations.js +221 -1
- package/apps/mcp-server/dist/db/migrations.js.map +1 -1
- package/apps/mcp-server/dist/db/schema.js +167 -2
- package/apps/mcp-server/dist/db/schema.js.map +1 -1
- package/apps/mcp-server/dist/lighthouse-report.js +1001 -0
- package/apps/mcp-server/dist/lighthouse-report.js.map +1 -0
- package/apps/mcp-server/dist/main.js +249 -1
- package/apps/mcp-server/dist/main.js.map +1 -1
- package/apps/mcp-server/dist/mcp/server.js +793 -1
- package/apps/mcp-server/dist/mcp/server.js.map +1 -1
- package/apps/mcp-server/dist/mock-store.js +408 -0
- package/apps/mcp-server/dist/mock-store.js.map +1 -0
- package/apps/mcp-server/dist/override-audit-contract.js +58 -0
- package/apps/mcp-server/dist/override-audit-contract.js.map +1 -1
- package/apps/mcp-server/dist/override-audit.js +97 -1
- package/apps/mcp-server/dist/override-audit.js.map +1 -1
- package/apps/mcp-server/dist/ssr-mock.js +480 -0
- package/apps/mcp-server/dist/ssr-mock.js.map +1 -0
- package/apps/mcp-server/package.json +5 -0
- package/package.json +2 -2
|
@@ -7,7 +7,8 @@ import { dirname, resolve } from 'path';
|
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { WorkflowTargetResolutionError, hasSemanticActionTargetMatcher, resolveWorkflowActionTarget, summarizeWorkflowTargetMatcher, } from './target-resolution.js';
|
|
9
9
|
import { getConnection } from '../db/connection.js';
|
|
10
|
-
import { diagnoseOverridePoc, insertOverridePlanAudit, listOverridePlanAudits, listOverridePocRequests, listOverridePocRuns, } from '../override-audit.js';
|
|
10
|
+
import { diagnoseOverridePoc, insertOverridePlanAudit, insertSsrMockAudit, listOverridePlanAudits, listOverridePocRequests, listOverridePocRuns, listSsrMockAudits, } from '../override-audit.js';
|
|
11
|
+
import { deleteMockRoute, getMockRoute, listMockHits, listMockRoutes, listMockRuns, upsertMockRoute } from '../mock-store.js';
|
|
11
12
|
import { createOverrideProfileConfig, OVERRIDE_PROFILE_ADAPTERS, } from '../override-profile-generator.js';
|
|
12
13
|
import { assertOverrideResponseRequestCaptureSafe, classifyOverrideResponseRequestCapability, } from '../override-capabilities.js';
|
|
13
14
|
import { getOverridePocConfigSummary } from '../override-poc.js';
|
|
@@ -16,6 +17,8 @@ import { mapNextOverrideAssetsWithDrift } from '../next-asset-mapper.js';
|
|
|
16
17
|
import { planNextSourceOverride } from '../next-source-override-planner.js';
|
|
17
18
|
import { listObservedOverrideAssets, persistObservedOverrideAssets } from '../override-observed-assets.js';
|
|
18
19
|
import { planOverrideResponsePatch } from '../override-response-planner.js';
|
|
20
|
+
import { applySsrMockConfig, discoverSsrMockability, removeSsrMockConfig } from '../ssr-mock.js';
|
|
21
|
+
import { getLighthouseReport, getLighthouseReportAsset, listLighthouseReports, normalizeLighthouseAsset, planLighthouseFixes, runLighthouseReport, } from '../lighthouse-report.js';
|
|
19
22
|
import { createToolLoopGuard } from './tool-loop-guard.js';
|
|
20
23
|
function createDefaultMcpLogger() {
|
|
21
24
|
const write = (level, message, payload) => {
|
|
@@ -1440,6 +1443,145 @@ const TOOL_SCHEMAS = {
|
|
|
1440
1443
|
runId: { type: 'string' },
|
|
1441
1444
|
},
|
|
1442
1445
|
},
|
|
1446
|
+
discover_ssr_mockability: {
|
|
1447
|
+
type: 'object',
|
|
1448
|
+
required: ['projectRoot'],
|
|
1449
|
+
properties: {
|
|
1450
|
+
projectRoot: { type: 'string' },
|
|
1451
|
+
targetUrl: { type: 'string' },
|
|
1452
|
+
apiHost: { type: 'string' },
|
|
1453
|
+
maxFiles: { type: 'number' },
|
|
1454
|
+
},
|
|
1455
|
+
},
|
|
1456
|
+
apply_ssr_mock_config: {
|
|
1457
|
+
type: 'object',
|
|
1458
|
+
required: ['projectRoot', 'envVarName', 'mockBaseUrl'],
|
|
1459
|
+
properties: {
|
|
1460
|
+
projectRoot: { type: 'string' },
|
|
1461
|
+
envVarName: { type: 'string' },
|
|
1462
|
+
mockBaseUrl: { type: 'string' },
|
|
1463
|
+
envFilePath: { type: 'string' },
|
|
1464
|
+
rollbackId: { type: 'string' },
|
|
1465
|
+
},
|
|
1466
|
+
},
|
|
1467
|
+
remove_ssr_mock_config: {
|
|
1468
|
+
type: 'object',
|
|
1469
|
+
required: ['envFilePath', 'envVarName'],
|
|
1470
|
+
properties: {
|
|
1471
|
+
envFilePath: { type: 'string' },
|
|
1472
|
+
envVarName: { type: 'string' },
|
|
1473
|
+
rollbackId: { type: 'string' },
|
|
1474
|
+
},
|
|
1475
|
+
},
|
|
1476
|
+
get_ssr_mock_audit_log: {
|
|
1477
|
+
type: 'object',
|
|
1478
|
+
properties: {
|
|
1479
|
+
projectRoot: { type: 'string' },
|
|
1480
|
+
rollbackId: { type: 'string' },
|
|
1481
|
+
envVarName: { type: 'string' },
|
|
1482
|
+
limit: { type: 'number' },
|
|
1483
|
+
offset: { type: 'number' },
|
|
1484
|
+
maxResponseBytes: { type: 'number' },
|
|
1485
|
+
},
|
|
1486
|
+
},
|
|
1487
|
+
create_mock_route: {
|
|
1488
|
+
type: 'object',
|
|
1489
|
+
required: ['targetUrl'],
|
|
1490
|
+
properties: {
|
|
1491
|
+
routeId: { type: 'string' },
|
|
1492
|
+
enabled: { type: 'boolean' },
|
|
1493
|
+
mode: { type: 'string' },
|
|
1494
|
+
method: { type: 'string' },
|
|
1495
|
+
matchMode: { type: 'string' },
|
|
1496
|
+
targetUrl: { type: 'string' },
|
|
1497
|
+
statusCode: { type: 'number' },
|
|
1498
|
+
responseHeaders: { type: 'object' },
|
|
1499
|
+
bodyJson: {},
|
|
1500
|
+
bodyText: { type: 'string' },
|
|
1501
|
+
bodyBase64: { type: 'string' },
|
|
1502
|
+
bodyFilePath: { type: 'string' },
|
|
1503
|
+
delayMs: { type: 'number' },
|
|
1504
|
+
sourceKind: { type: 'string' },
|
|
1505
|
+
sessionScope: { type: 'string' },
|
|
1506
|
+
projectRoot: { type: 'string' },
|
|
1507
|
+
ttlMs: { type: 'number' },
|
|
1508
|
+
},
|
|
1509
|
+
},
|
|
1510
|
+
update_mock_route: {
|
|
1511
|
+
type: 'object',
|
|
1512
|
+
required: ['routeId'],
|
|
1513
|
+
properties: {
|
|
1514
|
+
routeId: { type: 'string' },
|
|
1515
|
+
enabled: { type: 'boolean' },
|
|
1516
|
+
mode: { type: 'string' },
|
|
1517
|
+
method: { type: 'string' },
|
|
1518
|
+
matchMode: { type: 'string' },
|
|
1519
|
+
targetUrl: { type: 'string' },
|
|
1520
|
+
statusCode: { type: 'number' },
|
|
1521
|
+
responseHeaders: { type: 'object' },
|
|
1522
|
+
bodyJson: {},
|
|
1523
|
+
bodyText: { type: 'string' },
|
|
1524
|
+
bodyBase64: { type: 'string' },
|
|
1525
|
+
bodyFilePath: { type: 'string' },
|
|
1526
|
+
delayMs: { type: 'number' },
|
|
1527
|
+
sourceKind: { type: 'string' },
|
|
1528
|
+
sessionScope: { type: 'string' },
|
|
1529
|
+
projectRoot: { type: 'string' },
|
|
1530
|
+
ttlMs: { type: 'number' },
|
|
1531
|
+
},
|
|
1532
|
+
},
|
|
1533
|
+
delete_mock_route: {
|
|
1534
|
+
type: 'object',
|
|
1535
|
+
required: ['routeId'],
|
|
1536
|
+
properties: {
|
|
1537
|
+
routeId: { type: 'string' },
|
|
1538
|
+
},
|
|
1539
|
+
},
|
|
1540
|
+
list_mock_routes: {
|
|
1541
|
+
type: 'object',
|
|
1542
|
+
properties: {
|
|
1543
|
+
projectRoot: { type: 'string' },
|
|
1544
|
+
mode: { type: 'string' },
|
|
1545
|
+
enabled: { type: 'boolean' },
|
|
1546
|
+
limit: { type: 'number' },
|
|
1547
|
+
offset: { type: 'number' },
|
|
1548
|
+
maxResponseBytes: { type: 'number' },
|
|
1549
|
+
},
|
|
1550
|
+
},
|
|
1551
|
+
get_mock_route: {
|
|
1552
|
+
type: 'object',
|
|
1553
|
+
required: ['routeId'],
|
|
1554
|
+
properties: {
|
|
1555
|
+
routeId: { type: 'string' },
|
|
1556
|
+
},
|
|
1557
|
+
},
|
|
1558
|
+
get_mock_run_log: {
|
|
1559
|
+
type: 'object',
|
|
1560
|
+
properties: {
|
|
1561
|
+
routeId: { type: 'string' },
|
|
1562
|
+
sessionId: { type: 'string' },
|
|
1563
|
+
limit: { type: 'number' },
|
|
1564
|
+
offset: { type: 'number' },
|
|
1565
|
+
maxResponseBytes: { type: 'number' },
|
|
1566
|
+
},
|
|
1567
|
+
},
|
|
1568
|
+
get_mock_hit_log: {
|
|
1569
|
+
type: 'object',
|
|
1570
|
+
properties: {
|
|
1571
|
+
routeId: { type: 'string' },
|
|
1572
|
+
runId: { type: 'string' },
|
|
1573
|
+
limit: { type: 'number' },
|
|
1574
|
+
offset: { type: 'number' },
|
|
1575
|
+
maxResponseBytes: { type: 'number' },
|
|
1576
|
+
},
|
|
1577
|
+
},
|
|
1578
|
+
get_mock_status: {
|
|
1579
|
+
type: 'object',
|
|
1580
|
+
properties: {
|
|
1581
|
+
routeId: { type: 'string' },
|
|
1582
|
+
projectRoot: { type: 'string' },
|
|
1583
|
+
},
|
|
1584
|
+
},
|
|
1443
1585
|
explain_last_failure: {
|
|
1444
1586
|
type: 'object',
|
|
1445
1587
|
required: ['sessionId'],
|
|
@@ -1491,6 +1633,60 @@ const TOOL_SCHEMAS = {
|
|
|
1491
1633
|
encoding: { type: 'string' },
|
|
1492
1634
|
},
|
|
1493
1635
|
},
|
|
1636
|
+
run_lighthouse_report: {
|
|
1637
|
+
type: 'object',
|
|
1638
|
+
properties: {
|
|
1639
|
+
sessionId: { type: 'string' },
|
|
1640
|
+
url: { type: 'string' },
|
|
1641
|
+
formFactor: { type: 'string', enum: ['mobile', 'desktop'] },
|
|
1642
|
+
categories: {
|
|
1643
|
+
type: 'array',
|
|
1644
|
+
items: { type: 'string', enum: ['performance', 'accessibility', 'best-practices', 'seo', 'pwa'] },
|
|
1645
|
+
},
|
|
1646
|
+
maxWaitForLoadMs: { type: 'number' },
|
|
1647
|
+
chromeFlags: { type: 'array', items: { type: 'string' } },
|
|
1648
|
+
},
|
|
1649
|
+
},
|
|
1650
|
+
list_lighthouse_reports: {
|
|
1651
|
+
type: 'object',
|
|
1652
|
+
properties: {
|
|
1653
|
+
sessionId: { type: 'string' },
|
|
1654
|
+
urlContains: { type: 'string' },
|
|
1655
|
+
status: { type: 'string', enum: ['succeeded', 'failed'] },
|
|
1656
|
+
limit: { type: 'number' },
|
|
1657
|
+
offset: { type: 'number' },
|
|
1658
|
+
},
|
|
1659
|
+
},
|
|
1660
|
+
get_lighthouse_report: {
|
|
1661
|
+
type: 'object',
|
|
1662
|
+
required: ['reportId'],
|
|
1663
|
+
properties: {
|
|
1664
|
+
reportId: { type: 'string' },
|
|
1665
|
+
},
|
|
1666
|
+
},
|
|
1667
|
+
get_lighthouse_report_asset: {
|
|
1668
|
+
type: 'object',
|
|
1669
|
+
required: ['reportId'],
|
|
1670
|
+
properties: {
|
|
1671
|
+
reportId: { type: 'string' },
|
|
1672
|
+
asset: { type: 'string', enum: ['json', 'html'] },
|
|
1673
|
+
offset: { type: 'number' },
|
|
1674
|
+
maxBytes: { type: 'number' },
|
|
1675
|
+
encoding: { type: 'string', enum: ['base64', 'raw'] },
|
|
1676
|
+
},
|
|
1677
|
+
},
|
|
1678
|
+
plan_lighthouse_fixes: {
|
|
1679
|
+
type: 'object',
|
|
1680
|
+
required: ['reportId'],
|
|
1681
|
+
properties: {
|
|
1682
|
+
reportId: { type: 'string' },
|
|
1683
|
+
minPriority: { type: 'string', enum: ['critical', 'high', 'medium', 'low'] },
|
|
1684
|
+
limit: { type: 'number' },
|
|
1685
|
+
projectRoot: { type: 'string' },
|
|
1686
|
+
routePath: { type: 'string' },
|
|
1687
|
+
sourceCandidateLimit: { type: 'number' },
|
|
1688
|
+
},
|
|
1689
|
+
},
|
|
1494
1690
|
list_automation_runs: {
|
|
1495
1691
|
type: 'object',
|
|
1496
1692
|
required: ['sessionId'],
|
|
@@ -1809,11 +2005,28 @@ const TOOL_DESCRIPTIONS = {
|
|
|
1809
2005
|
get_override_request_log: 'Read persisted browser override request audit rows',
|
|
1810
2006
|
get_override_plan_log: 'Read persisted generated override plan audit rows with previews, hashes, and rollback metadata',
|
|
1811
2007
|
diagnose_overrides: 'Diagnose persisted browser override runs and failure indicators',
|
|
2008
|
+
discover_ssr_mockability: 'Inspect a local project for env-driven or central-client SSR mock injection points',
|
|
2009
|
+
apply_ssr_mock_config: 'Apply a temporary SSR mock base URL by commenting the old env value and writing a managed replacement',
|
|
2010
|
+
remove_ssr_mock_config: 'Restore or remove a managed SSR mock env patch from a local env file',
|
|
2011
|
+
get_ssr_mock_audit_log: 'Read persisted SSR mock discovery and env patch audit rows',
|
|
2012
|
+
create_mock_route: 'Create or persist a reusable browser or SSR mock route',
|
|
2013
|
+
update_mock_route: 'Update an existing mock route definition',
|
|
2014
|
+
delete_mock_route: 'Delete a persisted mock route',
|
|
2015
|
+
list_mock_routes: 'List persisted mock route definitions',
|
|
2016
|
+
get_mock_route: 'Read one persisted mock route definition',
|
|
2017
|
+
get_mock_run_log: 'Read persisted mock route run records',
|
|
2018
|
+
get_mock_hit_log: 'Read persisted mock route hit records',
|
|
2019
|
+
get_mock_status: 'Summarize persisted mock route, run, and hit state',
|
|
1812
2020
|
explain_last_failure: 'Explain the latest failure timeline',
|
|
1813
2021
|
get_event_correlation: 'Correlate related events by window',
|
|
1814
2022
|
list_snapshots: 'List snapshot metadata by session/time/trigger',
|
|
1815
2023
|
get_snapshot_for_event: 'Find snapshot most related to an event',
|
|
1816
2024
|
get_snapshot_asset: 'Read bounded binary chunks for snapshot assets',
|
|
2025
|
+
run_lighthouse_report: 'Run an official Lighthouse report for a URL or session URL and persist JSON/HTML artifacts',
|
|
2026
|
+
list_lighthouse_reports: 'List persisted Lighthouse report metadata',
|
|
2027
|
+
get_lighthouse_report: 'Read one persisted Lighthouse report summary',
|
|
2028
|
+
get_lighthouse_report_asset: 'Read bounded chunks from a persisted Lighthouse JSON or HTML report artifact',
|
|
2029
|
+
plan_lighthouse_fixes: 'Create a prioritized fix plan from a persisted Lighthouse report',
|
|
1817
2030
|
list_automation_runs: 'List first-class automation runs from dedicated automation tables',
|
|
1818
2031
|
get_automation_run: 'Inspect one automation run with bounded step details',
|
|
1819
2032
|
execute_ui_action: 'Execute one live UI action in the current bound extension session',
|
|
@@ -1907,6 +2120,23 @@ function resolveMaxResponseBytes(value) {
|
|
|
1907
2120
|
}
|
|
1908
2121
|
return Math.min(floored, MAX_RESPONSE_BYTES);
|
|
1909
2122
|
}
|
|
2123
|
+
function createSsrMockAuditRecord(input) {
|
|
2124
|
+
return {
|
|
2125
|
+
auditId: randomUUID(),
|
|
2126
|
+
createdAt: Date.now(),
|
|
2127
|
+
action: input.action,
|
|
2128
|
+
status: input.status,
|
|
2129
|
+
projectRoot: input.projectRoot,
|
|
2130
|
+
targetUrl: input.targetUrl ?? null,
|
|
2131
|
+
apiHost: input.apiHost ?? null,
|
|
2132
|
+
envVarName: input.envVarName ?? null,
|
|
2133
|
+
envFilePath: input.envFilePath ?? null,
|
|
2134
|
+
mockBaseUrl: input.mockBaseUrl ?? null,
|
|
2135
|
+
rollbackId: input.rollbackId ?? null,
|
|
2136
|
+
summary: input.summary ?? null,
|
|
2137
|
+
result: input.result ?? null,
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
1910
2140
|
function estimateJsonBytes(value) {
|
|
1911
2141
|
return Buffer.byteLength(JSON.stringify(value), 'utf-8');
|
|
1912
2142
|
}
|
|
@@ -3163,6 +3393,94 @@ function normalizeOptionalString(value) {
|
|
|
3163
3393
|
const trimmed = value.trim();
|
|
3164
3394
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
3165
3395
|
}
|
|
3396
|
+
function normalizeMockRouteMode(value) {
|
|
3397
|
+
return value === 'ssr' || value === 'both' ? value : 'browser';
|
|
3398
|
+
}
|
|
3399
|
+
function normalizeMockRouteSourceKind(value) {
|
|
3400
|
+
return value === 'captured' || value === 'patched' ? value : 'manual';
|
|
3401
|
+
}
|
|
3402
|
+
function normalizeMockRouteBody(input) {
|
|
3403
|
+
if (Object.prototype.hasOwnProperty.call(input, 'bodyJson')) {
|
|
3404
|
+
return {
|
|
3405
|
+
bodyKind: 'json',
|
|
3406
|
+
bodyJson: input.bodyJson,
|
|
3407
|
+
};
|
|
3408
|
+
}
|
|
3409
|
+
const bodyText = normalizeOptionalString(input.bodyText);
|
|
3410
|
+
if (bodyText !== undefined) {
|
|
3411
|
+
return {
|
|
3412
|
+
bodyKind: 'text',
|
|
3413
|
+
bodyText,
|
|
3414
|
+
};
|
|
3415
|
+
}
|
|
3416
|
+
const bodyBase64 = normalizeOptionalString(input.bodyBase64);
|
|
3417
|
+
if (bodyBase64 !== undefined) {
|
|
3418
|
+
return {
|
|
3419
|
+
bodyKind: 'base64',
|
|
3420
|
+
bodyBase64,
|
|
3421
|
+
};
|
|
3422
|
+
}
|
|
3423
|
+
const bodyFilePath = normalizeOptionalString(input.bodyFilePath);
|
|
3424
|
+
if (bodyFilePath !== undefined) {
|
|
3425
|
+
return {
|
|
3426
|
+
bodyKind: 'file',
|
|
3427
|
+
bodyFilePath,
|
|
3428
|
+
};
|
|
3429
|
+
}
|
|
3430
|
+
return null;
|
|
3431
|
+
}
|
|
3432
|
+
function createMockRouteRecord(input, existing) {
|
|
3433
|
+
const now = Date.now();
|
|
3434
|
+
const targetUrl = normalizeOptionalString(input.targetUrl) ?? existing?.targetUrl;
|
|
3435
|
+
if (!targetUrl) {
|
|
3436
|
+
throw new Error('targetUrl is required');
|
|
3437
|
+
}
|
|
3438
|
+
const body = normalizeMockRouteBody(input);
|
|
3439
|
+
const ttlMs = typeof input.ttlMs === 'number' && Number.isFinite(input.ttlMs) && input.ttlMs > 0
|
|
3440
|
+
? Math.floor(input.ttlMs)
|
|
3441
|
+
: existing?.ttlMs ?? null;
|
|
3442
|
+
return {
|
|
3443
|
+
routeId: normalizeOptionalString(input.routeId) ?? existing?.routeId ?? randomUUID(),
|
|
3444
|
+
createdAt: existing?.createdAt ?? now,
|
|
3445
|
+
updatedAt: now,
|
|
3446
|
+
enabled: typeof input.enabled === 'boolean' ? input.enabled : existing?.enabled ?? false,
|
|
3447
|
+
mode: normalizeMockRouteMode(input.mode ?? existing?.mode),
|
|
3448
|
+
method: normalizeHttpMethod(input.method) ?? existing?.method ?? 'GET',
|
|
3449
|
+
matchMode: input.matchMode === 'prefix' ? 'prefix' : existing?.matchMode ?? 'exact',
|
|
3450
|
+
targetUrl,
|
|
3451
|
+
statusCode: typeof input.statusCode === 'number' && Number.isFinite(input.statusCode)
|
|
3452
|
+
? Math.max(100, Math.min(599, Math.floor(input.statusCode)))
|
|
3453
|
+
: existing?.statusCode ?? 200,
|
|
3454
|
+
responseHeaders: Object.keys(normalizeMockHeaders(input.responseHeaders)).length > 0
|
|
3455
|
+
? normalizeMockHeaders(input.responseHeaders)
|
|
3456
|
+
: existing?.responseHeaders ?? {},
|
|
3457
|
+
bodyKind: body?.bodyKind ?? existing?.bodyKind ?? 'json',
|
|
3458
|
+
bodyJson: body?.bodyJson ?? existing?.bodyJson,
|
|
3459
|
+
bodyText: body?.bodyText ?? existing?.bodyText ?? null,
|
|
3460
|
+
bodyBase64: body?.bodyBase64 ?? existing?.bodyBase64 ?? null,
|
|
3461
|
+
bodyFilePath: body?.bodyFilePath ?? existing?.bodyFilePath ?? null,
|
|
3462
|
+
delayMs: typeof input.delayMs === 'number' && Number.isFinite(input.delayMs) && input.delayMs >= 0
|
|
3463
|
+
? Math.floor(input.delayMs)
|
|
3464
|
+
: existing?.delayMs ?? 0,
|
|
3465
|
+
sourceKind: normalizeMockRouteSourceKind(input.sourceKind ?? existing?.sourceKind),
|
|
3466
|
+
sessionScope: normalizeOptionalString(input.sessionScope) ?? existing?.sessionScope ?? null,
|
|
3467
|
+
projectRoot: normalizeOptionalString(input.projectRoot) ?? existing?.projectRoot ?? null,
|
|
3468
|
+
ttlMs,
|
|
3469
|
+
expiresAt: ttlMs !== null ? now + ttlMs : null,
|
|
3470
|
+
};
|
|
3471
|
+
}
|
|
3472
|
+
function normalizeMockHeaders(value) {
|
|
3473
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
3474
|
+
return {};
|
|
3475
|
+
}
|
|
3476
|
+
const headers = {};
|
|
3477
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
3478
|
+
if (typeof raw === 'string') {
|
|
3479
|
+
headers[key.toLowerCase()] = raw;
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
return headers;
|
|
3483
|
+
}
|
|
3166
3484
|
function normalizeStatusIn(value) {
|
|
3167
3485
|
if (!Array.isArray(value)) {
|
|
3168
3486
|
return [];
|
|
@@ -6521,6 +6839,373 @@ export function createV1ToolHandlers(getDb, getSessionConnectionState) {
|
|
|
6521
6839
|
: [{ code: 'NO_DIAGNOSIS_ISSUES', message: 'No diagnosis issues were found for the selected override run.' }],
|
|
6522
6840
|
};
|
|
6523
6841
|
},
|
|
6842
|
+
discover_ssr_mockability: async (input) => {
|
|
6843
|
+
const db = getDb();
|
|
6844
|
+
const projectRoot = normalizeOptionalString(input.projectRoot);
|
|
6845
|
+
if (!projectRoot) {
|
|
6846
|
+
throw new Error('projectRoot is required');
|
|
6847
|
+
}
|
|
6848
|
+
const discovery = discoverSsrMockability({
|
|
6849
|
+
projectRoot,
|
|
6850
|
+
targetUrl: normalizeOptionalString(input.targetUrl),
|
|
6851
|
+
apiHost: normalizeOptionalString(input.apiHost),
|
|
6852
|
+
maxFiles: typeof input.maxFiles === 'number' ? input.maxFiles : undefined,
|
|
6853
|
+
});
|
|
6854
|
+
const auditRecord = createSsrMockAuditRecord({
|
|
6855
|
+
action: 'discover',
|
|
6856
|
+
status: discovery.mockable ? 'succeeded' : 'not_mockable',
|
|
6857
|
+
projectRoot: discovery.projectRoot,
|
|
6858
|
+
targetUrl: discovery.targetUrl,
|
|
6859
|
+
apiHost: discovery.apiHost,
|
|
6860
|
+
envVarName: discovery.preferredEnvVarName,
|
|
6861
|
+
envFilePath: discovery.preferredEnvFilePath,
|
|
6862
|
+
summary: {
|
|
6863
|
+
classification: discovery.classification,
|
|
6864
|
+
scannedFileCount: discovery.scannedFileCount,
|
|
6865
|
+
candidateCount: discovery.candidates.length,
|
|
6866
|
+
},
|
|
6867
|
+
result: discovery,
|
|
6868
|
+
});
|
|
6869
|
+
insertSsrMockAudit(db, auditRecord);
|
|
6870
|
+
return {
|
|
6871
|
+
...createBaseResponse(),
|
|
6872
|
+
limitsApplied: {
|
|
6873
|
+
maxResults: discovery.candidates.length,
|
|
6874
|
+
truncated: false,
|
|
6875
|
+
},
|
|
6876
|
+
audit: auditRecord,
|
|
6877
|
+
...discovery,
|
|
6878
|
+
};
|
|
6879
|
+
},
|
|
6880
|
+
apply_ssr_mock_config: async (input) => {
|
|
6881
|
+
const db = getDb();
|
|
6882
|
+
const projectRoot = normalizeOptionalString(input.projectRoot);
|
|
6883
|
+
if (!projectRoot) {
|
|
6884
|
+
throw new Error('projectRoot is required');
|
|
6885
|
+
}
|
|
6886
|
+
const envVarName = normalizeOptionalString(input.envVarName);
|
|
6887
|
+
if (!envVarName) {
|
|
6888
|
+
throw new Error('envVarName is required');
|
|
6889
|
+
}
|
|
6890
|
+
const mockBaseUrl = normalizeOptionalString(input.mockBaseUrl);
|
|
6891
|
+
if (!mockBaseUrl) {
|
|
6892
|
+
throw new Error('mockBaseUrl is required');
|
|
6893
|
+
}
|
|
6894
|
+
const applied = applySsrMockConfig({
|
|
6895
|
+
projectRoot,
|
|
6896
|
+
envVarName,
|
|
6897
|
+
mockBaseUrl,
|
|
6898
|
+
envFilePath: normalizeOptionalString(input.envFilePath),
|
|
6899
|
+
rollbackId: normalizeOptionalString(input.rollbackId),
|
|
6900
|
+
});
|
|
6901
|
+
const auditRecord = createSsrMockAuditRecord({
|
|
6902
|
+
action: 'apply-config',
|
|
6903
|
+
status: applied.changed ? 'succeeded' : 'no_change',
|
|
6904
|
+
projectRoot,
|
|
6905
|
+
envVarName,
|
|
6906
|
+
envFilePath: applied.envFilePath,
|
|
6907
|
+
mockBaseUrl: applied.mockBaseUrl,
|
|
6908
|
+
rollbackId: applied.rollbackId,
|
|
6909
|
+
summary: {
|
|
6910
|
+
mode: applied.mode,
|
|
6911
|
+
createdFile: applied.createdFile,
|
|
6912
|
+
changed: applied.changed,
|
|
6913
|
+
},
|
|
6914
|
+
result: applied,
|
|
6915
|
+
});
|
|
6916
|
+
insertSsrMockAudit(db, auditRecord);
|
|
6917
|
+
return {
|
|
6918
|
+
...createBaseResponse(),
|
|
6919
|
+
limitsApplied: {
|
|
6920
|
+
maxResults: 1,
|
|
6921
|
+
truncated: false,
|
|
6922
|
+
},
|
|
6923
|
+
audit: auditRecord,
|
|
6924
|
+
...applied,
|
|
6925
|
+
nextActions: [
|
|
6926
|
+
{ code: 'RESTART_APP_SERVER', message: 'Restart the SSR app server so the env change takes effect.' },
|
|
6927
|
+
{ code: 'REMOVE_SSR_MOCK_CONFIG', message: 'Run remove_ssr_mock_config when the mock session is finished.' },
|
|
6928
|
+
],
|
|
6929
|
+
};
|
|
6930
|
+
},
|
|
6931
|
+
remove_ssr_mock_config: async (input) => {
|
|
6932
|
+
const db = getDb();
|
|
6933
|
+
const envFilePath = normalizeOptionalString(input.envFilePath);
|
|
6934
|
+
if (!envFilePath) {
|
|
6935
|
+
throw new Error('envFilePath is required');
|
|
6936
|
+
}
|
|
6937
|
+
const envVarName = normalizeOptionalString(input.envVarName);
|
|
6938
|
+
if (!envVarName) {
|
|
6939
|
+
throw new Error('envVarName is required');
|
|
6940
|
+
}
|
|
6941
|
+
const removed = removeSsrMockConfig({
|
|
6942
|
+
envFilePath,
|
|
6943
|
+
envVarName,
|
|
6944
|
+
rollbackId: normalizeOptionalString(input.rollbackId),
|
|
6945
|
+
});
|
|
6946
|
+
const auditRecord = createSsrMockAuditRecord({
|
|
6947
|
+
action: 'remove-config',
|
|
6948
|
+
status: removed.restored ? 'succeeded' : 'not_found',
|
|
6949
|
+
projectRoot: envFilePath,
|
|
6950
|
+
envVarName,
|
|
6951
|
+
envFilePath: removed.envFilePath,
|
|
6952
|
+
rollbackId: removed.rollbackId,
|
|
6953
|
+
summary: {
|
|
6954
|
+
mode: removed.mode,
|
|
6955
|
+
restored: removed.restored,
|
|
6956
|
+
},
|
|
6957
|
+
result: removed,
|
|
6958
|
+
});
|
|
6959
|
+
insertSsrMockAudit(db, auditRecord);
|
|
6960
|
+
return {
|
|
6961
|
+
...createBaseResponse(),
|
|
6962
|
+
limitsApplied: {
|
|
6963
|
+
maxResults: 1,
|
|
6964
|
+
truncated: false,
|
|
6965
|
+
},
|
|
6966
|
+
audit: auditRecord,
|
|
6967
|
+
...removed,
|
|
6968
|
+
nextActions: removed.restored
|
|
6969
|
+
? [{ code: 'RESTART_APP_SERVER', message: 'Restart the SSR app server so the restored env value takes effect.' }]
|
|
6970
|
+
: [{ code: 'INSPECT_ENV_FILE', message: 'No managed SSR mock patch was found for this env var.' }],
|
|
6971
|
+
};
|
|
6972
|
+
},
|
|
6973
|
+
get_ssr_mock_audit_log: async (input) => {
|
|
6974
|
+
const db = getDb();
|
|
6975
|
+
const limit = resolveLimit(input.limit, DEFAULT_EVENT_LIMIT);
|
|
6976
|
+
const offset = resolveOffset(input.offset);
|
|
6977
|
+
const maxResponseBytes = resolveMaxResponseBytes(input.maxResponseBytes);
|
|
6978
|
+
const projectRoot = normalizeOptionalString(input.projectRoot);
|
|
6979
|
+
const rollbackId = normalizeOptionalString(input.rollbackId);
|
|
6980
|
+
const envVarName = normalizeOptionalString(input.envVarName);
|
|
6981
|
+
const result = listSsrMockAudits(db, {
|
|
6982
|
+
projectRoot,
|
|
6983
|
+
rollbackId,
|
|
6984
|
+
envVarName,
|
|
6985
|
+
limit,
|
|
6986
|
+
offset,
|
|
6987
|
+
});
|
|
6988
|
+
const bytePage = applyByteBudget(result.audits, maxResponseBytes);
|
|
6989
|
+
const truncated = result.hasMore || bytePage.truncatedByBytes;
|
|
6990
|
+
return {
|
|
6991
|
+
...createBaseResponse(),
|
|
6992
|
+
limitsApplied: {
|
|
6993
|
+
maxResults: limit,
|
|
6994
|
+
truncated,
|
|
6995
|
+
},
|
|
6996
|
+
projectRoot: projectRoot ?? null,
|
|
6997
|
+
rollbackId: rollbackId ?? null,
|
|
6998
|
+
envVarName: envVarName ?? null,
|
|
6999
|
+
pagination: buildOffsetPagination(offset, bytePage.items.length, truncated, maxResponseBytes),
|
|
7000
|
+
responseBytes: bytePage.responseBytes,
|
|
7001
|
+
audits: bytePage.items,
|
|
7002
|
+
nextActions: bytePage.items.length === 0
|
|
7003
|
+
? [{ code: 'DISCOVER_SSR_MOCKABILITY', message: 'Run discover_ssr_mockability or apply_ssr_mock_config to create SSR mock audit rows.' }]
|
|
7004
|
+
: [{ code: 'REVIEW_SSR_MOCK_CLEANUP', message: 'Review the latest audit rows to confirm rollback ids and env file cleanup state.' }],
|
|
7005
|
+
};
|
|
7006
|
+
},
|
|
7007
|
+
create_mock_route: async (input) => {
|
|
7008
|
+
const db = getDb();
|
|
7009
|
+
const record = createMockRouteRecord(input);
|
|
7010
|
+
upsertMockRoute(db, record);
|
|
7011
|
+
const ssrScope = record.sessionScope ?? record.routeId;
|
|
7012
|
+
const ssrMockBasePath = record.mode === 'ssr' || record.mode === 'both'
|
|
7013
|
+
? `/mock/ssr/${encodeURIComponent(ssrScope)}`
|
|
7014
|
+
: undefined;
|
|
7015
|
+
return {
|
|
7016
|
+
...createBaseResponse(),
|
|
7017
|
+
limitsApplied: {
|
|
7018
|
+
maxResults: 1,
|
|
7019
|
+
truncated: false,
|
|
7020
|
+
},
|
|
7021
|
+
route: record,
|
|
7022
|
+
ssrMockBasePath,
|
|
7023
|
+
nextActions: [record.enabled
|
|
7024
|
+
? {
|
|
7025
|
+
code: record.mode === 'browser' ? 'ENABLE_BROWSER_MOCKS' : 'APPLY_SSR_MOCK_CONFIG',
|
|
7026
|
+
message: record.mode === 'browser'
|
|
7027
|
+
? 'Enable browser overrides so matching requests are fulfilled.'
|
|
7028
|
+
: record.mode === 'ssr'
|
|
7029
|
+
? `Point the discovered SSR env var at ${ssrMockBasePath ?? '/mock/ssr/<scope>'}.`
|
|
7030
|
+
: `Use browser overrides or point the discovered SSR env var at ${ssrMockBasePath ?? '/mock/ssr/<scope>'}.`,
|
|
7031
|
+
}
|
|
7032
|
+
: {
|
|
7033
|
+
code: 'ENABLE_MOCK_ROUTE',
|
|
7034
|
+
message: 'Set enabled=true when the route should start affecting browser or SSR traffic.',
|
|
7035
|
+
}],
|
|
7036
|
+
};
|
|
7037
|
+
},
|
|
7038
|
+
update_mock_route: async (input) => {
|
|
7039
|
+
const db = getDb();
|
|
7040
|
+
const routeId = normalizeOptionalString(input.routeId);
|
|
7041
|
+
if (!routeId) {
|
|
7042
|
+
throw new Error('routeId is required');
|
|
7043
|
+
}
|
|
7044
|
+
const existing = getMockRoute(db, routeId);
|
|
7045
|
+
if (!existing) {
|
|
7046
|
+
throw new Error(`Unknown mock route: ${routeId}`);
|
|
7047
|
+
}
|
|
7048
|
+
const record = createMockRouteRecord(input, existing);
|
|
7049
|
+
upsertMockRoute(db, record);
|
|
7050
|
+
return {
|
|
7051
|
+
...createBaseResponse(),
|
|
7052
|
+
limitsApplied: {
|
|
7053
|
+
maxResults: 1,
|
|
7054
|
+
truncated: false,
|
|
7055
|
+
},
|
|
7056
|
+
route: record,
|
|
7057
|
+
nextActions: [{ code: 'REVIEW_MOCK_STATUS', message: 'Review route mode, target URL, and body payload before running the mock.' }],
|
|
7058
|
+
};
|
|
7059
|
+
},
|
|
7060
|
+
delete_mock_route: async (input) => {
|
|
7061
|
+
const db = getDb();
|
|
7062
|
+
const routeId = normalizeOptionalString(input.routeId);
|
|
7063
|
+
if (!routeId) {
|
|
7064
|
+
throw new Error('routeId is required');
|
|
7065
|
+
}
|
|
7066
|
+
const deleted = deleteMockRoute(db, routeId);
|
|
7067
|
+
return {
|
|
7068
|
+
...createBaseResponse(),
|
|
7069
|
+
limitsApplied: {
|
|
7070
|
+
maxResults: 1,
|
|
7071
|
+
truncated: false,
|
|
7072
|
+
},
|
|
7073
|
+
routeId,
|
|
7074
|
+
deleted,
|
|
7075
|
+
nextActions: deleted
|
|
7076
|
+
? [{ code: 'CONFIRM_CLEANUP', message: 'If a runtime was using this route, confirm no active mock run still references it.' }]
|
|
7077
|
+
: [{ code: 'LIST_MOCK_ROUTES', message: 'The route was not found; list persisted mock routes to confirm the intended routeId.' }],
|
|
7078
|
+
};
|
|
7079
|
+
},
|
|
7080
|
+
list_mock_routes: async (input) => {
|
|
7081
|
+
const db = getDb();
|
|
7082
|
+
const limit = resolveLimit(input.limit, DEFAULT_EVENT_LIMIT);
|
|
7083
|
+
const offset = resolveOffset(input.offset);
|
|
7084
|
+
const maxResponseBytes = resolveMaxResponseBytes(input.maxResponseBytes);
|
|
7085
|
+
const result = listMockRoutes(db, {
|
|
7086
|
+
projectRoot: normalizeOptionalString(input.projectRoot),
|
|
7087
|
+
mode: normalizeOptionalString(input.mode) === 'ssr' || normalizeOptionalString(input.mode) === 'both'
|
|
7088
|
+
? normalizeOptionalString(input.mode)
|
|
7089
|
+
: normalizeOptionalString(input.mode) === 'browser'
|
|
7090
|
+
? 'browser'
|
|
7091
|
+
: undefined,
|
|
7092
|
+
enabled: typeof input.enabled === 'boolean' ? input.enabled : undefined,
|
|
7093
|
+
limit,
|
|
7094
|
+
offset,
|
|
7095
|
+
});
|
|
7096
|
+
const bytePage = applyByteBudget(result.routes, maxResponseBytes);
|
|
7097
|
+
const truncated = result.hasMore || bytePage.truncatedByBytes;
|
|
7098
|
+
return {
|
|
7099
|
+
...createBaseResponse(),
|
|
7100
|
+
limitsApplied: {
|
|
7101
|
+
maxResults: limit,
|
|
7102
|
+
truncated,
|
|
7103
|
+
},
|
|
7104
|
+
pagination: buildOffsetPagination(offset, bytePage.items.length, truncated, maxResponseBytes),
|
|
7105
|
+
responseBytes: bytePage.responseBytes,
|
|
7106
|
+
routes: bytePage.items,
|
|
7107
|
+
nextActions: bytePage.items.length === 0
|
|
7108
|
+
? [{ code: 'CREATE_MOCK_ROUTE', message: 'Create a persisted mock route before enabling browser or SSR mocking.' }]
|
|
7109
|
+
: [{ code: 'GET_MOCK_STATUS', message: 'Inspect mock status and recent run or hit records for one route.' }],
|
|
7110
|
+
};
|
|
7111
|
+
},
|
|
7112
|
+
get_mock_route: async (input) => {
|
|
7113
|
+
const db = getDb();
|
|
7114
|
+
const routeId = normalizeOptionalString(input.routeId);
|
|
7115
|
+
if (!routeId) {
|
|
7116
|
+
throw new Error('routeId is required');
|
|
7117
|
+
}
|
|
7118
|
+
const route = getMockRoute(db, routeId);
|
|
7119
|
+
if (!route) {
|
|
7120
|
+
throw new Error(`Unknown mock route: ${routeId}`);
|
|
7121
|
+
}
|
|
7122
|
+
return {
|
|
7123
|
+
...createBaseResponse(),
|
|
7124
|
+
limitsApplied: {
|
|
7125
|
+
maxResults: 1,
|
|
7126
|
+
truncated: false,
|
|
7127
|
+
},
|
|
7128
|
+
route,
|
|
7129
|
+
nextActions: [{ code: 'GET_MOCK_STATUS', message: 'Inspect latest runs and hits before enabling or updating this route.' }],
|
|
7130
|
+
};
|
|
7131
|
+
},
|
|
7132
|
+
get_mock_run_log: async (input) => {
|
|
7133
|
+
const db = getDb();
|
|
7134
|
+
const limit = resolveLimit(input.limit, DEFAULT_EVENT_LIMIT);
|
|
7135
|
+
const offset = resolveOffset(input.offset);
|
|
7136
|
+
const maxResponseBytes = resolveMaxResponseBytes(input.maxResponseBytes);
|
|
7137
|
+
const result = listMockRuns(db, {
|
|
7138
|
+
routeId: normalizeOptionalString(input.routeId),
|
|
7139
|
+
sessionId: normalizeOptionalString(input.sessionId),
|
|
7140
|
+
limit,
|
|
7141
|
+
offset,
|
|
7142
|
+
});
|
|
7143
|
+
const bytePage = applyByteBudget(result.runs, maxResponseBytes);
|
|
7144
|
+
const truncated = result.hasMore || bytePage.truncatedByBytes;
|
|
7145
|
+
return {
|
|
7146
|
+
...createBaseResponse(),
|
|
7147
|
+
limitsApplied: {
|
|
7148
|
+
maxResults: limit,
|
|
7149
|
+
truncated,
|
|
7150
|
+
},
|
|
7151
|
+
pagination: buildOffsetPagination(offset, bytePage.items.length, truncated, maxResponseBytes),
|
|
7152
|
+
responseBytes: bytePage.responseBytes,
|
|
7153
|
+
runs: bytePage.items,
|
|
7154
|
+
nextActions: bytePage.items.length === 0
|
|
7155
|
+
? [{ code: 'ENABLE_BROWSER_MOCKS', message: 'No mock runs exist yet; wire a runtime flow that can create execution records.' }]
|
|
7156
|
+
: [{ code: 'GET_MOCK_HIT_LOG', message: 'Inspect matching hit records for the selected run or route.' }],
|
|
7157
|
+
};
|
|
7158
|
+
},
|
|
7159
|
+
get_mock_hit_log: async (input) => {
|
|
7160
|
+
const db = getDb();
|
|
7161
|
+
const limit = resolveLimit(input.limit, DEFAULT_EVENT_LIMIT);
|
|
7162
|
+
const offset = resolveOffset(input.offset);
|
|
7163
|
+
const maxResponseBytes = resolveMaxResponseBytes(input.maxResponseBytes);
|
|
7164
|
+
const result = listMockHits(db, {
|
|
7165
|
+
routeId: normalizeOptionalString(input.routeId),
|
|
7166
|
+
runId: normalizeOptionalString(input.runId),
|
|
7167
|
+
limit,
|
|
7168
|
+
offset,
|
|
7169
|
+
});
|
|
7170
|
+
const bytePage = applyByteBudget(result.hits, maxResponseBytes);
|
|
7171
|
+
const truncated = result.hasMore || bytePage.truncatedByBytes;
|
|
7172
|
+
return {
|
|
7173
|
+
...createBaseResponse(),
|
|
7174
|
+
limitsApplied: {
|
|
7175
|
+
maxResults: limit,
|
|
7176
|
+
truncated,
|
|
7177
|
+
},
|
|
7178
|
+
pagination: buildOffsetPagination(offset, bytePage.items.length, truncated, maxResponseBytes),
|
|
7179
|
+
responseBytes: bytePage.responseBytes,
|
|
7180
|
+
hits: bytePage.items,
|
|
7181
|
+
nextActions: bytePage.items.length === 0
|
|
7182
|
+
? [{ code: 'RUN_MOCK_ROUTE', message: 'No hit records exist yet; execute the route through a browser or SSR runtime.' }]
|
|
7183
|
+
: [{ code: 'DIAGNOSE_MOCK_ROUTE', message: 'Use hit results to verify whether the route matched and fulfilled as expected.' }],
|
|
7184
|
+
};
|
|
7185
|
+
},
|
|
7186
|
+
get_mock_status: async (input) => {
|
|
7187
|
+
const db = getDb();
|
|
7188
|
+
const routeId = normalizeOptionalString(input.routeId);
|
|
7189
|
+
const projectRoot = normalizeOptionalString(input.projectRoot);
|
|
7190
|
+
const route = routeId ? getMockRoute(db, routeId) : null;
|
|
7191
|
+
const recentRoutes = listMockRoutes(db, { projectRoot, limit: 5, offset: 0 }).routes;
|
|
7192
|
+
const recentRuns = listMockRuns(db, { routeId, limit: 5, offset: 0 }).runs;
|
|
7193
|
+
const recentHits = listMockHits(db, { routeId, limit: 5, offset: 0 }).hits;
|
|
7194
|
+
return {
|
|
7195
|
+
...createBaseResponse(),
|
|
7196
|
+
limitsApplied: {
|
|
7197
|
+
maxResults: 5,
|
|
7198
|
+
truncated: false,
|
|
7199
|
+
},
|
|
7200
|
+
route,
|
|
7201
|
+
recentRoutes,
|
|
7202
|
+
recentRuns,
|
|
7203
|
+
recentHits,
|
|
7204
|
+
nextActions: route
|
|
7205
|
+
? [{ code: 'EXECUTE_MOCK_ROUTE', message: 'Bind the persisted route to browser or SSR execution so runs and hits begin to accumulate.' }]
|
|
7206
|
+
: [{ code: 'CREATE_MOCK_ROUTE', message: 'Persist a mock route first, then inspect status again.' }],
|
|
7207
|
+
};
|
|
7208
|
+
},
|
|
6524
7209
|
get_recent_events: async (input) => {
|
|
6525
7210
|
const db = getDb();
|
|
6526
7211
|
const sessionId = getSessionId(input);
|
|
@@ -7691,6 +8376,113 @@ export function createV1ToolHandlers(getDb, getSessionConnectionState) {
|
|
|
7691
8376
|
chunkBase64: encoding === 'base64' ? chunkBuffer.toString('base64') : undefined,
|
|
7692
8377
|
};
|
|
7693
8378
|
},
|
|
8379
|
+
run_lighthouse_report: async (input) => {
|
|
8380
|
+
const db = getDb();
|
|
8381
|
+
const sessionId = getSessionId(input);
|
|
8382
|
+
const report = await runLighthouseReport(db, {
|
|
8383
|
+
sessionId,
|
|
8384
|
+
url: typeof input.url === 'string' ? input.url : undefined,
|
|
8385
|
+
formFactor: input.formFactor === 'desktop' ? 'desktop' : 'mobile',
|
|
8386
|
+
categories: Array.isArray(input.categories)
|
|
8387
|
+
? input.categories.filter((entry) => typeof entry === 'string')
|
|
8388
|
+
: undefined,
|
|
8389
|
+
maxWaitForLoadMs: typeof input.maxWaitForLoadMs === 'number' ? input.maxWaitForLoadMs : undefined,
|
|
8390
|
+
chromeFlags: Array.isArray(input.chromeFlags)
|
|
8391
|
+
? input.chromeFlags.filter((entry) => typeof entry === 'string')
|
|
8392
|
+
: undefined,
|
|
8393
|
+
});
|
|
8394
|
+
return {
|
|
8395
|
+
...createBaseResponse(sessionId),
|
|
8396
|
+
limitsApplied: {
|
|
8397
|
+
maxResults: 1,
|
|
8398
|
+
truncated: false,
|
|
8399
|
+
},
|
|
8400
|
+
report,
|
|
8401
|
+
};
|
|
8402
|
+
},
|
|
8403
|
+
list_lighthouse_reports: async (input) => {
|
|
8404
|
+
const db = getDb();
|
|
8405
|
+
const result = listLighthouseReports(db, {
|
|
8406
|
+
sessionId: getSessionId(input),
|
|
8407
|
+
urlContains: typeof input.urlContains === 'string' ? input.urlContains : undefined,
|
|
8408
|
+
status: typeof input.status === 'string' ? input.status : undefined,
|
|
8409
|
+
limit: typeof input.limit === 'number' ? input.limit : undefined,
|
|
8410
|
+
offset: typeof input.offset === 'number' ? input.offset : undefined,
|
|
8411
|
+
});
|
|
8412
|
+
return {
|
|
8413
|
+
...createBaseResponse(getSessionId(input)),
|
|
8414
|
+
limitsApplied: {
|
|
8415
|
+
maxResults: result.pagination.limit,
|
|
8416
|
+
truncated: result.pagination.hasMore,
|
|
8417
|
+
},
|
|
8418
|
+
pagination: result.pagination,
|
|
8419
|
+
reports: result.reports,
|
|
8420
|
+
};
|
|
8421
|
+
},
|
|
8422
|
+
get_lighthouse_report: async (input) => {
|
|
8423
|
+
const db = getDb();
|
|
8424
|
+
const reportId = typeof input.reportId === 'string' ? input.reportId : '';
|
|
8425
|
+
if (!reportId) {
|
|
8426
|
+
throw new Error('reportId is required');
|
|
8427
|
+
}
|
|
8428
|
+
const report = getLighthouseReport(db, reportId);
|
|
8429
|
+
return {
|
|
8430
|
+
...createBaseResponse(report.sessionId),
|
|
8431
|
+
limitsApplied: {
|
|
8432
|
+
maxResults: 1,
|
|
8433
|
+
truncated: false,
|
|
8434
|
+
},
|
|
8435
|
+
report,
|
|
8436
|
+
};
|
|
8437
|
+
},
|
|
8438
|
+
get_lighthouse_report_asset: async (input) => {
|
|
8439
|
+
const db = getDb();
|
|
8440
|
+
const reportId = typeof input.reportId === 'string' ? input.reportId : '';
|
|
8441
|
+
if (!reportId) {
|
|
8442
|
+
throw new Error('reportId is required');
|
|
8443
|
+
}
|
|
8444
|
+
const asset = normalizeLighthouseAsset(input.asset);
|
|
8445
|
+
const chunk = getLighthouseReportAsset(db, {
|
|
8446
|
+
reportId,
|
|
8447
|
+
asset,
|
|
8448
|
+
offset: typeof input.offset === 'number' ? input.offset : undefined,
|
|
8449
|
+
maxBytes: typeof input.maxBytes === 'number' ? input.maxBytes : undefined,
|
|
8450
|
+
encoding: input.encoding === 'raw' ? 'raw' : 'base64',
|
|
8451
|
+
});
|
|
8452
|
+
return {
|
|
8453
|
+
...createBaseResponse(),
|
|
8454
|
+
limitsApplied: {
|
|
8455
|
+
maxResults: typeof chunk.bytesReturned === 'number' ? chunk.bytesReturned : 0,
|
|
8456
|
+
truncated: chunk.hasMore === true,
|
|
8457
|
+
},
|
|
8458
|
+
...chunk,
|
|
8459
|
+
};
|
|
8460
|
+
},
|
|
8461
|
+
plan_lighthouse_fixes: async (input) => {
|
|
8462
|
+
const db = getDb();
|
|
8463
|
+
const reportId = typeof input.reportId === 'string' ? input.reportId : '';
|
|
8464
|
+
if (!reportId) {
|
|
8465
|
+
throw new Error('reportId is required');
|
|
8466
|
+
}
|
|
8467
|
+
const plan = planLighthouseFixes(db, {
|
|
8468
|
+
reportId,
|
|
8469
|
+
minPriority: input.minPriority === 'critical' || input.minPriority === 'high' || input.minPriority === 'medium' || input.minPriority === 'low'
|
|
8470
|
+
? input.minPriority
|
|
8471
|
+
: undefined,
|
|
8472
|
+
limit: typeof input.limit === 'number' ? input.limit : undefined,
|
|
8473
|
+
projectRoot: typeof input.projectRoot === 'string' ? input.projectRoot : undefined,
|
|
8474
|
+
routePath: typeof input.routePath === 'string' ? input.routePath : undefined,
|
|
8475
|
+
sourceCandidateLimit: typeof input.sourceCandidateLimit === 'number' ? input.sourceCandidateLimit : undefined,
|
|
8476
|
+
});
|
|
8477
|
+
return {
|
|
8478
|
+
...createBaseResponse(plan.sessionId),
|
|
8479
|
+
limitsApplied: {
|
|
8480
|
+
maxResults: plan.itemCount,
|
|
8481
|
+
truncated: false,
|
|
8482
|
+
},
|
|
8483
|
+
plan,
|
|
8484
|
+
};
|
|
8485
|
+
},
|
|
7694
8486
|
list_automation_runs: async (input) => {
|
|
7695
8487
|
const db = getDb();
|
|
7696
8488
|
const sessionId = getSessionId(input);
|