Repository navigation
Expand file tree
/
Copy pathdaemon-composition.ts
More file actions
188 lines (184 loc) · 8.68 KB
/
Copy pathdaemon-composition.ts
File metadata and controls
188 lines (184 loc) · 8.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// The daemon runtime's dependency construction, extracted from `packages/daemon/src/server/http/daemon-runtime.ts`
// so the operational server owns sequencing and this module owns naming runtimes (ADR 0019).
// Everything here that names a platform runtime, a provider runtime, an interactor, host
// diagnostics, credential fingerprinting, resource cleanup, the platform lifecycle owners, the
// platform-services composition, or the claim-recovery gateway lives in the process root; the
// server calls `createDaemonRuntimeDeps` as a factory once it owns its paths, policy, and session
// store, so exactly one assembly builds the SessionStore and deps pair.
//
// The static import set is the family the operational server already evaluated eagerly, and the
// entry reaches this module through a function-scoped `await import`, so the daemon entry's eager
// closure does not grow; the platform-services and claim-recovery
// compositions stay behind function-scoped `await import`, and the command surface loads on first
// request use. Nothing here value-imports `packages/daemon/src/`: the daemon's session data enters through
// plain callbacks, so the platform-runtime zone keeps its value-free direction toward the daemon.
import {
createProviderDeviceRuntimeRequestProviders,
isActiveProviderDevice,
} from './provider-device-runtime.ts';
import { createDaemonProviderRuntimeComposition } from './provider-device-runtimes.ts';
import { readDaemonProviderCredentials } from './provider-credential-fingerprint.ts';
import { getInteractor } from './core/interactors.ts';
import {
androidObservation,
createPlatformRuntimeGateway,
createPlatformDeviceInventoryGateways,
createRequestPlatformProviders,
} from './platform-runtime.ts';
import { createHostDiagnostics } from './platform-runtime-host-diagnostics.ts';
import { createPlatformResourceCleanup } from './platform-runtime-resource-cleanup.ts';
import { createPlatformDaemonLifecycleOwners } from './platform-runtime-daemon-lifecycle.ts';
import {
createOwnedProcessRecordStore,
type OwnedProcessRecordStore,
} from '@agent-device/host-kit/process';
import { emitDiagnostic } from '@agent-device/host-kit/diagnostics';
import type { AgentDeviceRuntimeConfig } from '@agent-device/contracts/runtime-contract';
import type { DeviceInfo } from '@agent-device/kernel/device';
import type { HostEnvironment } from '@agent-device/kernel/source-value';
import type { RunnerContext } from '@agent-device/contracts/interactor-types';
import type { ComposeDaemonRuntimeDeps, DaemonRuntimeDeps } from '@agent-device/daemon';
/**
* Best-effort startup cleanup of managed-agent-browser orphans left by a previous daemon. It runs
* only after this process holds the daemon lock, and it never fails startup: a cleanup the host
* refuses is a diagnostic, not a reason to keep the next daemon from serving.
*/
export function createDaemonStartupWebOrphanCleanup(
host: HostEnvironment,
): (params: {
stateDir: string;
openWebSessionNames: readonly string[];
ownedProcessRecords?: OwnedProcessRecordStore;
}) => Promise<void> {
return async (params) => {
try {
await createPlatformDaemonLifecycleOwners(host).cleanupManagedWebOrphans(params);
} catch (error) {
emitDiagnostic({
level: 'warn',
phase: 'web_agent_browser_orphan_cleanup_failed',
data: { error: error instanceof Error ? error.message : String(error) },
});
}
};
}
/**
* Builds every capability the daemon runtime cannot construct for itself, in the order the
* runtime used to build them. The server calls it as a factory with the paths, the operator's
* policy, and the session-store callbacks it owns; all returned members are required, so a
* daemon process either receives this composition or cannot start, and a test passes the same
* factory (ADR 0019). The session-scoped callbacks are the daemon's own data seen as questions —
* recording artifact paths, session directories, and which web sessions are open — so no daemon
* value crosses here.
*/
export const createDaemonRuntimeDeps: ComposeDaemonRuntimeDeps = async (
input,
): Promise<DaemonRuntimeDeps> => {
const {
host,
baseDir,
sessionsDir,
assertShutdownAllowed,
resolveSessionDir,
resolveSessionArtifacts,
openWebSessionNames,
} = input;
const { env } = host;
const ownedProcessRecords = createOwnedProcessRecordStore({
stateDir: baseDir,
sessionsDir,
resolveSessionDir,
});
const hostDiagnostics = createHostDiagnostics(host);
const providerComposition = await createDaemonProviderRuntimeComposition(env);
// The registry has set up every plugin. Until this function returns, nothing else owns its
// cleanup, so any later failure closes it here; success transfers ownership to the daemon.
try {
const providerDeviceRuntimes = [...providerComposition.runtimes];
const deviceRuntimeGateway = createPlatformRuntimeGateway({
host,
assertShutdownAllowed,
providerRuntimes: providerDeviceRuntimes,
providerModules: providerComposition.platformModules,
sessionsDir,
ownedProcesses: ownedProcessRecords,
resolveSessionArtifacts,
});
const providerRuntimeProviders =
createProviderDeviceRuntimeRequestProviders(providerDeviceRuntimes);
const requestPlatformProviders = createRequestPlatformProviders({
providers: {
appleRunnerProvider: providerRuntimeProviders.appleRunnerProvider,
appleRunnerScreenRecordingTransport:
providerRuntimeProviders.appleRunnerScreenRecordingTransport,
},
defaultWebProvider: {
host,
stateDir: baseDir,
openWebSessionNames,
ownedProcessRecords,
},
});
const deviceInventoryGateways = await createPlatformDeviceInventoryGateways(
host,
providerRuntimeProviders.deviceInventorySource,
);
// Root composition of the platform-services port and the owner-scoped claim-recovery gateway.
// platform-runtime.ts is already static in this module, so the deferred imports keep these thin
// composition modules (and the readiness/open-target adapters only the services module pulls)
// off `src/daemon.ts`'s eager closure; what each adapter itself loads stays as lazy as it was.
const { createDaemonPlatformServices } = await import('./platform-runtime-daemon-services.ts');
const { createClaimRecoveryGatewayFor } = await import('./platform-runtime-claim-recovery.ts');
const { createAppleRunnerRuntimeState } =
await import('@agent-device/platform-apple/runner-owner');
const { createAndroidHelperRuntimeState } =
await import('@agent-device/platform-android/adb-host');
return Object.freeze({
runtimeStates: Object.freeze([
createAppleRunnerRuntimeState(host),
createAndroidHelperRuntimeState(),
]),
ownedProcessRecords,
hostDiagnostics,
ownerLifecycle: createPlatformDaemonLifecycleOwners(host),
shutdownProviderRuntimes: providerComposition.shutdown,
pluginInventory: providerComposition.inventory,
leaseLifecycleProvider: providerRuntimeProviders.leaseLifecycleProvider,
providerRuntimeIds: providerRuntimeProviders.providerRuntimeIds,
recoverExpiredLease: providerRuntimeProviders.recoverExpiredLease,
recoverableProviderIds: providerRuntimeProviders.recoverableProviderIds,
deviceRuntimeGateway,
platformResourceCleanup: createPlatformResourceCleanup(host),
claimRecoveryGateway: createClaimRecoveryGatewayFor(host),
interactorResolution: Object.freeze({
resolve: async (device: DeviceInfo, runnerContext: RunnerContext) =>
await getInteractor(device, runnerContext, host),
}),
providerDeviceAdmission: Object.freeze({
isActive: (device: DeviceInfo) => isActiveProviderDevice(device),
}),
cleanupManagedWebOrphans: createDaemonStartupWebOrphanCleanup(host),
requestRouter: Object.freeze({
requestPlatformProviders,
deviceInventoryGateways,
platformServices: createDaemonPlatformServices(),
androidObservation,
providerCredentials: readDaemonProviderCredentials(
env,
baseDir,
providerComposition.inventory,
),
createCommandSurface: async (config: AgentDeviceRuntimeConfig) =>
(
await import('./command-runtime/runtime-command-surface.ts')
).createCommandSurfaceAgentDevice(config),
cloudArtifactProvider: providerRuntimeProviders.cloudArtifactProvider,
providerAppCatalog: providerRuntimeProviders.providerAppCatalog,
providerDeviceRuntimeScope: providerRuntimeProviders.providerDeviceRuntimeScope,
}),
});
} catch (error) {
await providerComposition.shutdown();
throw error;
}
};