swipium 1.0.0 → 1.1.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 (74) hide show
  1. package/README.md +17 -27
  2. package/dist/appMap/codeIndex.js +37 -5
  3. package/dist/appMap/codeIndex.js.map +1 -1
  4. package/dist/appMap/query.js +22 -4
  5. package/dist/appMap/query.js.map +1 -1
  6. package/dist/build/plan.js +2 -1
  7. package/dist/build/plan.js.map +1 -1
  8. package/dist/drivers/WdaDriver.js +42 -6
  9. package/dist/drivers/WdaDriver.js.map +1 -1
  10. package/dist/explore/candidates.js +17 -0
  11. package/dist/explore/candidates.js.map +1 -1
  12. package/dist/explore/runner.js +29 -2
  13. package/dist/explore/runner.js.map +1 -1
  14. package/dist/lib/wda.js +70 -2
  15. package/dist/lib/wda.js.map +1 -1
  16. package/dist/oracle/failures.js +1 -0
  17. package/dist/oracle/failures.js.map +1 -1
  18. package/dist/orchestration/envelope.js +1 -0
  19. package/dist/orchestration/envelope.js.map +1 -1
  20. package/dist/prompts/index.js +10 -0
  21. package/dist/prompts/index.js.map +1 -1
  22. package/dist/report/export.js +9 -0
  23. package/dist/report/export.js.map +1 -1
  24. package/dist/server.js +29 -2
  25. package/dist/server.js.map +1 -1
  26. package/dist/services/build.js +15 -2
  27. package/dist/services/build.js.map +1 -1
  28. package/dist/services/prepareIos.js +34 -4
  29. package/dist/services/prepareIos.js.map +1 -1
  30. package/dist/services/report.js +47 -5
  31. package/dist/services/report.js.map +1 -1
  32. package/dist/session/store.js.map +1 -1
  33. package/dist/suite/pom.js +24 -9
  34. package/dist/suite/pom.js.map +1 -1
  35. package/dist/tools/act.js +23 -2
  36. package/dist/tools/act.js.map +1 -1
  37. package/dist/tools/appControl.js +176 -0
  38. package/dist/tools/appControl.js.map +1 -0
  39. package/dist/tools/capabilities.js +29 -0
  40. package/dist/tools/capabilities.js.map +1 -1
  41. package/dist/tools/clearOverlay.js +56 -21
  42. package/dist/tools/clearOverlay.js.map +1 -1
  43. package/dist/tools/device.js +151 -0
  44. package/dist/tools/device.js.map +1 -0
  45. package/dist/tools/doctor.js +174 -52
  46. package/dist/tools/doctor.js.map +1 -1
  47. package/dist/tools/history.js +56 -0
  48. package/dist/tools/history.js.map +1 -0
  49. package/dist/tools/metro.js +260 -0
  50. package/dist/tools/metro.js.map +1 -0
  51. package/dist/tools/network.js +97 -5
  52. package/dist/tools/network.js.map +1 -1
  53. package/dist/tools/permissions.js +149 -0
  54. package/dist/tools/permissions.js.map +1 -0
  55. package/dist/tools/screenInfo.js +70 -0
  56. package/dist/tools/screenInfo.js.map +1 -0
  57. package/dist/tools/screenRecord.js +215 -6
  58. package/dist/tools/screenRecord.js.map +1 -1
  59. package/dist/tools/seed.js +101 -0
  60. package/dist/tools/seed.js.map +1 -0
  61. package/dist/tools/state.js +83 -0
  62. package/dist/tools/state.js.map +1 -1
  63. package/dist/tools/testThis.js +55 -6
  64. package/dist/tools/testThis.js.map +1 -1
  65. package/dist/tools/visual.js +139 -0
  66. package/dist/tools/visual.js.map +1 -0
  67. package/dist/tools/visualText.js +61 -0
  68. package/dist/tools/visualText.js.map +1 -0
  69. package/dist/version.js +25 -3
  70. package/dist/version.js.map +1 -1
  71. package/docs/README.md +4 -4
  72. package/docs/mcp-server.md +5 -3
  73. package/docs/tools.md +68 -4
  74. package/package.json +2 -2
