swipium 1.4.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 +313 -0
- package/README.md +175 -219
- package/THREAT_MODEL.md +223 -47
- package/dist/appMap/automationLink.js +39 -26
- package/dist/appMap/automationLink.js.map +1 -1
- package/dist/appMap/build.js +68 -20
- package/dist/appMap/build.js.map +1 -1
- package/dist/appMap/codeIndex.js +31 -5
- package/dist/appMap/codeIndex.js.map +1 -1
- package/dist/appMap/featureIndex.js +23 -5
- package/dist/appMap/featureIndex.js.map +1 -1
- package/dist/appMap/featureModel.js +44 -8
- package/dist/appMap/featureModel.js.map +1 -1
- package/dist/appMap/firstRunApply.js +24 -6
- package/dist/appMap/firstRunApply.js.map +1 -1
- package/dist/appMap/fsWalk.js +19 -2
- package/dist/appMap/fsWalk.js.map +1 -1
- package/dist/appMap/issues.js +10 -30
- package/dist/appMap/issues.js.map +1 -1
- package/dist/appMap/migrations.js +6 -6
- 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 +83 -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 +21 -5
- package/dist/appMap/query.js.map +1 -1
- package/dist/appMap/runtimeMerge.js +12 -5
- 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 +6 -2
- package/dist/appMap/screenMatch.js.map +1 -1
- package/dist/appMap/staticScan.js +151 -39
- package/dist/appMap/staticScan.js.map +1 -1
- package/dist/appMap/store.js +58 -85
- package/dist/appMap/store.js.map +1 -1
- package/dist/appMap/tsAstScan.js +34 -12
- package/dist/appMap/tsAstScan.js.map +1 -1
- package/dist/artifacts/bundletool.js +49 -12
- package/dist/artifacts/bundletool.js.map +1 -1
- package/dist/artifacts/resolve.js +41 -14
- package/dist/artifacts/resolve.js.map +1 -1
- package/dist/automation/capabilities.js +112 -30
- package/dist/automation/capabilities.js.map +1 -1
- package/dist/automation/gestures.js +6 -2
- package/dist/automation/gestures.js.map +1 -1
- package/dist/automation/plan.js +71 -23
- package/dist/automation/plan.js.map +1 -1
- package/dist/automation/report.js +15 -5
- package/dist/automation/report.js.map +1 -1
- package/dist/automation/selectors.js +22 -28
- 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 +14 -8
- package/dist/automation/waits.js.map +1 -1
- package/dist/automation/webview.js +9 -3
- package/dist/automation/webview.js.map +1 -1
- package/dist/automationGen/appiumModel.js +16 -8
- package/dist/automationGen/appiumModel.js.map +1 -1
- package/dist/automationGen/ciEmitter.js +6 -6
- 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 +288 -121
- 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 +76 -27
- package/dist/automationGen/projectProfile.js.map +1 -1
- package/dist/automationGen/pythonEmitter.js +278 -107
- 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/automationGen/run.js +388 -0
- package/dist/automationGen/run.js.map +1 -0
- package/dist/automationGen/suitePlan.js +17 -10
- package/dist/automationGen/suitePlan.js.map +1 -1
- package/dist/automationGen/validation.js +52 -12
- package/dist/automationGen/validation.js.map +1 -1
- package/dist/build/parseBuildLog.js +235 -31
- package/dist/build/parseBuildLog.js.map +1 -1
- package/dist/build/plan.js +39 -18
- 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 +36 -15
- package/dist/cli/scan.js.map +1 -1
- package/dist/cli/suite.js +7 -7
- 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 +55 -18
- package/dist/context/detect.js.map +1 -1
- package/dist/context/findApps.js +22 -8
- 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 +10 -8
- package/dist/core/target.js.map +1 -1
- package/dist/core/targetPlan.js +160 -39
- package/dist/core/targetPlan.js.map +1 -1
- package/dist/drivers/DirectDriver.js +317 -43
- package/dist/drivers/DirectDriver.js.map +1 -1
- package/dist/drivers/SimctlDriver.js +124 -8
- package/dist/drivers/SimctlDriver.js.map +1 -1
- package/dist/drivers/WdaDriver.js +365 -55
- package/dist/drivers/WdaDriver.js.map +1 -1
- package/dist/explore/candidates.js +7 -3
- package/dist/explore/candidates.js.map +1 -1
- package/dist/explore/graph.js +15 -6
- package/dist/explore/graph.js.map +1 -1
- package/dist/explore/memory.js +4 -1
- package/dist/explore/memory.js.map +1 -1
- package/dist/explore/planner.js +54 -9
- package/dist/explore/planner.js.map +1 -1
- package/dist/explore/policy.js +11 -6
- package/dist/explore/policy.js.map +1 -1
- package/dist/explore/runner.js +363 -190
- package/dist/explore/runner.js.map +1 -1
- package/dist/explore/signatures.js +4 -2
- package/dist/explore/signatures.js.map +1 -1
- package/dist/explore/suite.js +3 -1
- package/dist/explore/suite.js.map +1 -1
- package/dist/featureTesting/executionBootstrap.js +137 -29
- package/dist/featureTesting/executionBootstrap.js.map +1 -1
- package/dist/featureTesting/featureMap.js +6 -3
- package/dist/featureTesting/featureMap.js.map +1 -1
- package/dist/featureTesting/featureScope.js +101 -20
- package/dist/featureTesting/featureScope.js.map +1 -1
- package/dist/featureTesting/mapFeatureScope.js +44 -15
- package/dist/featureTesting/mapFeatureScope.js.map +1 -1
- package/dist/featureTesting/objectiveModel.js +15 -11
- package/dist/featureTesting/objectiveModel.js.map +1 -1
- package/dist/featureTesting/resultMerge.js +16 -8
- package/dist/featureTesting/resultMerge.js.map +1 -1
- package/dist/featureTesting/sources.js +8 -5
- package/dist/featureTesting/sources.js.map +1 -1
- package/dist/featureTesting/suiteBridge.js +8 -2
- package/dist/featureTesting/suiteBridge.js.map +1 -1
- package/dist/featureTesting/synonyms.js +32 -4
- package/dist/featureTesting/synonyms.js.map +1 -1
- package/dist/featureTesting/testCaseFactory.js +27 -15
- package/dist/featureTesting/testCaseFactory.js.map +1 -1
- package/dist/featureTesting/testPlan.js +34 -12
- 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 +9 -6
- package/dist/firstRun/classifyScreen.js.map +1 -1
- package/dist/firstRun/firstRunPlanner.js +16 -13
- package/dist/firstRun/firstRunPlanner.js.map +1 -1
- package/dist/firstRun/firstRunRunner.js +92 -31
- package/dist/firstRun/firstRunRunner.js.map +1 -1
- package/dist/firstRun/generatedDataPolicy.js +25 -15
- package/dist/firstRun/generatedDataPolicy.js.map +1 -1
- package/dist/firstRun/inputPlanner.js +45 -11
- 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 +72 -9
- 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 +68 -17
- package/dist/flows/generate.js.map +1 -1
- package/dist/flows/lint.js +88 -14
- package/dist/flows/lint.js.map +1 -1
- package/dist/flows/pack.js +21 -5
- 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 +176 -37
- package/dist/flows/repair.js.map +1 -1
- package/dist/flows/run.js +308 -99
- package/dist/flows/run.js.map +1 -1
- package/dist/flows/schema.js +107 -25
- package/dist/flows/schema.js.map +1 -1
- package/dist/flows/seedExec.js +8 -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/ios/signing.js +32 -3
- package/dist/ios/signing.js.map +1 -1
- package/dist/issues/classify.js +28 -20
- package/dist/issues/classify.js.map +1 -1
- package/dist/issues/fingerprint.js +35 -9
- package/dist/issues/fingerprint.js.map +1 -1
- package/dist/issues/index.js +93 -46
- package/dist/issues/index.js.map +1 -1
- package/dist/issues/metrics.js +6 -5
- 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 +12 -8
- package/dist/issues/report.js.map +1 -1
- package/dist/issues/reportBridge.js +40 -9
- 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 +84 -8
- 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 +53 -12
- 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 +12 -3
- package/dist/lib/image.js.map +1 -1
- package/dist/lib/lockfile.js +399 -0
- package/dist/lib/lockfile.js.map +1 -0
- package/dist/lib/needsInput.js +9 -6
- package/dist/lib/needsInput.js.map +1 -1
- package/dist/lib/png.js +4 -3
- 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 +94 -10
- package/dist/lib/result.js.map +1 -1
- package/dist/lib/schemaHash.js +7 -5
- package/dist/lib/schemaHash.js.map +1 -1
- package/dist/lib/sensitive.js +6 -3
- 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 +78 -17
- 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 +321 -25
- package/dist/lib/wda.js.map +1 -1
- package/dist/lib/wdaConfig.js +12 -5
- package/dist/lib/wdaConfig.js.map +1 -1
- package/dist/lib/wdaTune.js +52 -23
- package/dist/lib/wdaTune.js.map +1 -1
- package/dist/mobileAudit/checks.js +150 -28
- 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 +121 -20
- package/dist/mobileAudit/profiles.js.map +1 -1
- package/dist/mobileAudit/results.js +9 -4
- package/dist/mobileAudit/results.js.map +1 -1
- package/dist/mobileAudit/runner.js +214 -35
- 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 +1085 -145
- package/dist/oracle/failures.js.map +1 -1
- package/dist/oracle/health.js +148 -26
- package/dist/oracle/health.js.map +1 -1
- package/dist/oracle/locator.js +148 -19
- 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 +7 -13
- package/dist/orchestration/goal.js.map +1 -1
- package/dist/orchestration/testThis/execute.js +167 -0
- package/dist/orchestration/testThis/execute.js.map +1 -0
- package/dist/orchestration/testThis/pipeline.js +413 -0
- package/dist/orchestration/testThis/pipeline.js.map +1 -0
- package/dist/orchestration/testThis/plan.js +519 -0
- package/dist/orchestration/testThis/plan.js.map +1 -0
- package/dist/orchestration/testThis/sessionIntent.js +72 -0
- package/dist/orchestration/testThis/sessionIntent.js.map +1 -0
- package/dist/orchestration/testThis/terminal.js +136 -0
- package/dist/orchestration/testThis/terminal.js.map +1 -0
- package/dist/orchestration/testThis/types.js +12 -0
- package/dist/orchestration/testThis/types.js.map +1 -0
- package/dist/plan/plan.js +9 -9
- package/dist/plan/plan.js.map +1 -1
- package/dist/prompts/index.js +24 -24
- package/dist/prompts/index.js.map +1 -1
- package/dist/report/coverage.js +0 -1
- package/dist/report/coverage.js.map +1 -1
- package/dist/report/evidence.js.map +1 -1
- package/dist/report/export.js +449 -72
- 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/flake.js +13 -14
- package/dist/report/flake.js.map +1 -1
- package/dist/report/history.js +72 -38
- package/dist/report/history.js.map +1 -1
- package/dist/report/policy.js +18 -7
- package/dist/report/policy.js.map +1 -1
- package/dist/report/qaLevel.js +10 -16
- package/dist/report/qaLevel.js.map +1 -1
- package/dist/report/readiness.js +42 -12
- package/dist/report/readiness.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/timing.js +23 -23
- package/dist/report/timing.js.map +1 -1
- package/dist/report/toolHealth.js +124 -0
- package/dist/report/toolHealth.js.map +1 -0
- package/dist/server.js +501 -69
- 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 +10 -4
- package/dist/services/build.js.map +1 -1
- package/dist/services/flowGenerate.js +75 -0
- package/dist/services/flowGenerate.js.map +1 -0
- package/dist/services/preflight.js +40 -8
- package/dist/services/preflight.js.map +1 -1
- package/dist/services/prepareAndroid.js +92 -18
- package/dist/services/prepareAndroid.js.map +1 -1
- package/dist/services/prepareIos.js +94 -17
- package/dist/services/prepareIos.js.map +1 -1
- package/dist/services/report.js +271 -80
- package/dist/services/report.js.map +1 -1
- package/dist/services/smoke.js +68 -22
- package/dist/services/smoke.js.map +1 -1
- package/dist/services/suiteGenerate.js +43 -6
- package/dist/services/suiteGenerate.js.map +1 -1
- package/dist/services/testSuiteKnowledge.js +78 -33
- package/dist/services/testSuiteKnowledge.js.map +1 -1
- package/dist/session/attach.js +284 -4
- package/dist/session/attach.js.map +1 -1
- package/dist/session/processRegistry.js +433 -0
- package/dist/session/processRegistry.js.map +1 -0
- package/dist/session/progress.js +1 -1
- package/dist/session/progress.js.map +1 -1
- package/dist/session/retention.js +201 -0
- package/dist/session/retention.js.map +1 -0
- package/dist/session/store.js +459 -59
- package/dist/session/store.js.map +1 -1
- package/dist/snapshot/overlays.js +60 -10
- package/dist/snapshot/overlays.js.map +1 -1
- package/dist/snapshot/parse.js +30 -12
- package/dist/snapshot/parse.js.map +1 -1
- package/dist/snapshot/present.js +36 -6
- 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 +82 -11
- package/dist/state/profile.js.map +1 -1
- package/dist/suite/compile.js +15 -6
- package/dist/suite/compile.js.map +1 -1
- package/dist/suite/lint.js +22 -4
- package/dist/suite/lint.js.map +1 -1
- package/dist/suite/pom.js +75 -44
- 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 +15 -11
- package/dist/suite/testcase.js.map +1 -1
- package/dist/testSuite/exporter.js +7 -3
- package/dist/testSuite/exporter.js.map +1 -1
- package/dist/testSuite/generator.js +43 -14
- 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 +13 -7
- package/dist/testSuite/merge.js.map +1 -1
- package/dist/testSuite/schema.js +21 -16
- package/dist/testSuite/schema.js.map +1 -1
- package/dist/testSuite/store.js +12 -4
- 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 +868 -174
- package/dist/tools/act.js.map +1 -1
- package/dist/tools/agent.js +521 -112
- package/dist/tools/agent.js.map +1 -1
- package/dist/tools/appControl.js +82 -19
- package/dist/tools/appControl.js.map +1 -1
- package/dist/tools/appMap.js +393 -189
- package/dist/tools/appMap.js.map +1 -1
- package/dist/tools/build.js +92 -51
- package/dist/tools/build.js.map +1 -1
- package/dist/tools/bundletool.js +141 -40
- package/dist/tools/bundletool.js.map +1 -1
- package/dist/tools/clearOverlay.js +98 -18
- package/dist/tools/clearOverlay.js.map +1 -1
- package/dist/tools/device.js +108 -19
- package/dist/tools/device.js.map +1 -1
- package/dist/tools/doctor.js +98 -35
- package/dist/tools/doctor.js.map +1 -1
- package/dist/tools/explore.js +110 -42
- package/dist/tools/explore.js.map +1 -1
- package/dist/tools/featureTesting.js +152 -130
- package/dist/tools/featureTesting.js.map +1 -1
- package/dist/tools/firstRun.js +94 -66
- package/dist/tools/firstRun.js.map +1 -1
- package/dist/tools/flow.js +347 -132
- package/dist/tools/flow.js.map +1 -1
- package/dist/tools/flowRepair.js +42 -10
- package/dist/tools/flowRepair.js.map +1 -1
- package/dist/tools/generate.js +199 -0
- package/dist/tools/generate.js.map +1 -0
- package/dist/tools/getArtifact.js +37 -8
- package/dist/tools/getArtifact.js.map +1 -1
- package/dist/tools/health.js +17 -12
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/ios.js +148 -194
- package/dist/tools/ios.js.map +1 -1
- package/dist/tools/issues.js +256 -171
- package/dist/tools/issues.js.map +1 -1
- package/dist/tools/jobs.js +55 -11
- package/dist/tools/jobs.js.map +1 -1
- package/dist/tools/metro.js +84 -38
- package/dist/tools/metro.js.map +1 -1
- package/dist/tools/mobileAudit.js +83 -26
- package/dist/tools/mobileAudit.js.map +1 -1
- package/dist/tools/network.js +36 -14
- package/dist/tools/network.js.map +1 -1
- package/dist/tools/note.js +31 -17
- package/dist/tools/note.js.map +1 -1
- package/dist/tools/prepareIosTarget.js +31 -14
- package/dist/tools/prepareIosTarget.js.map +1 -1
- package/dist/tools/prepareTarget.js +193 -47
- package/dist/tools/prepareTarget.js.map +1 -1
- package/dist/tools/report.js +16 -8
- package/dist/tools/report.js.map +1 -1
- package/dist/tools/resolveArtifact.js +12 -12
- package/dist/tools/resolveArtifact.js.map +1 -1
- package/dist/tools/resolveTarget.js +81 -18
- package/dist/tools/resolveTarget.js.map +1 -1
- package/dist/tools/screenRecord.js +85 -20
- package/dist/tools/screenRecord.js.map +1 -1
- package/dist/tools/screenshot.js +42 -18
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/smoke.js +29 -12
- package/dist/tools/smoke.js.map +1 -1
- package/dist/tools/snapshot.js +157 -107
- package/dist/tools/snapshot.js.map +1 -1
- package/dist/tools/startSession.js +117 -84
- package/dist/tools/startSession.js.map +1 -1
- package/dist/tools/suite.js +391 -310
- package/dist/tools/suite.js.map +1 -1
- package/dist/tools/testSuite.js +112 -36
- package/dist/tools/testSuite.js.map +1 -1
- package/dist/tools/testThis.js +35 -802
- package/dist/tools/testThis.js.map +1 -1
- package/dist/tools/visual.js +698 -116
- package/dist/tools/visual.js.map +1 -1
- package/dist/tools/wait.js +9 -23
- package/dist/tools/wait.js.map +1 -1
- package/dist/tools/wda.js +312 -54
- package/dist/tools/wda.js.map +1 -1
- package/dist/version.js +37 -64
- package/dist/version.js.map +1 -1
- package/dist/visual/ocr.js +65 -8
- package/dist/visual/ocr.js.map +1 -1
- package/dist/visual/provider.js +87 -15
- 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 +210 -104
- package/docs/physical-devices.md +84 -0
- package/docs/tools.md +808 -217
- package/package.json +16 -6
- package/dist/appMap/diff.js +0 -60
- package/dist/appMap/diff.js.map +0 -1
- package/dist/automation/maestroIr.js +0 -282
- package/dist/automation/maestroIr.js.map +0 -1
- package/dist/interop/maestro.js +0 -151
- package/dist/interop/maestro.js.map +0 -1
- package/dist/tools/assertVisual.js +0 -74
- package/dist/tools/assertVisual.js.map +0 -1
- package/dist/tools/automationGenerate.js +0 -394
- package/dist/tools/automationGenerate.js.map +0 -1
- package/dist/tools/capabilities.js +0 -209
- 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 +0 -55
- package/dist/tools/flowGenerate.js.map +0 -1
- package/dist/tools/history.js +0 -56
- package/dist/tools/history.js.map +0 -1
- package/dist/tools/idling.js +0 -104
- package/dist/tools/idling.js.map +0 -1
- package/dist/tools/inputCapabilities.js +0 -30
- package/dist/tools/inputCapabilities.js.map +0 -1
- package/dist/tools/locator.js +0 -57
- package/dist/tools/locator.js.map +0 -1
- package/dist/tools/maestro.js +0 -78
- package/dist/tools/maestro.js.map +0 -1
- package/dist/tools/permissions.js +0 -149
- package/dist/tools/permissions.js.map +0 -1
- package/dist/tools/plan.js +0 -49
- package/dist/tools/plan.js.map +0 -1
- package/dist/tools/screenInfo.js +0 -70
- package/dist/tools/screenInfo.js.map +0 -1
- package/dist/tools/seed.js +0 -101
- package/dist/tools/seed.js.map +0 -1
- package/dist/tools/state.js +0 -170
- package/dist/tools/state.js.map +0 -1
- package/dist/tools/visualText.js +0 -61
- 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';
|
|
@@ -15,24 +15,17 @@ import { registerCheckHealth } from './tools/health.js';
|
|
|
15
15
|
import { registerNetwork, restoreAllNetwork } from './tools/network.js';
|
|
16
16
|
import { registerScreenRecord, stopAllRecordings } from './tools/screenRecord.js';
|
|
17
17
|
import { registerDevice } from './tools/device.js';
|
|
18
|
-
import { registerPermissions } from './tools/permissions.js';
|
|
19
18
|
import { registerMetro, stopAllMetro } from './tools/metro.js';
|
|
20
19
|
import { registerAppControl } from './tools/appControl.js';
|
|
21
|
-
import { registerScreenInfo } from './tools/screenInfo.js';
|
|
22
|
-
import { registerVisual } from './tools/visual.js';
|
|
23
|
-
import { registerVisualText } from './tools/visualText.js';
|
|
24
|
-
import { registerSeed } from './tools/seed.js';
|
|
25
|
-
import { registerState } from './tools/state.js';
|
|
26
|
-
import { registerHistory } from './tools/history.js';
|
|
27
20
|
import { registerIos } from './tools/ios.js';
|
|
28
21
|
import { registerWda } from './tools/wda.js';
|
|
29
22
|
import { registerClearOverlay } from './tools/clearOverlay.js';
|
|
30
23
|
import { registerJobs } from './tools/jobs.js';
|
|
31
24
|
import { registerGetArtifact } from './tools/getArtifact.js';
|
|
32
25
|
import { registerNote } from './tools/note.js';
|
|
33
|
-
import {
|
|
26
|
+
import { registerVisual } from './tools/visual.js';
|
|
34
27
|
import { registerFlow } from './tools/flow.js';
|
|
35
|
-
import {
|
|
28
|
+
import { registerGenerate } from './tools/generate.js';
|
|
36
29
|
import { registerSmoke } from './tools/smoke.js';
|
|
37
30
|
import { registerReport } from './tools/report.js';
|
|
38
31
|
import { registerTestThis } from './tools/testThis.js';
|
|
@@ -40,17 +33,13 @@ import { registerPrepareIosTarget } from './tools/prepareIosTarget.js';
|
|
|
40
33
|
import { registerSuite } from './tools/suite.js';
|
|
41
34
|
import { registerExplore } from './tools/explore.js';
|
|
42
35
|
import { registerFirstRun } from './tools/firstRun.js';
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
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';
|
|
46
39
|
import { registerIssues } from './tools/issues.js';
|
|
47
40
|
import { registerTestSuite } from './tools/testSuite.js';
|
|
48
41
|
import { registerFlowRepair } from './tools/flowRepair.js';
|
|
49
|
-
import { registerLocator } from './tools/locator.js';
|
|
50
42
|
import { registerWait } from './tools/wait.js';
|
|
51
|
-
import { registerIdling } from './tools/idling.js';
|
|
52
|
-
import { registerInputCapabilities } from './tools/inputCapabilities.js';
|
|
53
|
-
import { registerMaestro } from './tools/maestro.js';
|
|
54
43
|
import { registerMobileAudit } from './tools/mobileAudit.js';
|
|
55
44
|
import { registerFeatureTesting } from './tools/featureTesting.js';
|
|
56
45
|
import { registerResolveArtifact } from './tools/resolveArtifact.js';
|
|
@@ -59,57 +48,445 @@ import { registerBuild } from './tools/build.js';
|
|
|
59
48
|
import { registerBundletool } from './tools/bundletool.js';
|
|
60
49
|
import { registerPrompts } from './prompts/index.js';
|
|
61
50
|
import { log } from './lib/logger.js';
|
|
62
|
-
import { runWithResponseMode } from './lib/result.js';
|
|
51
|
+
import { qaError, runWithResponseMode } from './lib/result.js';
|
|
52
|
+
import { recordToolErrorFromResult } from './report/toolHealth.js';
|
|
63
53
|
import { computeSchemaHash, describeZodField, setSchemaHash } from './lib/schemaHash.js';
|
|
64
|
-
import { SWIPIUM_VERSION, TOOL_COUNT, 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';
|
|
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;
|
|
64
|
+
/**
|
|
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
|
+
}
|
|
65
226
|
/**
|
|
66
|
-
* Wrap every tool handler so it runs inside the calling session's response mode
|
|
67
|
-
*
|
|
227
|
+
* Wrap every tool handler so it runs inside the calling session's response mode.
|
|
228
|
+
* Resolved once, centrally. Individual tools stay mode-agnostic.
|
|
68
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.
|
|
69
232
|
*/
|
|
70
|
-
function installResponseModeWrapper(server, sessions, surface) {
|
|
233
|
+
function installResponseModeWrapper(server, sessions, surface, attempted, paramNames) {
|
|
71
234
|
const orig = server.registerTool.bind(server);
|
|
72
235
|
const valid = (m) => m === 'compact' || m === 'normal' || m === 'verbose';
|
|
73
236
|
server.registerTool = (name, config, handler) => {
|
|
237
|
+
attempted.add(name);
|
|
74
238
|
if (!TOOL_NAME_SET.has(name))
|
|
75
|
-
return undefined;
|
|
239
|
+
return undefined; // assertToolSurface() makes this drop loud at startup
|
|
76
240
|
// Capture the tool surface (name + description + per-field type descriptors) for the schema hash
|
|
77
241
|
// (3.3 A/§5). Encoding each field's zod shape catches nested enum/type/optionality changes.
|
|
78
242
|
const cfg = config;
|
|
79
243
|
const inputKeys = Object.entries(cfg?.inputSchema ?? {}).map(([k, v]) => `${k}:${describeZodField(v)}`);
|
|
80
244
|
surface.push({ name, description: cfg?.description ?? '', inputKeys });
|
|
81
|
-
|
|
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) => {
|
|
82
249
|
const first = a[0];
|
|
83
250
|
// Prefer the existing session's mode; fall back to a directly-passed responseMode so the
|
|
84
251
|
// session-CREATING call (qa_start_session, no sessionId yet) also honors compact.
|
|
85
252
|
const fromSession = first?.sessionId ? sessions.get(first.sessionId)?.responseMode : undefined;
|
|
86
|
-
const mode = fromSession ?? (valid(first?.responseMode) ? first.responseMode : 'normal');
|
|
87
|
-
|
|
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;
|
|
88
270
|
});
|
|
89
271
|
};
|
|
90
272
|
}
|
|
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
|
|
374
|
+
* allowlisted in TOOL_NAMES, and every TOOL_NAMES entry must actually get registered.
|
|
375
|
+
* Without this, a tool missing from the allowlist is silently discarded by the wrapper
|
|
376
|
+
* above, and a stale TOOL_NAMES entry silently over-reports the surface. Fail LOUDLY. */
|
|
377
|
+
function assertToolSurface(attempted) {
|
|
378
|
+
const missing = TOOL_NAMES.filter((n) => !attempted.has(n));
|
|
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(', ')}`);
|
|
385
|
+
if (missing.length === 0 && extra.length === 0 && attempted.size === TOOL_NAMES.length)
|
|
386
|
+
return;
|
|
387
|
+
throw new Error(`Tool surface mismatch: ${attempted.size} tools registered vs ${TOOL_NAMES.length} in TOOL_NAMES (src/version.ts).` +
|
|
388
|
+
(missing.length ? `\n Missing (allowlisted but never registered): ${missing.join(', ')}` : '') +
|
|
389
|
+
(extra.length ? `\n Extra (registered but not in TOOL_NAMES, so they would be silently dropped): ${extra.join(', ')}` : '') +
|
|
390
|
+
'\nFix: add/remove the tool in TOOL_NAMES and CAPABILITY_GROUPS, or register it in createServer().');
|
|
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
|
+
}
|
|
91
466
|
/** Construct the server and register all tools + the artifact resource. Exported for tests. */
|
|
92
467
|
export function createServer() {
|
|
93
|
-
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));
|
|
94
474
|
const sessions = new SessionStore();
|
|
95
475
|
const surface = [];
|
|
96
|
-
|
|
476
|
+
const attemptedToolNames = new Set();
|
|
477
|
+
const paramNames = new Map();
|
|
478
|
+
installResponseModeWrapper(server, sessions, surface, attemptedToolNames, paramNames);
|
|
97
479
|
// Setup / context
|
|
98
480
|
registerDoctor(server);
|
|
99
|
-
registerCapabilities(server, sessions);
|
|
100
481
|
registerStartSession(server, sessions);
|
|
101
|
-
registerDetectContext(server, sessions);
|
|
102
|
-
registerPlan(server, sessions);
|
|
103
482
|
registerPrepareTarget(server, sessions);
|
|
104
483
|
registerIos(server, sessions);
|
|
105
484
|
registerWda(server, sessions);
|
|
106
485
|
// Device / app environment parity (Phase 5)
|
|
107
486
|
registerDevice(server, sessions);
|
|
108
|
-
registerPermissions(server, sessions);
|
|
109
487
|
registerNetwork(server, sessions);
|
|
110
488
|
registerMetro(server, sessions);
|
|
111
489
|
registerAppControl(server, sessions);
|
|
112
|
-
registerScreenInfo(server, sessions);
|
|
113
490
|
registerScreenRecord(server, sessions);
|
|
114
491
|
// Observation / action / oracle
|
|
115
492
|
registerScreenshot(server, sessions);
|
|
@@ -117,37 +494,27 @@ export function createServer() {
|
|
|
117
494
|
registerAct(server, sessions);
|
|
118
495
|
registerClearOverlay(server, sessions);
|
|
119
496
|
registerCheckHealth(server, sessions);
|
|
120
|
-
// Visual intelligence (Phase 8, local-first)
|
|
121
|
-
registerVisual(server, sessions);
|
|
122
|
-
registerVisualText(server, sessions);
|
|
123
|
-
// Seeded state (Phase 9)
|
|
124
|
-
registerSeed(server, sessions);
|
|
125
|
-
registerState(server, sessions);
|
|
126
497
|
// Jobs / artifacts / reporting (M6)
|
|
127
498
|
registerJobs(server, sessions);
|
|
128
499
|
registerGetArtifact(server, sessions);
|
|
129
500
|
registerNote(server, sessions);
|
|
130
|
-
|
|
501
|
+
registerVisual(server, sessions);
|
|
131
502
|
registerFlow(server, sessions);
|
|
132
|
-
|
|
503
|
+
registerGenerate(server, sessions);
|
|
133
504
|
registerSmoke(server, sessions);
|
|
134
505
|
registerReport(server, sessions);
|
|
135
|
-
// Report 2.0: history + comparison (Phase 10)
|
|
136
|
-
registerHistory(server, sessions);
|
|
137
506
|
registerTestThis(server, sessions);
|
|
138
507
|
registerPrepareIosTarget(server, sessions);
|
|
139
508
|
registerSuite(server, sessions);
|
|
140
509
|
registerExplore(server, sessions);
|
|
141
510
|
registerFirstRun(server, sessions);
|
|
142
|
-
registerAutomationGenerate(server, sessions);
|
|
143
511
|
registerAgentTools(server, sessions);
|
|
144
512
|
registerAppMap(server, sessions);
|
|
145
|
-
// Durable QA memory + repeatable assets
|
|
513
|
+
// Durable QA memory + repeatable assets
|
|
146
514
|
registerIssues(server, sessions);
|
|
147
515
|
registerMobileAudit(server, sessions);
|
|
148
516
|
registerTestSuite(server, sessions);
|
|
149
517
|
registerFlowRepair(server, sessions);
|
|
150
|
-
registerMaestro(server, sessions);
|
|
151
518
|
// Feature-focused testing + local build/artifact resolution (v4)
|
|
152
519
|
registerFeatureTesting(server, sessions);
|
|
153
520
|
registerResolveArtifact(server, sessions);
|
|
@@ -155,16 +522,37 @@ export function createServer() {
|
|
|
155
522
|
registerBuild(server, sessions);
|
|
156
523
|
registerBundletool(server, sessions);
|
|
157
524
|
// Agent-efficiency helpers (v3)
|
|
158
|
-
registerLocator(server, sessions);
|
|
159
525
|
registerWait(server, sessions);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
526
|
+
// Tool surface is now fully registered. Fail loudly on any allowlist mismatch, then
|
|
527
|
+
// freeze the surface's content fingerprint.
|
|
528
|
+
assertToolSurface(attemptedToolNames);
|
|
163
529
|
setSchemaHash(computeSchemaHash(surface));
|
|
164
|
-
|
|
530
|
+
installProtocolShims(server, paramNames);
|
|
531
|
+
// Reusable workflow templates (MCP prompts capability): thin orchestration of the tools above.
|
|
165
532
|
registerPrompts(server);
|
|
166
533
|
// Artifacts as MCP resources (clients that support them); qa_get_artifact is the fallback.
|
|
167
|
-
|
|
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) => {
|
|
168
556
|
const found = sessions.findArtifact(uri.href);
|
|
169
557
|
if (!found)
|
|
170
558
|
throw new Error(`Unknown artifact ${uri.href}`);
|
|
@@ -174,23 +562,35 @@ export function createServer() {
|
|
|
174
562
|
}
|
|
175
563
|
return { contents: [{ uri: uri.href, mimeType: rec.mime, text: readFileSync(rec.path, 'utf8') }] };
|
|
176
564
|
});
|
|
177
|
-
// App Knowledge Map as MCP resources
|
|
565
|
+
// App Knowledge Map as MCP resources: full map + per-feature / per-screen /
|
|
178
566
|
// test-suite sections, so large map data is read by URI instead of flooding a tool's text result.
|
|
179
|
-
|
|
180
|
-
|
|
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);
|
|
181
578
|
if (!root)
|
|
182
|
-
throw new Error(`Unknown project ${
|
|
183
|
-
const kind = vars.kind ? String(vars.kind) : undefined;
|
|
184
|
-
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 });
|
|
185
582
|
if (!res)
|
|
186
583
|
throw new Error(`No app-map resource for ${uri.href}`);
|
|
187
584
|
return { contents: [{ uri: uri.href, mimeType: res.mimeType, text: res.text }] };
|
|
188
585
|
});
|
|
189
586
|
// Bare full-map URI: swipium://project/{projectId}/app-map (no trailing section).
|
|
190
|
-
server.registerResource('qa-app-map-full', new ResourceTemplate('swipium://project/{projectId}/app-map', {
|
|
191
|
-
|
|
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);
|
|
192
592
|
if (!root)
|
|
193
|
-
throw new Error(`Unknown project ${
|
|
593
|
+
throw new Error(`Unknown project ${projectId} (build the map first)`);
|
|
194
594
|
const res = readAppMapResource(root, {});
|
|
195
595
|
if (!res)
|
|
196
596
|
throw new Error(`No app map for ${uri.href}`);
|
|
@@ -199,8 +599,21 @@ export function createServer() {
|
|
|
199
599
|
return { server, sessions };
|
|
200
600
|
}
|
|
201
601
|
export async function startServer() {
|
|
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.
|
|
606
|
+
try {
|
|
607
|
+
ensureAndroidToolsOnPath();
|
|
608
|
+
}
|
|
609
|
+
catch (e) {
|
|
610
|
+
log('warn', 'android sdk path setup failed', { err: String(e) });
|
|
611
|
+
}
|
|
202
612
|
const { server, sessions } = createServer();
|
|
203
613
|
const transport = new StdioServerTransport();
|
|
614
|
+
// Persistence is debounced (SessionStore.persist), so make sure a graceful exit never
|
|
615
|
+
// loses the trailing write. 'exit' handlers must be synchronous; flushAll is.
|
|
616
|
+
process.once('exit', () => sessions.flushAll());
|
|
204
617
|
// Best-effort: restore any network state Swipium changed, on shutdown / client disconnect
|
|
205
618
|
// (so a budget-stop or crash mid-offline doesn't leave the emulator offline). Idempotent.
|
|
206
619
|
let restoring = false;
|
|
@@ -214,25 +627,44 @@ export async function startServer() {
|
|
|
214
627
|
await restoreAllNetwork(sessions);
|
|
215
628
|
await stopAllRecordings(); // don't leave a device screen-recording after we exit
|
|
216
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.
|
|
217
632
|
log('info', 'shutdown: restore done');
|
|
218
633
|
}
|
|
219
634
|
catch (e) {
|
|
220
635
|
log('warn', 'shutdown: restore failed', { err: String(e) });
|
|
221
636
|
}
|
|
637
|
+
sessions.flushAll(); // write any debounced session state before exiting
|
|
222
638
|
process.exit(code);
|
|
223
639
|
};
|
|
224
|
-
process.once('SIGINT', () => void restoreThenExit(130,
|
|
225
|
-
process.once('SIGTERM', () => void restoreThenExit(143,
|
|
640
|
+
process.once('SIGINT', () => void restoreThenExit(130, 'SIGINT'));
|
|
641
|
+
process.once('SIGTERM', () => void restoreThenExit(143, 'SIGTERM'));
|
|
226
642
|
// Startup banner (P1.8): version + tool count on stderr so a stale build is obvious in logs.
|
|
227
643
|
log('info', 'swipium starting', { version: SWIPIUM_VERSION, tools: TOOL_COUNT });
|
|
228
644
|
await server.connect(transport);
|
|
229
|
-
// Chain AFTER connect
|
|
645
|
+
// Chain AFTER connect: server.connect() sets its own transport.onclose, so we must wrap
|
|
230
646
|
// it rather than assign before (which gets overwritten). stdin EOF = client disconnected.
|
|
231
647
|
const sdkOnClose = transport.onclose;
|
|
232
648
|
transport.onclose = () => {
|
|
233
649
|
sdkOnClose?.();
|
|
234
|
-
void restoreThenExit(0,
|
|
650
|
+
void restoreThenExit(0, 'transport-close');
|
|
235
651
|
};
|
|
236
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
|
+
}
|
|
237
669
|
}
|
|
238
670
|
//# sourceMappingURL=server.js.map
|