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.
@@ -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
- if (rule.requestMethod !== 'GET') {
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.requestHeaders,
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.every((code) => code === 'UNSUPPORTED_RSC_FLIGHT_RULE')
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 (sessionState && sessionState.connected !== true) {
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: `Session ${options.sessionId} is not currently connected to the live extension bridge.`,
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: 'warning',
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
- for (const rule of enabledRules) {
1891
- const ruleId = String(rule.ruleId ?? 'unknown');
1892
- const targetAssetUrl = normalizeOptionalString(rule.targetAssetUrl);
1893
- if (!targetAssetUrl) {
1894
- continue;
1895
- }
1896
- const requestMethod = normalizeOverrideRequestMethod(rule.requestMethod);
1897
- const matchingAssets = observedAssets.filter((asset) => {
1898
- return asset.url === targetAssetUrl
1899
- && normalizeOverrideRequestMethod(asset.requestMethod) === requestMethod;
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
- if (observedAssets.length > 0 && matchingAssets.length === 0) {
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: 'TARGET_ASSET_NOT_OBSERVED',
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 === 'SERVER_ACTION_UNSUPPORTED')
1940
- ? [{
1941
- code: 'REPLAN_SERVER_ACTION_OVERRIDE',
1942
- message: 'Server actions stay unsupported in production override mode; move the override to a GET document/data/API response.',
1943
- }]
1944
- : issues.some((issue) => issue.code === 'MUTATION_REPLAY_UNSUPPORTED')
1945
- ? [{
1946
- code: 'REPLAN_MUTATION_OVERRIDE',
1947
- message: 'Mutation responses are not replay-safe; use a GET document/data/API response path instead.',
1948
- }]
1949
- : issues.some((issue) => issue.code === 'UNSAFE_REQUEST_METHOD')
1950
- ? [{ code: 'REPLAN_GET_ONLY_OVERRIDE', message: 'Remove or regenerate non-GET rules before enabling overrides.' }]
1951
- : issues.some((issue) => issue.code === 'TARGET_ASSET_SRI_PRESENT')
1952
- ? [{ code: 'CHOOSE_ANOTHER_OVERRIDE_PATH', message: 'Choose a document/data response path or remove SRI on the production asset before enabling overrides.' }]
1953
- : issues.some((issue) => issue.code === 'SESSION_NOT_FOUND' || issue.code === 'SESSION_PAUSED' || issue.code === 'SESSION_ENDED' || issue.code === LIVE_SESSION_DISCONNECTED_CODE)
1954
- ? [{ code: 'RECONNECT_SESSION', message: 'Reconnect or resume the target session before enabling overrides.' }]
1955
- : buildOverrideProfileNextActions(profile, issues)
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: issues.every((issue) => issue.code !== 'TARGET_ASSET_NOT_OBSERVED'),
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 executeLiveCapture(captureClient, sessionId, 'CAPTURE_OVERRIDE_OBSERVE_ASSETS', { tabId, includePerformance: input.includePerformance !== false }, 5_000);
5409
- const payload = ensureCaptureSuccess(capture, sessionId);
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 executeLiveCapture(captureClient, sessionId, 'CAPTURE_OVERRIDE_RESPONSE_BODY', {
5444
- targetUrl,
5445
- tabId,
5446
- captureMode: normalizeOptionalString(input.captureMode),
5447
- triggerReload: typeof input.triggerReload === 'boolean' ? input.triggerReload : undefined,
5448
- matchMode: normalizeOptionalString(input.matchMode),
5449
- requestMethod: input.requestMethod,
5450
- requestHeaders: isRecord(input.requestHeaders) ? input.requestHeaders : undefined,
5451
- timeoutMs,
5452
- maxBodyBytes: input.maxBodyBytes,
5453
- includeBody: input.includeBody === true,
5454
- }, timeoutMs + 2_000);
5455
- const payload = ensureCaptureSuccess(capture, sessionId);
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 capture = await executeLiveCapture(captureClient, sessionId, 'CAPTURE_OVERRIDE_RESPONSE_BODY', {
5484
- targetUrl,
5485
- tabId,
5486
- captureMode: normalizeOptionalString(input.captureMode),
5487
- triggerReload: typeof input.triggerReload === 'boolean' ? input.triggerReload : undefined,
5488
- matchMode: normalizeOptionalString(input.matchMode),
5489
- requestMethod: input.requestMethod,
5490
- requestHeaders: isRecord(input.requestHeaders) ? input.requestHeaders : undefined,
5491
- timeoutMs,
5492
- maxBodyBytes: input.maxBodyBytes,
5493
- includeBody: true,
5494
- }, timeoutMs + 2_000);
5495
- const payload = ensureCaptureSuccess(capture, sessionId);
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, 'CAPTURE_OVERRIDE_OBSERVE_ASSETS', { tabId, includePerformance: true }, 5_000);
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 (!getDb || !isLiveSessionDisconnectedError(error)) {
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, 'CAPTURE_OVERRIDE_OBSERVE_ASSETS', { tabId, includePerformance: true }, 5_000);
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 (!getDb || !isLiveSessionDisconnectedError(error)) {
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 capture = await executeLiveCapture(captureClient, sessionId, 'CAPTURE_OVERRIDE_POC_GET_STATUS', {}, 3_000);
5720
- const payload = ensureCaptureSuccess(capture, sessionId);
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 preflight = getDb
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 = Array.isArray(preflight.issues)
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
- throw new Error(`Override preflight failed: ${blockingCodes.join(', ') || 'UNKNOWN'}`);
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 tabId = resolveOptionalTabId(input.tabId);
5786
- const capture = await executeLiveCapture(captureClient, sessionId, 'CAPTURE_OVERRIDE_POC_ENABLE', { tabId }, 8_000);
5787
- const payload = ensureCaptureSuccess(capture, sessionId);
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 capture = await executeLiveCapture(captureClient, sessionId, 'CAPTURE_OVERRIDE_POC_DISABLE', {}, 5_000);
5805
- const payload = ensureCaptureSuccess(capture, sessionId);
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
  };