@@ -0,0 +1,149 @@
1
+ // qa_permissions (PHASE3-PLAN §4.2) — list / grant / revoke Android runtime permissions without
2
+ // raw adb. grant is low-risk (commonly used to pre-approve and skip a dialog) and only logged;
3
+ // revoke can break app state, so it is consent-gated. Every mutation is recorded as an
4
+ // environment change for qa_report.
5
+ import { z } from 'zod';
6
+ import { qaOk, qaError } from '../lib/result.js';
7
+ import { requireConsent, consumeConsent } from '../consent/consent.js';
8
+ import { getDriver } from '../session/attach.js';
9
+ import { listRuntimePermissions, grantPermission, revokePermission } from '../lib/device.js';
10
+ export function registerPermissions(server, sessions) {
11
+ server.registerTool('qa_permissions', {
12
+ title: 'App permissions',
13
+ description: 'Inspect or change a package\'s Android runtime permissions. action: list (granted/denied), grant (pre-approve to skip a dialog — logged), revoke (consent-gated, can break app state — logged). `package` defaults to the session\'s appId; `permission` is a full android.permission.* name (e.g. android.permission.ACCESS_FINE_LOCATION).',
14
+ inputSchema: {
15
+ sessionId: z.string(),
16
+ action: z.enum(['list', 'grant', 'revoke']),
17
+ package: z.string().optional().describe('Target package (default: the session appId).'),
18
+ permission: z.string().optional().describe('Full permission name, required for grant/revoke.'),
19
+ consentId: z.string().optional(),
20
+ approve: z.boolean().optional(),
21
+ },
22
+ }, async ({ sessionId, action, package: pkgArg, permission, consentId, approve }) => {
23
+ const session = sessions.get(sessionId);
24
+ const { driver } = session ? await getDriver(session) : { driver: undefined };
25
+ const serial = driver?.currentDevice();
26
+ if (!session || !driver || !serial) {
27
+ return qaError({ what: 'No device attached to this session', changedState: false, retrySafe: true, nextSteps: ['Call qa_prepare_target first.'] });
28
+ }
29
+ const pkg = pkgArg ?? session.appId;
30
+ if (!pkg) {
31
+ return qaError({ what: 'No package given and the session has no appId', changedState: false, retrySafe: true, nextSteps: ['Pass package, or qa_prepare_target to set the appId.'] });
32
+ }
33
+ if (action === 'list') {
34
+ const perms = await listRuntimePermissions(serial, pkg);
35
+ return qaOk({ package: pkg, ...perms }, `${pkg}: ${perms.granted.length} granted, ${perms.denied.length} denied\n granted: ${perms.granted.join(', ') || '—'}\n denied: ${perms.denied.join(', ') || '—'}`);
36
+ }
37
+ if (!permission) {
38
+ return qaError({ what: `${action} requires a permission name`, changedState: false, retrySafe: true, nextSteps: ['Pass permission="android.permission.…" (qa_permissions list to see them).'] });
39
+ }
40
+ if (action === 'revoke') {
41
+ // Revoking can crash/break the app — gate behind consent.
42
+ const gate = consumeConsent(consentId, approve, { action: 'permission_revoke', affects: { package: pkg, permission } });
43
+ if (!gate.approved) {
44
+ sessions.recordMutation(session, {
45
+ tool: 'qa_permissions',
46
+ action: 'permission_revoke',
47
+ risk: 'medium',
48
+ target: { package: pkg, permission },
49
+ consent: { required: true, approved: false },
50
+ status: 'requested',
51
+ });
52
+ return requireConsent({
53
+ action: 'permission_revoke',
54
+ risk: 'medium',
55
+ exactCommand: `adb -s ${serial} shell pm revoke ${pkg} ${permission}`,
56
+ affects: { package: pkg, permission },
57
+ explain: `Revoke ${permission} from ${pkg}? This can change app behavior or crash a poorly-guarded app.`,
58
+ });
59
+ }
60
+ sessions.recordMutation(session, {
61
+ tool: 'qa_permissions',
62
+ action: 'permission_revoke',
63
+ risk: 'medium',
64
+ target: { package: pkg, permission },
65
+ consent: { required: true, consentId, approved: true },
66
+ status: 'approved',
67
+ });
68
+ try {
69
+ await revokePermission(serial, pkg, permission);
70
+ }
71
+ catch (e) {
72
+ sessions.recordMutation(session, {
73
+ tool: 'qa_permissions',
74
+ action: 'permission_revoke',
75
+ risk: 'medium',
76
+ target: { package: pkg, permission },
77
+ consent: { required: true, consentId, approved: true },
78
+ status: 'blocked',
79
+ detail: String(e),
80
+ });
81
+ return qaError({ what: `revoke failed: ${String(e)}`, changedState: false, retrySafe: true, nextSteps: ['Check the permission name; some are not revocable.'] });
82
+ }
83
+ sessions.addEnvChange(session, `permission revoke ${permission} from ${pkg}`);
84
+ sessions.recordMutation(session, {
85
+ tool: 'qa_permissions',
86
+ action: 'permission_revoke',
87
+ risk: 'medium',
88
+ target: { package: pkg, permission },
89
+ consent: { required: true, consentId, approved: true },
90
+ status: 'executed',
91
+ });
92
+ return qaOk({ package: pkg, permission, action }, `revoked ${permission} from ${pkg}`);
93
+ }
94
+ // grant — low risk, but still a state mutation (it can hide a permission-dialog bug), so
95
+ // it is consent-gated (low) and always reported, never silent.
96
+ const grantGate = consumeConsent(consentId, approve, { action: 'permission_grant', affects: { package: pkg, permission } });
97
+ if (!grantGate.approved) {
98
+ sessions.recordMutation(session, {
99
+ tool: 'qa_permissions',
100
+ action: 'permission_grant',
101
+ risk: 'low',
102
+ target: { package: pkg, permission },
103
+ consent: { required: true, approved: false },
104
+ status: 'requested',
105
+ });
106
+ return requireConsent({
107
+ action: 'permission_grant',
108
+ risk: 'low',
109
+ exactCommand: `adb -s ${serial} shell pm grant ${pkg} ${permission}`,
110
+ affects: { package: pkg, permission },
111
+ explain: `Pre-grant ${permission} to ${pkg}? This skips the runtime permission dialog — convenient, but it can mask a permission-prompt bug.`,
112
+ });
113
+ }
114
+ sessions.recordMutation(session, {
115
+ tool: 'qa_permissions',
116
+ action: 'permission_grant',
117
+ risk: 'low',
118
+ target: { package: pkg, permission },
119
+ consent: { required: true, consentId, approved: true },
120
+ status: 'approved',
121
+ });
122
+ try {
123
+ await grantPermission(serial, pkg, permission);
124
+ }
125
+ catch (e) {
126
+ sessions.recordMutation(session, {
127
+ tool: 'qa_permissions',
128
+ action: 'permission_grant',
129
+ risk: 'low',
130
+ target: { package: pkg, permission },
131
+ consent: { required: true, consentId, approved: true },
132
+ status: 'blocked',
133
+ detail: String(e),
134
+ });
135
+ return qaError({ what: `grant failed: ${String(e)}`, changedState: false, retrySafe: true, nextSteps: ['Check the permission name; only runtime permissions are grantable.'] });
136
+ }
137
+ sessions.addEnvChange(session, `permission grant ${permission} to ${pkg}`);
138
+ sessions.recordMutation(session, {
139
+ tool: 'qa_permissions',
140
+ action: 'permission_grant',
141
+ risk: 'low',
142
+ target: { package: pkg, permission },
143
+ consent: { required: true, consentId, approved: true },
144
+ status: 'executed',
145
+ });
146
+ return qaOk({ package: pkg, permission, action }, `granted ${permission} to ${pkg}`);
147
+ });
148
+ }
149
+ //# sourceMappingURL=permissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/tools/permissions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ // qa_screen_info — device + screen metadata for coordinate-driven (visual-fallback) work,
2
+ // so the agent doesn't shell out to `adb shell wm size/density` or measure screenshots.
3
+ import { z } from 'zod';
4
+ import { qaOk, qaError } from '../lib/result.js';
5
+ import { getOrientation } from '../lib/device.js';
6
+ import { getDriver } from '../session/attach.js';
7
+ export function registerScreenInfo(server, sessions) {
8
+ server.registerTool('qa_screen_info', {
9
+ title: 'Screen info',
10
+ description: 'Device + screen metadata for coordinate work: width/height (px), density (dpi), session mode (structured | visual-fallback), the latest screenshot URI, and budget counters. Use this in visual-fallback mode to place coordinate taps instead of shelling out to wm size/density.',
11
+ inputSchema: { sessionId: z.string() },
12
+ }, async ({ sessionId }) => {
13
+ const session = sessions.get(sessionId);
14
+ const { driver } = session ? await getDriver(session) : { driver: undefined };
15
+ if (!session || !driver) {
16
+ return qaError({
17
+ what: 'No device attached to this session',
18
+ changedState: false,
19
+ retrySafe: true,
20
+ nextSteps: ['Call qa_prepare_target first.'],
21
+ });
22
+ }
23
+ const serial = driver.currentDevice();
24
+ const [size, density, orient] = await Promise.all([driver.screenSize(), driver.screenDensity(), serial ? getOrientation(serial).catch(() => null) : Promise.resolve(null)]);
25
+ const orientation = orient ? (orient.rotation === 1 || orient.rotation === 3 ? 'landscape' : 'portrait') : 'unknown';
26
+ const lastShot = [...session.artifacts].reverse().find((a) => a.kind === 'screenshot');
27
+ // Coordinate landmarks (P1.7) for visual-fallback taps — so the agent places taps from
28
+ // named anchors instead of guessing pixels off a screenshot.
29
+ let landmarks;
30
+ let bands;
31
+ if (size) {
32
+ const w = size.width;
33
+ const h = size.height;
34
+ const X = (fx) => Math.round(w * fx);
35
+ const Y = (fy) => Math.round(h * fy);
36
+ landmarks = {
37
+ topNavCenter: [X(0.5), Y(0.06)],
38
+ bottomNavCenter: [X(0.5), Y(0.96)],
39
+ center: [X(0.5), Y(0.5)],
40
+ topLeft: [X(0.08), Y(0.06)],
41
+ topRight: [X(0.92), Y(0.06)],
42
+ bottomLeft: [X(0.08), Y(0.96)],
43
+ bottomRight: [X(0.92), Y(0.96)],
44
+ primaryCtaArea: [X(0.5), Y(0.88)], // most apps put the main CTA in the bottom band
45
+ };
46
+ bands = {
47
+ topNav: { yFrom: 0, yTo: Y(0.12) },
48
+ content: { yFrom: Y(0.12), yTo: Y(0.85) },
49
+ bottomNav: { yFrom: Y(0.9), yTo: h },
50
+ ctaBand: { yFrom: Y(0.78), yTo: Y(0.95) },
51
+ };
52
+ }
53
+ return qaOk({
54
+ device: session.device ?? null,
55
+ screen: size ? { width: size.width, height: size.height, density } : null,
56
+ orientation,
57
+ coordinateNote: 'landmarks/bands are DEVICE pixels (origin top-left) — pass directly as qa_act {x,y}. For screenshot-space hits (qa_visual find_image) convert with coordinateSpace.scale.',
58
+ mode: session.mode,
59
+ headless: session.headless ?? null,
60
+ latestScreenshot: lastShot?.uri ?? null,
61
+ landmarks,
62
+ bands,
63
+ counters: session.counters,
64
+ budget: session.budget,
65
+ }, `device=${session.device} screen=${size ? `${size.width}x${size.height}@${density ?? '?'}dpi` : 'unknown'} mode=${session.mode}` +
66
+ (landmarks ? `\nlandmarks: center=${landmarks.center} bottomNav=${landmarks.bottomNavCenter} cta=${landmarks.primaryCtaArea}` : '') +
67
+ (lastShot ? `\nlatest screenshot: ${lastShot.uri}` : '\n(no screenshot yet — call qa_screenshot)'));
68
+ });
69
+ }
70
+ //# sourceMappingURL=screenInfo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenInfo.js","sourceRoot":"","sources":["../../src/tools/screenInfo.ts"],"names":[],"mappings":"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"}
@@ -1,18 +1,34 @@
1
- // Screen-recording lifecycle helpers retained so shutdown/report paths stay idempotent.
1
+ // qa_screen_record (PHASE3-PLAN §4.2 / NEXT-PLAN Fix 5) — capture a screen video on Android
2
+ // (adb screenrecord) or the iOS Simulator (simctl io recordVideo). start spawns the recorder
3
+ // (consent-gated, sensitive-screen warning); status reports whether one is active; stop finalizes
4
+ // it gracefully (SIGINT so the mp4 isn't corrupted), saves an artifact, and cleans up. Active
5
+ // recordings are tracked in a module-level map (the child handle is live-only, like the driver);
6
+ // they are best-effort finalized on server shutdown and surfaced by qa_report.
7
+ import { z } from 'zod';
8
+ import { spawn } from 'node:child_process';
9
+ import { readFileSync, rmSync } from 'node:fs';
10
+ import { tmpdir } from 'node:os';
11
+ import { join } from 'node:path';
12
+ import { qaOk, qaError } from '../lib/result.js';
13
+ import { requireConsent, consumeConsent } from '../consent/consent.js';
14
+ import { sensitiveRefusal } from '../lib/sensitive.js';
2
15
  import { run } from '../lib/spawn.js';
