swipium 1.5.0 → 2.0.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/CHANGELOG.md +171 -0
- package/README.md +173 -280
- package/THREAT_MODEL.md +223 -47
- package/dist/appMap/automationLink.js +30 -27
- package/dist/appMap/automationLink.js.map +1 -1
- package/dist/appMap/build.js +47 -21
- package/dist/appMap/build.js.map +1 -1
- package/dist/appMap/codeIndex.js +2 -2
- package/dist/appMap/codeIndex.js.map +1 -1
- package/dist/appMap/featureIndex.js +3 -3
- package/dist/appMap/featureIndex.js.map +1 -1
- package/dist/appMap/featureModel.js +2 -2
- package/dist/appMap/featureModel.js.map +1 -1
- package/dist/appMap/firstRunApply.js +2 -2
- package/dist/appMap/firstRunApply.js.map +1 -1
- package/dist/appMap/issues.js +10 -10
- package/dist/appMap/issues.js.map +1 -1
- package/dist/appMap/migrations.js +5 -5
- package/dist/appMap/migrations.js.map +1 -1
- package/dist/appMap/prelaunch.js +2 -2
- package/dist/appMap/prelaunch.js.map +1 -1
- package/dist/appMap/projectRegistry.js +77 -11
- package/dist/appMap/projectRegistry.js.map +1 -1
- package/dist/appMap/provenance.js +2 -2
- package/dist/appMap/provenance.js.map +1 -1
- package/dist/appMap/query.js +2 -2
- package/dist/appMap/query.js.map +1 -1
- package/dist/appMap/runtimeMerge.js +3 -3
- package/dist/appMap/runtimeMerge.js.map +1 -1
- package/dist/appMap/schema.js +1 -1
- package/dist/appMap/schema.js.map +1 -1
- package/dist/appMap/screenMatch.js +1 -1
- package/dist/appMap/screenMatch.js.map +1 -1
- package/dist/appMap/staticScan.js +17 -17
- package/dist/appMap/staticScan.js.map +1 -1
- package/dist/appMap/store.js +55 -7
- package/dist/appMap/store.js.map +1 -1
- package/dist/appMap/tsAstScan.js +3 -3
- package/dist/appMap/tsAstScan.js.map +1 -1
- package/dist/artifacts/bundletool.js +5 -5
- package/dist/artifacts/bundletool.js.map +1 -1
- package/dist/artifacts/resolve.js +10 -10
- package/dist/artifacts/resolve.js.map +1 -1
- package/dist/automation/capabilities.js +3 -3
- package/dist/automation/capabilities.js.map +1 -1
- package/dist/automation/gestures.js +1 -1
- package/dist/automation/gestures.js.map +1 -1
- package/dist/automation/plan.js +7 -7
- package/dist/automation/plan.js.map +1 -1
- package/dist/automation/report.js +3 -3
- package/dist/automation/report.js.map +1 -1
- package/dist/automation/selectors.js +14 -25
- package/dist/automation/selectors.js.map +1 -1
- package/dist/automation/types.js +1 -1
- package/dist/automation/types.js.map +1 -1
- package/dist/automation/waits.js +5 -5
- package/dist/automation/waits.js.map +1 -1
- package/dist/automation/webview.js +2 -2
- package/dist/automation/webview.js.map +1 -1
- package/dist/automationGen/appiumModel.js +13 -7
- package/dist/automationGen/appiumModel.js.map +1 -1
- package/dist/automationGen/ciEmitter.js +5 -5
- package/dist/automationGen/ciEmitter.js.map +1 -1
- package/dist/automationGen/identifiers.js +224 -0
- package/dist/automationGen/identifiers.js.map +1 -0
- package/dist/automationGen/jsEmitter.js +265 -106
- package/dist/automationGen/jsEmitter.js.map +1 -1
- package/dist/automationGen/packagePatch.js +2 -2
- package/dist/automationGen/packagePatch.js.map +1 -1
- package/dist/automationGen/platformResolve.js +36 -0
- package/dist/automationGen/platformResolve.js.map +1 -0
- package/dist/automationGen/projectProfile.js +71 -26
- package/dist/automationGen/projectProfile.js.map +1 -1
- package/dist/automationGen/pythonEmitter.js +276 -105
- package/dist/automationGen/pythonEmitter.js.map +1 -1
- package/dist/automationGen/readmeEmitter.js +7 -6
- package/dist/automationGen/readmeEmitter.js.map +1 -1
- package/dist/{tools/automationGenerate.js → automationGen/run.js} +72 -113
- package/dist/automationGen/run.js.map +1 -0
- package/dist/automationGen/suitePlan.js +6 -4
- package/dist/automationGen/suitePlan.js.map +1 -1
- package/dist/automationGen/validation.js +20 -8
- package/dist/automationGen/validation.js.map +1 -1
- package/dist/build/parseBuildLog.js +2 -2
- package/dist/build/parseBuildLog.js.map +1 -1
- package/dist/build/plan.js +7 -7
- package/dist/build/plan.js.map +1 -1
- package/dist/ci/preflight.js +13 -4
- package/dist/ci/preflight.js.map +1 -1
- package/dist/cli/gc.js +74 -0
- package/dist/cli/gc.js.map +1 -0
- package/dist/cli/init.js +217 -26
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/main.js +50 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/report.js +218 -0
- package/dist/cli/report.js.map +1 -0
- package/dist/cli/scan.js +35 -14
- package/dist/cli/scan.js.map +1 -1
- package/dist/cli/suite.js +6 -6
- package/dist/cli/suite.js.map +1 -1
- package/dist/cli/verify.js +7 -7
- package/dist/cli/verify.js.map +1 -1
- package/dist/consent/consent.js +162 -5
- package/dist/consent/consent.js.map +1 -1
- package/dist/context/detect.js +45 -14
- package/dist/context/detect.js.map +1 -1
- package/dist/context/findApps.js +6 -6
- package/dist/context/findApps.js.map +1 -1
- package/dist/context/projectRoot.js +174 -19
- package/dist/context/projectRoot.js.map +1 -1
- package/dist/context/scan.js +1 -1
- package/dist/context/scan.js.map +1 -1
- package/dist/core/capabilityGroups.js +90 -0
- package/dist/core/capabilityGroups.js.map +1 -0
- package/dist/core/target.js +3 -3
- package/dist/core/target.js.map +1 -1
- package/dist/core/targetPlan.js +65 -62
- package/dist/core/targetPlan.js.map +1 -1
- package/dist/drivers/DirectDriver.js +300 -36
- package/dist/drivers/DirectDriver.js.map +1 -1
- package/dist/drivers/SimctlDriver.js +120 -7
- package/dist/drivers/SimctlDriver.js.map +1 -1
- package/dist/drivers/WdaDriver.js +350 -50
- package/dist/drivers/WdaDriver.js.map +1 -1
- package/dist/explore/candidates.js +2 -2
- package/dist/explore/candidates.js.map +1 -1
- package/dist/explore/graph.js +5 -5
- package/dist/explore/graph.js.map +1 -1
- package/dist/explore/policy.js +5 -5
- package/dist/explore/policy.js.map +1 -1
- package/dist/explore/runner.js +301 -284
- package/dist/explore/runner.js.map +1 -1
- package/dist/explore/signatures.js +1 -1
- package/dist/featureTesting/executionBootstrap.js +29 -30
- package/dist/featureTesting/executionBootstrap.js.map +1 -1
- package/dist/featureTesting/featureMap.js +2 -2
- package/dist/featureTesting/featureMap.js.map +1 -1
- package/dist/featureTesting/featureScope.js +12 -12
- package/dist/featureTesting/featureScope.js.map +1 -1
- package/dist/featureTesting/mapFeatureScope.js +7 -7
- package/dist/featureTesting/mapFeatureScope.js.map +1 -1
- package/dist/featureTesting/objectiveModel.js +10 -10
- package/dist/featureTesting/objectiveModel.js.map +1 -1
- package/dist/featureTesting/resultMerge.js +6 -6
- package/dist/featureTesting/resultMerge.js.map +1 -1
- package/dist/featureTesting/sources.js +4 -4
- package/dist/featureTesting/sources.js.map +1 -1
- package/dist/featureTesting/suiteBridge.js +1 -1
- package/dist/featureTesting/suiteBridge.js.map +1 -1
- package/dist/featureTesting/synonyms.js +1 -1
- package/dist/featureTesting/synonyms.js.map +1 -1
- package/dist/featureTesting/testCaseFactory.js +11 -11
- package/dist/featureTesting/testCaseFactory.js.map +1 -1
- package/dist/featureTesting/testPlan.js +3 -3
- package/dist/featureTesting/testPlan.js.map +1 -1
- package/dist/firstRun/authStateMachine.js +1 -1
- package/dist/firstRun/authStateMachine.js.map +1 -1
- package/dist/firstRun/classifyScreen.js +5 -5
- package/dist/firstRun/classifyScreen.js.map +1 -1
- package/dist/firstRun/firstRunPlanner.js +11 -11
- package/dist/firstRun/firstRunPlanner.js.map +1 -1
- package/dist/firstRun/firstRunRunner.js +8 -8
- package/dist/firstRun/firstRunRunner.js.map +1 -1
- package/dist/firstRun/generatedDataPolicy.js +9 -9
- package/dist/firstRun/generatedDataPolicy.js.map +1 -1
- package/dist/firstRun/inputPlanner.js +5 -5
- package/dist/firstRun/inputPlanner.js.map +1 -1
- package/dist/firstRun/onboardingStateMachine.js +1 -1
- package/dist/firstRun/onboardingStateMachine.js.map +1 -1
- package/dist/firstRun/paywallPolicy.js +3 -3
- package/dist/firstRun/paywallPolicy.js.map +1 -1
- package/dist/firstRun/types.js +1 -1
- package/dist/firstRun/types.js.map +1 -1
- package/dist/fixtures/catalog.js +33 -3
- package/dist/fixtures/catalog.js.map +1 -1
- package/dist/fixtures/load.js +50 -0
- package/dist/fixtures/load.js.map +1 -0
- package/dist/flows/discover.js +2 -2
- package/dist/flows/discover.js.map +1 -1
- package/dist/flows/generate.js +20 -10
- package/dist/flows/generate.js.map +1 -1
- package/dist/flows/pack.js +3 -3
- package/dist/flows/pack.js.map +1 -1
- package/dist/flows/paths.js +57 -0
- package/dist/flows/paths.js.map +1 -0
- package/dist/flows/repair.js +111 -23
- package/dist/flows/repair.js.map +1 -1
- package/dist/flows/run.js +153 -57
- package/dist/flows/run.js.map +1 -1
- package/dist/flows/schema.js +50 -5
- package/dist/flows/schema.js.map +1 -1
- package/dist/flows/seedExec.js +4 -4
- package/dist/flows/seedExec.js.map +1 -1
- package/dist/flows/templates.js +5 -5
- package/dist/index.js +50 -8
- package/dist/index.js.map +1 -1
- package/dist/issues/classify.js +16 -16
- package/dist/issues/classify.js.map +1 -1
- package/dist/issues/fingerprint.js +24 -4
- package/dist/issues/fingerprint.js.map +1 -1
- package/dist/issues/index.js +94 -31
- package/dist/issues/index.js.map +1 -1
- package/dist/issues/metrics.js +4 -4
- package/dist/issues/metrics.js.map +1 -1
- package/dist/issues/recurrence.js +35 -8
- package/dist/issues/recurrence.js.map +1 -1
- package/dist/issues/report.js +4 -4
- package/dist/issues/report.js.map +1 -1
- package/dist/issues/reportBridge.js +4 -4
- package/dist/issues/reportBridge.js.map +1 -1
- package/dist/issues/schema.js +2 -2
- package/dist/issues/schema.js.map +1 -1
- package/dist/issues/sourceRevision.js +2 -2
- package/dist/issues/sourceRevision.js.map +1 -1
- package/dist/issues/store.js +110 -32
- package/dist/issues/store.js.map +1 -1
- package/dist/lib/abortScope.js +48 -0
- package/dist/lib/abortScope.js.map +1 -0
- package/dist/lib/android.js +75 -7
- package/dist/lib/android.js.map +1 -1
- package/dist/lib/coordSpace.js +1 -1
- package/dist/lib/coordSpace.js.map +1 -1
- package/dist/lib/device.js +32 -7
- package/dist/lib/device.js.map +1 -1
- package/dist/lib/gestures.js +141 -0
- package/dist/lib/gestures.js.map +1 -0
- package/dist/lib/gitignore.js +4 -3
- package/dist/lib/gitignore.js.map +1 -1
- package/dist/lib/image.js +1 -1
- package/dist/lib/image.js.map +1 -1
- package/dist/lib/lockfile.js +370 -31
- package/dist/lib/lockfile.js.map +1 -1
- package/dist/lib/needsInput.js +3 -3
- package/dist/lib/needsInput.js.map +1 -1
- package/dist/lib/png.js +2 -2
- package/dist/lib/png.js.map +1 -1
- package/dist/lib/redact.js +170 -9
- package/dist/lib/redact.js.map +1 -1
- package/dist/lib/result.js +79 -10
- package/dist/lib/result.js.map +1 -1
- package/dist/lib/schemaHash.js +3 -3
- package/dist/lib/schemaHash.js.map +1 -1
- package/dist/lib/sensitive.js +3 -2
- package/dist/lib/sensitive.js.map +1 -1
- package/dist/lib/simctl.js +3 -3
- package/dist/lib/simctl.js.map +1 -1
- package/dist/lib/spawn.js +52 -10
- package/dist/lib/spawn.js.map +1 -1
- package/dist/lib/toolAnnotations.js +114 -0
- package/dist/lib/toolAnnotations.js.map +1 -0
- package/dist/lib/wda.js +294 -15
- package/dist/lib/wda.js.map +1 -1
- package/dist/mobileAudit/checks.js +12 -12
- package/dist/mobileAudit/checks.js.map +1 -1
- package/dist/mobileAudit/evidence.js +4 -4
- package/dist/mobileAudit/evidence.js.map +1 -1
- package/dist/mobileAudit/profiles.js +1 -1
- package/dist/mobileAudit/profiles.js.map +1 -1
- package/dist/mobileAudit/results.js +3 -3
- package/dist/mobileAudit/results.js.map +1 -1
- package/dist/mobileAudit/runner.js +72 -18
- package/dist/mobileAudit/runner.js.map +1 -1
- package/dist/oracle/auth.js +1 -1
- package/dist/oracle/auth.js.map +1 -1
- package/dist/oracle/failures.js +219 -36
- package/dist/oracle/failures.js.map +1 -1
- package/dist/oracle/health.js +63 -17
- package/dist/oracle/health.js.map +1 -1
- package/dist/oracle/locator.js +13 -13
- package/dist/oracle/locator.js.map +1 -1
- package/dist/oracle/record.js +3 -3
- package/dist/oracle/record.js.map +1 -1
- package/dist/orchestration/envelope.js +2 -2
- package/dist/orchestration/envelope.js.map +1 -1
- package/dist/orchestration/goal.js +4 -4
- package/dist/orchestration/goal.js.map +1 -1
- package/dist/orchestration/testThis/execute.js +62 -21
- package/dist/orchestration/testThis/execute.js.map +1 -1
- package/dist/orchestration/testThis/pipeline.js +119 -30
- package/dist/orchestration/testThis/pipeline.js.map +1 -1
- package/dist/orchestration/testThis/plan.js +162 -73
- package/dist/orchestration/testThis/plan.js.map +1 -1
- package/dist/orchestration/testThis/sessionIntent.js +72 -0
- package/dist/orchestration/testThis/sessionIntent.js.map +1 -0
- package/dist/orchestration/testThis/terminal.js +34 -16
- package/dist/orchestration/testThis/terminal.js.map +1 -1
- package/dist/orchestration/testThis/types.js +1 -1
- package/dist/orchestration/testThis/types.js.map +1 -1
- package/dist/plan/plan.js +9 -9
- package/dist/plan/plan.js.map +1 -1
- package/dist/prompts/index.js +22 -22
- package/dist/prompts/index.js.map +1 -1
- package/dist/report/export.js +392 -37
- package/dist/report/export.js.map +1 -1
- package/dist/report/findingsDedupe.js +41 -0
- package/dist/report/findingsDedupe.js.map +1 -0
- package/dist/report/policy.js +7 -4
- package/dist/report/policy.js.map +1 -1
- package/dist/report/qaLevel.js +8 -8
- package/dist/report/qaLevel.js.map +1 -1
- package/dist/report/sarifSources.js +160 -0
- package/dist/report/sarifSources.js.map +1 -0
- package/dist/report/summary.js +2 -2
- package/dist/report/summary.js.map +1 -1
- package/dist/report/testCatalog.js +4 -4
- package/dist/report/testCatalog.js.map +1 -1
- package/dist/report/toolHealth.js +124 -0
- package/dist/report/toolHealth.js.map +1 -0
- package/dist/server.js +472 -45
- package/dist/server.js.map +1 -1
- package/dist/services/automationGenerate.js +8 -2
- package/dist/services/automationGenerate.js.map +1 -1
- package/dist/services/build.js +2 -2
- package/dist/services/build.js.map +1 -1
- package/dist/{tools → services}/flowGenerate.js +29 -13
- package/dist/services/flowGenerate.js.map +1 -0
- package/dist/services/preflight.js +4 -4
- package/dist/services/preflight.js.map +1 -1
- package/dist/services/prepareAndroid.js +46 -8
- package/dist/services/prepareAndroid.js.map +1 -1
- package/dist/services/prepareIos.js +24 -9
- package/dist/services/prepareIos.js.map +1 -1
- package/dist/services/report.js +100 -45
- package/dist/services/report.js.map +1 -1
- package/dist/services/smoke.js +32 -16
- package/dist/services/smoke.js.map +1 -1
- package/dist/services/suiteGenerate.js +28 -3
- package/dist/services/suiteGenerate.js.map +1 -1
- package/dist/services/testSuiteKnowledge.js +40 -27
- package/dist/services/testSuiteKnowledge.js.map +1 -1
- package/dist/session/attach.js +270 -6
- package/dist/session/attach.js.map +1 -1
- package/dist/session/processRegistry.js +317 -48
- package/dist/session/processRegistry.js.map +1 -1
- package/dist/session/progress.js +1 -1
- package/dist/session/retention.js +201 -0
- package/dist/session/retention.js.map +1 -0
- package/dist/session/store.js +275 -48
- package/dist/session/store.js.map +1 -1
- package/dist/snapshot/overlays.js +53 -8
- package/dist/snapshot/overlays.js.map +1 -1
- package/dist/snapshot/parse.js +28 -4
- package/dist/snapshot/parse.js.map +1 -1
- package/dist/snapshot/present.js +11 -5
- package/dist/snapshot/present.js.map +1 -1
- package/dist/snapshot/settle.js +39 -9
- package/dist/snapshot/settle.js.map +1 -1
- package/dist/state/consent.js +99 -0
- package/dist/state/consent.js.map +1 -0
- package/dist/state/profile.js +46 -2
- package/dist/state/profile.js.map +1 -1
- package/dist/suite/compile.js +9 -5
- package/dist/suite/compile.js.map +1 -1
- package/dist/suite/lint.js +4 -4
- package/dist/suite/lint.js.map +1 -1
- package/dist/suite/pom.js +50 -41
- package/dist/suite/pom.js.map +1 -1
- package/dist/suite/secretGuard.js +266 -0
- package/dist/suite/secretGuard.js.map +1 -0
- package/dist/suite/testcase.js +9 -7
- package/dist/suite/testcase.js.map +1 -1
- package/dist/testSuite/exporter.js +4 -4
- package/dist/testSuite/exporter.js.map +1 -1
- package/dist/testSuite/generator.js +13 -9
- package/dist/testSuite/generator.js.map +1 -1
- package/dist/testSuite/history.js +1 -1
- package/dist/testSuite/history.js.map +1 -1
- package/dist/testSuite/issueLinks.js +3 -3
- package/dist/testSuite/issueLinks.js.map +1 -1
- package/dist/testSuite/lint.js +4 -4
- package/dist/testSuite/lint.js.map +1 -1
- package/dist/testSuite/merge.js +5 -5
- package/dist/testSuite/merge.js.map +1 -1
- package/dist/testSuite/schema.js +5 -5
- package/dist/testSuite/schema.js.map +1 -1
- package/dist/testSuite/store.js +3 -3
- package/dist/testSuite/store.js.map +1 -1
- package/dist/testSuite/traceability.js +2 -2
- package/dist/testSuite/traceability.js.map +1 -1
- package/dist/tools/act.js +794 -164
- package/dist/tools/act.js.map +1 -1
- package/dist/tools/agent.js +451 -128
- package/dist/tools/agent.js.map +1 -1
- package/dist/tools/appControl.js +69 -19
- package/dist/tools/appControl.js.map +1 -1
- package/dist/tools/appMap.js +188 -130
- package/dist/tools/appMap.js.map +1 -1
- package/dist/tools/build.js +32 -42
- package/dist/tools/build.js.map +1 -1
- package/dist/tools/bundletool.js +21 -31
- package/dist/tools/bundletool.js.map +1 -1
- package/dist/tools/clearOverlay.js +55 -12
- package/dist/tools/clearOverlay.js.map +1 -1
- package/dist/tools/device.js +76 -24
- package/dist/tools/device.js.map +1 -1
- package/dist/tools/doctor.js +68 -28
- package/dist/tools/doctor.js.map +1 -1
- package/dist/tools/explore.js +41 -47
- package/dist/tools/explore.js.map +1 -1
- package/dist/tools/featureTesting.js +31 -48
- package/dist/tools/featureTesting.js.map +1 -1
- package/dist/tools/firstRun.js +34 -41
- package/dist/tools/firstRun.js.map +1 -1
- package/dist/tools/flow.js +183 -103
- package/dist/tools/flow.js.map +1 -1
- package/dist/tools/flowRepair.js +25 -13
- package/dist/tools/flowRepair.js.map +1 -1
- package/dist/tools/generate.js +52 -89
- package/dist/tools/generate.js.map +1 -1
- package/dist/tools/getArtifact.js +16 -5
- package/dist/tools/getArtifact.js.map +1 -1
- package/dist/tools/health.js +15 -12
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/ios.js +45 -223
- package/dist/tools/ios.js.map +1 -1
- package/dist/tools/issues.js +251 -35
- package/dist/tools/issues.js.map +1 -1
- package/dist/tools/jobs.js +41 -16
- package/dist/tools/jobs.js.map +1 -1
- package/dist/tools/metro.js +36 -35
- package/dist/tools/metro.js.map +1 -1
- package/dist/tools/mobileAudit.js +50 -45
- package/dist/tools/mobileAudit.js.map +1 -1
- package/dist/tools/network.js +31 -14
- package/dist/tools/network.js.map +1 -1
- package/dist/tools/note.js +26 -26
- package/dist/tools/note.js.map +1 -1
- package/dist/tools/prepareIosTarget.js +10 -16
- package/dist/tools/prepareIosTarget.js.map +1 -1
- package/dist/tools/prepareTarget.js +117 -50
- package/dist/tools/prepareTarget.js.map +1 -1
- package/dist/tools/report.js +11 -22
- package/dist/tools/report.js.map +1 -1
- package/dist/tools/resolveArtifact.js +11 -15
- package/dist/tools/resolveArtifact.js.map +1 -1
- package/dist/tools/resolveTarget.js +78 -24
- package/dist/tools/resolveTarget.js.map +1 -1
- package/dist/tools/screenRecord.js +23 -21
- package/dist/tools/screenRecord.js.map +1 -1
- package/dist/tools/screenshot.js +23 -16
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/smoke.js +20 -16
- package/dist/tools/smoke.js.map +1 -1
- package/dist/tools/snapshot.js +155 -118
- package/dist/tools/snapshot.js.map +1 -1
- package/dist/tools/startSession.js +104 -116
- package/dist/tools/startSession.js.map +1 -1
- package/dist/tools/suite.js +72 -43
- package/dist/tools/suite.js.map +1 -1
- package/dist/tools/testSuite.js +30 -20
- package/dist/tools/testSuite.js.map +1 -1
- package/dist/tools/testThis.js +24 -28
- package/dist/tools/testThis.js.map +1 -1
- package/dist/tools/visual.js +686 -210
- package/dist/tools/visual.js.map +1 -1
- package/dist/tools/wait.js +9 -38
- package/dist/tools/wait.js.map +1 -1
- package/dist/tools/wda.js +187 -57
- package/dist/tools/wda.js.map +1 -1
- package/dist/version.js +31 -22
- package/dist/version.js.map +1 -1
- package/dist/visual/ocr.js +53 -6
- package/dist/visual/ocr.js.map +1 -1
- package/dist/visual/provider.js +80 -12
- package/dist/visual/provider.js.map +1 -1
- package/docs/README.md +24 -10
- package/docs/ci-reports.md +328 -0
- package/docs/concepts.md +227 -0
- package/docs/flows.md +153 -0
- package/docs/mcp-server.md +194 -128
- package/docs/physical-devices.md +84 -0
- package/docs/tools.md +812 -152
- package/package.json +3 -2
- package/dist/tools/assertVisual.js +0 -84
- package/dist/tools/assertVisual.js.map +0 -1
- package/dist/tools/automationGenerate.js.map +0 -1
- package/dist/tools/capabilities.js +0 -179
- package/dist/tools/capabilities.js.map +0 -1
- package/dist/tools/detectContext.js +0 -41
- package/dist/tools/detectContext.js.map +0 -1
- package/dist/tools/flowGenerate.js.map +0 -1
- package/dist/tools/history.js +0 -86
- package/dist/tools/history.js.map +0 -1
- package/dist/tools/locator.js +0 -85
- package/dist/tools/locator.js.map +0 -1
- package/dist/tools/permissions.js +0 -174
- package/dist/tools/permissions.js.map +0 -1
- package/dist/tools/plan.js +0 -52
- package/dist/tools/plan.js.map +0 -1
- package/dist/tools/screenInfo.js +0 -76
- package/dist/tools/screenInfo.js.map +0 -1
- package/dist/tools/seed.js +0 -133
- package/dist/tools/seed.js.map +0 -1
- package/dist/tools/state.js +0 -217
- package/dist/tools/state.js.map +0 -1
- package/dist/tools/visualText.js +0 -106
- package/dist/tools/visualText.js.map +0 -1
package/dist/server.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
1
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
2
4
|
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
5
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
-
import { SessionStore } from './session/store.js';
|
|
6
|
+
import { SessionStore, decodeUriSegment, encodeUriSegment, isWithinRoot } from './session/store.js';
|
|
7
|
+
import { burnConsent, peekConsent, requestConsentDecision, runWithConsentScope, setElicitationProvider, } from './consent/consent.js';
|
|
5
8
|
import { registerDoctor } from './tools/doctor.js';
|
|
6
|
-
import { registerCapabilities } from './tools/capabilities.js';
|
|
7
9
|
import { registerStartSession } from './tools/startSession.js';
|
|
8
|
-
import { registerDetectContext } from './tools/detectContext.js';
|
|
9
|
-
import { registerPlan } from './tools/plan.js';
|
|
10
10
|
import { registerPrepareTarget } from './tools/prepareTarget.js';
|
|
11
11
|
import { registerScreenshot } from './tools/screenshot.js';
|
|
12
12
|
import { registerSnapshot } from './tools/snapshot.js';
|
|
@@ -23,7 +23,7 @@ import { registerClearOverlay } from './tools/clearOverlay.js';
|
|
|
23
23
|
import { registerJobs } from './tools/jobs.js';
|
|
24
24
|
import { registerGetArtifact } from './tools/getArtifact.js';
|
|
25
25
|
import { registerNote } from './tools/note.js';
|
|
26
|
-
import {
|
|
26
|
+
import { registerVisual } from './tools/visual.js';
|
|
27
27
|
import { registerFlow } from './tools/flow.js';
|
|
28
28
|
import { registerGenerate } from './tools/generate.js';
|
|
29
29
|
import { registerSmoke } from './tools/smoke.js';
|
|
@@ -33,8 +33,9 @@ import { registerPrepareIosTarget } from './tools/prepareIosTarget.js';
|
|
|
33
33
|
import { registerSuite } from './tools/suite.js';
|
|
34
34
|
import { registerExplore } from './tools/explore.js';
|
|
35
35
|
import { registerFirstRun } from './tools/firstRun.js';
|
|
36
|
-
import { registerAgentTools } from './tools/agent.js';
|
|
37
|
-
import { registerAppMap, resolveAppMapRoot, readAppMapResource } from './tools/appMap.js';
|
|
36
|
+
import { registerAgentTools, SERVER_INSTRUCTIONS } from './tools/agent.js';
|
|
37
|
+
import { registerAppMap, resolveAppMapRoot, readAppMapResource, listAppMapResources } from './tools/appMap.js';
|
|
38
|
+
import { appMapPath } from './appMap/store.js';
|
|
38
39
|
import { registerIssues } from './tools/issues.js';
|
|
39
40
|
import { registerTestSuite } from './tools/testSuite.js';
|
|
40
41
|
import { registerFlowRepair } from './tools/flowRepair.js';
|
|
@@ -47,16 +48,189 @@ import { registerBuild } from './tools/build.js';
|
|
|
47
48
|
import { registerBundletool } from './tools/bundletool.js';
|
|
48
49
|
import { registerPrompts } from './prompts/index.js';
|
|
49
50
|
import { log } from './lib/logger.js';
|
|
50
|
-
import { runWithResponseMode } from './lib/result.js';
|
|
51
|
+
import { qaError, runWithResponseMode } from './lib/result.js';
|
|
52
|
+
import { recordToolErrorFromResult } from './report/toolHealth.js';
|
|
51
53
|
import { computeSchemaHash, describeZodField, setSchemaHash } from './lib/schemaHash.js';
|
|
52
|
-
import { SWIPIUM_VERSION, TOOL_COUNT, TOOL_NAMES, TOOL_NAME_SET } from './version.js';
|
|
54
|
+
import { REMOVED_TOOLS, STALE_CLIENT_HINT, SWIPIUM_VERSION, TOOL_COUNT, TOOL_NAMES, TOOL_NAME_SET } from './version.js';
|
|
55
|
+
import { toolAnnotations } from './lib/toolAnnotations.js';
|
|
56
|
+
import { CAPABILITY_GROUPS } from './core/capabilityGroups.js';
|
|
57
|
+
import { ensureAndroidToolsOnPath } from './lib/android.js';
|
|
58
|
+
import { annotateRootSource, withRootResolutionRecording } from './context/projectRoot.js';
|
|
53
59
|
import { reapOrphanedProcesses } from './session/processRegistry.js';
|
|
60
|
+
import { runWithSignal } from './lib/abortScope.js';
|
|
61
|
+
/** How long a consent prompt may stay open before it counts as unanswered (> refusal).
|
|
62
|
+
* Explicit, because the SDK's default request timeout (60 s) is far too short for a human. */
|
|
63
|
+
export const ELICITATION_TIMEOUT_MS = 10 * 60_000;
|
|
54
64
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
65
|
+
* Elicitation provider (consent.ts header, THREAT_MODEL "compromised client self-approval"):
|
|
66
|
+
* asks the connected client's HUMAN to decide a consent via the MCP elicitation capability
|
|
67
|
+
* (`elicitation/create` with a flat one-boolean-field schema, per spec). Capabilities are only
|
|
68
|
+
* known after `initialize` (and tools are registered before connect), so the capability check
|
|
69
|
+
* happens lazily on every call, never at construction. Only a client that does NOT advertise
|
|
70
|
+
* form elicitation yields 'unavailable' (portable re-call fallback). Once the prompt is sent,
|
|
71
|
+
* `cancel`, a timeout, an aborted tool call or a transport error all reject/return 'cancelled',
|
|
72
|
+
* which is a refusal (MCP spec: cancel = dismissed without an explicit choice, never consent).
|
|
73
|
+
*/
|
|
74
|
+
/** Consent-prompt field hygiene: the explain / command strings interpolate repo-derived values
|
|
75
|
+
* (flow names, queries, URLs, configured argv). Strip control characters (incl. newlines, which
|
|
76
|
+
* could fake a second "Will run:" line), bidi overrides and zero-width characters, collapse
|
|
77
|
+
* whitespace, and cap the length. The caller then QUOTES the result. Exported for tests. */
|
|
78
|
+
export function sanitizePromptField(value, max = 300) {
|
|
79
|
+
const flat = String(value ?? '')
|
|
80
|
+
.replace(/[\p{Cc}\u2028\u2029\u200B-\u200F\u202A-\u202E\u2060-\u2069\uFEFF]/gu, ' ')
|
|
81
|
+
.replace(/\s+/g, ' ')
|
|
82
|
+
.trim();
|
|
83
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
|
|
84
|
+
}
|
|
85
|
+
/** The out-of-band consent prompt text. Every interpolated field is sanitised and quoted
|
|
86
|
+
* (JSON-style), the command is shown one quoted line per step, and the whole message is capped. */
|
|
87
|
+
export function buildConsentPromptMessage(req) {
|
|
88
|
+
const q = (v, max) => JSON.stringify(sanitizePromptField(v, max));
|
|
89
|
+
const lines = [`Swipium requests consent (risk: ${q(req.risk, 20)}) for action ${q(req.action, 60)}.`, `Details: ${q(req.explain, 600)}`];
|
|
90
|
+
if (req.exactCommand) {
|
|
91
|
+
const steps = String(req.exactCommand)
|
|
92
|
+
.split(/\r?\n/)
|
|
93
|
+
.filter((l) => l.trim())
|
|
94
|
+
.slice(0, 4);
|
|
95
|
+
lines.push(steps.length > 1 ? 'Will run:' : `Will run: ${q(steps[0], 500)}`);
|
|
96
|
+
if (steps.length > 1)
|
|
97
|
+
for (const step of steps)
|
|
98
|
+
lines.push(` ${q(step, 500)}`);
|
|
99
|
+
}
|
|
100
|
+
const msg = lines.join('\n');
|
|
101
|
+
return msg.length > 2000 ? `${msg.slice(0, 1999)}…` : msg;
|
|
102
|
+
}
|
|
103
|
+
function makeElicitationProvider(server) {
|
|
104
|
+
return async (req, ctx) => {
|
|
105
|
+
// The SDK normalises a bare `elicitation: {}` capability to `{ form: {} }`.
|
|
106
|
+
if (!server.server.getClientCapabilities()?.elicitation?.form)
|
|
107
|
+
return 'unavailable';
|
|
108
|
+
const answer = await server.server.elicitInput({
|
|
109
|
+
message: buildConsentPromptMessage(req),
|
|
110
|
+
requestedSchema: {
|
|
111
|
+
type: 'object',
|
|
112
|
+
properties: {
|
|
113
|
+
approve: { type: 'boolean', title: 'Approve', description: 'Allow Swipium to perform this action.' },
|
|
114
|
+
},
|
|
115
|
+
required: ['approve'],
|
|
116
|
+
},
|
|
117
|
+
}, {
|
|
118
|
+
timeout: ELICITATION_TIMEOUT_MS,
|
|
119
|
+
...(ctx?.signal ? { signal: ctx.signal } : {}),
|
|
120
|
+
...(ctx?.relatedRequestId !== undefined ? { relatedRequestId: ctx.relatedRequestId } : {}),
|
|
121
|
+
});
|
|
122
|
+
if (answer.action === 'accept')
|
|
123
|
+
return answer.content?.approve === true ? 'approved' : 'declined';
|
|
124
|
+
if (answer.action === 'decline')
|
|
125
|
+
return 'declined';
|
|
126
|
+
return 'cancelled'; // 'cancel': dismissed without answering > refusal, never a self-approval fallback
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/** Ledger a consent that was decided WITHOUT the tool running (declined / cancelled / refused by
|
|
130
|
+
* policy), so the audit trail shows the refusal next to the tool's own `requested` row. */
|
|
131
|
+
function recordConsentRefusal(sessions, tool, sessionId, consentId, req, mechanism, detail) {
|
|
132
|
+
if (!req || typeof sessionId !== 'string')
|
|
133
|
+
return;
|
|
134
|
+
const session = sessions.get(sessionId);
|
|
135
|
+
if (!session)
|
|
136
|
+
return;
|
|
137
|
+
try {
|
|
138
|
+
sessions.recordMutation(session, {
|
|
139
|
+
tool,
|
|
140
|
+
action: req.action,
|
|
141
|
+
risk: req.risk,
|
|
142
|
+
target: req.affects ?? {},
|
|
143
|
+
consent: { required: true, consentId, approved: false, approvalMechanism: mechanism },
|
|
144
|
+
status: 'refused',
|
|
145
|
+
detail,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
log('warn', 'failed to ledger a consent refusal', { tool, consentId, err: String(e) });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function pendingConsentId(result) {
|
|
153
|
+
const sc = result?.structuredContent;
|
|
154
|
+
return sc?.requiresConsent === true && typeof sc.consentId === 'string' ? sc.consentId : undefined;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Consent routing (consent.ts header): when a tool handler returns a requiresConsent envelope,
|
|
158
|
+
* route the pending consent through a REAL out-of-band user prompt before the envelope ever
|
|
159
|
+
* reaches the model. Outcomes:
|
|
160
|
+
* - elicitation approved > re-invoke the SAME handler exactly once with the consent attached
|
|
161
|
+
* (the approval was recorded, so consumeConsent tags mechanism 'elicitation');
|
|
162
|
+
* - elicitation declined > CONSENT_DECLINED; cancelled/timed out/transport error >
|
|
163
|
+
* CONSENT_CANCELLED (retry-safe: a re-call issues a fresh prompt). Either way the challenge
|
|
164
|
+
* is burned (no approve:true self-approval afterwards) and a `refused` ledger row is written;
|
|
165
|
+
* - refused (SWIPIUM_REQUIRE_ELICITATION=1 and no elicitation support) > CONSENT_REFUSED;
|
|
166
|
+
* - client-assertion (client does not advertise elicitation) > the portable envelope unchanged.
|
|
167
|
+
* The re-invocation's result is never routed again, so this cannot loop; if it asks for a NEW
|
|
168
|
+
* consent (the target changed under the prompt) that challenge is burned and CONSENT_CANCELLED
|
|
169
|
+
* returned, so the model never receives a self-approvable envelope on an elicitation client.
|
|
170
|
+
*/
|
|
171
|
+
async function routePendingConsent(result, args, reinvoke, ledger) {
|
|
172
|
+
const consentId = pendingConsentId(result);
|
|
173
|
+
if (!consentId)
|
|
174
|
+
return result;
|
|
175
|
+
const first = (args[0] ?? {});
|
|
176
|
+
const extra = args[1];
|
|
177
|
+
const req = peekConsent(consentId);
|
|
178
|
+
const decision = await requestConsentDecision(consentId, { signal: extra?.signal, relatedRequestId: extra?.requestId });
|
|
179
|
+
if (decision.mechanism === 'refused') {
|
|
180
|
+
recordConsentRefusal(ledger.sessions, ledger.tool, first.sessionId, consentId, req, 'policy', decision.reason);
|
|
181
|
+
return qaError({
|
|
182
|
+
what: decision.reason,
|
|
183
|
+
changedState: false,
|
|
184
|
+
retrySafe: false,
|
|
185
|
+
failureCode: 'CONSENT_REFUSED',
|
|
186
|
+
nextSteps: ['Connect with an MCP client that supports elicitation, or unset SWIPIUM_REQUIRE_ELICITATION.'],
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (decision.mechanism === 'elicitation') {
|
|
190
|
+
if (!decision.approved) {
|
|
191
|
+
recordConsentRefusal(ledger.sessions, ledger.tool, first.sessionId, consentId, req, 'elicitation', decision.reason);
|
|
192
|
+
if (decision.outcome === 'cancelled') {
|
|
193
|
+
return qaError({
|
|
194
|
+
what: decision.reason,
|
|
195
|
+
changedState: false,
|
|
196
|
+
retrySafe: true,
|
|
197
|
+
failureCode: 'CONSENT_CANCELLED',
|
|
198
|
+
nextSteps: ['Nothing ran. Re-call the tool (without consentId) to show the user a fresh consent prompt, or ask them first.'],
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return qaError({
|
|
202
|
+
what: 'User declined via elicitation prompt',
|
|
203
|
+
changedState: false,
|
|
204
|
+
retrySafe: false,
|
|
205
|
+
failureCode: 'CONSENT_DECLINED',
|
|
206
|
+
nextSteps: ['Do not retry this action; ask the user before attempting it again.'],
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
const out = await reinvoke([{ ...first, consentId, approve: true }, ...args.slice(1)]);
|
|
210
|
+
const again = pendingConsentId(out);
|
|
211
|
+
if (again) {
|
|
212
|
+
burnConsent(again);
|
|
213
|
+
burnConsent(consentId);
|
|
214
|
+
return qaError({
|
|
215
|
+
what: 'The action changed while the user was deciding, so the approval no longer matches it. Nothing ran.',
|
|
216
|
+
changedState: false,
|
|
217
|
+
retrySafe: true,
|
|
218
|
+
failureCode: 'CONSENT_CANCELLED',
|
|
219
|
+
nextSteps: ['Re-call the tool (without consentId) to prompt the user for the current action.'],
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
return out;
|
|
223
|
+
}
|
|
224
|
+
return result; // 'client-assertion': portable path (the model relays the consent envelope)
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Wrap every tool handler so it runs inside the calling session's response mode.
|
|
228
|
+
* Resolved once, centrally. Individual tools stay mode-agnostic.
|
|
57
229
|
* `compact` shrinks the text channel; `structuredContent` is always full.
|
|
230
|
+
* The same wrapper also routes requiresConsent envelopes through out-of-band elicitation
|
|
231
|
+
* (routePendingConsent), so every consent-gated tool inherits it with zero per-tool changes.
|
|
58
232
|
*/
|
|
59
|
-
function installResponseModeWrapper(server, sessions, surface, attempted) {
|
|
233
|
+
function installResponseModeWrapper(server, sessions, surface, attempted, paramNames) {
|
|
60
234
|
const orig = server.registerTool.bind(server);
|
|
61
235
|
const valid = (m) => m === 'compact' || m === 'normal' || m === 'verbose';
|
|
62
236
|
server.registerTool = (name, config, handler) => {
|
|
@@ -68,43 +242,243 @@ function installResponseModeWrapper(server, sessions, surface, attempted) {
|
|
|
68
242
|
const cfg = config;
|
|
69
243
|
const inputKeys = Object.entries(cfg?.inputSchema ?? {}).map(([k, v]) => `${k}:${describeZodField(v)}`);
|
|
70
244
|
surface.push({ name, description: cfg?.description ?? '', inputKeys });
|
|
71
|
-
|
|
245
|
+
paramNames.set(name, Object.keys(cfg?.inputSchema ?? {}));
|
|
246
|
+
// MCP annotations for every tool, from one reviewed table (src/lib/toolAnnotations.ts).
|
|
247
|
+
const annotated = { ...config, annotations: toolAnnotations(name) };
|
|
248
|
+
return orig(name, annotated, async (...a) => {
|
|
72
249
|
const first = a[0];
|
|
73
250
|
// Prefer the existing session's mode; fall back to a directly-passed responseMode so the
|
|
74
251
|
// session-CREATING call (qa_start_session, no sessionId yet) also honors compact.
|
|
75
252
|
const fromSession = first?.sessionId ? sessions.get(first.sessionId)?.responseMode : undefined;
|
|
76
|
-
const mode = fromSession ?? (valid(first?.responseMode) ? first.responseMode : 'normal');
|
|
77
|
-
|
|
253
|
+
const mode = (fromSession ?? (valid(first?.responseMode) ? first.responseMode : 'normal'));
|
|
254
|
+
// Every call records the project root it resolves (if any) so the result carries
|
|
255
|
+
// `rootSource` (+ a note when the root was only guessed from the server cwd).
|
|
256
|
+
// Consents minted/consumed during this call are bound to its sessionId (consent.ts).
|
|
257
|
+
// Cancellation: the call's MCP signal (extra.signal, the handler's last argument) is scoped
|
|
258
|
+
// to THIS call (abortScope): driver adb/WDA calls made by the tool abort with it, and a
|
|
259
|
+
// background job's signal (bound by the job itself) never leaks into or out of it.
|
|
260
|
+
const extra = a[a.length - 1];
|
|
261
|
+
const callSignal = extra?.signal instanceof AbortSignal ? extra.signal : undefined;
|
|
262
|
+
const run = async (callArgs) => {
|
|
263
|
+
const { value, resolved } = await runWithSignal(callSignal, () => withRootResolutionRecording(async () => runWithConsentScope(first?.sessionId, () => runWithResponseMode(mode, () => handler(...callArgs)))));
|
|
264
|
+
return annotateRootSource(value, resolved);
|
|
265
|
+
};
|
|
266
|
+
const result = await run(a);
|
|
267
|
+
const out = await routePendingConsent(result, a, run, { sessions, tool: name });
|
|
268
|
+
recordToolErrorFromResult(sessions, name, a[0], out, callSignal); // qa_report tool status (report/toolHealth.ts)
|
|
269
|
+
return out;
|
|
78
270
|
});
|
|
79
271
|
};
|
|
80
272
|
}
|
|
81
|
-
/**
|
|
273
|
+
/** Legacy (≤ 1.5) call shapes that a client spawned before the upgrade may still send, mapped to
|
|
274
|
+
* the 2.0 replacement. Returned as a typed STALE_CLIENT error instead of a raw "Tool not found" /
|
|
275
|
+
* zod validation message, WITHOUT polluting the current schemas/descriptions. Exported for tests. */
|
|
276
|
+
export function staleClientReplacement(name, args) {
|
|
277
|
+
if (!TOOL_NAME_SET.has(name))
|
|
278
|
+
return REMOVED_TOOLS[name];
|
|
279
|
+
const action = args?.action;
|
|
280
|
+
if (name === 'qa_ios' && typeof action === 'string') {
|
|
281
|
+
if (action === 'screenshot')
|
|
282
|
+
return 'qa_screenshot { sessionId }';
|
|
283
|
+
if (action.startsWith('wda_'))
|
|
284
|
+
return `qa_wda { sessionId, action: "${action.slice(4) || 'status'}" }`;
|
|
285
|
+
}
|
|
286
|
+
if (name === 'qa_wait' && args?.for === 'job_done')
|
|
287
|
+
return 'qa_job_status { sessionId, jobId, waitMs }';
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
function staleClientError(name, replacement) {
|
|
291
|
+
return qaError({
|
|
292
|
+
what: `${name} (this call shape) was removed in Swipium v${SWIPIUM_VERSION}; use ${replacement}`,
|
|
293
|
+
changedState: false,
|
|
294
|
+
retrySafe: true,
|
|
295
|
+
failureCode: 'STALE_CLIENT',
|
|
296
|
+
nextSteps: [`Call ${replacement} instead.`, 'Restart the MCP client so it reloads the current tool list.'],
|
|
297
|
+
clientHint: STALE_CLIENT_HINT,
|
|
298
|
+
}, { removedCall: name, replacement });
|
|
299
|
+
}
|
|
300
|
+
/** The SDK stores handlers per method; wrapping the stored (already SDK-wrapped) function keeps
|
|
301
|
+
* the SDK's own request/result validation intact. */
|
|
302
|
+
function wrapRequestHandler(server, method, wrap) {
|
|
303
|
+
const map = server.server._requestHandlers;
|
|
304
|
+
const orig = map?.get(method);
|
|
305
|
+
if (!map || !orig) {
|
|
306
|
+
log('warn', 'could not wrap MCP request handler (SDK internals changed?)', { method });
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
map.set(method, wrap(orig));
|
|
310
|
+
}
|
|
311
|
+
/** Drop the per-schema `$schema` dialect key (~2.8 KB of repetition across the tool list). */
|
|
312
|
+
export function stripSchemaDialect(result) {
|
|
313
|
+
const tools = result?.tools;
|
|
314
|
+
if (!Array.isArray(tools))
|
|
315
|
+
return result;
|
|
316
|
+
for (const t of tools) {
|
|
317
|
+
for (const k of ['inputSchema', 'outputSchema']) {
|
|
318
|
+
const sch = t[k];
|
|
319
|
+
if (sch && typeof sch === 'object' && '$schema' in sch)
|
|
320
|
+
delete sch.$schema;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
/** Top-level argument keys a tool's input schema does not declare. The advertised JSON schema
|
|
326
|
+
* says additionalProperties:false, but the SDK's zod object silently STRIPS unknown keys, so a
|
|
327
|
+
* call like qa_app_control { action:"force_stop", appId:"other.app" } used to run against the
|
|
328
|
+
* session's app while the caller believed it targeted another. Deprecated aliases that are still
|
|
329
|
+
* declared in the schema are accepted (they are schema properties). Exported for tests. */
|
|
330
|
+
export function unknownArgumentKeys(args, accepted) {
|
|
331
|
+
if (!args || typeof args !== 'object' || Array.isArray(args))
|
|
332
|
+
return [];
|
|
333
|
+
const known = new Set(accepted);
|
|
334
|
+
return Object.keys(args).filter((k) => !known.has(k));
|
|
335
|
+
}
|
|
336
|
+
export function unknownArgumentsError(name, unknown, accepted) {
|
|
337
|
+
const list = (keys) => keys.map((k) => JSON.stringify(k)).join(', ');
|
|
338
|
+
return qaError({
|
|
339
|
+
what: `${name} does not accept the argument${unknown.length === 1 ? '' : 's'} ${list(unknown)}. Nothing was run.`,
|
|
340
|
+
changedState: false,
|
|
341
|
+
retrySafe: true,
|
|
342
|
+
failureCode: 'INVALID_ARGUMENT',
|
|
343
|
+
nextSteps: [
|
|
344
|
+
`Remove ${list(unknown)} and re-call. Accepted parameters: ${accepted.length ? list(accepted) : '(none)'}.`,
|
|
345
|
+
'If the tool list looks outdated, restart the MCP client so it reloads the current schemas.',
|
|
346
|
+
],
|
|
347
|
+
}, { unknownArguments: unknown, acceptedParameters: [...accepted] });
|
|
348
|
+
}
|
|
349
|
+
/** tools/call: unknown removed-tool names and legacy call shapes > STALE_CLIENT (with the
|
|
350
|
+
* replacement + stale-client hint); undeclared top-level arguments > INVALID_ARGUMENT (before the
|
|
351
|
+
* handler runs); tools/list: strip `$schema`. */
|
|
352
|
+
function installProtocolShims(server, paramNames) {
|
|
353
|
+
wrapRequestHandler(server, 'tools/call', (orig) => async (request, extra) => {
|
|
354
|
+
const name = String(request?.params?.name ?? '');
|
|
355
|
+
const args = request?.params?.arguments;
|
|
356
|
+
const replacement = staleClientReplacement(name, args);
|
|
357
|
+
if (replacement && !TOOL_NAME_SET.has(name))
|
|
358
|
+
return staleClientError(name, replacement);
|
|
359
|
+
const accepted = paramNames.get(name);
|
|
360
|
+
if (accepted && !replacement) {
|
|
361
|
+
const unknown = unknownArgumentKeys(args, accepted);
|
|
362
|
+
if (unknown.length)
|
|
363
|
+
return unknownArgumentsError(name, unknown, accepted);
|
|
364
|
+
}
|
|
365
|
+
const result = (await orig(request, extra));
|
|
366
|
+
// Legacy enum values fail the CURRENT schema's validation > rewrite that raw error only.
|
|
367
|
+
if (replacement && result?.isError && !result.structuredContent)
|
|
368
|
+
return staleClientError(`${name} ${JSON.stringify(args?.action ?? args?.for)}`, replacement);
|
|
369
|
+
return result;
|
|
370
|
+
});
|
|
371
|
+
wrapRequestHandler(server, 'tools/list', (orig) => async (request, extra) => stripSchemaDialect(await orig(request, extra)));
|
|
372
|
+
}
|
|
373
|
+
/** Startup assertion: every registerTool() call must be
|
|
82
374
|
* allowlisted in TOOL_NAMES, and every TOOL_NAMES entry must actually get registered.
|
|
83
375
|
* Without this, a tool missing from the allowlist is silently discarded by the wrapper
|
|
84
376
|
* above, and a stale TOOL_NAMES entry silently over-reports the surface. Fail LOUDLY. */
|
|
85
377
|
function assertToolSurface(attempted) {
|
|
86
378
|
const missing = TOOL_NAMES.filter((n) => !attempted.has(n));
|
|
87
379
|
const extra = [...attempted].filter((n) => !TOOL_NAME_SET.has(n));
|
|
380
|
+
// Every tool sits in exactly one capability group (qa_status orientation + docs/tools.md layout).
|
|
381
|
+
const grouped = CAPABILITY_GROUPS.flatMap((g) => g.tools);
|
|
382
|
+
const ungrouped = TOOL_NAMES.filter((n) => grouped.filter((t) => t === n).length !== 1);
|
|
383
|
+
if (ungrouped.length)
|
|
384
|
+
throw new Error(`CAPABILITY_GROUPS (src/core/capabilityGroups.ts) must list each tool exactly once: ${ungrouped.join(', ')}`);
|
|
88
385
|
if (missing.length === 0 && extra.length === 0 && attempted.size === TOOL_NAMES.length)
|
|
89
386
|
return;
|
|
90
387
|
throw new Error(`Tool surface mismatch: ${attempted.size} tools registered vs ${TOOL_NAMES.length} in TOOL_NAMES (src/version.ts).` +
|
|
91
388
|
(missing.length ? `\n Missing (allowlisted but never registered): ${missing.join(', ')}` : '') +
|
|
92
|
-
(extra.length ? `\n Extra (registered but not in TOOL_NAMES
|
|
389
|
+
(extra.length ? `\n Extra (registered but not in TOOL_NAMES, so they would be silently dropped): ${extra.join(', ')}` : '') +
|
|
93
390
|
'\nFix: add/remove the tool in TOOL_NAMES and CAPABILITY_GROUPS, or register it in createServer().');
|
|
94
391
|
}
|
|
392
|
+
/** resources/list size cap. The SDK aggregates every template's list callback into one
|
|
393
|
+
* un-paginated resources/list response, so each listing stays bounded. */
|
|
394
|
+
const RESOURCE_LIST_CAP = 100;
|
|
395
|
+
/** Cap a resources/list result and DISCLOSE the truncation on the final entry's description.
|
|
396
|
+
* Silent truncation is forbidden. Clients read anything omitted here directly by URI
|
|
397
|
+
* (qa_get_artifact / qa_app_map_read always cover the full set). */
|
|
398
|
+
function capResourceListing(all, cap = RESOURCE_LIST_CAP) {
|
|
399
|
+
if (all.length <= cap)
|
|
400
|
+
return all;
|
|
401
|
+
const shown = all.slice(0, cap);
|
|
402
|
+
const last = shown[shown.length - 1];
|
|
403
|
+
last.description = `${last.description ? `${last.description} ` : ''}[listing capped: showing ${cap} of ${all.length}; the rest remain readable by URI]`;
|
|
404
|
+
return shown;
|
|
405
|
+
}
|
|
406
|
+
/** Project roots the CURRENT client works in: its MCP roots (when it advertises the capability)
|
|
407
|
+
* plus the roots of sessions created or used in this server process. resources/list is scoped to
|
|
408
|
+
* these so one client never browses another project's artifacts from the machine-wide registry. */
|
|
409
|
+
async function currentProjectRoots(server, sessions) {
|
|
410
|
+
const roots = new Set(sessions.activeRoots());
|
|
411
|
+
try {
|
|
412
|
+
if (server.server.getClientCapabilities()?.roots) {
|
|
413
|
+
const res = await server.server.listRoots(undefined, { timeout: 5_000 });
|
|
414
|
+
for (const r of res.roots ?? [])
|
|
415
|
+
if (typeof r.uri === 'string' && r.uri.startsWith('file://'))
|
|
416
|
+
roots.add(fileURLToPath(r.uri));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
// roots/list failed: fall back to the process-local roots only
|
|
421
|
+
}
|
|
422
|
+
return [...roots];
|
|
423
|
+
}
|
|
424
|
+
/** Sessions whose artifacts/maps may be LISTED: inside a current project root. */
|
|
425
|
+
function sessionsInRoots(sessions, roots) {
|
|
426
|
+
return sessions.list().filter((s) => roots.some((r) => isWithinRoot(s.root, r)));
|
|
427
|
+
}
|
|
428
|
+
/** Re-encode a stored artifact URI so every segment matches the qa-artifact template (older
|
|
429
|
+
* records stored raw names; a raw `/` inside the name spans several segments). */
|
|
430
|
+
function listedArtifactUri(uri) {
|
|
431
|
+
const prefix = 'swipium://session/';
|
|
432
|
+
if (!uri.startsWith(prefix))
|
|
433
|
+
return uri;
|
|
434
|
+
const [id = '', kind = '', ...rest] = uri.slice(prefix.length).split('/');
|
|
435
|
+
const seg = (x) => encodeUriSegment(decodeUriSegment(x));
|
|
436
|
+
return `${prefix}${seg(id)}/${seg(kind)}/${seg(rest.map(decodeUriSegment).join('/'))}`;
|
|
437
|
+
}
|
|
438
|
+
/** App-map listings load every map JSON, so cache per scope, keyed on each root's map mtime.
|
|
439
|
+
* The short TTL also covers roots known only to src/tools/appMap.ts's in-process registry. */
|
|
440
|
+
const APP_MAP_LIST_TTL_MS = 10_000;
|
|
441
|
+
function makeAppMapLister(sessions) {
|
|
442
|
+
const cache = new Map();
|
|
443
|
+
return (roots, which) => {
|
|
444
|
+
const scoped = sessionsInRoots(sessions, roots);
|
|
445
|
+
const scopedRoots = [...new Set([...scoped.map((s) => s.root), ...roots])].sort();
|
|
446
|
+
const key = scopedRoots
|
|
447
|
+
.map((r) => {
|
|
448
|
+
try {
|
|
449
|
+
return `${r}@${statSync(appMapPath(r)).mtimeMs}`;
|
|
450
|
+
}
|
|
451
|
+
catch {
|
|
452
|
+
return `${r}@-`;
|
|
453
|
+
}
|
|
454
|
+
})
|
|
455
|
+
.join('|');
|
|
456
|
+
const hit = cache.get(which);
|
|
457
|
+
if (hit && hit.key === key && Date.now() - hit.at < APP_MAP_LIST_TTL_MS)
|
|
458
|
+
return hit.value;
|
|
459
|
+
// listAppMapResources enumerates roots via store.list(); hand it only the in-scope roots.
|
|
460
|
+
const scopedStore = { list: () => scopedRoots.map((root) => ({ root })) };
|
|
461
|
+
const value = listAppMapResources(scopedStore, which);
|
|
462
|
+
cache.set(which, { key, at: Date.now(), value });
|
|
463
|
+
return value;
|
|
464
|
+
};
|
|
465
|
+
}
|
|
95
466
|
/** Construct the server and register all tools + the artifact resource. Exported for tests. */
|
|
96
467
|
export function createServer() {
|
|
97
|
-
const server = new McpServer({ name: 'swipium', version: SWIPIUM_VERSION });
|
|
468
|
+
const server = new McpServer({ name: 'swipium', version: SWIPIUM_VERSION }, { instructions: SERVER_INSTRUCTIONS });
|
|
469
|
+
// Out-of-band consent (consent.ts header): when the connected client supports MCP
|
|
470
|
+
// elicitation, pending consents are decided by a real user prompt instead of a
|
|
471
|
+
// model-mediated re-call. The provider checks client capabilities lazily per call,
|
|
472
|
+
// since they are only known after `initialize` (long after tool registration).
|
|
473
|
+
setElicitationProvider(makeElicitationProvider(server));
|
|
98
474
|
const sessions = new SessionStore();
|
|
99
475
|
const surface = [];
|
|
100
476
|
const attemptedToolNames = new Set();
|
|
101
|
-
|
|
477
|
+
const paramNames = new Map();
|
|
478
|
+
installResponseModeWrapper(server, sessions, surface, attemptedToolNames, paramNames);
|
|
102
479
|
// Setup / context
|
|
103
480
|
registerDoctor(server);
|
|
104
|
-
registerCapabilities(server, sessions);
|
|
105
481
|
registerStartSession(server, sessions);
|
|
106
|
-
registerDetectContext(server, sessions);
|
|
107
|
-
registerPlan(server, sessions);
|
|
108
482
|
registerPrepareTarget(server, sessions);
|
|
109
483
|
registerIos(server, sessions);
|
|
110
484
|
registerWda(server, sessions);
|
|
@@ -124,7 +498,7 @@ export function createServer() {
|
|
|
124
498
|
registerJobs(server, sessions);
|
|
125
499
|
registerGetArtifact(server, sessions);
|
|
126
500
|
registerNote(server, sessions);
|
|
127
|
-
|
|
501
|
+
registerVisual(server, sessions);
|
|
128
502
|
registerFlow(server, sessions);
|
|
129
503
|
registerGenerate(server, sessions);
|
|
130
504
|
registerSmoke(server, sessions);
|
|
@@ -136,7 +510,7 @@ export function createServer() {
|
|
|
136
510
|
registerFirstRun(server, sessions);
|
|
137
511
|
registerAgentTools(server, sessions);
|
|
138
512
|
registerAppMap(server, sessions);
|
|
139
|
-
// Durable QA memory + repeatable assets
|
|
513
|
+
// Durable QA memory + repeatable assets
|
|
140
514
|
registerIssues(server, sessions);
|
|
141
515
|
registerMobileAudit(server, sessions);
|
|
142
516
|
registerTestSuite(server, sessions);
|
|
@@ -153,10 +527,32 @@ export function createServer() {
|
|
|
153
527
|
// freeze the surface's content fingerprint.
|
|
154
528
|
assertToolSurface(attemptedToolNames);
|
|
155
529
|
setSchemaHash(computeSchemaHash(surface));
|
|
156
|
-
|
|
530
|
+
installProtocolShims(server, paramNames);
|
|
531
|
+
// Reusable workflow templates (MCP prompts capability): thin orchestration of the tools above.
|
|
157
532
|
registerPrompts(server);
|
|
158
533
|
// Artifacts as MCP resources (clients that support them); qa_get_artifact is the fallback.
|
|
159
|
-
|
|
534
|
+
// The list callback lets resource-aware clients BROWSE artifacts instead of mining
|
|
535
|
+
// URIs out of tool text: newest RESOURCE_LIST_CAP across the sessions of the CURRENT project
|
|
536
|
+
// root(s) (currentProjectRoots), straight from the in-memory ledger (no device calls).
|
|
537
|
+
// Sensitive-mode sessions are never listed (their artifacts stay readable by exact URI only).
|
|
538
|
+
// ArtifactRecord carries no byte size, so the description is kind + label; sizes come from
|
|
539
|
+
// qa_get_artifact { mode: "metadata" }.
|
|
540
|
+
server.registerResource('qa-artifact', new ResourceTemplate('swipium://session/{sessionId}/{kind}/{name}', {
|
|
541
|
+
list: async () => {
|
|
542
|
+
const roots = await currentProjectRoots(server, sessions);
|
|
543
|
+
const all = sessionsInRoots(sessions, roots)
|
|
544
|
+
.filter((s) => !s.sensitive)
|
|
545
|
+
.flatMap((s) => s.artifacts)
|
|
546
|
+
.sort((a, b) => b.createdAt - a.createdAt)
|
|
547
|
+
.map((rec) => ({
|
|
548
|
+
uri: listedArtifactUri(rec.uri),
|
|
549
|
+
name: basename(rec.path),
|
|
550
|
+
mimeType: rec.mime,
|
|
551
|
+
description: rec.label ? `${rec.kind}: ${rec.label}` : rec.kind,
|
|
552
|
+
}));
|
|
553
|
+
return { resources: capResourceListing(all) };
|
|
554
|
+
},
|
|
555
|
+
}), { title: 'QA artifact', description: 'Session artifacts: screenshots, dumps, reports, logs.' }, async (uri) => {
|
|
160
556
|
const found = sessions.findArtifact(uri.href);
|
|
161
557
|
if (!found)
|
|
162
558
|
throw new Error(`Unknown artifact ${uri.href}`);
|
|
@@ -166,23 +562,35 @@ export function createServer() {
|
|
|
166
562
|
}
|
|
167
563
|
return { contents: [{ uri: uri.href, mimeType: rec.mime, text: readFileSync(rec.path, 'utf8') }] };
|
|
168
564
|
});
|
|
169
|
-
// App Knowledge Map as MCP resources
|
|
565
|
+
// App Knowledge Map as MCP resources: full map + per-feature / per-screen /
|
|
170
566
|
// test-suite sections, so large map data is read by URI instead of flooding a tool's text result.
|
|
171
|
-
|
|
172
|
-
|
|
567
|
+
// Both app-map templates list what is ACTUALLY readable right now: section/full URIs
|
|
568
|
+
// for every project root known to this run that has a map on disk; no map > empty list, never
|
|
569
|
+
// an error. Enumeration is a local JSON load (listAppMapResources), scoped to the current project
|
|
570
|
+
// root(s) and cached on map mtime (makeAppMapLister), no device calls. Listed section ids are
|
|
571
|
+
// percent-encoded by listAppMapResources; the read handlers decode template variables.
|
|
572
|
+
const listAppMaps = makeAppMapLister(sessions);
|
|
573
|
+
server.registerResource('qa-app-map', new ResourceTemplate('swipium://project/{projectId}/app-map/{kind}/{id}', {
|
|
574
|
+
list: async () => ({ resources: capResourceListing(listAppMaps(await currentProjectRoots(server, sessions), 'sections')) }),
|
|
575
|
+
}), { title: 'App knowledge map', description: 'Durable app map: full map, or a feature/screen/test-suite section.' }, async (uri, vars) => {
|
|
576
|
+
const projectId = decodeUriSegment(String(vars.projectId));
|
|
577
|
+
const root = resolveAppMapRoot(projectId, sessions);
|
|
173
578
|
if (!root)
|
|
174
|
-
throw new Error(`Unknown project ${
|
|
175
|
-
const kind = vars.kind ? String(vars.kind) : undefined;
|
|
176
|
-
const res = readAppMapResource(root, { kind, id: vars.id ? String(vars.id) : undefined });
|
|
579
|
+
throw new Error(`Unknown project ${projectId} (build the map first)`);
|
|
580
|
+
const kind = vars.kind ? decodeUriSegment(String(vars.kind)) : undefined;
|
|
581
|
+
const res = readAppMapResource(root, { kind, id: vars.id ? decodeUriSegment(String(vars.id)) : undefined });
|
|
177
582
|
if (!res)
|
|
178
583
|
throw new Error(`No app-map resource for ${uri.href}`);
|
|
179
584
|
return { contents: [{ uri: uri.href, mimeType: res.mimeType, text: res.text }] };
|
|
180
585
|
});
|
|
181
586
|
// Bare full-map URI: swipium://project/{projectId}/app-map (no trailing section).
|
|
182
|
-
server.registerResource('qa-app-map-full', new ResourceTemplate('swipium://project/{projectId}/app-map', {
|
|
183
|
-
|
|
587
|
+
server.registerResource('qa-app-map-full', new ResourceTemplate('swipium://project/{projectId}/app-map', {
|
|
588
|
+
list: async () => ({ resources: capResourceListing(listAppMaps(await currentProjectRoots(server, sessions), 'full')) }),
|
|
589
|
+
}), { title: 'App knowledge map (full)', description: 'The complete durable app map JSON.' }, async (uri, vars) => {
|
|
590
|
+
const projectId = decodeUriSegment(String(vars.projectId));
|
|
591
|
+
const root = resolveAppMapRoot(projectId, sessions);
|
|
184
592
|
if (!root)
|
|
185
|
-
throw new Error(`Unknown project ${
|
|
593
|
+
throw new Error(`Unknown project ${projectId} (build the map first)`);
|
|
186
594
|
const res = readAppMapResource(root, {});
|
|
187
595
|
if (!res)
|
|
188
596
|
throw new Error(`No app map for ${uri.href}`);
|
|
@@ -191,18 +599,19 @@ export function createServer() {
|
|
|
191
599
|
return { server, sessions };
|
|
192
600
|
}
|
|
193
601
|
export async function startServer() {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
// concurrent instance and against recycled PIDs.
|
|
602
|
+
// GUI MCP clients don't inherit the shell PATH: APPEND the Android SDK's platform-tools/ and
|
|
603
|
+
// emulator/ to PATH, but only for a tool (adb / emulator) that is not already resolvable on PATH,
|
|
604
|
+
// so the user's own copy is never shadowed. Idempotent (the CLI entry already calls it); done
|
|
605
|
+
// here too so embedders that call startServer() directly get it.
|
|
199
606
|
try {
|
|
200
|
-
|
|
607
|
+
ensureAndroidToolsOnPath();
|
|
201
608
|
}
|
|
202
609
|
catch (e) {
|
|
203
|
-
log('warn', '
|
|
610
|
+
log('warn', 'android sdk path setup failed', { err: String(e) });
|
|
204
611
|
}
|
|
205
|
-
|
|
612
|
+
const { server, sessions } = createServer();
|
|
613
|
+
const transport = new StdioServerTransport();
|
|
614
|
+
// Persistence is debounced (SessionStore.persist), so make sure a graceful exit never
|
|
206
615
|
// loses the trailing write. 'exit' handlers must be synchronous; flushAll is.
|
|
207
616
|
process.once('exit', () => sessions.flushAll());
|
|
208
617
|
// Best-effort: restore any network state Swipium changed, on shutdown / client disconnect
|
|
@@ -218,6 +627,8 @@ export async function startServer() {
|
|
|
218
627
|
await restoreAllNetwork(sessions);
|
|
219
628
|
await stopAllRecordings(); // don't leave a device screen-recording after we exit
|
|
220
629
|
await stopAllMetro(sessions); // don't leave a node bundler holding :8081 after we exit
|
|
630
|
+
// Managed WDA is NOT stopped: the next server adopts it if healthy and < 12 h old
|
|
631
|
+
// (processRegistry.reapOrphanedProcesses), else reaps it. `qa_wda stop` stops it explicitly.
|
|
221
632
|
log('info', 'shutdown: restore done');
|
|
222
633
|
}
|
|
223
634
|
catch (e) {
|
|
@@ -231,7 +642,7 @@ export async function startServer() {
|
|
|
231
642
|
// Startup banner (P1.8): version + tool count on stderr so a stale build is obvious in logs.
|
|
232
643
|
log('info', 'swipium starting', { version: SWIPIUM_VERSION, tools: TOOL_COUNT });
|
|
233
644
|
await server.connect(transport);
|
|
234
|
-
// Chain AFTER connect
|
|
645
|
+
// Chain AFTER connect: server.connect() sets its own transport.onclose, so we must wrap
|
|
235
646
|
// it rather than assign before (which gets overwritten). stdin EOF = client disconnected.
|
|
236
647
|
const sdkOnClose = transport.onclose;
|
|
237
648
|
transport.onclose = () => {
|
|
@@ -239,5 +650,21 @@ export async function startServer() {
|
|
|
239
650
|
void restoreThenExit(0, 'transport-close');
|
|
240
651
|
};
|
|
241
652
|
log('info', 'swipium connected over stdio');
|
|
653
|
+
// Reap long-lived children (Metro, managed WDA, recorders) left behind by a crashed previous
|
|
654
|
+
// server run. Runs AFTER connect and in the background, so a slow sweep (lock wait, WDA /status
|
|
655
|
+
// probes) never delays the server becoming ready. Ownership + start-time/command fingerprint
|
|
656
|
+
// checks make this safe next to a live concurrent instance and against recycled PIDs. A managed
|
|
657
|
+
// WDA < 12 h old whose /status is healthy is ADOPTED instead (re-owned by this server) so a
|
|
658
|
+
// resumed iOS session keeps its WDA (shutdown intentionally leaves managed WDA running).
|
|
659
|
+
void startOrphanSweep();
|
|
660
|
+
}
|
|
661
|
+
/** Background orphan sweep; never rejects. Exported for tests. */
|
|
662
|
+
export async function startOrphanSweep(reap = reapOrphanedProcesses) {
|
|
663
|
+
try {
|
|
664
|
+
await reap();
|
|
665
|
+
}
|
|
666
|
+
catch (e) {
|
|
667
|
+
log('warn', 'orphaned-process sweep failed', { err: String(e) });
|
|
668
|
+
}
|
|
242
669
|
}
|
|
243
670
|
//# sourceMappingURL=server.js.map
|