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/session/store.js
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
// Session + job + artifact store with DISK PERSISTENCE
|
|
1
|
+
// Session + job + artifact store with DISK PERSISTENCE.
|
|
2
2
|
// Live, non-serializable bits (driver, lastSnapshot, abort controllers) stay in memory;
|
|
3
|
-
// a serializable subset is written to <sessionDir>/state.json on
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// a serializable subset is written to <sessionDir>/state.json on mutation (debounced;
|
|
4
|
+
// synchronous for the mutation ledger + session creation + shutdown flush), and a small
|
|
5
|
+
// registry under ~/.swipium/registry.json (lock-guarded, lazily loaded on first access)
|
|
6
|
+
// lets a fresh server instance reload prior sessions so artifacts/reports/job-status survive
|
|
7
|
+
// a server restart. (A restart does NOT resurrect a running child process: such jobs are
|
|
8
|
+
// marked failed on reload, and orphaned Metro pids are verified via `ps` and reaped.)
|
|
7
9
|
import { createHash, randomUUID } from 'node:crypto';
|
|
8
|
-
import { mkdirSync, writeFileSync, readFileSync, existsSync } from 'node:fs';
|
|
9
|
-
import { join, resolve } from 'node:path';
|
|
10
|
+
import { chmodSync, mkdirSync, writeFileSync, readFileSync, existsSync } from 'node:fs';
|
|
11
|
+
import { extname, join, resolve, sep } from 'node:path';
|
|
10
12
|
import { homedir } from 'node:os';
|
|
11
13
|
import { DEFAULT_RESPONSE_MODE } from '../lib/result.js';
|
|
12
|
-
import { makeRedactor } from '../lib/redact.js';
|
|
14
|
+
import { makeRedactor, redactDeep, redactStructuredText, structuredKindOf } from '../lib/redact.js';
|
|
15
|
+
import { generatedOutputRedactor, inputBindings, inputPlaceholderFor, secretSafeActions, secretSafeNotes, swipiumVarName, } from '../suite/secretGuard.js';
|
|
16
|
+
import { withFileLock, writeFileAtomicSync } from '../lib/lockfile.js';
|
|
17
|
+
import { log } from '../lib/logger.js';
|
|
18
|
+
import { pidOwnedByLiveServer, reclaimPid } from './processRegistry.js';
|
|
19
|
+
import { fixtureMetadata, rehydrateFixtures } from '../fixtures/load.js';
|
|
20
|
+
import { scheduleStartupPrune } from './retention.js';
|
|
21
|
+
import { approvalMechanismFor } from '../consent/consent.js';
|
|
13
22
|
// Phase 2.2 P1.6: budget classes (minutes) keyed by workflow ambition.
|
|
14
23
|
export const BUDGET_PROFILES = {
|
|
15
24
|
guardrail: 8, // guardrail/setup validation
|
|
@@ -17,7 +26,7 @@ export const BUDGET_PROFILES = {
|
|
|
17
26
|
full_smoke: 15, // full authenticated smoke
|
|
18
27
|
install_smoke: 20, // install/boot/rebuild involved
|
|
19
28
|
};
|
|
20
|
-
// Security model
|
|
29
|
+
// Security model: raw secret values (generated passwords/OTPs/tokens) must never touch
|
|
21
30
|
// disk. In memory we keep the raw value (for same-session reuse via inputValues), but the
|
|
22
31
|
// serialized form sent to state.json redacts secret values while preserving all reproducibility
|
|
23
32
|
// metadata (varName/field/generator/secret/artifactUri/timestamps). Non-secret generated values
|
|
@@ -25,6 +34,36 @@ export const BUDGET_PROFILES = {
|
|
|
25
34
|
export function serializeGeneratedValues(records) {
|
|
26
35
|
return records.map((r) => (r.secret ? { ...r, value: '<redacted>' } : r));
|
|
27
36
|
}
|
|
37
|
+
/** state.json form of the session's free-text records: raw registered secret values never touch
|
|
38
|
+
* disk. Recorded actions go through the same rewrite the generators use (a secret literal typed into
|
|
39
|
+
* a non-secure field becomes a secret step with no text); note/finding prose is redacted. URIs,
|
|
40
|
+
* paths and numbers are left alone. */
|
|
41
|
+
export function serializeSecretSafe(s) {
|
|
42
|
+
const toolErrors = s.toolErrors ?? [];
|
|
43
|
+
const jobs = [...(s.jobs?.values() ?? [])];
|
|
44
|
+
const envChanges = s.envChanges ?? [];
|
|
45
|
+
const mutations = s.mutations ?? [];
|
|
46
|
+
// Fixture values and seed specs are live config, never persisted (not even redacted): a
|
|
47
|
+
// rehydrated session re-reads them from .swipium/fixtures.json (see fixtures/load.ts).
|
|
48
|
+
const fixtures = fixtureMetadata(s.fixtures ?? []);
|
|
49
|
+
if (!s.secrets.size)
|
|
50
|
+
return { recordedActions: s.recordedActions, notes: s.notes, findings: s.findings, toolErrors, jobs, envChanges, mutations, fixtures };
|
|
51
|
+
const redact = makeRedactor(s.secrets);
|
|
52
|
+
const r = (v) => (v ? (redact(v) ?? v) : v);
|
|
53
|
+
// Structured records (job results, mutation targets, fixtures): strong secrets scrubbed anywhere,
|
|
54
|
+
// weak (dictionary-like) ones only as a whole value, so package ids / selectors stay intact.
|
|
55
|
+
const structured = generatedOutputRedactor(s.secrets);
|
|
56
|
+
return {
|
|
57
|
+
recordedActions: secretSafeActions(s.recordedActions, s.secrets).actions,
|
|
58
|
+
notes: secretSafeNotes(s.notes, s.secrets),
|
|
59
|
+
findings: s.findings.map((f) => ({ ...f, detail: r(f.detail) ?? f.detail, evidence: r(f.evidence) })),
|
|
60
|
+
toolErrors: toolErrors.map((t) => ({ ...t, message: r(t.message) ?? t.message })),
|
|
61
|
+
jobs: jobs.map((j) => ({ ...redactDeep(j, structured), error: r(j.error), progress: r(j.progress), resultText: r(j.resultText) })),
|
|
62
|
+
envChanges: envChanges.map((e) => r(e) ?? e),
|
|
63
|
+
mutations: mutations.map((m) => ({ ...redactDeep(m, structured), detail: r(m.detail) })),
|
|
64
|
+
fixtures: redactDeep(fixtures, structured),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
28
67
|
export const DEFAULT_BUDGET = {
|
|
29
68
|
maxMinutes: 8,
|
|
30
69
|
maxActions: 20,
|
|
@@ -40,8 +79,10 @@ export function isTextArtifactMime(mime) {
|
|
|
40
79
|
}
|
|
41
80
|
export function artifactDirectoryName(kind) {
|
|
42
81
|
switch (kind) {
|
|
43
|
-
case 'screenshot':
|
|
44
|
-
|
|
82
|
+
case 'screenshot':
|
|
83
|
+
return 'screenshots';
|
|
84
|
+
case 'recording':
|
|
85
|
+
return 'videos';
|
|
45
86
|
case 'logs':
|
|
46
87
|
case 'logcat':
|
|
47
88
|
case 'metro':
|
|
@@ -51,60 +92,247 @@ export function artifactDirectoryName(kind) {
|
|
|
51
92
|
return kind;
|
|
52
93
|
}
|
|
53
94
|
}
|
|
95
|
+
/** Filesystem-safe artifact file name (review B5): path separators, NUL/control chars and `..`
|
|
96
|
+
* runs are replaced, so a caller-supplied name (e.g. a visual baseline name) can never escape
|
|
97
|
+
* the session directory. Long names are truncated, keeping the extension. */
|
|
98
|
+
export function safeArtifactName(name) {
|
|
99
|
+
let n = String(name)
|
|
100
|
+
// eslint-disable-next-line no-control-regex
|
|
101
|
+
.replace(/[\\/\x00-\x1f]/g, '_')
|
|
102
|
+
.replace(/\.{2,}/g, '_')
|
|
103
|
+
.trim();
|
|
104
|
+
if (!n || n === '.')
|
|
105
|
+
n = 'artifact';
|
|
106
|
+
if (n.length > 180) {
|
|
107
|
+
const ext = extname(n).slice(0, 16);
|
|
108
|
+
n = n.slice(0, 180 - ext.length) + ext;
|
|
109
|
+
}
|
|
110
|
+
return n;
|
|
111
|
+
}
|
|
112
|
+
/** Percent-encode the characters that would stop a URI segment from matching the
|
|
113
|
+
* swipium:// resource templates (`/`, `,`, space, `%`, `?`, `#`, non-ASCII…). Readable ids such as
|
|
114
|
+
* `feature:login` stay as-is; pair with decodeUriSegment on the read side. */
|
|
115
|
+
export function encodeUriSegment(value) {
|
|
116
|
+
return value.replace(/[^A-Za-z0-9\-._~:@!$&'()*+;=]/gu, (c) => encodeURIComponent(c));
|
|
117
|
+
}
|
|
118
|
+
/** Inverse of encodeUriSegment; returns the input unchanged if it is not valid percent-encoding. */
|
|
119
|
+
export function decodeUriSegment(value) {
|
|
120
|
+
try {
|
|
121
|
+
return decodeURIComponent(value);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Canonical form of a swipium:// URI for equality: every path segment decoded. */
|
|
128
|
+
function canonicalUri(uri) {
|
|
129
|
+
return uri.split('/').map(decodeUriSegment).join('/');
|
|
130
|
+
}
|
|
131
|
+
/** True when `child` is `parent` or lies inside it (both resolved). */
|
|
132
|
+
export function isWithinRoot(child, parent) {
|
|
133
|
+
const c = resolve(child);
|
|
134
|
+
const p = resolve(parent);
|
|
135
|
+
return c === p || c.startsWith(p.endsWith(sep) ? p : p + sep);
|
|
136
|
+
}
|
|
137
|
+
/** Owner-only permissions for ~/.swipium/runs session data (dirs 0700, files 0600). POSIX only;
|
|
138
|
+
* best-effort (a chmod failure never breaks a write). */
|
|
139
|
+
export const PRIVATE_DIR_MODE = 0o700;
|
|
140
|
+
export const PRIVATE_FILE_MODE = 0o600;
|
|
141
|
+
function restrictFile(path) {
|
|
142
|
+
if (process.platform === 'win32')
|
|
143
|
+
return;
|
|
144
|
+
try {
|
|
145
|
+
chmodSync(path, PRIVATE_FILE_MODE);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
/* best-effort */
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function mkdirPrivate(dir) {
|
|
152
|
+
mkdirSync(dir, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
153
|
+
if (process.platform === 'win32')
|
|
154
|
+
return;
|
|
155
|
+
try {
|
|
156
|
+
chmodSync(dir, PRIVATE_DIR_MODE); // mode above only applies to NEWLY created dirs (and umask)
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
/* best-effort */
|
|
160
|
+
}
|
|
161
|
+
}
|
|
54
162
|
function defaultSessionDir(root, id) {
|
|
55
163
|
const projectHash = createHash('sha256').update(resolve(root)).digest('hex').slice(0, 16);
|
|
56
164
|
return join(REGISTRY_DIR, 'runs', projectHash, id);
|
|
57
165
|
}
|
|
166
|
+
/** Trailing debounce for state.json writes (P2 write amplification): every mutator used to
|
|
167
|
+
* rewrite the full file synchronously. Correctness-critical writes (session creation, the
|
|
168
|
+
* mutation ledger, shutdown) still flush synchronously via persistNow()/flushAll(). */
|
|
169
|
+
const PERSIST_DEBOUNCE_MS = 150;
|
|
58
170
|
export class SessionStore {
|
|
59
171
|
sessions = new Map();
|
|
60
|
-
|
|
172
|
+
// Lazy prior-session loading (P2 startup cost): the registry of up to 200 prior sessions is
|
|
173
|
+
// read on first access (get/list/find/create), not in the constructor.
|
|
174
|
+
registryLoaded = false;
|
|
175
|
+
dirty = new Set();
|
|
176
|
+
flushTimer;
|
|
177
|
+
// Sessions created or looked up by id in THIS process (see activeRoots).
|
|
178
|
+
activeIds = new Set();
|
|
179
|
+
ensureRegistryLoaded() {
|
|
180
|
+
if (this.registryLoaded)
|
|
181
|
+
return;
|
|
182
|
+
this.registryLoaded = true;
|
|
61
183
|
this.loadRegistry();
|
|
184
|
+
// Retention (session/retention.ts): one non-blocking background prune of old ~/.swipium/runs
|
|
185
|
+
// session dirs per process, never a registered or live session. SWIPIUM_RETENTION_DAYS=off disables.
|
|
186
|
+
scheduleStartupPrune(() => [...this.sessions.values()].map((x) => x.dir));
|
|
62
187
|
}
|
|
63
188
|
create(root, budget, opts) {
|
|
189
|
+
this.ensureRegistryLoaded();
|
|
64
190
|
const id = randomUUID().slice(0, 8);
|
|
65
191
|
const dir = opts?.sessionDir ?? defaultSessionDir(root, id);
|
|
66
|
-
|
|
192
|
+
mkdirPrivate(dir);
|
|
67
193
|
const now = Date.now();
|
|
68
194
|
const s = {
|
|
69
|
-
id,
|
|
195
|
+
id,
|
|
196
|
+
root,
|
|
197
|
+
dir,
|
|
198
|
+
createdAt: now,
|
|
199
|
+
screenshotCount: 0,
|
|
70
200
|
mode: 'structured',
|
|
71
201
|
responseMode: opts?.responseMode ?? DEFAULT_RESPONSE_MODE,
|
|
72
202
|
sensitive: opts?.sensitive ?? false,
|
|
73
203
|
budget: { ...DEFAULT_BUDGET, ...(budget ?? {}) },
|
|
74
204
|
counters: { actions: 0, screenshots: 0, snapshotFailures: 0, noChangeActions: 0 },
|
|
75
|
-
envChanges: [],
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
205
|
+
envChanges: [],
|
|
206
|
+
workarounds: [],
|
|
207
|
+
jobs: new Map(),
|
|
208
|
+
artifacts: [],
|
|
209
|
+
findings: [],
|
|
210
|
+
notes: [],
|
|
211
|
+
mutations: [],
|
|
212
|
+
recordedActions: [],
|
|
213
|
+
toolErrors: [],
|
|
214
|
+
fixtures: opts?.fixtures ?? [],
|
|
215
|
+
auth: {},
|
|
216
|
+
milestones: { session_start: now },
|
|
217
|
+
budgetProfile: opts?.budgetProfile,
|
|
218
|
+
secrets: new Set(),
|
|
219
|
+
inputs: [],
|
|
220
|
+
generatedValues: [],
|
|
221
|
+
inputValues: new Map(),
|
|
222
|
+
aborts: new Map(),
|
|
79
223
|
};
|
|
80
224
|
this.sessions.set(id, s);
|
|
225
|
+
this.activeIds.add(id);
|
|
81
226
|
this.appendRegistry(id, dir);
|
|
82
|
-
this.
|
|
227
|
+
this.persistNow(s); // synchronous: a concurrent instance / crash must see the new session
|
|
83
228
|
return s;
|
|
84
229
|
}
|
|
85
230
|
get(id) {
|
|
86
|
-
|
|
231
|
+
this.ensureRegistryLoaded();
|
|
232
|
+
const s = this.sessions.get(id);
|
|
233
|
+
if (s)
|
|
234
|
+
this.activeIds.add(id);
|
|
235
|
+
return s;
|
|
87
236
|
}
|
|
88
237
|
list() {
|
|
238
|
+
this.ensureRegistryLoaded();
|
|
89
239
|
return [...this.sessions.values()];
|
|
90
240
|
}
|
|
241
|
+
/** Schedule a state.json write (trailing debounce). Mutators call this on every change;
|
|
242
|
+
* the actual write happens at most once per PERSIST_DEBOUNCE_MS per burst. Use
|
|
243
|
+
* persistNow()/flushAll() where a synchronous write is required for correctness. */
|
|
91
244
|
persist(s) {
|
|
245
|
+
this.dirty.add(s);
|
|
246
|
+
if (this.flushTimer)
|
|
247
|
+
return;
|
|
248
|
+
this.flushTimer = setTimeout(() => this.flushDirty(), PERSIST_DEBOUNCE_MS);
|
|
249
|
+
this.flushTimer.unref?.(); // never keep the process alive just for a pending flush
|
|
250
|
+
}
|
|
251
|
+
/** Write this session's state.json immediately (and drop any pending debounce for it). */
|
|
252
|
+
persistNow(s) {
|
|
253
|
+
this.dirty.delete(s);
|
|
254
|
+
this.writeState(s);
|
|
255
|
+
}
|
|
256
|
+
/** Flush every dirty session synchronously. Called on shutdown / process exit so a graceful
|
|
257
|
+
* stop never loses debounced state. */
|
|
258
|
+
flushAll() {
|
|
259
|
+
if (this.flushTimer) {
|
|
260
|
+
clearTimeout(this.flushTimer);
|
|
261
|
+
this.flushTimer = undefined;
|
|
262
|
+
}
|
|
263
|
+
for (const s of [...this.dirty]) {
|
|
264
|
+
this.dirty.delete(s);
|
|
265
|
+
this.writeState(s);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
flushDirty() {
|
|
269
|
+
this.flushTimer = undefined;
|
|
270
|
+
for (const s of [...this.dirty]) {
|
|
271
|
+
this.dirty.delete(s);
|
|
272
|
+
this.writeState(s);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
writeState(s) {
|
|
92
276
|
try {
|
|
277
|
+
const safe = serializeSecretSafe(s); // registered secret values never reach state.json
|
|
278
|
+
// Keep the plain transport fields in sync with the live driver (duck-typed: no driver import),
|
|
279
|
+
// except while the live driver is a fallback for an unreachable WDA: the persisted 'wda'
|
|
280
|
+
// transport stays authoritative so a later restart retries it.
|
|
281
|
+
if (s.driver && !(s.transportFallback && s.driver.kind === 'simulator')) {
|
|
282
|
+
s.driverKind = s.driver.kind;
|
|
283
|
+
const baseUrl = s.driver.baseUrl;
|
|
284
|
+
if (s.driver.kind === 'wda' && typeof baseUrl === 'string')
|
|
285
|
+
s.wdaUrl = baseUrl;
|
|
286
|
+
}
|
|
93
287
|
const state = {
|
|
94
|
-
id: s.id,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
288
|
+
id: s.id,
|
|
289
|
+
root: s.root,
|
|
290
|
+
dir: s.dir,
|
|
291
|
+
createdAt: s.createdAt,
|
|
292
|
+
device: s.device,
|
|
293
|
+
driverKind: s.driverKind,
|
|
294
|
+
wdaUrl: s.wdaUrl,
|
|
295
|
+
appId: s.appId,
|
|
296
|
+
headless: s.headless,
|
|
297
|
+
metroPid: s.metroPid,
|
|
298
|
+
screenshotCount: s.screenshotCount,
|
|
299
|
+
network: s.network,
|
|
300
|
+
envChanges: safe.envChanges,
|
|
301
|
+
workarounds: s.workarounds,
|
|
302
|
+
chosenTarget: s.chosenTarget,
|
|
303
|
+
lastTestThisArgs: s.lastTestThisArgs && s.secrets.size ? redactDeep(s.lastTestThisArgs, makeRedactor(s.secrets)) : s.lastTestThisArgs,
|
|
304
|
+
mode: s.mode,
|
|
305
|
+
responseMode: s.responseMode,
|
|
306
|
+
sensitive: s.sensitive,
|
|
307
|
+
budget: s.budget,
|
|
308
|
+
counters: s.counters,
|
|
309
|
+
jobs: safe.jobs,
|
|
310
|
+
artifacts: s.artifacts,
|
|
311
|
+
findings: safe.findings,
|
|
312
|
+
notes: safe.notes,
|
|
313
|
+
mutations: safe.mutations,
|
|
314
|
+
recordedActions: safe.recordedActions,
|
|
315
|
+
toolErrors: safe.toolErrors,
|
|
316
|
+
fixtures: safe.fixtures,
|
|
317
|
+
auth: s.auth,
|
|
318
|
+
milestones: s.milestones,
|
|
319
|
+
budgetProfile: s.budgetProfile,
|
|
320
|
+
inputs: s.inputs, // METADATA only, never the values
|
|
101
321
|
generatedValues: serializeGeneratedValues(s.generatedValues), // secret raw values redacted before disk
|
|
102
322
|
exploration: s.exploration,
|
|
103
323
|
};
|
|
104
|
-
|
|
324
|
+
// Atomic write (tmp + rename) so a crash mid-write never leaves a truncated state.json.
|
|
325
|
+
const target = join(s.dir, 'state.json');
|
|
326
|
+
writeFileAtomicSync(target, JSON.stringify(state, null, 2));
|
|
327
|
+
restrictFile(target); // owner-only: state.json carries run history / paths
|
|
105
328
|
}
|
|
106
|
-
catch {
|
|
107
|
-
|
|
329
|
+
catch (e) {
|
|
330
|
+
// A silent failure here means session state (jobs/artifacts/findings) is lost on restart.
|
|
331
|
+
log('error', 'failed to persist session state.json; this session will not survive a server restart', {
|
|
332
|
+
sessionId: s.id,
|
|
333
|
+
dir: s.dir,
|
|
334
|
+
err: String(e),
|
|
335
|
+
});
|
|
108
336
|
}
|
|
109
337
|
}
|
|
110
338
|
// ---- jobs ----
|
|
@@ -123,6 +351,20 @@ export class SessionStore {
|
|
|
123
351
|
s.mode = mode;
|
|
124
352
|
this.persist(s);
|
|
125
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* A structured UI dump just succeeded on this session: visual-fallback is per-screen, never
|
|
356
|
+
* permanent. Switch back to 'structured' and restart the consecutive-failure count. Returns
|
|
357
|
+
* true when the session was in visual-fallback (the caller may tell the agent it recovered).
|
|
358
|
+
*/
|
|
359
|
+
noteStructuredDump(s) {
|
|
360
|
+
const recovered = s.mode === 'visual-fallback';
|
|
361
|
+
if (!recovered && s.counters.snapshotFailures === 0)
|
|
362
|
+
return false;
|
|
363
|
+
s.mode = 'structured';
|
|
364
|
+
s.counters.snapshotFailures = 0;
|
|
365
|
+
this.persist(s);
|
|
366
|
+
return recovered;
|
|
367
|
+
}
|
|
126
368
|
bump(s, key, by = 1) {
|
|
127
369
|
s.counters[key] += by;
|
|
128
370
|
this.persist(s);
|
|
@@ -139,14 +381,14 @@ export class SessionStore {
|
|
|
139
381
|
if (c.screenshots >= b.maxScreenshots)
|
|
140
382
|
return `screenshot budget reached (${c.screenshots}/${b.maxScreenshots})`;
|
|
141
383
|
if (c.noChangeActions >= b.maxNoChangeActions)
|
|
142
|
-
return `repeated no-change actions (${c.noChangeActions}/${b.maxNoChangeActions})
|
|
384
|
+
return `repeated no-change actions (${c.noChangeActions}/${b.maxNoChangeActions}): likely wrong coords / disabled element / auth wall`;
|
|
143
385
|
return null;
|
|
144
386
|
}
|
|
145
387
|
updateJob(s, job, patch) {
|
|
146
388
|
Object.assign(job, patch);
|
|
147
389
|
this.persist(s);
|
|
148
390
|
}
|
|
149
|
-
/** Apply a patch ONLY while the job is still running
|
|
391
|
+
/** Apply a patch ONLY while the job is still running, so a cancelled (or otherwise
|
|
150
392
|
* terminal) job is never overwritten back to done/failed by a racing worker. */
|
|
151
393
|
updateJobIfRunning(s, job, patch) {
|
|
152
394
|
const cur = s.jobs.get(job.jobId);
|
|
@@ -175,7 +417,7 @@ export class SessionStore {
|
|
|
175
417
|
s.envChanges.push(`${new Date().toISOString()} ${note}`);
|
|
176
418
|
this.persist(s);
|
|
177
419
|
}
|
|
178
|
-
/** Record a safe fallback Swipium chose
|
|
420
|
+
/** Record a safe fallback Swipium chose. De-duped. */
|
|
179
421
|
addWorkaround(s, note) {
|
|
180
422
|
if (!s.workarounds.includes(note)) {
|
|
181
423
|
s.workarounds.push(note);
|
|
@@ -187,15 +429,43 @@ export class SessionStore {
|
|
|
187
429
|
this.persist(s);
|
|
188
430
|
}
|
|
189
431
|
recordMutation(s, mutation) {
|
|
190
|
-
|
|
432
|
+
// Audit trail (THREAT_MODEL): stamp HOW a consented action was approved (elicitation vs
|
|
433
|
+
// client re-call assertion) from the consent module, so every call site gets it for free.
|
|
434
|
+
let consent = mutation.consent;
|
|
435
|
+
if (consent?.approved && consent.consentId && !consent.approvalMechanism) {
|
|
436
|
+
const mechanism = approvalMechanismFor(consent.consentId);
|
|
437
|
+
if (mechanism)
|
|
438
|
+
consent = { ...consent, approvalMechanism: mechanism };
|
|
439
|
+
}
|
|
440
|
+
const rec = { id: randomUUID().slice(0, 8), at: Date.now(), ...mutation, ...(consent ? { consent } : {}) };
|
|
191
441
|
s.mutations.push(rec);
|
|
192
442
|
if (s.mutations.length > 500)
|
|
193
443
|
s.mutations.splice(0, s.mutations.length - 500);
|
|
194
|
-
this.
|
|
444
|
+
this.persistNow(s); // the mutation ledger is an audit trail; never leave it debounce-only
|
|
195
445
|
return rec;
|
|
196
446
|
}
|
|
197
|
-
/**
|
|
447
|
+
/** Record a tool call that returned an error for this session (bounded to the last 200). */
|
|
448
|
+
recordToolError(s, rec) {
|
|
449
|
+
if (rec.failureCode === 'CANCELLED')
|
|
450
|
+
return; // cancelled work is not a tool error
|
|
451
|
+
const list = (s.toolErrors ??= []);
|
|
452
|
+
list.push({ at: rec.at ?? Date.now(), tool: rec.tool, failureCode: rec.failureCode, message: rec.message.slice(0, 500) });
|
|
453
|
+
if (list.length > 200)
|
|
454
|
+
list.splice(0, list.length - 200);
|
|
455
|
+
this.persist(s);
|
|
456
|
+
}
|
|
457
|
+
/** Append an action-IR step (bounded) for qa_generate target:"flow". */
|
|
198
458
|
addRecordedAction(s, ra) {
|
|
459
|
+
// Typed text equal to a stored session input (e.g. the email from qa_continue_from_blocker) is
|
|
460
|
+
// recorded as that input's ${VAR} placeholder, never the literal (generators do this too).
|
|
461
|
+
const bound = ra.action === 'type' ? inputPlaceholderFor(ra.text, inputBindings(s)) : undefined;
|
|
462
|
+
if (bound) {
|
|
463
|
+
ra = {
|
|
464
|
+
...ra,
|
|
465
|
+
text: `\${${swipiumVarName(bound.varName)}}`,
|
|
466
|
+
...(bound.secret ? { secret: true, exportability: 'needs-human-data' } : {}),
|
|
467
|
+
};
|
|
468
|
+
}
|
|
199
469
|
s.recordedActions.push(ra);
|
|
200
470
|
if (s.recordedActions.length > 300)
|
|
201
471
|
s.recordedActions.splice(0, s.recordedActions.length - 300);
|
|
@@ -221,7 +491,9 @@ export class SessionStore {
|
|
|
221
491
|
this.persist(s);
|
|
222
492
|
}
|
|
223
493
|
/** Store a user-provided input (hardening P0.5). The VALUE stays in-memory only; secrets are
|
|
224
|
-
* added to the redaction set. Persists metadata so reports can say it was provided.
|
|
494
|
+
* added to the redaction set. Persists metadata so reports can say it was provided.
|
|
495
|
+
* Secrets of 3+ characters are redacted (short ones as whole tokens); 1-2 character values are
|
|
496
|
+
* not matched and are reported as partial redaction (src/lib/redact.ts). */
|
|
225
497
|
setInput(s, varName, value, secret, source) {
|
|
226
498
|
s.inputValues.set(varName, value);
|
|
227
499
|
if (secret)
|
|
@@ -237,7 +509,7 @@ export class SessionStore {
|
|
|
237
509
|
}
|
|
238
510
|
this.persist(s);
|
|
239
511
|
}
|
|
240
|
-
/** Variable map (varName
|
|
512
|
+
/** Variable map (varName > value) for the flow runner. In-memory values only. */
|
|
241
513
|
inputVariables(s) {
|
|
242
514
|
return Object.fromEntries(s.inputValues);
|
|
243
515
|
}
|
|
@@ -245,33 +517,86 @@ export class SessionStore {
|
|
|
245
517
|
clearInputValues(s) {
|
|
246
518
|
s.inputValues.clear();
|
|
247
519
|
}
|
|
520
|
+
/** Remember the ORIGINAL qa_test_this arguments (goal, goalText, flags) of this session's run. */
|
|
521
|
+
setLastTestThisArgs(s, args) {
|
|
522
|
+
s.lastTestThisArgs = { ...args, at: args.at ?? Date.now() };
|
|
523
|
+
this.persist(s);
|
|
524
|
+
}
|
|
248
525
|
/** Record the latest guided-exploration result (Phase 3.3) for qa_report. */
|
|
249
526
|
setExploration(s, rec) {
|
|
250
527
|
s.exploration = rec;
|
|
251
528
|
this.persist(s);
|
|
252
529
|
}
|
|
253
530
|
saveArtifact(s, kind, name, data, mime, label) {
|
|
531
|
+
// B5: never trust kind/name as path components. Sanitize, then verify containment.
|
|
532
|
+
kind = safeArtifactName(kind);
|
|
533
|
+
name = safeArtifactName(name);
|
|
254
534
|
const sub = join(s.dir, artifactDirectoryName(kind));
|
|
255
|
-
mkdirSync(sub, { recursive: true });
|
|
256
535
|
const path = join(sub, name);
|
|
536
|
+
if (!isWithinRoot(path, s.dir) || resolve(path) === resolve(s.dir)) {
|
|
537
|
+
throw new Error(`Refusing to write artifact outside the session directory: ${kind}/${name}`);
|
|
538
|
+
}
|
|
539
|
+
mkdirPrivate(sub);
|
|
257
540
|
const redact = makeRedactor(s.secrets);
|
|
258
|
-
const
|
|
541
|
+
const isRedactableText = typeof data === 'string' && isTextArtifactMime(mime);
|
|
542
|
+
// JSON/XML artifacts are redacted structurally (string values / attribute values / text
|
|
543
|
+
// nodes only) so a short numeric secret (CVV "123") can never rewrite `{"actions":123}`.
|
|
544
|
+
const storedData = isRedactableText ? redactStructuredText(data, structuredKindOf(mime, name), redact) : data;
|
|
259
545
|
const storedLabel = label ? redact(label) : label;
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
546
|
+
const skipped = redact.skippedShortSecrets ?? 0;
|
|
547
|
+
writeFileSync(path, storedData, { mode: PRIVATE_FILE_MODE });
|
|
548
|
+
restrictFile(path); // an overwritten artifact keeps its old mode otherwise
|
|
549
|
+
const uri = `swipium://session/${encodeUriSegment(s.id)}/${encodeUriSegment(kind)}/${encodeUriSegment(name)}`;
|
|
550
|
+
// Binary artifacts (screenshots/recordings) cannot be redacted, so tag them explicitly so
|
|
551
|
+
// consumers (qa_get_artifact, reports) know pixels may show sensitive on-screen content.
|
|
552
|
+
s.artifacts.push({
|
|
553
|
+
uri,
|
|
554
|
+
path,
|
|
555
|
+
mime,
|
|
556
|
+
kind,
|
|
557
|
+
createdAt: Date.now(),
|
|
558
|
+
label: storedLabel,
|
|
559
|
+
redaction: !isRedactableText ? 'not-applied' : skipped > 0 ? 'partial' : 'applied',
|
|
560
|
+
...(isRedactableText && skipped > 0
|
|
561
|
+
? {
|
|
562
|
+
redactionNote: `${skipped} secret value(s) shorter than 3 characters were not redacted (matching them would corrupt ordinary text).`,
|
|
563
|
+
}
|
|
564
|
+
: {}),
|
|
565
|
+
});
|
|
263
566
|
this.persist(s);
|
|
264
567
|
return uri;
|
|
265
568
|
}
|
|
266
569
|
findArtifact(uri) {
|
|
570
|
+
this.ensureRegistryLoaded();
|
|
267
571
|
for (const s of this.sessions.values()) {
|
|
268
572
|
const rec = s.artifacts.find((a) => a.uri === uri);
|
|
269
573
|
if (rec)
|
|
270
574
|
return { session: s, rec };
|
|
271
575
|
}
|
|
576
|
+
// Encoding-insensitive fallback: a client (or URL normalisation) may percent-encode a URI
|
|
577
|
+
// that was stored raw (pre-1.6 records), or vice versa.
|
|
578
|
+
const want = canonicalUri(uri);
|
|
579
|
+
for (const s of this.sessions.values()) {
|
|
580
|
+
const rec = s.artifacts.find((a) => canonicalUri(a.uri) === want);
|
|
581
|
+
if (rec)
|
|
582
|
+
return { session: s, rec };
|
|
583
|
+
}
|
|
272
584
|
return undefined;
|
|
273
585
|
}
|
|
274
|
-
|
|
586
|
+
/** Project roots this server process is actively working on: roots of sessions created here
|
|
587
|
+
* or touched by a tool call (get) since start, NOT every prior session reloaded from the
|
|
588
|
+
* machine-wide registry. Used to scope resources/list to the current project(s). */
|
|
589
|
+
activeRoots() {
|
|
590
|
+
this.ensureRegistryLoaded();
|
|
591
|
+
const roots = new Set();
|
|
592
|
+
for (const id of this.activeIds) {
|
|
593
|
+
const s = this.sessions.get(id);
|
|
594
|
+
if (s)
|
|
595
|
+
roots.add(s.root);
|
|
596
|
+
}
|
|
597
|
+
return [...roots];
|
|
598
|
+
}
|
|
599
|
+
// ---- persistence reload (lazy, triggered by the first get/list/find/create) ----
|
|
275
600
|
loadRegistry() {
|
|
276
601
|
try {
|
|
277
602
|
if (!existsSync(REGISTRY))
|
|
@@ -283,6 +608,9 @@ export class SessionStore {
|
|
|
283
608
|
continue;
|
|
284
609
|
try {
|
|
285
610
|
const st = JSON.parse(readFileSync(sp, 'utf8'));
|
|
611
|
+
// Never clobber a LIVE session created by this process (lazy load can run after create()).
|
|
612
|
+
if (typeof st.id === 'string' && this.sessions.has(st.id))
|
|
613
|
+
continue;
|
|
286
614
|
const jobs = new Map((st.jobs ?? []).map((j) => [j.jobId, j]));
|
|
287
615
|
for (const j of jobs.values()) {
|
|
288
616
|
if (j.status === 'running') {
|
|
@@ -290,39 +618,111 @@ export class SessionStore {
|
|
|
290
618
|
j.error = 'server restarted while job was running (child process gone)';
|
|
291
619
|
}
|
|
292
620
|
}
|
|
621
|
+
// Orphaned-process handling: a prior server persisted its Metro pid. If no live
|
|
622
|
+
// server instance owns that pid, reclaimPid (processRegistry) reaps it ONLY when the
|
|
623
|
+
// registry's recorded fingerprint (start time + full command) still matches. A
|
|
624
|
+
// recycled or unverifiable pid is never signalled, and a process group is signalled
|
|
625
|
+
// only when Swipium spawned the child as a group leader. Either way the reloaded
|
|
626
|
+
// session drops the pid: it is not ours to manage (or stop) anymore.
|
|
627
|
+
if (typeof st.metroPid === 'number' && st.metroPid > 0) {
|
|
628
|
+
if (!pidOwnedByLiveServer(st.metroPid)) {
|
|
629
|
+
const outcome = reclaimPid(st.metroPid, 'metro');
|
|
630
|
+
if (outcome === 'killed')
|
|
631
|
+
log('warn', 'reaped orphaned Metro bundler from a previous server run', { pid: st.metroPid, sessionId: st.id });
|
|
632
|
+
}
|
|
633
|
+
st.metroPid = undefined; // reaped, gone, recycled, or another live server's; never ours
|
|
634
|
+
}
|
|
635
|
+
// Raw secret values never touch disk, so a rehydrated session cannot rebuild
|
|
636
|
+
// its redaction set. If the persisted state shows prior secret-bearing activity
|
|
637
|
+
// (secret inputs, secret generated values, or a performed login), flag the session so
|
|
638
|
+
// qa_report discloses that artifacts written after the restart are no longer scrubbed.
|
|
639
|
+
const priorSecrets = (st.inputs ?? []).some((i) => i.secret) ||
|
|
640
|
+
(st.generatedValues ?? []).some((g) => g.secret) ||
|
|
641
|
+
st.auth?.loginPerformed === true;
|
|
293
642
|
const s = {
|
|
294
|
-
id: st.id,
|
|
295
|
-
|
|
296
|
-
|
|
643
|
+
id: st.id,
|
|
644
|
+
root: st.root,
|
|
645
|
+
dir: st.dir,
|
|
646
|
+
createdAt: st.createdAt,
|
|
647
|
+
device: st.device,
|
|
648
|
+
appId: st.appId,
|
|
649
|
+
headless: st.headless,
|
|
650
|
+
metroPid: st.metroPid,
|
|
651
|
+
screenshotCount: st.screenshotCount ?? 0,
|
|
652
|
+
network: st.network,
|
|
653
|
+
envChanges: st.envChanges ?? [],
|
|
654
|
+
workarounds: st.workarounds ?? [],
|
|
655
|
+
chosenTarget: typeof st.chosenTarget === 'string' ? st.chosenTarget : undefined,
|
|
656
|
+
lastTestThisArgs: st.lastTestThisArgs && typeof st.lastTestThisArgs === 'object' && !Array.isArray(st.lastTestThisArgs)
|
|
657
|
+
? st.lastTestThisArgs
|
|
658
|
+
: undefined,
|
|
297
659
|
mode: st.mode ?? 'structured',
|
|
298
660
|
responseMode: st.responseMode ?? DEFAULT_RESPONSE_MODE,
|
|
299
661
|
sensitive: st.sensitive ?? false,
|
|
300
662
|
budget: { ...DEFAULT_BUDGET, ...(st.budget ?? {}) },
|
|
301
663
|
counters: { actions: 0, screenshots: 0, snapshotFailures: 0, noChangeActions: 0, ...(st.counters ?? {}) },
|
|
302
|
-
jobs,
|
|
303
|
-
|
|
304
|
-
|
|
664
|
+
jobs,
|
|
665
|
+
artifacts: st.artifacts ?? [],
|
|
666
|
+
findings: st.findings ?? [],
|
|
667
|
+
notes: st.notes ?? [],
|
|
668
|
+
mutations: st.mutations ?? [],
|
|
669
|
+
recordedActions: st.recordedActions ?? [],
|
|
670
|
+
toolErrors: st.toolErrors ?? [],
|
|
671
|
+
driverKind: typeof st.driverKind === 'string' ? st.driverKind : undefined,
|
|
672
|
+
wdaUrl: typeof st.wdaUrl === 'string' ? st.wdaUrl : undefined,
|
|
673
|
+
// Live fixture values come from the project's fixtures.json (+ SWIPIUM_* env at use),
|
|
674
|
+
// never from state.json, which only holds value-less metadata.
|
|
675
|
+
fixtures: rehydrateFixtures(st.root, st.fixtures),
|
|
676
|
+
auth: st.auth ?? {},
|
|
677
|
+
milestones: st.milestones ?? { session_start: st.createdAt },
|
|
678
|
+
budgetProfile: st.budgetProfile,
|
|
679
|
+
secrets: new Set(),
|
|
680
|
+
redactionDegraded: priorSecrets || undefined,
|
|
681
|
+
inputs: st.inputs ?? [],
|
|
682
|
+
generatedValues: st.generatedValues ?? [],
|
|
683
|
+
inputValues: new Map(),
|
|
684
|
+
exploration: st.exploration,
|
|
685
|
+
aborts: new Map(),
|
|
305
686
|
};
|
|
306
687
|
this.sessions.set(s.id, s);
|
|
307
688
|
}
|
|
308
|
-
catch {
|
|
309
|
-
|
|
689
|
+
catch (e) {
|
|
690
|
+
// Skip a corrupt session, but say so, otherwise prior artifacts/reports vanish silently.
|
|
691
|
+
log('warn', 'skipping corrupt prior session state.json on reload', { dir, err: String(e) });
|
|
310
692
|
}
|
|
311
693
|
}
|
|
312
694
|
}
|
|
313
|
-
catch {
|
|
314
|
-
|
|
695
|
+
catch (e) {
|
|
696
|
+
log('warn', 'session registry unreadable, prior sessions will not be reloaded', { registry: REGISTRY, err: String(e) });
|
|
315
697
|
}
|
|
316
698
|
}
|
|
699
|
+
/** Append this session to the shared ~/.swipium/registry.json. Guarded by an advisory
|
|
700
|
+
* lockfile + atomic rename so two concurrent server instances (common with multiple MCP
|
|
701
|
+
* clients) never clobber each other's read-modify-write. */
|
|
317
702
|
appendRegistry(id, dir) {
|
|
318
703
|
try {
|
|
319
704
|
mkdirSync(REGISTRY_DIR, { recursive: true });
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
705
|
+
withFileLock(`${REGISTRY}.lock`, () => {
|
|
706
|
+
let reg = [];
|
|
707
|
+
if (existsSync(REGISTRY)) {
|
|
708
|
+
try {
|
|
709
|
+
const parsed = JSON.parse(readFileSync(REGISTRY, 'utf8'));
|
|
710
|
+
if (Array.isArray(parsed))
|
|
711
|
+
reg = parsed;
|
|
712
|
+
}
|
|
713
|
+
catch (e) {
|
|
714
|
+
log('warn', 'registry.json corrupt, rebuilding it (prior sessions may need re-registration)', {
|
|
715
|
+
registry: REGISTRY,
|
|
716
|
+
err: String(e),
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
reg.push({ id, dir });
|
|
721
|
+
writeFileAtomicSync(REGISTRY, JSON.stringify(reg.slice(-200), null, 2)); // atomic on the same filesystem
|
|
722
|
+
});
|
|
323
723
|
}
|
|
324
|
-
catch {
|
|
325
|
-
|
|
724
|
+
catch (e) {
|
|
725
|
+
log('error', 'failed to append session to registry; it will not be reloadable after a restart', { id, dir, err: String(e) });
|
|
326
726
|
}
|
|
327
727
|
}
|
|
328
728
|
}
|