@xenon-device-management/xenon 2.14.0 → 2.15.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 +7 -4
- package/lib/package.json +5 -5
- package/lib/public/assets/{AnnotationOverlay-B_VNtKBH.js → AnnotationOverlay-Btq3dZRI.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-BwuaMjkX.js → ApiKeyGate-C9OoAgg_.js} +1 -1
- package/lib/public/assets/DeviceMosaicView-DBC_xczB.js +2 -0
- package/lib/public/assets/{EmptyState-BSjhk_IV.js → EmptyState-DqWU2-vM.js} +1 -1
- package/lib/public/assets/{FieldGroup-Cb5QZsE1.js → FieldGroup-CveQ3xrA.js} +1 -1
- package/lib/public/assets/{FilterMenu-BAaSlWbW.js → FilterMenu-BfKDSfHz.js} +1 -1
- package/lib/public/assets/{Menu-JGGYrSQQ.js → Menu-CtdCybzA.js} +1 -1
- package/lib/public/assets/{Modal-CFCp3n9d.js → Modal-Bsvj1tm2.js} +1 -1
- package/lib/public/assets/{RecordingPage-CbJj_EBT.js → RecordingPage-CRofOiyx.js} +1 -1
- package/lib/public/assets/{RecordingsPage-BJeYnygR.js → RecordingsPage-jNQHb2IR.js} +1 -1
- package/lib/public/assets/{SegmentedControl-Bg42Atjy.js → SegmentedControl-C8HtTha-.js} +1 -1
- package/lib/public/assets/{SettingCard-Njz1pUIJ.js → SettingCard-D8K1LZNo.js} +1 -1
- package/lib/public/assets/{Table-BcTm5xV2.js → Table-zNTX92Pn.js} +1 -1
- package/lib/public/assets/{activity-DOVdsfMQ.js → activity-CJ8LwZq9.js} +1 -1
- package/lib/public/assets/ai-settings-CgxQZqrc.js +16 -0
- package/lib/public/assets/{api-keys-BKnNSTw9.js → api-keys-ECpo-zTo.js} +1 -1
- package/lib/public/assets/{apps-D0SC6uw6.js → apps-BjGymyf3.js} +1 -1
- package/lib/public/assets/{arrow-left-MMbTmgoE.js → arrow-left-BWfCbeHP.js} +1 -1
- package/lib/public/assets/{arrow-right-BMfnGmxW.js → arrow-right-DC7UGf_n.js} +1 -1
- package/lib/public/assets/{arrow-up-right-DuAftMWB.js → arrow-up-right-BOjwybDk.js} +1 -1
- package/lib/public/assets/{auth-shell-nZC2JNDO.js → auth-shell-CxMTo9aH.js} +2 -2
- package/lib/public/assets/{builds-page-hDCgTwkS.js → builds-page-DOCm4HkA.js} +1 -1
- package/lib/public/assets/{button-Dda4irn_.js → button-Cg08-EMT.js} +1 -1
- package/lib/public/assets/{calendar-BMr0Q62v.js → calendar-CA1ikLJs.js} +1 -1
- package/lib/public/assets/{check-CZ9iw9-e.js → check-BMTaH72W.js} +1 -1
- package/lib/public/assets/{chevron-right-BRYJJVC9.js → chevron-right-B2QOv9Nx.js} +1 -1
- package/lib/public/assets/{circle-check-CqAJjQ5O.js → circle-check-CYXBoUqT.js} +1 -1
- package/lib/public/assets/{circle-x-DlDJtnVz.js → circle-x-DTwnSAnl.js} +1 -1
- package/lib/public/assets/{clock-CpuDKysU.js → clock-C8_HViQP.js} +1 -1
- package/lib/public/assets/{copy-CAQydNFe.js → copy-Be7LNMXV.js} +1 -1
- package/lib/public/assets/derive-UnLRp4r3.js +1 -0
- package/lib/public/assets/device-explorer-BOqtfGXz.js +282 -0
- package/lib/public/assets/{download-C9XG6lLv.js → download-B56lwxWP.js} +1 -1
- package/lib/public/assets/forgot-password-CCU6bzLn.js +1 -0
- package/lib/public/assets/{h264Stream-VQBphYhg.js → h264Stream-C-OtmIuk.js} +6 -6
- package/lib/public/assets/index-B9KQ6LsY.js +15 -0
- package/lib/public/assets/{index-BbKEXUFH.js → index-BOxe43p1.js} +2 -2
- package/lib/public/assets/{index-DJGc1M2f.js → index-CfxsUslp.js} +1 -1
- package/lib/public/assets/{input-Cm0tmSoc.js → input-CZHFjbSg.js} +1 -1
- package/lib/public/assets/{line-chart-D2DyJCml.js → line-chart-Gl_Dz3X_.js} +1 -1
- package/lib/public/assets/{list-checks-UWf1GY99.js → list-checks-CCgjeyGY.js} +1 -1
- package/lib/public/assets/{lock-CCJv9m_I.js → lock-CkOwgDcx.js} +1 -1
- package/lib/public/assets/{login-f8vOVW-U.js → login-D5cc4w7M.js} +1 -1
- package/lib/public/assets/{maintenance-settings-C1PrkuAH.js → maintenance-settings-t7qr3fvX.js} +1 -1
- package/lib/public/assets/{monitor-B2jDgwm7.js → monitor-DEJixKQO.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-DLI1RtzU.js → mouse-pointer-2-CtOrk4M8.js} +1 -1
- package/lib/public/assets/{network-ebT86qoi.js → network-Dii9qdvk.js} +1 -1
- package/lib/public/assets/{overview-C0aykVrd.js → overview-rolAACrO.js} +1 -1
- package/lib/public/assets/{page-header-BjzgWz-T.js → page-header-COQZxJfR.js} +1 -1
- package/lib/public/assets/{play-DLG9t8FU.js → play-DrN1Se_R.js} +1 -1
- package/lib/public/assets/{plus-nBFSMu_X.js → plus-5aq-9uRx.js} +1 -1
- package/lib/public/assets/{profile-page-CSl0UYlv.js → profile-page-6i5kY6kF.js} +1 -1
- package/lib/public/assets/{reset-password-LeOuHqmB.js → reset-password-sp2IBrc7.js} +1 -1
- package/lib/public/assets/runbook-page-DJDjeopU.js +253 -0
- package/lib/public/assets/{select-88jHcl-Z.js → select-Bpyn7rWO.js} +1 -1
- package/lib/public/assets/{selector-detail-redirect-BbXKJjZM.js → selector-detail-redirect-BH74vmKv.js} +1 -1
- package/lib/public/assets/{selector-health-page-B4rA1Xkv.js → selector-health-page-Cw_g218H.js} +1 -1
- package/lib/public/assets/session-detail-page-AZJE6XXE.js +4 -0
- package/lib/public/assets/{settings-GF6ZMSQ6.js → settings-C3uyGhyc.js} +1 -1
- package/lib/public/assets/{settings-Dep_3kbA.css → settings-K8UHWMdI.css} +1 -1
- package/lib/public/assets/{stat-tile-BV98e_Sv.js → stat-tile-xVqAKIlI.js} +1 -1
- package/lib/public/assets/{tablet-BiAsnFDj.js → tablet-BxUScAeO.js} +1 -1
- package/lib/public/assets/{teams-B4-jrr-1.js → teams-B5ljGkY2.js} +1 -1
- package/lib/public/assets/{trash-2-C5RSuX0e.js → trash-2-B701i8sy.js} +1 -1
- package/lib/public/assets/{upload-BG7_rMXK.js → upload-C2gr44RS.js} +1 -1
- package/lib/public/assets/{use-builds-data-J-riQYyC.js → use-builds-data-NeQOFEoy.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-C8-eH-UG.js → use-password-reset-mode-DEzJq5ef.js} +1 -1
- package/lib/public/assets/{users-CFba5TSy.js → users-D_vv_yz4.js} +1 -1
- package/lib/public/assets/{users-Cp0beJm3.js → users-MY6eNe6s.js} +1 -1
- package/lib/public/assets/{video-off-DDsEISjV.js → video-off-OK1lNZQn.js} +1 -1
- package/lib/public/assets/{webhook-settings-BGu32vSB.js → webhook-settings-BU-C9lBf.js} +1 -1
- package/lib/public/assets/{zap-l4VaXzq8.js → zap-DO9ry1iz.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/schema.json +5 -5
- package/lib/src/app/dashboardPluginLink.js +117 -0
- package/lib/src/app/index.js +13 -34
- package/lib/src/app/openapi/control.yaml +6 -4
- package/lib/src/app/openapi/grid.yaml +8 -2
- package/lib/src/app/openapi/identity.yaml +103 -31
- package/lib/src/app/openapi/platform.yaml +33 -15
- package/lib/src/app/openapi/sessions.yaml +10 -3
- package/lib/src/app/routers/apikeys.js +14 -2
- package/lib/src/app/routers/auth.js +29 -5
- package/lib/src/app/routers/control.js +13 -5
- package/lib/src/app/routers/dashboard.js +42 -27
- package/lib/src/app/routers/grid.js +5 -3
- package/lib/src/app/routers/profile.js +21 -0
- package/lib/src/app/routers/users.js +10 -5
- package/lib/src/app/swagger.js +2 -0
- package/lib/src/app/ws/h264StreamWs.js +10 -1
- package/lib/src/config.js +1 -0
- package/lib/src/dashboard/commands.js +5 -5
- package/lib/src/dashboard/event-manager.js +49 -61
- package/lib/src/dashboard/services/failure-analysis-service.js +136 -45
- package/lib/src/dashboard/sessionCommandSummary.js +39 -0
- package/lib/src/data-service/device-store.js +14 -1
- package/lib/src/data-service/prisma-store.js +19 -7
- package/lib/src/data-service/web-config-service.js +7 -0
- package/lib/src/device-managers/android/AndroidH264StreamService.js +69 -20
- package/lib/src/device-managers/android/H264Multiplexer.js +38 -4
- package/lib/src/device-utils.js +20 -2
- package/lib/src/gateway/defaultGateway.js +1 -1
- package/lib/src/gateway/forwardToNode.js +2 -0
- package/lib/src/gateway/healReport.js +87 -0
- package/lib/src/gateway/sessionGateway.js +12 -2
- package/lib/src/generated/client/edge.js +4 -3
- package/lib/src/generated/client/index-browser.js +1 -0
- package/lib/src/generated/client/index.d.ts +32 -0
- package/lib/src/generated/client/index.js +4 -3
- package/lib/src/generated/client/package.json +1 -1
- package/lib/src/generated/client/schema.prisma +3 -0
- package/lib/src/generated/client/wasm.js +1 -0
- package/lib/src/interceptors/CommandInterceptor.js +243 -181
- package/lib/src/middleware/authMiddleware.js +15 -3
- package/lib/src/middleware/commandCaller.js +3 -3
- package/lib/src/middleware/verifyCredential.js +58 -1
- package/lib/src/plugin.js +8 -3
- package/lib/src/services/AICommandService.js +9 -2
- package/lib/src/services/AIService.js +192 -42
- package/lib/src/services/EmailService.js +29 -0
- package/lib/src/services/OrphanSweeper.js +2 -0
- package/lib/src/services/SelectorVerificationJob.js +15 -4
- package/lib/src/services/ServerManager.js +11 -1
- package/lib/src/services/SessionLifecycleService.js +86 -26
- package/lib/src/services/SocketServer.js +19 -3
- package/lib/src/services/bug-report/video-slice.js +4 -1
- package/lib/src/services/device-access/leaseOverride.js +7 -5
- package/lib/src/services/healing/FuzzyXmlHealingProvider.js +51 -0
- package/lib/src/services/healing/HealEtalonService.js +3 -2
- package/lib/src/services/healing/HealingOrchestrator.js +55 -72
- package/lib/src/services/healing/OcrHealingProvider.js +41 -64
- package/lib/src/services/healing/ResilioTreeHealingProvider.js +41 -50
- package/lib/src/services/healing/VisualAiHealingProvider.js +1 -1
- package/lib/src/services/healing/fingerprintIdentity.js +126 -0
- package/lib/src/services/healing/resilioPath.js +274 -0
- package/lib/src/services/logcat/SessionDeviceLogs.js +271 -0
- package/lib/src/services/logcat/deviceLogBook.js +163 -0
- package/lib/src/services/ocr/ocrData.js +118 -0
- package/lib/src/services/ocr/vendor/LICENSE-tessdata.txt +202 -0
- package/lib/src/services/ocr/vendor/eng.traineddata.gz +0 -0
- package/lib/src/services/omni-vision/OmniVisionService.js +83 -56
- package/lib/src/services/passwordResetLink.js +50 -9
- package/lib/src/services/passwordResetMode.js +3 -1
- package/lib/src/services/publicUrl.js +45 -0
- package/lib/src/services/session/sessionIdentity.js +5 -3
- package/lib/src/services/session/shutdownReason.js +26 -0
- package/lib/src/services/settings/aiEngineSettings.js +219 -0
- package/lib/src/services/token/mintedLifetime.js +51 -0
- package/lib/src/sessions/SessionManager.js +8 -2
- package/lib/src/sessions/sessionMemory.js +25 -0
- package/lib/test/helpers/fake-ai-provider.js +188 -0
- package/lib/test/helpers/loki-stores.js +33 -0
- package/lib/test/helpers/page-sources.js +164 -0
- package/lib/test/helpers/scratch-database.js +57 -13
- package/lib/test/integration/audit-ingest.spec.js +10 -10
- package/lib/test/integration/auth-flow.spec.js +9 -14
- package/lib/test/integration/auth-rate-limit.spec.js +6 -3
- package/lib/test/integration/bug-report-route.spec.js +22 -2
- package/lib/test/integration/forgot-password-rate-limit.spec.js +6 -3
- package/lib/test/integration/forgot-password.spec.js +17 -18
- package/lib/test/integration/legacy-deprecation-headers.spec.js +5 -2
- package/lib/test/integration/migration.spec.js +6 -1
- package/lib/test/integration/node-pair-auth-rest.spec.js +5 -9
- package/lib/test/integration/ports-router.spec.js +4 -4
- package/lib/test/integration/prisma-device-store-add-devices.spec.js +2 -3
- package/lib/test/integration/prisma-device-store-team-filter.spec.js +2 -5
- package/lib/test/integration/profile-tokens.spec.js +10 -16
- package/lib/test/integration/recordings-router.spec.js +16 -16
- package/lib/test/integration/reset-revokes-sessions.spec.js +4 -9
- package/lib/test/integration/role-matrix.spec.js +4 -9
- package/lib/test/integration/sdk-leases-router.spec.js +9 -9
- package/lib/test/integration/sdk-version.spec.js +2 -2
- package/lib/test/integration/selector-health-endpoint.spec.js +5 -5
- package/lib/test/integration/team-membership-migration.spec.js +73 -12
- package/lib/test/integration/team-visibility-apps.spec.js +15 -22
- package/lib/test/integration/team-visibility-control.spec.js +96 -69
- package/lib/test/integration/team-visibility-dashboard.spec.js +7 -23
- package/lib/test/integration/team-visibility-grid.spec.js +16 -15
- package/lib/test/integration/team-visibility-leases.spec.js +8 -11
- package/lib/test/integration/team-visibility-recordings.spec.js +6 -19
- package/lib/test/integration/team-visibility-sessions.spec.js +6 -19
- package/lib/test/integration/users-flow.spec.js +7 -12
- package/lib/test/integration/users-lockout.spec.js +5 -3
- package/lib/test/unit/EmailService.test.js +66 -0
- package/lib/test/unit/ResilioTreeHealingProvider.test.js +46 -19
- package/lib/test/unit/ai-call-time-limit.spec.js +130 -0
- package/lib/test/unit/ai-engine-settings.spec.js +338 -0
- package/lib/test/unit/ai-provider-switch.spec.js +130 -0
- package/lib/test/unit/ai-rate-limit.spec.js +126 -0
- package/lib/test/unit/android-h264-viewers.spec.js +272 -0
- package/lib/test/unit/android-preview-hold.spec.js +3 -2
- package/lib/test/unit/apikeys-router.spec.js +84 -0
- package/lib/test/unit/authMiddleware.bearer.spec.js +9 -0
- package/lib/test/unit/authMiddleware.credentialLifetime.spec.js +149 -0
- package/lib/test/unit/bug-report/video-slice.spec.js +19 -0
- package/lib/test/unit/dashboard-events-device-logs.spec.js +207 -0
- package/lib/test/unit/dashboard-plugin-link.spec.js +213 -0
- package/lib/test/unit/device-log-book.spec.js +180 -0
- package/lib/test/unit/enable-dashboard-option.spec.js +57 -0
- package/lib/test/unit/event-log-session-data.spec.js +244 -0
- package/lib/test/unit/failure-analysis.spec.js +182 -0
- package/lib/test/unit/failure-categories.spec.js +128 -0
- package/lib/test/unit/fingerprint-identity.spec.js +433 -0
- package/lib/test/unit/h264-multiplexer.spec.js +46 -0
- package/lib/test/unit/heal-report.spec.js +71 -0
- package/lib/test/unit/healing-instrumentation.spec.js +6 -1
- package/lib/test/unit/healing-tier-gate.spec.js +73 -29
- package/lib/test/unit/healing-tiers-every-session.spec.js +219 -0
- package/lib/test/unit/hub-node-gateway-appium-server.spec.js +171 -0
- package/lib/test/unit/interceptor-visual-elements.spec.js +192 -0
- package/lib/test/unit/lease/lease-override.spec.js +23 -6
- package/lib/test/unit/lease/session-lease-proof.spec.js +16 -4
- package/lib/test/unit/ocr-healing-tier-real-ocr.spec.js +113 -0
- package/lib/test/unit/ocr-healing-tier.spec.js +122 -0
- package/lib/test/unit/ocr-offline.spec.js +213 -0
- package/lib/test/unit/omni-virtual-element-store.spec.js +301 -0
- package/lib/test/unit/omni-vision-ios-points.spec.js +0 -2
- package/lib/test/unit/omni-vision.spec.js +5 -7
- package/lib/test/unit/package-engines.spec.js +27 -0
- package/lib/test/unit/passwordResetMode.test.js +10 -2
- package/lib/test/unit/profile-router.test.js +122 -1
- package/lib/test/unit/queue-requester.spec.js +1 -1
- package/lib/test/unit/reset-link-address.spec.js +266 -0
- package/lib/test/unit/reset-link.test.js +10 -4
- package/lib/test/unit/resilio-path-healing.spec.js +270 -0
- package/lib/test/unit/selector-learning-every-session.spec.js +179 -0
- package/lib/test/unit/selector-verification-clean-build.spec.js +130 -0
- package/lib/test/unit/selector-verification-job.spec.js +13 -13
- package/lib/test/unit/self-healing-boot.spec.js +10 -5
- package/lib/test/unit/self-healing-setting.spec.js +7 -6
- package/lib/test/unit/session-app-resolution.spec.js +5 -1
- package/lib/test/unit/session-attribution-wiring.spec.js +41 -13
- package/lib/test/unit/session-create-credential-owner.spec.js +242 -0
- package/lib/test/unit/session-credential-strip.spec.js +1 -1
- package/lib/test/unit/session-device-logs.spec.js +347 -0
- package/lib/test/unit/session-end-failure-analysis.spec.js +133 -0
- package/lib/test/unit/session-log-indexes.spec.js +64 -3
- package/lib/test/unit/socketServer.sessionCookie.spec.js +2 -1
- package/lib/test/unit/stream-start-pipelines.spec.js +72 -18
- package/lib/test/unit/token-mint-limits.spec.js +165 -0
- package/lib/test/unit/unexpected-shutdown-reason.spec.js +163 -0
- package/lib/test/unit/verify-credential.spec.js +33 -0
- package/lib/test/unit/vision-assertion.spec.js +3 -2
- package/lib/test/unit/web-config-service.spec.js +19 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/prisma/migrations/20261004174413_locator_etalon_path/migration.sql +2 -0
- package/prisma/schema.prisma +3 -0
- package/schema.json +5 -5
- package/lib/public/assets/BugReportButton-BAt0_pzq.js +0 -15
- package/lib/public/assets/DeviceMosaicView-BYaQHhTf.js +0 -2
- package/lib/public/assets/ai-settings-CsuJroRf.js +0 -21
- package/lib/public/assets/device-explorer-CdO6_nrn.js +0 -282
- package/lib/public/assets/forgot-password-CGnkRgE9.js +0 -1
- package/lib/public/assets/runbook-page-CMMeQml9.js +0 -124
- package/lib/public/assets/session-detail-page-COL_RZxw.js +0 -4
- /package/lib/public/assets/{BugReportButton-CPBjfiv4.css → index-CPBjfiv4.css} +0 -0
|
@@ -67,8 +67,6 @@ const appium_ios_device_1 = require("appium-ios-device");
|
|
|
67
67
|
const config_1 = require("../config");
|
|
68
68
|
const helpers_2 = require("../helpers");
|
|
69
69
|
const typedi_1 = require("typedi");
|
|
70
|
-
const device_managers_1 = require("../device-managers");
|
|
71
|
-
const AndroidDeviceManager_1 = __importDefault(require("../device-managers/AndroidDeviceManager"));
|
|
72
70
|
const IOSStreamService_1 = __importDefault(require("../device-managers/ios/IOSStreamService"));
|
|
73
71
|
const AndroidStreamService_1 = __importDefault(require("../device-managers/android/AndroidStreamService"));
|
|
74
72
|
const SocketServer_1 = require("../services/SocketServer");
|
|
@@ -80,7 +78,9 @@ const SelectorStateService_1 = require("../services/SelectorStateService");
|
|
|
80
78
|
const recording_store_1 = require("../services/recording/recording-store");
|
|
81
79
|
const NotificationService_1 = require("../services/NotificationService");
|
|
82
80
|
const SessionMetricsService_1 = require("../services/metrics/SessionMetricsService");
|
|
81
|
+
const SessionDeviceLogs_1 = require("../services/logcat/SessionDeviceLogs");
|
|
83
82
|
const nodeMetrics_1 = require("../services/metrics/nodeMetrics");
|
|
83
|
+
const sessionCommandSummary_1 = require("./sessionCommandSummary");
|
|
84
84
|
const typedi_2 = require("typedi");
|
|
85
85
|
/**
|
|
86
86
|
* What a Session row keeps of the capabilities the driver returned. The row
|
|
@@ -106,7 +106,7 @@ let DashboardEventManager = class DashboardEventManager {
|
|
|
106
106
|
this.sessionToUdid = new Map();
|
|
107
107
|
// Map session ID to device info
|
|
108
108
|
this.sessionToDevice = new Map();
|
|
109
|
-
// Track last log line for each session (
|
|
109
|
+
// Track last log line for each session (an iPhone's device logs)
|
|
110
110
|
this.lastLogLine = new Map();
|
|
111
111
|
// Track start time for each command to calculate duration
|
|
112
112
|
this.commandStartTime = new Map();
|
|
@@ -158,6 +158,14 @@ let DashboardEventManager = class DashboardEventManager {
|
|
|
158
158
|
if (sampled) {
|
|
159
159
|
metrics.start(Object.assign({ sessionId: session.getId(), device, capabilities: session.getCapabilities() }, (source ? { source } : {})));
|
|
160
160
|
}
|
|
161
|
+
// An Android phone's device log, from about when the phone was given to
|
|
162
|
+
// the session, once its row exists (the lines point at it). Not waited
|
|
163
|
+
// for: the phone's clock and log stream are read in the background.
|
|
164
|
+
void typedi_1.Container.get(SessionDeviceLogs_1.SessionDeviceLogs).start({
|
|
165
|
+
sessionId: session.getId(),
|
|
166
|
+
device,
|
|
167
|
+
since: session.allocatedAt,
|
|
168
|
+
});
|
|
161
169
|
// Emit session started event
|
|
162
170
|
void typedi_1.Container.get(SocketServer_1.SocketServer).emitToDashboardForDevices(SocketEvents_1.SocketEvents.SESSION_STARTED, Object.assign(Object.assign({}, createData), { status: 'running', build_name: buildName }), { udid: device.udid });
|
|
163
171
|
// Increment Metrics
|
|
@@ -211,8 +219,10 @@ let DashboardEventManager = class DashboardEventManager {
|
|
|
211
219
|
this.stoppingSessionIds.add(sessionId);
|
|
212
220
|
try {
|
|
213
221
|
logger_1.default.info(`🟢 onSessionStopped called for session ${sessionId}`);
|
|
214
|
-
// However the session ended, in memory or not, its sampler stops here
|
|
222
|
+
// However the session ended, in memory or not, its sampler stops here,
|
|
223
|
+
// and its device log is written to the end.
|
|
215
224
|
yield typedi_1.Container.get(SessionMetricsService_1.SessionMetricsService).stop(sessionId);
|
|
225
|
+
yield typedi_1.Container.get(SessionDeviceLogs_1.SessionDeviceLogs).stop(sessionId);
|
|
216
226
|
// Video recording is now handled in plugin.ts deleteSession() before the session is deleted
|
|
217
227
|
// This ensures we can call stop_recording_screen while the session is still active
|
|
218
228
|
// Here we just handle the session status update
|
|
@@ -356,11 +366,15 @@ let DashboardEventManager = class DashboardEventManager {
|
|
|
356
366
|
if (updateData.status === SessionStatus_1.SessionStatus.FAILED && options.notify !== false) {
|
|
357
367
|
this.announceFailure(Object.assign(Object.assign({}, sessionEntry), updateData));
|
|
358
368
|
}
|
|
359
|
-
// Principal Triage: If session failed, perform intelligent failure analysis
|
|
369
|
+
// Principal Triage: If session failed, perform intelligent failure analysis.
|
|
370
|
+
// The category now; the AI's analysis is not awaited, since every way
|
|
371
|
+
// a session ends waits for this method (the client's quit, a hub's
|
|
372
|
+
// DELETE) and an AI call can take minutes. It is saved when it comes.
|
|
360
373
|
if (updateData.status === SessionStatus_1.SessionStatus.FAILED) {
|
|
361
374
|
try {
|
|
362
|
-
const {
|
|
363
|
-
yield
|
|
375
|
+
const { categorizeSessionFailure, explainSessionFailure } = yield Promise.resolve().then(() => __importStar(require('./services/failure-analysis-service')));
|
|
376
|
+
yield categorizeSessionFailure(sessionId);
|
|
377
|
+
void explainSessionFailure(sessionId);
|
|
364
378
|
}
|
|
365
379
|
catch (analysisErr) {
|
|
366
380
|
logger_1.default.warn(`⚠️ Failure analysis skipped for ${sessionId}: ${analysisErr.message}`);
|
|
@@ -429,7 +443,7 @@ let DashboardEventManager = class DashboardEventManager {
|
|
|
429
443
|
}
|
|
430
444
|
afterSessionCommand(sessionId, commandName, driver, request, response, responseBody, healingInfo) {
|
|
431
445
|
return __awaiter(this, void 0, void 0, function* () {
|
|
432
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q
|
|
446
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
433
447
|
const session = SessionManager_1.SESSION_MANAGER.getSession(sessionId);
|
|
434
448
|
if (session) {
|
|
435
449
|
try {
|
|
@@ -470,15 +484,20 @@ let DashboardEventManager = class DashboardEventManager {
|
|
|
470
484
|
};
|
|
471
485
|
// Smart Passive: capture strategy + selector on every findElement,
|
|
472
486
|
// not just heals. CommandInterceptor synthesizes request.body = args
|
|
473
|
-
// (the array [strategy, value])
|
|
474
|
-
//
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
487
|
+
// (the array [strategy, value]); a command a hub forwards to a node
|
|
488
|
+
// has the client's own W3C body ({ using, value }). This gives the
|
|
489
|
+
// verification job exact evidence of "selector ran and didn't heal"
|
|
490
|
+
// per build.
|
|
491
|
+
if (commandName === 'findElement' || commandName === 'findElements') {
|
|
492
|
+
const body = request.body;
|
|
493
|
+
const [strategy, selector] = Array.isArray(body)
|
|
494
|
+
? [body[0], body[1]]
|
|
495
|
+
: [body === null || body === void 0 ? void 0 : body.using, body === null || body === void 0 ? void 0 : body.value];
|
|
496
|
+
if (logEntry.original_strategy === null && typeof strategy === 'string') {
|
|
497
|
+
logEntry.original_strategy = strategy;
|
|
479
498
|
}
|
|
480
|
-
if (logEntry.original_selector === null) {
|
|
481
|
-
logEntry.original_selector =
|
|
499
|
+
if (logEntry.original_selector === null && typeof selector === 'string') {
|
|
500
|
+
logEntry.original_selector = selector;
|
|
482
501
|
}
|
|
483
502
|
}
|
|
484
503
|
// Increment Healing Metrics
|
|
@@ -530,8 +549,10 @@ let DashboardEventManager = class DashboardEventManager {
|
|
|
530
549
|
});
|
|
531
550
|
// Emit command log event to dashboard. One per command: the phone's
|
|
532
551
|
// team comes from DeviceTeamResolver's cache, not a query each time.
|
|
552
|
+
// Its summary only: what the command typed and answered stays in the
|
|
553
|
+
// SessionLog row above, which goes with the session.
|
|
533
554
|
const device = session.getDevice();
|
|
534
|
-
void typedi_1.Container.get(SocketServer_1.SocketServer).emitToDashboardForDevices(SocketEvents_1.SocketEvents.SESSION_COMMAND, Object.assign({ session_id: session.getId() }
|
|
555
|
+
void typedi_1.Container.get(SocketServer_1.SocketServer).emitToDashboardForDevices(SocketEvents_1.SocketEvents.SESSION_COMMAND, (0, sessionCommandSummary_1.sessionCommandSummary)(Object.assign(Object.assign({}, logEntry), { session_id: session.getId() })), { udid: device === null || device === void 0 ? void 0 : device.udid });
|
|
535
556
|
// Heal event broadcast — match the shape returned by GET /healing/events
|
|
536
557
|
// so the Overview activity feed and Settings list can consume both
|
|
537
558
|
// sources interchangeably without per-source field translation.
|
|
@@ -539,15 +560,15 @@ let DashboardEventManager = class DashboardEventManager {
|
|
|
539
560
|
void typedi_1.Container.get(SocketServer_1.SocketServer).emitToDashboardForDevices(SocketEvents_1.SocketEvents.HEALING_EVENT, {
|
|
540
561
|
id: persistedLog.id,
|
|
541
562
|
sessionId: session.getId(),
|
|
542
|
-
deviceUdid: (
|
|
543
|
-
deviceName: (
|
|
544
|
-
devicePlatform: (
|
|
545
|
-
commandName: (
|
|
546
|
-
originalSelector: (
|
|
547
|
-
healedSelector: (
|
|
548
|
-
confidence: (
|
|
549
|
-
tier: (
|
|
550
|
-
isSuccess: (
|
|
563
|
+
deviceUdid: (_g = device === null || device === void 0 ? void 0 : device.udid) !== null && _g !== void 0 ? _g : null,
|
|
564
|
+
deviceName: (_h = device === null || device === void 0 ? void 0 : device.name) !== null && _h !== void 0 ? _h : null,
|
|
565
|
+
devicePlatform: (_j = device === null || device === void 0 ? void 0 : device.platform) !== null && _j !== void 0 ? _j : null,
|
|
566
|
+
commandName: (_k = logEntry.command_name) !== null && _k !== void 0 ? _k : null,
|
|
567
|
+
originalSelector: (_l = logEntry.original_selector) !== null && _l !== void 0 ? _l : null,
|
|
568
|
+
healedSelector: (_m = logEntry.healed_selector) !== null && _m !== void 0 ? _m : null,
|
|
569
|
+
confidence: (_o = logEntry.healing_confidence) !== null && _o !== void 0 ? _o : null,
|
|
570
|
+
tier: (_p = logEntry.healing_tier) !== null && _p !== void 0 ? _p : null,
|
|
571
|
+
isSuccess: (_q = logEntry.is_success) !== null && _q !== void 0 ? _q : null,
|
|
551
572
|
createdAt: persistedLog.createdAt.toISOString(),
|
|
552
573
|
}, { udid: device === null || device === void 0 ? void 0 : device.udid });
|
|
553
574
|
}
|
|
@@ -650,41 +671,8 @@ let DashboardEventManager = class DashboardEventManager {
|
|
|
650
671
|
return [];
|
|
651
672
|
}
|
|
652
673
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
if (typeof driver.extractLogs === 'function') {
|
|
656
|
-
const logs = yield driver.extractLogs('logcat');
|
|
657
|
-
if (Array.isArray(logs) && logs.length > 0) {
|
|
658
|
-
return logs;
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
catch (err) {
|
|
663
|
-
logger_1.default.debug(`Could not extract logcat via driver: ${err}. Trying direct ADB...`);
|
|
664
|
-
}
|
|
665
|
-
// Principal Intelligence: Fallback to direct ADB logs if driver fails or returns nothing
|
|
666
|
-
if (device === null || device === void 0 ? void 0 : device.udid) {
|
|
667
|
-
try {
|
|
668
|
-
const deviceManager = typedi_1.Container.get(device_managers_1.XenonManager);
|
|
669
|
-
const androidManager = (yield deviceManager.deviceInstances()).find((m) => m instanceof AndroidDeviceManager_1.default);
|
|
670
|
-
if (androidManager) {
|
|
671
|
-
const rawLogs = yield androidManager.getLogs(device.udid);
|
|
672
|
-
if (rawLogs && typeof rawLogs === 'string') {
|
|
673
|
-
const logLines = rawLogs.split('\n').filter((l) => l.trim().length > 0);
|
|
674
|
-
// Convert string lines to expected format (last 100 lines to avoid DB bloat)
|
|
675
|
-
return logLines.slice(-100).map((line) => ({
|
|
676
|
-
message: line,
|
|
677
|
-
timestamp: Date.now(),
|
|
678
|
-
level: 'INFO',
|
|
679
|
-
}));
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
catch (adbErr) {
|
|
684
|
-
logger_1.default.debug(`Direct ADB log fetch failed for ${device.udid}: ${adbErr.message}`);
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
}
|
|
674
|
+
// An Android phone's lines are recorded for the whole session by
|
|
675
|
+
// SessionDeviceLogs, not per command.
|
|
688
676
|
return [];
|
|
689
677
|
}
|
|
690
678
|
catch (error) {
|
|
@@ -45,10 +45,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
45
45
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
46
|
};
|
|
47
47
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
-
exports.
|
|
48
|
+
exports.MAX_CONCURRENT_FAILURE_ANALYSES = exports.ANALYSIS_CATEGORIES = exports.ERROR_PATTERNS = void 0;
|
|
49
|
+
exports.categorizeSessionFailure = categorizeSessionFailure;
|
|
50
|
+
exports.explainSessionFailure = explainSessionFailure;
|
|
49
51
|
const prisma_1 = require("../../prisma");
|
|
50
52
|
const logger_1 = __importDefault(require("../../logger"));
|
|
51
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Each category and the texts that file a failure under it, tried in this
|
|
55
|
+
* order against the failure reason and the last five failed commands. The
|
|
56
|
+
* dashboard's runbooks describe a category by these, not by its name
|
|
57
|
+
* (failure-categories.spec.ts).
|
|
58
|
+
*/
|
|
59
|
+
exports.ERROR_PATTERNS = [
|
|
52
60
|
{
|
|
53
61
|
category: 'ELEMENT_NOT_FOUND',
|
|
54
62
|
patterns: [
|
|
@@ -98,7 +106,54 @@ const ERROR_PATTERNS = [
|
|
|
98
106
|
patterns: ['OutOfMemory', 'MemoryLimit', 'thermal throttling', 'too many open files'],
|
|
99
107
|
},
|
|
100
108
|
];
|
|
101
|
-
|
|
109
|
+
/** What the analysis writes when no pattern matches. */
|
|
110
|
+
const UNMATCHED = 'UNKNOWN';
|
|
111
|
+
/**
|
|
112
|
+
* Every category the analysis writes to a failed session's
|
|
113
|
+
* `failure_category`, upper case as stored. The only other value the column
|
|
114
|
+
* holds is `HUB_RESTART_CATEGORY` (SessionManager). The API reference's
|
|
115
|
+
* examples and the dashboard's runbooks are held to these
|
|
116
|
+
* (failure-categories.spec.ts).
|
|
117
|
+
*/
|
|
118
|
+
exports.ANALYSIS_CATEGORIES = [
|
|
119
|
+
...exports.ERROR_PATTERNS.map((p) => p.category),
|
|
120
|
+
UNMATCHED,
|
|
121
|
+
];
|
|
122
|
+
/**
|
|
123
|
+
* How many failed sessions' AI analyses run at once (explainSessionFailure);
|
|
124
|
+
* the others wait their turn. They run after their sessions have ended, so
|
|
125
|
+
* nothing else limits them: a broken build on a big lab ends hundreds of
|
|
126
|
+
* failed sessions in minutes, and each analysis holds a screenshot while it
|
|
127
|
+
* runs. Few at once also spares the provider's rate limit.
|
|
128
|
+
*/
|
|
129
|
+
exports.MAX_CONCURRENT_FAILURE_ANALYSES = 4;
|
|
130
|
+
/** Each session's analysis, waiting or running, so one session gets one. */
|
|
131
|
+
const analyses = new Map();
|
|
132
|
+
let analysesRunning = 0;
|
|
133
|
+
const analysesWaiting = [];
|
|
134
|
+
function analysisTurn() {
|
|
135
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
136
|
+
if (analysesRunning < exports.MAX_CONCURRENT_FAILURE_ANALYSES) {
|
|
137
|
+
analysesRunning++;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// The turn is handed over by endAnalysisTurn, still counted as running.
|
|
141
|
+
yield new Promise((resolve) => analysesWaiting.push(resolve));
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function endAnalysisTurn() {
|
|
145
|
+
const next = analysesWaiting.shift();
|
|
146
|
+
if (next)
|
|
147
|
+
next();
|
|
148
|
+
else
|
|
149
|
+
analysesRunning--;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Files a failed session under a category (`failure_category`) by its failure
|
|
153
|
+
* reason and its failed commands. Rules only, so it is quick: a session's end
|
|
154
|
+
* waits for it. Never throws.
|
|
155
|
+
*/
|
|
156
|
+
function categorizeSessionFailure(sessionId) {
|
|
102
157
|
return __awaiter(this, void 0, void 0, function* () {
|
|
103
158
|
try {
|
|
104
159
|
const session = yield prisma_1.prisma.session.findUnique({
|
|
@@ -112,8 +167,8 @@ function analyzeSessionFailure(sessionId) {
|
|
|
112
167
|
const reason = session.failure_reason || '';
|
|
113
168
|
const logs_text = session.SessionLog.map((l) => `${l.title} ${l.response}`).join(' ');
|
|
114
169
|
const combined_text = (reason + ' ' + logs_text).toLowerCase();
|
|
115
|
-
let identifiedCategory =
|
|
116
|
-
for (const item of ERROR_PATTERNS) {
|
|
170
|
+
let identifiedCategory = UNMATCHED;
|
|
171
|
+
for (const item of exports.ERROR_PATTERNS) {
|
|
117
172
|
if (item.patterns.some((p) => new RegExp(p, 'i').test(combined_text))) {
|
|
118
173
|
identifiedCategory = item.category;
|
|
119
174
|
break;
|
|
@@ -122,48 +177,9 @@ function analyzeSessionFailure(sessionId) {
|
|
|
122
177
|
// Special case for App Crash - check Logcat/Syslog if available
|
|
123
178
|
// For now we rely on the error response text which usually mentions "process has died"
|
|
124
179
|
logger_1.default.info(`[FailureAnalysis] Session ${sessionId} identified as ${identifiedCategory}`);
|
|
125
|
-
// AI Root-Cause Analysis
|
|
126
|
-
let aiAnalysis = null;
|
|
127
|
-
try {
|
|
128
|
-
const { AI_SERVICE } = yield Promise.resolve().then(() => __importStar(require('../../services/AIService')));
|
|
129
|
-
if (AI_SERVICE.isEnabled()) {
|
|
130
|
-
const lastLogs = yield prisma_1.prisma.log.findMany({
|
|
131
|
-
where: { session_id: sessionId, log_type: 'DEVICE' },
|
|
132
|
-
take: 50,
|
|
133
|
-
orderBy: { timestamp: 'desc' },
|
|
134
|
-
});
|
|
135
|
-
const lastCommands = yield prisma_1.prisma.sessionLog.findMany({
|
|
136
|
-
where: { session_id: sessionId },
|
|
137
|
-
take: 10,
|
|
138
|
-
orderBy: { createdAt: 'desc' },
|
|
139
|
-
});
|
|
140
|
-
// Find the last screenshot in logs
|
|
141
|
-
const lastScreenshotLog = lastCommands.find((l) => l.screenshot !== null);
|
|
142
|
-
aiAnalysis = yield AI_SERVICE.analyzeFailure({
|
|
143
|
-
sessionId,
|
|
144
|
-
failureReason: reason,
|
|
145
|
-
commandLogs: lastCommands.map((c) => {
|
|
146
|
-
var _a;
|
|
147
|
-
return ({
|
|
148
|
-
command: c.command_name,
|
|
149
|
-
success: c.is_success,
|
|
150
|
-
response: (_a = c.response) === null || _a === void 0 ? void 0 : _a.slice(0, 500), // Truncate long responses
|
|
151
|
-
});
|
|
152
|
-
}),
|
|
153
|
-
deviceLogs: lastLogs.map((l) => l.message),
|
|
154
|
-
screenshotPath: (lastScreenshotLog === null || lastScreenshotLog === void 0 ? void 0 : lastScreenshotLog.screenshot) || undefined,
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
catch (aiErr) {
|
|
159
|
-
logger_1.default.warn(`[FailureAnalysis] AI Analysis failed for ${sessionId}: ${aiErr.message}`);
|
|
160
|
-
}
|
|
161
180
|
yield prisma_1.prisma.session.update({
|
|
162
181
|
where: { id: sessionId },
|
|
163
|
-
data: {
|
|
164
|
-
failure_category: identifiedCategory,
|
|
165
|
-
ai_analysis: aiAnalysis,
|
|
166
|
-
},
|
|
182
|
+
data: { failure_category: identifiedCategory },
|
|
167
183
|
});
|
|
168
184
|
}
|
|
169
185
|
catch (err) {
|
|
@@ -171,3 +187,78 @@ function analyzeSessionFailure(sessionId) {
|
|
|
171
187
|
}
|
|
172
188
|
});
|
|
173
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Asks the AI provider why a failed session failed, and saves the answer
|
|
192
|
+
* (`ai_analysis`). Only an answer is saved: with no provider, a rate limit, a
|
|
193
|
+
* time-out (FAILURE_ANALYSIS_TIMEOUT_MS) or a failed call nothing is written,
|
|
194
|
+
* and an analysis saved earlier stays. Never rejects.
|
|
195
|
+
*
|
|
196
|
+
* A session's end doesn't wait for it (onSessionStopped): an AI call can take
|
|
197
|
+
* minutes, and the client's quit, or a hub's DELETE, waits for the end. At
|
|
198
|
+
* most MAX_CONCURRENT_FAILURE_ANALYSES run at once, and a session whose
|
|
199
|
+
* analysis is waiting or running gets that one (a session can end twice: a
|
|
200
|
+
* crash, then the client's delete).
|
|
201
|
+
*/
|
|
202
|
+
function explainSessionFailure(sessionId) {
|
|
203
|
+
const pending = analyses.get(sessionId);
|
|
204
|
+
if (pending)
|
|
205
|
+
return pending;
|
|
206
|
+
const analysis = (() => __awaiter(this, void 0, void 0, function* () {
|
|
207
|
+
yield analysisTurn();
|
|
208
|
+
try {
|
|
209
|
+
yield explain(sessionId);
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
endAnalysisTurn();
|
|
213
|
+
}
|
|
214
|
+
}))().finally(() => analyses.delete(sessionId));
|
|
215
|
+
analyses.set(sessionId, analysis);
|
|
216
|
+
return analysis;
|
|
217
|
+
}
|
|
218
|
+
function explain(sessionId) {
|
|
219
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
220
|
+
try {
|
|
221
|
+
const { AI_SERVICE } = yield Promise.resolve().then(() => __importStar(require('../../services/AIService')));
|
|
222
|
+
if (!AI_SERVICE.isEnabled())
|
|
223
|
+
return;
|
|
224
|
+
const session = yield prisma_1.prisma.session.findUnique({ where: { id: sessionId } });
|
|
225
|
+
if (!session)
|
|
226
|
+
return;
|
|
227
|
+
const lastLogs = yield prisma_1.prisma.log.findMany({
|
|
228
|
+
where: { session_id: sessionId, log_type: 'DEVICE' },
|
|
229
|
+
take: 50,
|
|
230
|
+
orderBy: { timestamp: 'desc' },
|
|
231
|
+
});
|
|
232
|
+
const lastCommands = yield prisma_1.prisma.sessionLog.findMany({
|
|
233
|
+
where: { session_id: sessionId },
|
|
234
|
+
take: 10,
|
|
235
|
+
orderBy: { createdAt: 'desc' },
|
|
236
|
+
});
|
|
237
|
+
// Find the last screenshot in logs
|
|
238
|
+
const lastScreenshotLog = lastCommands.find((l) => l.screenshot !== null);
|
|
239
|
+
const aiAnalysis = yield AI_SERVICE.analyzeFailure({
|
|
240
|
+
sessionId,
|
|
241
|
+
failureReason: session.failure_reason || '',
|
|
242
|
+
commandLogs: lastCommands.map((c) => {
|
|
243
|
+
var _a;
|
|
244
|
+
return ({
|
|
245
|
+
command: c.command_name,
|
|
246
|
+
success: c.is_success,
|
|
247
|
+
response: (_a = c.response) === null || _a === void 0 ? void 0 : _a.slice(0, 500), // Truncate long responses
|
|
248
|
+
});
|
|
249
|
+
}),
|
|
250
|
+
deviceLogs: lastLogs.map((l) => l.message),
|
|
251
|
+
screenshotPath: (lastScreenshotLog === null || lastScreenshotLog === void 0 ? void 0 : lastScreenshotLog.screenshot) || undefined,
|
|
252
|
+
});
|
|
253
|
+
if (!aiAnalysis)
|
|
254
|
+
return;
|
|
255
|
+
yield prisma_1.prisma.session.update({
|
|
256
|
+
where: { id: sessionId },
|
|
257
|
+
data: { ai_analysis: aiAnalysis },
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
catch (aiErr) {
|
|
261
|
+
logger_1.default.warn(`[FailureAnalysis] AI Analysis failed for ${sessionId}: ${aiErr.message}`);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SESSION_COMMAND_SUMMARY_FIELDS = void 0;
|
|
4
|
+
exports.sessionCommandSummary = sessionCommandSummary;
|
|
5
|
+
/**
|
|
6
|
+
* The fields of a command's record that leave its session: the live
|
|
7
|
+
* `session_command` event and the event log carry these and nothing else.
|
|
8
|
+
* Which command ran in which session, how it went and how it healed. The rest
|
|
9
|
+
* stays in the session's SessionLog, which goes with the session: the
|
|
10
|
+
* record's `body` is the command's arguments (the text `setValue` typed) and
|
|
11
|
+
* its `response` the command's answer (a page source, a screenshot's base64).
|
|
12
|
+
* Named one by one, so a field added to the record later goes nowhere until
|
|
13
|
+
* it is listed here.
|
|
14
|
+
*/
|
|
15
|
+
exports.SESSION_COMMAND_SUMMARY_FIELDS = [
|
|
16
|
+
'session_id',
|
|
17
|
+
'command_name',
|
|
18
|
+
'method',
|
|
19
|
+
'is_success',
|
|
20
|
+
'is_error',
|
|
21
|
+
'is_healed',
|
|
22
|
+
'original_strategy',
|
|
23
|
+
'original_selector',
|
|
24
|
+
'healed_strategy',
|
|
25
|
+
'healed_selector',
|
|
26
|
+
'healing_confidence',
|
|
27
|
+
'healing_tier',
|
|
28
|
+
'duration',
|
|
29
|
+
'span_id',
|
|
30
|
+
'trace_id',
|
|
31
|
+
];
|
|
32
|
+
function sessionCommandSummary(record) {
|
|
33
|
+
const summary = {};
|
|
34
|
+
for (const field of exports.SESSION_COMMAND_SUMMARY_FIELDS) {
|
|
35
|
+
if ((record === null || record === void 0 ? void 0 : record[field]) !== undefined)
|
|
36
|
+
summary[field] = record[field];
|
|
37
|
+
}
|
|
38
|
+
return summary;
|
|
39
|
+
}
|
|
@@ -8,6 +8,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
12
|
+
var t = {};
|
|
13
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
14
|
+
t[p] = s[p];
|
|
15
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
16
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
17
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
18
|
+
t[p[i]] = s[p[i]];
|
|
19
|
+
}
|
|
20
|
+
return t;
|
|
21
|
+
};
|
|
11
22
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
23
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
24
|
};
|
|
@@ -438,7 +449,9 @@ class LokiHealEtalonStore {
|
|
|
438
449
|
const coll = yield this.getCollection();
|
|
439
450
|
const existing = coll.findOne({ selector: etalon.selector });
|
|
440
451
|
if (existing) {
|
|
441
|
-
|
|
452
|
+
// A fingerprint saved without a path keeps the one stored, as in Prisma.
|
|
453
|
+
const { path } = etalon, rest = __rest(etalon, ["path"]);
|
|
454
|
+
Object.assign(existing, rest, path == null ? {} : { path });
|
|
442
455
|
coll.update(existing);
|
|
443
456
|
}
|
|
444
457
|
else {
|
|
@@ -625,21 +625,24 @@ class PrismaHealEtalonStore {
|
|
|
625
625
|
get prisma() {
|
|
626
626
|
return typedi_1.Container.get(prisma_service_1.PrismaService).client;
|
|
627
627
|
}
|
|
628
|
+
/**
|
|
629
|
+
* The fingerprint, and its path when it has one. A fingerprint saved without
|
|
630
|
+
* a path (a heal whose element's path couldn't be worked out) keeps the one
|
|
631
|
+
* stored. Through 2.14 the path was never stored, so the Resilio tier,
|
|
632
|
+
* which needs it, never found anything.
|
|
633
|
+
*/
|
|
628
634
|
saveSignature(etalon) {
|
|
629
635
|
return __awaiter(this, void 0, void 0, function* () {
|
|
636
|
+
const path = etalon.path == null ? undefined : JSON.stringify(etalon.path);
|
|
630
637
|
yield this.prisma.locatorEtalon.upsert({
|
|
631
638
|
where: { selector: etalon.selector },
|
|
632
|
-
update: {
|
|
633
|
-
strategy: etalon.strategy,
|
|
634
|
-
attributes: JSON.stringify(etalon.attributes),
|
|
635
|
-
nodeName: etalon.nodeName,
|
|
636
|
-
lastSeen: new Date(),
|
|
637
|
-
},
|
|
639
|
+
update: Object.assign(Object.assign({ strategy: etalon.strategy, attributes: JSON.stringify(etalon.attributes), nodeName: etalon.nodeName }, (path !== undefined ? { path } : {})), { lastSeen: new Date() }),
|
|
638
640
|
create: {
|
|
639
641
|
selector: etalon.selector,
|
|
640
642
|
strategy: etalon.strategy,
|
|
641
643
|
attributes: JSON.stringify(etalon.attributes),
|
|
642
644
|
nodeName: etalon.nodeName,
|
|
645
|
+
path,
|
|
643
646
|
lastSeen: new Date(),
|
|
644
647
|
},
|
|
645
648
|
});
|
|
@@ -652,7 +655,16 @@ class PrismaHealEtalonStore {
|
|
|
652
655
|
});
|
|
653
656
|
if (!etalon)
|
|
654
657
|
return null;
|
|
655
|
-
|
|
658
|
+
let path = null;
|
|
659
|
+
if (etalon.path) {
|
|
660
|
+
try {
|
|
661
|
+
path = JSON.parse(etalon.path);
|
|
662
|
+
}
|
|
663
|
+
catch (_a) {
|
|
664
|
+
path = null;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return Object.assign(Object.assign({}, etalon), { attributes: JSON.parse(etalon.attributes), path, lastSeen: etalon.lastSeen.getTime() });
|
|
656
668
|
});
|
|
657
669
|
}
|
|
658
670
|
}
|
|
@@ -27,6 +27,13 @@ const SETTINGS = {
|
|
|
27
27
|
buildCleanupMaxCount: 'number',
|
|
28
28
|
buildCleanupSchedule: 'text',
|
|
29
29
|
deleteBuildAssets: 'boolean',
|
|
30
|
+
aiProvider: 'text',
|
|
31
|
+
aiModel: 'text',
|
|
32
|
+
aiBaseUrl: 'text',
|
|
33
|
+
geminiModel: 'text',
|
|
34
|
+
openaiModel: 'text',
|
|
35
|
+
anthropicModel: 'text',
|
|
36
|
+
ollamaModel: 'text',
|
|
30
37
|
};
|
|
31
38
|
/**
|
|
32
39
|
* The lab settings the dashboard saves, one `WebConfig` row each, keyed by the
|