Repository navigation
Expand file tree
/
Copy pathruntime-session.ts
More file actions
106 lines (102 loc) · 3.78 KB
/
Copy pathruntime-session.ts
File metadata and controls
106 lines (102 loc) · 3.78 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
import type {
CommandSessionRecord,
CommandSessionStore,
} from '@agent-device/contracts/runtime-contract';
import { emitDiagnostic } from '@agent-device/host-kit/diagnostics';
import { movingTargetBaseline } from './deferred-interaction-outcome.ts';
import { refFrameTree } from './ref-frame.ts';
import type { SessionRef, SessionState } from './session-state.ts';
import type { SessionStore } from './session-store.ts';
export type RuntimeSessionRecordOptions = {
includeSnapshot?: boolean;
/**
* ADR 0014: omit the authorized frame tree so `@ref` resolution binds against
* the latest observation instead. Set for a mutating `find`'s internal leaf
* dispatch, whose ref was just re-resolved by locator against the find's fresh
* capture — the frame model does not govern that internal re-resolution.
*/
omitRefFrameSnapshot?: boolean;
metadata?: Record<string, unknown>;
};
function toRuntimeSessionRecord(
session: SessionState | undefined,
name: string,
options: RuntimeSessionRecordOptions = {},
): CommandSessionRecord | undefined {
if (!session) return undefined;
const frameTree = refFrameTree(session);
// #3471: the moving-target gate judges a freshly resolved rect against the tree the PREVIOUS
// action acted through, so that tree has to reach the command layer — the `snapshot` beside it is
// the newest observation, which is the one thing that cannot show a target having moved. Absent
// whenever the session is idle or owes the capture-side loop instead, which is what makes an
// ordinary press pay the gate nothing.
const baseline = movingTargetBaseline(session);
return {
name,
appBundleId: session.appBundleId,
appName: session.appName,
...(options.includeSnapshot === true
? {
snapshot: session.snapshot,
// ADR 0014: expose the authorized frame tree so ref resolution binds a
// `@eN` to the node the caller was authorized against, not to whatever
// now sits at that index in a newer observation.
...(frameTree && options.omitRefFrameSnapshot !== true
? { refFrameSnapshot: frameTree }
: {}),
...(baseline ? { movingTargetBaseline: baseline } : {}),
}
: {}),
metadata: {
surface: session.surface,
...(options.metadata ?? {}),
},
};
}
export function createReadonlyRuntimeSessionStore(
sessionName: string,
session: SessionState,
): CommandSessionStore {
return {
get: (name) =>
name === sessionName ? toRuntimeSessionRecord(session, sessionName) : undefined,
set: () => {},
};
}
export function createDaemonRuntimeSessionStore(params: {
sessionName: string;
sessionStore: SessionStore;
ref: SessionRef | undefined;
recordOptions?: RuntimeSessionRecordOptions;
setRecord: (
record: CommandSessionRecord,
current: SessionState | undefined,
ref: SessionRef | undefined,
) => SessionRef | void;
}): CommandSessionStore & { getRef(): SessionRef | undefined } {
let ref = params.ref;
return {
getRef: () => (ref ? params.sessionStore.refresh(ref) : undefined),
get: (name) =>
name === params.sessionName
? toRuntimeSessionRecord(
ref ? params.sessionStore.resolveCurrent(ref) : undefined,
params.sessionName,
params.recordOptions,
)
: undefined,
set: (record) => {
if (record.name !== params.sessionName) {
emitDiagnostic({
level: 'warn',
phase: 'runtime_session_write_skipped',
data: { expected: params.sessionName, received: record.name },
});
return;
}
const current = ref ? params.sessionStore.requireCurrent(ref) : undefined;
const published = params.setRecord(record, current, ref);
if (published) ref = published;
},
};
}