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.
Files changed (438) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/README.md +89 -25
  3. package/THREAT_MODEL.md +1 -1
  4. package/dist/appMap/automationLink.js +13 -3
  5. package/dist/appMap/automationLink.js.map +1 -1
  6. package/dist/appMap/build.js +28 -6
  7. package/dist/appMap/build.js.map +1 -1
  8. package/dist/appMap/codeIndex.js +29 -3
  9. package/dist/appMap/codeIndex.js.map +1 -1
  10. package/dist/appMap/featureIndex.js +20 -2
  11. package/dist/appMap/featureIndex.js.map +1 -1
  12. package/dist/appMap/featureModel.js +42 -6
  13. package/dist/appMap/featureModel.js.map +1 -1
  14. package/dist/appMap/firstRunApply.js +22 -4
  15. package/dist/appMap/firstRunApply.js.map +1 -1
  16. package/dist/appMap/fsWalk.js +19 -2
  17. package/dist/appMap/fsWalk.js.map +1 -1
  18. package/dist/appMap/issues.js +0 -20
  19. package/dist/appMap/issues.js.map +1 -1
  20. package/dist/appMap/migrations.js +1 -1
  21. package/dist/appMap/migrations.js.map +1 -1
  22. package/dist/appMap/prelaunch.js.map +1 -1
  23. package/dist/appMap/projectRegistry.js +8 -2
  24. package/dist/appMap/projectRegistry.js.map +1 -1
  25. package/dist/appMap/query.js +20 -4
  26. package/dist/appMap/query.js.map +1 -1
  27. package/dist/appMap/runtimeMerge.js +9 -2
  28. package/dist/appMap/runtimeMerge.js.map +1 -1
  29. package/dist/appMap/schema.js.map +1 -1
  30. package/dist/appMap/screenMatch.js +5 -1
  31. package/dist/appMap/screenMatch.js.map +1 -1
  32. package/dist/appMap/staticScan.js +134 -22
  33. package/dist/appMap/staticScan.js.map +1 -1
  34. package/dist/appMap/store.js +4 -79
  35. package/dist/appMap/store.js.map +1 -1
  36. package/dist/appMap/tsAstScan.js +31 -9
  37. package/dist/appMap/tsAstScan.js.map +1 -1
  38. package/dist/artifacts/bundletool.js +44 -7
  39. package/dist/artifacts/bundletool.js.map +1 -1
  40. package/dist/artifacts/resolve.js +33 -6
  41. package/dist/artifacts/resolve.js.map +1 -1
  42. package/dist/automation/capabilities.js +109 -27
  43. package/dist/automation/capabilities.js.map +1 -1
  44. package/dist/automation/gestures.js +5 -1
  45. package/dist/automation/gestures.js.map +1 -1
  46. package/dist/automation/plan.js +65 -17
  47. package/dist/automation/plan.js.map +1 -1
  48. package/dist/automation/report.js +12 -2
  49. package/dist/automation/report.js.map +1 -1
  50. package/dist/automation/selectors.js +8 -3
  51. package/dist/automation/selectors.js.map +1 -1
  52. package/dist/automation/types.js.map +1 -1
  53. package/dist/automation/waits.js +9 -3
  54. package/dist/automation/waits.js.map +1 -1
  55. package/dist/automation/webview.js +7 -1
  56. package/dist/automation/webview.js.map +1 -1
  57. package/dist/automationGen/appiumModel.js +3 -1
  58. package/dist/automationGen/appiumModel.js.map +1 -1
  59. package/dist/automationGen/ciEmitter.js +1 -1
  60. package/dist/automationGen/ciEmitter.js.map +1 -1
  61. package/dist/automationGen/jsEmitter.js +29 -21
  62. package/dist/automationGen/jsEmitter.js.map +1 -1
  63. package/dist/automationGen/projectProfile.js +5 -1
  64. package/dist/automationGen/projectProfile.js.map +1 -1
  65. package/dist/automationGen/pythonEmitter.js +3 -3
  66. package/dist/automationGen/pythonEmitter.js.map +1 -1
  67. package/dist/automationGen/readmeEmitter.js.map +1 -1
  68. package/dist/automationGen/suitePlan.js +11 -6
  69. package/dist/automationGen/suitePlan.js.map +1 -1
  70. package/dist/automationGen/validation.js +33 -5
  71. package/dist/automationGen/validation.js.map +1 -1
  72. package/dist/build/parseBuildLog.js +234 -30
  73. package/dist/build/parseBuildLog.js.map +1 -1
  74. package/dist/build/plan.js +33 -12
  75. package/dist/build/plan.js.map +1 -1
  76. package/dist/ci/preflight.js.map +1 -1
  77. package/dist/cli/init.js.map +1 -1
  78. package/dist/cli/scan.js +1 -1
  79. package/dist/cli/scan.js.map +1 -1
  80. package/dist/cli/suite.js +2 -2
  81. package/dist/cli/suite.js.map +1 -1
  82. package/dist/cli/verify.js.map +1 -1
  83. package/dist/context/detect.js +10 -4
  84. package/dist/context/detect.js.map +1 -1
  85. package/dist/context/findApps.js +16 -2
  86. package/dist/context/findApps.js.map +1 -1
  87. package/dist/context/scan.js.map +1 -1
  88. package/dist/core/target.js +7 -5
  89. package/dist/core/target.js.map +1 -1
  90. package/dist/core/targetPlan.js +135 -17
  91. package/dist/core/targetPlan.js.map +1 -1
  92. package/dist/drivers/DirectDriver.js +17 -7
  93. package/dist/drivers/DirectDriver.js.map +1 -1
  94. package/dist/drivers/SimctlDriver.js +4 -1
  95. package/dist/drivers/SimctlDriver.js.map +1 -1
  96. package/dist/drivers/WdaDriver.js +15 -5
  97. package/dist/drivers/WdaDriver.js.map +1 -1
  98. package/dist/explore/candidates.js +5 -1
  99. package/dist/explore/candidates.js.map +1 -1
  100. package/dist/explore/graph.js +11 -2
  101. package/dist/explore/graph.js.map +1 -1
  102. package/dist/explore/memory.js +4 -1
  103. package/dist/explore/memory.js.map +1 -1
  104. package/dist/explore/planner.js +54 -9
  105. package/dist/explore/planner.js.map +1 -1
  106. package/dist/explore/policy.js +6 -1
  107. package/dist/explore/policy.js.map +1 -1
  108. package/dist/explore/runner.js +179 -23
  109. package/dist/explore/runner.js.map +1 -1
  110. package/dist/explore/signatures.js +3 -1
  111. package/dist/explore/signatures.js.map +1 -1
  112. package/dist/explore/suite.js +3 -1
  113. package/dist/explore/suite.js.map +1 -1
  114. package/dist/featureTesting/executionBootstrap.js +128 -19
  115. package/dist/featureTesting/executionBootstrap.js.map +1 -1
  116. package/dist/featureTesting/featureMap.js +4 -1
  117. package/dist/featureTesting/featureMap.js.map +1 -1
  118. package/dist/featureTesting/featureScope.js +94 -13
  119. package/dist/featureTesting/featureScope.js.map +1 -1
  120. package/dist/featureTesting/mapFeatureScope.js +37 -8
  121. package/dist/featureTesting/mapFeatureScope.js.map +1 -1
  122. package/dist/featureTesting/objectiveModel.js +5 -1
  123. package/dist/featureTesting/objectiveModel.js.map +1 -1
  124. package/dist/featureTesting/resultMerge.js +10 -2
  125. package/dist/featureTesting/resultMerge.js.map +1 -1
  126. package/dist/featureTesting/sources.js +4 -1
  127. package/dist/featureTesting/sources.js.map +1 -1
  128. package/dist/featureTesting/suiteBridge.js +7 -1
  129. package/dist/featureTesting/suiteBridge.js.map +1 -1
  130. package/dist/featureTesting/synonyms.js +31 -3
  131. package/dist/featureTesting/synonyms.js.map +1 -1
  132. package/dist/featureTesting/testCaseFactory.js +16 -4
  133. package/dist/featureTesting/testCaseFactory.js.map +1 -1
  134. package/dist/featureTesting/testPlan.js +32 -10
  135. package/dist/featureTesting/testPlan.js.map +1 -1
  136. package/dist/firstRun/classifyScreen.js +4 -1
  137. package/dist/firstRun/classifyScreen.js.map +1 -1
  138. package/dist/firstRun/firstRunPlanner.js +5 -2
  139. package/dist/firstRun/firstRunPlanner.js.map +1 -1
  140. package/dist/firstRun/firstRunRunner.js +84 -23
  141. package/dist/firstRun/firstRunRunner.js.map +1 -1
  142. package/dist/firstRun/generatedDataPolicy.js +16 -6
  143. package/dist/firstRun/generatedDataPolicy.js.map +1 -1
  144. package/dist/firstRun/inputPlanner.js +40 -6
  145. package/dist/firstRun/inputPlanner.js.map +1 -1
  146. package/dist/firstRun/paywallPolicy.js.map +1 -1
  147. package/dist/fixtures/catalog.js +39 -6
  148. package/dist/fixtures/catalog.js.map +1 -1
  149. package/dist/flows/generate.js +53 -12
  150. package/dist/flows/generate.js.map +1 -1
  151. package/dist/flows/lint.js +88 -14
  152. package/dist/flows/lint.js.map +1 -1
  153. package/dist/flows/pack.js +18 -2
  154. package/dist/flows/pack.js.map +1 -1
  155. package/dist/flows/repair.js +71 -20
  156. package/dist/flows/repair.js.map +1 -1
  157. package/dist/flows/run.js +164 -51
  158. package/dist/flows/run.js.map +1 -1
  159. package/dist/flows/schema.js +57 -20
  160. package/dist/flows/schema.js.map +1 -1
  161. package/dist/flows/seedExec.js +5 -1
  162. package/dist/flows/seedExec.js.map +1 -1
  163. package/dist/ios/signing.js +32 -3
  164. package/dist/ios/signing.js.map +1 -1
  165. package/dist/issues/classify.js +12 -4
  166. package/dist/issues/classify.js.map +1 -1
  167. package/dist/issues/fingerprint.js +11 -5
  168. package/dist/issues/fingerprint.js.map +1 -1
  169. package/dist/issues/index.js +4 -20
  170. package/dist/issues/index.js.map +1 -1
  171. package/dist/issues/metrics.js +2 -1
  172. package/dist/issues/metrics.js.map +1 -1
  173. package/dist/issues/recurrence.js.map +1 -1
  174. package/dist/issues/report.js +8 -4
  175. package/dist/issues/report.js.map +1 -1
  176. package/dist/issues/reportBridge.js +36 -5
  177. package/dist/issues/reportBridge.js.map +1 -1
  178. package/dist/issues/schema.js.map +1 -1
  179. package/dist/issues/store.js.map +1 -1
  180. package/dist/lib/android.js +9 -1
  181. package/dist/lib/android.js.map +1 -1
  182. package/dist/lib/device.js +21 -5
  183. package/dist/lib/device.js.map +1 -1
  184. package/dist/lib/image.js +11 -2
  185. package/dist/lib/image.js.map +1 -1
  186. package/dist/lib/lockfile.js +60 -0
  187. package/dist/lib/lockfile.js.map +1 -0
  188. package/dist/lib/needsInput.js +6 -3
  189. package/dist/lib/needsInput.js.map +1 -1
  190. package/dist/lib/png.js +2 -1
  191. package/dist/lib/png.js.map +1 -1
  192. package/dist/lib/result.js +16 -1
  193. package/dist/lib/result.js.map +1 -1
  194. package/dist/lib/schemaHash.js +4 -2
  195. package/dist/lib/schemaHash.js.map +1 -1
  196. package/dist/lib/sensitive.js +3 -1
  197. package/dist/lib/sensitive.js.map +1 -1
  198. package/dist/lib/simctl.js.map +1 -1
  199. package/dist/lib/spawn.js +29 -10
  200. package/dist/lib/spawn.js.map +1 -1
  201. package/dist/lib/wda.js +27 -10
  202. package/dist/lib/wda.js.map +1 -1
  203. package/dist/lib/wdaConfig.js +12 -5
  204. package/dist/lib/wdaConfig.js.map +1 -1
  205. package/dist/lib/wdaTune.js +52 -23
  206. package/dist/lib/wdaTune.js.map +1 -1
  207. package/dist/mobileAudit/checks.js +141 -19
  208. package/dist/mobileAudit/checks.js.map +1 -1
  209. package/dist/mobileAudit/profiles.js +120 -19
  210. package/dist/mobileAudit/profiles.js.map +1 -1
  211. package/dist/mobileAudit/results.js +7 -2
  212. package/dist/mobileAudit/results.js.map +1 -1
  213. package/dist/mobileAudit/runner.js +144 -19
  214. package/dist/mobileAudit/runner.js.map +1 -1
  215. package/dist/oracle/auth.js.map +1 -1
  216. package/dist/oracle/failures.js +888 -131
  217. package/dist/oracle/failures.js.map +1 -1
  218. package/dist/oracle/health.js +90 -14
  219. package/dist/oracle/health.js.map +1 -1
  220. package/dist/oracle/locator.js +147 -18
  221. package/dist/oracle/locator.js.map +1 -1
  222. package/dist/oracle/record.js.map +1 -1
  223. package/dist/orchestration/goal.js +3 -9
  224. package/dist/orchestration/goal.js.map +1 -1
  225. package/dist/orchestration/testThis/execute.js +126 -0
  226. package/dist/orchestration/testThis/execute.js.map +1 -0
  227. package/dist/orchestration/testThis/pipeline.js +324 -0
  228. package/dist/orchestration/testThis/pipeline.js.map +1 -0
  229. package/dist/orchestration/testThis/plan.js +430 -0
  230. package/dist/orchestration/testThis/plan.js.map +1 -0
  231. package/dist/orchestration/testThis/terminal.js +118 -0
  232. package/dist/orchestration/testThis/terminal.js.map +1 -0
  233. package/dist/orchestration/testThis/types.js +12 -0
  234. package/dist/orchestration/testThis/types.js.map +1 -0
  235. package/dist/plan/plan.js.map +1 -1
  236. package/dist/prompts/index.js +3 -3
  237. package/dist/prompts/index.js.map +1 -1
  238. package/dist/report/coverage.js +0 -1
  239. package/dist/report/coverage.js.map +1 -1
  240. package/dist/report/evidence.js.map +1 -1
  241. package/dist/report/export.js +59 -37
  242. package/dist/report/export.js.map +1 -1
  243. package/dist/report/flake.js +13 -14
  244. package/dist/report/flake.js.map +1 -1
  245. package/dist/report/history.js +72 -38
  246. package/dist/report/history.js.map +1 -1
  247. package/dist/report/policy.js +11 -3
  248. package/dist/report/policy.js.map +1 -1
  249. package/dist/report/qaLevel.js +2 -8
  250. package/dist/report/qaLevel.js.map +1 -1
  251. package/dist/report/readiness.js +42 -12
  252. package/dist/report/readiness.js.map +1 -1
  253. package/dist/report/summary.js.map +1 -1
  254. package/dist/report/timing.js +23 -23
  255. package/dist/report/timing.js.map +1 -1
  256. package/dist/server.js +42 -37
  257. package/dist/server.js.map +1 -1
  258. package/dist/services/automationGenerate.js.map +1 -1
  259. package/dist/services/build.js +8 -2
  260. package/dist/services/build.js.map +1 -1
  261. package/dist/services/preflight.js +38 -6
  262. package/dist/services/preflight.js.map +1 -1
  263. package/dist/services/prepareAndroid.js +46 -10
  264. package/dist/services/prepareAndroid.js.map +1 -1
  265. package/dist/services/prepareIos.js +72 -10
  266. package/dist/services/prepareIos.js.map +1 -1
  267. package/dist/services/report.js +173 -37
  268. package/dist/services/report.js.map +1 -1
  269. package/dist/services/smoke.js +36 -6
  270. package/dist/services/smoke.js.map +1 -1
  271. package/dist/services/suiteGenerate.js +16 -4
  272. package/dist/services/suiteGenerate.js.map +1 -1
  273. package/dist/services/testSuiteKnowledge.js +38 -6
  274. package/dist/services/testSuiteKnowledge.js.map +1 -1
  275. package/dist/session/attach.js +16 -0
  276. package/dist/session/attach.js.map +1 -1
  277. package/dist/session/processRegistry.js +164 -0
  278. package/dist/session/processRegistry.js.map +1 -0
  279. package/dist/session/progress.js +1 -1
  280. package/dist/session/progress.js.map +1 -1
  281. package/dist/session/store.js +216 -43
  282. package/dist/session/store.js.map +1 -1
  283. package/dist/snapshot/overlays.js +7 -2
  284. package/dist/snapshot/overlays.js.map +1 -1
  285. package/dist/snapshot/parse.js +2 -8
  286. package/dist/snapshot/parse.js.map +1 -1
  287. package/dist/snapshot/present.js +27 -3
  288. package/dist/snapshot/present.js.map +1 -1
  289. package/dist/state/profile.js +36 -9
  290. package/dist/state/profile.js.map +1 -1
  291. package/dist/suite/compile.js +7 -2
  292. package/dist/suite/compile.js.map +1 -1
  293. package/dist/suite/lint.js +21 -3
  294. package/dist/suite/lint.js.map +1 -1
  295. package/dist/suite/pom.js +26 -4
  296. package/dist/suite/pom.js.map +1 -1
  297. package/dist/suite/testcase.js +6 -4
  298. package/dist/suite/testcase.js.map +1 -1
  299. package/dist/testSuite/exporter.js +6 -2
  300. package/dist/testSuite/exporter.js.map +1 -1
  301. package/dist/testSuite/generator.js +31 -6
  302. package/dist/testSuite/generator.js.map +1 -1
  303. package/dist/testSuite/issueLinks.js.map +1 -1
  304. package/dist/testSuite/lint.js +1 -1
  305. package/dist/testSuite/lint.js.map +1 -1
  306. package/dist/testSuite/merge.js +8 -2
  307. package/dist/testSuite/merge.js.map +1 -1
  308. package/dist/testSuite/schema.js +16 -11
  309. package/dist/testSuite/schema.js.map +1 -1
  310. package/dist/testSuite/store.js +9 -1
  311. package/dist/testSuite/store.js.map +1 -1
  312. package/dist/tools/act.js +107 -43
  313. package/dist/tools/act.js.map +1 -1
  314. package/dist/tools/agent.js +123 -25
  315. package/dist/tools/agent.js.map +1 -1
  316. package/dist/tools/appControl.js +16 -3
  317. package/dist/tools/appControl.js.map +1 -1
  318. package/dist/tools/appMap.js +331 -63
  319. package/dist/tools/appMap.js.map +1 -1
  320. package/dist/tools/assertVisual.js +12 -2
  321. package/dist/tools/assertVisual.js.map +1 -1
  322. package/dist/tools/automationGenerate.js +276 -241
  323. package/dist/tools/automationGenerate.js.map +1 -1
  324. package/dist/tools/build.js +92 -41
  325. package/dist/tools/build.js.map +1 -1
  326. package/dist/tools/bundletool.js +139 -28
  327. package/dist/tools/bundletool.js.map +1 -1
  328. package/dist/tools/capabilities.js +45 -71
  329. package/dist/tools/capabilities.js.map +1 -1
  330. package/dist/tools/clearOverlay.js +45 -8
  331. package/dist/tools/clearOverlay.js.map +1 -1
  332. package/dist/tools/detectContext.js +1 -1
  333. package/dist/tools/detectContext.js.map +1 -1
  334. package/dist/tools/device.js +44 -7
  335. package/dist/tools/device.js.map +1 -1
  336. package/dist/tools/doctor.js +38 -15
  337. package/dist/tools/doctor.js.map +1 -1
  338. package/dist/tools/explore.js +96 -22
  339. package/dist/tools/explore.js.map +1 -1
  340. package/dist/tools/featureTesting.js +153 -114
  341. package/dist/tools/featureTesting.js.map +1 -1
  342. package/dist/tools/firstRun.js +95 -60
  343. package/dist/tools/firstRun.js.map +1 -1
  344. package/dist/tools/flow.js +236 -101
  345. package/dist/tools/flow.js.map +1 -1
  346. package/dist/tools/flowGenerate.js +51 -47
  347. package/dist/tools/flowGenerate.js.map +1 -1
  348. package/dist/tools/flowRepair.js +24 -4
  349. package/dist/tools/flowRepair.js.map +1 -1
  350. package/dist/tools/generate.js +236 -0
  351. package/dist/tools/generate.js.map +1 -0
  352. package/dist/tools/getArtifact.js +21 -3
  353. package/dist/tools/getArtifact.js.map +1 -1
  354. package/dist/tools/health.js +3 -1
  355. package/dist/tools/health.js.map +1 -1
  356. package/dist/tools/history.js +40 -10
  357. package/dist/tools/history.js.map +1 -1
  358. package/dist/tools/ios.js +157 -25
  359. package/dist/tools/ios.js.map +1 -1
  360. package/dist/tools/issues.js +19 -150
  361. package/dist/tools/issues.js.map +1 -1
  362. package/dist/tools/jobs.js +23 -4
  363. package/dist/tools/jobs.js.map +1 -1
  364. package/dist/tools/locator.js +36 -8
  365. package/dist/tools/locator.js.map +1 -1
  366. package/dist/tools/metro.js +61 -16
  367. package/dist/tools/metro.js.map +1 -1
  368. package/dist/tools/mobileAudit.js +67 -15
  369. package/dist/tools/mobileAudit.js.map +1 -1
  370. package/dist/tools/network.js +6 -1
  371. package/dist/tools/network.js.map +1 -1
  372. package/dist/tools/note.js +20 -6
  373. package/dist/tools/note.js.map +1 -1
  374. package/dist/tools/permissions.js +31 -6
  375. package/dist/tools/permissions.js.map +1 -1
  376. package/dist/tools/plan.js +5 -2
  377. package/dist/tools/plan.js.map +1 -1
  378. package/dist/tools/prepareIosTarget.js +31 -8
  379. package/dist/tools/prepareIosTarget.js.map +1 -1
  380. package/dist/tools/prepareTarget.js +97 -18
  381. package/dist/tools/prepareTarget.js.map +1 -1
  382. package/dist/tools/report.js +24 -5
  383. package/dist/tools/report.js.map +1 -1
  384. package/dist/tools/resolveArtifact.js +9 -5
  385. package/dist/tools/resolveArtifact.js.map +1 -1
  386. package/dist/tools/resolveTarget.js +11 -2
  387. package/dist/tools/resolveTarget.js.map +1 -1
  388. package/dist/tools/screenInfo.js +8 -2
  389. package/dist/tools/screenInfo.js.map +1 -1
  390. package/dist/tools/screenRecord.js +74 -11
  391. package/dist/tools/screenRecord.js.map +1 -1
  392. package/dist/tools/screenshot.js +23 -6
  393. package/dist/tools/screenshot.js.map +1 -1
  394. package/dist/tools/seed.js +38 -6
  395. package/dist/tools/seed.js.map +1 -1
  396. package/dist/tools/smoke.js +16 -3
  397. package/dist/tools/smoke.js.map +1 -1
  398. package/dist/tools/snapshot.js +59 -6
  399. package/dist/tools/snapshot.js.map +1 -1
  400. package/dist/tools/startSession.js +54 -9
  401. package/dist/tools/startSession.js.map +1 -1
  402. package/dist/tools/state.js +62 -15
  403. package/dist/tools/state.js.map +1 -1
  404. package/dist/tools/suite.js +350 -298
  405. package/dist/tools/suite.js.map +1 -1
  406. package/dist/tools/testSuite.js +94 -28
  407. package/dist/tools/testSuite.js.map +1 -1
  408. package/dist/tools/testThis.js +30 -793
  409. package/dist/tools/testThis.js.map +1 -1
  410. package/dist/tools/visual.js +124 -18
  411. package/dist/tools/visual.js.map +1 -1
  412. package/dist/tools/visualText.js +51 -6
  413. package/dist/tools/visualText.js.map +1 -1
  414. package/dist/tools/wait.js +18 -3
  415. package/dist/tools/wait.js.map +1 -1
  416. package/dist/tools/wda.js +157 -29
  417. package/dist/tools/wda.js.map +1 -1
  418. package/dist/version.js +17 -49
  419. package/dist/version.js.map +1 -1
  420. package/dist/visual/ocr.js +12 -2
  421. package/dist/visual/ocr.js.map +1 -1
  422. package/dist/visual/provider.js +7 -3
  423. package/dist/visual/provider.js.map +1 -1
  424. package/docs/mcp-server.md +44 -4
  425. package/docs/tools.md +42 -100
  426. package/package.json +14 -5
  427. package/dist/appMap/diff.js +0 -60
  428. package/dist/appMap/diff.js.map +0 -1
  429. package/dist/automation/maestroIr.js +0 -282
  430. package/dist/automation/maestroIr.js.map +0 -1
  431. package/dist/interop/maestro.js +0 -151
  432. package/dist/interop/maestro.js.map +0 -1
  433. package/dist/tools/idling.js +0 -104
  434. package/dist/tools/idling.js.map +0 -1
  435. package/dist/tools/inputCapabilities.js +0 -30
  436. package/dist/tools/inputCapabilities.js.map +0 -1
  437. package/dist/tools/maestro.js +0 -78
  438. 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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"}