16
+ import { getDriver } from '../session/attach.js';
3
17
  const active = new Map();
4
- /** Is a recording active for this session? Used by qa_report. */
18
+ export function shouldSaveRecording(saveMode, failed) {
19
+ return saveMode === 'always' || failed === true;
20
+ }
21
+ /** Is a recording active for this session? (used by qa_report). */
5
22
  export function activeRecording(sessionId) {
6
23
  const r = active.get(sessionId);
7
- return r ? { backend: r.backend, seconds: Math.round((Date.now() - r.startedAt) / 1000) } : undefined;
24
+ return r ? { backend: r.backend, seconds: Math.round(((r.endedAt ?? Date.now()) - r.startedAt) / 1000) } : undefined;
8
25
  }
9
- /** Best-effort: stop every active recorder so the server does not leave a device recording. */
26
+ /** Best-effort: stop every active recorder so the server doesn't leave a device recording. */
10
27
  export async function stopAllRecordings() {
11
28
  for (const [, rec] of active) {
12
29
  try {
13
- if (rec.backend === 'direct') {
30
+ if (rec.backend === 'direct')
14
31
  await run('adb', ['-s', rec.serial, 'shell', 'pkill', '-INT', 'screenrecord'], { timeoutMs: 5000 }).catch(() => { });
15
- }
16
32
  rec.child.kill('SIGINT');
17
33
  }
18
34
  catch {
@@ -21,4 +37,197 @@ export async function stopAllRecordings() {
21
37
  }
22
38
  active.clear();
23
39
  }
40
+ export function registerScreenRecord(server, sessions) {
41
+ server.registerTool('qa_screen_record', {
42
+ title: 'Record the screen',
43
+ description: 'Record a screen video (Android emulator/device, iOS Simulator, or WDA-backed iOS Simulator). action:"start" begins recording (consent-gated — it captures whatever is on screen, including anything sensitive; avoid password/OTP screens); action:"status" reports whether one is active; action:"stop" finalizes it and saves an mp4 artifact. Use save:"on_failure" on start plus failed:false on stop to discard passing-run videos in CI. Android recordings auto-stop after ~3 minutes. One recording per session at a time.',
44
+ inputSchema: {
45
+ sessionId: z.string(),
46
+ action: z.enum(['start', 'status', 'stop']),
47
+ save: z.enum(['always', 'on_failure']).optional().describe('Recording retention mode for action:"start". Defaults to always. Use on_failure in CI to discard videos when stop is called with failed:false.'),
48
+ failed: z.boolean().optional().describe('For action:"stop" with save:"on_failure": true saves the video, false discards it.'),
49
+ consentId: z.string().optional(),
50
+ approve: z.boolean().optional(),
51
+ },
52
+ }, async ({ sessionId, action, save, failed, consentId, approve }) => {
53
+ const session = sessions.get(sessionId);
54
+ const { driver } = session ? await getDriver(session) : { driver: undefined };
55
+ const serial = driver?.currentDevice();
56
+ if (!session || !driver || !serial) {
57
+ return qaError({ what: 'No device attached to this session', changedState: false, retrySafe: true, nextSteps: ['Call qa_prepare_target / qa_ios boot first.'] });
58
+ }
59
+ const backend = driver.kind === 'direct' ? 'direct' : driver.kind === 'simulator' ? 'simulator' : driver.kind === 'wda' ? 'wda_simulator' : null;
60
+ if (!backend) {
61
+ return qaError({ what: 'Screen recording is not supported on this backend', changedState: false, retrySafe: false, failureCode: 'BACKEND_UNSUPPORTED', nextSteps: ['Use the Android, iOS-simulator, or WDA-backed iOS simulator backend.'] });
62
+ }
63
+ // ---- status ----
64
+ if (action === 'status') {
65
+ const r = active.get(sessionId);
66
+ // The recorder process may have ended on its own (the Android `--time-limit 180` cap)
67
+ // while the entry is still held so `stop` can pull and save the file. Report that
68
+ // honestly instead of implying it's still capturing.
69
+ const ended = !!r?.endedAt;
70
+ const seconds = r ? Math.round(((r.endedAt ?? Date.now()) - r.startedAt) / 1000) : 0;
71
+ return qaOk({ recording: !!r, capturing: !!r && !ended, autoStopped: ended, backend: r?.backend ?? null, saveMode: r?.saveMode ?? null, seconds }, r
72
+ ? ended
73
+ ? `recording auto-stopped (${r.backend}, ${r.saveMode}, ~${seconds}s; Android time-limit reached) — qa_screen_record { action: "stop", failed:<bool> } to save the mp4.`
74
+ : `recording active (${r.backend}, ${r.saveMode}, ~${seconds}s) — qa_screen_record { action: "stop", failed:<bool> } to finalize.`
75
+ : 'no active recording.');
76
+ }
77
+ // ---- start ----
78
+ if (action === 'start') {
79
+ if (session.sensitive)
80
+ return sensitiveRefusal('Screen recording');
81
+ if (active.has(sessionId)) {
82
+ return qaError({ what: 'A recording is already in progress for this session', changedState: false, retrySafe: true, nextSteps: ['Call qa_screen_record { action: "stop" } first (or "status").'] });
83
+ }
84
+ const saveMode = save ?? 'always';
85
+ const gate = consumeConsent(consentId, approve, { action: 'screen_record', affects: { device: serial } });
86
+ if (!gate.approved) {
87
+ sessions.recordMutation(session, {
88
+ tool: 'qa_screen_record',
89
+ action: 'screen_record',
90
+ risk: 'medium',
91
+ target: { device: serial, backend, saveMode },
92
+ consent: { required: true, approved: false },
93
+ status: 'requested',
94
+ });
95
+ const cmd = backend === 'direct' ? `adb -s ${serial} shell screenrecord --time-limit 180 /sdcard/…mp4` : `xcrun simctl io ${serial} recordVideo …mp4`;
96
+ return requireConsent({ action: 'screen_record', risk: 'medium', exactCommand: cmd, affects: { device: serial }, explain: 'Record the device screen to a video? It captures everything shown — do NOT record password/OTP/payment screens.' });
97
+ }
98
+ sessions.recordMutation(session, {
99
+ tool: 'qa_screen_record',
100
+ action: 'screen_record',
101
+ risk: 'medium',
102
+ target: { device: serial, backend, saveMode },
103
+ consent: { required: true, consentId, approved: true },
104
+ status: 'approved',
105
+ });
106
+ let rec;
107
+ if (backend === 'direct') {
108
+ const remotePath = `/sdcard/swipium-rec-${Date.now()}.mp4`;
109
+ const child = spawn('adb', ['-s', serial, 'shell', 'screenrecord', '--time-limit', '180', remotePath], { stdio: 'ignore' });
110
+ child.unref();
111
+ rec = { child, backend, serial, saveMode, remotePath, startedAt: Date.now() };
112
+ }
113
+ else {
114
+ const hostPath = join(tmpdir(), `swipium-iosrec-${Date.now()}.mp4`);
115
+ const child = spawn('xcrun', ['simctl', 'io', serial, 'recordVideo', '--codec', 'h264', hostPath], { stdio: 'ignore' });
116
+ child.unref();
117
+ rec = { child, backend, serial, saveMode, hostPath, startedAt: Date.now() };
118
+ }
119
+ active.set(sessionId, rec);
120
+ // Mark when the recorder exits on its own (Android `--time-limit 180`) so `status`
121
+ // stops claiming it's still capturing. The entry stays so `stop` can still save the file.
122
+ rec.child.once('exit', () => {
123
+ if (!rec.endedAt)
124
+ rec.endedAt = Date.now();
125
+ });
126
+ sessions.addEnvChange(session, `screen_record started (${backend}, ${saveMode})`);
127
+ sessions.recordMutation(session, {
128
+ tool: 'qa_screen_record',
129
+ action: 'screen_record',
130
+ risk: 'medium',
131
+ target: { device: serial, backend, saveMode },
132
+ consent: { required: true, consentId, approved: true },
133
+ status: 'executed',
134
+ detail: 'recording started',
135
+ });
136
+ return qaOk({ recording: true, backend, saveMode }, `recording started (${backend}, ${saveMode})${backend === 'direct' ? ' (auto-stops after ~3 min)' : ''} — call qa_screen_record { action: "stop", failed:<bool> } to finalize${saveMode === 'on_failure' ? ' and keep only on failure' : ' and save the video'}.`);
137
+ }
138
+ // ---- stop ----
139
+ const rec = active.get(sessionId);
140
+ if (!rec) {
141
+ return qaError({ what: 'No active recording for this session', changedState: false, retrySafe: true, nextSteps: ['Start one with qa_screen_record { action: "start" } (or check "status").'] });
142
+ }
143
+ active.delete(sessionId);
144
+ const localTmp = join(tmpdir(), `swipium-recpull-${Date.now()}.mp4`);
145
+ try {
146
+ const keep = shouldSaveRecording(rec.saveMode, failed);
147
+ if (!keep) {
148
+ if (rec.backend === 'direct') {
149
+ await run('adb', ['-s', rec.serial, 'shell', 'pkill', '-INT', 'screenrecord'], { timeoutMs: 8000 }).catch(() => { });
150
+ await new Promise((r) => setTimeout(r, 1500));
151
+ try {
152
+ rec.child.kill();
153
+ }
154
+ catch {
155
+ /* already gone */
156
+ }
157
+ await run('adb', ['-s', rec.serial, 'shell', 'rm', rec.remotePath], { timeoutMs: 8000 }).catch(() => { });
158
+ }
159
+ else {
160
+ rec.child.kill('SIGINT');
161
+ await new Promise((r) => setTimeout(r, 2000));
162
+ }
163
+ sessions.addEnvChange(session, 'screen_record discarded (passed, on_failure)');
164
+ sessions.recordMutation(session, {
165
+ tool: 'qa_screen_record',
166
+ action: 'screen_record_stop',
167
+ risk: 'low',
168
+ target: { device: rec.serial, backend: rec.backend, saveMode: rec.saveMode, failed: failed ?? null },
169
+ consent: { required: false, approved: true },
170
+ status: 'restored',
171
+ detail: 'discarded recording',
172
+ });
173
+ return qaOk({ recording: false, saved: false, discarded: true, seconds: Math.round((Date.now() - rec.startedAt) / 1000), backend: rec.backend, saveMode: rec.saveMode }, 'discarded recording because save mode is on_failure and failed:false');
174
+ }
175
+ let buf;
176
+ if (rec.backend === 'direct') {
177
+ await run('adb', ['-s', rec.serial, 'shell', 'pkill', '-INT', 'screenrecord'], { timeoutMs: 8000 }).catch(() => { });
178
+ await new Promise((r) => setTimeout(r, 1500));
179
+ try {
180
+ rec.child.kill();
181
+ }
182
+ catch {
183
+ /* already gone */
184
+ }
185
+ await run('adb', ['-s', rec.serial, 'pull', rec.remotePath, localTmp], { timeoutMs: 30000, rejectOnNonZero: true });
186
+ buf = readFileSync(localTmp);
187
+ await run('adb', ['-s', rec.serial, 'shell', 'rm', rec.remotePath], { timeoutMs: 8000 }).catch(() => { });
188
+ }
189
+ else {
190
+ // iOS: SIGINT the recorder so it finalizes the file it's writing on the host, then read it.
191
+ rec.child.kill('SIGINT');
192
+ await new Promise((r) => setTimeout(r, 2000));
193
+ buf = readFileSync(rec.hostPath);
194
+ }
195
+ const uri = sessions.saveArtifact(session, 'recording', `recording-${Date.now()}.mp4`, buf, 'video/mp4', `screen recording (${rec.backend}, ${Math.round((Date.now() - rec.startedAt) / 1000)}s)`);
196
+ sessions.addEnvChange(session, 'screen_record stopped');
197
+ sessions.recordMutation(session, {
198
+ tool: 'qa_screen_record',
199
+ action: 'screen_record_stop',
200
+ risk: 'low',
201
+ target: { device: rec.serial, backend: rec.backend, saveMode: rec.saveMode, failed: failed ?? null },
202
+ consent: { required: false, approved: true },
203
+ status: 'restored',
204
+ ledgerUri: uri,
205
+ detail: 'saved recording',
206
+ });
207
+ return qaOk({ recording: false, saved: true, uri, bytes: buf.length, seconds: Math.round((Date.now() - rec.startedAt) / 1000), backend: rec.backend, saveMode: rec.saveMode }, `saved recording (${buf.length} bytes) → ${uri}`);
208
+ }
209
+ catch (e) {
210
+ sessions.recordMutation(session, {
211
+ tool: 'qa_screen_record',
212
+ action: 'screen_record_stop',
213
+ risk: 'low',
214
+ target: { device: rec.serial, backend: rec.backend, saveMode: rec.saveMode, failed: failed ?? null },
215
+ consent: { required: false, approved: true },
216
+ status: 'blocked',
217
+ detail: String(e),
218
+ });
219
+ return qaError({ what: `Could not finalize the recording: ${String(e)}`, changedState: true, retrySafe: false, nextSteps: ['The backend may not support screen recording, or it stopped early. Try a shorter clip.'] });
220
+ }
221
+ finally {
222
+ try {
223
+ rmSync(localTmp, { force: true });
224
+ if (rec.hostPath)
225
+ rmSync(rec.hostPath, { force: true });
226
+ }
227
+ catch {
228
+ /* best-effort */
229
+ }
230
+ }
231
+ });
232
+ }
24
233
  //# sourceMappingURL=screenRecord.js.map
@@ -1 +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"}
1
+ {"version":3,"file":"screenRecord.js","sourceRoot":"","sources":["../../src/tools/screenRecord.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ // qa_seed (PHASE3-PLAN §4.4 / roadmap §9) — turn a declared precondition into one Swipium can
2
+ // actually create, OPT-IN and consent-gated. A fixture may carry a `seed` spec: a deep link, a
3
+ // project-root script, or an API call. Every seed is a mutation → explicit consent, exact action
4
+ // shown. The raw execution lives in src/flows/seedExec.ts (shared with the flow `seed` step). If
5
+ // seeding fails it is reported as a SETUP failure (environment bucket), never an app bug.
6
+ import { z } from 'zod';
7
+ import { qaOk, qaError } from '../lib/result.js';
8
+ import { qaFail } from '../oracle/failures.js';
9
+ import { requireConsent, consumeConsent } from '../consent/consent.js';
10
+ import { executeSeed, seedExactCommand, seedGitScopeViolation } from '../flows/seedExec.js';
11
+ import { getDriver } from '../session/attach.js';
12
+ export function registerSeed(server, sessions) {
13
+ server.registerTool('qa_seed', {
14
+ title: 'Seed a precondition',
15
+ description: 'Create a declared precondition (opt-in, consent-gated) so a blocked workflow can run. The named fixture must declare a `seed`: deeplink (open a setup deep link), script (argv array run under the project root), or api (HTTP call). Every seed is a mutation and requires consent showing the exact action. If seeding fails it is reported as a SETUP failure, not an app bug.',
16
+ inputSchema: {
17
+ sessionId: z.string(),
18
+ fixture: z.string().describe('Name of a declared fixture that has a `seed` spec.'),
19
+ consentId: z.string().optional(),
20
+ approve: z.boolean().optional(),
21
+ },
22
+ }, async ({ sessionId, fixture: fixtureName, consentId, approve }) => {
23
+ const session = sessions.get(sessionId);
24
+ if (!session) {
25
+ return qaError({ what: `Unknown sessionId ${sessionId}`, changedState: false, retrySafe: true, nextSteps: ['Call qa_start_session first.'] });
26
+ }
27
+ const fixture = session.fixtures.find((f) => f.name === fixtureName);
28
+ if (!fixture) {
29
+ return qaError({ what: `No declared fixture "${fixtureName}"`, changedState: false, retrySafe: true, nextSteps: ['Declare it in .swipium/fixtures.json or qa_start_session { fixtures }.'] });
30
+ }
31
+ const seed = fixture.seed;
32
+ if (!seed) {
33
+ return qaError({ what: `Fixture "${fixtureName}" has no seed spec`, changedState: false, retrySafe: true, nextSteps: ['Add a seed: { type: "deeplink"|"script"|"api", … } to the fixture to make it creatable.'] });
34
+ }
35
+ const git = seedGitScopeViolation(seed);
36
+ if (git) {
37
+ return qaFail('GIT_SCOPE_FORBIDDEN', {
38
+ what: `Fixture "${fixtureName}" seed command is refused because Git is outside Swipium's QA scope: ${git}`,
39
+ changedState: false,
40
+ extra: { fixture: fixtureName, command: git },
41
+ });
42
+ }
43
+ // Consent — exact action shown, scoped to this fixture + seed type.
44
+ const affects = { fixture: fixtureName, type: seed.type };
45
+ const risk = seed.type === 'script' ? 'high' : seed.type === 'api' ? 'medium' : 'low';
46
+ const gate = consumeConsent(consentId, approve, { action: 'seed_state', affects });
47
+ if (!gate.approved) {
48
+ sessions.recordMutation(session, {
49
+ tool: 'qa_seed',
50
+ action: 'seed_state',
51
+ risk,
52
+ target: affects,
53
+ consent: { required: true, approved: false },
54
+ status: 'requested',
55
+ });
56
+ return requireConsent({
57
+ action: 'seed_state',
58
+ risk,
59
+ exactCommand: seedExactCommand(seed),
60
+ affects,
61
+ explain: `Seed precondition "${fixtureName}" via ${seed.type}? This mutates state${seed.type === 'script' ? ' by running a command under the project root' : seed.type === 'api' ? ' by calling an external API' : ' by opening a deep link'}.`,
62
+ });
63
+ }
64
+ sessions.recordMutation(session, {
65
+ tool: 'qa_seed',
66
+ action: 'seed_state',
67
+ risk,
68
+ target: affects,
69
+ consent: { required: true, consentId, approved: true },
70
+ status: 'approved',
71
+ });
72
+ const { driver } = await getDriver(session);
73
+ const result = await executeSeed(sessions, session, driver, fixtureName, seed);
74
+ if (!result.ok) {
75
+ // Record as a SETUP failure (not an app bug) so the report can't mislabel it.
76
+ sessions.addNote(session, { at: Date.now(), workflow: `seed:${fixtureName}`, outcome: 'blocked', category: 'missing_test_data', reason: `seed failed: ${result.detail}`, requiredState: fixture.requiredState, recommendedSetup: fixture.recommendedSetup });
77
+ sessions.recordMutation(session, {
78
+ tool: 'qa_seed',
79
+ action: 'seed_state',
80
+ risk,
81
+ target: affects,
82
+ consent: { required: true, consentId, approved: true },
83
+ status: 'blocked',
84
+ detail: result.detail,
85
+ });
86
+ return qaFail('SEED_FAILED', { what: `Seeding "${fixtureName}" failed: ${result.detail}`, changedState: true, extra: { fixture: fixtureName, type: seed.type } });
87
+ }
88
+ sessions.addNote(session, { at: Date.now(), workflow: `seed:${fixtureName}`, outcome: 'pass', reason: `precondition created via ${seed.type}` });
89
+ sessions.recordMutation(session, {
90
+ tool: 'qa_seed',
91
+ action: 'seed_state',
92
+ risk,
93
+ target: affects,
94
+ consent: { required: true, consentId, approved: true },
95
+ status: 'executed',
96
+ detail: result.warnings.join(' ') || undefined,
97
+ });
98
+ return qaOk({ fixture: fixtureName, type: seed.type, seeded: true, warnings: result.warnings }, `✅ seeded "${fixtureName}" via ${seed.type} — the precondition should now be met. Re-run the workflow.${result.warnings.length ? `\n⚠ ${result.warnings.join(' ')}` : ''}`);
99
+ });
100
+ }
101
+ //# sourceMappingURL=seed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed.js","sourceRoot":"","sources":["../../src/tools/seed.ts"],"names":[],"mappings":"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"}