Skip to content

Commit bf22464

Browse files
thymikeeclaude
andauthored
refactor(platforms): share direct lifecycle binding and the interactor operation set (#3304)
* refactor(apple-runner): drop the discarded uninstall-output classifier isBenignSimulatorRunnerUninstallResult sniffed simctl output, and both branches of its verdict returned without acting on it. Co-Authored-By: Claude Fable 5.1 <[email protected]> * refactor(contracts): bind direct local lifecycles once instead of per platform Web, Linux, Vega, and HarmonyOS each carried a 30-line lifecycle.ts that differed only in the owner label and open-target identity. Co-Authored-By: Claude Fable 5.1 <[email protected]> * refactor(harmonyos): bind the shared local interactor operation set Co-Authored-By: Claude Fable 5.1 <[email protected]> * refactor(platforms): name HarmonyOS in the shared set's doc and return void from the uninstall step Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: Claude Fable 5.1 <[email protected]>
1 parent 0c45805 commit bf22464

11 files changed

Lines changed: 61 additions & 222 deletions

File tree

‎packages/contracts/src/application-lifecycle-interaction.ts‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,3 +380,27 @@ export async function invokeApplicationClose(
380380
}
381381
await interactor.close(app);
382382
}
383+
384+
/**
385+
* The lifecycle binding of an owner whose interactor drives open and close directly: resolve the
386+
* local interactor for the device, then dispatch through {@link bindDirectApplicationLifecycle}.
387+
*/
388+
export function bindLocalDirectApplicationLifecycle(
389+
params: Readonly<{
390+
owner: string;
391+
openTargetIdentity: DirectOpenTargetIdentity;
392+
host: Readonly<{ resolve: LocalApplicationLifecycleInteractorResolver }>;
393+
device: DeviceInfo;
394+
signal: AbortSignal;
395+
}>,
396+
): ApplicationLifecycleRuntimeOperations {
397+
return bindDirectApplicationLifecycle({
398+
owner: params.owner,
399+
openTargetIdentity: params.openTargetIdentity,
400+
binding: bindLocalApplicationLifecycleInteractor({
401+
device: params.device,
402+
signal: params.signal,
403+
resolveInteractor: params.host.resolve,
404+
}),
405+
});
406+
}

‎packages/contracts/src/local-interactor-operation-set.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ import { bindLocalTypeTextInteractor } from './type-text-runtime.ts';
1414
/**
1515
* The whole local interactor-backed operation set a pointer-driving family binds: the uniform
1616
* one-fact-one-bind leaves the interactor catalog walks, plus the four that carry extra input of their own — gesture and touch read the
17-
* fact map, and touch needs a pause clock. Android and Linux each held a byte-identical copy of
18-
* this list; it is one list now, so neither family can drift from the other by forgetting a
19-
* member. Capture stays out: an owner's snapshot mechanics are its own (Linux captures a surface
20-
* through its host, Android drives the interactor), so there is no shared reading to make.
17+
* fact map, and touch needs a pause clock. Android, Linux, and HarmonyOS bind this one list, so
18+
* no pointer-driving family can drift from another by forgetting a member. Capture stays out: an
19+
* owner's snapshot mechanics are its own (Linux captures a surface through its host, Android
20+
* drives the interactor), so there is no shared reading to make.
2121
*/
2222
export function bindLocalInteractorOperationSet(
2323
params: Readonly<{

‎packages/platform-apple/src/runner/runner-session.ts‎

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
buildSimctlArgsForDevice,
88
runXcrun,
99
} from './host.ts';
10-
import type { ExecResult } from '@agent-device/host-kit/command';
1110
import { isApplePlatform, type DeviceInfo } from '@agent-device/kernel/device';
1211
import type { RunnerLogicalLeaseContext } from '@agent-device/contracts/runner-lease-context';
1312
import type { AppleRunnerLifecycleOptions } from './runner-provider.ts';
@@ -521,21 +520,18 @@ async function cleanupStaleSimulatorRunnerBundles(device: DeviceInfo): Promise<v
521520

522521
await Promise.allSettled(
523522
IOS_RUNNER_CONTAINER_BUNDLE_IDS.map(async (bundleId) => {
524-
const result = await uninstallStaleSimulatorRunnerBundle(device, bundleId);
525-
if (!result || isBenignSimulatorRunnerUninstallResult(result)) {
526-
return;
527-
}
528523
// Best-effort cleanup only; xcodebuild may still be able to install.
524+
await uninstallStaleSimulatorRunnerBundle(device, bundleId);
529525
}),
530526
);
531527
}
532528

