swipium 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +295 -0
- package/dist/appMap/automationLink.js +74 -0
- package/dist/appMap/automationLink.js.map +1 -0
- package/dist/appMap/build.js +193 -0
- package/dist/appMap/build.js.map +1 -0
- package/dist/appMap/codeIndex.js +73 -0
- package/dist/appMap/codeIndex.js.map +1 -0
- package/dist/appMap/featureModel.js +83 -0
- package/dist/appMap/featureModel.js.map +1 -0
- package/dist/appMap/firstRunApply.js +131 -0
- package/dist/appMap/firstRunApply.js.map +1 -0
- package/dist/appMap/fsWalk.js +75 -0
- package/dist/appMap/fsWalk.js.map +1 -0
- package/dist/appMap/issues.js +227 -0
- package/dist/appMap/issues.js.map +1 -0
- package/dist/appMap/migrations.js +87 -0
- package/dist/appMap/migrations.js.map +1 -0
- package/dist/appMap/prelaunch.js +66 -0
- package/dist/appMap/prelaunch.js.map +1 -0
- package/dist/appMap/projectRegistry.js +70 -0
- package/dist/appMap/projectRegistry.js.map +1 -0
- package/dist/appMap/provenance.js +52 -0
- package/dist/appMap/provenance.js.map +1 -0
- package/dist/appMap/query.js +118 -0
- package/dist/appMap/query.js.map +1 -0
- package/dist/appMap/runtimeMerge.js +207 -0
- package/dist/appMap/runtimeMerge.js.map +1 -0
- package/dist/appMap/schema.js +65 -0
- package/dist/appMap/schema.js.map +1 -0
- package/dist/appMap/screenMatch.js +75 -0
- package/dist/appMap/screenMatch.js.map +1 -0
- package/dist/appMap/staticScan.js +614 -0
- package/dist/appMap/staticScan.js.map +1 -0
- package/dist/appMap/store.js +173 -0
- package/dist/appMap/store.js.map +1 -0
- package/dist/appMap/tsAstScan.js +184 -0
- package/dist/appMap/tsAstScan.js.map +1 -0
- package/dist/artifacts/bundletool.js +199 -0
- package/dist/artifacts/bundletool.js.map +1 -0
- package/dist/artifacts/resolve.js +388 -0
- package/dist/artifacts/resolve.js.map +1 -0
- package/dist/automationGen/appiumModel.js +129 -0
- package/dist/automationGen/appiumModel.js.map +1 -0
- package/dist/automationGen/ciEmitter.js +80 -0
- package/dist/automationGen/ciEmitter.js.map +1 -0
- package/dist/automationGen/jsEmitter.js +440 -0
- package/dist/automationGen/jsEmitter.js.map +1 -0
- package/dist/automationGen/packagePatch.js +56 -0
- package/dist/automationGen/packagePatch.js.map +1 -0
- package/dist/automationGen/projectProfile.js +317 -0
- package/dist/automationGen/projectProfile.js.map +1 -0
- package/dist/automationGen/pythonEmitter.js +386 -0
- package/dist/automationGen/pythonEmitter.js.map +1 -0
- package/dist/automationGen/readmeEmitter.js +66 -0
- package/dist/automationGen/readmeEmitter.js.map +1 -0
- package/dist/automationGen/suitePlan.js +94 -0
- package/dist/automationGen/suitePlan.js.map +1 -0
- package/dist/automationGen/validation.js +150 -0
- package/dist/automationGen/validation.js.map +1 -0
- package/dist/build/parseBuildLog.js +119 -0
- package/dist/build/parseBuildLog.js.map +1 -0
- package/dist/build/plan.js +150 -0
- package/dist/build/plan.js.map +1 -0
- package/dist/ci/preflight.js +85 -0
- package/dist/ci/preflight.js.map +1 -0
- package/dist/cli/init.js +105 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/scan.js +85 -0
- package/dist/cli/scan.js.map +1 -0
- package/dist/cli/suite.js +85 -0
- package/dist/cli/suite.js.map +1 -0
- package/dist/cli/verify.js +62 -0
- package/dist/cli/verify.js.map +1 -0
- package/dist/consent/consent.js +43 -0
- package/dist/consent/consent.js.map +1 -0
- package/dist/context/detect.js +156 -0
- package/dist/context/detect.js.map +1 -0
- package/dist/context/findApps.js +198 -0
- package/dist/context/findApps.js.map +1 -0
- package/dist/context/projectRoot.js +41 -0
- package/dist/context/projectRoot.js.map +1 -0
- package/dist/context/scan.js +167 -0
- package/dist/context/scan.js.map +1 -0
- package/dist/core/target.js +115 -0
- package/dist/core/target.js.map +1 -0
- package/dist/core/targetPlan.js +140 -0
- package/dist/core/targetPlan.js.map +1 -0
- package/dist/drivers/DirectDriver.js +228 -0
- package/dist/drivers/DirectDriver.js.map +1 -0
- package/dist/drivers/Driver.js +4 -0
- package/dist/drivers/Driver.js.map +1 -0
- package/dist/drivers/SimctlDriver.js +127 -0
- package/dist/drivers/SimctlDriver.js.map +1 -0
- package/dist/drivers/WdaDriver.js +250 -0
- package/dist/drivers/WdaDriver.js.map +1 -0
- package/dist/explore/candidates.js +93 -0
- package/dist/explore/candidates.js.map +1 -0
- package/dist/explore/graph.js +116 -0
- package/dist/explore/graph.js.map +1 -0
- package/dist/explore/memory.js +35 -0
- package/dist/explore/memory.js.map +1 -0
- package/dist/explore/planner.js +105 -0
- package/dist/explore/planner.js.map +1 -0
- package/dist/explore/policy.js +85 -0
- package/dist/explore/policy.js.map +1 -0
- package/dist/explore/runner.js +475 -0
- package/dist/explore/runner.js.map +1 -0
- package/dist/explore/signatures.js +35 -0
- package/dist/explore/signatures.js.map +1 -0
- package/dist/explore/suite.js +55 -0
- package/dist/explore/suite.js.map +1 -0
- package/dist/featureTesting/executionBootstrap.js +140 -0
- package/dist/featureTesting/executionBootstrap.js.map +1 -0
- package/dist/firstRun/authStateMachine.js +83 -0
- package/dist/firstRun/authStateMachine.js.map +1 -0
- package/dist/firstRun/classifyScreen.js +201 -0
- package/dist/firstRun/classifyScreen.js.map +1 -0
- package/dist/firstRun/firstRunPlanner.js +207 -0
- package/dist/firstRun/firstRunPlanner.js.map +1 -0
- package/dist/firstRun/firstRunRunner.js +331 -0
- package/dist/firstRun/firstRunRunner.js.map +1 -0
- package/dist/firstRun/generatedDataPolicy.js +215 -0
- package/dist/firstRun/generatedDataPolicy.js.map +1 -0
- package/dist/firstRun/inputPlanner.js +67 -0
- package/dist/firstRun/inputPlanner.js.map +1 -0
- package/dist/firstRun/onboardingStateMachine.js +46 -0
- package/dist/firstRun/onboardingStateMachine.js.map +1 -0
- package/dist/firstRun/paywallPolicy.js +54 -0
- package/dist/firstRun/paywallPolicy.js.map +1 -0
- package/dist/firstRun/types.js +7 -0
- package/dist/firstRun/types.js.map +1 -0
- package/dist/fixtures/catalog.js +131 -0
- package/dist/fixtures/catalog.js.map +1 -0
- package/dist/flows/discover.js +18 -0
- package/dist/flows/discover.js.map +1 -0
- package/dist/flows/generate.js +211 -0
- package/dist/flows/generate.js.map +1 -0
- package/dist/flows/lint.js +97 -0
- package/dist/flows/lint.js.map +1 -0
- package/dist/flows/pack.js +68 -0
- package/dist/flows/pack.js.map +1 -0
- package/dist/flows/run.js +738 -0
- package/dist/flows/run.js.map +1 -0
- package/dist/flows/schema.js +240 -0
- package/dist/flows/schema.js.map +1 -0
- package/dist/flows/seedExec.js +108 -0
- package/dist/flows/seedExec.js.map +1 -0
- package/dist/flows/templates.js +155 -0
- package/dist/flows/templates.js.map +1 -0
- package/dist/flows/waitGuards.js +12 -0
- package/dist/flows/waitGuards.js.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/ios/signing.js +182 -0
- package/dist/ios/signing.js.map +1 -0
- package/dist/issues/classify.js +186 -0
- package/dist/issues/classify.js.map +1 -0
- package/dist/issues/fingerprint.js +130 -0
- package/dist/issues/fingerprint.js.map +1 -0
- package/dist/issues/index.js +240 -0
- package/dist/issues/index.js.map +1 -0
- package/dist/issues/metrics.js +155 -0
- package/dist/issues/metrics.js.map +1 -0
- package/dist/issues/recurrence.js +193 -0
- package/dist/issues/recurrence.js.map +1 -0
- package/dist/issues/report.js +110 -0
- package/dist/issues/report.js.map +1 -0
- package/dist/issues/reportBridge.js +119 -0
- package/dist/issues/reportBridge.js.map +1 -0
- package/dist/issues/schema.js +90 -0
- package/dist/issues/schema.js.map +1 -0
- package/dist/issues/sourceRevision.js +108 -0
- package/dist/issues/sourceRevision.js.map +1 -0
- package/dist/issues/store.js +155 -0
- package/dist/issues/store.js.map +1 -0
- package/dist/lib/android.js +278 -0
- package/dist/lib/android.js.map +1 -0
- package/dist/lib/commandTemplate.js +55 -0
- package/dist/lib/commandTemplate.js.map +1 -0
- package/dist/lib/coordSpace.js +23 -0
- package/dist/lib/coordSpace.js.map +1 -0
- package/dist/lib/device.js +87 -0
- package/dist/lib/device.js.map +1 -0
- package/dist/lib/gitignore.js +22 -0
- package/dist/lib/gitignore.js.map +1 -0
- package/dist/lib/image.js +97 -0
- package/dist/lib/image.js.map +1 -0
- package/dist/lib/logger.js +8 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/metroState.js +45 -0
- package/dist/lib/metroState.js.map +1 -0
- package/dist/lib/needsInput.js +201 -0
- package/dist/lib/needsInput.js.map +1 -0
- package/dist/lib/png.js +103 -0
- package/dist/lib/png.js.map +1 -0
- package/dist/lib/redact.js +21 -0
- package/dist/lib/redact.js.map +1 -0
- package/dist/lib/result.js +87 -0
- package/dist/lib/result.js.map +1 -0
- package/dist/lib/schemaHash.js +65 -0
- package/dist/lib/schemaHash.js.map +1 -0
- package/dist/lib/sensitive.js +13 -0
- package/dist/lib/sensitive.js.map +1 -0
- package/dist/lib/simctl.js +149 -0
- package/dist/lib/simctl.js.map +1 -0
- package/dist/lib/spawn.js +144 -0
- package/dist/lib/spawn.js.map +1 -0
- package/dist/lib/wda.js +347 -0
- package/dist/lib/wda.js.map +1 -0
- package/dist/lib/wdaConfig.js +69 -0
- package/dist/lib/wdaConfig.js.map +1 -0
- package/dist/lib/wdaTune.js +58 -0
- package/dist/lib/wdaTune.js.map +1 -0
- package/dist/oracle/auth.js +22 -0
- package/dist/oracle/auth.js.map +1 -0
- package/dist/oracle/failures.js +193 -0
- package/dist/oracle/failures.js.map +1 -0
- package/dist/oracle/health.js +123 -0
- package/dist/oracle/health.js.map +1 -0
- package/dist/oracle/record.js +36 -0
- package/dist/oracle/record.js.map +1 -0
- package/dist/orchestration/envelope.js +52 -0
- package/dist/orchestration/envelope.js.map +1 -0
- package/dist/orchestration/goal.js +98 -0
- package/dist/orchestration/goal.js.map +1 -0
- package/dist/plan/plan.js +124 -0
- package/dist/plan/plan.js.map +1 -0
- package/dist/prompts/index.js +63 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/report/artifactManifest.js +44 -0
- package/dist/report/artifactManifest.js.map +1 -0
- package/dist/report/coverage.js +60 -0
- package/dist/report/coverage.js.map +1 -0
- package/dist/report/evidence.js +84 -0
- package/dist/report/evidence.js.map +1 -0
- package/dist/report/export.js +447 -0
- package/dist/report/export.js.map +1 -0
- package/dist/report/flake.js +61 -0
- package/dist/report/flake.js.map +1 -0
- package/dist/report/history.js +564 -0
- package/dist/report/history.js.map +1 -0
- package/dist/report/policy.js +98 -0
- package/dist/report/policy.js.map +1 -0
- package/dist/report/qaLevel.js +96 -0
- package/dist/report/qaLevel.js.map +1 -0
- package/dist/report/readiness.js +213 -0
- package/dist/report/readiness.js.map +1 -0
- package/dist/report/summary.js +42 -0
- package/dist/report/summary.js.map +1 -0
- package/dist/report/testCatalog.js +93 -0
- package/dist/report/testCatalog.js.map +1 -0
- package/dist/report/timing.js +37 -0
- package/dist/report/timing.js.map +1 -0
- package/dist/server.js +180 -0
- package/dist/server.js.map +1 -0
- package/dist/services/automationGenerate.js +60 -0
- package/dist/services/automationGenerate.js.map +1 -0
- package/dist/services/build.js +66 -0
- package/dist/services/build.js.map +1 -0
- package/dist/services/preflight.js +51 -0
- package/dist/services/preflight.js.map +1 -0
- package/dist/services/prepareAndroid.js +183 -0
- package/dist/services/prepareAndroid.js.map +1 -0
- package/dist/services/prepareIos.js +162 -0
- package/dist/services/prepareIos.js.map +1 -0
- package/dist/services/report.js +537 -0
- package/dist/services/report.js.map +1 -0
- package/dist/services/smoke.js +120 -0
- package/dist/services/smoke.js.map +1 -0
- package/dist/services/suiteGenerate.js +123 -0
- package/dist/services/suiteGenerate.js.map +1 -0
- package/dist/services/testSuiteKnowledge.js +150 -0
- package/dist/services/testSuiteKnowledge.js.map +1 -0
- package/dist/session/attach.js +47 -0
- package/dist/session/attach.js.map +1 -0
- package/dist/session/progress.js +56 -0
- package/dist/session/progress.js.map +1 -0
- package/dist/session/store.js +329 -0
- package/dist/session/store.js.map +1 -0
- package/dist/snapshot/overlays.js +132 -0
- package/dist/snapshot/overlays.js.map +1 -0
- package/dist/snapshot/parse.js +204 -0
- package/dist/snapshot/parse.js.map +1 -0
- package/dist/snapshot/present.js +12 -0
- package/dist/snapshot/present.js.map +1 -0
- package/dist/snapshot/settle.js +36 -0
- package/dist/snapshot/settle.js.map +1 -0
- package/dist/state/profile.js +318 -0
- package/dist/state/profile.js.map +1 -0
- package/dist/suite/compile.js +176 -0
- package/dist/suite/compile.js.map +1 -0
- package/dist/suite/lint.js +45 -0
- package/dist/suite/lint.js.map +1 -0
- package/dist/suite/pom.js +285 -0
- package/dist/suite/pom.js.map +1 -0
- package/dist/suite/testcase.js +87 -0
- package/dist/suite/testcase.js.map +1 -0
- package/dist/testSuite/generator.js +222 -0
- package/dist/testSuite/generator.js.map +1 -0
- package/dist/testSuite/history.js +44 -0
- package/dist/testSuite/history.js.map +1 -0
- package/dist/testSuite/issueLinks.js +96 -0
- package/dist/testSuite/issueLinks.js.map +1 -0
- package/dist/testSuite/merge.js +190 -0
- package/dist/testSuite/merge.js.map +1 -0
- package/dist/testSuite/schema.js +86 -0
- package/dist/testSuite/schema.js.map +1 -0
- package/dist/testSuite/store.js +116 -0
- package/dist/testSuite/store.js.map +1 -0
- package/dist/testSuite/traceability.js +55 -0
- package/dist/testSuite/traceability.js.map +1 -0
- package/dist/tools/act.js +457 -0
- package/dist/tools/act.js.map +1 -0
- package/dist/tools/agent.js +225 -0
- package/dist/tools/agent.js.map +1 -0
- package/dist/tools/appMap.js +317 -0
- package/dist/tools/appMap.js.map +1 -0
- package/dist/tools/assertVisual.js +74 -0
- package/dist/tools/assertVisual.js.map +1 -0
- package/dist/tools/automationGenerate.js +394 -0
- package/dist/tools/automationGenerate.js.map +1 -0
- package/dist/tools/capabilities.js +114 -0
- package/dist/tools/capabilities.js.map +1 -0
- package/dist/tools/clearOverlay.js +150 -0
- package/dist/tools/clearOverlay.js.map +1 -0
- package/dist/tools/detectContext.js +41 -0
- package/dist/tools/detectContext.js.map +1 -0
- package/dist/tools/doctor.js +116 -0
- package/dist/tools/doctor.js.map +1 -0
- package/dist/tools/explore.js +289 -0
- package/dist/tools/explore.js.map +1 -0
- package/dist/tools/firstRun.js +136 -0
- package/dist/tools/firstRun.js.map +1 -0
- package/dist/tools/flow.js +331 -0
- package/dist/tools/flow.js.map +1 -0
- package/dist/tools/flowGenerate.js +55 -0
- package/dist/tools/flowGenerate.js.map +1 -0
- package/dist/tools/getArtifact.js +39 -0
- package/dist/tools/getArtifact.js.map +1 -0
- package/dist/tools/health.js +41 -0
- package/dist/tools/health.js.map +1 -0
- package/dist/tools/ios.js +492 -0
- package/dist/tools/ios.js.map +1 -0
- package/dist/tools/jobs.js +20 -0
- package/dist/tools/jobs.js.map +1 -0
- package/dist/tools/network.js +50 -0
- package/dist/tools/network.js.map +1 -0
- package/dist/tools/note.js +69 -0
- package/dist/tools/note.js.map +1 -0
- package/dist/tools/plan.js +49 -0
- package/dist/tools/plan.js.map +1 -0
- package/dist/tools/prepareIosTarget.js +99 -0
- package/dist/tools/prepareIosTarget.js.map +1 -0
- package/dist/tools/prepareTarget.js +257 -0
- package/dist/tools/prepareTarget.js.map +1 -0
- package/dist/tools/report.js +42 -0
- package/dist/tools/report.js.map +1 -0
- package/dist/tools/screenRecord.js +24 -0
- package/dist/tools/screenRecord.js.map +1 -0
- package/dist/tools/screenshot.js +63 -0
- package/dist/tools/screenshot.js.map +1 -0
- package/dist/tools/smoke.js +39 -0
- package/dist/tools/smoke.js.map +1 -0
- package/dist/tools/snapshot.js +129 -0
- package/dist/tools/snapshot.js.map +1 -0
- package/dist/tools/startSession.js +169 -0
- package/dist/tools/startSession.js.map +1 -0
- package/dist/tools/state.js +87 -0
- package/dist/tools/state.js.map +1 -0
- package/dist/tools/suite.js +412 -0
- package/dist/tools/suite.js.map +1 -0
- package/dist/tools/testThis.js +774 -0
- package/dist/tools/testThis.js.map +1 -0
- package/dist/tools/wda.js +477 -0
- package/dist/tools/wda.js.map +1 -0
- package/dist/version.js +62 -0
- package/dist/version.js.map +1 -0
- package/dist/visual/ocr.js +72 -0
- package/dist/visual/ocr.js.map +1 -0
- package/dist/visual/provider.js +75 -0
- package/dist/visual/provider.js.map +1 -0
- package/docs/README.md +19 -0
- package/docs/assets/swipium-lockup-ink-on-light.png +0 -0
- package/docs/mcp-server.md +135 -0
- package/docs/tools.md +130 -0
- package/package.json +70 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// qa_note — record a structured test outcome (Phase 2.2). Lets the agent state explicitly
|
|
2
|
+
// that a workflow passed / failed / was blocked / skipped / not-applicable, with the reason,
|
|
3
|
+
// missing precondition, required state, and recommended setup. This is how a report
|
|
4
|
+
// distinguishes a real app bug from "no saved flight existed to delete" — so missing test
|
|
5
|
+
// data and intentional skips stop being mislabeled as failures.
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { qaOk, qaError } from '../lib/result.js';
|
|
8
|
+
const OUTCOMES = ['pass', 'fail', 'blocked', 'skipped', 'not_applicable'];
|
|
9
|
+
const CATEGORIES = ['app_bug', 'mcp_limitation', 'missing_test_data', 'intentionally_skipped', 'destructive_refused', 'other'];
|
|
10
|
+
export function registerNote(server, sessions) {
|
|
11
|
+
server.registerTool('qa_note', {
|
|
12
|
+
title: 'Record a test outcome',
|
|
13
|
+
description: 'Record a STRUCTURED test outcome for one workflow so the report is honest about what was and was not verified. Use outcome="blocked" with a missingPrecondition (e.g. "no saved flight exists") instead of reporting a false failure; "not_applicable" when the workflow does not apply; "skipped" when intentionally not run. Set category to classify (app_bug | mcp_limitation | missing_test_data | intentionally_skipped | destructive_refused | other). Attach artifactUris (screenshots/dumps) as evidence.',
|
|
14
|
+
inputSchema: {
|
|
15
|
+
sessionId: z.string(),
|
|
16
|
+
workflow: z.string().describe('the workflow/test this outcome is about, e.g. "Delete saved flight"'),
|
|
17
|
+
outcome: z.enum(OUTCOMES).describe('WHAT happened (independent of category).'),
|
|
18
|
+
category: z.enum(CATEGORIES).optional().describe('WHY / classification — an INDEPENDENT axis from outcome (e.g. a not_applicable outcome may be category=intentionally_skipped). The report cross-tabs the two; they are not folded together.'),
|
|
19
|
+
reason: z.string().optional(),
|
|
20
|
+
missingPrecondition: z.string().optional().describe('what state was required but absent, e.g. "no saved flight exists"'),
|
|
21
|
+
requiredState: z.string().optional().describe('the state the test needs, e.g. "at least one saved flight"'),
|
|
22
|
+
recommendedSetup: z.string().optional().describe('how to satisfy the precondition next time'),
|
|
23
|
+
artifactUris: z.array(z.string()).optional(),
|
|
24
|
+
verifiedVisually: z.boolean().optional().describe('pass was confirmed from a screenshot (animated/map/canvas screen with no structured tree) — counts as a real pass, not a weak one. Attach the screenshot in artifactUris.'),
|
|
25
|
+
},
|
|
26
|
+
}, async ({ sessionId, workflow, outcome, category, reason, missingPrecondition, requiredState, recommendedSetup, artifactUris, verifiedVisually }) => {
|
|
27
|
+
const session = sessions.get(sessionId);
|
|
28
|
+
if (!session) {
|
|
29
|
+
return qaError({ what: `Unknown sessionId ${sessionId}`, changedState: false, retrySafe: true, nextSteps: ['Call qa_start_session first.'] });
|
|
30
|
+
}
|
|
31
|
+
// A visual-only pass should carry its evidence so the report isn't taking it on faith.
|
|
32
|
+
if (verifiedVisually && outcome === 'pass' && !(artifactUris && artifactUris.length)) {
|
|
33
|
+
return qaError({
|
|
34
|
+
what: 'A visually-verified pass needs a screenshot in artifactUris as evidence.',
|
|
35
|
+
changedState: false,
|
|
36
|
+
retrySafe: true,
|
|
37
|
+
nextSteps: ['Call qa_screenshot (with a reason), then re-call qa_note with its URI in artifactUris.'],
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
// A "blocked" outcome with no explanation is exactly the unhelpful case this tool exists
|
|
41
|
+
// to prevent — nudge for the precondition.
|
|
42
|
+
if (outcome === 'blocked' && !missingPrecondition && !reason) {
|
|
43
|
+
return qaError({
|
|
44
|
+
what: 'A "blocked" outcome needs a missingPrecondition or reason so the report is actionable.',
|
|
45
|
+
changedState: false,
|
|
46
|
+
retrySafe: true,
|
|
47
|
+
nextSteps: ['Re-call qa_note with missingPrecondition (e.g. "no saved flight exists") and/or reason.'],
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// If a declared fixture matches this workflow/precondition, borrow its recommendedSetup
|
|
51
|
+
// so a blocked outcome carries the setup guidance the agent already declared (P1.4).
|
|
52
|
+
const fx = session.fixtures.find((f) => f.name.toLowerCase() === workflow.toLowerCase() || (missingPrecondition && f.name.toLowerCase() === missingPrecondition.toLowerCase()));
|
|
53
|
+
sessions.addNote(session, {
|
|
54
|
+
at: Date.now(),
|
|
55
|
+
workflow,
|
|
56
|
+
outcome: outcome,
|
|
57
|
+
category: category,
|
|
58
|
+
reason,
|
|
59
|
+
missingPrecondition: missingPrecondition ?? fx?.requiredState,
|
|
60
|
+
requiredState: requiredState ?? fx?.requiredState,
|
|
61
|
+
recommendedSetup: recommendedSetup ?? fx?.recommendedSetup,
|
|
62
|
+
artifactUris,
|
|
63
|
+
verifiedVisually,
|
|
64
|
+
});
|
|
65
|
+
const tally = session.notes.reduce((a, n) => ((a[n.outcome] = (a[n.outcome] ?? 0) + 1), a), {});
|
|
66
|
+
return qaOk({ recorded: { workflow, outcome, category }, tally }, `noted: "${workflow}" → ${outcome}${category ? ` (${category})` : ''}${missingPrecondition ? ` — missing: ${missingPrecondition}` : ''}\ntally: ${Object.entries(tally).map(([k, v]) => `${k}=${v}`).join(' ')}`);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=note.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"note.js","sourceRoot":"","sources":["../../src/tools/note.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// qa_plan (PHASE3-PLAN §3.2) — propose a safe test plan instead of making the agent guess.
|
|
2
|
+
// Synthesizes detectContext() + the session's declared fixtures / observed auth / prepared
|
|
3
|
+
// appId into READY / BLOCKED / UNSAFE workflows. Pure planning logic lives in src/plan/plan.ts.
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { qaOk, qaError } from '../lib/result.js';
|
|
6
|
+
import { detectContext } from '../context/detect.js';
|
|
7
|
+
import { buildPlan } from '../plan/plan.js';
|
|
8
|
+
import { listFlowFiles } from '../flows/discover.js';
|
|
9
|
+
export function registerPlan(server, sessions) {
|
|
10
|
+
server.registerTool('qa_plan', {
|
|
11
|
+
title: 'Plan what to test',
|
|
12
|
+
description: 'Propose a safe test plan for the current session instead of guessing. Returns READY workflows (with budget profile + satisfied preconditions), BLOCKED workflows (with a category — missing_device/missing_artifact/missing_test_data/missing_toolchain — required state, and how to unblock), and UNSAFE workflows (with a reason, e.g. bundle_cache_loss for fresh_start on a debug RN/Expo build). Call this first to decide what to run. Reads detected context + declared fixtures + observed auth; does not touch the device.',
|
|
13
|
+
inputSchema: {
|
|
14
|
+
sessionId: z.string().describe('A session from qa_start_session (its fixtures/auth/root inform the plan).'),
|
|
15
|
+
},
|
|
16
|
+
}, async ({ sessionId }) => {
|
|
17
|
+
const session = sessions.get(sessionId);
|
|
18
|
+
if (!session) {
|
|
19
|
+
return qaError({
|
|
20
|
+
what: 'No such session',
|
|
21
|
+
changedState: false,
|
|
22
|
+
retrySafe: true,
|
|
23
|
+
nextSteps: ['Call qa_start_session first, then qa_plan with its sessionId.'],
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const ctx = await detectContext(session.root);
|
|
27
|
+
const plan = buildPlan({
|
|
28
|
+
framework: ctx.framework,
|
|
29
|
+
hasDevice: ctx.devices.androidOnline.length > 0 || ctx.devices.avds.length > 0,
|
|
30
|
+
hasApk: ctx.artifacts.apks.length > 0 || ctx.artifacts.ipas.length > 0 || ctx.artifacts.appBundles.length > 0,
|
|
31
|
+
appPrepared: !!session.appId,
|
|
32
|
+
fixtures: session.fixtures,
|
|
33
|
+
auth: session.auth,
|
|
34
|
+
blockers: ctx.blockers,
|
|
35
|
+
flows: listFlowFiles(session.root).map((f) => f.name),
|
|
36
|
+
});
|
|
37
|
+
const line = (s) => ` - ${s}`;
|
|
38
|
+
const summary = `Plan for session ${session.id} (framework=${ctx.framework})\n` +
|
|
39
|
+
`READY (${plan.ready.length}):\n` +
|
|
40
|
+
(plan.ready.map((w) => line(`${w.workflow} [${w.budgetProfile}]${w.requires.length ? ` needs ${w.requires.join(', ')}` : ''}`)).join('\n') || ' (none)') +
|
|
41
|
+
`\nBLOCKED (${plan.blocked.length}):\n` +
|
|
42
|
+
(plan.blocked.map((w) => line(`${w.workflow}: ${w.category} — ${w.requiredState} → ${w.recommendedSetup}`)).join('\n') || ' (none)') +
|
|
43
|
+
`\nUNSAFE (${plan.unsafe.length}):\n` +
|
|
44
|
+
(plan.unsafe.map((w) => line(`${w.workflow}: ${w.reason} — ${w.detail}`)).join('\n') || ' (none)') +
|
|
45
|
+
(plan.notes.length ? `\nnotes:\n` + plan.notes.map(line).join('\n') : '');
|
|
46
|
+
return qaOk({ ...plan, framework: ctx.framework }, summary);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/tools/plan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// qa_prepare_ios_target (hardening P0.3) — one high-level iOS prepare: boot simulator → install
|
|
2
|
+
// .app → launch bundle → verify → report WDA/visual mode. The cross-platform counterpart to
|
|
3
|
+
// qa_prepare_target's Android path, so qa_test_this can complete iOS first-runs end-to-end.
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { isAbsolute, join } from 'node:path';
|
|
6
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
7
|
+
import { createHash } from 'node:crypto';
|
|
8
|
+
import { qaOk, qaError } from '../lib/result.js';
|
|
9
|
+
import { qaFail } from '../oracle/failures.js';
|
|
10
|
+
import { requireConsent, consumeConsent } from '../consent/consent.js';
|
|
11
|
+
import { prepareIos } from '../services/prepareIos.js';
|
|
12
|
+
/** A .app is a directory — hash a stable signature (Info.plist) for consent binding. */
|
|
13
|
+
function appSignature(appPath) {
|
|
14
|
+
try {
|
|
15
|
+
const plist = join(appPath, 'Info.plist');
|
|
16
|
+
if (existsSync(plist))
|
|
17
|
+
return createHash('sha256').update(readFileSync(plist)).digest('hex').slice(0, 16);
|
|
18
|
+
return createHash('sha256').update(`${appPath}:${statSync(appPath).mtimeMs}`).digest('hex').slice(0, 16);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return 'unknown';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function externalToRoot(session, appPath) {
|
|
25
|
+
const abs = isAbsolute(appPath) ? appPath : join(session.root, appPath);
|
|
26
|
+
return !abs.startsWith(session.root);
|
|
27
|
+
}
|
|
28
|
+
export function registerPrepareIosTarget(server, sessions) {
|
|
29
|
+
server.registerTool('qa_prepare_ios_target', {
|
|
30
|
+
title: 'Prepare an iOS simulator target',
|
|
31
|
+
description: 'High-level iOS simulator prepare: pick + boot a simulator, install a simulator .app (consent-gated), launch its bundle id, verify foreground, and report whether structured automation (WDA) is available or it is honestly visual-only. Refuses a .ipa on the simulator (real-device only). attachWda: auto (use WDA if reachable, else visual) | required (fail if no WDA) | skip (visual-only). Returns typed blockers (IPA_NEEDS_REAL_DEVICE, IOS_SIMULATOR_APP_MISSING, SIMULATOR_BOOT_FAILED, WDA_UNREACHABLE).',
|
|
32
|
+
inputSchema: {
|
|
33
|
+
sessionId: z.string(),
|
|
34
|
+
app: z.string().optional().describe('Simulator .app path (absolute or project-relative).'),
|
|
35
|
+
bundleId: z.string().optional(),
|
|
36
|
+
simulator: z.string().optional().describe('Simulator udid or name substring.'),
|
|
37
|
+
launch: z.boolean().optional(),
|
|
38
|
+
attachWda: z.enum(['auto', 'required', 'skip']).optional(),
|
|
39
|
+
consentId: z.string().optional(),
|
|
40
|
+
approve: z.boolean().optional(),
|
|
41
|
+
},
|
|
42
|
+
}, async ({ sessionId, app, bundleId, simulator, launch, attachWda, consentId, approve }) => {
|
|
43
|
+
const session = sessions.get(sessionId);
|
|
44
|
+
if (!session)
|
|
45
|
+
return qaError({ what: `Unknown sessionId "${sessionId}"`, changedState: false, retrySafe: true, nextSteps: ['Call qa_start_session first.'] });
|
|
46
|
+
// Installing app code is privileged → consent (mirrors qa_ios install).
|
|
47
|
+
let mutationConsent;
|
|
48
|
+
let installAffects;
|
|
49
|
+
if (app) {
|
|
50
|
+
const abs = isAbsolute(app) ? app : join(session.root, app);
|
|
51
|
+
const sig = appSignature(abs);
|
|
52
|
+
const affects = { appPath: abs, sig, external: externalToRoot(session, app) };
|
|
53
|
+
installAffects = affects;
|
|
54
|
+
const gate = consumeConsent(consentId, approve, { action: 'install_app', affects });
|
|
55
|
+
if (!gate.approved) {
|
|
56
|
+
sessions.recordMutation(session, {
|
|
57
|
+
tool: 'qa_prepare_ios_target',
|
|
58
|
+
action: 'install_app',
|
|
59
|
+
risk: affects.external ? 'medium' : 'low',
|
|
60
|
+
target: affects,
|
|
61
|
+
consent: { required: true, approved: false, payloadHash: sig },
|
|
62
|
+
status: 'requested',
|
|
63
|
+
});
|
|
64
|
+
return requireConsent({
|
|
65
|
+
action: 'install_app', risk: affects.external ? 'medium' : 'low',
|
|
66
|
+
exactCommand: `xcrun simctl install <booted> ${abs}`,
|
|
67
|
+
affects,
|
|
68
|
+
explain: `Boot a simulator and install ${abs}${affects.external ? ' (outside the project root)' : ''}? It runs third-party app code.`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
mutationConsent = { required: true, consentId, approved: true, payloadHash: sig };
|
|
72
|
+
sessions.recordMutation(session, {
|
|
73
|
+
tool: 'qa_prepare_ios_target',
|
|
74
|
+
action: 'install_app',
|
|
75
|
+
risk: affects.external ? 'medium' : 'low',
|
|
76
|
+
target: affects,
|
|
77
|
+
consent: mutationConsent,
|
|
78
|
+
status: 'approved',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const res = await prepareIos(sessions, session, { app, bundleId, simulator, launch, attachWda, mutationConsent }, { onProgress: () => { } });
|
|
82
|
+
if (!res.ok) {
|
|
83
|
+
if (installAffects) {
|
|
84
|
+
sessions.recordMutation(session, {
|
|
85
|
+
tool: 'qa_prepare_ios_target',
|
|
86
|
+
action: 'install_app',
|
|
87
|
+
risk: installAffects.external ? 'medium' : 'low',
|
|
88
|
+
target: installAffects,
|
|
89
|
+
consent: mutationConsent,
|
|
90
|
+
status: 'blocked',
|
|
91
|
+
detail: res.error ?? res.failureCode ?? 'prepare failed',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return qaFail(res.failureCode ?? 'APP_LAUNCH_FAILED', { what: res.error ?? 'iOS prepare failed', extra: { udid: res.udid ?? null, name: res.name ?? null } });
|
|
95
|
+
}
|
|
96
|
+
return qaOk({ udid: res.udid, name: res.name, bundleId: res.bundleId ?? null, installed: res.installed, launched: res.launched, mode: res.mode, wda: res.wda ?? null }, res.resultText ?? 'iOS target prepared.');
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=prepareIosTarget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prepareIosTarget.js","sourceRoot":"","sources":["../../src/tools/prepareIosTarget.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,4FAA4F;AAC5F,4FAA4F;AAE5F,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAoB,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAGvD,wFAAwF;AACxF,SAAS,YAAY,CAAC,OAAe;IACnC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QAC1C,IAAI,UAAU,CAAC,KAAK,CAAC;YAAE,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC1G,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,OAAO,IAAI,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC3G,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,OAAgB,EAAE,OAAe;IACvD,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACxE,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,MAAiB,EAAE,QAAsB;IAChF,MAAM,CAAC,YAAY,CACjB,uBAAuB,EACvB;QACE,KAAK,EAAE,iCAAiC;QACxC,WAAW,EACT,ufAAuf;QACzf,WAAW,EAAE;YACX,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;YACrB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qDAAqD,CAAC;YAC1F,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC/B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,mCAAmC,CAAC;YAC9E,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;YAC9B,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE;YAC1D,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAChC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;SAChC;KACF,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,EAAE;QACvF,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO;YAAE,OAAO,OAAO,CAAC,EAAE,IAAI,EAAE,sBAAsB,SAAS,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,8BAA8B,CAAC,EAAE,CAAC,CAAC;QAE9J,wEAAwE;QACxE,IAAI,eAA+G,CAAC;QACpH,IAAI,cAA+E,CAAC;QACpF,IAAI,GAAG,EAAE,CAAC;YACR,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAC5D,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;YAC9B,MAAM,OAAO,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC;YAC9E,cAAc,GAAG,OAAO,CAAC;YACzB,MAAM,IAAI,GAAG,cAAc,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,CAAC,CAAC;YACpF,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACnB,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE;oBAC/B,IAAI,EAAE,uBAAuB;oBAC7B,MAAM,EAAE,aAAa;oBACrB,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK;oBACzC,MAAM,EAAE,OAAO;oBACf,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE;oBAC9D,MAAM,EAAE,WAAW;iBACpB,CAAC,CAAC;gBACH,OAAO,cAAc,CAAC;oBACpB,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK;oBAChE,YAAY,EAAE,iCAAiC,GAAG,EAAE;oBACpD,OAAO;oBACP,OAAO,EAAE,gCAAgC,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,6BAA6B,CAAC,CAAC,CAAC,EAAE,iCAAiC;iBACtI,CAAC,CAAC;YACL,CAAC;YACD,eAAe,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC;YAClF,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE;gBAC/B,IAAI,EAAE,uBAAuB;gBAC7B,MAAM,EAAE,aAAa;gBACrB,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK;gBACzC,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,eAAe;gBACxB,MAAM,EAAE,UAAU;aACnB,CAAC,CAAC;QACL,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC,CAAC;QAC5I,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,IAAI,cAAc,EAAE,CAAC;gBACnB,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE;oBAC/B,IAAI,EAAE,uBAAuB;oBAC7B,MAAM,EAAE,aAAa;oBACrB,IAAI,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK;oBAChD,MAAM,EAAE,cAAc;oBACtB,OAAO,EAAE,eAAe;oBACxB,MAAM,EAAE,SAAS;oBACjB,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,WAAW,IAAI,gBAAgB;iBACzD,CAAC,CAAC;YACL,CAAC;YACD,OAAO,MAAM,CAAE,GAAG,CAAC,WAA2B,IAAI,mBAAmB,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,IAAI,oBAAoB,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;QACjL,CAAC;QACD,OAAO,IAAI,CACT,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,IAAI,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,IAAI,EAAE,EAC1J,GAAG,CAAC,UAAU,IAAI,sBAAsB,CACzC,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// qa_prepare_target — get the app running on a device.
|
|
2
|
+
//
|
|
3
|
+
// Fast path (device online + app installed + !force): run synchronously, return the result.
|
|
4
|
+
// Long-op path (needs emulator boot or an install): create a JOB, run async, return a
|
|
5
|
+
// `jobId` immediately (poll with qa_job_status) — so client tool-call timeouts don't hit
|
|
6
|
+
// the slow paths (review #2). Interactive consent (boot, external-APK) is resolved
|
|
7
|
+
// synchronously BEFORE the job is kicked off.
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { readFileSync } from 'node:fs';
|
|
10
|
+
import { sep } from 'node:path';
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
import { qaOk, qaError } from '../lib/result.js';
|
|
13
|
+
import { requireConsent, consumeConsent } from '../consent/consent.js';
|
|
14
|
+
import { listAvds, resolveApk, apkPackageId, } from '../lib/android.js';
|
|
15
|
+
import { DirectDriver } from '../drivers/DirectDriver.js';
|
|
16
|
+
import { detectFramework } from '../context/detect.js';
|
|
17
|
+
import { metroReadiness, reverseSet } from '../lib/metroState.js';
|
|
18
|
+
import { resolveDevice, bindDevice } from '../session/attach.js';
|
|
19
|
+
import { prepareAndroid } from '../services/prepareAndroid.js';
|
|
20
|
+
/** RN/Expo debug builds load JS from Metro; launching before Metro is SERVING → RedBox. */
|
|
21
|
+
function needsMetro(root) {
|
|
22
|
+
const fw = detectFramework(root);
|
|
23
|
+
return fw === 'expo' || fw === 'bare-react-native';
|
|
24
|
+
}
|
|
25
|
+
function isAndroidEmulatorSerial(serial) {
|
|
26
|
+
return /^emulator-\d+/.test(serial);
|
|
27
|
+
}
|
|
28
|
+
export function registerPrepareTarget(server, sessions) {
|
|
29
|
+
server.registerTool('qa_prepare_target', {
|
|
30
|
+
title: 'Prepare a target device + app',
|
|
31
|
+
description: 'Orchestrate device→Metro→install→launch in the correct order, with a SINGLE combined consent for any privileged steps (boot, external-APK). Resolves/boots a device (binds the single online one automatically; asks if >1), sets adb reverse for RN/Expo, gates launch on Metro SERVING, installs the APK if needed, launches, verifies. Long ops return a jobId. Flags: bindOnly (bind/boot + reverse, no install/launch — use to break a setup deadlock); allowLaunchWithoutMetro (risky override); headless.',
|
|
32
|
+
inputSchema: {
|
|
33
|
+
sessionId: z.string(),
|
|
34
|
+
apk: z.string().optional(),
|
|
35
|
+
appId: z.string().optional(),
|
|
36
|
+
avd: z.string().optional(),
|
|
37
|
+
device: z.string().optional().describe('Target serial; required when >1 device is online.'),
|
|
38
|
+
force: z.boolean().optional(),
|
|
39
|
+
headless: z.boolean().optional().describe('If Swipium boots an AVD: headless (default true) or visible window (false).'),
|
|
40
|
+
bindOnly: z.boolean().optional().describe('Bind/boot the device + set adb reverse, but do NOT install or launch. Safe way to break a device/Metro setup deadlock.'),
|
|
41
|
+
allowLaunchWithoutMetro: z.boolean().optional().describe('Risky: launch a debug RN/Expo build even if Metro is not serving (may RedBox).'),
|
|
42
|
+
consentId: z.string().optional(),
|
|
43
|
+
approve: z.boolean().optional(),
|
|
44
|
+
},
|
|
45
|
+
}, async ({ sessionId, apk, appId, avd, device, force, headless, bindOnly, allowLaunchWithoutMetro, consentId, approve }) => {
|
|
46
|
+
const session = sessions.get(sessionId);
|
|
47
|
+
if (!session) {
|
|
48
|
+
return qaError({ what: `Unknown sessionId "${sessionId}"`, changedState: false, retrySafe: true, nextSteps: ['Call qa_start_session first.'] });
|
|
49
|
+
}
|
|
50
|
+
const rnDebug = needsMetro(session.root);
|
|
51
|
+
// ---- appId (+apk if needed for detection) — not needed for bindOnly ----
|
|
52
|
+
let resolvedAppId = appId;
|
|
53
|
+
let apkPath = apk;
|
|
54
|
+
if (!bindOnly && !resolvedAppId) {
|
|
55
|
+
const r = resolveApk(session.root, apk);
|
|
56
|
+
if (!r.apk)
|
|
57
|
+
return qaError({ what: r.error ?? 'No APK to detect appId', changedState: false, retrySafe: true, nextSteps: ['Pass appId= or apk=, or drop a build under apps/android/, or use bindOnly:true to just bind a device.'] });
|
|
58
|
+
apkPath = r.apk;
|
|
59
|
+
resolvedAppId = (await apkPackageId(apkPath)) ?? undefined;
|
|
60
|
+
if (!resolvedAppId)
|
|
61
|
+
return qaError({ what: 'Could not determine applicationId from the APK', changedState: false, retrySafe: true, nextSteps: ['Pass appId explicitly.'] });
|
|
62
|
+
}
|
|
63
|
+
// ---- device resolution (centralized; binds single online, asks on >1) ----
|
|
64
|
+
const res = await resolveDevice(session, device);
|
|
65
|
+
if (device && !res.effective) {
|
|
66
|
+
return qaError({ what: `Device "${device}" is not online`, changedState: false, retrySafe: true, nextSteps: [`Online: ${res.available.join(', ') || '(none)'}`] });
|
|
67
|
+
}
|
|
68
|
+
if (res.effective && !isAndroidEmulatorSerial(res.effective)) {
|
|
69
|
+
return qaError({
|
|
70
|
+
what: `Android target "${res.effective}" appears to be a real device. Swipium 1.0.0 supports Android Emulator only.`,
|
|
71
|
+
changedState: false,
|
|
72
|
+
retrySafe: true,
|
|
73
|
+
failureCode: 'BACKEND_UNSUPPORTED',
|
|
74
|
+
nextSteps: ['Start or create an Android Emulator, then retry with its emulator serial.'],
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (res.needSelection) {
|
|
78
|
+
return qaError({ what: 'Multiple devices online — choose one', changedState: false, retrySafe: true, nextSteps: [`Re-call with device="<serial>". Online: ${res.available.join(', ')}`] });
|
|
79
|
+
}
|
|
80
|
+
const needBoot = !res.effective;
|
|
81
|
+
// ---- external-APK detection (needs the file hash for the plan consent) ----
|
|
82
|
+
let externalApk;
|
|
83
|
+
if (!bindOnly && apkPath && !apkPath.startsWith(session.root + sep)) {
|
|
84
|
+
externalApk = { path: apkPath, sha256: createHash('sha256').update(readFileSync(apkPath)).digest('hex') };
|
|
85
|
+
}
|
|
86
|
+
// ---- COMBINED plan consent (Phase 2.1): all privileged steps approved at once,
|
|
87
|
+
// so boot + external-APK don't ping-pong across calls. ----
|
|
88
|
+
const hl = headless ?? true;
|
|
89
|
+
const avds = needBoot ? await listAvds() : [];
|
|
90
|
+
const bootTarget = avd ?? avds[0];
|
|
91
|
+
if (needBoot && avds.length === 0) {
|
|
92
|
+
return qaError({ what: 'No device online and no AVD to boot', changedState: false, retrySafe: true, nextSteps: ['Create an AVD (see qa_doctor), or start a device, then retry.'] });
|
|
93
|
+
}
|
|
94
|
+
const plan = [
|
|
95
|
+
needBoot ? `boot_emulator(${bootTarget}${hl ? ',headless' : ',windowed'})` : null,
|
|
96
|
+
rnDebug ? 'set_metro_reverse' : null,
|
|
97
|
+
externalApk ? `install_external_apk(${externalApk.sha256.slice(0, 12)}…)` : (!bindOnly ? 'install_if_needed' : null),
|
|
98
|
+
bindOnly ? 'bind_only' : 'launch_app',
|
|
99
|
+
].filter(Boolean);
|
|
100
|
+
const privileged = needBoot || !!externalApk;
|
|
101
|
+
const planAffects = { plan, boot: needBoot ? { avd: bootTarget, headless: hl } : null, externalApkSha256: externalApk?.sha256 ?? null };
|
|
102
|
+
let mutationConsent;
|
|
103
|
+
let preparePlanMutation;
|
|
104
|
+
if (privileged) {
|
|
105
|
+
const gate = consumeConsent(consentId, approve, { action: 'prepare_plan', affects: planAffects });
|
|
106
|
+
if (!gate.approved) {
|
|
107
|
+
sessions.recordMutation(session, {
|
|
108
|
+
tool: 'qa_prepare_target',
|
|
109
|
+
action: 'prepare_plan',
|
|
110
|
+
risk: externalApk ? 'medium' : 'low',
|
|
111
|
+
target: planAffects,
|
|
112
|
+
consent: { required: true, approved: false, payloadHash: externalApk?.sha256 },
|
|
113
|
+
status: 'requested',
|
|
114
|
+
});
|
|
115
|
+
const lines = [
|
|
116
|
+
needBoot ? `• boot emulator "${bootTarget}" (${hl ? 'headless' : 'visible'}): emulator -avd ${bootTarget}${hl ? ' -no-window' : ''}` : '',
|
|
117
|
+
externalApk ? `• install EXTERNAL apk (outside project root), sha256 ${externalApk.sha256.slice(0, 16)}…: adb install -r -g ${externalApk.path}` : '',
|
|
118
|
+
].filter(Boolean).join('\n');
|
|
119
|
+
return requireConsent({
|
|
120
|
+
action: 'prepare_plan',
|
|
121
|
+
risk: externalApk ? 'medium' : 'low',
|
|
122
|
+
exactCommand: lines,
|
|
123
|
+
affects: planAffects,
|
|
124
|
+
explain: `This prepare needs ${privileged ? 'these privileged steps' : 'no consent'} (approved together so they don't re-prompt):\n${lines}\nPlan: ${plan.join(' → ')}`,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
mutationConsent = { required: true, consentId, approved: true, payloadHash: externalApk?.sha256 };
|
|
128
|
+
preparePlanMutation = { affects: planAffects, risk: externalApk ? 'medium' : 'low', consentId };
|
|
129
|
+
sessions.recordMutation(session, {
|
|
130
|
+
tool: 'qa_prepare_target',
|
|
131
|
+
action: 'prepare_plan',
|
|
132
|
+
risk: preparePlanMutation.risk,
|
|
133
|
+
target: planAffects,
|
|
134
|
+
consent: mutationConsent,
|
|
135
|
+
status: 'approved',
|
|
136
|
+
});
|
|
137
|
+
if (externalApk)
|
|
138
|
+
sessions.addEnvChange(session, `consented external-APK install (sha256 ${externalApk.sha256.slice(0, 12)}…)`);
|
|
139
|
+
}
|
|
140
|
+
// ---- FAST PATHS (synchronous) when no long op (no boot, no install) ----
|
|
141
|
+
if (!needBoot && res.effective) {
|
|
142
|
+
const serial = res.effective;
|
|
143
|
+
const driver = bindDevice(session, serial); // bind now (also fixes device:null across tools)
|
|
144
|
+
await driver.disableAnimations().catch(() => { });
|
|
145
|
+
// Set reverse for RN/Expo (cheap, non-destructive) so the bundle path is wired.
|
|
146
|
+
if (rnDebug && !(await reverseSet(serial))) {
|
|
147
|
+
try {
|
|
148
|
+
await driver.adbReverseMetro();
|
|
149
|
+
sessions.addEnvChange(session, 'set adb reverse tcp:8081');
|
|
150
|
+
}
|
|
151
|
+
catch { /* best-effort */ }
|
|
152
|
+
}
|
|
153
|
+
if (bindOnly) {
|
|
154
|
+
const rd = await metroReadiness(serial);
|
|
155
|
+
sessions.persist(session);
|
|
156
|
+
return qaOk({ device: serial, bound: true, metro: rd }, `Bound ${serial}.${rnDebug ? ` Metro: serving=${rd.serving} reverse=${rd.reverseSet} ready=${rd.ready}.` : ''} (bindOnly — no install/launch)`);
|
|
157
|
+
}
|
|
158
|
+
const installed = await driver.isInstalled(resolvedAppId);
|
|
159
|
+
if (installed && !force) {
|
|
160
|
+
// Launch gate (Phase 2.1, P1.5): refuse only if Metro is NOT SERVING. A missing
|
|
161
|
+
// reverse is not fatal — emulators reach the host via 10.0.2.2 — so serving is the
|
|
162
|
+
// real signal. allowLaunchWithoutMetro overrides.
|
|
163
|
+
if (rnDebug && !allowLaunchWithoutMetro) {
|
|
164
|
+
const rd = await metroReadiness(serial);
|
|
165
|
+
if (!rd.serving) {
|
|
166
|
+
return qaError({
|
|
167
|
+
what: `Debug RN/Expo build; Metro is not SERVING the bundle yet (listening=${rd.listening} reverse=${rd.reverseSet} serving=${rd.serving}). Launching now risks the "Unable to load script" RedBox.`,
|
|
168
|
+
changedState: false,
|
|
169
|
+
retrySafe: true,
|
|
170
|
+
nextSteps: [
|
|
171
|
+
'Start Metro/dev server manually, wait for serving=true, then re-run qa_prepare_target.',
|
|
172
|
+
'If this is a release build with an embedded bundle, OR you accept the risk, pass allowLaunchWithoutMetro:true.',
|
|
173
|
+
],
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (rnDebug && allowLaunchWithoutMetro)
|
|
178
|
+
sessions.addEnvChange(session, 'OVERRIDE allowLaunchWithoutMetro — launched without confirmed Metro readiness');
|
|
179
|
+
sessions.milestone(session, 'app_launch_start');
|
|
180
|
+
await driver.launchApp(resolvedAppId);
|
|
181
|
+
await new Promise((r) => setTimeout(r, 2500));
|
|
182
|
+
const foreground = await driver.foregroundOwner();
|
|
183
|
+
sessions.milestone(session, 'app_launch_end');
|
|
184
|
+
session.appId = resolvedAppId;
|
|
185
|
+
sessions.persist(session);
|
|
186
|
+
const launchedOk = foreground.startsWith(resolvedAppId);
|
|
187
|
+
sessions.recordMutation(session, {
|
|
188
|
+
tool: 'qa_prepare_target',
|
|
189
|
+
action: 'launch_app',
|
|
190
|
+
risk: 'low',
|
|
191
|
+
target: { device: serial, appId: resolvedAppId, foreground },
|
|
192
|
+
consent: { required: false, approved: true },
|
|
193
|
+
status: 'executed',
|
|
194
|
+
});
|
|
195
|
+
if (preparePlanMutation) {
|
|
196
|
+
sessions.recordMutation(session, {
|
|
197
|
+
tool: 'qa_prepare_target',
|
|
198
|
+
action: 'prepare_plan',
|
|
199
|
+
risk: preparePlanMutation.risk,
|
|
200
|
+
target: preparePlanMutation.affects,
|
|
201
|
+
consent: mutationConsent,
|
|
202
|
+
status: 'executed',
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return qaOk({ device: serial, appId: resolvedAppId, installed: 'already-present', foreground, launchedOk, launchedWithoutMetro: !!(rnDebug && allowLaunchWithoutMetro) || undefined }, `${launchedOk ? '✅' : '⚠️'} ${resolvedAppId} on ${serial} (already present); foreground=${foreground}.`);
|
|
206
|
+
}
|
|
207
|
+
// install needed → JOB
|
|
208
|
+
return startJob(sessions, session, driver, { needBoot: false, serial, resolvedAppId: resolvedAppId, apkPath, apk, force, rnDebug, allowLaunchWithoutMetro: !!allowLaunchWithoutMetro, mutationConsent, preparePlanMutation });
|
|
209
|
+
}
|
|
210
|
+
// ---- BOOT path → JOB (consent already granted via the plan) ----
|
|
211
|
+
const driver = new DirectDriver();
|
|
212
|
+
return startJob(sessions, session, driver, { needBoot: true, bootTarget, resolvedAppId: resolvedAppId, apkPath, apk, force, headless: hl, rnDebug, allowLaunchWithoutMetro: !!allowLaunchWithoutMetro, bindOnly: !!bindOnly, mutationConsent, preparePlanMutation });
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
function startJob(sessions, session, driver, a) {
|
|
216
|
+
const job = sessions.createJob(session, a.needBoot ? 'boot+install' : 'install');
|
|
217
|
+
void runHeavy(sessions, session, driver, job, a);
|
|
218
|
+
return qaOk({ jobId: job.jobId, status: 'running', kind: job.kind }, `Started ${job.kind} as job ${job.jobId}. Poll with qa_job_status { sessionId:"${session.id}", jobId:"${job.jobId}" }.`);
|
|
219
|
+
}
|
|
220
|
+
async function runHeavy(sessions, session, driver, job, a) {
|
|
221
|
+
const signal = sessions.abortSignal(session, job.jobId);
|
|
222
|
+
driver.setSignal(signal); // cancel kills in-flight adb children (install/dump/etc.)
|
|
223
|
+
const upd = (patch) => {
|
|
224
|
+
sessions.updateJobIfRunning(session, job, patch);
|
|
225
|
+
};
|
|
226
|
+
// Delegate to the shared service so qa_test_this execute mode runs the identical path.
|
|
227
|
+
const res = await prepareAndroid(sessions, session, driver, a, { signal, onProgress: (p) => upd({ progress: p }) });
|
|
228
|
+
if (res.aborted)
|
|
229
|
+
return; // cancelJob already set the terminal status; do not overwrite
|
|
230
|
+
if (!res.ok) {
|
|
231
|
+
if (a.preparePlanMutation) {
|
|
232
|
+
sessions.recordMutation(session, {
|
|
233
|
+
tool: 'qa_prepare_target',
|
|
234
|
+
action: 'prepare_plan',
|
|
235
|
+
risk: a.preparePlanMutation.risk,
|
|
236
|
+
target: a.preparePlanMutation.affects,
|
|
237
|
+
consent: a.mutationConsent,
|
|
238
|
+
status: 'blocked',
|
|
239
|
+
detail: res.error ?? res.failureCode ?? 'prepare failed',
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
upd({ status: 'failed', error: res.error ?? 'prepare failed', endedAt: Date.now() });
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (a.preparePlanMutation) {
|
|
246
|
+
sessions.recordMutation(session, {
|
|
247
|
+
tool: 'qa_prepare_target',
|
|
248
|
+
action: 'prepare_plan',
|
|
249
|
+
risk: a.preparePlanMutation.risk,
|
|
250
|
+
target: a.preparePlanMutation.affects,
|
|
251
|
+
consent: a.mutationConsent,
|
|
252
|
+
status: 'executed',
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
upd({ status: 'done', progress: 'done', result: res.result, resultText: res.resultText, endedAt: Date.now() });
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=prepareTarget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prepareTarget.js","sourceRoot":"","sources":["../../src/tools/prepareTarget.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// qa_report — thin wrapper around the report service (Phase 3.2 Milestone B). All assembly lives in
|
|
2
|
+
// src/services/report.ts so qa_test_this execute produces the identical report artifact in every
|
|
3
|
+
// terminal state. This tool resolves the session, runs the service, and surfaces its result.
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { qaOk, qaError } from '../lib/result.js';
|
|
6
|
+
import { generateSessionReport } from '../services/report.js';
|
|
7
|
+
export function registerReport(server, sessions) {
|
|
8
|
+
server.registerTool('qa_report', {
|
|
9
|
+
title: 'Build a session report',
|
|
10
|
+
description: 'Assemble a report for the session: an executive summary (release risk ship/caution/block + the single next action), native/app health, structured outcomes by workflow, findings, artifact links, env mutations + restoration, workarounds + provided inputs. Returns a summary + resource URIs (not the whole bundle); saves the full report as an artifact. Pass `format` to also export: markdown (issue-ready), json, junit (CI), playwright (dashboard JSON), or flow — returns the export artifact URI. qa_test_this execute generates this report automatically at the end of a run.',
|
|
11
|
+
inputSchema: {
|
|
12
|
+
sessionId: z.string(),
|
|
13
|
+
format: z.enum(['summary', 'markdown', 'json', 'junit', 'flow', 'playwright']).optional().describe('Also emit this export as an artifact (default summary only). markdown = issue-ready; junit = CI; playwright = Playwright-style dashboard JSON; flow = a replayable flow drafted from the actions recorded this run.'),
|
|
14
|
+
baseline: z.string().optional().describe('Optional baseline report.json path. When provided, qa_report adds comparison and PR-summary links.'),
|
|
15
|
+
trendRoot: z.string().optional().describe('Optional project root containing .swipium/runs or legacy .swipium/ci history. When provided, qa_report adds trend/flake context.'),
|
|
16
|
+
},
|
|
17
|
+
}, async ({ sessionId, format, baseline, trendRoot }) => {
|
|
18
|
+
const session = sessions.get(sessionId);
|
|
19
|
+
if (!session) {
|
|
20
|
+
return qaError({ what: `Unknown sessionId ${sessionId}`, changedState: false, retrySafe: true, nextSteps: ['Call qa_start_session first.'] });
|
|
21
|
+
}
|
|
22
|
+
// format:"flow" with no recorded actions is a user error (kept from the original tool).
|
|
23
|
+
if (format === 'flow' && !session.recordedActions.length) {
|
|
24
|
+
return qaError({ what: 'No actions were recorded this run, so there is no flow to export', changedState: false, retrySafe: true, nextSteps: ['Drive the app with qa_act first, then qa_report { format: "flow" } — or use qa_flow_generate.'] });
|
|
25
|
+
}
|
|
26
|
+
const r = await generateSessionReport(sessions, session, {
|
|
27
|
+
format,
|
|
28
|
+
baseline,
|
|
29
|
+
trendRoot,
|
|
30
|
+
});
|
|
31
|
+
return qaOk({
|
|
32
|
+
...r.report,
|
|
33
|
+
reportUri: r.reportUri,
|
|
34
|
+
manifestUri: r.manifestUri,
|
|
35
|
+
manifest: r.manifest,
|
|
36
|
+
dumpUri: r.dumpUri,
|
|
37
|
+
...(r.reportLinks ? { reportLinks: r.reportLinks } : {}),
|
|
38
|
+
...(r.exportUri ? { exportUri: r.exportUri, exportFormat: r.exportFormat } : {}),
|
|
39
|
+
}, r.summaryText);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/tools/report.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,iGAAiG;AACjG,6FAA6F;AAE7F,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAG9D,MAAM,UAAU,cAAc,CAAC,MAAiB,EAAE,QAAsB;IACtE,MAAM,CAAC,YAAY,CACjB,WAAW,EACX;QACE,KAAK,EAAE,wBAAwB;QAC/B,WAAW,EACT,6jBAA6jB;QAC/jB,WAAW,EAAE;YACX,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;YACrB,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qNAAqN,CAAC;YACzT,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oGAAoG,CAAC;YAC9I,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kIAAkI,CAAC;SAC9K;KACF,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,EAAE;QACnD,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,OAAO,CAAC,EAAE,IAAI,EAAE,qBAAqB,SAAS,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,8BAA8B,CAAC,EAAE,CAAC,CAAC;QAChJ,CAAC;QAED,wFAAwF;QACxF,IAAI,MAAM,KAAK,MAAM,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC;YACzD,OAAO,OAAO,CAAC,EAAE,IAAI,EAAE,kEAAkE,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,+FAA+F,CAAC,EAAE,CAAC,CAAC;QACnP,CAAC;QAED,MAAM,CAAC,GAAG,MAAM,qBAAqB,CAAC,QAAQ,EAAE,OAAO,EAAE;YACvD,MAAM;YACN,QAAQ;YACR,SAAS;SACV,CAAC,CAAC;QACH,OAAO,IAAI,CACT;YACE,GAAG,CAAC,CAAC,MAAM;YACX,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACxD,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,YAAY,EAAE,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACjF,EACD,CAAC,CAAC,WAAW,CACd,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Screen-recording lifecycle helpers retained so shutdown/report paths stay idempotent.
|
|
2
|
+
import { run } from '../lib/spawn.js';
|
|
3
|
+
const active = new Map();
|
|
4
|
+
/** Is a recording active for this session? Used by qa_report. */
|
|
5
|
+
export function activeRecording(sessionId) {
|
|
6
|
+
const r = active.get(sessionId);
|
|
7
|
+
return r ? { backend: r.backend, seconds: Math.round((Date.now() - r.startedAt) / 1000) } : undefined;
|
|
8
|
+
}
|
|
9
|
+
/** Best-effort: stop every active recorder so the server does not leave a device recording. */
|
|
10
|
+
export async function stopAllRecordings() {
|
|
11
|
+
for (const [, rec] of active) {
|
|
12
|
+
try {
|
|
13
|
+
if (rec.backend === 'direct') {
|
|
14
|
+
await run('adb', ['-s', rec.serial, 'shell', 'pkill', '-INT', 'screenrecord'], { timeoutMs: 5000 }).catch(() => { });
|
|
15
|
+
}
|
|
16
|
+
rec.child.kill('SIGINT');
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
/* best-effort */
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
active.clear();
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=screenRecord.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenRecord.js","sourceRoot":"","sources":["../../src/tools/screenRecord.ts"],"names":[],"mappings":"AAAA,wFAAwF;AAGxF,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAStC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAqB,CAAC;AAE5C,iEAAiE;AACjE,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AACxG,CAAC;AAED,+FAA+F;AAC/F,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,KAAK,MAAM,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,IAAI,GAAG,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;gBAC7B,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACtH,CAAC;YACD,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,iBAAiB;QACnB,CAAC;IACH,CAAC;IACD,MAAM,CAAC,KAAK,EAAE,CAAC;AACjB,CAAC"}
|