mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
dcd8b65d4c
* refactor(daemon): split daemon/types.ts into request and session-state modules `src/daemon/types.ts` served two audiences from one file: the dispatch request shape and the daemon's live session record. It also sat in the only daemon type cycle — it imported `RefFrame` from `ref-frame.ts`, which imported `SessionState` back — so neither file could be read in isolation. Three modules replace it, each importing only downward: - `daemon-request-wire.ts` declares `DaemonWireRequest`: a dispatched request with no `internal` key and no property path to `SessionState` or `DeviceLease`, so a consumer can read a request's command, flags and public metadata without depending on the session record. - `daemon-request.ts` adds the daemon-only half (`DaemonRequestInternal`, which stays unexported) plus the response vocabulary. - `session-state.ts` owns `SessionState` and the shapes only it holds. The cycle is cut by `ref-frame-slot.ts`, declared below both `ref-frame.ts` and `session-state.ts`: it owns the frame VALUE (the class stays unexported, so the type remains nominal and unconstructible from outside), while `ref-frame.ts` keeps every lifetime transition and every `session.refFrame` write. No behavior change: every importer moves to the module owning the symbol it uses, with no re-export shim at the old path. `client-normalizers.ts` takes `SessionRuntimeHints` from `@agent-device/kernel/contracts`, which declares it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ujrc8LYmvM249WY8921J1Y * test(daemon): assert the wire request shape cannot reach session state A type-level walk over `DaemonWireRequest` fails `tsc` if the shape regains an `internal` key or grows a property path back to `SessionState` or `DeviceLease`. Positive controls over `DaemonRequest` prove the walk finds both when they are there, so a walk that never matches anything cannot pass by accident. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ujrc8LYmvM249WY8921J1Y * test(daemon): keep the three over-budget test files at their base length Splitting `daemon/types.ts` turns one combined import into two in every file that used both halves. Three of those test files are already over the 1,000-line tripwire, where the size ratchet allows no growth, so each sheds one line that was carrying nothing: - `snapshot-handler.test.ts` and `find.test.ts` each drop a `toHaveLength` assertion an adjacent `toEqual` on an explicit array literal already makes. - `session-replay-repair-transaction.test.ts` names the filtered close actions instead of wrapping the expression across three lines inside `expect`. No assertion is weakened and no test content is removed. Splitting these files along the modules they mirror is the standing remedy, but none of those modules split here, so it stays out of this change and is tracked in #2353. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ujrc8LYmvM249WY8921J1Y * chore(gates): point the daemon modularity and wire-compat gates at the split modules R7 now locates the `SessionState` declaration by the declaration itself rather than by a recorded path: `sessionStateWritePressure` measures the merge-base tree too, and that tree still declares it in `daemon/types.ts` — a path constant would measure it as zero pressure and bank the headroom. R10's external-importer ratchet covers all three modules that replaced `daemon/types.ts`, so moving a symbol between them cannot reopen the boundary to a new outside zone. The recorded membership is unchanged: `client-normalizers.ts` and `remote/daemon-artifacts.ts` both import `daemon-request.ts` only. The daemon RPC closure gate waives `DaemonRequest`, `DaemonResponse` and `DaemonArtifact` by path, so those three keys follow the declarations to `daemon-request.ts`. `DaemonRequest`'s rationale now says what it is — the server-side narrowing of the kernel declaration that fixes the wire shape — rather than calling it a re-export alias. The `live-state-shape` and session-resource declaration sites move with `SessionState`; the depgraph lookalike fixture takes a new plausible path now that `daemon/session-state.ts` is the real root. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ujrc8LYmvM249WY8921J1Y --------- Co-authored-by: Claude <noreply@anthropic.com>
247 lines
7.6 KiB
TypeScript
247 lines
7.6 KiB
TypeScript
type FacadeDeclaration = Readonly<{
|
|
root: string;
|
|
exports: readonly string[];
|
|
}>;
|
|
|
|
export type LogicalModulePolicy = Readonly<{
|
|
name: string;
|
|
roots: readonly string[];
|
|
forbiddenTargetRoots: readonly string[];
|
|
internalForbiddenTargetRoots?: readonly string[];
|
|
facade?: FacadeDeclaration;
|
|
}>;
|
|
|
|
const DAEMON_REPLAY_FACADE = {
|
|
root: 'src/daemon/replay/index.ts',
|
|
exports: ['ReplaySession', 'ReplayTestVideoOwner', 'runReplayCommand', 'runReplayTestCommand'],
|
|
} as const;
|
|
|
|
const DAEMON_SESSION_LIFECYCLE_FACADE = {
|
|
root: 'src/daemon/session-lifecycle/index.ts',
|
|
exports: [
|
|
'SessionCloseCommandInput',
|
|
'SessionInventoryCommandInput',
|
|
'SessionOpenCommandInput',
|
|
'handleSessionCloseCommands',
|
|
'handleSessionInventoryCommands',
|
|
'handleSessionOpenCommands',
|
|
],
|
|
} as const;
|
|
|
|
const DAEMON_SESSION_OBSERVABILITY_FACADE = {
|
|
root: 'src/daemon/session-observability/index.ts',
|
|
exports: ['SessionObservabilityCommandInput', 'handleSessionObservabilityCommands'],
|
|
} as const;
|
|
|
|
export const SESSION_LIFECYCLE_RETIRED_HANDLER_PATHS = [
|
|
'src/daemon/handlers/session-device-utils.ts',
|
|
'src/daemon/handlers/session-runtime-admission.ts',
|
|
'src/daemon/handlers/session-open.ts',
|
|
'src/daemon/handlers/session-open-prepare.ts',
|
|
'src/daemon/handlers/session-open-execution.ts',
|
|
'src/daemon/handlers/session-open-foreground.ts',
|
|
'src/daemon/handlers/session-open-surface.ts',
|
|
'src/daemon/handlers/session-startup-metrics.ts',
|
|
] as const;
|
|
|
|
export const INTERACTION_RETIRED_HANDLER_PATHS = [
|
|
'src/daemon/handlers/find.ts',
|
|
'src/daemon/handlers/find-match-ranking.ts',
|
|
'src/daemon/handlers/find-match-resolution.ts',
|
|
'src/daemon/handlers/find-target-capture.ts',
|
|
'src/daemon/handlers/interaction.ts',
|
|
'src/daemon/handlers/interaction-android-escape.ts',
|
|
'src/daemon/handlers/interaction-gesture.ts',
|
|
'src/daemon/handlers/interaction-gesture-response.ts',
|
|
'src/daemon/handlers/interaction-ios-tap-outcome.ts',
|
|
'src/daemon/handlers/interaction-targeting.ts',
|
|
'src/daemon/handlers/interaction-touch.ts',
|
|
'src/daemon/handlers/interaction-touch-android-freshness.ts',
|
|
'src/daemon/handlers/interaction-touch-android-readiness.ts',
|
|
'src/daemon/handlers/interaction-touch-direct-ios-eligibility.ts',
|
|
'src/daemon/handlers/interaction-touch-direct-ios.ts',
|
|
'src/daemon/handlers/interaction-touch-fill.ts',
|
|
'src/daemon/handlers/interaction-touch-payload.ts',
|
|
'src/daemon/handlers/interaction-touch-policy.ts',
|
|
'src/daemon/handlers/interaction-touch-prepare.ts',
|
|
'src/daemon/handlers/interaction-touch-press-admission.ts',
|
|
'src/daemon/handlers/interaction-touch-press.ts',
|
|
'src/daemon/handlers/interaction-touch-reference-frame.ts',
|
|
'src/daemon/handlers/interaction-touch-response.ts',
|
|
'src/daemon/handlers/interaction-touch-runtime.ts',
|
|
'src/daemon/handlers/interaction-touch-targets.ts',
|
|
'src/daemon/handlers/system-surface-disclosure.ts',
|
|
] as const;
|
|
|
|
const DAEMON_INTERACTION_FACADE = {
|
|
root: 'src/daemon/interaction/index.ts',
|
|
exports: [
|
|
'FindRouteInput',
|
|
'InteractionRouteInput',
|
|
'captureSnapshotForSession',
|
|
'createInteractionRuntime',
|
|
'finalizeTouchInteraction',
|
|
'handleFindCommands',
|
|
'handleInteractionCommands',
|
|
'readSettleRequest',
|
|
'refMutationAdmissionResponse',
|
|
'settleFlagGuardResponse',
|
|
],
|
|
} as const;
|
|
|
|
export const SESSION_OBSERVABILITY_RETIRED_HANDLER_PATHS = [
|
|
'src/daemon/handlers/session-observability.ts',
|
|
'src/daemon/handlers/session-perf-runtime.ts',
|
|
'src/daemon/handlers/session-network.ts',
|
|
'src/daemon/handlers/session-audio.ts',
|
|
] as const;
|
|
|
|
export const SNAPSHOT_EXECUTION_RETIRED_HANDLER_PATHS = [
|
|
'src/daemon/handlers/snapshot-capture.ts',
|
|
'src/daemon/handlers/snapshot-interactor-capture.ts',
|
|
'src/daemon/handlers/snapshot-session.ts',
|
|
] as const;
|
|
|
|
export const LOGICAL_MODULE_POLICIES = [
|
|
{
|
|
name: 'ad-replay',
|
|
roots: ['packages/ad-replay/src/'],
|
|
forbiddenTargetRoots: ['src/daemon/', 'src/providers/', 'src/compat/', 'packages/maestro/'],
|
|
},
|
|
{
|
|
name: 'maestro',
|
|
roots: ['packages/maestro/src/'],
|
|
forbiddenTargetRoots: ['src/daemon/', 'src/providers/', 'packages/ad-replay/'],
|
|
},
|
|
{
|
|
name: 'replay-test',
|
|
roots: ['packages/replay-test/src/'],
|
|
forbiddenTargetRoots: [
|
|
'src/daemon/',
|
|
'src/providers/',
|
|
'src/request/',
|
|
'src/compat/',
|
|
'packages/maestro/',
|
|
'packages/ad-replay/',
|
|
],
|
|
},
|
|
{
|
|
name: 'daemon-replay',
|
|
roots: ['src/daemon/replay/'],
|
|
forbiddenTargetRoots: [
|
|
'src/daemon/handlers/record-runtime.ts',
|
|
'src/daemon/session-store.ts',
|
|
'src/daemon/session-lifecycle/',
|
|
],
|
|
facade: DAEMON_REPLAY_FACADE,
|
|
},
|
|
{
|
|
name: 'daemon-session-lifecycle',
|
|
roots: ['src/daemon/session-lifecycle/'],
|
|
forbiddenTargetRoots: ['src/daemon/handlers/'],
|
|
facade: DAEMON_SESSION_LIFECYCLE_FACADE,
|
|
},
|
|
{
|
|
name: 'daemon-interaction',
|
|
roots: ['src/daemon/interaction/'],
|
|
forbiddenTargetRoots: [],
|
|
internalForbiddenTargetRoots: ['src/daemon/handlers/'],
|
|
facade: DAEMON_INTERACTION_FACADE,
|
|
},
|
|
{
|
|
name: 'daemon-session-observability',
|
|
roots: ['src/daemon/session-observability/'],
|
|
forbiddenTargetRoots: ['src/daemon/handlers/'],
|
|
facade: DAEMON_SESSION_OBSERVABILITY_FACADE,
|
|
},
|
|
] as const satisfies readonly LogicalModulePolicy[];
|
|
|
|
export const ARCHITECTURE_OWNERSHIP = {
|
|
logicalModules: LOGICAL_MODULE_POLICIES,
|
|
facades: LOGICAL_MODULE_POLICIES.flatMap((module) =>
|
|
module.facade ? [{ name: module.name, ...module.facade }] : [],
|
|
),
|
|
vocabulary: [
|
|
{
|
|
name: 'client-contract',
|
|
kind: 'vocabulary',
|
|
roots: ['packages/contracts/src/facades/client.ts'],
|
|
},
|
|
{
|
|
name: 'capture-contract',
|
|
kind: 'vocabulary',
|
|
roots: ['packages/contracts/src/facades/capture.ts'],
|
|
},
|
|
{
|
|
name: 'replay-contract',
|
|
kind: 'vocabulary',
|
|
roots: ['packages/contracts/src/facades/replay.ts'],
|
|
},
|
|
{
|
|
name: 'progress-contract',
|
|
kind: 'vocabulary',
|
|
roots: ['packages/contracts/src/facades/progress.ts'],
|
|
},
|
|
],
|
|
capabilities: [
|
|
{
|
|
name: 'request-runtime-binding',
|
|
kind: 'capability',
|
|
root: 'src/daemon/request-runtime-binding.ts',
|
|
exports: [
|
|
'BindDeviceRuntime',
|
|
'BindExactDeviceRuntime',
|
|
'BoundDeviceIdentity',
|
|
'InspectDeviceRuntimeFacts',
|
|
'RequestRuntimeBindings',
|
|
'RuntimeAdmissionBindings',
|
|
'createRequestRuntimeBindings',
|
|
'ensureBoundDeviceReady',
|
|
],
|
|
},
|
|
{
|
|
name: 'session-script-publication',
|
|
kind: 'capability',
|
|
root: 'src/daemon/session-script-publication-capability.ts',
|
|
exports: [
|
|
'abortAuthoringOnSecondOpen',
|
|
'applyRecordedSaveScriptFlags',
|
|
'armAuthoringOnOpen',
|
|
'effectiveWriteForce',
|
|
'isAuthoringArmedSession',
|
|
'isSessionRecording',
|
|
'isSessionScriptPublished',
|
|
'markActivePublicationDone',
|
|
'markCloseGeneratedPublicationDone',
|
|
'retargetActivePublication',
|
|
],
|
|
},
|
|
],
|
|
liveState: [
|
|
{
|
|
name: 'session-state-shape',
|
|
kind: 'live-state-shape',
|
|
root: 'src/daemon/session-state.ts',
|
|
exports: ['SessionState'],
|
|
},
|
|
{
|
|
name: 'session-store-authority',
|
|
kind: 'live-state-authority',
|
|
root: 'src/daemon/session-store.ts',
|
|
exports: ['SessionStore'],
|
|
},
|
|
],
|
|
executablePolicies: [
|
|
{
|
|
name: 'snapshot-policy',
|
|
kind: 'executable-policy',
|
|
roots: ['src/snapshot/'],
|
|
forbiddenTargetRoots: ['src/daemon/'],
|
|
},
|
|
],
|
|
} as const;
|
|
|
|
export function matchesDeclaredRoot(file: string, root: string): boolean {
|
|
return root.endsWith('/') ? file.startsWith(root) : file === root;
|
|
}
|