533529
async function uninstallStaleSimulatorRunnerBundle(
534530
device: DeviceInfo,
535531
bundleId: string,
536-
): Promise<ExecResult | undefined> {
532+
): Promise<void> {
537533
try {
538-
return await runXcrun(buildSimctlArgsForDevice(device, ['uninstall', device.id, bundleId]), {
534+
await runXcrun(buildSimctlArgsForDevice(device, ['uninstall', device.id, bundleId]), {
539535
allowFailure: true,
540536
timeoutMs: RUNNER_STALE_BUNDLE_UNINSTALL_TIMEOUT_MS,
541537
});
@@ -550,22 +546,9 @@ async function uninstallStaleSimulatorRunnerBundle(
550546
error: error instanceof Error ? error.message : String(error),
551547
},
552548
});
553-
return undefined;
554549
}
555550
}
556551

557-
function isBenignSimulatorRunnerUninstallResult(result: ExecResult): boolean {
558-
if (result.exitCode === 0) return true;
559-
const output = `${result.stdout}\n${result.stderr}`.toLowerCase();
560-
return (
561-
output.includes('not installed') ||
562-
output.includes('found nothing') ||
563-
output.includes('no such file') ||
564-
output.includes('invalid device') ||
565-
output.includes('could not find')
566-
);
567-
}
568-
569552
/**
570553
* The one reader of what is registered for a device: the session's state plus the one fact the
571554
* session cannot know itself — whether its runner process is still there. `null` means nothing is

‎packages/platform-harmonyos/src/lifecycle.ts‎

Lines changed: 0 additions & 30 deletions
This file was deleted.

‎packages/platform-harmonyos/src/runtime.ts‎

Lines changed: 14 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,11 @@ import {
1010
} from '@agent-device/contracts/application-lifecycle-runtime';
1111
import { backRuntimeOperationFacts } from '@agent-device/contracts/back-runtime';
1212
import { elementTextRuntimeOperationFacts } from '@agent-device/contracts/element-text-runtime';
13-
import {
14-
bindLocalFocusInteractor,
15-
focusRuntimeOperationFacts,
16-
} from '@agent-device/contracts/focus-runtime';
13+
import { focusRuntimeOperationFacts } from '@agent-device/contracts/focus-runtime';
1714
import { TARGET_AUTHORED_DRAG_UNSUPPORTED_HINT } from '@agent-device/contracts/gesture-admission';
18-
import {
19-
bindLocalGestureInteractor,
20-
gestureRuntimeOperationFacts,
21-
} from '@agent-device/contracts/gesture-runtime';
22-
import {
23-
bindLocalScrollInteractor,
24-
scrollRuntimeOperationFacts,
25-
} from '@agent-device/contracts/scroll-runtime';
26-
import { bindAdmittedLocalInteractorOperations } from '@agent-device/contracts/interactor-operation-catalog';
15+
import { gestureRuntimeOperationFacts } from '@agent-device/contracts/gesture-runtime';
16+
import { scrollRuntimeOperationFacts } from '@agent-device/contracts/scroll-runtime';
17+
import { bindLocalInteractorOperationSet } from '@agent-device/contracts/local-interactor-operation-set';
2718
import { alertRuntimeOperationFacts } from '@agent-device/contracts/alert-runtime';
2819
import { appEventRuntimeOperationFacts } from '@agent-device/contracts/app-event-runtime';
2920
import { settingsRuntimeOperationFacts } from '@agent-device/contracts/settings-runtime';
@@ -38,24 +29,15 @@ import {
3829
unavailableFact,
3930
whenAdmitted,
4031
} from '@agent-device/contracts/platform-runtime';
41-
import {
42-
bindLocalScreenshotInteractor,
43-
screenshotRuntimeOperationFacts,
44-
} from '@agent-device/contracts/screenshot-runtime';
32+
import { screenshotRuntimeOperationFacts } from '@agent-device/contracts/screenshot-runtime';
4533
import { selectorObservationRuntimeOperationFacts } from '@agent-device/contracts/selector-observation-runtime';
4634
import {
4735
bindLocalSnapshotInteractor,
4836
snapshotRuntimeOperationFacts,
4937
} from '@agent-device/contracts/snapshot-runtime';
5038
import { tvRemoteRuntimeOperationFacts } from '@agent-device/contracts/tv-remote-runtime';
51-
import {
52-
bindLocalTypeTextInteractor,
53-
typeTextRuntimeOperationFacts,
54-
} from '@agent-device/contracts/type-text-runtime';
55-
import {
56-
bindLocalTouchInteractor,
57-
touchRuntimeOperationFacts,
58-
} from '@agent-device/contracts/touch-runtime';
39+
import { typeTextRuntimeOperationFacts } from '@agent-device/contracts/type-text-runtime';
40+
import { touchRuntimeOperationFacts } from '@agent-device/contracts/touch-runtime';
5941
import { viewportRuntimeOperationFacts } from '@agent-device/contracts/viewport-runtime';
6042
import type { DeviceInfo } from '@agent-device/kernel/device';
6143
import { createHarmonyAppLogRuntime } from './logs/runtime.ts';
@@ -65,7 +47,7 @@ import {
6547
harmonyScreenRecordingFacts,
6648
} from './recording/runtime.ts';
6749
import { readHarmonyAppState } from './app-state.ts';
68-
import { bindHarmonyApplicationLifecycle } from './lifecycle.ts';
50+
import { bindLocalDirectApplicationLifecycle } from '@agent-device/contracts/application-lifecycle-interaction';
6951
import {
7052
createHarmonyAppDeploymentOperations,
7153
harmonyAppDeploymentFacts,
@@ -347,62 +329,20 @@ export function createHarmonyPlatformRuntime(host: PlatformRuntimeHost): Platfor
347329
resolveInteractor: host.localInteractors.resolve,
348330
})
349331
: {}),
350-
...(facts.operations.captureScreenshot.available
351-
? bindLocalScreenshotInteractor({
352-
device: request.device,
353-
signal: request.scope.signal,
354-
resolveInteractor: host.localInteractors.resolve,
355-
})
356-
: {}),
357-
...(facts.operations.focusPoint.available
358-
? bindLocalFocusInteractor({
359-
device: request.device,
360-
signal: request.scope.signal,
361-
resolveInteractor: host.localInteractors.resolve,
362-
})
363-
: {}),
364-
...(facts.operations.typeText.available
365-
? bindLocalTypeTextInteractor({
366-
device: request.device,
367-
signal: request.scope.signal,
368-
resolveInteractor: host.localInteractors.resolve,
369-
})
370-
: {}),
371-
...bindLocalGestureInteractor({
372-
device: request.device,
373-
signal: request.scope.signal,
374-
facts: facts.operations,
375-
resolveInteractor: host.localInteractors.resolve,
376-
}),
377-
...(facts.operations.scrollDirection.available
378-
? bindLocalScrollInteractor({
379-
device: request.device,
380-
signal: request.scope.signal,
381-
resolveInteractor: host.localInteractors.resolve,
382-
})
383-
: {}),
384-
...bindAdmittedLocalInteractorOperations({
332+
...bindLocalInteractorOperationSet({
385333
device: request.device,
386334
signal: request.scope.signal,
387335
resolveInteractor: host.localInteractors.resolve,
388336
facts: facts.operations,
337+
pause: async (milliseconds) =>
338+
await host.clock.sleep(milliseconds, request.scope.signal),
389339
}),
390340
...whenAdmitted(facts.operations.perfFrames, () =>
391341
createHarmonyPerfOperations({
392342
resolveHost: () => host.perf.harmony,
393343
device: request.device,
394344
}),
395345
),
396-
...whenAdmitted(facts.operations.tapPoint, () =>
397-
bindLocalTouchInteractor({
398-
device: request.device,
399-
signal: request.scope.signal,
400-
resolveInteractor: host.localInteractors.resolve,
401-
facts: facts.operations,
402-
pause: async (milliseconds) =>
403-
await host.clock.sleep(milliseconds, request.scope.signal),
404-
}),
405-
),
406346
listApps: async (input: { device: DeviceInfo; filter: 'all' | 'user-installed' }) => {
407347
request.scope.signal.throwIfAborted();
408348
const { listHarmonyApps } = await import('./app-lifecycle.ts');
@@ -411,7 +351,9 @@ export function createHarmonyPlatformRuntime(host: PlatformRuntimeHost): Platfor
411351
).map((app) => ({ id: app.package, name: app.name }));
412352
},
413353
...availableApplicationLifecycleOperations(
414-
bindHarmonyApplicationLifecycle({
354+
bindLocalDirectApplicationLifecycle({
355+
owner: 'HarmonyOS',
356+
openTargetIdentity: 'bundle-id',
415357
host: host.localInteractors,
416358
device: request.device,
417359
signal: request.scope.signal,

‎packages/platform-linux/src/lifecycle.ts‎

Lines changed: 0 additions & 30 deletions
This file was deleted.

‎packages/platform-linux/src/runtime.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import { typeTextRuntimeOperationFacts } from '@agent-device/contracts/type-text
3838
import { touchRuntimeOperationFacts } from '@agent-device/contracts/touch-runtime';
3939
import type { DeviceInfo } from '@agent-device/kernel/device';
4040
import { AppError } from '@agent-device/kernel/errors';
41-
import { bindLinuxApplicationLifecycle } from './lifecycle.ts';
41+
import { bindLocalDirectApplicationLifecycle } from '@agent-device/contracts/application-lifecycle-interaction';
4242

4343
const supported = Object.freeze({ available: true } as const);
4444
const linuxOwner = localRuntimeOwner('linux');
@@ -140,7 +140,9 @@ export function createLinuxPlatformRuntime(host: PlatformRuntimeHost): PlatformR
140140
throw new AppError('UNSUPPORTED_PLATFORM', 'Linux runtime cannot bind this device');
141141
}
142142
const facts = linuxFacts(request.device);
143-
const lifecycle = bindLinuxApplicationLifecycle({
143+
const lifecycle = bindLocalDirectApplicationLifecycle({
144+
owner: 'Linux',
145+
openTargetIdentity: 'app-name',
144146
host: host.localInteractors,
145147
device: request.device,
146148
signal: request.scope.signal,

‎packages/platform-vega/src/lifecycle.ts‎

Lines changed: 0 additions & 30 deletions
This file was deleted.

‎packages/platform-vega/src/runtime.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { gestureRuntimeOperationFacts } from '@agent-device/contracts/gesture-ru
2222
import { tvRemoteRuntimeOperationFacts } from '@agent-device/contracts/tv-remote-runtime';
2323
import type { DeviceInfo } from '@agent-device/kernel/device';
2424
import { AppError } from '@agent-device/kernel/errors';
25-
import { bindVegaApplicationLifecycle } from './lifecycle.ts';
25+
import { bindLocalDirectApplicationLifecycle } from '@agent-device/contracts/application-lifecycle-interaction';
2626

2727
const vegaOwner = localRuntimeOwner('vega');
2828
const lifecycleAvailable = Object.freeze({ available: true } as const);
@@ -63,7 +63,9 @@ export function createVegaPlatformRuntime(host: PlatformRuntimeHost): PlatformRu
6363
throw new AppError('UNSUPPORTED_PLATFORM', 'Vega runtime cannot bind this device');
6464
}
6565
const facts = vegaFacts(request.device);
66-
const lifecycle = bindVegaApplicationLifecycle({
66+
const lifecycle = bindLocalDirectApplicationLifecycle({
67+
owner: 'Vega',
68+
openTargetIdentity: 'app-name',
6769
host: host.localInteractors,
6870
device: request.device,
6971
signal: request.scope.signal,

0 commit comments

Comments
 (0)