swipium 1.3.0 → 1.5.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 +142 -0
- package/README.md +89 -25
- package/THREAT_MODEL.md +1 -1
- package/dist/appMap/automationLink.js +13 -3
- package/dist/appMap/automationLink.js.map +1 -1
- package/dist/appMap/build.js +28 -6
- package/dist/appMap/build.js.map +1 -1
- package/dist/appMap/codeIndex.js +29 -3
- package/dist/appMap/codeIndex.js.map +1 -1
- package/dist/appMap/featureIndex.js +20 -2
- package/dist/appMap/featureIndex.js.map +1 -1
- package/dist/appMap/featureModel.js +42 -6
- package/dist/appMap/featureModel.js.map +1 -1
- package/dist/appMap/firstRunApply.js +22 -4
- 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 +0 -20
- package/dist/appMap/issues.js.map +1 -1
- package/dist/appMap/migrations.js +1 -1
- package/dist/appMap/migrations.js.map +1 -1
- package/dist/appMap/prelaunch.js.map +1 -1
- package/dist/appMap/projectRegistry.js +8 -2
- package/dist/appMap/projectRegistry.js.map +1 -1
- package/dist/appMap/query.js +20 -4
- package/dist/appMap/query.js.map +1 -1
- package/dist/appMap/runtimeMerge.js +9 -2
- package/dist/appMap/runtimeMerge.js.map +1 -1
- package/dist/appMap/schema.js.map +1 -1
- package/dist/appMap/screenMatch.js +5 -1
- package/dist/appMap/screenMatch.js.map +1 -1
- package/dist/appMap/staticScan.js +134 -22
- package/dist/appMap/staticScan.js.map +1 -1
- package/dist/appMap/store.js +4 -79
- package/dist/appMap/store.js.map +1 -1
- package/dist/appMap/tsAstScan.js +31 -9
- package/dist/appMap/tsAstScan.js.map +1 -1
- package/dist/artifacts/bundletool.js +44 -7
- package/dist/artifacts/bundletool.js.map +1 -1
- package/dist/artifacts/resolve.js +33 -6
- package/dist/artifacts/resolve.js.map +1 -1
- package/dist/automation/capabilities.js +109 -27
- package/dist/automation/capabilities.js.map +1 -1
- package/dist/automation/gestures.js +5 -1
- package/dist/automation/gestures.js.map +1 -1
- package/dist/automation/plan.js +65 -17
- package/dist/automation/plan.js.map +1 -1
- package/dist/automation/report.js +12 -2
- package/dist/automation/report.js.map +1 -1
- package/dist/automation/selectors.js +8 -3
- package/dist/automation/selectors.js.map +1 -1
- package/dist/automation/types.js.map +1 -1
- package/dist/automation/waits.js +9 -3
- package/dist/automation/waits.js.map +1 -1
- package/dist/automation/webview.js +7 -1
- package/dist/automation/webview.js.map +1 -1
- package/dist/automationGen/appiumModel.js +3 -1
- package/dist/automationGen/appiumModel.js.map +1 -1
- package/dist/automationGen/ciEmitter.js +1 -1
- package/dist/automationGen/ciEmitter.js.map +1 -1
- package/dist/automationGen/jsEmitter.js +29 -21
- package/dist/automationGen/jsEmitter.js.map +1 -1
- package/dist/automationGen/projectProfile.js +5 -1
- package/dist/automationGen/projectProfile.js.map +1 -1
- package/dist/automationGen/pythonEmitter.js +3 -3
- package/dist/automationGen/pythonEmitter.js.map +1 -1
- package/dist/automationGen/readmeEmitter.js.map +1 -1
- package/dist/automationGen/suitePlan.js +11 -6
- package/dist/automationGen/suitePlan.js.map +1 -1
- package/dist/automationGen/validation.js +33 -5
- package/dist/automationGen/validation.js.map +1 -1
- package/dist/build/parseBuildLog.js +234 -30
- package/dist/build/parseBuildLog.js.map +1 -1
- package/dist/build/plan.js +33 -12
- package/dist/build/plan.js.map +1 -1
- package/dist/ci/preflight.js.map +1 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/scan.js +1 -1
- package/dist/cli/scan.js.map +1 -1
- package/dist/cli/suite.js +2 -2
- package/dist/cli/suite.js.map +1 -1
- package/dist/cli/verify.js.map +1 -1
- package/dist/context/detect.js +10 -4
- package/dist/context/detect.js.map +1 -1
- package/dist/context/findApps.js +16 -2
- package/dist/context/findApps.js.map +1 -1
- package/dist/context/scan.js.map +1 -1
- package/dist/core/target.js +7 -5
- package/dist/core/target.js.map +1 -1
- package/dist/core/targetPlan.js +135 -17
- package/dist/core/targetPlan.js.map +1 -1
- package/dist/drivers/DirectDriver.js +17 -7
- package/dist/drivers/DirectDriver.js.map +1 -1
- package/dist/drivers/SimctlDriver.js +4 -1
- package/dist/drivers/SimctlDriver.js.map +1 -1
- package/dist/drivers/WdaDriver.js +15 -5
- package/dist/drivers/WdaDriver.js.map +1 -1
- package/dist/explore/candidates.js +5 -1
- package/dist/explore/candidates.js.map +1 -1
- package/dist/explore/graph.js +11 -2
- 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 +6 -1
- package/dist/explore/policy.js.map +1 -1
- package/dist/explore/runner.js +179 -23
- package/dist/explore/runner.js.map +1 -1
- package/dist/explore/signatures.js +3 -1
- 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 +128 -19
- package/dist/featureTesting/executionBootstrap.js.map +1 -1
- package/dist/featureTesting/featureMap.js +4 -1
- package/dist/featureTesting/featureMap.js.map +1 -1
- package/dist/featureTesting/featureScope.js +94 -13
- package/dist/featureTesting/featureScope.js.map +1 -1
- package/dist/featureTesting/mapFeatureScope.js +37 -8
- package/dist/featureTesting/mapFeatureScope.js.map +1 -1
- package/dist/featureTesting/objectiveModel.js +5 -1
- package/dist/featureTesting/objectiveModel.js.map +1 -1
- package/dist/featureTesting/resultMerge.js +10 -2
- package/dist/featureTesting/resultMerge.js.map +1 -1
- package/dist/featureTesting/sources.js +4 -1
- package/dist/featureTesting/sources.js.map +1 -1
- package/dist/featureTesting/suiteBridge.js +7 -1
- package/dist/featureTesting/suiteBridge.js.map +1 -1
- package/dist/featureTesting/synonyms.js +31 -3
- package/dist/featureTesting/synonyms.js.map +1 -1
- package/dist/featureTesting/testCaseFactory.js +16 -4
- package/dist/featureTesting/testCaseFactory.js.map +1 -1
- package/dist/featureTesting/testPlan.js +32 -10
- package/dist/featureTesting/testPlan.js.map +1 -1
- package/dist/firstRun/classifyScreen.js +4 -1
- package/dist/firstRun/classifyScreen.js.map +1 -1
- package/dist/firstRun/firstRunPlanner.js +5 -2
- package/dist/firstRun/firstRunPlanner.js.map +1 -1
- package/dist/firstRun/firstRunRunner.js +84 -23
- package/dist/firstRun/firstRunRunner.js.map +1 -1
- package/dist/firstRun/generatedDataPolicy.js +16 -6
- package/dist/firstRun/generatedDataPolicy.js.map +1 -1
- package/dist/firstRun/inputPlanner.js +40 -6
- package/dist/firstRun/inputPlanner.js.map +1 -1
- package/dist/firstRun/paywallPolicy.js.map +1 -1
- package/dist/fixtures/catalog.js +39 -6
- package/dist/fixtures/catalog.js.map +1 -1
- package/dist/flows/generate.js +53 -12
- 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 +18 -2
- package/dist/flows/pack.js.map +1 -1
- package/dist/flows/repair.js +71 -20
- package/dist/flows/repair.js.map +1 -1
- package/dist/flows/run.js +164 -51
- package/dist/flows/run.js.map +1 -1
- package/dist/flows/schema.js +57 -20
- package/dist/flows/schema.js.map +1 -1
- package/dist/flows/seedExec.js +5 -1
- package/dist/flows/seedExec.js.map +1 -1
- package/dist/ios/signing.js +32 -3
- package/dist/ios/signing.js.map +1 -1
- package/dist/issues/classify.js +12 -4
- package/dist/issues/classify.js.map +1 -1
- package/dist/issues/fingerprint.js +11 -5
- package/dist/issues/fingerprint.js.map +1 -1
- package/dist/issues/index.js +4 -20
- package/dist/issues/index.js.map +1 -1
- package/dist/issues/metrics.js +2 -1
- package/dist/issues/metrics.js.map +1 -1
- package/dist/issues/recurrence.js.map +1 -1
- package/dist/issues/report.js +8 -4
- package/dist/issues/report.js.map +1 -1
- package/dist/issues/reportBridge.js +36 -5
- package/dist/issues/reportBridge.js.map +1 -1
- package/dist/issues/schema.js.map +1 -1
- package/dist/issues/store.js.map +1 -1
- package/dist/lib/android.js +9 -1
- package/dist/lib/android.js.map +1 -1
- package/dist/lib/device.js +21 -5
- package/dist/lib/device.js.map +1 -1
- package/dist/lib/image.js +11 -2
- package/dist/lib/image.js.map +1 -1
- package/dist/lib/lockfile.js +60 -0
- package/dist/lib/lockfile.js.map +1 -0
- package/dist/lib/needsInput.js +6 -3
- package/dist/lib/needsInput.js.map +1 -1
- package/dist/lib/png.js +2 -1
- package/dist/lib/png.js.map +1 -1
- package/dist/lib/result.js +16 -1
- package/dist/lib/result.js.map +1 -1
- package/dist/lib/schemaHash.js +4 -2
- package/dist/lib/schemaHash.js.map +1 -1
- package/dist/lib/sensitive.js +3 -1
- package/dist/lib/sensitive.js.map +1 -1
- package/dist/lib/simctl.js.map +1 -1
- package/dist/lib/spawn.js +29 -10
- package/dist/lib/spawn.js.map +1 -1
- package/dist/lib/wda.js +27 -10
- 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 +141 -19
- package/dist/mobileAudit/checks.js.map +1 -1
- package/dist/mobileAudit/profiles.js +120 -19
- package/dist/mobileAudit/profiles.js.map +1 -1
- package/dist/mobileAudit/results.js +7 -2
- package/dist/mobileAudit/results.js.map +1 -1
- package/dist/mobileAudit/runner.js +144 -19
- package/dist/mobileAudit/runner.js.map +1 -1
- package/dist/oracle/auth.js.map +1 -1
- package/dist/oracle/failures.js +888 -131
- package/dist/oracle/failures.js.map +1 -1
- package/dist/oracle/health.js +90 -14
- package/dist/oracle/health.js.map +1 -1
- package/dist/oracle/locator.js +147 -18
- package/dist/oracle/locator.js.map +1 -1
- package/dist/oracle/record.js.map +1 -1
- package/dist/orchestration/goal.js +3 -9
- package/dist/orchestration/goal.js.map +1 -1
- package/dist/orchestration/testThis/execute.js +126 -0
- package/dist/orchestration/testThis/execute.js.map +1 -0
- package/dist/orchestration/testThis/pipeline.js +324 -0
- package/dist/orchestration/testThis/pipeline.js.map +1 -0
- package/dist/orchestration/testThis/plan.js +430 -0
- package/dist/orchestration/testThis/plan.js.map +1 -0
- package/dist/orchestration/testThis/terminal.js +118 -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.map +1 -1
- package/dist/prompts/index.js +3 -3
- 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 +59 -37
- package/dist/report/export.js.map +1 -1
- 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 +11 -3
- package/dist/report/policy.js.map +1 -1
- package/dist/report/qaLevel.js +2 -8
- 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/summary.js.map +1 -1
- package/dist/report/timing.js +23 -23
- package/dist/report/timing.js.map +1 -1
- package/dist/server.js +42 -37
- package/dist/server.js.map +1 -1
- package/dist/services/automationGenerate.js.map +1 -1
- package/dist/services/build.js +8 -2
- package/dist/services/build.js.map +1 -1
- package/dist/services/preflight.js +38 -6
- package/dist/services/preflight.js.map +1 -1
- package/dist/services/prepareAndroid.js +46 -10
- package/dist/services/prepareAndroid.js.map +1 -1
- package/dist/services/prepareIos.js +72 -10
- package/dist/services/prepareIos.js.map +1 -1
- package/dist/services/report.js +173 -37
- package/dist/services/report.js.map +1 -1
- package/dist/services/smoke.js +36 -6
- package/dist/services/smoke.js.map +1 -1
- package/dist/services/suiteGenerate.js +16 -4
- package/dist/services/suiteGenerate.js.map +1 -1
- package/dist/services/testSuiteKnowledge.js +38 -6
- package/dist/services/testSuiteKnowledge.js.map +1 -1
- package/dist/session/attach.js +16 -0
- package/dist/session/attach.js.map +1 -1
- package/dist/session/processRegistry.js +164 -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/store.js +216 -43
- package/dist/session/store.js.map +1 -1
- package/dist/snapshot/overlays.js +7 -2
- package/dist/snapshot/overlays.js.map +1 -1
- package/dist/snapshot/parse.js +2 -8
- package/dist/snapshot/parse.js.map +1 -1
- package/dist/snapshot/present.js +27 -3
- package/dist/snapshot/present.js.map +1 -1
- package/dist/state/profile.js +36 -9
- package/dist/state/profile.js.map +1 -1
- package/dist/suite/compile.js +7 -2
- package/dist/suite/compile.js.map +1 -1
- package/dist/suite/lint.js +21 -3
- package/dist/suite/lint.js.map +1 -1
- package/dist/suite/pom.js +26 -4
- package/dist/suite/pom.js.map +1 -1
- package/dist/suite/testcase.js +6 -4
- package/dist/suite/testcase.js.map +1 -1
- package/dist/testSuite/exporter.js +6 -2
- package/dist/testSuite/exporter.js.map +1 -1
- package/dist/testSuite/generator.js +31 -6
- package/dist/testSuite/generator.js.map +1 -1
- package/dist/testSuite/issueLinks.js.map +1 -1
- package/dist/testSuite/lint.js +1 -1
- package/dist/testSuite/lint.js.map +1 -1
- package/dist/testSuite/merge.js +8 -2
- package/dist/testSuite/merge.js.map +1 -1
- package/dist/testSuite/schema.js +16 -11
- package/dist/testSuite/schema.js.map +1 -1
- package/dist/testSuite/store.js +9 -1
- package/dist/testSuite/store.js.map +1 -1
- package/dist/tools/act.js +107 -43
- package/dist/tools/act.js.map +1 -1
- package/dist/tools/agent.js +123 -25
- package/dist/tools/agent.js.map +1 -1
- package/dist/tools/appControl.js +16 -3
- package/dist/tools/appControl.js.map +1 -1
- package/dist/tools/appMap.js +331 -63
- package/dist/tools/appMap.js.map +1 -1
- package/dist/tools/assertVisual.js +12 -2
- package/dist/tools/assertVisual.js.map +1 -1
- package/dist/tools/automationGenerate.js +276 -241
- package/dist/tools/automationGenerate.js.map +1 -1
- package/dist/tools/build.js +92 -41
- package/dist/tools/build.js.map +1 -1
- package/dist/tools/bundletool.js +139 -28
- package/dist/tools/bundletool.js.map +1 -1
- package/dist/tools/capabilities.js +45 -71
- package/dist/tools/capabilities.js.map +1 -1
- package/dist/tools/clearOverlay.js +45 -8
- package/dist/tools/clearOverlay.js.map +1 -1
- package/dist/tools/detectContext.js +1 -1
- package/dist/tools/detectContext.js.map +1 -1
- package/dist/tools/device.js +44 -7
- package/dist/tools/device.js.map +1 -1
- package/dist/tools/doctor.js +38 -15
- package/dist/tools/doctor.js.map +1 -1
- package/dist/tools/explore.js +96 -22
- package/dist/tools/explore.js.map +1 -1
- package/dist/tools/featureTesting.js +153 -114
- package/dist/tools/featureTesting.js.map +1 -1
- package/dist/tools/firstRun.js +95 -60
- package/dist/tools/firstRun.js.map +1 -1
- package/dist/tools/flow.js +236 -101
- package/dist/tools/flow.js.map +1 -1
- package/dist/tools/flowGenerate.js +51 -47
- package/dist/tools/flowGenerate.js.map +1 -1
- package/dist/tools/flowRepair.js +24 -4
- package/dist/tools/flowRepair.js.map +1 -1
- package/dist/tools/generate.js +236 -0
- package/dist/tools/generate.js.map +1 -0
- package/dist/tools/getArtifact.js +21 -3
- package/dist/tools/getArtifact.js.map +1 -1
- package/dist/tools/health.js +3 -1
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/history.js +40 -10
- package/dist/tools/history.js.map +1 -1
- package/dist/tools/ios.js +157 -25
- package/dist/tools/ios.js.map +1 -1
- package/dist/tools/issues.js +19 -150
- package/dist/tools/issues.js.map +1 -1
- package/dist/tools/jobs.js +23 -4
- package/dist/tools/jobs.js.map +1 -1
- package/dist/tools/locator.js +36 -8
- package/dist/tools/locator.js.map +1 -1
- package/dist/tools/metro.js +61 -16
- package/dist/tools/metro.js.map +1 -1
- package/dist/tools/mobileAudit.js +67 -15
- package/dist/tools/mobileAudit.js.map +1 -1
- package/dist/tools/network.js +6 -1
- package/dist/tools/network.js.map +1 -1
- package/dist/tools/note.js +20 -6
- package/dist/tools/note.js.map +1 -1
- package/dist/tools/permissions.js +31 -6
- package/dist/tools/permissions.js.map +1 -1
- package/dist/tools/plan.js +5 -2
- package/dist/tools/plan.js.map +1 -1
- package/dist/tools/prepareIosTarget.js +31 -8
- package/dist/tools/prepareIosTarget.js.map +1 -1
- package/dist/tools/prepareTarget.js +97 -18
- package/dist/tools/prepareTarget.js.map +1 -1
- package/dist/tools/report.js +24 -5
- package/dist/tools/report.js.map +1 -1
- package/dist/tools/resolveArtifact.js +9 -5
- package/dist/tools/resolveArtifact.js.map +1 -1
- package/dist/tools/resolveTarget.js +11 -2
- package/dist/tools/resolveTarget.js.map +1 -1
- package/dist/tools/screenInfo.js +8 -2
- package/dist/tools/screenInfo.js.map +1 -1
- package/dist/tools/screenRecord.js +74 -11
- package/dist/tools/screenRecord.js.map +1 -1
- package/dist/tools/screenshot.js +23 -6
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/seed.js +38 -6
- package/dist/tools/seed.js.map +1 -1
- package/dist/tools/smoke.js +16 -3
- package/dist/tools/smoke.js.map +1 -1
- package/dist/tools/snapshot.js +59 -6
- package/dist/tools/snapshot.js.map +1 -1
- package/dist/tools/startSession.js +54 -9
- package/dist/tools/startSession.js.map +1 -1
- package/dist/tools/state.js +62 -15
- package/dist/tools/state.js.map +1 -1
- package/dist/tools/suite.js +350 -298
- package/dist/tools/suite.js.map +1 -1
- package/dist/tools/testSuite.js +94 -28
- package/dist/tools/testSuite.js.map +1 -1
- package/dist/tools/testThis.js +30 -793
- package/dist/tools/testThis.js.map +1 -1
- package/dist/tools/visual.js +124 -18
- package/dist/tools/visual.js.map +1 -1
- package/dist/tools/visualText.js +51 -6
- package/dist/tools/visualText.js.map +1 -1
- package/dist/tools/wait.js +18 -3
- package/dist/tools/wait.js.map +1 -1
- package/dist/tools/wda.js +157 -29
- package/dist/tools/wda.js.map +1 -1
- package/dist/version.js +17 -49
- package/dist/version.js.map +1 -1
- package/dist/visual/ocr.js +12 -2
- package/dist/visual/ocr.js.map +1 -1
- package/dist/visual/provider.js +7 -3
- package/dist/visual/provider.js.map +1 -1
- package/docs/mcp-server.md +44 -4
- package/docs/tools.md +42 -100
- package/package.json +14 -5
- 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/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/maestro.js +0 -78
- package/dist/tools/maestro.js.map +0 -1
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// Cross-restart registry of the long-lived child processes Swipium spawns (Metro bundler,
|
|
2
|
+
// managed WDA xcodebuild, screen recorders, emulators) in ~/.swipium/processes.json, so a
|
|
3
|
+
// crashed server's orphans can be reaped on the next startup (P0 §2 "orphaned processes").
|
|
4
|
+
//
|
|
5
|
+
// Safety rules:
|
|
6
|
+
// - Every entry records the OWNING server pid. A child whose owner is still a live
|
|
7
|
+
// node/swipium process belongs to a concurrent server instance and is never touched.
|
|
8
|
+
// - Before signalling, the child's command line is re-checked via `ps` so a PID recycled
|
|
9
|
+
// by the OS to an unrelated process is never killed.
|
|
10
|
+
// - Emulators are ADOPTED, not killed — an orphaned emulator stays booted and remains
|
|
11
|
+
// usable via adb for the next run.
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { homedir } from 'node:os';
|
|
15
|
+
import { spawnSync } from 'node:child_process';
|
|
16
|
+
import { withFileLock } from '../lib/lockfile.js';
|
|
17
|
+
import { log } from '../lib/logger.js';
|
|
18
|
+
const REGISTRY_DIR = join(homedir(), '.swipium');
|
|
19
|
+
const PROCESSES_FILE = join(REGISTRY_DIR, 'processes.json');
|
|
20
|
+
const PROCESSES_LOCK = `${PROCESSES_FILE}.lock`;
|
|
21
|
+
const MAX_ENTRIES = 100;
|
|
22
|
+
/** What the child's `ps` command line must look like before we dare signal it. */
|
|
23
|
+
const KIND_COMMAND_RE = {
|
|
24
|
+
metro: /metro|expo|react-native|npx|node/i,
|
|
25
|
+
wda: /xcodebuild/i,
|
|
26
|
+
recording: /screenrecord|recordvideo|simctl|adb/i,
|
|
27
|
+
emulator: /emulator|qemu/i,
|
|
28
|
+
};
|
|
29
|
+
function pidAlive(pid) {
|
|
30
|
+
try {
|
|
31
|
+
process.kill(pid, 0);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** The live command line for `pid`, or null when it is gone / unreadable (POSIX `ps`). */
|
|
39
|
+
function psCommand(pid) {
|
|
40
|
+
try {
|
|
41
|
+
const out = spawnSync('ps', ['-o', 'command=', '-p', String(pid)], { encoding: 'utf8' });
|
|
42
|
+
if (out.status !== 0 || !out.stdout?.trim())
|
|
43
|
+
return null;
|
|
44
|
+
return out.stdout.trim();
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function readEntries() {
|
|
51
|
+
try {
|
|
52
|
+
if (!existsSync(PROCESSES_FILE))
|
|
53
|
+
return [];
|
|
54
|
+
const parsed = JSON.parse(readFileSync(PROCESSES_FILE, 'utf8'));
|
|
55
|
+
if (!Array.isArray(parsed))
|
|
56
|
+
return [];
|
|
57
|
+
return parsed.filter((e) => typeof e?.pid === 'number');
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
log('warn', 'managed-process registry unreadable — starting fresh', { file: PROCESSES_FILE, err: String(e) });
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function writeEntries(entries) {
|
|
65
|
+
const tmp = `${PROCESSES_FILE}.${process.pid}.tmp`;
|
|
66
|
+
writeFileSync(tmp, JSON.stringify(entries.slice(-MAX_ENTRIES), null, 2));
|
|
67
|
+
renameSync(tmp, PROCESSES_FILE); // atomic on the same filesystem
|
|
68
|
+
}
|
|
69
|
+
function mutateEntries(fn) {
|
|
70
|
+
try {
|
|
71
|
+
mkdirSync(REGISTRY_DIR, { recursive: true });
|
|
72
|
+
withFileLock(PROCESSES_LOCK, () => writeEntries(fn(readEntries())));
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
log('error', 'failed to update managed-process registry — a crash may leave this child unreaped', {
|
|
76
|
+
file: PROCESSES_FILE,
|
|
77
|
+
err: String(e),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Record a long-lived child we spawned so a future server instance can reap it if we crash. */
|
|
82
|
+
export function registerManagedProcess(pid, kind, sessionId) {
|
|
83
|
+
if (!pid || pid <= 0)
|
|
84
|
+
return;
|
|
85
|
+
mutateEntries((entries) => [
|
|
86
|
+
...entries.filter((e) => e.pid !== pid),
|
|
87
|
+
{ pid, kind, serverPid: process.pid, sessionId, startedAt: Date.now() },
|
|
88
|
+
]);
|
|
89
|
+
}
|
|
90
|
+
/** Remove a child we stopped (or that finished) from the registry. */
|
|
91
|
+
export function unregisterManagedProcess(pid) {
|
|
92
|
+
if (!pid || pid <= 0)
|
|
93
|
+
return;
|
|
94
|
+
mutateEntries((entries) => entries.filter((e) => e.pid !== pid));
|
|
95
|
+
}
|
|
96
|
+
/** SIGTERM the child's process group (detached children lead their own group), else the pid. */
|
|
97
|
+
function killTree(pid) {
|
|
98
|
+
try {
|
|
99
|
+
process.kill(-pid, 'SIGTERM');
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
/* not a group leader, or group already gone — fall back to the pid itself */
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
process.kill(pid, 'SIGTERM');
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** True when `serverPid` is a live process that plausibly IS a Swipium/node server. The `ps`
|
|
114
|
+
* check guards against an OS-recycled server pid making us "adopt" a real orphan forever. */
|
|
115
|
+
function serverStillAlive(serverPid) {
|
|
116
|
+
if (serverPid === process.pid)
|
|
117
|
+
return false; // our pid at startup = a recycled dead server's
|
|
118
|
+
if (!pidAlive(serverPid))
|
|
119
|
+
return false;
|
|
120
|
+
const cmd = psCommand(serverPid);
|
|
121
|
+
return cmd != null && /node|swipium/i.test(cmd);
|
|
122
|
+
}
|
|
123
|
+
/** Is this child pid registered to a DIFFERENT, still-live server instance? (adopt, don't touch) */
|
|
124
|
+
export function pidOwnedByLiveServer(pid) {
|
|
125
|
+
const entry = readEntries().find((e) => e.pid === pid);
|
|
126
|
+
return !!entry && serverStillAlive(entry.serverPid);
|
|
127
|
+
}
|
|
128
|
+
/** Verify (via `ps`) that `pid` still runs a command matching `kind`, then kill or adopt it.
|
|
129
|
+
* Never signals a pid whose command no longer matches — that pid was recycled by the OS. */
|
|
130
|
+
export function reclaimPid(pid, kind) {
|
|
131
|
+
if (!pidAlive(pid))
|
|
132
|
+
return 'gone';
|
|
133
|
+
const cmd = psCommand(pid);
|
|
134
|
+
if (!cmd || !KIND_COMMAND_RE[kind].test(cmd))
|
|
135
|
+
return 'recycled';
|
|
136
|
+
if (kind === 'emulator')
|
|
137
|
+
return 'adopted'; // still a real emulator — leave it booted (usable via adb)
|
|
138
|
+
return killTree(pid) ? 'killed' : 'gone';
|
|
139
|
+
}
|
|
140
|
+
/** Startup sweep (called once from startServer): reap children whose owning server died. */
|
|
141
|
+
export function reapOrphanedProcesses() {
|
|
142
|
+
mutateEntries((entries) => {
|
|
143
|
+
const keep = [];
|
|
144
|
+
for (const e of entries) {
|
|
145
|
+
if (serverStillAlive(e.serverPid)) {
|
|
146
|
+
keep.push(e); // a live concurrent server owns it — not ours to touch
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const outcome = reclaimPid(e.pid, e.kind);
|
|
150
|
+
if (outcome === 'killed') {
|
|
151
|
+
log('warn', 'reaped orphaned child process from a previous server run', { pid: e.pid, kind: e.kind, sessionId: e.sessionId });
|
|
152
|
+
}
|
|
153
|
+
else if (outcome === 'adopted') {
|
|
154
|
+
log('info', 'adopted orphaned emulator (left booted; reachable via adb)', { pid: e.pid, sessionId: e.sessionId });
|
|
155
|
+
}
|
|
156
|
+
else if (outcome === 'recycled') {
|
|
157
|
+
log('info', 'dropped orphan entry: PID was recycled by an unrelated process — not signalled', { pid: e.pid, kind: e.kind });
|
|
158
|
+
}
|
|
159
|
+
// In every non-live-owner case the entry is dropped: it has been handled.
|
|
160
|
+
}
|
|
161
|
+
return keep;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=processRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"processRegistry.js","sourceRoot":"","sources":["../../src/session/processRegistry.ts"],"names":[],"mappings":"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"}
|
package/dist/session/progress.js
CHANGED
|
@@ -51,6 +51,6 @@ export function progressLine(p) {
|
|
|
51
51
|
if (!p)
|
|
52
52
|
return undefined;
|
|
53
53
|
const sec = Math.round((p.updatedAt - p.startedAt) / 1000);
|
|
54
|
-
return `${p.phase} (${sec}s)${p.userActionRequired ? ' ⚠ needs you' : ''}: ${p.statusText}` + (p.nextExpected ? ` → ${p.nextExpected}` : '');
|
|
54
|
+
return (`${p.phase} (${sec}s)${p.userActionRequired ? ' ⚠ needs you' : ''}: ${p.statusText}` + (p.nextExpected ? ` → ${p.nextExpected}` : ''));
|
|
55
55
|
}
|
|
56
56
|
//# sourceMappingURL=progress.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"progress.js","sourceRoot":"","sources":["../../src/session/progress.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,gGAAgG;AAChG,gGAAgG;AAChG,gGAAgG;AAahG,MAAM,UAAU,aAAa,CAC3B,QAAsB,EACtB,OAAgB,EAChB,GAAc,EACd,KAAa,EACb,OAAuE,EAAE,UAAU,EAAE,KAAK,EAAE;IAE5F,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,KAAK,GAAkB;QAC3B,KAAK;QACL,SAAS,EAAE,GAAG;QACd,SAAS,EAAE,GAAG;QACd,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,kBAAkB,EAAE,KAAK;KAC1B,CAAC;IACF,MAAM,KAAK,GAAG,GAAG,EAAE;QACjB,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,QAAQ,CAAC,kBAAkB,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,UAAU,EAAE,cAAc,EAAE,EAAE,GAAG,KAAK,EAAE,EAAE,CAAC,CAAC;IAC1G,CAAC,CAAC;IACF,KAAK,EAAE,CAAC;IACR,OAAO;QACL,UAAU,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;QACnE,KAAK,CAAC,SAAS,EAAE,IAAI;YACnB,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC;YAC5B,IAAI,IAAI,EAAE,UAAU;gBAAE,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;YACzD,IAAI,IAAI,EAAE,YAAY;gBAAE,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;YAC/D,KAAK,EAAE,CAAC;QACV,CAAC;QACD,MAAM,CAAC,MAAM;YACX,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;YACtB,KAAK,EAAE,CAAC;QACV,CAAC;QACD,SAAS,CAAC,UAAU;YAClB,KAAK,CAAC,kBAAkB,GAAG,IAAI,CAAC;YAChC,KAAK,CAAC,UAAU,GAAG,UAAU,CAAC;YAC9B,KAAK,EAAE,CAAC;QACV,CAAC;QACD,IAAI,CAAC,UAAU;YACb,KAAK,CAAC,kBAAkB,GAAG,KAAK,CAAC;YACjC,IAAI,UAAU;gBAAE,KAAK,CAAC,UAAU,GAAG,UAAU,CAAC;YAC9C,KAAK,CAAC,YAAY,GAAG,SAAS,CAAC;YAC/B,KAAK,EAAE,CAAC;QACV,CAAC;KACF,CAAC;AACJ,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,YAAY,CAAC,CAA4B;IACvD,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACzB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3D,OAAO,GAAG,CAAC,CAAC,KAAK,KAAK,GAAG,KAAK,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"progress.js","sourceRoot":"","sources":["../../src/session/progress.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,gGAAgG;AAChG,gGAAgG;AAChG,gGAAgG;AAahG,MAAM,UAAU,aAAa,CAC3B,QAAsB,EACtB,OAAgB,EAChB,GAAc,EACd,KAAa,EACb,OAAuE,EAAE,UAAU,EAAE,KAAK,EAAE;IAE5F,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,KAAK,GAAkB;QAC3B,KAAK;QACL,SAAS,EAAE,GAAG;QACd,SAAS,EAAE,GAAG;QACd,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,kBAAkB,EAAE,KAAK;KAC1B,CAAC;IACF,MAAM,KAAK,GAAG,GAAG,EAAE;QACjB,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,QAAQ,CAAC,kBAAkB,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,UAAU,EAAE,cAAc,EAAE,EAAE,GAAG,KAAK,EAAE,EAAE,CAAC,CAAC;IAC1G,CAAC,CAAC;IACF,KAAK,EAAE,CAAC;IACR,OAAO;QACL,UAAU,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;QACnE,KAAK,CAAC,SAAS,EAAE,IAAI;YACnB,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC;YAC5B,IAAI,IAAI,EAAE,UAAU;gBAAE,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;YACzD,IAAI,IAAI,EAAE,YAAY;gBAAE,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;YAC/D,KAAK,EAAE,CAAC;QACV,CAAC;QACD,MAAM,CAAC,MAAM;YACX,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;YACtB,KAAK,EAAE,CAAC;QACV,CAAC;QACD,SAAS,CAAC,UAAU;YAClB,KAAK,CAAC,kBAAkB,GAAG,IAAI,CAAC;YAChC,KAAK,CAAC,UAAU,GAAG,UAAU,CAAC;YAC9B,KAAK,EAAE,CAAC;QACV,CAAC;QACD,IAAI,CAAC,UAAU;YACb,KAAK,CAAC,kBAAkB,GAAG,KAAK,CAAC;YACjC,IAAI,UAAU;gBAAE,KAAK,CAAC,UAAU,GAAG,UAAU,CAAC;YAC9C,KAAK,CAAC,YAAY,GAAG,SAAS,CAAC;YAC/B,KAAK,EAAE,CAAC;QACV,CAAC;KACF,CAAC;AACJ,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,YAAY,CAAC,CAA4B;IACvD,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACzB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3D,OAAO,CACL,GAAG,CAAC,CAAC,KAAK,KAAK,GAAG,KAAK,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CACtI,CAAC;AACJ,CAAC"}
|
package/dist/session/store.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
// Session + job + artifact store with DISK PERSISTENCE (DESIGN §3, M6).
|
|
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';
|
|
10
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync, renameSync } from 'node:fs';
|
|
9
11
|
import { join, resolve } from 'node:path';
|
|
10
12
|
import { homedir } from 'node:os';
|
|
11
13
|
import { DEFAULT_RESPONSE_MODE } from '../lib/result.js';
|
|
12
14
|
import { makeRedactor } from '../lib/redact.js';
|
|
15
|
+
import { withFileLock } from '../lib/lockfile.js';
|
|
16
|
+
import { log } from '../lib/logger.js';
|
|
17
|
+
import { pidOwnedByLiveServer, reclaimPid } from './processRegistry.js';
|
|
13
18
|
// Phase 2.2 P1.6: budget classes (minutes) keyed by workflow ambition.
|
|
14
19
|
export const BUDGET_PROFILES = {
|
|
15
20
|
guardrail: 8, // guardrail/setup validation
|
|
@@ -40,8 +45,10 @@ export function isTextArtifactMime(mime) {
|
|
|
40
45
|
}
|
|
41
46
|
export function artifactDirectoryName(kind) {
|
|
42
47
|
switch (kind) {
|
|
43
|
-
case 'screenshot':
|
|
44
|
-
|
|
48
|
+
case 'screenshot':
|
|
49
|
+
return 'screenshots';
|
|
50
|
+
case 'recording':
|
|
51
|
+
return 'videos';
|
|
45
52
|
case 'logs':
|
|
46
53
|
case 'logcat':
|
|
47
54
|
case 'metro':
|
|
@@ -55,56 +62,152 @@ function defaultSessionDir(root, id) {
|
|
|
55
62
|
const projectHash = createHash('sha256').update(resolve(root)).digest('hex').slice(0, 16);
|
|
56
63
|
return join(REGISTRY_DIR, 'runs', projectHash, id);
|
|
57
64
|
}
|
|
65
|
+
/** Trailing debounce for state.json writes (P2 write amplification): every mutator used to
|
|
66
|
+
* rewrite the full file synchronously. Correctness-critical writes (session creation, the
|
|
67
|
+
* mutation ledger, shutdown) still flush synchronously via persistNow()/flushAll(). */
|
|
68
|
+
const PERSIST_DEBOUNCE_MS = 150;
|
|
58
69
|
export class SessionStore {
|
|
59
70
|
sessions = new Map();
|
|
60
|
-
|
|
71
|
+
// Lazy prior-session loading (P2 startup cost): the registry of up to 200 prior sessions is
|
|
72
|
+
// read on first access (get/list/find/create), not in the constructor.
|
|
73
|
+
registryLoaded = false;
|
|
74
|
+
dirty = new Set();
|
|
75
|
+
flushTimer;
|
|
76
|
+
ensureRegistryLoaded() {
|
|
77
|
+
if (this.registryLoaded)
|
|
78
|
+
return;
|
|
79
|
+
this.registryLoaded = true;
|
|
61
80
|
this.loadRegistry();
|
|
62
81
|
}
|
|
63
82
|
create(root, budget, opts) {
|
|
83
|
+
this.ensureRegistryLoaded();
|
|
64
84
|
const id = randomUUID().slice(0, 8);
|
|
65
85
|
const dir = opts?.sessionDir ?? defaultSessionDir(root, id);
|
|
66
86
|
mkdirSync(dir, { recursive: true });
|
|
67
87
|
const now = Date.now();
|
|
68
88
|
const s = {
|
|
69
|
-
id,
|
|
89
|
+
id,
|
|
90
|
+
root,
|
|
91
|
+
dir,
|
|
92
|
+
createdAt: now,
|
|
93
|
+
screenshotCount: 0,
|
|
70
94
|
mode: 'structured',
|
|
71
95
|
responseMode: opts?.responseMode ?? DEFAULT_RESPONSE_MODE,
|
|
72
96
|
sensitive: opts?.sensitive ?? false,
|
|
73
97
|
budget: { ...DEFAULT_BUDGET, ...(budget ?? {}) },
|
|
74
98
|
counters: { actions: 0, screenshots: 0, snapshotFailures: 0, noChangeActions: 0 },
|
|
75
|
-
envChanges: [],
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
99
|
+
envChanges: [],
|
|
100
|
+
workarounds: [],
|
|
101
|
+
jobs: new Map(),
|
|
102
|
+
artifacts: [],
|
|
103
|
+
findings: [],
|
|
104
|
+
notes: [],
|
|
105
|
+
mutations: [],
|
|
106
|
+
recordedActions: [],
|
|
107
|
+
fixtures: opts?.fixtures ?? [],
|
|
108
|
+
auth: {},
|
|
109
|
+
milestones: { session_start: now },
|
|
110
|
+
budgetProfile: opts?.budgetProfile,
|
|
111
|
+
secrets: new Set(),
|
|
112
|
+
inputs: [],
|
|
113
|
+
generatedValues: [],
|
|
114
|
+
inputValues: new Map(),
|
|
115
|
+
aborts: new Map(),
|
|
79
116
|
};
|
|
80
117
|
this.sessions.set(id, s);
|
|
81
118
|
this.appendRegistry(id, dir);
|
|
82
|
-
this.
|
|
119
|
+
this.persistNow(s); // synchronous: a concurrent instance / crash must see the new session
|
|
83
120
|
return s;
|
|
84
121
|
}
|
|
85
122
|
get(id) {
|
|
123
|
+
this.ensureRegistryLoaded();
|
|
86
124
|
return this.sessions.get(id);
|
|
87
125
|
}
|
|
88
126
|
list() {
|
|
127
|
+
this.ensureRegistryLoaded();
|
|
89
128
|
return [...this.sessions.values()];
|
|
90
129
|
}
|
|
130
|
+
/** Schedule a state.json write (trailing debounce). Mutators call this on every change;
|
|
131
|
+
* the actual write happens at most once per PERSIST_DEBOUNCE_MS per burst. Use
|
|
132
|
+
* persistNow()/flushAll() where a synchronous write is required for correctness. */
|
|
91
133
|
persist(s) {
|
|
134
|
+
this.dirty.add(s);
|
|
135
|
+
if (this.flushTimer)
|
|
136
|
+
return;
|
|
137
|
+
this.flushTimer = setTimeout(() => this.flushDirty(), PERSIST_DEBOUNCE_MS);
|
|
138
|
+
this.flushTimer.unref?.(); // never keep the process alive just for a pending flush
|
|
139
|
+
}
|
|
140
|
+
/** Write this session's state.json immediately (and drop any pending debounce for it). */
|
|
141
|
+
persistNow(s) {
|
|
142
|
+
this.dirty.delete(s);
|
|
143
|
+
this.writeState(s);
|
|
144
|
+
}
|
|
145
|
+
/** Flush every dirty session synchronously. Called on shutdown / process exit so a graceful
|
|
146
|
+
* stop never loses debounced state. */
|
|
147
|
+
flushAll() {
|
|
148
|
+
if (this.flushTimer) {
|
|
149
|
+
clearTimeout(this.flushTimer);
|
|
150
|
+
this.flushTimer = undefined;
|
|
151
|
+
}
|
|
152
|
+
for (const s of [...this.dirty]) {
|
|
153
|
+
this.dirty.delete(s);
|
|
154
|
+
this.writeState(s);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
flushDirty() {
|
|
158
|
+
this.flushTimer = undefined;
|
|
159
|
+
for (const s of [...this.dirty]) {
|
|
160
|
+
this.dirty.delete(s);
|
|
161
|
+
this.writeState(s);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
writeState(s) {
|
|
92
165
|
try {
|
|
93
166
|
const state = {
|
|
94
|
-
id: s.id,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
167
|
+
id: s.id,
|
|
168
|
+
root: s.root,
|
|
169
|
+
dir: s.dir,
|
|
170
|
+
createdAt: s.createdAt,
|
|
171
|
+
device: s.device,
|
|
172
|
+
appId: s.appId,
|
|
173
|
+
headless: s.headless,
|
|
174
|
+
metroPid: s.metroPid,
|
|
175
|
+
screenshotCount: s.screenshotCount,
|
|
176
|
+
network: s.network,
|
|
177
|
+
envChanges: s.envChanges,
|
|
178
|
+
workarounds: s.workarounds,
|
|
179
|
+
mode: s.mode,
|
|
180
|
+
responseMode: s.responseMode,
|
|
181
|
+
sensitive: s.sensitive,
|
|
182
|
+
budget: s.budget,
|
|
183
|
+
counters: s.counters,
|
|
184
|
+
jobs: [...s.jobs.values()],
|
|
185
|
+
artifacts: s.artifacts,
|
|
186
|
+
findings: s.findings,
|
|
187
|
+
notes: s.notes,
|
|
188
|
+
mutations: s.mutations,
|
|
189
|
+
recordedActions: s.recordedActions,
|
|
190
|
+
fixtures: s.fixtures,
|
|
191
|
+
auth: s.auth,
|
|
192
|
+
milestones: s.milestones,
|
|
193
|
+
budgetProfile: s.budgetProfile,
|
|
100
194
|
inputs: s.inputs, // METADATA only — never the values
|
|
101
195
|
generatedValues: serializeGeneratedValues(s.generatedValues), // secret raw values redacted before disk
|
|
102
196
|
exploration: s.exploration,
|
|
103
197
|
};
|
|
104
|
-
|
|
198
|
+
// Atomic write (tmp + rename) so a crash mid-write never leaves a truncated state.json.
|
|
199
|
+
const target = join(s.dir, 'state.json');
|
|
200
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
201
|
+
writeFileSync(tmp, JSON.stringify(state, null, 2));
|
|
202
|
+
renameSync(tmp, target);
|
|
105
203
|
}
|
|
106
|
-
catch {
|
|
107
|
-
|
|
204
|
+
catch (e) {
|
|
205
|
+
// A silent failure here means session state (jobs/artifacts/findings) is lost on restart.
|
|
206
|
+
log('error', 'failed to persist session state.json — this session will not survive a server restart', {
|
|
207
|
+
sessionId: s.id,
|
|
208
|
+
dir: s.dir,
|
|
209
|
+
err: String(e),
|
|
210
|
+
});
|
|
108
211
|
}
|
|
109
212
|
}
|
|
110
213
|
// ---- jobs ----
|
|
@@ -191,10 +294,10 @@ export class SessionStore {
|
|
|
191
294
|
s.mutations.push(rec);
|
|
192
295
|
if (s.mutations.length > 500)
|
|
193
296
|
s.mutations.splice(0, s.mutations.length - 500);
|
|
194
|
-
this.
|
|
297
|
+
this.persistNow(s); // the mutation ledger is an audit trail — never leave it debounce-only
|
|
195
298
|
return rec;
|
|
196
299
|
}
|
|
197
|
-
/** Append an action-IR step (bounded) for
|
|
300
|
+
/** Append an action-IR step (bounded) for qa_generate target:"flow". */
|
|
198
301
|
addRecordedAction(s, ra) {
|
|
199
302
|
s.recordedActions.push(ra);
|
|
200
303
|
if (s.recordedActions.length > 300)
|
|
@@ -255,15 +358,27 @@ export class SessionStore {
|
|
|
255
358
|
mkdirSync(sub, { recursive: true });
|
|
256
359
|
const path = join(sub, name);
|
|
257
360
|
const redact = makeRedactor(s.secrets);
|
|
258
|
-
const
|
|
361
|
+
const isRedactableText = typeof data === 'string' && isTextArtifactMime(mime);
|
|
362
|
+
const storedData = isRedactableText ? (redact(data) ?? '') : data;
|
|
259
363
|
const storedLabel = label ? redact(label) : label;
|
|
260
364
|
writeFileSync(path, storedData);
|
|
261
365
|
const uri = `swipium://session/${s.id}/${kind}/${name}`;
|
|
262
|
-
|
|
366
|
+
// Binary artifacts (screenshots/recordings) cannot be redacted — tag them explicitly so
|
|
367
|
+
// consumers (qa_get_artifact, reports) know pixels may show sensitive on-screen content.
|
|
368
|
+
s.artifacts.push({
|
|
369
|
+
uri,
|
|
370
|
+
path,
|
|
371
|
+
mime,
|
|
372
|
+
kind,
|
|
373
|
+
createdAt: Date.now(),
|
|
374
|
+
label: storedLabel,
|
|
375
|
+
redaction: isRedactableText ? 'applied' : 'not-applied',
|
|
376
|
+
});
|
|
263
377
|
this.persist(s);
|
|
264
378
|
return uri;
|
|
265
379
|
}
|
|
266
380
|
findArtifact(uri) {
|
|
381
|
+
this.ensureRegistryLoaded();
|
|
267
382
|
for (const s of this.sessions.values()) {
|
|
268
383
|
const rec = s.artifacts.find((a) => a.uri === uri);
|
|
269
384
|
if (rec)
|
|
@@ -271,7 +386,7 @@ export class SessionStore {
|
|
|
271
386
|
}
|
|
272
387
|
return undefined;
|
|
273
388
|
}
|
|
274
|
-
// ---- persistence reload ----
|
|
389
|
+
// ---- persistence reload (lazy — triggered by the first get/list/find/create) ----
|
|
275
390
|
loadRegistry() {
|
|
276
391
|
try {
|
|
277
392
|
if (!existsSync(REGISTRY))
|
|
@@ -283,6 +398,9 @@ export class SessionStore {
|
|
|
283
398
|
continue;
|
|
284
399
|
try {
|
|
285
400
|
const st = JSON.parse(readFileSync(sp, 'utf8'));
|
|
401
|
+
// Never clobber a LIVE session created by this process (lazy load can run after create()).
|
|
402
|
+
if (typeof st.id === 'string' && this.sessions.has(st.id))
|
|
403
|
+
continue;
|
|
286
404
|
const jobs = new Map((st.jobs ?? []).map((j) => [j.jobId, j]));
|
|
287
405
|
for (const j of jobs.values()) {
|
|
288
406
|
if (j.status === 'running') {
|
|
@@ -290,39 +408,94 @@ export class SessionStore {
|
|
|
290
408
|
j.error = 'server restarted while job was running (child process gone)';
|
|
291
409
|
}
|
|
292
410
|
}
|
|
411
|
+
// Orphaned-process handling: a prior server persisted its Metro pid. If no live
|
|
412
|
+
// server instance owns that pid, verify via `ps` that it still IS a bundler-ish
|
|
413
|
+
// process and reap it; a recycled pid is never signalled. Either way the reloaded
|
|
414
|
+
// session drops the pid — it is not ours to manage (or stop) anymore.
|
|
415
|
+
if (typeof st.metroPid === 'number' && st.metroPid > 0) {
|
|
416
|
+
if (!pidOwnedByLiveServer(st.metroPid)) {
|
|
417
|
+
const outcome = reclaimPid(st.metroPid, 'metro');
|
|
418
|
+
if (outcome === 'killed')
|
|
419
|
+
log('warn', 'reaped orphaned Metro bundler from a previous server run', { pid: st.metroPid, sessionId: st.id });
|
|
420
|
+
}
|
|
421
|
+
st.metroPid = undefined; // reaped, gone, recycled, or another live server's — never ours
|
|
422
|
+
}
|
|
293
423
|
const s = {
|
|
294
|
-
id: st.id,
|
|
295
|
-
|
|
296
|
-
|
|
424
|
+
id: st.id,
|
|
425
|
+
root: st.root,
|
|
426
|
+
dir: st.dir,
|
|
427
|
+
createdAt: st.createdAt,
|
|
428
|
+
device: st.device,
|
|
429
|
+
appId: st.appId,
|
|
430
|
+
headless: st.headless,
|
|
431
|
+
metroPid: st.metroPid,
|
|
432
|
+
screenshotCount: st.screenshotCount ?? 0,
|
|
433
|
+
network: st.network,
|
|
434
|
+
envChanges: st.envChanges ?? [],
|
|
435
|
+
workarounds: st.workarounds ?? [],
|
|
297
436
|
mode: st.mode ?? 'structured',
|
|
298
437
|
responseMode: st.responseMode ?? DEFAULT_RESPONSE_MODE,
|
|
299
438
|
sensitive: st.sensitive ?? false,
|
|
300
439
|
budget: { ...DEFAULT_BUDGET, ...(st.budget ?? {}) },
|
|
301
440
|
counters: { actions: 0, screenshots: 0, snapshotFailures: 0, noChangeActions: 0, ...(st.counters ?? {}) },
|
|
302
|
-
jobs,
|
|
303
|
-
|
|
304
|
-
|
|
441
|
+
jobs,
|
|
442
|
+
artifacts: st.artifacts ?? [],
|
|
443
|
+
findings: st.findings ?? [],
|
|
444
|
+
notes: st.notes ?? [],
|
|
445
|
+
mutations: st.mutations ?? [],
|
|
446
|
+
recordedActions: st.recordedActions ?? [],
|
|
447
|
+
fixtures: st.fixtures ?? [],
|
|
448
|
+
auth: st.auth ?? {},
|
|
449
|
+
milestones: st.milestones ?? { session_start: st.createdAt },
|
|
450
|
+
budgetProfile: st.budgetProfile,
|
|
451
|
+
secrets: new Set(),
|
|
452
|
+
inputs: st.inputs ?? [],
|
|
453
|
+
generatedValues: st.generatedValues ?? [],
|
|
454
|
+
inputValues: new Map(),
|
|
455
|
+
exploration: st.exploration,
|
|
456
|
+
aborts: new Map(),
|
|
305
457
|
};
|
|
306
458
|
this.sessions.set(s.id, s);
|
|
307
459
|
}
|
|
308
|
-
catch {
|
|
309
|
-
|
|
460
|
+
catch (e) {
|
|
461
|
+
// Skip a corrupt session, but say so — otherwise prior artifacts/reports vanish silently.
|
|
462
|
+
log('warn', 'skipping corrupt prior session state.json on reload', { dir, err: String(e) });
|
|
310
463
|
}
|
|
311
464
|
}
|
|
312
465
|
}
|
|
313
|
-
catch {
|
|
314
|
-
|
|
466
|
+
catch (e) {
|
|
467
|
+
log('warn', 'session registry unreadable — prior sessions will not be reloaded', { registry: REGISTRY, err: String(e) });
|
|
315
468
|
}
|
|
316
469
|
}
|
|
470
|
+
/** Append this session to the shared ~/.swipium/registry.json. Guarded by an advisory
|
|
471
|
+
* lockfile + atomic rename so two concurrent server instances (common with multiple MCP
|
|
472
|
+
* clients) never clobber each other's read-modify-write. */
|
|
317
473
|
appendRegistry(id, dir) {
|
|
318
474
|
try {
|
|
319
475
|
mkdirSync(REGISTRY_DIR, { recursive: true });
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
476
|
+
withFileLock(`${REGISTRY}.lock`, () => {
|
|
477
|
+
let reg = [];
|
|
478
|
+
if (existsSync(REGISTRY)) {
|
|
479
|
+
try {
|
|
480
|
+
const parsed = JSON.parse(readFileSync(REGISTRY, 'utf8'));
|
|
481
|
+
if (Array.isArray(parsed))
|
|
482
|
+
reg = parsed;
|
|
483
|
+
}
|
|
484
|
+
catch (e) {
|
|
485
|
+
log('warn', 'registry.json corrupt — rebuilding it (prior sessions may need re-registration)', {
|
|
486
|
+
registry: REGISTRY,
|
|
487
|
+
err: String(e),
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
reg.push({ id, dir });
|
|
492
|
+
const tmp = `${REGISTRY}.${process.pid}.tmp`;
|
|
493
|
+
writeFileSync(tmp, JSON.stringify(reg.slice(-200), null, 2));
|
|
494
|
+
renameSync(tmp, REGISTRY); // atomic on the same filesystem
|
|
495
|
+
});
|
|
323
496
|
}
|
|
324
|
-
catch {
|
|
325
|
-
|
|
497
|
+
catch (e) {
|
|
498
|
+
log('error', 'failed to append session to registry — it will not be reloadable after a restart', { id, dir, err: String(e) });
|
|
326
499
|
}
|
|
327
500
|
}
|
|
328
501
|
}
|