@@ -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;AAC/I,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"}
@@ -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 every mutation, and a
4
- // small registry under ~/.swipium/registry.json lets a fresh server instance reload
5
- // prior sessions so artifacts/reports/job-status survive a server restart. (A restart does
6
- // NOT resurrect a running child process — such jobs are marked failed on reload.)
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': return 'screenshots';
44
- case 'recording': return 'videos';
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
- constructor() {
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, root, dir, createdAt: now, screenshotCount: 0,
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: [], workarounds: [],
76
- jobs: new Map(), artifacts: [], findings: [], notes: [], mutations: [], recordedActions: [],
77
- fixtures: opts?.fixtures ?? [], auth: {}, milestones: { session_start: now }, budgetProfile: opts?.budgetProfile,
78
- secrets: new Set(), inputs: [], generatedValues: [], inputValues: new Map(), aborts: new Map(),
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.persist(s);
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, root: s.root, dir: s.dir, createdAt: s.createdAt,
95
- device: s.device, appId: s.appId, headless: s.headless, metroPid: s.metroPid, screenshotCount: s.screenshotCount,
96
- network: s.network, envChanges: s.envChanges, workarounds: s.workarounds,
97
- mode: s.mode, responseMode: s.responseMode, sensitive: s.sensitive, budget: s.budget, counters: s.counters,
98
- jobs: [...s.jobs.values()], artifacts: s.artifacts, findings: s.findings, notes: s.notes, mutations: s.mutations, recordedActions: s.recordedActions,
99
- fixtures: s.fixtures, auth: s.auth, milestones: s.milestones, budgetProfile: s.budgetProfile,
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
- writeFileSync(join(s.dir, 'state.json'), JSON.stringify(state, null, 2));
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
- /* best-effort */
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.persist(s);
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 qa_flow_generate. */
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 storedData = typeof data === 'string' && isTextArtifactMime(mime) ? (redact(data) ?? '') : data;
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
- s.artifacts.push({ uri, path, mime, kind, createdAt: Date.now(), label: storedLabel });
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, root: st.root, dir: st.dir, createdAt: st.createdAt,
295
- device: st.device, appId: st.appId, headless: st.headless, metroPid: st.metroPid, screenshotCount: st.screenshotCount ?? 0,
296
- network: st.network, envChanges: st.envChanges ?? [], workarounds: st.workarounds ?? [],
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, artifacts: st.artifacts ?? [], findings: st.findings ?? [], notes: st.notes ?? [], mutations: st.mutations ?? [], recordedActions: st.recordedActions ?? [],
303
- fixtures: st.fixtures ?? [], auth: st.auth ?? {}, milestones: st.milestones ?? { session_start: st.createdAt }, budgetProfile: st.budgetProfile,
304
- secrets: new Set(), inputs: st.inputs ?? [], generatedValues: st.generatedValues ?? [], inputValues: new Map(), exploration: st.exploration, aborts: new Map(),
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
- /* skip a corrupt session */
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
- /* no registry */
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
- const reg = existsSync(REGISTRY) ? JSON.parse(readFileSync(REGISTRY, 'utf8')) : [];
321
- reg.push({ id, dir });
322
- writeFileSync(REGISTRY, JSON.stringify(reg.slice(-200), null, 2));
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
- /* best-effort */
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
  }