browser-debug-mcp-bridge 1.11.0 → 1.11.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.
- package/apps/mcp-server/dist/mcp/server.js +588 -92
- package/apps/mcp-server/dist/mcp/server.js.map +1 -1
- package/apps/mcp-server/dist/override-capabilities.js +22 -1
- package/apps/mcp-server/dist/override-capabilities.js.map +1 -1
- package/apps/mcp-server/dist/override-response-planner.js +6 -4
- package/apps/mcp-server/dist/override-response-planner.js.map +1 -1
- package/package.json +1 -1
|
@@ -649,6 +649,7 @@ const TOOL_SCHEMAS = {
|
|
|
649
649
|
captureMode: { type: 'string', enum: ['extension-fetch', 'cdp-response'] },
|
|
650
650
|
triggerReload: { type: 'boolean' },
|
|
651
651
|
matchMode: { type: 'string', enum: ['exact', 'prefix'] },
|
|
652
|
+
ruleType: { type: 'string' },
|
|
652
653
|
requestMethod: { type: 'string' },
|
|
653
654
|
requestHeaders: { type: 'object' },
|
|
654
655
|
timeoutMs: { type: 'number' },
|
|
@@ -1113,6 +1114,8 @@ const DEFAULT_NETWORK_POLL_TIMEOUT_MS = 15_000;
|
|
|
1113
1114
|
const MAX_NETWORK_POLL_TIMEOUT_MS = 120_000;
|
|
1114
1115
|
const DEFAULT_NETWORK_POLL_INTERVAL_MS = 250;
|
|
1115
1116
|
const LIVE_SESSION_DISCONNECTED_CODE = 'LIVE_SESSION_DISCONNECTED';
|
|
1117
|
+
const OVERRIDE_LIVE_COMMAND_TIMEOUT_CODE = 'OVERRIDE_LIVE_COMMAND_TIMEOUT';
|
|
1118
|
+
const OVERRIDE_LIVE_COMMAND_FAILED_CODE = 'OVERRIDE_LIVE_COMMAND_FAILED';
|
|
1116
1119
|
const STALE_LIVE_CONNECTION_GRACE_WINDOW_MS = 30 * 60 * 1000;
|
|
1117
1120
|
const NOISE_SESSION_HOST_PATTERNS = [
|
|
1118
1121
|
/(^|\.)adtrafficquality\.google$/i,
|
|
@@ -1422,6 +1425,29 @@ function isRecordWithRscFlightMetadata(value) {
|
|
|
1422
1425
|
&& value.source !== undefined
|
|
1423
1426
|
&& value.patchKind !== undefined;
|
|
1424
1427
|
}
|
|
1428
|
+
function normalizeRuleStringHeaders(value) {
|
|
1429
|
+
if (!isRecord(value)) {
|
|
1430
|
+
return undefined;
|
|
1431
|
+
}
|
|
1432
|
+
const headers = {};
|
|
1433
|
+
for (const [name, rawValue] of Object.entries(value)) {
|
|
1434
|
+
if (typeof rawValue !== 'string') {
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
const normalizedName = name.trim().toLowerCase();
|
|
1438
|
+
const normalizedValue = rawValue.trim();
|
|
1439
|
+
if (normalizedName.length > 0 && normalizedValue.length > 0) {
|
|
1440
|
+
headers[normalizedName] = normalizedValue;
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
1444
|
+
}
|
|
1445
|
+
function getRscFlightRuleRequestHeaders(rule) {
|
|
1446
|
+
return isRecord(rule.rscFlight) ? normalizeRuleStringHeaders(rule.rscFlight.requestHeaders) : undefined;
|
|
1447
|
+
}
|
|
1448
|
+
function getOverrideRuleRequestHeaders(rule) {
|
|
1449
|
+
return normalizeRuleStringHeaders(rule.requestHeaders) ?? getRscFlightRuleRequestHeaders(rule);
|
|
1450
|
+
}
|
|
1425
1451
|
function buildRscFlightRuleIssues(rule) {
|
|
1426
1452
|
const ruleId = String(rule.ruleId ?? 'unknown');
|
|
1427
1453
|
const issues = [];
|
|
@@ -1465,17 +1491,20 @@ function buildRscFlightRuleIssues(rule) {
|
|
|
1465
1491
|
}
|
|
1466
1492
|
}
|
|
1467
1493
|
}
|
|
1468
|
-
|
|
1494
|
+
const requestMethod = normalizeOverrideRequestMethod(rule.requestMethod);
|
|
1495
|
+
const requestHeaders = getRscFlightRuleRequestHeaders(rule);
|
|
1496
|
+
const isCapturedPostRscFlight = requestMethod === 'POST' && requestHeaders?.rsc === '1';
|
|
1497
|
+
if (requestMethod !== 'GET' && !isCapturedPostRscFlight) {
|
|
1469
1498
|
issues.push({
|
|
1470
1499
|
code: 'RSC_FLIGHT_METHOD_UNSUPPORTED',
|
|
1471
1500
|
severity: 'error',
|
|
1472
|
-
message: `Rule ${ruleId} RSC flight overrides only support GET requests.`,
|
|
1501
|
+
message: `Rule ${ruleId} RSC flight overrides only support GET requests or captured POST RSC response-stage patches.`,
|
|
1473
1502
|
});
|
|
1474
1503
|
}
|
|
1475
1504
|
const targetAssetUrl = typeof rule.targetAssetUrl === 'string' ? rule.targetAssetUrl : '';
|
|
1476
1505
|
try {
|
|
1477
1506
|
const parsed = new URL(targetAssetUrl);
|
|
1478
|
-
if (!parsed.searchParams.has('_rsc')) {
|
|
1507
|
+
if (requestMethod === 'GET' && !parsed.searchParams.has('_rsc')) {
|
|
1479
1508
|
issues.push({
|
|
1480
1509
|
code: 'RSC_FLIGHT_TARGET_INVALID',
|
|
1481
1510
|
severity: 'error',
|
|
@@ -1595,7 +1624,7 @@ function buildOverrideProfileIssues(profile) {
|
|
|
1595
1624
|
issues.push(...classifyOverrideResponseRequestCapability({
|
|
1596
1625
|
ruleId: rule.ruleId,
|
|
1597
1626
|
requestMethod: rule.requestMethod,
|
|
1598
|
-
requestHeaders: rule
|
|
1627
|
+
requestHeaders: getOverrideRuleRequestHeaders(rule),
|
|
1599
1628
|
ruleType: rule.ruleType,
|
|
1600
1629
|
}).issues.map((issue) => ({ ...issue })));
|
|
1601
1630
|
if (rule.ruleType === 'rsc-flight') {
|
|
@@ -1676,8 +1705,14 @@ function hasEnabledExperimentalRscFlightRule(profile) {
|
|
|
1676
1705
|
});
|
|
1677
1706
|
}
|
|
1678
1707
|
function canBypassPreflightForExperimentalRsc(profile, blockingCodes) {
|
|
1708
|
+
const allowedExperimentalBlockers = new Set([
|
|
1709
|
+
'UNSUPPORTED_RSC_FLIGHT_RULE',
|
|
1710
|
+
'NO_OBSERVED_ASSETS',
|
|
1711
|
+
'TARGET_ASSET_NOT_OBSERVED',
|
|
1712
|
+
]);
|
|
1679
1713
|
return blockingCodes.length > 0
|
|
1680
|
-
&& blockingCodes.
|
|
1714
|
+
&& blockingCodes.includes('UNSUPPORTED_RSC_FLIGHT_RULE')
|
|
1715
|
+
&& blockingCodes.every((code) => allowedExperimentalBlockers.has(code))
|
|
1681
1716
|
&& hasEnabledExperimentalRscFlightRule(profile);
|
|
1682
1717
|
}
|
|
1683
1718
|
const OVERRIDE_VARIANT_HEADER_ALLOWLIST = new Set([
|
|
@@ -1797,6 +1832,36 @@ function pushOverridePreflightIssue(issues, issue) {
|
|
|
1797
1832
|
}
|
|
1798
1833
|
issues.push(issue);
|
|
1799
1834
|
}
|
|
1835
|
+
function getPreflightIssues(preflight) {
|
|
1836
|
+
return Array.isArray(preflight?.issues)
|
|
1837
|
+
? preflight.issues.filter((issue) => isRecord(issue))
|
|
1838
|
+
: [];
|
|
1839
|
+
}
|
|
1840
|
+
function getBlockingPreflightCodes(preflight) {
|
|
1841
|
+
return getPreflightIssues(preflight)
|
|
1842
|
+
.filter((issue) => issue.severity === 'error')
|
|
1843
|
+
.map((issue) => String(issue.code ?? 'UNKNOWN'));
|
|
1844
|
+
}
|
|
1845
|
+
function hasPreflightIssue(preflight, codes) {
|
|
1846
|
+
const expected = new Set(codes);
|
|
1847
|
+
return getPreflightIssues(preflight).some((issue) => expected.has(String(issue.code ?? '')));
|
|
1848
|
+
}
|
|
1849
|
+
function shouldRefreshObservedAssetsForEnable(preflight) {
|
|
1850
|
+
const assetReadinessCodes = new Set([
|
|
1851
|
+
'NO_OBSERVED_ASSETS',
|
|
1852
|
+
'TARGET_ASSET_NOT_OBSERVED',
|
|
1853
|
+
'SESSION_SCOPE_DRIFT',
|
|
1854
|
+
]);
|
|
1855
|
+
const blockingCodes = getBlockingPreflightCodes(preflight);
|
|
1856
|
+
if (blockingCodes.length === 0 || !blockingCodes.every((code) => assetReadinessCodes.has(code))) {
|
|
1857
|
+
return false;
|
|
1858
|
+
}
|
|
1859
|
+
return hasPreflightIssue(preflight, [
|
|
1860
|
+
'NO_OBSERVED_ASSETS',
|
|
1861
|
+
'TARGET_ASSET_NOT_OBSERVED',
|
|
1862
|
+
'SESSION_SCOPE_DRIFT',
|
|
1863
|
+
]);
|
|
1864
|
+
}
|
|
1800
1865
|
function buildOverridePreflight(options) {
|
|
1801
1866
|
const session = options.db
|
|
1802
1867
|
.prepare(`
|
|
@@ -1835,7 +1900,20 @@ function buildOverridePreflight(options) {
|
|
|
1835
1900
|
.filter((context) => context !== null)
|
|
1836
1901
|
.map((context) => [String(context.variantKey ?? JSON.stringify(context)), context])).values()];
|
|
1837
1902
|
const sessionState = options.getSessionConnectionState?.(options.sessionId);
|
|
1903
|
+
const hasLiveConnectionLookup = typeof options.getSessionConnectionState === 'function';
|
|
1838
1904
|
const diagnosis = session ? diagnoseOverridePoc(options.db, options.sessionId, latestRun?.runId) : null;
|
|
1905
|
+
const observedAssetTabs = [...new Set(observedAssets
|
|
1906
|
+
.map((asset) => asset.tabId)
|
|
1907
|
+
.filter((tabId) => typeof tabId === 'number' && Number.isFinite(tabId)))].sort((a, b) => a - b);
|
|
1908
|
+
const observedAssetPageUrls = [...new Set(observedAssets
|
|
1909
|
+
.map((asset) => asset.pageUrl)
|
|
1910
|
+
.filter((pageUrl) => typeof pageUrl === 'string' && pageUrl.trim().length > 0))].slice(0, 5);
|
|
1911
|
+
const sessionTabId = typeof session?.tab_id === 'number' ? session.tab_id : undefined;
|
|
1912
|
+
const observedAssetsWithKnownTabs = observedAssets.filter((asset) => typeof asset.tabId === 'number');
|
|
1913
|
+
const topLevelScopeLikely = sessionTabId === undefined
|
|
1914
|
+
|| observedAssets.length === 0
|
|
1915
|
+
|| observedAssetsWithKnownTabs.length === 0
|
|
1916
|
+
|| observedAssetsWithKnownTabs.some((asset) => asset.tabId === sessionTabId);
|
|
1839
1917
|
for (const issue of buildOverrideProfileIssues(profile)) {
|
|
1840
1918
|
pushOverridePreflightIssue(issues, { ...issue, source: 'profile' });
|
|
1841
1919
|
}
|
|
@@ -1865,55 +1943,117 @@ function buildOverridePreflight(options) {
|
|
|
1865
1943
|
message: `Session ${options.sessionId} has ended and cannot enable overrides.`,
|
|
1866
1944
|
});
|
|
1867
1945
|
}
|
|
1868
|
-
if (
|
|
1946
|
+
if (hasLiveConnectionLookup && (!sessionState || sessionState.connected !== true)) {
|
|
1869
1947
|
pushOverridePreflightIssue(issues, {
|
|
1870
1948
|
code: LIVE_SESSION_DISCONNECTED_CODE,
|
|
1871
1949
|
severity: 'error',
|
|
1872
1950
|
source: 'connection',
|
|
1873
|
-
message:
|
|
1951
|
+
message: sessionState
|
|
1952
|
+
? `Session ${options.sessionId} is not currently connected to the live extension bridge. Last disconnect reason: ${sessionState.disconnectReason ?? 'unknown'}.`
|
|
1953
|
+
: `Session ${options.sessionId} has no current live extension connection state.`,
|
|
1954
|
+
disconnectedAt: sessionState?.disconnectedAt,
|
|
1955
|
+
disconnectReason: sessionState?.disconnectReason,
|
|
1874
1956
|
});
|
|
1875
1957
|
}
|
|
1876
1958
|
}
|
|
1877
1959
|
const enabledRules = Array.isArray(profile.rules)
|
|
1878
1960
|
? profile.rules.filter((rule) => isRecord(rule) && rule.enabled === true)
|
|
1879
1961
|
: [];
|
|
1962
|
+
const enabledRuleAssetReadiness = enabledRules
|
|
1963
|
+
.map((rule) => {
|
|
1964
|
+
const targetAssetUrl = normalizeOptionalString(rule.targetAssetUrl);
|
|
1965
|
+
if (!targetAssetUrl) {
|
|
1966
|
+
return null;
|
|
1967
|
+
}
|
|
1968
|
+
const requestMethod = normalizeOverrideRequestMethod(rule.requestMethod);
|
|
1969
|
+
const matchMode = String(rule.matchMode ?? 'exact');
|
|
1970
|
+
const matchingAssets = observedAssets.filter((asset) => {
|
|
1971
|
+
const methodMatches = normalizeOverrideRequestMethod(asset.requestMethod) === requestMethod;
|
|
1972
|
+
if (!methodMatches) {
|
|
1973
|
+
return false;
|
|
1974
|
+
}
|
|
1975
|
+
return matchMode === 'prefix'
|
|
1976
|
+
? asset.url.startsWith(targetAssetUrl)
|
|
1977
|
+
: asset.url === targetAssetUrl;
|
|
1978
|
+
});
|
|
1979
|
+
return {
|
|
1980
|
+
ruleId: String(rule.ruleId ?? 'unknown'),
|
|
1981
|
+
targetAssetUrl,
|
|
1982
|
+
requestMethod,
|
|
1983
|
+
matchMode,
|
|
1984
|
+
captureProven: rule.ruleType === 'rsc-flight' && isRecordWithRscFlightMetadata(rule.rscFlight),
|
|
1985
|
+
matchingAssets,
|
|
1986
|
+
};
|
|
1987
|
+
})
|
|
1988
|
+
.filter((readiness) => readiness !== null);
|
|
1989
|
+
const matchedTargetAssetCount = enabledRuleAssetReadiness.filter((readiness) => readiness.matchingAssets.length > 0).length;
|
|
1990
|
+
const capturedTargetAssetCount = enabledRuleAssetReadiness
|
|
1991
|
+
.filter((readiness) => readiness.matchingAssets.length === 0 && readiness.captureProven)
|
|
1992
|
+
.length;
|
|
1993
|
+
const unobservedTargetAssetCount = enabledRuleAssetReadiness.length - matchedTargetAssetCount;
|
|
1994
|
+
const unsatisfiedTargetAssetCount = enabledRuleAssetReadiness.length - matchedTargetAssetCount - capturedTargetAssetCount;
|
|
1995
|
+
const targetAssetObserved = observedAssets.length > 0 && matchedTargetAssetCount > 0;
|
|
1996
|
+
const targetAssetReadinessSatisfied = observedAssets.length > 0
|
|
1997
|
+
&& (enabledRuleAssetReadiness.length === 0 || matchedTargetAssetCount > 0 || capturedTargetAssetCount > 0);
|
|
1880
1998
|
const anyServiceWorkerControlled = observedAssets.some((asset) => asset.serviceWorkerControlled);
|
|
1881
1999
|
const cspMetaTags = [...new Set(observedAssets.flatMap((asset) => asset.cspMetaTags))];
|
|
1882
2000
|
if (observedAssets.length === 0) {
|
|
1883
2001
|
pushOverridePreflightIssue(issues, {
|
|
1884
2002
|
code: 'NO_OBSERVED_ASSETS',
|
|
1885
|
-
severity: '
|
|
2003
|
+
severity: 'error',
|
|
1886
2004
|
source: 'observed-assets',
|
|
1887
|
-
message: 'No observed production assets are stored for this session yet.',
|
|
2005
|
+
message: 'No observed production assets are stored for this session yet; the target route is not capture-ready for override enablement.',
|
|
1888
2006
|
});
|
|
1889
2007
|
}
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
2008
|
+
else if (!topLevelScopeLikely) {
|
|
2009
|
+
pushOverridePreflightIssue(issues, {
|
|
2010
|
+
code: 'SESSION_SCOPE_DRIFT',
|
|
2011
|
+
severity: 'error',
|
|
2012
|
+
source: 'observed-assets',
|
|
2013
|
+
message: `Observed override assets were recorded only for tab(s) ${observedAssetTabs.join(', ')}, but the session top-level tab is ${sessionTabId}.`,
|
|
2014
|
+
observedAssetTabs,
|
|
2015
|
+
sessionTabId,
|
|
2016
|
+
observedPageUrls: observedAssetPageUrls,
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
2019
|
+
if (observedAssets.length > 0 && enabledRuleAssetReadiness.length > 0 && matchedTargetAssetCount === 0 && capturedTargetAssetCount === 0) {
|
|
2020
|
+
const sampleTargets = enabledRuleAssetReadiness.slice(0, 5).map((readiness) => ({
|
|
2021
|
+
ruleId: readiness.ruleId,
|
|
2022
|
+
requestMethod: readiness.requestMethod,
|
|
2023
|
+
matchMode: readiness.matchMode,
|
|
2024
|
+
targetAssetUrl: readiness.targetAssetUrl,
|
|
2025
|
+
}));
|
|
2026
|
+
pushOverridePreflightIssue(issues, {
|
|
2027
|
+
code: 'TARGET_ASSET_NOT_OBSERVED',
|
|
2028
|
+
severity: 'error',
|
|
2029
|
+
source: 'observed-assets',
|
|
2030
|
+
message: enabledRuleAssetReadiness.length === 1
|
|
2031
|
+
? `Rule ${enabledRuleAssetReadiness[0].ruleId} target asset was not observed for ${enabledRuleAssetReadiness[0].requestMethod} ${enabledRuleAssetReadiness[0].targetAssetUrl}.`
|
|
2032
|
+
: `None of the ${enabledRuleAssetReadiness.length} enabled override targets were observed for this session.`,
|
|
2033
|
+
checkedTargetAssetCount: enabledRuleAssetReadiness.length,
|
|
2034
|
+
sampleTargets,
|
|
1900
2035
|
});
|
|
1901
|
-
|
|
2036
|
+
}
|
|
2037
|
+
for (const readiness of enabledRuleAssetReadiness) {
|
|
2038
|
+
if (readiness.matchingAssets.length === 0) {
|
|
2039
|
+
if (readiness.captureProven) {
|
|
2040
|
+
continue;
|
|
2041
|
+
}
|
|
1902
2042
|
pushOverridePreflightIssue(issues, {
|
|
1903
|
-
code: '
|
|
2043
|
+
code: 'TARGET_ASSET_NOT_OBSERVED_FOR_RULE',
|
|
1904
2044
|
severity: 'warning',
|
|
1905
2045
|
source: 'observed-assets',
|
|
1906
|
-
message: `Rule ${ruleId} target asset was not observed for ${requestMethod} ${targetAssetUrl}.`,
|
|
2046
|
+
message: `Rule ${readiness.ruleId} target asset was not observed for ${readiness.requestMethod} ${readiness.targetAssetUrl}.`,
|
|
1907
2047
|
});
|
|
1908
2048
|
continue;
|
|
1909
2049
|
}
|
|
1910
|
-
for (const asset of matchingAssets) {
|
|
2050
|
+
for (const asset of readiness.matchingAssets) {
|
|
1911
2051
|
if (typeof asset.integrity === 'string' && asset.integrity.length > 0) {
|
|
1912
2052
|
pushOverridePreflightIssue(issues, {
|
|
1913
2053
|
code: 'TARGET_ASSET_SRI_PRESENT',
|
|
1914
2054
|
severity: 'error',
|
|
1915
2055
|
source: 'observed-assets',
|
|
1916
|
-
message: `Rule ${ruleId} target asset ${asset.url} includes integrity="${asset.integrity}" and cannot be overridden safely.`,
|
|
2056
|
+
message: `Rule ${readiness.ruleId} target asset ${asset.url} includes integrity="${asset.integrity}" and cannot be overridden safely.`,
|
|
1917
2057
|
});
|
|
1918
2058
|
}
|
|
1919
2059
|
}
|
|
@@ -1936,23 +2076,29 @@ function buildOverridePreflight(options) {
|
|
|
1936
2076
|
}
|
|
1937
2077
|
const ready = !issues.some((issue) => issue.severity === 'error');
|
|
1938
2078
|
const nextActions = !ready
|
|
1939
|
-
? issues.some((issue) => issue.code === '
|
|
1940
|
-
? [{
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
: issues.some((issue) => issue.code === '
|
|
1954
|
-
? [{ code: '
|
|
1955
|
-
:
|
|
2079
|
+
? issues.some((issue) => issue.code === 'SESSION_NOT_FOUND' || issue.code === 'SESSION_PAUSED' || issue.code === 'SESSION_ENDED' || issue.code === LIVE_SESSION_DISCONNECTED_CODE)
|
|
2080
|
+
? [{ code: 'RECONNECT_SESSION', message: 'Reconnect or resume the target session before enabling overrides.' }]
|
|
2081
|
+
: issues.some((issue) => issue.code === 'SESSION_SCOPE_DRIFT')
|
|
2082
|
+
? [{ code: 'FOCUS_BOUND_TAB', message: 'Focus or reselect the bound top-level tab, then observe override assets again.' }]
|
|
2083
|
+
: issues.some((issue) => issue.code === 'SERVER_ACTION_UNSUPPORTED')
|
|
2084
|
+
? [{
|
|
2085
|
+
code: 'REPLAN_SERVER_ACTION_OVERRIDE',
|
|
2086
|
+
message: 'Server actions stay unsupported in production override mode; move the override to a GET document/data/API response.',
|
|
2087
|
+
}]
|
|
2088
|
+
: issues.some((issue) => issue.code === 'MUTATION_REPLAY_UNSUPPORTED')
|
|
2089
|
+
? [{
|
|
2090
|
+
code: 'REPLAN_MUTATION_OVERRIDE',
|
|
2091
|
+
message: 'Mutation responses are not replay-safe; use a GET document/data/API response path instead.',
|
|
2092
|
+
}]
|
|
2093
|
+
: issues.some((issue) => issue.code === 'UNSAFE_REQUEST_METHOD')
|
|
2094
|
+
? [{ code: 'REPLAN_GET_ONLY_OVERRIDE', message: 'Remove or regenerate non-GET rules before enabling overrides.' }]
|
|
2095
|
+
: issues.some((issue) => issue.code === 'TARGET_ASSET_SRI_PRESENT')
|
|
2096
|
+
? [{ code: 'CHOOSE_ANOTHER_OVERRIDE_PATH', message: 'Choose a document/data response path or remove SRI on the production asset before enabling overrides.' }]
|
|
2097
|
+
: issues.some((issue) => issue.code === 'NO_OBSERVED_ASSETS')
|
|
2098
|
+
? [{ code: 'OBSERVE_OVERRIDE_ASSETS', message: 'Observe the bound target route before enabling overrides.' }]
|
|
2099
|
+
: issues.some((issue) => issue.code === 'TARGET_ASSET_NOT_OBSERVED')
|
|
2100
|
+
? [{ code: 'OBSERVE_TARGET_ROUTE', message: 'Load the route that requests the configured target and observe assets again.' }]
|
|
2101
|
+
: buildOverrideProfileNextActions(profile, issues)
|
|
1956
2102
|
: observedAssets.length === 0
|
|
1957
2103
|
? [{ code: 'OBSERVE_OVERRIDE_ASSETS', message: 'Run observe_override_assets on the target route before enabling overrides in production workflows.' }]
|
|
1958
2104
|
: [{ code: 'ENABLE_OVERRIDES', message: 'Preflight checks passed; the selected profile can be enabled on the live session.' }];
|
|
@@ -1976,8 +2122,22 @@ function buildOverridePreflight(options) {
|
|
|
1976
2122
|
checks: {
|
|
1977
2123
|
sessionFound: session !== undefined,
|
|
1978
2124
|
connected: sessionState?.connected === true,
|
|
2125
|
+
captureReady: session !== undefined
|
|
2126
|
+
&& getSessionStatus(session) === 'active'
|
|
2127
|
+
&& (!hasLiveConnectionLookup || sessionState?.connected === true)
|
|
2128
|
+
&& observedAssets.length > 0
|
|
2129
|
+
&& topLevelScopeLikely
|
|
2130
|
+
&& !issues.some((issue) => issue.severity === 'error'),
|
|
2131
|
+
topLevelScopeLikely,
|
|
2132
|
+
observedAssetTabs,
|
|
2133
|
+
observedAssetPageUrls,
|
|
1979
2134
|
observedAssetCount: observedAssets.length,
|
|
1980
|
-
targetAssetObserved
|
|
2135
|
+
targetAssetObserved,
|
|
2136
|
+
targetAssetReadinessSatisfied,
|
|
2137
|
+
matchedTargetAssetCount,
|
|
2138
|
+
capturedTargetAssetCount,
|
|
2139
|
+
unobservedTargetAssetCount,
|
|
2140
|
+
unsatisfiedTargetAssetCount,
|
|
1981
2141
|
serviceWorkerControlled: anyServiceWorkerControlled,
|
|
1982
2142
|
cspMetaTagCount: cspMetaTags.length,
|
|
1983
2143
|
recentPlanCount: recentPlans.length,
|
|
@@ -1985,6 +2145,14 @@ function buildOverridePreflight(options) {
|
|
|
1985
2145
|
},
|
|
1986
2146
|
observedAssets: {
|
|
1987
2147
|
count: observedAssets.length,
|
|
2148
|
+
tabIds: observedAssetTabs,
|
|
2149
|
+
pageUrls: observedAssetPageUrls,
|
|
2150
|
+
targetAssetObserved,
|
|
2151
|
+
targetAssetReadinessSatisfied,
|
|
2152
|
+
matchedTargetAssetCount,
|
|
2153
|
+
capturedTargetAssetCount,
|
|
2154
|
+
unobservedTargetAssetCount,
|
|
2155
|
+
unsatisfiedTargetAssetCount,
|
|
1988
2156
|
serviceWorkerControlled: anyServiceWorkerControlled,
|
|
1989
2157
|
cspMetaTags,
|
|
1990
2158
|
},
|
|
@@ -3156,6 +3324,66 @@ function normalizeCaptureError(sessionId, error) {
|
|
|
3156
3324
|
}
|
|
3157
3325
|
return fallback;
|
|
3158
3326
|
}
|
|
3327
|
+
function isCaptureTimeoutMessage(message) {
|
|
3328
|
+
const normalized = message.toLowerCase();
|
|
3329
|
+
return normalized.includes('timed out') || normalized.includes('timeout');
|
|
3330
|
+
}
|
|
3331
|
+
function isRecoverableOverrideLiveCommandError(error) {
|
|
3332
|
+
if (isLiveSessionDisconnectedError(error)) {
|
|
3333
|
+
return true;
|
|
3334
|
+
}
|
|
3335
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3336
|
+
return isCaptureTimeoutMessage(message);
|
|
3337
|
+
}
|
|
3338
|
+
function extractTimeoutMsFromMessage(message, fallback) {
|
|
3339
|
+
const match = message.match(/(?:after|waiting)\s+(\d+)ms/i);
|
|
3340
|
+
if (!match) {
|
|
3341
|
+
return fallback;
|
|
3342
|
+
}
|
|
3343
|
+
const parsed = Number.parseInt(match[1] ?? '', 10);
|
|
3344
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
3345
|
+
}
|
|
3346
|
+
function buildOverrideLiveCommandFailure(options) {
|
|
3347
|
+
const originalMessage = options.error instanceof Error ? options.error.message : String(options.error);
|
|
3348
|
+
const timeout = extractTimeoutMsFromMessage(originalMessage, options.timeoutMs);
|
|
3349
|
+
const timedOut = isCaptureTimeoutMessage(originalMessage);
|
|
3350
|
+
const disconnected = isLiveSessionDisconnectedError(options.error);
|
|
3351
|
+
const sessionState = options.getSessionConnectionState?.(options.sessionId);
|
|
3352
|
+
const code = disconnected
|
|
3353
|
+
? LIVE_SESSION_DISCONNECTED_CODE
|
|
3354
|
+
: timedOut
|
|
3355
|
+
? OVERRIDE_LIVE_COMMAND_TIMEOUT_CODE
|
|
3356
|
+
: OVERRIDE_LIVE_COMMAND_FAILED_CODE;
|
|
3357
|
+
const message = timedOut
|
|
3358
|
+
? `${options.command} for session ${options.sessionId} timed out after ${timeout}ms before the live extension returned an override command result.`
|
|
3359
|
+
: disconnected
|
|
3360
|
+
? `${options.command} for session ${options.sessionId} could not reach a connected live extension target.`
|
|
3361
|
+
: `${options.command} for session ${options.sessionId} failed before returning an override command result.`;
|
|
3362
|
+
return {
|
|
3363
|
+
ok: false,
|
|
3364
|
+
available: false,
|
|
3365
|
+
code,
|
|
3366
|
+
command: options.command,
|
|
3367
|
+
timeoutMs: timeout,
|
|
3368
|
+
timedOut,
|
|
3369
|
+
disconnected,
|
|
3370
|
+
message,
|
|
3371
|
+
originalMessage,
|
|
3372
|
+
sessionConnected: sessionState?.connected,
|
|
3373
|
+
disconnectedAt: sessionState?.disconnectedAt,
|
|
3374
|
+
disconnectReason: sessionState?.disconnectReason,
|
|
3375
|
+
};
|
|
3376
|
+
}
|
|
3377
|
+
function createOverrideLiveCommandError(options) {
|
|
3378
|
+
const failure = buildOverrideLiveCommandFailure(options);
|
|
3379
|
+
const code = String(failure.code ?? OVERRIDE_LIVE_COMMAND_FAILED_CODE);
|
|
3380
|
+
const message = `${code}: ${options.command} for session ${options.sessionId} ${failure.timedOut === true
|
|
3381
|
+
? `timed out after ${String(failure.timeoutMs)}ms`
|
|
3382
|
+
: `failed`}. ${String(failure.message ?? '')} Original error: ${String(failure.originalMessage ?? 'unknown')}`;
|
|
3383
|
+
const error = new Error(message);
|
|
3384
|
+
Object.assign(error, { code, details: failure });
|
|
3385
|
+
return error;
|
|
3386
|
+
}
|
|
3159
3387
|
function isLiveSessionDisconnectedError(error) {
|
|
3160
3388
|
return error instanceof LiveSessionDisconnectedError;
|
|
3161
3389
|
}
|
|
@@ -3167,12 +3395,99 @@ async function executeLiveCapture(captureClient, sessionId, command, payload, ti
|
|
|
3167
3395
|
throw normalizeCaptureError(sessionId, error);
|
|
3168
3396
|
}
|
|
3169
3397
|
}
|
|
3398
|
+
async function executeOverrideLiveCaptureWithDiagnostics(options) {
|
|
3399
|
+
try {
|
|
3400
|
+
const capture = await executeLiveCapture(options.captureClient, options.sessionId, options.command, options.payload, options.timeoutMs);
|
|
3401
|
+
return { capture, payload: ensureCaptureSuccess(capture, options.sessionId) };
|
|
3402
|
+
}
|
|
3403
|
+
catch (error) {
|
|
3404
|
+
throw createOverrideLiveCommandError({
|
|
3405
|
+
sessionId: options.sessionId,
|
|
3406
|
+
command: options.command,
|
|
3407
|
+
timeoutMs: options.timeoutMs,
|
|
3408
|
+
error,
|
|
3409
|
+
getSessionConnectionState: options.getSessionConnectionState,
|
|
3410
|
+
});
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3170
3413
|
function ensureCaptureSuccess(result, sessionId) {
|
|
3171
3414
|
if (!result.ok) {
|
|
3172
3415
|
throw normalizeCaptureError(sessionId, new Error(result.error ?? 'Capture command failed'));
|
|
3173
3416
|
}
|
|
3174
3417
|
return result.payload ?? {};
|
|
3175
3418
|
}
|
|
3419
|
+
async function refreshObservedAssetsForOverrideEnable(options) {
|
|
3420
|
+
const { payload } = await executeOverrideLiveCaptureWithDiagnostics({
|
|
3421
|
+
captureClient: options.captureClient,
|
|
3422
|
+
sessionId: options.sessionId,
|
|
3423
|
+
command: 'CAPTURE_OVERRIDE_OBSERVE_ASSETS',
|
|
3424
|
+
payload: { tabId: options.tabId, includePerformance: true },
|
|
3425
|
+
timeoutMs: 5_000,
|
|
3426
|
+
getSessionConnectionState: options.getSessionConnectionState,
|
|
3427
|
+
});
|
|
3428
|
+
persistObservedOverrideAssets(options.db, {
|
|
3429
|
+
...payload,
|
|
3430
|
+
sessionId: options.sessionId,
|
|
3431
|
+
tabId: payload.tabId ?? options.tabId,
|
|
3432
|
+
});
|
|
3433
|
+
return {
|
|
3434
|
+
tabId: typeof payload.tabId === 'number' ? payload.tabId : options.tabId,
|
|
3435
|
+
pageUrl: typeof payload.pageUrl === 'string' ? payload.pageUrl : undefined,
|
|
3436
|
+
assetCount: Array.isArray(payload.assets) ? payload.assets.length : 0,
|
|
3437
|
+
};
|
|
3438
|
+
}
|
|
3439
|
+
function buildPersistedOverrideStatus(options) {
|
|
3440
|
+
let profile = null;
|
|
3441
|
+
let profileError;
|
|
3442
|
+
try {
|
|
3443
|
+
profile = resolveOverrideProfileRecord(options.profileId);
|
|
3444
|
+
}
|
|
3445
|
+
catch (error) {
|
|
3446
|
+
profileError = error instanceof Error ? error.message : String(error);
|
|
3447
|
+
}
|
|
3448
|
+
const latestRun = listOverridePocRuns(options.db, options.sessionId, 1, 0).runs[0] ?? null;
|
|
3449
|
+
const recentRequests = listOverridePocRequests(options.db, options.sessionId, 5, 0, latestRun?.runId).requests;
|
|
3450
|
+
const recentPlans = listOverridePlanAudits(options.db, { sessionId: options.sessionId, limit: 5, offset: 0 }).plans;
|
|
3451
|
+
let preflight;
|
|
3452
|
+
if (profileError) {
|
|
3453
|
+
preflight = {
|
|
3454
|
+
ready: false,
|
|
3455
|
+
profileId: null,
|
|
3456
|
+
profile: null,
|
|
3457
|
+
issues: [{
|
|
3458
|
+
code: 'OVERRIDE_CONFIG_UNAVAILABLE',
|
|
3459
|
+
severity: 'error',
|
|
3460
|
+
source: 'profile',
|
|
3461
|
+
message: profileError,
|
|
3462
|
+
}],
|
|
3463
|
+
checks: {
|
|
3464
|
+
sessionFound: auditSessionExists(options.db, options.sessionId),
|
|
3465
|
+
connected: options.getSessionConnectionState?.(options.sessionId)?.connected === true,
|
|
3466
|
+
},
|
|
3467
|
+
nextActions: [{
|
|
3468
|
+
code: 'FIX_OVERRIDE_CONFIG_PATH',
|
|
3469
|
+
message: 'Create a readable override-poc config or point OVERRIDE_POC_CONFIG_PATH at the intended config, then retry override status.',
|
|
3470
|
+
}],
|
|
3471
|
+
};
|
|
3472
|
+
}
|
|
3473
|
+
else {
|
|
3474
|
+
preflight = buildOverridePreflight({
|
|
3475
|
+
db: options.db,
|
|
3476
|
+
sessionId: options.sessionId,
|
|
3477
|
+
profileId: options.profileId,
|
|
3478
|
+
getSessionConnectionState: options.getSessionConnectionState,
|
|
3479
|
+
});
|
|
3480
|
+
}
|
|
3481
|
+
return {
|
|
3482
|
+
profile,
|
|
3483
|
+
profileError,
|
|
3484
|
+
latestRun,
|
|
3485
|
+
recentRequests,
|
|
3486
|
+
recentPlans,
|
|
3487
|
+
preflight,
|
|
3488
|
+
diagnosis: diagnoseOverridePoc(options.db, options.sessionId, latestRun?.runId),
|
|
3489
|
+
};
|
|
3490
|
+
}
|
|
3176
3491
|
function auditSessionExists(db, sessionId) {
|
|
3177
3492
|
const row = db.prepare('SELECT 1 FROM sessions WHERE session_id = ? LIMIT 1').get(sessionId);
|
|
3178
3493
|
return row !== undefined;
|
|
@@ -5405,8 +5720,14 @@ export function createV2ToolHandlers(captureClient, getDb, getSessionConnectionS
|
|
|
5405
5720
|
throw new Error('sessionId is required');
|
|
5406
5721
|
}
|
|
5407
5722
|
const tabId = resolveOptionalTabId(input.tabId);
|
|
5408
|
-
const capture = await
|
|
5409
|
-
|
|
5723
|
+
const { capture, payload } = await executeOverrideLiveCaptureWithDiagnostics({
|
|
5724
|
+
captureClient,
|
|
5725
|
+
sessionId,
|
|
5726
|
+
command: 'CAPTURE_OVERRIDE_OBSERVE_ASSETS',
|
|
5727
|
+
payload: { tabId, includePerformance: input.includePerformance !== false },
|
|
5728
|
+
timeoutMs: 5_000,
|
|
5729
|
+
getSessionConnectionState,
|
|
5730
|
+
});
|
|
5410
5731
|
const assetCount = Array.isArray(payload.assets) ? payload.assets.length : 0;
|
|
5411
5732
|
const persisted = getDb
|
|
5412
5733
|
? persistObservedOverrideAssets(getDb(), { ...payload, sessionId, tabId: payload.tabId ?? tabId })
|
|
@@ -5436,23 +5757,31 @@ export function createV2ToolHandlers(captureClient, getDb, getSessionConnectionS
|
|
|
5436
5757
|
assertOverrideResponseRequestCaptureSafe({
|
|
5437
5758
|
requestMethod: input.requestMethod,
|
|
5438
5759
|
requestHeaders: input.requestHeaders,
|
|
5760
|
+
ruleType: input.ruleType,
|
|
5439
5761
|
subject: 'Response body capture request',
|
|
5440
5762
|
});
|
|
5441
5763
|
const tabId = resolveOptionalTabId(input.tabId);
|
|
5442
5764
|
const timeoutMs = resolveTimeoutMs(input.timeoutMs, 10_000, 60_000);
|
|
5443
|
-
const capture = await
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5765
|
+
const { capture, payload } = await executeOverrideLiveCaptureWithDiagnostics({
|
|
5766
|
+
captureClient,
|
|
5767
|
+
sessionId,
|
|
5768
|
+
command: 'CAPTURE_OVERRIDE_RESPONSE_BODY',
|
|
5769
|
+
payload: {
|
|
5770
|
+
targetUrl,
|
|
5771
|
+
tabId,
|
|
5772
|
+
captureMode: normalizeOptionalString(input.captureMode),
|
|
5773
|
+
triggerReload: typeof input.triggerReload === 'boolean' ? input.triggerReload : undefined,
|
|
5774
|
+
matchMode: normalizeOptionalString(input.matchMode),
|
|
5775
|
+
ruleType: normalizeOptionalString(input.ruleType),
|
|
5776
|
+
requestMethod: input.requestMethod,
|
|
5777
|
+
requestHeaders: isRecord(input.requestHeaders) ? input.requestHeaders : undefined,
|
|
5778
|
+
timeoutMs,
|
|
5779
|
+
maxBodyBytes: input.maxBodyBytes,
|
|
5780
|
+
includeBody: input.includeBody === true,
|
|
5781
|
+
},
|
|
5782
|
+
timeoutMs: timeoutMs + 2_000,
|
|
5783
|
+
getSessionConnectionState,
|
|
5784
|
+
});
|
|
5456
5785
|
return {
|
|
5457
5786
|
...createBaseResponse(sessionId),
|
|
5458
5787
|
limitsApplied: {
|
|
@@ -5480,19 +5809,26 @@ export function createV2ToolHandlers(captureClient, getDb, getSessionConnectionS
|
|
|
5480
5809
|
}
|
|
5481
5810
|
const tabId = resolveOptionalTabId(input.tabId);
|
|
5482
5811
|
const timeoutMs = resolveTimeoutMs(input.timeoutMs, 10_000, 60_000);
|
|
5483
|
-
const
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5812
|
+
const { payload } = await executeOverrideLiveCaptureWithDiagnostics({
|
|
5813
|
+
captureClient,
|
|
5814
|
+
sessionId,
|
|
5815
|
+
command: 'CAPTURE_OVERRIDE_RESPONSE_BODY',
|
|
5816
|
+
payload: {
|
|
5817
|
+
targetUrl,
|
|
5818
|
+
tabId,
|
|
5819
|
+
captureMode: normalizeOptionalString(input.captureMode),
|
|
5820
|
+
triggerReload: typeof input.triggerReload === 'boolean' ? input.triggerReload : undefined,
|
|
5821
|
+
matchMode: normalizeOptionalString(input.matchMode),
|
|
5822
|
+
ruleType: normalizeOptionalString(input.ruleType),
|
|
5823
|
+
requestMethod: input.requestMethod,
|
|
5824
|
+
requestHeaders: isRecord(input.requestHeaders) ? input.requestHeaders : undefined,
|
|
5825
|
+
timeoutMs,
|
|
5826
|
+
maxBodyBytes: input.maxBodyBytes,
|
|
5827
|
+
includeBody: true,
|
|
5828
|
+
},
|
|
5829
|
+
timeoutMs: timeoutMs + 2_000,
|
|
5830
|
+
getSessionConnectionState,
|
|
5831
|
+
});
|
|
5496
5832
|
if (payload.truncated === true) {
|
|
5497
5833
|
throw new Error('Captured response body was truncated; increase maxBodyBytes before planning a patch.');
|
|
5498
5834
|
}
|
|
@@ -5585,8 +5921,10 @@ export function createV2ToolHandlers(captureClient, getDb, getSessionConnectionS
|
|
|
5585
5921
|
let observedFromPersisted;
|
|
5586
5922
|
if (!Array.isArray(observedAssets) && sessionId) {
|
|
5587
5923
|
const tabId = resolveOptionalTabId(input.tabId);
|
|
5924
|
+
const command = 'CAPTURE_OVERRIDE_OBSERVE_ASSETS';
|
|
5925
|
+
const timeoutMs = 5_000;
|
|
5588
5926
|
try {
|
|
5589
|
-
const capture = await executeLiveCapture(captureClient, sessionId,
|
|
5927
|
+
const capture = await executeLiveCapture(captureClient, sessionId, command, { tabId, includePerformance: true }, timeoutMs);
|
|
5590
5928
|
observedFromLiveTab = ensureCaptureSuccess(capture, sessionId);
|
|
5591
5929
|
observedAssets = observedFromLiveTab.assets;
|
|
5592
5930
|
if (getDb) {
|
|
@@ -5594,11 +5932,22 @@ export function createV2ToolHandlers(captureClient, getDb, getSessionConnectionS
|
|
|
5594
5932
|
}
|
|
5595
5933
|
}
|
|
5596
5934
|
catch (error) {
|
|
5597
|
-
if (
|
|
5935
|
+
if (getDb && isRecoverableOverrideLiveCommandError(error)) {
|
|
5936
|
+
observedAssets = listObservedOverrideAssets(getDb(), { sessionId });
|
|
5937
|
+
observedFromPersisted = { sessionId, assetCount: Array.isArray(observedAssets) ? observedAssets.length : 0 };
|
|
5938
|
+
}
|
|
5939
|
+
else if (isRecoverableOverrideLiveCommandError(error)) {
|
|
5940
|
+
throw createOverrideLiveCommandError({
|
|
5941
|
+
sessionId,
|
|
5942
|
+
command,
|
|
5943
|
+
timeoutMs,
|
|
5944
|
+
error,
|
|
5945
|
+
getSessionConnectionState,
|
|
5946
|
+
});
|
|
5947
|
+
}
|
|
5948
|
+
else {
|
|
5598
5949
|
throw error;
|
|
5599
5950
|
}
|
|
5600
|
-
observedAssets = listObservedOverrideAssets(getDb(), { sessionId });
|
|
5601
|
-
observedFromPersisted = { sessionId, assetCount: Array.isArray(observedAssets) ? observedAssets.length : 0 };
|
|
5602
5951
|
}
|
|
5603
5952
|
}
|
|
5604
5953
|
const mapping = await mapNextOverrideAssetsWithDrift({
|
|
@@ -5642,8 +5991,10 @@ export function createV2ToolHandlers(captureClient, getDb, getSessionConnectionS
|
|
|
5642
5991
|
let observedFromPersisted;
|
|
5643
5992
|
if (!Array.isArray(observedAssets) && sessionId) {
|
|
5644
5993
|
const tabId = resolveOptionalTabId(input.tabId);
|
|
5994
|
+
const command = 'CAPTURE_OVERRIDE_OBSERVE_ASSETS';
|
|
5995
|
+
const timeoutMs = 5_000;
|
|
5645
5996
|
try {
|
|
5646
|
-
const capture = await executeLiveCapture(captureClient, sessionId,
|
|
5997
|
+
const capture = await executeLiveCapture(captureClient, sessionId, command, { tabId, includePerformance: true }, timeoutMs);
|
|
5647
5998
|
observedFromLiveTab = ensureCaptureSuccess(capture, sessionId);
|
|
5648
5999
|
observedAssets = observedFromLiveTab.assets;
|
|
5649
6000
|
if (getDb) {
|
|
@@ -5651,11 +6002,22 @@ export function createV2ToolHandlers(captureClient, getDb, getSessionConnectionS
|
|
|
5651
6002
|
}
|
|
5652
6003
|
}
|
|
5653
6004
|
catch (error) {
|
|
5654
|
-
if (
|
|
6005
|
+
if (getDb && isRecoverableOverrideLiveCommandError(error)) {
|
|
6006
|
+
observedAssets = listObservedOverrideAssets(getDb(), { sessionId });
|
|
6007
|
+
observedFromPersisted = { sessionId, assetCount: Array.isArray(observedAssets) ? observedAssets.length : 0 };
|
|
6008
|
+
}
|
|
6009
|
+
else if (isRecoverableOverrideLiveCommandError(error)) {
|
|
6010
|
+
throw createOverrideLiveCommandError({
|
|
6011
|
+
sessionId,
|
|
6012
|
+
command,
|
|
6013
|
+
timeoutMs,
|
|
6014
|
+
error,
|
|
6015
|
+
getSessionConnectionState,
|
|
6016
|
+
});
|
|
6017
|
+
}
|
|
6018
|
+
else {
|
|
5655
6019
|
throw error;
|
|
5656
6020
|
}
|
|
5657
|
-
observedAssets = listObservedOverrideAssets(getDb(), { sessionId });
|
|
5658
|
-
observedFromPersisted = { sessionId, assetCount: Array.isArray(observedAssets) ? observedAssets.length : 0 };
|
|
5659
6021
|
}
|
|
5660
6022
|
}
|
|
5661
6023
|
const plan = await planNextSourceOverride({
|
|
@@ -5716,14 +6078,58 @@ export function createV2ToolHandlers(captureClient, getDb, getSessionConnectionS
|
|
|
5716
6078
|
if (!sessionId) {
|
|
5717
6079
|
throw new Error('sessionId is required');
|
|
5718
6080
|
}
|
|
5719
|
-
const
|
|
5720
|
-
const
|
|
6081
|
+
const command = 'CAPTURE_OVERRIDE_POC_GET_STATUS';
|
|
6082
|
+
const timeoutMs = 3_000;
|
|
6083
|
+
let capture;
|
|
6084
|
+
let payload;
|
|
6085
|
+
try {
|
|
6086
|
+
capture = await executeLiveCapture(captureClient, sessionId, command, {}, timeoutMs);
|
|
6087
|
+
payload = ensureCaptureSuccess(capture, sessionId);
|
|
6088
|
+
}
|
|
6089
|
+
catch (error) {
|
|
6090
|
+
if (!getDb || !isRecoverableOverrideLiveCommandError(error)) {
|
|
6091
|
+
throw error;
|
|
6092
|
+
}
|
|
6093
|
+
const persisted = buildPersistedOverrideStatus({
|
|
6094
|
+
db: getDb(),
|
|
6095
|
+
sessionId,
|
|
6096
|
+
profileId: input.profileId,
|
|
6097
|
+
getSessionConnectionState,
|
|
6098
|
+
});
|
|
6099
|
+
const liveStatus = buildOverrideLiveCommandFailure({
|
|
6100
|
+
sessionId,
|
|
6101
|
+
command,
|
|
6102
|
+
timeoutMs,
|
|
6103
|
+
error,
|
|
6104
|
+
getSessionConnectionState,
|
|
6105
|
+
});
|
|
6106
|
+
return {
|
|
6107
|
+
...createBaseResponse(sessionId),
|
|
6108
|
+
limitsApplied: {
|
|
6109
|
+
maxResults: 1,
|
|
6110
|
+
truncated: false,
|
|
6111
|
+
},
|
|
6112
|
+
statusSource: 'persisted-audit',
|
|
6113
|
+
liveStatus,
|
|
6114
|
+
...persisted,
|
|
6115
|
+
nextActions: [
|
|
6116
|
+
{ code: 'RECONNECT_OR_RETRY_OVERRIDE_STATUS', message: 'Reconnect or rebind the top-level session, then retry get_override_status for live debugger state.' },
|
|
6117
|
+
{ code: 'DIAGNOSE_OVERRIDES', message: 'Run diagnose_overrides to inspect persisted run and readiness signals while live status is unavailable.' },
|
|
6118
|
+
],
|
|
6119
|
+
};
|
|
6120
|
+
}
|
|
5721
6121
|
return {
|
|
5722
6122
|
...createBaseResponse(sessionId),
|
|
5723
6123
|
limitsApplied: {
|
|
5724
6124
|
maxResults: 1,
|
|
5725
6125
|
truncated: capture.truncated ?? false,
|
|
5726
6126
|
},
|
|
6127
|
+
statusSource: 'live',
|
|
6128
|
+
liveStatus: {
|
|
6129
|
+
available: true,
|
|
6130
|
+
command,
|
|
6131
|
+
timeoutMs,
|
|
6132
|
+
},
|
|
5727
6133
|
preflight: getDb
|
|
5728
6134
|
? buildOverridePreflight({
|
|
5729
6135
|
db: getDb(),
|
|
@@ -5763,7 +6169,8 @@ export function createV2ToolHandlers(captureClient, getDb, getSessionConnectionS
|
|
|
5763
6169
|
if (!sessionId) {
|
|
5764
6170
|
throw new Error('sessionId is required');
|
|
5765
6171
|
}
|
|
5766
|
-
const
|
|
6172
|
+
const tabId = resolveOptionalTabId(input.tabId);
|
|
6173
|
+
let preflight = getDb
|
|
5767
6174
|
? buildOverridePreflight({
|
|
5768
6175
|
db: getDb(),
|
|
5769
6176
|
sessionId,
|
|
@@ -5771,20 +6178,58 @@ export function createV2ToolHandlers(captureClient, getDb, getSessionConnectionS
|
|
|
5771
6178
|
getSessionConnectionState,
|
|
5772
6179
|
})
|
|
5773
6180
|
: null;
|
|
6181
|
+
let observedBeforeEnable;
|
|
6182
|
+
let observedAssetRefreshError;
|
|
6183
|
+
const initialBlockingCodes = getBlockingPreflightCodes(preflight);
|
|
6184
|
+
const initialProfile = isRecord(preflight?.profile) ? preflight.profile : {};
|
|
6185
|
+
const initialExperimentalBypass = canBypassPreflightForExperimentalRsc(initialProfile, initialBlockingCodes);
|
|
6186
|
+
if (preflight && getDb && !initialExperimentalBypass && shouldRefreshObservedAssetsForEnable(preflight)) {
|
|
6187
|
+
try {
|
|
6188
|
+
observedBeforeEnable = await refreshObservedAssetsForOverrideEnable({
|
|
6189
|
+
captureClient,
|
|
6190
|
+
db: getDb(),
|
|
6191
|
+
sessionId,
|
|
6192
|
+
tabId,
|
|
6193
|
+
getSessionConnectionState,
|
|
6194
|
+
});
|
|
6195
|
+
preflight = buildOverridePreflight({
|
|
6196
|
+
db: getDb(),
|
|
6197
|
+
sessionId,
|
|
6198
|
+
profileId: input.profileId,
|
|
6199
|
+
getSessionConnectionState,
|
|
6200
|
+
});
|
|
6201
|
+
}
|
|
6202
|
+
catch (error) {
|
|
6203
|
+
observedAssetRefreshError = error instanceof Error ? error.message : String(error);
|
|
6204
|
+
}
|
|
6205
|
+
}
|
|
5774
6206
|
if (preflight && preflight.ready !== true) {
|
|
5775
|
-
const blockingCodes =
|
|
5776
|
-
? preflight.issues
|
|
5777
|
-
.filter((issue) => isRecord(issue) && issue.severity === 'error')
|
|
5778
|
-
.map((issue) => String(issue.code ?? 'UNKNOWN'))
|
|
5779
|
-
: [];
|
|
6207
|
+
const blockingCodes = getBlockingPreflightCodes(preflight);
|
|
5780
6208
|
const profile = isRecord(preflight.profile) ? preflight.profile : {};
|
|
5781
6209
|
if (!canBypassPreflightForExperimentalRsc(profile, blockingCodes)) {
|
|
5782
|
-
|
|
6210
|
+
const refreshSuffix = observedAssetRefreshError
|
|
6211
|
+
? `; observed asset refresh failed: ${observedAssetRefreshError}`
|
|
6212
|
+
: '';
|
|
6213
|
+
throw new Error(`Override preflight failed: ${blockingCodes.join(', ') || 'UNKNOWN'}${refreshSuffix}`);
|
|
5783
6214
|
}
|
|
5784
6215
|
}
|
|
5785
|
-
const
|
|
5786
|
-
const
|
|
5787
|
-
|
|
6216
|
+
const command = 'CAPTURE_OVERRIDE_POC_ENABLE';
|
|
6217
|
+
const timeoutMs = 8_000;
|
|
6218
|
+
let capture;
|
|
6219
|
+
let payload;
|
|
6220
|
+
try {
|
|
6221
|
+
capture = await executeLiveCapture(captureClient, sessionId, command, { tabId }, timeoutMs);
|
|
6222
|
+
payload = ensureCaptureSuccess(capture, sessionId);
|
|
6223
|
+
}
|
|
6224
|
+
catch (error) {
|
|
6225
|
+
throw createOverrideLiveCommandError({
|
|
6226
|
+
sessionId,
|
|
6227
|
+
command,
|
|
6228
|
+
timeoutMs,
|
|
6229
|
+
error,
|
|
6230
|
+
getSessionConnectionState,
|
|
6231
|
+
});
|
|
6232
|
+
}
|
|
5788
6233
|
return {
|
|
5789
6234
|
...createBaseResponse(sessionId),
|
|
5790
6235
|
limitsApplied: {
|
|
@@ -5792,6 +6237,7 @@ export function createV2ToolHandlers(captureClient, getDb, getSessionConnectionS
|
|
|
5792
6237
|
truncated: capture.truncated ?? false,
|
|
5793
6238
|
},
|
|
5794
6239
|
preflight,
|
|
6240
|
+
observedBeforeEnable,
|
|
5795
6241
|
...payload,
|
|
5796
6242
|
nextActions: [{ code: 'RELOAD_OR_INTERACT', message: 'Reload or interact with the tab so configured asset requests occur under the active override.' }],
|
|
5797
6243
|
};
|
|
@@ -5801,14 +6247,64 @@ export function createV2ToolHandlers(captureClient, getDb, getSessionConnectionS
|
|
|
5801
6247
|
if (!sessionId) {
|
|
5802
6248
|
throw new Error('sessionId is required');
|
|
5803
6249
|
}
|
|
5804
|
-
const
|
|
5805
|
-
const
|
|
6250
|
+
const command = 'CAPTURE_OVERRIDE_POC_DISABLE';
|
|
6251
|
+
const timeoutMs = 5_000;
|
|
6252
|
+
let capture;
|
|
6253
|
+
let payload;
|
|
6254
|
+
try {
|
|
6255
|
+
capture = await executeLiveCapture(captureClient, sessionId, command, {}, timeoutMs);
|
|
6256
|
+
payload = ensureCaptureSuccess(capture, sessionId);
|
|
6257
|
+
}
|
|
6258
|
+
catch (error) {
|
|
6259
|
+
if (!getDb || !isRecoverableOverrideLiveCommandError(error)) {
|
|
6260
|
+
throw createOverrideLiveCommandError({
|
|
6261
|
+
sessionId,
|
|
6262
|
+
command,
|
|
6263
|
+
timeoutMs,
|
|
6264
|
+
error,
|
|
6265
|
+
getSessionConnectionState,
|
|
6266
|
+
});
|
|
6267
|
+
}
|
|
6268
|
+
const persisted = buildPersistedOverrideStatus({
|
|
6269
|
+
db: getDb(),
|
|
6270
|
+
sessionId,
|
|
6271
|
+
profileId: input.profileId,
|
|
6272
|
+
getSessionConnectionState,
|
|
6273
|
+
});
|
|
6274
|
+
const disableAttempt = buildOverrideLiveCommandFailure({
|
|
6275
|
+
sessionId,
|
|
6276
|
+
command,
|
|
6277
|
+
timeoutMs,
|
|
6278
|
+
error,
|
|
6279
|
+
getSessionConnectionState,
|
|
6280
|
+
});
|
|
6281
|
+
return {
|
|
6282
|
+
...createBaseResponse(sessionId),
|
|
6283
|
+
limitsApplied: {
|
|
6284
|
+
maxResults: 1,
|
|
6285
|
+
truncated: false,
|
|
6286
|
+
},
|
|
6287
|
+
statusSource: 'persisted-audit',
|
|
6288
|
+
disableAttempt,
|
|
6289
|
+
...persisted,
|
|
6290
|
+
nextActions: [
|
|
6291
|
+
{ code: 'RECONNECT_OR_RETRY_DISABLE', message: 'Reconnect or rebind the top-level session, then retry disable_overrides to confirm debugger detachment.' },
|
|
6292
|
+
{ code: 'GET_OVERRIDE_STATUS', message: 'Run get_override_status after reconnecting to verify whether the override is still active.' },
|
|
6293
|
+
],
|
|
6294
|
+
};
|
|
6295
|
+
}
|
|
5806
6296
|
return {
|
|
5807
6297
|
...createBaseResponse(sessionId),
|
|
5808
6298
|
limitsApplied: {
|
|
5809
6299
|
maxResults: 1,
|
|
5810
6300
|
truncated: capture.truncated ?? false,
|
|
5811
6301
|
},
|
|
6302
|
+
statusSource: 'live',
|
|
6303
|
+
disableAttempt: {
|
|
6304
|
+
ok: true,
|
|
6305
|
+
command,
|
|
6306
|
+
timeoutMs,
|
|
6307
|
+
},
|
|
5812
6308
|
...payload,
|
|
5813
6309
|
nextActions: [{ code: 'VERIFY_DISABLED', message: 'Run get_override_status if you need to confirm the debugger override is inactive.' }],
|
|
5814
6310
|
};
|