gesso-framework 0.4.2 → 0.5.1

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.
@@ -0,0 +1,326 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
+ import { a as agentSurface, i as serveAgentPort, n as combineSurfaces, r as remoteSurface, t as AGENT_PORT } from "./remote-xxij8zXe.js";
3
+ //#region src/agent/ui.ts
4
+ const TARGET = {
5
+ ref: {
6
+ type: "string",
7
+ description: "The ref ui_snapshot gave the control, such as \"e4\"."
8
+ },
9
+ role: {
10
+ type: "string",
11
+ description: "The control's role, such as \"button\" or \"textbox\"."
12
+ },
13
+ name: {
14
+ type: "string",
15
+ description: "The control's accessible name, as ui_snapshot shows it in quotes."
16
+ }
17
+ };
18
+ const TOOLS = [
19
+ {
20
+ name: "ui_snapshot",
21
+ title: "Read the screen",
22
+ description: "The screen as an outline of what a screen reader would announce: each control with its role, name, state, value and a ref to act on it by. Read it before acting, and again whenever an action did not do what you expected. Prefer a channel tool when one covers what you want; these act on the interface.",
23
+ inputSchema: {
24
+ type: "object",
25
+ properties: {},
26
+ additionalProperties: false
27
+ },
28
+ annotations: {
29
+ readOnlyHint: true,
30
+ openWorldHint: false
31
+ }
32
+ },
33
+ {
34
+ name: "ui_press",
35
+ title: "Press a control",
36
+ description: "Presses a button, link, checkbox, tab or any other control, as a click would. Name it by ref, or by role and name. Returns the screen afterwards.",
37
+ inputSchema: {
38
+ type: "object",
39
+ properties: TARGET,
40
+ additionalProperties: false
41
+ },
42
+ annotations: {
43
+ readOnlyHint: false,
44
+ destructiveHint: false,
45
+ openWorldHint: false
46
+ }
47
+ },
48
+ {
49
+ name: "ui_type",
50
+ title: "Fill a field",
51
+ description: "Replaces a text field's whole text, as if the person had selected it all and typed. Name the field by ref, or by role and name. Returns the screen afterwards; press Enter with ui_key to submit.",
52
+ inputSchema: {
53
+ type: "object",
54
+ properties: {
55
+ ...TARGET,
56
+ text: {
57
+ type: "string",
58
+ description: "The text the field should hold."
59
+ }
60
+ },
61
+ required: ["text"],
62
+ additionalProperties: false
63
+ },
64
+ annotations: {
65
+ readOnlyHint: false,
66
+ destructiveHint: false,
67
+ openWorldHint: false
68
+ }
69
+ },
70
+ {
71
+ name: "ui_focus",
72
+ title: "Focus a control",
73
+ description: "Moves keyboard focus to a control, by ref or by role and name. Returns the screen afterwards.",
74
+ inputSchema: {
75
+ type: "object",
76
+ properties: TARGET,
77
+ additionalProperties: false
78
+ },
79
+ annotations: {
80
+ readOnlyHint: false,
81
+ destructiveHint: false,
82
+ idempotentHint: true,
83
+ openWorldHint: false
84
+ }
85
+ },
86
+ {
87
+ name: "ui_key",
88
+ title: "Press a key",
89
+ description: "Presses a key where focus is, as the keyboard would: Enter, Escape, Tab, ArrowDown, a letter. Modifiers are optional. Returns the screen afterwards.",
90
+ inputSchema: {
91
+ type: "object",
92
+ properties: {
93
+ key: {
94
+ type: "string",
95
+ description: "A KeyboardEvent key name, such as \"Enter\", \"Escape\" or \"a\"."
96
+ },
97
+ shift: { type: "boolean" },
98
+ ctrl: { type: "boolean" },
99
+ alt: { type: "boolean" },
100
+ meta: { type: "boolean" }
101
+ },
102
+ required: ["key"],
103
+ additionalProperties: false
104
+ },
105
+ annotations: {
106
+ readOnlyHint: false,
107
+ destructiveHint: false,
108
+ openWorldHint: false
109
+ }
110
+ }
111
+ ];
112
+ /**
113
+ * Short names for record ids.
114
+ *
115
+ * A record's id is its path through the tree, `root:0:0:component:1:0:1`,
116
+ * which is exact and long, and an outline repeats one on every line an
117
+ * agent reads. A ref is `e` and a number, given the first time a record
118
+ * is seen and kept for as long as the surface lives, so the ref an agent
119
+ * read in one snapshot still names the same control in the next.
120
+ */
121
+ var UiRefs = class {
122
+ byId = /* @__PURE__ */ new Map();
123
+ byRef = /* @__PURE__ */ new Map();
124
+ refOf(id) {
125
+ let ref = this.byId.get(id);
126
+ if (ref === void 0) {
127
+ ref = `e${this.byId.size + 1}`;
128
+ this.byId.set(id, ref);
129
+ this.byRef.set(ref, id);
130
+ }
131
+ return ref;
132
+ }
133
+ /** The record id a ref names, or the input itself when it is not a ref this surface gave. */
134
+ idOf(ref) {
135
+ return this.byRef.get(ref) ?? ref;
136
+ }
137
+ };
138
+ /**
139
+ * The tools, over a host. `host` returns undefined until the
140
+ * application has started, and a tool called before then says so.
141
+ */
142
+ function uiSurface(host, options = {}) {
143
+ const quietMs = options.quietMs ?? 50;
144
+ const settleMs = options.settleMs ?? 1e3;
145
+ const refs = new UiRefs();
146
+ /** Waits until two readings of the screen agree, then returns the last. */
147
+ const settled = async (ui) => {
148
+ const start = Date.now();
149
+ let previous = "";
150
+ for (;;) {
151
+ ui.flush();
152
+ const current = outline(ui.semanticsTree(), ui.focusedNodeId(), refs);
153
+ if (current === previous || Date.now() - start >= settleMs) return current;
154
+ previous = current;
155
+ await new Promise((resolve) => setTimeout(resolve, quietMs));
156
+ }
157
+ };
158
+ const act = async (args, perform) => {
159
+ const ui = host();
160
+ if (ui === void 0) return failure("The application has not started yet.");
161
+ ui.flush();
162
+ const found = resolveTarget(ui.semanticsTree(), args, refs);
163
+ if (typeof found === "string") return failure(found);
164
+ if (found.disabled === true) return failure(`${describe(found)} is disabled.`);
165
+ const problem = perform(ui, found);
166
+ if (problem !== null) return failure(problem);
167
+ return success(await settled(ui));
168
+ };
169
+ return {
170
+ tools: () => TOOLS,
171
+ call: async (name, args = {}) => {
172
+ switch (name) {
173
+ case "ui_snapshot": {
174
+ const ui = host();
175
+ return ui === void 0 ? failure("The application has not started yet.") : success(await settled(ui));
176
+ }
177
+ case "ui_press": return act(args, (ui, record) => (ui.applySemanticsAction({
178
+ id: record.id,
179
+ action: "click"
180
+ }), null));
181
+ case "ui_focus": return act(args, (ui, record) => (ui.applySemanticsAction({
182
+ id: record.id,
183
+ action: "focus"
184
+ }), null));
185
+ case "ui_type":
186
+ if (typeof args.text !== "string") return failure("ui_type needs text: the text the field should hold.");
187
+ return act(args, (ui, record) => {
188
+ if (record.states?.includes("readonly")) return `${describe(record)} is read-only.`;
189
+ ui.applySemanticsAction({
190
+ id: record.id,
191
+ action: "setValue",
192
+ value: args.text
193
+ });
194
+ return null;
195
+ });
196
+ case "ui_key": {
197
+ const ui = host();
198
+ if (ui === void 0) return failure("The application has not started yet.");
199
+ if (typeof args.key !== "string" || args.key === "") return failure("ui_key needs key: a KeyboardEvent key name, such as \"Enter\".");
200
+ ui.key(args.key, {
201
+ shift: args.shift === true,
202
+ ctrl: args.ctrl === true,
203
+ alt: args.alt === true,
204
+ meta: args.meta === true
205
+ });
206
+ return success(await settled(ui));
207
+ }
208
+ default: return failure(`There is no tool called ${name}.`);
209
+ }
210
+ },
211
+ resources: () => [],
212
+ read: () => void 0,
213
+ dispose: () => {}
214
+ };
215
+ }
216
+ /**
217
+ * The tree as an indented outline, one control per line:
218
+ *
219
+ * - button "Add one" [e3]
220
+ * - textbox "Title" value="Groceries" [e4] (focused)
221
+ *
222
+ * Text, because an agent reads it, and an outline is a third the size
223
+ * of the same tree as JSON. A record with neither a role nor a name is
224
+ * structure, and its children are lifted to its depth.
225
+ */
226
+ function outline(tree, focused, refs = new UiRefs()) {
227
+ const depths = /* @__PURE__ */ new Map();
228
+ const lines = [];
229
+ for (const record of tree.values()) {
230
+ const parentDepth = record.parent === null ? -1 : depths.get(record.parent) ?? -1;
231
+ const shown = record.role !== void 0 || record.label !== void 0 && record.label !== "";
232
+ const depth = shown ? parentDepth + 1 : parentDepth;
233
+ depths.set(record.id, depth);
234
+ if (!shown) continue;
235
+ const parts = [`${" ".repeat(depth)}- ${describe(record)}`];
236
+ if (record.states !== void 0 && record.states.length > 0) parts.push(`[${record.states.join(", ")}]`);
237
+ const value = valueOf(record);
238
+ if (value !== null) parts.push(`value=${JSON.stringify(value)}`);
239
+ if (record.disabled === true) parts.push("(disabled)");
240
+ parts.push(`[${refs.refOf(record.id)}]`);
241
+ if (record.id === focused) parts.push("(focused)");
242
+ lines.push(parts.join(" "));
243
+ }
244
+ return lines.length === 0 ? "(the screen announces nothing)" : lines.join("\n");
245
+ }
246
+ function describe(record) {
247
+ const name = record.label === void 0 || record.label === "" ? "" : ` ${JSON.stringify(record.label)}`;
248
+ return `${record.role ?? "text"}${name}`;
249
+ }
250
+ function valueOf(record) {
251
+ if (record.valueText !== void 0 && record.valueText !== record.label) return record.valueText;
252
+ if (record.valueNow !== void 0) return String(record.valueNow);
253
+ return null;
254
+ }
255
+ /**
256
+ * The record an action names: by ref, or by role and name. A name
257
+ * matches exactly before it matches as a part, ignoring case, so
258
+ * "Save" finds the button called Save rather than "Save as". More than
259
+ * one match is an error listing them, because guessing which of two
260
+ * buttons an agent meant is how the wrong one gets pressed.
261
+ */
262
+ function resolveTarget(tree, args, refs = new UiRefs()) {
263
+ const ref = typeof args.ref === "string" ? args.ref : void 0;
264
+ const role = typeof args.role === "string" ? args.role : void 0;
265
+ const name = typeof args.name === "string" ? args.name : void 0;
266
+ if (ref !== void 0) return tree.get(refs.idOf(ref)) ?? `Nothing on the screen has the ref ${ref} now. Read ui_snapshot again for current refs.`;
267
+ if (role === void 0 && name === void 0) return "Say which control: a ref from ui_snapshot, or a role and a name.";
268
+ const candidates = [...tree.values()].filter((record) => role === void 0 || record.role === role);
269
+ let matches = candidates;
270
+ if (name !== void 0) {
271
+ const wanted = name.toLowerCase();
272
+ const exact = candidates.filter((record) => record.label?.toLowerCase() === wanted);
273
+ matches = exact.length > 0 ? exact : candidates.filter((record) => record.label?.toLowerCase().includes(wanted));
274
+ }
275
+ const wanted = [role, name === void 0 ? void 0 : JSON.stringify(name)].filter(Boolean).join(" ");
276
+ if (matches.length === 1) return matches[0];
277
+ if (matches.length === 0) {
278
+ const nearby = candidates.filter((record) => record.role !== void 0).slice(0, 10).map((record) => `${describe(record)} [${refs.refOf(record.id)}]`);
279
+ return `Nothing on the screen is ${wanted}.${nearby.length > 0 ? ` Some that are: ${nearby.join("; ")}.` : ""}`;
280
+ }
281
+ return `${matches.length} controls are ${wanted}: ${matches.slice(0, 10).map((record) => `${describe(record)} [${refs.refOf(record.id)}]`).join("; ")}. Use a ref.`;
282
+ }
283
+ function success(text) {
284
+ return {
285
+ content: [{
286
+ type: "text",
287
+ text
288
+ }],
289
+ isError: false
290
+ };
291
+ }
292
+ function failure(text) {
293
+ return {
294
+ content: [{
295
+ type: "text",
296
+ text
297
+ }],
298
+ isError: true
299
+ };
300
+ }
301
+ //#endregion
302
+ //#region src/agent/app.ts
303
+ var app_exports = /* @__PURE__ */ __exportAll({ serveApplicationAgent: () => serveApplicationAgent });
304
+ /**
305
+ * Answers an agent port with the whole application: the channels this
306
+ * thread feeds, whatever each worker behind it serves, and the screen.
307
+ *
308
+ * The thread that draws is the one that knows all three, which is the
309
+ * render worker in the worker configuration and the page in the single
310
+ * thread one; both answer with this. The screen comes last, so a
311
+ * channel tool keeps its name.
312
+ */
313
+ function serveApplicationAgent(port, parts) {
314
+ return serveAgentPort(port, (confirm) => {
315
+ const workers = [...parts.workers()].filter((worker) => worker.spawned).map((worker) => remoteSurface(worker.open(AGENT_PORT), { confirm }));
316
+ return combineSurfaces([
317
+ agentSurface(parts.channels(), { confirm }),
318
+ ...workers,
319
+ uiSurface(parts.ui)
320
+ ]);
321
+ });
322
+ }
323
+ //#endregion
324
+ export { resolveTarget as a, outline as i, serveApplicationAgent as n, uiSurface as o, UiRefs as r, app_exports as t };
325
+
326
+ //# sourceMappingURL=app-BvlIO1G9.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-BvlIO1G9.js","names":[],"sources":["../src/agent/ui.ts","../src/agent/app.ts"],"sourcesContent":["import type { UiKeyModifiers, UiSemanticsAction, UiSemanticsMap, UiSemanticsRecord } from 'gesso-core';\n\nimport type { JsonSchema } from '../channel/ChannelSchema';\nimport type { AgentSurface, AgentTool, AgentToolResult } from './AgentSurface';\n\n/**\n * The screen, as tools an agent can use.\n *\n * Channels are the application's own vocabulary, and the right way in\n * for anything they cover. Not everything is covered: a dialog's\n * buttons, a tab, a field the person is halfway through filling. For\n * those an agent has to do what a person does, and a canvas gives it\n * nothing to do that with: no DOM to query, no element to click.\n *\n * The semantics tree is the answer a screen reader already gets. Every\n * control has a role and an accessible name, a state, a value, and an\n * id, so an agent can read the screen as an outline and act on a\n * control by naming it. Acting goes through `applySemanticsAction`,\n * the path the accessibility mirror uses, which turns a press into the\n * same click a pointer makes and a value into the same edit a keyboard\n * makes: an agent can do exactly what a person could, no more, and an\n * open focus trap holds it as it holds Tab.\n *\n * ui_snapshot the screen as an outline, each control with a ref\n * ui_press press a control, by ref or by role and name\n * ui_type replace a field's text\n * ui_focus move focus to a control\n * ui_key press a key where focus is: Enter, Escape, Tab, ArrowDown\n *\n * Each action answers with the outline as it is afterwards.\n */\n\n/** What the tools need of the running application. */\nexport interface UiHost {\n semanticsTree(): UiSemanticsMap;\n focusedNodeId(): string | null;\n applySemanticsAction(action: UiSemanticsAction): void;\n key(key: string, modifiers: UiKeyModifiers): void;\n /**\n * Runs a pending frame now. A tab in the background is sent no\n * frames, and without one an action's effect never reaches the tree\n * this reads back.\n */\n flush(): void;\n}\n\nexport interface UiSurfaceOptions {\n /** How long the screen must hold still after an action before it is read back (default 50 ms). */\n quietMs?: number;\n /** The longest an action waits for that (default 1000 ms). */\n settleMs?: number;\n}\n\nconst TARGET: Record<string, JsonSchema> = {\n ref: { type: 'string', description: 'The ref ui_snapshot gave the control, such as \"e4\".' },\n role: { type: 'string', description: 'The control\\'s role, such as \"button\" or \"textbox\".' },\n name: { type: 'string', description: \"The control's accessible name, as ui_snapshot shows it in quotes.\" }\n};\n\nconst TOOLS: readonly AgentTool[] = [\n {\n name: 'ui_snapshot',\n title: 'Read the screen',\n description:\n 'The screen as an outline of what a screen reader would announce: each control with its role, name, state, ' +\n 'value and a ref to act on it by. Read it before acting, and again whenever an action did not do what you ' +\n 'expected. Prefer a channel tool when one covers what you want; these act on the interface.',\n inputSchema: { type: 'object', properties: {}, additionalProperties: false },\n annotations: { readOnlyHint: true, openWorldHint: false }\n },\n {\n name: 'ui_press',\n title: 'Press a control',\n description:\n 'Presses a button, link, checkbox, tab or any other control, as a click would. Name it by ref, or by role and ' +\n 'name. Returns the screen afterwards.',\n inputSchema: { type: 'object', properties: TARGET, additionalProperties: false },\n annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }\n },\n {\n name: 'ui_type',\n title: 'Fill a field',\n description:\n \"Replaces a text field's whole text, as if the person had selected it all and typed. Name the field by ref, or \" +\n 'by role and name. Returns the screen afterwards; press Enter with ui_key to submit.',\n inputSchema: {\n type: 'object',\n properties: { ...TARGET, text: { type: 'string', description: 'The text the field should hold.' } },\n required: ['text'],\n additionalProperties: false\n },\n annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }\n },\n {\n name: 'ui_focus',\n title: 'Focus a control',\n description: 'Moves keyboard focus to a control, by ref or by role and name. Returns the screen afterwards.',\n inputSchema: { type: 'object', properties: TARGET, additionalProperties: false },\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }\n },\n {\n name: 'ui_key',\n title: 'Press a key',\n description:\n 'Presses a key where focus is, as the keyboard would: Enter, Escape, Tab, ArrowDown, a letter. Modifiers are ' +\n 'optional. Returns the screen afterwards.',\n inputSchema: {\n type: 'object',\n properties: {\n key: { type: 'string', description: 'A KeyboardEvent key name, such as \"Enter\", \"Escape\" or \"a\".' },\n shift: { type: 'boolean' },\n ctrl: { type: 'boolean' },\n alt: { type: 'boolean' },\n meta: { type: 'boolean' }\n },\n required: ['key'],\n additionalProperties: false\n },\n annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }\n }\n];\n\n/**\n * Short names for record ids.\n *\n * A record's id is its path through the tree, `root:0:0:component:1:0:1`,\n * which is exact and long, and an outline repeats one on every line an\n * agent reads. A ref is `e` and a number, given the first time a record\n * is seen and kept for as long as the surface lives, so the ref an agent\n * read in one snapshot still names the same control in the next.\n */\nexport class UiRefs {\n private readonly byId = new Map<string, string>();\n private readonly byRef = new Map<string, string>();\n\n refOf(id: string): string {\n let ref = this.byId.get(id);\n if (ref === undefined) {\n ref = `e${this.byId.size + 1}`;\n this.byId.set(id, ref);\n this.byRef.set(ref, id);\n }\n return ref;\n }\n\n /** The record id a ref names, or the input itself when it is not a ref this surface gave. */\n idOf(ref: string): string {\n return this.byRef.get(ref) ?? ref;\n }\n}\n\n/**\n * The tools, over a host. `host` returns undefined until the\n * application has started, and a tool called before then says so.\n */\nexport function uiSurface(host: () => UiHost | undefined, options: UiSurfaceOptions = {}): AgentSurface {\n const quietMs = options.quietMs ?? 50;\n const settleMs = options.settleMs ?? 1000;\n const refs = new UiRefs();\n\n /** Waits until two readings of the screen agree, then returns the last. */\n const settled = async (ui: UiHost): Promise<string> => {\n const start = Date.now();\n let previous = '';\n for (;;) {\n ui.flush();\n const current = outline(ui.semanticsTree(), ui.focusedNodeId(), refs);\n if (current === previous || Date.now() - start >= settleMs) {\n return current;\n }\n previous = current;\n await new Promise(resolve => setTimeout(resolve, quietMs));\n }\n };\n\n const act = async (\n args: Readonly<Record<string, unknown>>,\n perform: (ui: UiHost, record: UiSemanticsRecord) => string | null\n ): Promise<AgentToolResult> => {\n const ui = host();\n if (ui === undefined) {\n return failure('The application has not started yet.');\n }\n ui.flush();\n const found = resolveTarget(ui.semanticsTree(), args, refs);\n if (typeof found === 'string') {\n return failure(found);\n }\n if (found.disabled === true) {\n return failure(`${describe(found)} is disabled.`);\n }\n const problem = perform(ui, found);\n if (problem !== null) {\n return failure(problem);\n }\n return success(await settled(ui));\n };\n\n return {\n tools: () => TOOLS,\n call: async (name, args = {}) => {\n switch (name) {\n case 'ui_snapshot': {\n const ui = host();\n return ui === undefined ? failure('The application has not started yet.') : success(await settled(ui));\n }\n case 'ui_press':\n return act(args, (ui, record) => (ui.applySemanticsAction({ id: record.id, action: 'click' }), null));\n case 'ui_focus':\n return act(args, (ui, record) => (ui.applySemanticsAction({ id: record.id, action: 'focus' }), null));\n case 'ui_type':\n if (typeof args.text !== 'string') {\n return failure('ui_type needs text: the text the field should hold.');\n }\n return act(args, (ui, record) => {\n if (record.states?.includes('readonly')) {\n return `${describe(record)} is read-only.`;\n }\n ui.applySemanticsAction({ id: record.id, action: 'setValue', value: args.text as string });\n return null;\n });\n case 'ui_key': {\n const ui = host();\n if (ui === undefined) {\n return failure('The application has not started yet.');\n }\n if (typeof args.key !== 'string' || args.key === '') {\n return failure('ui_key needs key: a KeyboardEvent key name, such as \"Enter\".');\n }\n ui.key(args.key, {\n shift: args.shift === true,\n ctrl: args.ctrl === true,\n alt: args.alt === true,\n meta: args.meta === true\n });\n return success(await settled(ui));\n }\n default:\n return failure(`There is no tool called ${name}.`);\n }\n },\n resources: () => [],\n read: () => undefined,\n dispose: () => {}\n };\n}\n\n/**\n * The tree as an indented outline, one control per line:\n *\n * - button \"Add one\" [e3]\n * - textbox \"Title\" value=\"Groceries\" [e4] (focused)\n *\n * Text, because an agent reads it, and an outline is a third the size\n * of the same tree as JSON. A record with neither a role nor a name is\n * structure, and its children are lifted to its depth.\n */\nexport function outline(tree: UiSemanticsMap, focused: string | null, refs: UiRefs = new UiRefs()): string {\n const depths = new Map<string, number>();\n const lines: string[] = [];\n for (const record of tree.values()) {\n const parentDepth = record.parent === null ? -1 : (depths.get(record.parent) ?? -1);\n const shown = record.role !== undefined || (record.label !== undefined && record.label !== '');\n const depth = shown ? parentDepth + 1 : parentDepth;\n depths.set(record.id, depth);\n if (!shown) {\n continue;\n }\n const parts = [`${' '.repeat(depth)}- ${describe(record)}`];\n if (record.states !== undefined && record.states.length > 0) {\n parts.push(`[${record.states.join(', ')}]`);\n }\n const value = valueOf(record);\n if (value !== null) {\n parts.push(`value=${JSON.stringify(value)}`);\n }\n if (record.disabled === true) {\n parts.push('(disabled)');\n }\n parts.push(`[${refs.refOf(record.id)}]`);\n if (record.id === focused) {\n parts.push('(focused)');\n }\n lines.push(parts.join(' '));\n }\n return lines.length === 0 ? '(the screen announces nothing)' : lines.join('\\n');\n}\n\nfunction describe(record: UiSemanticsRecord): string {\n const name = record.label === undefined || record.label === '' ? '' : ` ${JSON.stringify(record.label)}`;\n return `${record.role ?? 'text'}${name}`;\n}\n\nfunction valueOf(record: UiSemanticsRecord): string | null {\n if (record.valueText !== undefined && record.valueText !== record.label) {\n return record.valueText;\n }\n if (record.valueNow !== undefined) {\n return String(record.valueNow);\n }\n return null;\n}\n\n/**\n * The record an action names: by ref, or by role and name. A name\n * matches exactly before it matches as a part, ignoring case, so\n * \"Save\" finds the button called Save rather than \"Save as\". More than\n * one match is an error listing them, because guessing which of two\n * buttons an agent meant is how the wrong one gets pressed.\n */\nexport function resolveTarget(\n tree: UiSemanticsMap,\n args: Readonly<Record<string, unknown>>,\n refs: UiRefs = new UiRefs()\n): UiSemanticsRecord | string {\n const ref = typeof args.ref === 'string' ? args.ref : undefined;\n const role = typeof args.role === 'string' ? args.role : undefined;\n const name = typeof args.name === 'string' ? args.name : undefined;\n if (ref !== undefined) {\n return (\n tree.get(refs.idOf(ref)) ??\n `Nothing on the screen has the ref ${ref} now. Read ui_snapshot again for current refs.`\n );\n }\n if (role === undefined && name === undefined) {\n return 'Say which control: a ref from ui_snapshot, or a role and a name.';\n }\n const candidates = [...tree.values()].filter(record => role === undefined || record.role === role);\n let matches = candidates;\n if (name !== undefined) {\n const wanted = name.toLowerCase();\n const exact = candidates.filter(record => record.label?.toLowerCase() === wanted);\n matches = exact.length > 0 ? exact : candidates.filter(record => record.label?.toLowerCase().includes(wanted));\n }\n const wanted = [role, name === undefined ? undefined : JSON.stringify(name)].filter(Boolean).join(' ');\n if (matches.length === 1) {\n return matches[0];\n }\n if (matches.length === 0) {\n const nearby = candidates\n .filter(record => record.role !== undefined)\n .slice(0, 10)\n .map(record => `${describe(record)} [${refs.refOf(record.id)}]`);\n return `Nothing on the screen is ${wanted}.${nearby.length > 0 ? ` Some that are: ${nearby.join('; ')}.` : ''}`;\n }\n return `${matches.length} controls are ${wanted}: ${matches\n .slice(0, 10)\n .map(record => `${describe(record)} [${refs.refOf(record.id)}]`)\n .join('; ')}. Use a ref.`;\n}\n\nfunction success(text: string): AgentToolResult {\n return { content: [{ type: 'text', text }], isError: false };\n}\n\nfunction failure(text: string): AgentToolResult {\n return { content: [{ type: 'text', text }], isError: true };\n}\n","import type { ServedChannel } from '../channel/serveChannels';\nimport type { WorkerHandle } from '../worker/WorkerPorts';\nimport { agentSurface } from './AgentSurface';\nimport { AGENT_PORT, combineSurfaces, remoteSurface, serveAgentPort } from './remote';\nimport { uiSurface, type UiHost } from './ui';\n\n/** What a thread that draws the application can offer an agent. */\nexport interface ApplicationAgentParts {\n /** The channels fed from this thread. */\n readonly channels: () => readonly ServedChannel[];\n /** The workers behind it, each asked over an agent port of its own. */\n readonly workers: () => Iterable<WorkerHandle>;\n /** The screen, once the application has started. */\n readonly ui: () => UiHost | undefined;\n}\n\n/**\n * Answers an agent port with the whole application: the channels this\n * thread feeds, whatever each worker behind it serves, and the screen.\n *\n * The thread that draws is the one that knows all three, which is the\n * render worker in the worker configuration and the page in the single\n * thread one; both answer with this. The screen comes last, so a\n * channel tool keeps its name.\n */\nexport function serveApplicationAgent(port: MessagePort, parts: ApplicationAgentParts): () => void {\n return serveAgentPort(port, confirm => {\n const workers = [...parts.workers()]\n .filter(worker => worker.spawned)\n .map(worker => remoteSurface(worker.open(AGENT_PORT), { confirm }));\n return combineSurfaces([agentSurface(parts.channels(), { confirm }), ...workers, uiSurface(parts.ui)]);\n });\n}\n"],"mappings":"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"}