opera-devtools-mcp 0.2.3 → 0.3.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 +104 -7
- package/build/src/McpContext.js +365 -365
- package/build/src/McpPage.js +475 -48
- package/build/src/McpResponse.js +462 -288
- package/build/src/SlimMcpResponse.js +1 -1
- package/build/src/TextSnapshot.js +101 -22
- package/build/src/ToolHandler.js +142 -20
- package/build/src/bin/check-latest-version.js +27 -1
- package/build/src/bin/chrome-devtools-cli-options.js +573 -10
- package/build/src/bin/chrome-devtools-mcp-cli-options.js +119 -32
- package/build/src/bin/chrome-devtools-mcp-main.js +50 -7
- package/build/src/bin/chrome-devtools-mcp.js +7 -4
- package/build/src/bin/chrome-devtools.js +57 -14
- package/build/src/bin/opera-devtools-cli-options.js +3 -829
- package/build/src/bin/opera-devtools-mcp-cli-options.js +3 -348
- package/build/src/bin/opera-devtools-mcp-main.js +3 -37
- package/build/src/bin/opera-devtools-mcp.js +4 -19
- package/build/src/bin/opera-devtools.js +4 -185
- package/build/src/browser.js +45 -5
- package/build/src/{PageCollector.js → collectors/PageCollector.js} +64 -133
- package/build/src/collectors/ServiceWorkerCollector.js +171 -0
- package/build/src/daemon/client.js +75 -17
- package/build/src/daemon/daemon.js +92 -30
- package/build/src/daemon/utils.js +24 -8
- package/build/src/{DevtoolsUtils.js → devtools/DevtoolsUtils.js} +94 -88
- package/build/src/devtools/McpHostBindingAdapter.js +165 -0
- package/build/src/{issue-descriptions.js → devtools/issueDescriptions.js} +8 -6
- package/build/src/formatters/ConsoleFormatter.js +44 -11
- package/build/src/formatters/HeapSnapshotFormatter.js +140 -9
- package/build/src/formatters/IssueFormatter.js +7 -7
- package/build/src/formatters/NetworkFormatter.js +10 -8
- package/build/src/index.js +101 -232
- package/build/src/opera/branding.js +34 -0
- package/build/src/opera/browserFlags.js +85 -0
- package/build/src/opera/browserLaunch.js +52 -0
- package/build/src/opera/policy.js +50 -0
- package/build/src/opera/serviceWorkerRetry.js +37 -0
- package/build/src/opera/toolHandlerHooks.js +39 -0
- package/build/src/{tools → opera/tools}/opera.js +9 -21
- package/build/src/processors/HeapSnapshotManager.js +258 -0
- package/build/src/{trace-processing/parse.js → processors/PerformanceTrace.js} +9 -9
- package/build/src/telemetry/ClearcutLogger.js +12 -119
- package/build/src/telemetry/WatchdogClient.js +5 -5
- package/build/src/telemetry/errors.js +1 -1
- package/build/src/telemetry/flagUtils.js +6 -6
- package/build/src/telemetry/{toolMetricsUtils.js → metricsRegistry.js} +4 -11
- package/build/src/telemetry/persistence.js +3 -3
- package/build/src/telemetry/transformation.js +184 -0
- package/build/src/telemetry/watchdog/ClearcutSender.js +11 -11
- package/build/src/telemetry/watchdog/main.js +6 -6
- package/build/src/third_party/THIRD_PARTY_NOTICES +3493 -0
- package/build/src/third_party/bundled-packages.json +9 -0
- package/build/src/third_party/devtools-formatter-worker.js +15340 -7
- package/build/src/third_party/devtools-heap-snapshot-worker.js +10337 -7
- package/build/src/third_party/index.js +179960 -26
- package/build/src/third_party/issue-descriptions/CoepCorpNotSameOrigin.md +2 -2
- package/build/src/third_party/issue-descriptions/CoepCorpNotSameOriginAfterDefaultedToSameOriginByCoep.md +3 -3
- package/build/src/third_party/issue-descriptions/CoepCorpNotSameSite.md +2 -2
- package/build/src/third_party/issue-descriptions/CoepFrameResourceNeedsCoepHeader.md +1 -1
- package/build/src/third_party/issue-descriptions/CompatibilityModeQuirks.md +1 -1
- package/build/src/third_party/issue-descriptions/CookieAttributeValueExceedsMaxSize.md +2 -2
- package/build/src/third_party/issue-descriptions/LowTextContrast.md +1 -1
- package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureErrorRead.md +1 -1
- package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureErrorSet.md +1 -1
- package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureWarnRead.md +1 -1
- package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureWarnSet.md +1 -1
- package/build/src/third_party/issue-descriptions/SameSiteUnspecifiedLaxAllowUnsafeRead.md +3 -3
- package/build/src/third_party/issue-descriptions/SameSiteUnspecifiedLaxAllowUnsafeSet.md +3 -3
- package/build/src/third_party/issue-descriptions/bounceTrackingMitigations.md +1 -1
- package/build/src/third_party/issue-descriptions/connectionAllowlistEmbeddingRequirementNotSatisfied.md +26 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistIFrameAttributeLoosensEmbeddingRequirement.md +19 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidAllowConnectionAllowlistFrom.md +21 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidAllowlistItemType.md +2 -2
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidHeader.md +2 -2
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidUrlPattern.md +3 -3
- package/build/src/third_party/issue-descriptions/connectionAllowlistItemNotInnerList.md +2 -2
- package/build/src/third_party/issue-descriptions/connectionAllowlistMoreThanOneList.md +2 -2
- package/build/src/third_party/issue-descriptions/connectionAllowlistReportingEndpointNotToken.md +1 -1
- package/build/src/third_party/issue-descriptions/cookieCrossSiteRedirectDowngrade.md +3 -3
- package/build/src/third_party/issue-descriptions/cookieExcludeBlockedWithinRelatedWebsiteSet.md +1 -1
- package/build/src/third_party/issue-descriptions/cookieExcludePortMismatch.md +1 -1
- package/build/src/third_party/issue-descriptions/cookieExcludeSchemeMismatch.md +1 -1
- package/build/src/third_party/issue-descriptions/cookieExcludeThirdPartyPhaseoutSet.md +1 -1
- package/build/src/third_party/issue-descriptions/cookieWarnThirdPartyPhaseoutRead.md +1 -1
- package/build/src/third_party/issue-descriptions/corsAllowCredentialsRequired.md +1 -1
- package/build/src/third_party/issue-descriptions/corsDisabledScheme.md +2 -2
- package/build/src/third_party/issue-descriptions/corsDisallowedByMode.md +2 -2
- package/build/src/third_party/issue-descriptions/corsHeaderDisallowedByPreflightResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/corsInvalidHeaderValues.md +3 -3
- package/build/src/third_party/issue-descriptions/corsLocalNetworkAccessPermissionDenied.md +1 -1
- package/build/src/third_party/issue-descriptions/corsMethodDisallowedByPreflightResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/corsNoCorsRedirectModeNotFollow.md +1 -1
- package/build/src/third_party/issue-descriptions/corsOriginMismatch.md +3 -3
- package/build/src/third_party/issue-descriptions/corsPreflightResponseInvalid.md +2 -2
- package/build/src/third_party/issue-descriptions/corsRedirectContainsCredentials.md +3 -3
- package/build/src/third_party/issue-descriptions/corsWildcardOriginNotAllowed.md +4 -3
- package/build/src/third_party/issue-descriptions/cspEvalViolation.md +4 -4
- package/build/src/third_party/issue-descriptions/cspInlineViolation.md +2 -2
- package/build/src/third_party/issue-descriptions/cspTrustedTypesPolicyViolation.md +2 -2
- package/build/src/third_party/issue-descriptions/cspTrustedTypesSinkViolation.md +4 -4
- package/build/src/third_party/issue-descriptions/cspURLViolation.md +4 -4
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsEmptyList.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestDnsFetchFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestDnsInvalidRecord.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestInvalidEmail.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestJwksHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestJwksInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestKeyBindingSigningFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestRpOriginIsOpaque.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidSdJwt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenMalformedSdJwt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidAudience.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidIssuedAt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidNonce.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidSdHash.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidTyp.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingAud.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingCnf.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingIat.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingNonce.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingSdHash.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbSignatureFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmail.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmailVerified.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidHolderKey.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuedAt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuer.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtJwksMissingKeys.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingCnf.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingEmail.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIat.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIss.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtSignatureFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtUnsupportedHeaderAlg.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestUserLoggedOut.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownAccountsEndpointCrossOrigin.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownIssuanceEndpointCrossOrigin.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownListEmpty.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingAccountsEndpoint.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingIssuanceEndpoint.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownUnsupportedSigningAlgorithm.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsHttpNotFound.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsInvalidResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsNoResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestApprovalDeclined.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestCanceled.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestErrorFetchingSignin.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestErrorIdToken.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenHttpNotFound.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenInvalidRequest.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenInvalidResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenNoResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestInvalidSigninResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestManifestHttpNotFound.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestManifestInvalidResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestManifestNoResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestTooManyRequests.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestInvalidAccountsResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestInvalidConfigOrWellKnown.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoAccountSharingPermission.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoApiPermission.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoReturningUserFromFetchedAccounts.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotIframe.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotPotentiallyTrustworthy.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotSameOrigin.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotSignedInWithIdp.md +1 -1
- package/build/src/third_party/issue-descriptions/fetchingPartitionedBlobURL.md +2 -2
- package/build/src/third_party/issue-descriptions/genericBackUINavigationWouldSkipAd.md +4 -0
- package/build/src/third_party/issue-descriptions/genericFormAriaLabelledByToNonExistingIdError.md +2 -2
- package/build/src/third_party/issue-descriptions/genericFormAutocompleteAttributeEmptyError.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormDuplicateIdForInputError.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormEmptyIdAndNameAttributesForInputError.md +2 -2
- package/build/src/third_party/issue-descriptions/genericFormInputAssignedAutocompleteValueToIdOrNameAttributeError.md +2 -2
- package/build/src/third_party/issue-descriptions/genericFormInputHasWrongButWellIntendedAutocompleteValueError.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormInputWithNoLabelError.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormLabelForMatchesNonExistingIdError.md +2 -2
- package/build/src/third_party/issue-descriptions/genericFormLabelForNameError.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormLabelHasNeitherForNorNestedInputError.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormModelContextMissingToolName.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormModelContextParameterMissingName.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormModelContextRequiredParameterMissingName.md +1 -1
- package/build/src/third_party/issue-descriptions/genericNavigationEntryMarkedSkippable.md +2 -2
- package/build/src/third_party/issue-descriptions/heavyAd.md +1 -1
- package/build/src/third_party/issue-descriptions/lazyLoadImageZeroSize.md +7 -0
- package/build/src/third_party/issue-descriptions/navigatingPartitionedBlobURL.md +3 -3
- package/build/src/third_party/issue-descriptions/permissionElementActivationDisabled.md +1 -1
- package/build/src/third_party/issue-descriptions/permissionElementActivationDisabledWithOccluder.md +1 -1
- package/build/src/third_party/issue-descriptions/permissionElementActivationDisabledWithOccluderParent.md +1 -1
- package/build/src/third_party/issue-descriptions/permissionElementFencedFrameDisallowed.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementFontSizeTooLarge.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementFontSizeTooSmall.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementInsetBoxShadowUnsupported.md +1 -1
- package/build/src/third_party/issue-descriptions/permissionElementInvalidDisplayStyle.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementInvalidSizeValue.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementInvalidType.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementInvalidTypeActivation.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementNonOpaqueColor.md +1 -1
- package/build/src/third_party/issue-descriptions/permissionElementPaddingBottomUnsupported.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementPaddingRightUnsupported.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementRequestInProgress.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementSecurityChecksFailed.md +1 -1
- package/build/src/third_party/issue-descriptions/permissionElementTypeNotSupported.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementUntrustedEvent.md +1 -1
- package/build/src/third_party/issue-descriptions/placeholderDescriptionForInvisibleIssues.md +1 -1
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityDisallowedOptGroupChild.md +1 -1
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityDisallowedSelectChild.md +1 -1
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentAttributesSelectDescendant.md +1 -1
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentLegendChild.md +1 -1
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentOptionChild.md +1 -1
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityNonPhrasingContentOptionChild.md +1 -1
- package/build/src/third_party/issue-descriptions/selectivePermissionsIntervention.md +3 -3
- package/build/src/third_party/issue-descriptions/sharedArrayBuffer.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorCrossOriginNoCorsRequest.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorMatchingDictionaryNotUsed.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorUnexpectedContentDictionaryHeader.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorCossOriginNoCorsRequest.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorDisallowedBySettings.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorExpiredResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorFeatureDisabled.md +2 -2
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInvalidStructuredHeader.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNavigationRequest.md +2 -2
- package/build/src/third_party/issue-descriptions/sriInvalidSignatureHeader.md +4 -4
- package/build/src/third_party/issue-descriptions/sriInvalidSignatureInputHeader.md +4 -4
- package/build/src/third_party/issue-descriptions/sriMissingSignatureHeader.md +1 -1
- package/build/src/third_party/issue-descriptions/sriMissingSignatureInputHeader.md +2 -2
- package/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsIncorrectLength.md +2 -2
- package/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsNotByteSequence.md +4 -4
- package/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsParameterized.md +4 -4
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidComponentName.md +1 -1
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidComponentType.md +2 -2
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidDerivedComponentParameter.md +1 -1
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidHeaderComponentParameter.md +1 -1
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidParameter.md +2 -2
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderKeyIdLength.md +5 -5
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderMissingLabel.md +3 -3
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderMissingRequiredParameters.md +2 -2
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderValueMissingComponents.md +2 -2
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderValueNotInnerList.md +2 -2
- package/build/src/third_party/issue-descriptions/sriValidationFailedIntegrityMismatch.md +4 -4
- package/build/src/third_party/issue-descriptions/sriValidationFailedInvalidLength.md +3 -3
- package/build/src/third_party/issue-descriptions/sriValidationFailedSignatureExpired.md +2 -2
- package/build/src/third_party/issue-descriptions/sriValidationFailedSignatureMismatch.md +2 -2
- package/build/src/third_party/issue-descriptions/stylesheetLateImport.md +1 -1
- package/build/src/third_party/issue-descriptions/summaryElementAccessibilityInteractiveContentSummaryDescendant.md +1 -1
- package/build/src/third_party/issue-descriptions/unencodedDigestIncorrectDigestLength.md +2 -2
- package/build/src/third_party/issue-descriptions/unencodedDigestIncorrectDigestType.md +3 -3
- package/build/src/third_party/issue-descriptions/unencodedDigestMalformedDictionary.md +2 -2
- package/build/src/third_party/issue-descriptions/unencodedDigestUnknownAlgorithm.md +1 -1
- package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +3177 -6340
- package/build/src/tools/ToolDefinition.js +2 -2
- package/build/src/tools/categories.js +3 -0
- package/build/src/tools/console.js +13 -0
- package/build/src/tools/emulation.js +30 -3
- package/build/src/tools/extensions.js +8 -1
- package/build/src/tools/input.js +246 -128
- package/build/src/tools/lighthouse.js +14 -7
- package/build/src/tools/memory.js +289 -25
- package/build/src/tools/network.js +10 -7
- package/build/src/tools/pages.js +33 -92
- package/build/src/tools/performance.js +67 -52
- package/build/src/tools/pwa.js +128 -0
- package/build/src/tools/screencast.js +35 -12
- package/build/src/tools/screenshot.js +234 -75
- package/build/src/tools/script.js +130 -45
- package/build/src/tools/slim/tools.js +3 -0
- package/build/src/tools/snapshot.js +7 -4
- package/build/src/tools/thirdPartyDeveloper.js +19 -9
- package/build/src/tools/tools.js +3 -1
- package/build/src/tools/webmcp.js +3 -1
- package/build/src/utils/WaitForHelper.js +299 -0
- package/build/src/utils/check-for-updates.js +8 -2
- package/build/src/utils/files.js +42 -3
- package/build/src/utils/logger.js +51 -0
- package/build/src/utils/polyfill.js +11 -0
- package/build/src/utils/url.js +43 -0
- package/build/src/version.js +1 -1
- package/package.json +42 -33
- package/build/src/DevToolsConnectionAdapter.js +0 -70
- package/build/src/HeapSnapshotManager.js +0 -110
- package/build/src/Mutex.js +0 -38
- package/build/src/WaitForHelper.js +0 -195
- package/build/src/bin/cliDefinitions.js +0 -616
- package/build/src/logger.js +0 -37
- package/build/src/polyfill.js +0 -8
- package/build/src/telemetry/metricUtils.js +0 -15
- package/build/src/third_party/issue-descriptions/SameSiteExcludeContextDowngradeRead.md +0 -8
- package/build/src/third_party/issue-descriptions/SameSiteExcludeContextDowngradeSet.md +0 -8
- package/build/src/third_party/issue-descriptions/SameSiteExcludeNavigationContextDowngrade.md +0 -8
- package/build/src/third_party/issue-descriptions/SameSiteWarnCrossDowngradeRead.md +0 -8
- package/build/src/third_party/issue-descriptions/SameSiteWarnCrossDowngradeSet.md +0 -8
- package/build/src/third_party/issue-descriptions/SameSiteWarnStrictLaxDowngradeStrict.md +0 -8
- package/build/src/third_party/issue-descriptions/arInsecureContext.md +0 -7
- package/build/src/third_party/issue-descriptions/arInvalidInfoHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arInvalidRegisterOsSourceHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arInvalidRegisterOsTriggerHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arInvalidRegisterSourceHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arInvalidRegisterTriggerHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arNavigationRegistrationUniqueScopeAlreadySet.md +0 -5
- package/build/src/third_party/issue-descriptions/arNavigationRegistrationWithoutTransientUserActivation.md +0 -6
- package/build/src/third_party/issue-descriptions/arNoRegisterOsSourceHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arNoRegisterOsTriggerHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arNoRegisterSourceHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arNoRegisterTriggerHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arNoWebOrOsSupport.md +0 -4
- package/build/src/third_party/issue-descriptions/arOsSourceIgnored.md +0 -18
- package/build/src/third_party/issue-descriptions/arOsTriggerIgnored.md +0 -19
- package/build/src/third_party/issue-descriptions/arPermissionPolicyDisabled.md +0 -8
- package/build/src/third_party/issue-descriptions/arSourceAndTriggerHeaders.md +0 -9
- package/build/src/third_party/issue-descriptions/arSourceIgnored.md +0 -13
- package/build/src/third_party/issue-descriptions/arTriggerIgnored.md +0 -12
- package/build/src/third_party/issue-descriptions/arUntrustworthyReportingOrigin.md +0 -10
- package/build/src/third_party/issue-descriptions/arWebAndOsHeaders.md +0 -11
- package/build/src/utils/string.js +0 -37
- package/build/src/utils/types.js +0 -7
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Opera Norway AS. All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This file is an original work developed by Opera.
|
|
6
|
+
*/
|
|
7
|
+
import { ToolCategory } from '../tools/categories.js';
|
|
8
|
+
import { ensureBrowserFlagsForTool } from './browserFlags.js';
|
|
9
|
+
export function createOperaToolHooks(deps) {
|
|
10
|
+
return {
|
|
11
|
+
bypassMutex(tool) {
|
|
12
|
+
return tool.annotations.category === ToolCategory.OPERA;
|
|
13
|
+
},
|
|
14
|
+
async beforeInvoke(tool) {
|
|
15
|
+
await ensureBrowserFlagsForTool(tool.name, deps.serverArgs, deps.logFile, { resetContext: deps.resetContext });
|
|
16
|
+
},
|
|
17
|
+
makeLogCallback(extra) {
|
|
18
|
+
const sendNotification = extra?.sendNotification;
|
|
19
|
+
if (!sendNotification) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
return (message) => {
|
|
23
|
+
// `logger` carries the MCP request ID so the opera-cli bridge can route
|
|
24
|
+
// this chunk to the correct HTTP response (see bridge.ts requestLoggers).
|
|
25
|
+
// `data` stays a plain string so non-bridge MCP hosts (Claude Desktop,
|
|
26
|
+
// VS Code, etc.) continue to render it as readable text.
|
|
27
|
+
void sendNotification.call(extra, {
|
|
28
|
+
method: 'notifications/message',
|
|
29
|
+
params: {
|
|
30
|
+
level: 'info',
|
|
31
|
+
data: message,
|
|
32
|
+
logger: String(extra?.requestId),
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=toolHandlerHooks.js.map
|
|
@@ -4,28 +4,11 @@
|
|
|
4
4
|
*
|
|
5
5
|
* This file is an original work developed by Opera.
|
|
6
6
|
*/
|
|
7
|
-
import { zod } from '
|
|
8
|
-
import { ToolCategory } from '
|
|
9
|
-
import { definePageTool } from '
|
|
7
|
+
import { zod } from '../../third_party/index.js';
|
|
8
|
+
import { ToolCategory } from '../../tools/categories.js';
|
|
9
|
+
import { definePageTool } from '../../tools/ToolDefinition.js';
|
|
10
|
+
import { withServiceWorkerRetry } from '../serviceWorkerRetry.js';
|
|
10
11
|
const getCDPSession = (page) => page._client();
|
|
11
|
-
const MAX_SW_RETRIES = 5;
|
|
12
|
-
const SW_RETRY_DELAY_MS = 2500;
|
|
13
|
-
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
14
|
-
async function withServiceWorkerRetry(fn) {
|
|
15
|
-
let lastError;
|
|
16
|
-
for (let attempt = 0; attempt < MAX_SW_RETRIES; attempt++) {
|
|
17
|
-
try {
|
|
18
|
-
return await fn();
|
|
19
|
-
}
|
|
20
|
-
catch (e) {
|
|
21
|
-
lastError = e;
|
|
22
|
-
if (attempt < MAX_SW_RETRIES - 1) {
|
|
23
|
-
await sleep(SW_RETRY_DELAY_MS);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
throw lastError;
|
|
28
|
-
}
|
|
29
12
|
const dispatchAction = async (session, payload) => {
|
|
30
13
|
const response = (await withServiceWorkerRetry(() => session.send('Opera.dispatchAction', { payload })));
|
|
31
14
|
return response.result;
|
|
@@ -77,6 +60,7 @@ export const operaChat = definePageTool({
|
|
|
77
60
|
name: 'opera_chat',
|
|
78
61
|
description: "Send a chat prompt to Opera's built-in AI and return the response. Only available when connected to Opera Neon.",
|
|
79
62
|
blockedByDialog: false,
|
|
63
|
+
verifyFilesSchema: {},
|
|
80
64
|
annotations: {
|
|
81
65
|
category: ToolCategory.OPERA,
|
|
82
66
|
readOnlyHint: false,
|
|
@@ -111,6 +95,7 @@ export const operaDo = definePageTool({
|
|
|
111
95
|
name: 'opera_do',
|
|
112
96
|
description: "Instruct Opera's built-in AI to perform an action on the current page and return the result. Only available when connected to Opera Neon.",
|
|
113
97
|
blockedByDialog: false,
|
|
98
|
+
verifyFilesSchema: {},
|
|
114
99
|
annotations: {
|
|
115
100
|
category: ToolCategory.OPERA,
|
|
116
101
|
readOnlyHint: false,
|
|
@@ -142,6 +127,7 @@ export const operaMake = definePageTool({
|
|
|
142
127
|
name: 'opera_make',
|
|
143
128
|
description: "Ask Opera's built-in AI to create or generate content and return the result. Only available when connected to Opera Neon.",
|
|
144
129
|
blockedByDialog: false,
|
|
130
|
+
verifyFilesSchema: {},
|
|
145
131
|
annotations: {
|
|
146
132
|
category: ToolCategory.OPERA,
|
|
147
133
|
readOnlyHint: false,
|
|
@@ -168,6 +154,7 @@ export const operaResearch = definePageTool({
|
|
|
168
154
|
name: 'opera_research',
|
|
169
155
|
description: "Ask Opera's built-in AI to research a topic and return a summary. Only available when connected to Opera Neon.",
|
|
170
156
|
blockedByDialog: false,
|
|
157
|
+
verifyFilesSchema: {},
|
|
171
158
|
annotations: {
|
|
172
159
|
category: ToolCategory.OPERA,
|
|
173
160
|
readOnlyHint: false,
|
|
@@ -205,6 +192,7 @@ export const operaListModels = definePageTool({
|
|
|
205
192
|
name: 'opera_list_models',
|
|
206
193
|
description: 'List available AI models for Opera chat. Returns model IDs, display names, and which is the default. Only available when connected to Opera Neon.',
|
|
207
194
|
blockedByDialog: false,
|
|
195
|
+
verifyFilesSchema: {},
|
|
208
196
|
annotations: {
|
|
209
197
|
category: ToolCategory.OPERA,
|
|
210
198
|
readOnlyHint: true,
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import fsSync from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { DevTools } from '../third_party/index.js';
|
|
9
|
+
import { createIdGenerator, stableIdSymbol, } from '../utils/id.js';
|
|
10
|
+
export class HeapSnapshotManager {
|
|
11
|
+
#snapshotIdGenerator = createIdGenerator();
|
|
12
|
+
#snapshots = new Map();
|
|
13
|
+
async getSnapshot(filePath) {
|
|
14
|
+
const absolutePath = path.resolve(filePath);
|
|
15
|
+
const cached = this.#snapshots.get(absolutePath);
|
|
16
|
+
if (cached) {
|
|
17
|
+
return cached.snapshot;
|
|
18
|
+
}
|
|
19
|
+
const uid = this.#snapshotIdGenerator();
|
|
20
|
+
const { snapshot, worker } = await this.#loadSnapshot(absolutePath, uid);
|
|
21
|
+
this.#snapshots.set(absolutePath, {
|
|
22
|
+
snapshot,
|
|
23
|
+
worker,
|
|
24
|
+
idToClassKey: [''],
|
|
25
|
+
classKeyToId: new Map(),
|
|
26
|
+
});
|
|
27
|
+
return snapshot;
|
|
28
|
+
}
|
|
29
|
+
async #applyNodeFilter(snapshot, filter, filterName, objectId) {
|
|
30
|
+
if (filterName === 'attributedToSpecificNativeContext') {
|
|
31
|
+
if (objectId === undefined) {
|
|
32
|
+
throw new Error('objectId is required when filterName is attributedToSpecificNativeContext');
|
|
33
|
+
}
|
|
34
|
+
const nodeIndex = await snapshot.nodeIndexForId(objectId);
|
|
35
|
+
if (nodeIndex === undefined) {
|
|
36
|
+
throw new Error(`Node with ID ${objectId} not found`);
|
|
37
|
+
}
|
|
38
|
+
filter.filterName = `nativeContext_${nodeIndex}`;
|
|
39
|
+
}
|
|
40
|
+
else if (filterName) {
|
|
41
|
+
filter.filterName = filterName;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async getAggregates(filePath, filterName, objectId) {
|
|
45
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
46
|
+
const filter = new DevTools.HeapSnapshotModel.HeapSnapshotModel.NodeFilter();
|
|
47
|
+
await this.#applyNodeFilter(snapshot, filter, filterName, objectId);
|
|
48
|
+
const aggregates = await snapshot.aggregatesWithFilter(filter);
|
|
49
|
+
let objectCount = 0;
|
|
50
|
+
let totalSelfSize = 0;
|
|
51
|
+
for (const [key, aggregate] of Object.entries(aggregates)) {
|
|
52
|
+
const id = await this.getOrCreateIdForClassKey(filePath, key);
|
|
53
|
+
aggregate[stableIdSymbol] = id;
|
|
54
|
+
objectCount += aggregate.count;
|
|
55
|
+
totalSelfSize += aggregate.self;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
aggregates,
|
|
59
|
+
objectCount,
|
|
60
|
+
totalSelfSize,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
async getStats(filePath) {
|
|
64
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
65
|
+
return await snapshot.getStatistics();
|
|
66
|
+
}
|
|
67
|
+
async getStaticData(filePath) {
|
|
68
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
69
|
+
return snapshot.staticData;
|
|
70
|
+
}
|
|
71
|
+
async getNativeContextSizes(filePath) {
|
|
72
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
73
|
+
return await snapshot.getNativeContextSizes();
|
|
74
|
+
}
|
|
75
|
+
async getRetainedByContextSummary(filePath) {
|
|
76
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
77
|
+
return await snapshot.getRetainedByContextSummary();
|
|
78
|
+
}
|
|
79
|
+
async getOrCreateIdForClassKey(filePath, classKey) {
|
|
80
|
+
const cached = this.#getCachedSnapshot(filePath);
|
|
81
|
+
let id = cached.classKeyToId.get(classKey);
|
|
82
|
+
if (!id) {
|
|
83
|
+
id = cached.idToClassKey.length;
|
|
84
|
+
cached.classKeyToId.set(classKey, id);
|
|
85
|
+
cached.idToClassKey.push(classKey);
|
|
86
|
+
}
|
|
87
|
+
return id;
|
|
88
|
+
}
|
|
89
|
+
async getNodesById(filePath, id, filterName, objectId) {
|
|
90
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
91
|
+
const filter = new DevTools.HeapSnapshotModel.HeapSnapshotModel.NodeFilter();
|
|
92
|
+
await this.#applyNodeFilter(snapshot, filter, filterName, objectId);
|
|
93
|
+
const className = await this.resolveClassKeyFromId(filePath, id);
|
|
94
|
+
if (!className) {
|
|
95
|
+
throw new Error(`Class with ID ${id} not found in heap snapshot`);
|
|
96
|
+
}
|
|
97
|
+
const provider = snapshot.createNodesProviderForClass(className, filter);
|
|
98
|
+
return await provider.serializeItemsRange(0, Infinity);
|
|
99
|
+
}
|
|
100
|
+
async getRetainers(filePath, nodeId) {
|
|
101
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
102
|
+
const nodeIndex = await snapshot.nodeIndexForId(nodeId);
|
|
103
|
+
if (nodeIndex === undefined) {
|
|
104
|
+
throw new Error(`Node with ID ${nodeId} not found`);
|
|
105
|
+
}
|
|
106
|
+
const provider = snapshot.createRetainingEdgesProvider(nodeIndex);
|
|
107
|
+
return await provider.serializeItemsRange(0, Infinity);
|
|
108
|
+
}
|
|
109
|
+
async getObjectInfo(filePath, nodeId) {
|
|
110
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
111
|
+
const nodeIndex = await snapshot.nodeIndexForId(nodeId);
|
|
112
|
+
if (nodeIndex === undefined) {
|
|
113
|
+
throw new Error(`Node with ID ${nodeId} not found`);
|
|
114
|
+
}
|
|
115
|
+
return await snapshot.getObjectInfo(nodeIndex);
|
|
116
|
+
}
|
|
117
|
+
async getRetainingPaths(filePath, nodeId, maxDepth, maxNodes, maxSiblings) {
|
|
118
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
119
|
+
const nodeIndex = await snapshot.nodeIndexForId(nodeId);
|
|
120
|
+
if (nodeIndex === undefined) {
|
|
121
|
+
throw new Error(`Node with ID ${nodeId} not found`);
|
|
122
|
+
}
|
|
123
|
+
return await snapshot.getRetainingPaths(nodeIndex, maxDepth, maxNodes, maxSiblings);
|
|
124
|
+
}
|
|
125
|
+
async getDominatorsOf(filePath, nodeId) {
|
|
126
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
127
|
+
const nodeIndex = await snapshot.nodeIndexForId(nodeId);
|
|
128
|
+
if (nodeIndex === undefined) {
|
|
129
|
+
throw new Error(`Node with ID ${nodeId} not found`);
|
|
130
|
+
}
|
|
131
|
+
return await snapshot.getDominatorsOf(nodeIndex);
|
|
132
|
+
}
|
|
133
|
+
async getEdges(filePath, nodeId, options) {
|
|
134
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
135
|
+
const nodeIndex = await snapshot.nodeIndexForId(nodeId);
|
|
136
|
+
if (nodeIndex === undefined) {
|
|
137
|
+
throw new Error(`Node with ID ${nodeId} not found`);
|
|
138
|
+
}
|
|
139
|
+
const provider = snapshot.createEdgesProvider(nodeIndex, options);
|
|
140
|
+
return await provider.serializeItemsRange(0, Infinity);
|
|
141
|
+
}
|
|
142
|
+
async getClassDiffs(baseFilePath, currentFilePath) {
|
|
143
|
+
const rawDiffs = await this.#getSortedRawClassDiffs(baseFilePath, currentFilePath);
|
|
144
|
+
return rawDiffs.map(rawDiff => ({
|
|
145
|
+
className: rawDiff.name,
|
|
146
|
+
addedCount: rawDiff.addedCount,
|
|
147
|
+
removedCount: rawDiff.removedCount,
|
|
148
|
+
countDelta: rawDiff.countDelta,
|
|
149
|
+
addedSize: rawDiff.addedSize,
|
|
150
|
+
removedSize: rawDiff.removedSize,
|
|
151
|
+
sizeDelta: rawDiff.sizeDelta,
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
154
|
+
async getDetailedClassDiff(baseFilePath, currentFilePath, classIndex) {
|
|
155
|
+
const classDiffs = await this.#getSortedRawClassDiffs(baseFilePath, currentFilePath);
|
|
156
|
+
const rawDiff = classDiffs[classIndex];
|
|
157
|
+
if (!rawDiff) {
|
|
158
|
+
throw new Error(`Invalid classIndex: ${classIndex}. Total classes with changes: ${classDiffs.length}`);
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
className: rawDiff.name,
|
|
162
|
+
addedCount: rawDiff.addedCount,
|
|
163
|
+
removedCount: rawDiff.removedCount,
|
|
164
|
+
countDelta: rawDiff.countDelta,
|
|
165
|
+
addedSize: rawDiff.addedSize,
|
|
166
|
+
removedSize: rawDiff.removedSize,
|
|
167
|
+
sizeDelta: rawDiff.sizeDelta,
|
|
168
|
+
addedIds: rawDiff.addedIds ?? [],
|
|
169
|
+
addedSelfSizes: rawDiff.addedSelfSizes ?? [],
|
|
170
|
+
deletedIds: rawDiff.deletedIds ?? [],
|
|
171
|
+
deletedSelfSizes: rawDiff.deletedSelfSizes ?? [],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
#getCachedSnapshot(filePath) {
|
|
175
|
+
const absolutePath = path.resolve(filePath);
|
|
176
|
+
const cached = this.#snapshots.get(absolutePath);
|
|
177
|
+
if (!cached) {
|
|
178
|
+
throw new Error(`Snapshot not loaded for ${filePath}`);
|
|
179
|
+
}
|
|
180
|
+
return cached;
|
|
181
|
+
}
|
|
182
|
+
async #getSortedRawClassDiffs(baseFilePath, currentFilePath) {
|
|
183
|
+
const baseSnapshot = await this.getSnapshot(baseFilePath);
|
|
184
|
+
const currentSnapshot = await this.getSnapshot(currentFilePath);
|
|
185
|
+
const interfaceDefinitions = await currentSnapshot.interfaceDefinitions();
|
|
186
|
+
const aggregatesForDiff = await baseSnapshot.aggregatesForDiff(interfaceDefinitions);
|
|
187
|
+
const baseSnapshotId = baseSnapshot.uid;
|
|
188
|
+
if (baseSnapshotId === undefined) {
|
|
189
|
+
throw new Error('Base snapshot UID is undefined');
|
|
190
|
+
}
|
|
191
|
+
// DevTools calculateSnapshotDiff uses the first parameter (baseSnapshotId)
|
|
192
|
+
// as a cache key. We pass the unique UID of the base snapshot.
|
|
193
|
+
const rawDiffs = await currentSnapshot.calculateSnapshotDiff(baseSnapshotId, aggregatesForDiff);
|
|
194
|
+
// Return a filtered and sorted array here to ensure that
|
|
195
|
+
// compare_heapsnapshot_summary and compare_heapsnapshot_details agree
|
|
196
|
+
// on indices.
|
|
197
|
+
return Object.values(rawDiffs)
|
|
198
|
+
.filter(diff => diff.addedCount > 0 || diff.removedCount > 0)
|
|
199
|
+
.sort((a, b) => b.sizeDelta - a.sizeDelta);
|
|
200
|
+
}
|
|
201
|
+
async resolveClassKeyFromId(filePath, id) {
|
|
202
|
+
const cached = this.#getCachedSnapshot(filePath);
|
|
203
|
+
return cached.idToClassKey[id];
|
|
204
|
+
}
|
|
205
|
+
async #loadSnapshot(absolutePath, uid) {
|
|
206
|
+
const workerProxy = new DevTools.HeapSnapshotModel.HeapSnapshotProxy.HeapSnapshotWorkerProxy(() => {
|
|
207
|
+
/* noop */
|
|
208
|
+
}, DevTools.Common.Console.Console.instance(), import.meta.resolve('../third_party/devtools-heap-snapshot-worker.js'));
|
|
209
|
+
try {
|
|
210
|
+
const { promise: snapshotPromise, resolve: resolveSnapshot } = Promise.withResolvers();
|
|
211
|
+
const loaderProxy = workerProxy.createLoader(uid, snapshotProxy => {
|
|
212
|
+
resolveSnapshot(snapshotProxy);
|
|
213
|
+
});
|
|
214
|
+
const fileStream = fsSync.createReadStream(absolutePath, {
|
|
215
|
+
encoding: 'utf-8',
|
|
216
|
+
highWaterMark: 1024 * 1024,
|
|
217
|
+
});
|
|
218
|
+
for await (const chunk of fileStream) {
|
|
219
|
+
await loaderProxy.write(chunk);
|
|
220
|
+
}
|
|
221
|
+
await loaderProxy.close();
|
|
222
|
+
const snapshot = await snapshotPromise;
|
|
223
|
+
return { snapshot, worker: workerProxy };
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
// The worker is created before the read, and a failed load never reaches
|
|
227
|
+
// the #snapshots map, so dispose()/disposeAll() can never clean it up.
|
|
228
|
+
// Dispose it here to avoid leaking a worker on every failed load (e.g. a
|
|
229
|
+
// missing or invalid .heapsnapshot path).
|
|
230
|
+
workerProxy.dispose();
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async getDuplicateStrings(filePath) {
|
|
235
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
236
|
+
return await snapshot.getDuplicateStrings();
|
|
237
|
+
}
|
|
238
|
+
hasSnapshots() {
|
|
239
|
+
return this.#snapshots.size > 0;
|
|
240
|
+
}
|
|
241
|
+
disposeSnapshot(filePath) {
|
|
242
|
+
const absolutePath = path.resolve(filePath);
|
|
243
|
+
const cached = this.#snapshots.get(absolutePath);
|
|
244
|
+
if (cached) {
|
|
245
|
+
cached.worker.dispose();
|
|
246
|
+
this.#snapshots.delete(absolutePath);
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
dispose() {
|
|
252
|
+
for (const cached of this.#snapshots.values()) {
|
|
253
|
+
cached.worker.dispose();
|
|
254
|
+
}
|
|
255
|
+
this.#snapshots.clear();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=HeapSnapshotManager.js.map
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
import { logger } from '../logger.js';
|
|
7
6
|
import { DevTools } from '../third_party/index.js';
|
|
7
|
+
import { logger } from '../utils/logger.js';
|
|
8
8
|
const engine = DevTools.TraceEngine.TraceModel.Model.createWithAllHandlers();
|
|
9
9
|
export function traceResultIsSuccess(x) {
|
|
10
10
|
return 'parsedTrace' in x;
|
|
11
11
|
}
|
|
12
|
-
export async function parseRawTraceBuffer(buffer) {
|
|
12
|
+
export async function parseRawTraceBuffer(buffer, metadata) {
|
|
13
13
|
engine.resetProcessor();
|
|
14
14
|
if (!buffer) {
|
|
15
15
|
return {
|
|
@@ -25,7 +25,7 @@ export async function parseRawTraceBuffer(buffer) {
|
|
|
25
25
|
try {
|
|
26
26
|
const data = JSON.parse(asString);
|
|
27
27
|
const events = Array.isArray(data) ? data : data.traceEvents;
|
|
28
|
-
await engine.parse(events);
|
|
28
|
+
await engine.parse(events, { metadata });
|
|
29
29
|
const parsedTrace = engine.parsedTrace();
|
|
30
30
|
if (!parsedTrace) {
|
|
31
31
|
return {
|
|
@@ -40,7 +40,7 @@ export async function parseRawTraceBuffer(buffer) {
|
|
|
40
40
|
}
|
|
41
41
|
catch (e) {
|
|
42
42
|
const errorText = e instanceof Error ? e.message : JSON.stringify(e);
|
|
43
|
-
logger(`Unexpected error parsing trace: ${errorText}`);
|
|
43
|
+
logger?.(`Unexpected error parsing trace: ${errorText}`);
|
|
44
44
|
return {
|
|
45
45
|
error: errorText,
|
|
46
46
|
};
|
|
@@ -51,9 +51,9 @@ const extraFormatDescriptions = `Information on performance traces may contain m
|
|
|
51
51
|
${DevTools.PerformanceTraceFormatter.callFrameDataFormatDescription}
|
|
52
52
|
|
|
53
53
|
${DevTools.PerformanceTraceFormatter.networkDataFormatDescription}`;
|
|
54
|
-
export function getTraceSummary(result) {
|
|
54
|
+
export function getTraceSummary(result, deviceScope) {
|
|
55
55
|
const focus = DevTools.AgentFocus.fromParsedTrace(result.parsedTrace);
|
|
56
|
-
const formatter = new DevTools.PerformanceTraceFormatter(focus);
|
|
56
|
+
const formatter = new DevTools.PerformanceTraceFormatter(focus, deviceScope);
|
|
57
57
|
const summaryText = formatter.formatTraceSummary();
|
|
58
58
|
return `## Summary of Performance trace findings:
|
|
59
59
|
${summaryText}
|
|
@@ -61,7 +61,7 @@ ${summaryText}
|
|
|
61
61
|
## Details on call tree & network request formats:
|
|
62
62
|
${extraFormatDescriptions}`;
|
|
63
63
|
}
|
|
64
|
-
export function getInsightOutput(result, insightSetId, insightName) {
|
|
64
|
+
export function getInsightOutput(result, insightSetId, insightName, deviceScope) {
|
|
65
65
|
if (!result.insights) {
|
|
66
66
|
return {
|
|
67
67
|
error: 'No Performance insights are available for this trace.',
|
|
@@ -79,7 +79,7 @@ export function getInsightOutput(result, insightSetId, insightName) {
|
|
|
79
79
|
error: `No Insight with the name ${insightName} found. Double check the name you provided is accurate and try again.`,
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
-
const formatter = new DevTools.PerformanceInsightFormatter(DevTools.AgentFocus.fromParsedTrace(result.parsedTrace), matchingInsight);
|
|
82
|
+
const formatter = new DevTools.PerformanceInsightFormatter(DevTools.AgentFocus.fromParsedTrace(result.parsedTrace), matchingInsight, deviceScope);
|
|
83
83
|
return { output: formatter.formatInsight() };
|
|
84
84
|
}
|
|
85
|
-
//# sourceMappingURL=
|
|
85
|
+
//# sourceMappingURL=PerformanceTrace.js.map
|
|
@@ -5,124 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import process from 'node:process';
|
|
7
7
|
import { DAEMON_CLIENT_NAME } from '../daemon/utils.js';
|
|
8
|
-
import { logger } from '../logger.js';
|
|
8
|
+
import { logger } from '../utils/logger.js';
|
|
9
|
+
import { sanitizeParams, stripUnderscoreBeforeNumber } from './transformation.js';
|
|
9
10
|
import { McpClient, WatchdogMessageType, OsType, } from './types.js';
|
|
10
11
|
import { WatchdogClient } from './WatchdogClient.js';
|
|
11
12
|
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
12
|
-
export const PARAM_BLOCKLIST = new Set(['uid', 'reqid', 'msgid']);
|
|
13
|
-
const SUPPORTED_ZOD_TYPES = [
|
|
14
|
-
'ZodString',
|
|
15
|
-
'ZodNumber',
|
|
16
|
-
'ZodBoolean',
|
|
17
|
-
'ZodArray',
|
|
18
|
-
'ZodEnum',
|
|
19
|
-
];
|
|
20
|
-
function isZodType(type) {
|
|
21
|
-
return SUPPORTED_ZOD_TYPES.includes(type);
|
|
22
|
-
}
|
|
23
|
-
export function getZodType(zodType) {
|
|
24
|
-
const def = zodType._def;
|
|
25
|
-
const typeName = def.typeName;
|
|
26
|
-
if (typeName === 'ZodOptional' ||
|
|
27
|
-
typeName === 'ZodDefault' ||
|
|
28
|
-
typeName === 'ZodNullable') {
|
|
29
|
-
return getZodType(def.innerType);
|
|
30
|
-
}
|
|
31
|
-
if (typeName === 'ZodEffects') {
|
|
32
|
-
return getZodType(def.schema);
|
|
33
|
-
}
|
|
34
|
-
if (isZodType(typeName)) {
|
|
35
|
-
return typeName;
|
|
36
|
-
}
|
|
37
|
-
throw new Error(`Unsupported zod type for tool parameter: ${typeName}`);
|
|
38
|
-
}
|
|
39
|
-
export function transformArgName(zodType, name) {
|
|
40
|
-
const snakeCaseName = name.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
|
|
41
|
-
if (zodType === 'ZodString') {
|
|
42
|
-
return `${snakeCaseName}_length`;
|
|
43
|
-
}
|
|
44
|
-
else if (zodType === 'ZodArray') {
|
|
45
|
-
return `${snakeCaseName}_count`;
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
return snakeCaseName;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
export function transformArgType(zodType) {
|
|
52
|
-
if (zodType === 'ZodString' || zodType === 'ZodArray') {
|
|
53
|
-
return 'number';
|
|
54
|
-
}
|
|
55
|
-
switch (zodType) {
|
|
56
|
-
case 'ZodNumber':
|
|
57
|
-
return 'number';
|
|
58
|
-
case 'ZodBoolean':
|
|
59
|
-
return 'boolean';
|
|
60
|
-
case 'ZodEnum':
|
|
61
|
-
return 'enum';
|
|
62
|
-
default:
|
|
63
|
-
throw new Error(`Unsupported zod type for tool parameter: ${zodType}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
const BUCKETS = [
|
|
67
|
-
0, 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000,
|
|
68
|
-
];
|
|
69
|
-
function bucketize(value) {
|
|
70
|
-
for (const bucket of BUCKETS) {
|
|
71
|
-
if (bucket >= value) {
|
|
72
|
-
return bucket;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return BUCKETS[BUCKETS.length - 1];
|
|
76
|
-
}
|
|
77
|
-
function transformValue(zodType, value) {
|
|
78
|
-
if (zodType === 'ZodString') {
|
|
79
|
-
return bucketize(value.length);
|
|
80
|
-
}
|
|
81
|
-
else if (zodType === 'ZodArray') {
|
|
82
|
-
return value.length;
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
return value;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
function hasEquivalentType(zodType, value) {
|
|
89
|
-
if (zodType === 'ZodString') {
|
|
90
|
-
return typeof value === 'string';
|
|
91
|
-
}
|
|
92
|
-
else if (zodType === 'ZodArray') {
|
|
93
|
-
return Array.isArray(value);
|
|
94
|
-
}
|
|
95
|
-
else if (zodType === 'ZodNumber') {
|
|
96
|
-
return typeof value === 'number';
|
|
97
|
-
}
|
|
98
|
-
else if (zodType === 'ZodBoolean') {
|
|
99
|
-
return typeof value === 'boolean';
|
|
100
|
-
}
|
|
101
|
-
else if (zodType === 'ZodEnum') {
|
|
102
|
-
return (typeof value === 'string' ||
|
|
103
|
-
typeof value === 'number' ||
|
|
104
|
-
typeof value === 'boolean');
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
export function sanitizeParams(params, schema) {
|
|
111
|
-
const transformed = {};
|
|
112
|
-
for (const [name, value] of Object.entries(params)) {
|
|
113
|
-
if (PARAM_BLOCKLIST.has(name)) {
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
const zodType = getZodType(schema[name]);
|
|
117
|
-
if (!hasEquivalentType(zodType, value)) {
|
|
118
|
-
throw new Error(`parameter ${name} has type ${zodType} but value ${value} is not of equivalent type`);
|
|
119
|
-
}
|
|
120
|
-
const transformedName = transformArgName(zodType, name);
|
|
121
|
-
const transformedValue = transformValue(zodType, value);
|
|
122
|
-
transformed[transformedName] = transformedValue;
|
|
123
|
-
}
|
|
124
|
-
return transformed;
|
|
125
|
-
}
|
|
126
13
|
function detectOsType() {
|
|
127
14
|
switch (process.platform) {
|
|
128
15
|
case 'win32':
|
|
@@ -194,14 +81,18 @@ export class ClearcutLogger {
|
|
|
194
81
|
}
|
|
195
82
|
}
|
|
196
83
|
async logToolInvocation(args) {
|
|
84
|
+
const sanitizedToolName = stripUnderscoreBeforeNumber(args.toolName);
|
|
197
85
|
const tool_invocation = {
|
|
198
|
-
tool_name:
|
|
86
|
+
tool_name: sanitizedToolName,
|
|
199
87
|
success: args.success,
|
|
200
88
|
latency_ms: args.latencyMs,
|
|
201
89
|
};
|
|
90
|
+
if (Object.keys(args.context).length > 0) {
|
|
91
|
+
tool_invocation.context = args.context;
|
|
92
|
+
}
|
|
202
93
|
if (Object.keys(args.params).length > 0) {
|
|
203
94
|
tool_invocation.tool_params = {
|
|
204
|
-
[`${
|
|
95
|
+
[`${sanitizedToolName}_params`]: sanitizeParams(args.params, args.schema),
|
|
205
96
|
};
|
|
206
97
|
}
|
|
207
98
|
this.#watchdog.send({
|
|
@@ -248,7 +139,7 @@ export class ClearcutLogger {
|
|
|
248
139
|
}
|
|
249
140
|
}
|
|
250
141
|
catch (err) {
|
|
251
|
-
logger('Error in logDailyActiveIfNeeded:', err);
|
|
142
|
+
logger?.('Error in logDailyActiveIfNeeded:', err);
|
|
252
143
|
}
|
|
253
144
|
}
|
|
254
145
|
async logServerError(args) {
|
|
@@ -257,7 +148,9 @@ export class ClearcutLogger {
|
|
|
257
148
|
payload: {
|
|
258
149
|
mcp_client: this.#mcpClient,
|
|
259
150
|
server_error: {
|
|
260
|
-
tool_name: args.toolName
|
|
151
|
+
tool_name: args.toolName
|
|
152
|
+
? stripUnderscoreBeforeNumber(args.toolName)
|
|
153
|
+
: '',
|
|
261
154
|
error_code: args.errorCode,
|
|
262
155
|
},
|
|
263
156
|
},
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
import { logger } from '../logger.js';
|
|
8
|
+
import { logger } from '../utils/logger.js';
|
|
9
9
|
export class WatchdogClient {
|
|
10
10
|
#childProcess;
|
|
11
11
|
constructor(config, options) {
|
|
@@ -35,10 +35,10 @@ export class WatchdogClient {
|
|
|
35
35
|
});
|
|
36
36
|
this.#childProcess.unref();
|
|
37
37
|
this.#childProcess.on('error', err => {
|
|
38
|
-
logger('Watchdog process error:', err);
|
|
38
|
+
logger?.('Watchdog process error:', err);
|
|
39
39
|
});
|
|
40
40
|
this.#childProcess.on('exit', (code, signal) => {
|
|
41
|
-
logger(`Watchdog exited with code ${code} and signal ${signal}`);
|
|
41
|
+
logger?.(`Watchdog exited with code ${code} and signal ${signal}`);
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
send(message) {
|
|
@@ -50,11 +50,11 @@ export class WatchdogClient {
|
|
|
50
50
|
this.#childProcess.stdin.write(line);
|
|
51
51
|
}
|
|
52
52
|
catch (err) {
|
|
53
|
-
logger('Failed to write to watchdog stdin', err);
|
|
53
|
+
logger?.('Failed to write to watchdog stdin', err);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
else {
|
|
57
|
-
logger('Watchdog stdin not available, dropping message');
|
|
57
|
+
logger?.('Watchdog stdin not available, dropping message');
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -13,6 +13,6 @@ export var ErrorCode;
|
|
|
13
13
|
(function (ErrorCode) {
|
|
14
14
|
ErrorCode[ErrorCode["ERROR_CODE_UNSPECIFIED"] = 0] = "ERROR_CODE_UNSPECIFIED";
|
|
15
15
|
ErrorCode[ErrorCode["ERROR_CODE_PERSISTENCE_FILE_READ_FAILED"] = 1] = "ERROR_CODE_PERSISTENCE_FILE_READ_FAILED";
|
|
16
|
-
ErrorCode[ErrorCode["ERROR_CODE_PERSISTENCE_FILE_SAVE_FAILED"] =
|
|
16
|
+
ErrorCode[ErrorCode["ERROR_CODE_PERSISTENCE_FILE_SAVE_FAILED"] = 2] = "ERROR_CODE_PERSISTENCE_FILE_SAVE_FAILED";
|
|
17
17
|
})(ErrorCode || (ErrorCode = {}));
|
|
18
18
|
//# sourceMappingURL=errors.js.map
|
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
* @license
|
|
3
3
|
* Copyright 2026 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*
|
|
6
|
-
* Modified by Opera Software AS.
|
|
7
5
|
*/
|
|
8
|
-
import {
|
|
6
|
+
import { DevTools } from '../third_party/index.js';
|
|
7
|
+
import { stripUnderscoreBeforeNumber } from './transformation.js';
|
|
8
|
+
const { StringUtilities } = DevTools.Platform;
|
|
9
9
|
/**
|
|
10
10
|
* For enums, log the value as uppercase.
|
|
11
11
|
* We're going to have an enum for such flags with choices represented
|
|
12
12
|
* as an `enum` where the keys of the enum will map to the uppercase `choice`.
|
|
13
13
|
*/
|
|
14
14
|
function formatEnumChoice(snakeCaseName, choice) {
|
|
15
|
-
return `${snakeCaseName}_${choice}
|
|
15
|
+
return stripUnderscoreBeforeNumber(`${snakeCaseName}_${choice}`).toUpperCase();
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
18
|
* Computes telemetry flag usage from parsed arguments and CLI options.
|
|
@@ -31,7 +31,7 @@ export function computeFlagUsage(args, options) {
|
|
|
31
31
|
const usage = {};
|
|
32
32
|
for (const [flagName, config] of Object.entries(options)) {
|
|
33
33
|
const value = args[flagName];
|
|
34
|
-
const snakeCaseName = toSnakeCase(flagName);
|
|
34
|
+
const snakeCaseName = stripUnderscoreBeforeNumber(StringUtilities.toSnakeCase(flagName));
|
|
35
35
|
// If there isn't a default value provided for the flag,
|
|
36
36
|
// we're going to log whether it's present on the args user
|
|
37
37
|
// provided or not. If there is a default value, we only log presence
|
|
@@ -60,7 +60,7 @@ export function computeFlagUsage(args, options) {
|
|
|
60
60
|
export function getPossibleFlagMetrics(options) {
|
|
61
61
|
const metrics = [];
|
|
62
62
|
for (const [flagName, config] of Object.entries(options)) {
|
|
63
|
-
const snakeCaseName = toSnakeCase(flagName);
|
|
63
|
+
const snakeCaseName = stripUnderscoreBeforeNumber(StringUtilities.toSnakeCase(flagName));
|
|
64
64
|
// _present is always a possible metric
|
|
65
65
|
metrics.push({
|
|
66
66
|
name: `${snakeCaseName}_present`,
|