swipium 1.5.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +171 -0
- package/README.md +173 -280
- package/THREAT_MODEL.md +223 -47
- package/dist/appMap/automationLink.js +30 -27
- package/dist/appMap/automationLink.js.map +1 -1
- package/dist/appMap/build.js +47 -21
- package/dist/appMap/build.js.map +1 -1
- package/dist/appMap/codeIndex.js +2 -2
- package/dist/appMap/codeIndex.js.map +1 -1
- package/dist/appMap/featureIndex.js +3 -3
- package/dist/appMap/featureIndex.js.map +1 -1
- package/dist/appMap/featureModel.js +2 -2
- package/dist/appMap/featureModel.js.map +1 -1
- package/dist/appMap/firstRunApply.js +2 -2
- package/dist/appMap/firstRunApply.js.map +1 -1
- package/dist/appMap/issues.js +10 -10
- package/dist/appMap/issues.js.map +1 -1
- package/dist/appMap/migrations.js +5 -5
- package/dist/appMap/migrations.js.map +1 -1
- package/dist/appMap/prelaunch.js +2 -2
- package/dist/appMap/prelaunch.js.map +1 -1
- package/dist/appMap/projectRegistry.js +77 -11
- package/dist/appMap/projectRegistry.js.map +1 -1
- package/dist/appMap/provenance.js +2 -2
- package/dist/appMap/provenance.js.map +1 -1
- package/dist/appMap/query.js +2 -2
- package/dist/appMap/query.js.map +1 -1
- package/dist/appMap/runtimeMerge.js +3 -3
- package/dist/appMap/runtimeMerge.js.map +1 -1
- package/dist/appMap/schema.js +1 -1
- package/dist/appMap/schema.js.map +1 -1
- package/dist/appMap/screenMatch.js +1 -1
- package/dist/appMap/screenMatch.js.map +1 -1
- package/dist/appMap/staticScan.js +17 -17
- package/dist/appMap/staticScan.js.map +1 -1
- package/dist/appMap/store.js +55 -7
- package/dist/appMap/store.js.map +1 -1
- package/dist/appMap/tsAstScan.js +3 -3
- package/dist/appMap/tsAstScan.js.map +1 -1
- package/dist/artifacts/bundletool.js +5 -5
- package/dist/artifacts/bundletool.js.map +1 -1
- package/dist/artifacts/resolve.js +10 -10
- package/dist/artifacts/resolve.js.map +1 -1
- package/dist/automation/capabilities.js +3 -3
- package/dist/automation/capabilities.js.map +1 -1
- package/dist/automation/gestures.js +1 -1
- package/dist/automation/gestures.js.map +1 -1
- package/dist/automation/plan.js +7 -7
- package/dist/automation/plan.js.map +1 -1
- package/dist/automation/report.js +3 -3
- package/dist/automation/report.js.map +1 -1
- package/dist/automation/selectors.js +14 -25
- package/dist/automation/selectors.js.map +1 -1
- package/dist/automation/types.js +1 -1
- package/dist/automation/types.js.map +1 -1
- package/dist/automation/waits.js +5 -5
- package/dist/automation/waits.js.map +1 -1
- package/dist/automation/webview.js +2 -2
- package/dist/automation/webview.js.map +1 -1
- package/dist/automationGen/appiumModel.js +13 -7
- package/dist/automationGen/appiumModel.js.map +1 -1
- package/dist/automationGen/ciEmitter.js +5 -5
- package/dist/automationGen/ciEmitter.js.map +1 -1
- package/dist/automationGen/identifiers.js +224 -0
- package/dist/automationGen/identifiers.js.map +1 -0
- package/dist/automationGen/jsEmitter.js +265 -106
- package/dist/automationGen/jsEmitter.js.map +1 -1
- package/dist/automationGen/packagePatch.js +2 -2
- package/dist/automationGen/packagePatch.js.map +1 -1
- package/dist/automationGen/platformResolve.js +36 -0
- package/dist/automationGen/platformResolve.js.map +1 -0
- package/dist/automationGen/projectProfile.js +71 -26
- package/dist/automationGen/projectProfile.js.map +1 -1
- package/dist/automationGen/pythonEmitter.js +276 -105
- package/dist/automationGen/pythonEmitter.js.map +1 -1
- package/dist/automationGen/readmeEmitter.js +7 -6
- package/dist/automationGen/readmeEmitter.js.map +1 -1
- package/dist/{tools/automationGenerate.js → automationGen/run.js} +72 -113
- package/dist/automationGen/run.js.map +1 -0
- package/dist/automationGen/suitePlan.js +6 -4
- package/dist/automationGen/suitePlan.js.map +1 -1
- package/dist/automationGen/validation.js +20 -8
- package/dist/automationGen/validation.js.map +1 -1
- package/dist/build/parseBuildLog.js +2 -2
- package/dist/build/parseBuildLog.js.map +1 -1
- package/dist/build/plan.js +7 -7
- package/dist/build/plan.js.map +1 -1
- package/dist/ci/preflight.js +13 -4
- package/dist/ci/preflight.js.map +1 -1
- package/dist/cli/gc.js +74 -0
- package/dist/cli/gc.js.map +1 -0
- package/dist/cli/init.js +217 -26
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/main.js +50 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/report.js +218 -0
- package/dist/cli/report.js.map +1 -0
- package/dist/cli/scan.js +35 -14
- package/dist/cli/scan.js.map +1 -1
- package/dist/cli/suite.js +6 -6
- package/dist/cli/suite.js.map +1 -1
- package/dist/cli/verify.js +7 -7
- package/dist/cli/verify.js.map +1 -1
- package/dist/consent/consent.js +162 -5
- package/dist/consent/consent.js.map +1 -1
- package/dist/context/detect.js +45 -14
- package/dist/context/detect.js.map +1 -1
- package/dist/context/findApps.js +6 -6
- package/dist/context/findApps.js.map +1 -1
- package/dist/context/projectRoot.js +174 -19
- package/dist/context/projectRoot.js.map +1 -1
- package/dist/context/scan.js +1 -1
- package/dist/context/scan.js.map +1 -1
- package/dist/core/capabilityGroups.js +90 -0
- package/dist/core/capabilityGroups.js.map +1 -0
- package/dist/core/target.js +3 -3
- package/dist/core/target.js.map +1 -1
- package/dist/core/targetPlan.js +65 -62
- package/dist/core/targetPlan.js.map +1 -1
- package/dist/drivers/DirectDriver.js +300 -36
- package/dist/drivers/DirectDriver.js.map +1 -1
- package/dist/drivers/SimctlDriver.js +120 -7
- package/dist/drivers/SimctlDriver.js.map +1 -1
- package/dist/drivers/WdaDriver.js +350 -50
- package/dist/drivers/WdaDriver.js.map +1 -1
- package/dist/explore/candidates.js +2 -2
- package/dist/explore/candidates.js.map +1 -1
- package/dist/explore/graph.js +5 -5
- package/dist/explore/graph.js.map +1 -1
- package/dist/explore/policy.js +5 -5
- package/dist/explore/policy.js.map +1 -1
- package/dist/explore/runner.js +301 -284
- package/dist/explore/runner.js.map +1 -1
- package/dist/explore/signatures.js +1 -1
- package/dist/featureTesting/executionBootstrap.js +29 -30
- package/dist/featureTesting/executionBootstrap.js.map +1 -1
- package/dist/featureTesting/featureMap.js +2 -2
- package/dist/featureTesting/featureMap.js.map +1 -1
- package/dist/featureTesting/featureScope.js +12 -12
- package/dist/featureTesting/featureScope.js.map +1 -1
- package/dist/featureTesting/mapFeatureScope.js +7 -7
- package/dist/featureTesting/mapFeatureScope.js.map +1 -1
- package/dist/featureTesting/objectiveModel.js +10 -10
- package/dist/featureTesting/objectiveModel.js.map +1 -1
- package/dist/featureTesting/resultMerge.js +6 -6
- package/dist/featureTesting/resultMerge.js.map +1 -1
- package/dist/featureTesting/sources.js +4 -4
- package/dist/featureTesting/sources.js.map +1 -1
- package/dist/featureTesting/suiteBridge.js +1 -1
- package/dist/featureTesting/suiteBridge.js.map +1 -1
- package/dist/featureTesting/synonyms.js +1 -1
- package/dist/featureTesting/synonyms.js.map +1 -1
- package/dist/featureTesting/testCaseFactory.js +11 -11
- package/dist/featureTesting/testCaseFactory.js.map +1 -1
- package/dist/featureTesting/testPlan.js +3 -3
- package/dist/featureTesting/testPlan.js.map +1 -1
- package/dist/firstRun/authStateMachine.js +1 -1
- package/dist/firstRun/authStateMachine.js.map +1 -1
- package/dist/firstRun/classifyScreen.js +5 -5
- package/dist/firstRun/classifyScreen.js.map +1 -1
- package/dist/firstRun/firstRunPlanner.js +11 -11
- package/dist/firstRun/firstRunPlanner.js.map +1 -1
- package/dist/firstRun/firstRunRunner.js +8 -8
- package/dist/firstRun/firstRunRunner.js.map +1 -1
- package/dist/firstRun/generatedDataPolicy.js +9 -9
- package/dist/firstRun/generatedDataPolicy.js.map +1 -1
- package/dist/firstRun/inputPlanner.js +5 -5
- package/dist/firstRun/inputPlanner.js.map +1 -1
- package/dist/firstRun/onboardingStateMachine.js +1 -1
- package/dist/firstRun/onboardingStateMachine.js.map +1 -1
- package/dist/firstRun/paywallPolicy.js +3 -3
- package/dist/firstRun/paywallPolicy.js.map +1 -1
- package/dist/firstRun/types.js +1 -1
- package/dist/firstRun/types.js.map +1 -1
- package/dist/fixtures/catalog.js +33 -3
- package/dist/fixtures/catalog.js.map +1 -1
- package/dist/fixtures/load.js +50 -0
- package/dist/fixtures/load.js.map +1 -0
- package/dist/flows/discover.js +2 -2
- package/dist/flows/discover.js.map +1 -1
- package/dist/flows/generate.js +20 -10
- package/dist/flows/generate.js.map +1 -1
- package/dist/flows/pack.js +3 -3
- package/dist/flows/pack.js.map +1 -1
- package/dist/flows/paths.js +57 -0
- package/dist/flows/paths.js.map +1 -0
- package/dist/flows/repair.js +111 -23
- package/dist/flows/repair.js.map +1 -1
- package/dist/flows/run.js +153 -57
- package/dist/flows/run.js.map +1 -1
- package/dist/flows/schema.js +50 -5
- package/dist/flows/schema.js.map +1 -1
- package/dist/flows/seedExec.js +4 -4
- package/dist/flows/seedExec.js.map +1 -1
- package/dist/flows/templates.js +5 -5
- package/dist/index.js +50 -8
- package/dist/index.js.map +1 -1
- package/dist/issues/classify.js +16 -16
- package/dist/issues/classify.js.map +1 -1
- package/dist/issues/fingerprint.js +24 -4
- package/dist/issues/fingerprint.js.map +1 -1
- package/dist/issues/index.js +94 -31
- package/dist/issues/index.js.map +1 -1
- package/dist/issues/metrics.js +4 -4
- package/dist/issues/metrics.js.map +1 -1
- package/dist/issues/recurrence.js +35 -8
- package/dist/issues/recurrence.js.map +1 -1
- package/dist/issues/report.js +4 -4
- package/dist/issues/report.js.map +1 -1
- package/dist/issues/reportBridge.js +4 -4
- package/dist/issues/reportBridge.js.map +1 -1
- package/dist/issues/schema.js +2 -2
- package/dist/issues/schema.js.map +1 -1
- package/dist/issues/sourceRevision.js +2 -2
- package/dist/issues/sourceRevision.js.map +1 -1
- package/dist/issues/store.js +110 -32
- package/dist/issues/store.js.map +1 -1
- package/dist/lib/abortScope.js +48 -0
- package/dist/lib/abortScope.js.map +1 -0
- package/dist/lib/android.js +75 -7
- package/dist/lib/android.js.map +1 -1
- package/dist/lib/coordSpace.js +1 -1
- package/dist/lib/coordSpace.js.map +1 -1
- package/dist/lib/device.js +32 -7
- package/dist/lib/device.js.map +1 -1
- package/dist/lib/gestures.js +141 -0
- package/dist/lib/gestures.js.map +1 -0
- package/dist/lib/gitignore.js +4 -3
- package/dist/lib/gitignore.js.map +1 -1
- package/dist/lib/image.js +1 -1
- package/dist/lib/image.js.map +1 -1
- package/dist/lib/lockfile.js +370 -31
- package/dist/lib/lockfile.js.map +1 -1
- package/dist/lib/needsInput.js +3 -3
- package/dist/lib/needsInput.js.map +1 -1
- package/dist/lib/png.js +2 -2
- package/dist/lib/png.js.map +1 -1
- package/dist/lib/redact.js +170 -9
- package/dist/lib/redact.js.map +1 -1
- package/dist/lib/result.js +79 -10
- package/dist/lib/result.js.map +1 -1
- package/dist/lib/schemaHash.js +3 -3
- package/dist/lib/schemaHash.js.map +1 -1
- package/dist/lib/sensitive.js +3 -2
- package/dist/lib/sensitive.js.map +1 -1
- package/dist/lib/simctl.js +3 -3
- package/dist/lib/simctl.js.map +1 -1
- package/dist/lib/spawn.js +52 -10
- package/dist/lib/spawn.js.map +1 -1
- package/dist/lib/toolAnnotations.js +114 -0
- package/dist/lib/toolAnnotations.js.map +1 -0
- package/dist/lib/wda.js +294 -15
- package/dist/lib/wda.js.map +1 -1
- package/dist/mobileAudit/checks.js +12 -12
- package/dist/mobileAudit/checks.js.map +1 -1
- package/dist/mobileAudit/evidence.js +4 -4
- package/dist/mobileAudit/evidence.js.map +1 -1
- package/dist/mobileAudit/profiles.js +1 -1
- package/dist/mobileAudit/profiles.js.map +1 -1
- package/dist/mobileAudit/results.js +3 -3
- package/dist/mobileAudit/results.js.map +1 -1
- package/dist/mobileAudit/runner.js +72 -18
- package/dist/mobileAudit/runner.js.map +1 -1
- package/dist/oracle/auth.js +1 -1
- package/dist/oracle/auth.js.map +1 -1
- package/dist/oracle/failures.js +219 -36
- package/dist/oracle/failures.js.map +1 -1
- package/dist/oracle/health.js +63 -17
- package/dist/oracle/health.js.map +1 -1
- package/dist/oracle/locator.js +13 -13
- package/dist/oracle/locator.js.map +1 -1
- package/dist/oracle/record.js +3 -3
- package/dist/oracle/record.js.map +1 -1
- package/dist/orchestration/envelope.js +2 -2
- package/dist/orchestration/envelope.js.map +1 -1
- package/dist/orchestration/goal.js +4 -4
- package/dist/orchestration/goal.js.map +1 -1
- package/dist/orchestration/testThis/execute.js +62 -21
- package/dist/orchestration/testThis/execute.js.map +1 -1
- package/dist/orchestration/testThis/pipeline.js +119 -30
- package/dist/orchestration/testThis/pipeline.js.map +1 -1
- package/dist/orchestration/testThis/plan.js +162 -73
- package/dist/orchestration/testThis/plan.js.map +1 -1
- package/dist/orchestration/testThis/sessionIntent.js +72 -0
- package/dist/orchestration/testThis/sessionIntent.js.map +1 -0
- package/dist/orchestration/testThis/terminal.js +34 -16
- package/dist/orchestration/testThis/terminal.js.map +1 -1
- package/dist/orchestration/testThis/types.js +1 -1
- package/dist/orchestration/testThis/types.js.map +1 -1
- package/dist/plan/plan.js +9 -9
- package/dist/plan/plan.js.map +1 -1
- package/dist/prompts/index.js +22 -22
- package/dist/prompts/index.js.map +1 -1
- package/dist/report/export.js +392 -37
- package/dist/report/export.js.map +1 -1
- package/dist/report/findingsDedupe.js +41 -0
- package/dist/report/findingsDedupe.js.map +1 -0
- package/dist/report/policy.js +7 -4
- package/dist/report/policy.js.map +1 -1
- package/dist/report/qaLevel.js +8 -8
- package/dist/report/qaLevel.js.map +1 -1
- package/dist/report/sarifSources.js +160 -0
- package/dist/report/sarifSources.js.map +1 -0
- package/dist/report/summary.js +2 -2
- package/dist/report/summary.js.map +1 -1
- package/dist/report/testCatalog.js +4 -4
- package/dist/report/testCatalog.js.map +1 -1
- package/dist/report/toolHealth.js +124 -0
- package/dist/report/toolHealth.js.map +1 -0
- package/dist/server.js +472 -45
- package/dist/server.js.map +1 -1
- package/dist/services/automationGenerate.js +8 -2
- package/dist/services/automationGenerate.js.map +1 -1
- package/dist/services/build.js +2 -2
- package/dist/services/build.js.map +1 -1
- package/dist/{tools → services}/flowGenerate.js +29 -13
- package/dist/services/flowGenerate.js.map +1 -0
- package/dist/services/preflight.js +4 -4
- package/dist/services/preflight.js.map +1 -1
- package/dist/services/prepareAndroid.js +46 -8
- package/dist/services/prepareAndroid.js.map +1 -1
- package/dist/services/prepareIos.js +24 -9
- package/dist/services/prepareIos.js.map +1 -1
- package/dist/services/report.js +100 -45
- package/dist/services/report.js.map +1 -1
- package/dist/services/smoke.js +32 -16
- package/dist/services/smoke.js.map +1 -1
- package/dist/services/suiteGenerate.js +28 -3
- package/dist/services/suiteGenerate.js.map +1 -1
- package/dist/services/testSuiteKnowledge.js +40 -27
- package/dist/services/testSuiteKnowledge.js.map +1 -1
- package/dist/session/attach.js +270 -6
- package/dist/session/attach.js.map +1 -1
- package/dist/session/processRegistry.js +317 -48
- package/dist/session/processRegistry.js.map +1 -1
- package/dist/session/progress.js +1 -1
- package/dist/session/retention.js +201 -0
- package/dist/session/retention.js.map +1 -0
- package/dist/session/store.js +275 -48
- package/dist/session/store.js.map +1 -1
- package/dist/snapshot/overlays.js +53 -8
- package/dist/snapshot/overlays.js.map +1 -1
- package/dist/snapshot/parse.js +28 -4
- package/dist/snapshot/parse.js.map +1 -1
- package/dist/snapshot/present.js +11 -5
- package/dist/snapshot/present.js.map +1 -1
- package/dist/snapshot/settle.js +39 -9
- package/dist/snapshot/settle.js.map +1 -1
- package/dist/state/consent.js +99 -0
- package/dist/state/consent.js.map +1 -0
- package/dist/state/profile.js +46 -2
- package/dist/state/profile.js.map +1 -1
- package/dist/suite/compile.js +9 -5
- package/dist/suite/compile.js.map +1 -1
- package/dist/suite/lint.js +4 -4
- package/dist/suite/lint.js.map +1 -1
- package/dist/suite/pom.js +50 -41
- package/dist/suite/pom.js.map +1 -1
- package/dist/suite/secretGuard.js +266 -0
- package/dist/suite/secretGuard.js.map +1 -0
- package/dist/suite/testcase.js +9 -7
- package/dist/suite/testcase.js.map +1 -1
- package/dist/testSuite/exporter.js +4 -4
- package/dist/testSuite/exporter.js.map +1 -1
- package/dist/testSuite/generator.js +13 -9
- package/dist/testSuite/generator.js.map +1 -1
- package/dist/testSuite/history.js +1 -1
- package/dist/testSuite/history.js.map +1 -1
- package/dist/testSuite/issueLinks.js +3 -3
- package/dist/testSuite/issueLinks.js.map +1 -1
- package/dist/testSuite/lint.js +4 -4
- package/dist/testSuite/lint.js.map +1 -1
- package/dist/testSuite/merge.js +5 -5
- package/dist/testSuite/merge.js.map +1 -1
- package/dist/testSuite/schema.js +5 -5
- package/dist/testSuite/schema.js.map +1 -1
- package/dist/testSuite/store.js +3 -3
- package/dist/testSuite/store.js.map +1 -1
- package/dist/testSuite/traceability.js +2 -2
- package/dist/testSuite/traceability.js.map +1 -1
- package/dist/tools/act.js +794 -164
- package/dist/tools/act.js.map +1 -1
- package/dist/tools/agent.js +451 -128
- package/dist/tools/agent.js.map +1 -1
- package/dist/tools/appControl.js +69 -19
- package/dist/tools/appControl.js.map +1 -1
- package/dist/tools/appMap.js +188 -130
- package/dist/tools/appMap.js.map +1 -1
- package/dist/tools/build.js +32 -42
- package/dist/tools/build.js.map +1 -1
- package/dist/tools/bundletool.js +21 -31
- package/dist/tools/bundletool.js.map +1 -1
- package/dist/tools/clearOverlay.js +55 -12
- package/dist/tools/clearOverlay.js.map +1 -1
- package/dist/tools/device.js +76 -24
- package/dist/tools/device.js.map +1 -1
- package/dist/tools/doctor.js +68 -28
- package/dist/tools/doctor.js.map +1 -1
- package/dist/tools/explore.js +41 -47
- package/dist/tools/explore.js.map +1 -1
- package/dist/tools/featureTesting.js +31 -48
- package/dist/tools/featureTesting.js.map +1 -1
- package/dist/tools/firstRun.js +34 -41
- package/dist/tools/firstRun.js.map +1 -1
- package/dist/tools/flow.js +183 -103
- package/dist/tools/flow.js.map +1 -1
- package/dist/tools/flowRepair.js +25 -13
- package/dist/tools/flowRepair.js.map +1 -1
- package/dist/tools/generate.js +52 -89
- package/dist/tools/generate.js.map +1 -1
- package/dist/tools/getArtifact.js +16 -5
- package/dist/tools/getArtifact.js.map +1 -1
- package/dist/tools/health.js +15 -12
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/ios.js +45 -223
- package/dist/tools/ios.js.map +1 -1
- package/dist/tools/issues.js +251 -35
- package/dist/tools/issues.js.map +1 -1
- package/dist/tools/jobs.js +41 -16
- package/dist/tools/jobs.js.map +1 -1
- package/dist/tools/metro.js +36 -35
- package/dist/tools/metro.js.map +1 -1
- package/dist/tools/mobileAudit.js +50 -45
- package/dist/tools/mobileAudit.js.map +1 -1
- package/dist/tools/network.js +31 -14
- package/dist/tools/network.js.map +1 -1
- package/dist/tools/note.js +26 -26
- package/dist/tools/note.js.map +1 -1
- package/dist/tools/prepareIosTarget.js +10 -16
- package/dist/tools/prepareIosTarget.js.map +1 -1
- package/dist/tools/prepareTarget.js +117 -50
- package/dist/tools/prepareTarget.js.map +1 -1
- package/dist/tools/report.js +11 -22
- package/dist/tools/report.js.map +1 -1
- package/dist/tools/resolveArtifact.js +11 -15
- package/dist/tools/resolveArtifact.js.map +1 -1
- package/dist/tools/resolveTarget.js +78 -24
- package/dist/tools/resolveTarget.js.map +1 -1
- package/dist/tools/screenRecord.js +23 -21
- package/dist/tools/screenRecord.js.map +1 -1
- package/dist/tools/screenshot.js +23 -16
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/smoke.js +20 -16
- package/dist/tools/smoke.js.map +1 -1
- package/dist/tools/snapshot.js +155 -118
- package/dist/tools/snapshot.js.map +1 -1
- package/dist/tools/startSession.js +104 -116
- package/dist/tools/startSession.js.map +1 -1
- package/dist/tools/suite.js +72 -43
- package/dist/tools/suite.js.map +1 -1
- package/dist/tools/testSuite.js +30 -20
- package/dist/tools/testSuite.js.map +1 -1
- package/dist/tools/testThis.js +24 -28
- package/dist/tools/testThis.js.map +1 -1
- package/dist/tools/visual.js +686 -210
- package/dist/tools/visual.js.map +1 -1
- package/dist/tools/wait.js +9 -38
- package/dist/tools/wait.js.map +1 -1
- package/dist/tools/wda.js +187 -57
- package/dist/tools/wda.js.map +1 -1
- package/dist/version.js +31 -22
- package/dist/version.js.map +1 -1
- package/dist/visual/ocr.js +53 -6
- package/dist/visual/ocr.js.map +1 -1
- package/dist/visual/provider.js +80 -12
- package/dist/visual/provider.js.map +1 -1
- package/docs/README.md +24 -10
- package/docs/ci-reports.md +328 -0
- package/docs/concepts.md +227 -0
- package/docs/flows.md +153 -0
- package/docs/mcp-server.md +194 -128
- package/docs/physical-devices.md +84 -0
- package/docs/tools.md +812 -152
- package/package.json +3 -2
- package/dist/tools/assertVisual.js +0 -84
- package/dist/tools/assertVisual.js.map +0 -1
- package/dist/tools/automationGenerate.js.map +0 -1
- package/dist/tools/capabilities.js +0 -179
- package/dist/tools/capabilities.js.map +0 -1
- package/dist/tools/detectContext.js +0 -41
- package/dist/tools/detectContext.js.map +0 -1
- package/dist/tools/flowGenerate.js.map +0 -1
- package/dist/tools/history.js +0 -86
- package/dist/tools/history.js.map +0 -1
- package/dist/tools/locator.js +0 -85
- package/dist/tools/locator.js.map +0 -1
- package/dist/tools/permissions.js +0 -174
- package/dist/tools/permissions.js.map +0 -1
- package/dist/tools/plan.js +0 -52
- package/dist/tools/plan.js.map +0 -1
- package/dist/tools/screenInfo.js +0 -76
- package/dist/tools/screenInfo.js.map +0 -1
- package/dist/tools/seed.js +0 -133
- package/dist/tools/seed.js.map +0 -1
- package/dist/tools/state.js +0 -217
- package/dist/tools/state.js.map +0 -1
- package/dist/tools/visualText.js +0 -106
- package/dist/tools/visualText.js.map +0 -1
package/dist/session/store.js
CHANGED
|
@@ -1,20 +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
3
|
// a serializable subset is written to <sessionDir>/state.json on mutation (debounced;
|
|
4
4
|
// synchronous for the mutation ledger + session creation + shutdown flush), and a small
|
|
5
|
-
// registry under ~/.swipium/registry.json
|
|
5
|
+
// registry under ~/.swipium/registry.json (lock-guarded, lazily loaded on first access)
|
|
6
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
|
|
7
|
+
// a server restart. (A restart does NOT resurrect a running child process: such jobs are
|
|
8
8
|
// marked failed on reload, and orphaned Metro pids are verified via `ps` and reaped.)
|
|
9
9
|
import { createHash, randomUUID } from 'node:crypto';
|
|
10
|
-
import { mkdirSync, writeFileSync, readFileSync, existsSync
|
|
11
|
-
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';
|
|
12
12
|
import { homedir } from 'node:os';
|
|
13
13
|
import { DEFAULT_RESPONSE_MODE } from '../lib/result.js';
|
|
14
|
-
import { makeRedactor } from '../lib/redact.js';
|
|
15
|
-
import {
|
|
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';
|
|
16
17
|
import { log } from '../lib/logger.js';
|
|
17
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';
|
|
18
22
|
// Phase 2.2 P1.6: budget classes (minutes) keyed by workflow ambition.
|
|
19
23
|
export const BUDGET_PROFILES = {
|
|
20
24
|
guardrail: 8, // guardrail/setup validation
|
|
@@ -22,7 +26,7 @@ export const BUDGET_PROFILES = {
|
|
|
22
26
|
full_smoke: 15, // full authenticated smoke
|
|
23
27
|
install_smoke: 20, // install/boot/rebuild involved
|
|
24
28
|
};
|
|
25
|
-
// Security model
|
|
29
|
+
// Security model: raw secret values (generated passwords/OTPs/tokens) must never touch
|
|
26
30
|
// disk. In memory we keep the raw value (for same-session reuse via inputValues), but the
|
|
27
31
|
// serialized form sent to state.json redacts secret values while preserving all reproducibility
|
|
28
32
|
// metadata (varName/field/generator/secret/artifactUri/timestamps). Non-secret generated values
|
|
@@ -30,6 +34,36 @@ export const BUDGET_PROFILES = {
|
|
|
30
34
|
export function serializeGeneratedValues(records) {
|
|
31
35
|
return records.map((r) => (r.secret ? { ...r, value: '<redacted>' } : r));
|
|
32
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
|
+
}
|
|
33
67
|
export const DEFAULT_BUDGET = {
|
|
34
68
|
maxMinutes: 8,
|
|
35
69
|
maxActions: 20,
|
|
@@ -58,6 +92,73 @@ export function artifactDirectoryName(kind) {
|
|
|
58
92
|
return kind;
|
|
59
93
|
}
|
|
60
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
|
+
}
|
|
61
162
|
function defaultSessionDir(root, id) {
|
|
62
163
|
const projectHash = createHash('sha256').update(resolve(root)).digest('hex').slice(0, 16);
|
|
63
164
|
return join(REGISTRY_DIR, 'runs', projectHash, id);
|
|
@@ -73,17 +174,22 @@ export class SessionStore {
|
|
|
73
174
|
registryLoaded = false;
|
|
74
175
|
dirty = new Set();
|
|
75
176
|
flushTimer;
|
|
177
|
+
// Sessions created or looked up by id in THIS process (see activeRoots).
|
|
178
|
+
activeIds = new Set();
|
|
76
179
|
ensureRegistryLoaded() {
|
|
77
180
|
if (this.registryLoaded)
|
|
78
181
|
return;
|
|
79
182
|
this.registryLoaded = true;
|
|
80
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));
|
|
81
187
|
}
|
|
82
188
|
create(root, budget, opts) {
|
|
83
189
|
this.ensureRegistryLoaded();
|
|
84
190
|
const id = randomUUID().slice(0, 8);
|
|
85
191
|
const dir = opts?.sessionDir ?? defaultSessionDir(root, id);
|
|
86
|
-
|
|
192
|
+
mkdirPrivate(dir);
|
|
87
193
|
const now = Date.now();
|
|
88
194
|
const s = {
|
|
89
195
|
id,
|
|
@@ -104,6 +210,7 @@ export class SessionStore {
|
|
|
104
210
|
notes: [],
|
|
105
211
|
mutations: [],
|
|
106
212
|
recordedActions: [],
|
|
213
|
+
toolErrors: [],
|
|
107
214
|
fixtures: opts?.fixtures ?? [],
|
|
108
215
|
auth: {},
|
|
109
216
|
milestones: { session_start: now },
|
|
@@ -115,13 +222,17 @@ export class SessionStore {
|
|
|
115
222
|
aborts: new Map(),
|
|
116
223
|
};
|
|
117
224
|
this.sessions.set(id, s);
|
|
225
|
+
this.activeIds.add(id);
|
|
118
226
|
this.appendRegistry(id, dir);
|
|
119
227
|
this.persistNow(s); // synchronous: a concurrent instance / crash must see the new session
|
|
120
228
|
return s;
|
|
121
229
|
}
|
|
122
230
|
get(id) {
|
|
123
231
|
this.ensureRegistryLoaded();
|
|
124
|
-
|
|
232
|
+
const s = this.sessions.get(id);
|
|
233
|
+
if (s)
|
|
234
|
+
this.activeIds.add(id);
|
|
235
|
+
return s;
|
|
125
236
|
}
|
|
126
237
|
list() {
|
|
127
238
|
this.ensureRegistryLoaded();
|
|
@@ -163,47 +274,61 @@ export class SessionStore {
|
|
|
163
274
|
}
|
|
164
275
|
writeState(s) {
|
|
165
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
|
+
}
|
|
166
287
|
const state = {
|
|
167
288
|
id: s.id,
|
|
168
289
|
root: s.root,
|
|
169
290
|
dir: s.dir,
|
|
170
291
|
createdAt: s.createdAt,
|
|
171
292
|
device: s.device,
|
|
293
|
+
driverKind: s.driverKind,
|
|
294
|
+
wdaUrl: s.wdaUrl,
|
|
172
295
|
appId: s.appId,
|
|
173
296
|
headless: s.headless,
|
|
174
297
|
metroPid: s.metroPid,
|
|
175
298
|
screenshotCount: s.screenshotCount,
|
|
176
299
|
network: s.network,
|
|
177
|
-
envChanges:
|
|
300
|
+
envChanges: safe.envChanges,
|
|
178
301
|
workarounds: s.workarounds,
|
|
302
|
+
chosenTarget: s.chosenTarget,
|
|
303
|
+
lastTestThisArgs: s.lastTestThisArgs && s.secrets.size ? redactDeep(s.lastTestThisArgs, makeRedactor(s.secrets)) : s.lastTestThisArgs,
|
|
179
304
|
mode: s.mode,
|
|
180
305
|
responseMode: s.responseMode,
|
|
181
306
|
sensitive: s.sensitive,
|
|
182
307
|
budget: s.budget,
|
|
183
308
|
counters: s.counters,
|
|
184
|
-
jobs:
|
|
309
|
+
jobs: safe.jobs,
|
|
185
310
|
artifacts: s.artifacts,
|
|
186
|
-
findings:
|
|
187
|
-
notes:
|
|
188
|
-
mutations:
|
|
189
|
-
recordedActions:
|
|
190
|
-
|
|
311
|
+
findings: safe.findings,
|
|
312
|
+
notes: safe.notes,
|
|
313
|
+
mutations: safe.mutations,
|
|
314
|
+
recordedActions: safe.recordedActions,
|
|
315
|
+
toolErrors: safe.toolErrors,
|
|
316
|
+
fixtures: safe.fixtures,
|
|
191
317
|
auth: s.auth,
|
|
192
318
|
milestones: s.milestones,
|
|
193
319
|
budgetProfile: s.budgetProfile,
|
|
194
|
-
inputs: s.inputs, // METADATA only
|
|
320
|
+
inputs: s.inputs, // METADATA only, never the values
|
|
195
321
|
generatedValues: serializeGeneratedValues(s.generatedValues), // secret raw values redacted before disk
|
|
196
322
|
exploration: s.exploration,
|
|
197
323
|
};
|
|
198
324
|
// Atomic write (tmp + rename) so a crash mid-write never leaves a truncated state.json.
|
|
199
325
|
const target = join(s.dir, 'state.json');
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
renameSync(tmp, target);
|
|
326
|
+
writeFileAtomicSync(target, JSON.stringify(state, null, 2));
|
|
327
|
+
restrictFile(target); // owner-only: state.json carries run history / paths
|
|
203
328
|
}
|
|
204
329
|
catch (e) {
|
|
205
330
|
// A silent failure here means session state (jobs/artifacts/findings) is lost on restart.
|
|
206
|
-
log('error', 'failed to persist session state.json
|
|
331
|
+
log('error', 'failed to persist session state.json; this session will not survive a server restart', {
|
|
207
332
|
sessionId: s.id,
|
|
208
333
|
dir: s.dir,
|
|
209
334
|
err: String(e),
|
|
@@ -226,6 +351,20 @@ export class SessionStore {
|
|
|
226
351
|
s.mode = mode;
|
|
227
352
|
this.persist(s);
|
|
228
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
|
+
}
|
|
229
368
|
bump(s, key, by = 1) {
|
|
230
369
|
s.counters[key] += by;
|
|
231
370
|
this.persist(s);
|
|
@@ -242,14 +381,14 @@ export class SessionStore {
|
|
|
242
381
|
if (c.screenshots >= b.maxScreenshots)
|
|
243
382
|
return `screenshot budget reached (${c.screenshots}/${b.maxScreenshots})`;
|
|
244
383
|
if (c.noChangeActions >= b.maxNoChangeActions)
|
|
245
|
-
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`;
|
|
246
385
|
return null;
|
|
247
386
|
}
|
|
248
387
|
updateJob(s, job, patch) {
|
|
249
388
|
Object.assign(job, patch);
|
|
250
389
|
this.persist(s);
|
|
251
390
|
}
|
|
252
|
-
/** 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
|
|
253
392
|
* terminal) job is never overwritten back to done/failed by a racing worker. */
|
|
254
393
|
updateJobIfRunning(s, job, patch) {
|
|
255
394
|
const cur = s.jobs.get(job.jobId);
|
|
@@ -278,7 +417,7 @@ export class SessionStore {
|
|
|
278
417
|
s.envChanges.push(`${new Date().toISOString()} ${note}`);
|
|
279
418
|
this.persist(s);
|
|
280
419
|
}
|
|
281
|
-
/** Record a safe fallback Swipium chose
|
|
420
|
+
/** Record a safe fallback Swipium chose. De-duped. */
|
|
282
421
|
addWorkaround(s, note) {
|
|
283
422
|
if (!s.workarounds.includes(note)) {
|
|
284
423
|
s.workarounds.push(note);
|
|
@@ -290,15 +429,43 @@ export class SessionStore {
|
|
|
290
429
|
this.persist(s);
|
|
291
430
|
}
|
|
292
431
|
recordMutation(s, mutation) {
|
|
293
|
-
|
|
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 } : {}) };
|
|
294
441
|
s.mutations.push(rec);
|
|
295
442
|
if (s.mutations.length > 500)
|
|
296
443
|
s.mutations.splice(0, s.mutations.length - 500);
|
|
297
|
-
this.persistNow(s); // the mutation ledger is an audit trail
|
|
444
|
+
this.persistNow(s); // the mutation ledger is an audit trail; never leave it debounce-only
|
|
298
445
|
return rec;
|
|
299
446
|
}
|
|
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
|
+
}
|
|
300
457
|
/** Append an action-IR step (bounded) for qa_generate target:"flow". */
|
|
301
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
|
+
}
|
|
302
469
|
s.recordedActions.push(ra);
|
|
303
470
|
if (s.recordedActions.length > 300)
|
|
304
471
|
s.recordedActions.splice(0, s.recordedActions.length - 300);
|
|
@@ -324,7 +491,9 @@ export class SessionStore {
|
|
|
324
491
|
this.persist(s);
|
|
325
492
|
}
|
|
326
493
|
/** Store a user-provided input (hardening P0.5). The VALUE stays in-memory only; secrets are
|
|
327
|
-
* 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). */
|
|
328
497
|
setInput(s, varName, value, secret, source) {
|
|
329
498
|
s.inputValues.set(varName, value);
|
|
330
499
|
if (secret)
|
|
@@ -340,7 +509,7 @@ export class SessionStore {
|
|
|
340
509
|
}
|
|
341
510
|
this.persist(s);
|
|
342
511
|
}
|
|
343
|
-
/** Variable map (varName
|
|
512
|
+
/** Variable map (varName > value) for the flow runner. In-memory values only. */
|
|
344
513
|
inputVariables(s) {
|
|
345
514
|
return Object.fromEntries(s.inputValues);
|
|
346
515
|
}
|
|
@@ -348,22 +517,37 @@ export class SessionStore {
|
|
|
348
517
|
clearInputValues(s) {
|
|
349
518
|
s.inputValues.clear();
|
|
350
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
|
+
}
|
|
351
525
|
/** Record the latest guided-exploration result (Phase 3.3) for qa_report. */
|
|
352
526
|
setExploration(s, rec) {
|
|
353
527
|
s.exploration = rec;
|
|
354
528
|
this.persist(s);
|
|
355
529
|
}
|
|
356
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);
|
|
357
534
|
const sub = join(s.dir, artifactDirectoryName(kind));
|
|
358
|
-
mkdirSync(sub, { recursive: true });
|
|
359
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);
|
|
360
540
|
const redact = makeRedactor(s.secrets);
|
|
361
541
|
const isRedactableText = typeof data === 'string' && isTextArtifactMime(mime);
|
|
362
|
-
|
|
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;
|
|
363
545
|
const storedLabel = label ? redact(label) : label;
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
|
367
551
|
// consumers (qa_get_artifact, reports) know pixels may show sensitive on-screen content.
|
|
368
552
|
s.artifacts.push({
|
|
369
553
|
uri,
|
|
@@ -372,7 +556,12 @@ export class SessionStore {
|
|
|
372
556
|
kind,
|
|
373
557
|
createdAt: Date.now(),
|
|
374
558
|
label: storedLabel,
|
|
375
|
-
redaction: isRedactableText ? 'applied' : '
|
|
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
|
+
: {}),
|
|
376
565
|
});
|
|
377
566
|
this.persist(s);
|
|
378
567
|
return uri;
|
|
@@ -384,9 +573,30 @@ export class SessionStore {
|
|
|
384
573
|
if (rec)
|
|
385
574
|
return { session: s, rec };
|
|
386
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
|
+
}
|
|
387
584
|
return undefined;
|
|
388
585
|
}
|
|
389
|
-
|
|
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) ----
|
|
390
600
|
loadRegistry() {
|
|
391
601
|
try {
|
|
392
602
|
if (!existsSync(REGISTRY))
|
|
@@ -409,17 +619,26 @@ export class SessionStore {
|
|
|
409
619
|
}
|
|
410
620
|
}
|
|
411
621
|
// Orphaned-process handling: a prior server persisted its Metro pid. If no live
|
|
412
|
-
// server instance owns that pid,
|
|
413
|
-
//
|
|
414
|
-
//
|
|
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.
|
|
415
627
|
if (typeof st.metroPid === 'number' && st.metroPid > 0) {
|
|
416
628
|
if (!pidOwnedByLiveServer(st.metroPid)) {
|
|
417
629
|
const outcome = reclaimPid(st.metroPid, 'metro');
|
|
418
630
|
if (outcome === 'killed')
|
|
419
631
|
log('warn', 'reaped orphaned Metro bundler from a previous server run', { pid: st.metroPid, sessionId: st.id });
|
|
420
632
|
}
|
|
421
|
-
st.metroPid = undefined; // reaped, gone, recycled, or another live server's
|
|
633
|
+
st.metroPid = undefined; // reaped, gone, recycled, or another live server's; never ours
|
|
422
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;
|
|
423
642
|
const s = {
|
|
424
643
|
id: st.id,
|
|
425
644
|
root: st.root,
|
|
@@ -433,6 +652,10 @@ export class SessionStore {
|
|
|
433
652
|
network: st.network,
|
|
434
653
|
envChanges: st.envChanges ?? [],
|
|
435
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,
|
|
436
659
|
mode: st.mode ?? 'structured',
|
|
437
660
|
responseMode: st.responseMode ?? DEFAULT_RESPONSE_MODE,
|
|
438
661
|
sensitive: st.sensitive ?? false,
|
|
@@ -444,11 +667,17 @@ export class SessionStore {
|
|
|
444
667
|
notes: st.notes ?? [],
|
|
445
668
|
mutations: st.mutations ?? [],
|
|
446
669
|
recordedActions: st.recordedActions ?? [],
|
|
447
|
-
|
|
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),
|
|
448
676
|
auth: st.auth ?? {},
|
|
449
677
|
milestones: st.milestones ?? { session_start: st.createdAt },
|
|
450
678
|
budgetProfile: st.budgetProfile,
|
|
451
679
|
secrets: new Set(),
|
|
680
|
+
redactionDegraded: priorSecrets || undefined,
|
|
452
681
|
inputs: st.inputs ?? [],
|
|
453
682
|
generatedValues: st.generatedValues ?? [],
|
|
454
683
|
inputValues: new Map(),
|
|
@@ -458,13 +687,13 @@ export class SessionStore {
|
|
|
458
687
|
this.sessions.set(s.id, s);
|
|
459
688
|
}
|
|
460
689
|
catch (e) {
|
|
461
|
-
// Skip a corrupt session, but say so
|
|
690
|
+
// Skip a corrupt session, but say so, otherwise prior artifacts/reports vanish silently.
|
|
462
691
|
log('warn', 'skipping corrupt prior session state.json on reload', { dir, err: String(e) });
|
|
463
692
|
}
|
|
464
693
|
}
|
|
465
694
|
}
|
|
466
695
|
catch (e) {
|
|
467
|
-
log('warn', 'session registry unreadable
|
|
696
|
+
log('warn', 'session registry unreadable, prior sessions will not be reloaded', { registry: REGISTRY, err: String(e) });
|
|
468
697
|
}
|
|
469
698
|
}
|
|
470
699
|
/** Append this session to the shared ~/.swipium/registry.json. Guarded by an advisory
|
|
@@ -482,20 +711,18 @@ export class SessionStore {
|
|
|
482
711
|
reg = parsed;
|
|
483
712
|
}
|
|
484
713
|
catch (e) {
|
|
485
|
-
log('warn', 'registry.json corrupt
|
|
714
|
+
log('warn', 'registry.json corrupt, rebuilding it (prior sessions may need re-registration)', {
|
|
486
715
|
registry: REGISTRY,
|
|
487
716
|
err: String(e),
|
|
488
717
|
});
|
|
489
718
|
}
|
|
490
719
|
}
|
|
491
720
|
reg.push({ id, dir });
|
|
492
|
-
|
|
493
|
-
writeFileSync(tmp, JSON.stringify(reg.slice(-200), null, 2));
|
|
494
|
-
renameSync(tmp, REGISTRY); // atomic on the same filesystem
|
|
721
|
+
writeFileAtomicSync(REGISTRY, JSON.stringify(reg.slice(-200), null, 2)); // atomic on the same filesystem
|
|
495
722
|
});
|
|
496
723
|
}
|
|
497
724
|
catch (e) {
|
|
498
|
-
log('error', 'failed to append session to registry
|
|
725
|
+
log('error', 'failed to append session to registry; it will not be reloadable after a restart', { id, dir, err: String(e) });
|
|
499
726
|
}
|
|
500
727
|
}
|
|
501
728
|
}
|