libretto 0.6.11 → 0.6.12

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.
Files changed (119) hide show
  1. package/README.md +4 -0
  2. package/README.template.md +4 -0
  3. package/dist/cli/cli.js +4 -3
  4. package/dist/cli/commands/ai.js +3 -2
  5. package/dist/cli/commands/browser.js +17 -17
  6. package/dist/cli/commands/execution.js +254 -234
  7. package/dist/cli/commands/experiments.js +100 -0
  8. package/dist/cli/commands/setup.js +20 -34
  9. package/dist/cli/commands/shared.js +10 -0
  10. package/dist/cli/commands/snapshot.js +81 -9
  11. package/dist/cli/commands/status.js +5 -4
  12. package/dist/cli/core/ai-model.js +6 -3
  13. package/dist/cli/core/browser.js +300 -121
  14. package/dist/cli/core/config.js +4 -2
  15. package/dist/cli/core/context.js +4 -0
  16. package/dist/cli/core/daemon/config.js +0 -6
  17. package/dist/cli/core/daemon/daemon.js +535 -89
  18. package/dist/cli/core/daemon/ipc.js +170 -129
  19. package/dist/cli/core/daemon/snapshot.js +72 -6
  20. package/dist/cli/core/experiments.js +66 -0
  21. package/dist/cli/core/session.js +5 -4
  22. package/dist/cli/core/skill-version.js +2 -1
  23. package/dist/cli/core/snapshot-analyzer.js +4 -3
  24. package/dist/cli/core/workflow-runner/runner.js +147 -0
  25. package/dist/cli/core/workflow-runtime.js +60 -0
  26. package/dist/cli/router.js +4 -1
  27. package/dist/shared/debug/pause-handler.d.ts +9 -0
  28. package/dist/shared/debug/pause-handler.js +15 -0
  29. package/dist/shared/debug/pause.d.ts +1 -2
  30. package/dist/shared/debug/pause.js +13 -36
  31. package/dist/shared/ipc/child-process-transport.d.ts +7 -0
  32. package/dist/shared/ipc/child-process-transport.js +60 -0
  33. package/dist/shared/ipc/child-process-transport.spec.d.ts +2 -0
  34. package/dist/shared/ipc/child-process-transport.spec.js +68 -0
  35. package/dist/shared/ipc/ipc.d.ts +46 -0
  36. package/dist/shared/ipc/ipc.js +165 -0
  37. package/dist/shared/ipc/ipc.spec.d.ts +2 -0
  38. package/dist/shared/ipc/ipc.spec.js +114 -0
  39. package/dist/shared/ipc/socket-transport.d.ts +9 -0
  40. package/dist/shared/ipc/socket-transport.js +143 -0
  41. package/dist/shared/ipc/socket-transport.spec.d.ts +2 -0
  42. package/dist/shared/ipc/socket-transport.spec.js +117 -0
  43. package/dist/shared/package-manager.d.ts +7 -0
  44. package/dist/shared/package-manager.js +60 -0
  45. package/dist/shared/paths/paths.d.ts +1 -8
  46. package/dist/shared/paths/paths.js +1 -49
  47. package/dist/shared/snapshot/capture-snapshot.d.ts +9 -0
  48. package/dist/shared/snapshot/capture-snapshot.js +463 -0
  49. package/dist/shared/snapshot/diff-snapshots.d.ts +72 -0
  50. package/dist/shared/snapshot/diff-snapshots.js +358 -0
  51. package/dist/shared/snapshot/render-snapshot.d.ts +39 -0
  52. package/dist/shared/snapshot/render-snapshot.js +651 -0
  53. package/dist/shared/snapshot/snapshot.spec.d.ts +2 -0
  54. package/dist/shared/snapshot/snapshot.spec.js +333 -0
  55. package/dist/shared/snapshot/types.d.ts +40 -0
  56. package/dist/shared/snapshot/types.js +0 -0
  57. package/dist/shared/snapshot/wait-for-page-stable.d.ts +17 -0
  58. package/dist/shared/snapshot/wait-for-page-stable.js +281 -0
  59. package/dist/shared/state/session-state.d.ts +1 -0
  60. package/dist/shared/state/session-state.js +1 -0
  61. package/docs/experiments.md +67 -0
  62. package/package.json +4 -2
  63. package/skills/libretto/SKILL.md +3 -1
  64. package/skills/libretto-readonly/SKILL.md +1 -1
  65. package/src/cli/AGENTS.md +7 -0
  66. package/src/cli/cli.ts +4 -3
  67. package/src/cli/commands/ai.ts +3 -2
  68. package/src/cli/commands/browser.ts +13 -11
  69. package/src/cli/commands/execution.ts +303 -271
  70. package/src/cli/commands/experiments.ts +120 -0
  71. package/src/cli/commands/setup.ts +18 -36
  72. package/src/cli/commands/shared.ts +20 -0
  73. package/src/cli/commands/snapshot.ts +99 -11
  74. package/src/cli/commands/status.ts +5 -4
  75. package/src/cli/core/ai-model.ts +6 -3
  76. package/src/cli/core/browser.ts +369 -147
  77. package/src/cli/core/config.ts +3 -1
  78. package/src/cli/core/context.ts +4 -0
  79. package/src/cli/core/daemon/config.ts +35 -19
  80. package/src/cli/core/daemon/daemon.ts +686 -106
  81. package/src/cli/core/daemon/ipc.ts +330 -214
  82. package/src/cli/core/daemon/snapshot.ts +106 -8
  83. package/src/cli/core/experiments.ts +85 -0
  84. package/src/cli/core/session.ts +5 -4
  85. package/src/cli/core/skill-version.ts +2 -1
  86. package/src/cli/core/snapshot-analyzer.ts +4 -3
  87. package/src/cli/core/workflow-runner/runner.ts +237 -0
  88. package/src/cli/core/workflow-runtime.ts +85 -0
  89. package/src/cli/router.ts +4 -1
  90. package/src/shared/debug/pause-handler.ts +20 -0
  91. package/src/shared/debug/pause.ts +14 -48
  92. package/src/shared/ipc/AGENTS.md +24 -0
  93. package/src/shared/ipc/child-process-transport.spec.ts +86 -0
  94. package/src/shared/ipc/child-process-transport.ts +96 -0
  95. package/src/shared/ipc/ipc.spec.ts +161 -0
  96. package/src/shared/ipc/ipc.ts +288 -0
  97. package/src/shared/ipc/socket-transport.spec.ts +141 -0
  98. package/src/shared/ipc/socket-transport.ts +189 -0
  99. package/src/shared/package-manager.ts +76 -0
  100. package/src/shared/paths/paths.ts +0 -72
  101. package/src/shared/snapshot/capture-snapshot.ts +615 -0
  102. package/src/shared/snapshot/diff-snapshots.ts +579 -0
  103. package/src/shared/snapshot/render-snapshot.ts +962 -0
  104. package/src/shared/snapshot/snapshot.spec.ts +388 -0
  105. package/src/shared/snapshot/types.ts +43 -0
  106. package/src/shared/snapshot/wait-for-page-stable.ts +425 -0
  107. package/src/shared/state/session-state.ts +1 -0
  108. package/dist/cli/core/daemon/index.js +0 -16
  109. package/dist/cli/core/daemon/spawn.js +0 -90
  110. package/dist/cli/core/pause-signals.js +0 -29
  111. package/dist/cli/workers/run-integration-runtime.js +0 -235
  112. package/dist/cli/workers/run-integration-worker-protocol.js +0 -17
  113. package/dist/cli/workers/run-integration-worker.js +0 -64
  114. package/src/cli/core/daemon/index.ts +0 -24
  115. package/src/cli/core/daemon/spawn.ts +0 -171
  116. package/src/cli/core/pause-signals.ts +0 -35
  117. package/src/cli/workers/run-integration-runtime.ts +0 -326
  118. package/src/cli/workers/run-integration-worker-protocol.ts +0 -19
  119. package/src/cli/workers/run-integration-worker.ts +0 -72
@@ -0,0 +1,651 @@
1
+ import { scopeSnapshotToRef } from "./capture-snapshot.js";
2
+ const MAX_CHILDREN_PER_PARENT = 4;
3
+ const MAX_LABEL_CHARS = 140;
4
+ const MAX_SUMMARY_TEXT_CHARS = 80;
5
+ const MAX_HREF_CHARS = 96;
6
+ const MAX_ACTIONS_IN_SUMMARY = 3;
7
+ const MAX_ACTION_LABEL_CHARS = 80;
8
+ const PRESERVE_CHILDREN_BY_ROLE = /* @__PURE__ */ new Set([
9
+ "document",
10
+ "main",
11
+ "navigation",
12
+ "banner",
13
+ "contentinfo",
14
+ "form",
15
+ "search",
16
+ "list",
17
+ "table",
18
+ "tabpanel"
19
+ ]);
20
+ const FLATTEN_ROLES = /* @__PURE__ */ new Set([
21
+ "none",
22
+ "presentation",
23
+ "LayoutTable",
24
+ "LayoutTableRow",
25
+ "LayoutTableCell"
26
+ ]);
27
+ const SKIP_ROLES = /* @__PURE__ */ new Set(["InlineTextBox", "ListMarker"]);
28
+ const ACTION_ROLES = /* @__PURE__ */ new Set([
29
+ "button",
30
+ "link",
31
+ "textbox",
32
+ "checkbox",
33
+ "radio",
34
+ "switch",
35
+ "combobox",
36
+ "listbox",
37
+ "menuitem",
38
+ "tab",
39
+ "slider"
40
+ ]);
41
+ const ACTION_STATE_ATTRS = /* @__PURE__ */ new Set([
42
+ "checked",
43
+ "disabled",
44
+ "expanded",
45
+ "pressed",
46
+ "selected",
47
+ "value",
48
+ "placeholder"
49
+ ]);
50
+ const TEXT_ACTION_ROLES = /* @__PURE__ */ new Set(["button", "link", "menuitem", "tab"]);
51
+ const KEEP_ROLES = /* @__PURE__ */ new Set([
52
+ "document",
53
+ "main",
54
+ "navigation",
55
+ "banner",
56
+ "contentinfo",
57
+ "form",
58
+ "search",
59
+ "list",
60
+ "listitem",
61
+ "button",
62
+ "link",
63
+ "image",
64
+ "textbox",
65
+ "checkbox",
66
+ "radio",
67
+ "switch",
68
+ "combobox",
69
+ "listbox",
70
+ "menu",
71
+ "menuitem",
72
+ "option",
73
+ "tab",
74
+ "slider"
75
+ ]);
76
+ const BLOCK_FLATTEN_ROLES = /* @__PURE__ */ new Set([
77
+ "paragraph",
78
+ "section",
79
+ "article",
80
+ "region",
81
+ "group",
82
+ "figure"
83
+ ]);
84
+ const RENDERED_STATE_PROPERTIES = [
85
+ "disabled",
86
+ "checked",
87
+ "expanded",
88
+ "selected",
89
+ "pressed",
90
+ "required",
91
+ "invalid",
92
+ "readonly",
93
+ "multiline",
94
+ "autocomplete",
95
+ "haspopup",
96
+ "value"
97
+ ];
98
+ function renderSnapshot(snapshot, refId) {
99
+ const snapshotTree = refId ? scopeSnapshotToRef(snapshot, refId) : snapshot;
100
+ const lines = [renderPageOpen(snapshotTree, "")];
101
+ for (const frame of renderSnapshotFrames(snapshotTree)) {
102
+ renderFrame(frame, 1, lines);
103
+ }
104
+ lines.push("</page>");
105
+ return lines.join("\n");
106
+ }
107
+ function renderSnapshotFrames(snapshot) {
108
+ return snapshot.frames.map(toRenderedFrame).filter(hasRenderedFrameContent);
109
+ }
110
+ function renderPageOpen(snapshot, prefix, selfClosing = false) {
111
+ return `${prefix}${formatTag(
112
+ "page",
113
+ [
114
+ ["title", firstNonEmpty(snapshot.title, snapshot.url) ?? ""],
115
+ ["url", snapshot.url]
116
+ ],
117
+ !selfClosing
118
+ )}`;
119
+ }
120
+ function renderFrameLine(frame, depth, prefix, selfClosing) {
121
+ const attrs = [
122
+ ["index", String(frame.index)],
123
+ ["url", normalizeText(frame.url, MAX_LABEL_CHARS)]
124
+ ];
125
+ if (frame.name)
126
+ attrs.push(["name", normalizeText(frame.name, MAX_LABEL_CHARS)]);
127
+ if (frame.parentId) attrs.push(["parent", frame.parentId]);
128
+ if (frame.status === "unavailable") {
129
+ attrs.push(["error", normalizeText(frame.error, 180)]);
130
+ }
131
+ return `${prefix}${indent(depth)}${formatTag("frame", attrs, !selfClosing)}`;
132
+ }
133
+ function renderTextNode(node, depth, prefix) {
134
+ return `${prefix}${indent(depth)}${escapeText(node.text)}`;
135
+ }
136
+ function indent(depth) {
137
+ return " ".repeat(depth);
138
+ }
139
+ function formatTag(tagName, attributes, hasChildren) {
140
+ const attrs = attributes.filter(([, value]) => value !== "").map(([name, value]) => ` ${name}="${escapeAttribute(value)}"`).join("");
141
+ return hasChildren ? `<${tagName}${attrs}>` : `<${tagName}${attrs} />`;
142
+ }
143
+ function renderChildrenTruncationNotice(children) {
144
+ const count = children.length;
145
+ const summaryActions = actionSummariesForChildren(children);
146
+ const textSnippet = previewForChildren(children, summaryActions.labels);
147
+ const elementLabel = count === 1 ? "element" : "elements";
148
+ const textSnippetPart = textSnippet ? `. Text snippet: ${JSON.stringify(textSnippet)}` : "";
149
+ const interactiveText = summaryActions.actions.length ? `. Interactive elements: ${summaryActions.actions.map((action) => action.markup).join(", ")}${summaryActions.hasMore ? ", ..." : ""}` : "";
150
+ return `[Truncated ${count} more ${elementLabel}${textSnippetPart}${interactiveText}]`;
151
+ }
152
+ function toRenderedFrame(frame) {
153
+ if (frame.status === "unavailable") return frame;
154
+ return {
155
+ status: "ok",
156
+ id: frame.id,
157
+ index: frame.index,
158
+ url: frame.url,
159
+ name: frame.name,
160
+ parentId: frame.parentId,
161
+ roots: frame.roots.flatMap((root) => toRenderedNodes(root, null))
162
+ };
163
+ }
164
+ function hasRenderedFrameContent(frame) {
165
+ if (frame.status === "unavailable") return true;
166
+ return frame.roots.length > 0;
167
+ }
168
+ function toRenderedNodes(node, parent) {
169
+ return toRenderedChildren(node, parent).filter(isRenderedNode);
170
+ }
171
+ function toRenderedChildren(node, parent) {
172
+ if (shouldSkipNode(node, parent)) return [];
173
+ if (isTextRole(node.role)) {
174
+ const text2 = firstNonEmpty(
175
+ node.name,
176
+ node.description,
177
+ primitiveToString(node.value)
178
+ );
179
+ if (text2 && text2 !== parent?.name && text2 !== nodeTextValue(parent)) {
180
+ return [{ kind: "text", text: text2 }];
181
+ }
182
+ return [];
183
+ }
184
+ const children = renderableChildren(node);
185
+ const role = tagNameForRole(node.role);
186
+ if (role === "heading") return renderHeading(node, children);
187
+ const compactRole = roleForNode(node, role, children);
188
+ if (compactRole === "image" && !hasNonEmptyAttribute(node, "src"))
189
+ return [];
190
+ if (compactRole === "link" && !hasNonEmptyAttribute(node, "href")) {
191
+ return flattenedChildren(node, children).filter(
192
+ hasVisibleTextOrInteractive
193
+ );
194
+ }
195
+ if (node.ignored || FLATTEN_ROLES.has(node.role) || !KEEP_ROLES.has(compactRole)) {
196
+ return flattenedChildren(node, children).filter(
197
+ hasVisibleTextOrInteractive
198
+ );
199
+ }
200
+ const text = normalizedText(children);
201
+ const suppressName = text.includes(normalizeRawText(node.name ?? "")) ? node.name : null;
202
+ const attrs = nodeAttributes(node, suppressName);
203
+ const content = nameAttributeAsContent(attrs, children);
204
+ const renderedChildren = removeDuplicateNestedActions(
205
+ compactRole,
206
+ content.attrs,
207
+ content.children
208
+ ).filter(hasVisibleTextOrInteractive);
209
+ if (!ACTION_ROLES.has(compactRole) && !renderedChildren.some(hasVisibleTextOrInteractive)) {
210
+ return [];
211
+ }
212
+ const rendered = {
213
+ kind: "node",
214
+ key: node.nodeId || node.ref || `${compactRole}:${content.attrs.map(([name, value]) => `${name}=${value}`).join(";")}`,
215
+ role: compactRole,
216
+ attrs: content.attrs,
217
+ children: renderedChildren
218
+ };
219
+ return [rendered];
220
+ }
221
+ function renderableChildren(node) {
222
+ const children = [];
223
+ for (const child of node.children)
224
+ children.push(...toRenderedChildren(child, node));
225
+ return mergeAdjacentText(children).filter(hasVisibleTextOrInteractive);
226
+ }
227
+ function renderHeading(node, children) {
228
+ const text = firstNonEmpty(node.name, normalizedText(children));
229
+ if (!text) return [];
230
+ return [
231
+ {
232
+ kind: "text",
233
+ text: `${"#".repeat(headingLevel(node))} ${text}`,
234
+ block: true
235
+ }
236
+ ];
237
+ }
238
+ function headingLevel(node) {
239
+ const rawLevel = node.properties.level;
240
+ const level = typeof rawLevel === "number" ? rawLevel : Number(rawLevel);
241
+ if (!Number.isFinite(level)) return 2;
242
+ return Math.min(6, Math.max(1, Math.round(level)));
243
+ }
244
+ function roleForNode(node, role, children) {
245
+ if (isPointerButtonCandidate(node, role, children)) return "button";
246
+ return role;
247
+ }
248
+ function isPointerButtonCandidate(node, role, children) {
249
+ if (KEEP_ROLES.has(role) && role !== "document") return false;
250
+ if (children.some(hasInteractiveNode)) return false;
251
+ if (!hasClickableHint(node)) return false;
252
+ return Boolean(firstNonEmpty(node.name, normalizedText(children)));
253
+ }
254
+ function hasClickableHint(node) {
255
+ if (node.attributes.cursor === "pointer") return true;
256
+ if (Object.hasOwn(node.attributes, "onclick")) return true;
257
+ const tabindex = node.attributes.tabindex;
258
+ return tabindex !== void 0 && Number(tabindex) >= 0;
259
+ }
260
+ function hasInteractiveNode(child) {
261
+ if (child.kind === "text") return false;
262
+ if (ACTION_ROLES.has(child.role)) return true;
263
+ return child.children.some(hasInteractiveNode);
264
+ }
265
+ function flattenedChildren(node, children) {
266
+ const fallbackText = fallbackTextForFlattenedNode(node);
267
+ const flattened = children.length > 0 || !fallbackText ? children : [{ kind: "text", text: fallbackText }];
268
+ if (!BLOCK_FLATTEN_ROLES.has(tagNameForRole(node.role))) return flattened;
269
+ return flattened.map(
270
+ (child) => child.kind === "text" ? { ...child, block: true } : child
271
+ );
272
+ }
273
+ function fallbackTextForFlattenedNode(node) {
274
+ const name = firstNonEmpty(node.name, primitiveToString(node.value));
275
+ if (!name) return null;
276
+ if (attributeMatchesName(node, "aria-label", name)) return null;
277
+ if (attributeMatchesName(node, "title", name)) return null;
278
+ if (attributeMatchesName(node, "alt", name)) return null;
279
+ return name;
280
+ }
281
+ function attributeMatchesName(node, attributeName, name) {
282
+ return normalizeRawText(node.attributes[attributeName] ?? "") === name;
283
+ }
284
+ function hasNonEmptyAttribute(node, attributeName) {
285
+ return normalizeRawText(node.attributes[attributeName] ?? "") !== "";
286
+ }
287
+ function removeDuplicateNestedActions(role, attrs, children) {
288
+ if (!ACTION_ROLES.has(role)) return children;
289
+ const label = firstNonEmpty(
290
+ attrFromAttrs(attrs, "name"),
291
+ normalizedText(children)
292
+ );
293
+ if (!label) return children;
294
+ return children.flatMap((child) => {
295
+ if (child.kind === "text") return [child];
296
+ if (!ACTION_ROLES.has(child.role)) return [child];
297
+ const childLabel = firstNonEmpty(
298
+ attrValue(child, "name"),
299
+ singleTextChild(child),
300
+ normalizedText(child.children)
301
+ );
302
+ return childLabel === label ? child.children : [child];
303
+ });
304
+ }
305
+ function nameAttributeAsContent(attrs, children) {
306
+ const name = attrFromAttrs(attrs, "name");
307
+ if (!name) return { attrs, children };
308
+ const attrsWithoutName = attrs.filter(([attr]) => attr !== "name");
309
+ if (normalizedText(children).includes(normalizeRawText(name))) {
310
+ return { attrs: attrsWithoutName, children };
311
+ }
312
+ return {
313
+ attrs: attrsWithoutName,
314
+ children: [{ kind: "text", text: name }, ...children]
315
+ };
316
+ }
317
+ function renderFrame(frame, depth, lines, prefix = "") {
318
+ if (frame.status === "unavailable") {
319
+ lines.push(renderFrameLine(frame, depth, prefix, true));
320
+ return;
321
+ }
322
+ lines.push(renderFrameLine(frame, depth, prefix, false));
323
+ for (const root of frame.roots) renderNode(root, depth + 1, lines, prefix);
324
+ lines.push(`${prefix}${indent(depth)}</frame>`);
325
+ }
326
+ function renderNode(node, depth, lines, prefix = "") {
327
+ if (renderFoldedSingleChildChain(node, depth, lines, prefix)) return;
328
+ if (node.children.length === 0) {
329
+ lines.push(
330
+ `${prefix}${indent(depth)}${formatTag(node.role, node.attrs, false)}`
331
+ );
332
+ return;
333
+ }
334
+ const singleText = singleTextChild(node);
335
+ if (singleText !== null) {
336
+ if (shouldRenderBareText(node)) {
337
+ lines.push(`${prefix}${indent(depth)}${escapeText(singleText)}`);
338
+ return;
339
+ }
340
+ lines.push(
341
+ `${prefix}${indent(depth)}${formatTag(node.role, node.attrs, true)}${escapeText(singleText)}</${node.role}>`
342
+ );
343
+ return;
344
+ }
345
+ lines.push(
346
+ `${prefix}${indent(depth)}${formatTag(node.role, node.attrs, true)}`
347
+ );
348
+ renderChildren(node.children, depth + 1, lines, prefix);
349
+ lines.push(`${prefix}${indent(depth)}</${node.role}>`);
350
+ }
351
+ function renderChildren(children, depth, lines, prefix) {
352
+ const renderedChildren = children.slice(0, MAX_CHILDREN_PER_PARENT);
353
+ for (const child of renderedChildren) {
354
+ if (child.kind === "text") {
355
+ lines.push(`${prefix}${indent(depth)}${escapeText(child.text)}`);
356
+ continue;
357
+ }
358
+ renderNode(child, depth, lines, prefix);
359
+ }
360
+ if (children.length > MAX_CHILDREN_PER_PARENT) {
361
+ const truncated = children.slice(MAX_CHILDREN_PER_PARENT);
362
+ lines.push(
363
+ `${prefix}${indent(depth)}${renderChildrenTruncationNotice(
364
+ truncated
365
+ )}`
366
+ );
367
+ }
368
+ }
369
+ function renderFoldedSingleChildChain(node, depth, lines, prefix) {
370
+ const chain = singleChildChain(node);
371
+ if (chain.length <= 1) return false;
372
+ const keptIndexes = chain.map((chainNode, index) => ({ chainNode, index })).filter(
373
+ ({ chainNode, index }) => shouldKeepFoldedChainNode(chainNode, index)
374
+ ).map(({ index }) => index);
375
+ if (keptIndexes.length === chain.length) return false;
376
+ renderFoldedChainNode(chain, keptIndexes, 0, depth, lines, prefix);
377
+ return true;
378
+ }
379
+ function shouldKeepFoldedChainNode(node, index) {
380
+ return index === 0 || node.role === "list";
381
+ }
382
+ function renderFoldedChainNode(chain, keptIndexes, keptIndexPosition, depth, lines, prefix) {
383
+ const currentIndex = keptIndexes[keptIndexPosition];
384
+ const current = chain[currentIndex];
385
+ lines.push(
386
+ `${prefix}${indent(depth)}${formatTag(current.role, current.attrs, true)}`
387
+ );
388
+ renderFoldedChainNodeOwnContent(current, depth + 1, lines, prefix);
389
+ const nextKeptIndex = keptIndexes[keptIndexPosition + 1];
390
+ if (nextKeptIndex !== void 0) {
391
+ if (nextKeptIndex > currentIndex + 1)
392
+ lines.push(`${prefix}${indent(depth + 1)}...`);
393
+ renderFoldedChainNode(
394
+ chain,
395
+ keptIndexes,
396
+ keptIndexPosition + 1,
397
+ depth + 1,
398
+ lines,
399
+ prefix
400
+ );
401
+ } else {
402
+ const terminal = chain[chain.length - 1];
403
+ if (chain.length - 1 > currentIndex)
404
+ lines.push(`${prefix}${indent(depth + 1)}...`);
405
+ renderChildren(terminal.children, depth + 1, lines, prefix);
406
+ }
407
+ lines.push(`${prefix}${indent(depth)}</${current.role}>`);
408
+ }
409
+ function renderFoldedChainNodeOwnContent(node, depth, lines, prefix) {
410
+ for (const child of node.children) {
411
+ if (child.kind === "text")
412
+ lines.push(`${prefix}${indent(depth)}${escapeText(child.text)}`);
413
+ }
414
+ }
415
+ function singleChildChain(node) {
416
+ const chain = [node];
417
+ let current = node;
418
+ while (isDeprioritizedSingleChildParent(current)) {
419
+ const child = singleElementChild(current);
420
+ if (!child) break;
421
+ if (ACTION_ROLES.has(child.role)) break;
422
+ chain.push(child);
423
+ current = child;
424
+ }
425
+ return chain;
426
+ }
427
+ function isDeprioritizedSingleChildParent(node) {
428
+ if (node.role === "document") return false;
429
+ if (ACTION_ROLES.has(node.role)) return false;
430
+ return singleElementChild(node) !== null;
431
+ }
432
+ function singleElementChild(node) {
433
+ let result = null;
434
+ for (const child of node.children) {
435
+ if (child.kind === "text") continue;
436
+ if (result) return null;
437
+ result = child;
438
+ }
439
+ return result;
440
+ }
441
+ function shouldRenderBareText(node) {
442
+ if (ACTION_ROLES.has(node.role)) return false;
443
+ if (attrValue(node, "ref")) return false;
444
+ if (PRESERVE_CHILDREN_BY_ROLE.has(node.role)) return false;
445
+ return true;
446
+ }
447
+ function nodeAttributes(node, suppressName) {
448
+ const attributes = [];
449
+ const usedNames = /* @__PURE__ */ new Set();
450
+ const push = (name, value) => {
451
+ if (value === void 0 || value === null || value === "") return;
452
+ if (value === false || value === "false") return;
453
+ const normalizedName = uniqueAttributeName(
454
+ sanitizeAttributeName(name),
455
+ usedNames
456
+ );
457
+ attributes.push([
458
+ normalizedName,
459
+ normalizeAttributeValue(normalizedName, value)
460
+ ]);
461
+ usedNames.add(normalizedName);
462
+ };
463
+ push("ref", node.ref);
464
+ if (node.name !== suppressName) push("name", node.name);
465
+ const hasStateValue = node.properties.value !== void 0 && node.properties.value !== null && node.properties.value !== "";
466
+ for (const name of RENDERED_STATE_PROPERTIES) {
467
+ const value = node.properties[name];
468
+ if (value === true) push(name, "true");
469
+ else push(name, value);
470
+ }
471
+ if (!hasStateValue) push("value", node.value);
472
+ push("href", node.attributes.href);
473
+ push("placeholder", node.attributes.placeholder);
474
+ return attributes;
475
+ }
476
+ function normalizeAttributeValue(name, value) {
477
+ const normalized = normalizeRawText(String(value));
478
+ return name === "href" ? truncate(normalized, MAX_HREF_CHARS) : normalized;
479
+ }
480
+ function singleTextChild(node) {
481
+ if (node.children.length !== 1) return null;
482
+ const child = node.children[0];
483
+ return child.kind === "text" && !child.block ? child.text : null;
484
+ }
485
+ function mergeAdjacentText(children) {
486
+ const result = [];
487
+ for (const child of children) {
488
+ const previous = result[result.length - 1];
489
+ if (child.kind === "text" && previous?.kind === "text" && !child.block && !previous.block) {
490
+ previous.text = normalizeRawText(`${previous.text} ${child.text}`);
491
+ } else {
492
+ result.push(child);
493
+ }
494
+ }
495
+ return result;
496
+ }
497
+ function normalizedText(children) {
498
+ return children.map(
499
+ (child) => child.kind === "text" ? child.text : normalizedText(child.children)
500
+ ).join(" ");
501
+ }
502
+ function previewForChildren(children, excludedText) {
503
+ const labels = [];
504
+ const seen = /* @__PURE__ */ new Set();
505
+ function visit(child, insideInteractive) {
506
+ if (child.kind === "text") {
507
+ if (!insideInteractive) pushLabel(child.text);
508
+ return;
509
+ }
510
+ const nextInsideInteractive = insideInteractive || ACTION_ROLES.has(child.role);
511
+ if (labels.join(" \xB7 ").length > MAX_SUMMARY_TEXT_CHARS) return;
512
+ for (const grandchild of child.children)
513
+ visit(grandchild, nextInsideInteractive);
514
+ }
515
+ function pushLabel(value) {
516
+ const normalized = normalizeRawText(value ?? "");
517
+ if (!normalized || normalized === "no visible text" || seen.has(normalized) || excludedText.has(normalized)) {
518
+ return;
519
+ }
520
+ seen.add(normalized);
521
+ labels.push(normalized);
522
+ }
523
+ for (const child of children) visit(child, false);
524
+ const preview = labels.join(" \xB7 ");
525
+ return preview ? truncate(preview, MAX_SUMMARY_TEXT_CHARS) : "";
526
+ }
527
+ function actionSummariesForChildren(children) {
528
+ const actions = [];
529
+ const labels = /* @__PURE__ */ new Set();
530
+ const seenRefs = /* @__PURE__ */ new Set();
531
+ let hasMore = false;
532
+ function visit(child) {
533
+ if (child.kind === "text") return;
534
+ const ref = attrValue(child, "ref");
535
+ if (ref && ACTION_ROLES.has(child.role) && !seenRefs.has(ref)) {
536
+ seenRefs.add(ref);
537
+ const label = actionLabel(child);
538
+ if (label) labels.add(label);
539
+ if (actions.length < MAX_ACTIONS_IN_SUMMARY) {
540
+ actions.push({ markup: renderActionSummary(child, ref), label });
541
+ } else {
542
+ hasMore = true;
543
+ }
544
+ }
545
+ for (const grandchild of child.children) visit(grandchild);
546
+ }
547
+ for (const child of children) visit(child);
548
+ return { actions, labels, hasMore };
549
+ }
550
+ function renderActionSummary(node, ref) {
551
+ const label = actionLabel(node);
552
+ const attrs = [["ref", ref]];
553
+ for (const [name, value] of node.attrs) {
554
+ if (name === "ref" || !ACTION_STATE_ATTRS.has(name)) continue;
555
+ attrs.push([name, normalizeText(value, MAX_ACTION_LABEL_CHARS)]);
556
+ }
557
+ if (!label || !TEXT_ACTION_ROLES.has(node.role)) {
558
+ const name = attrValue(node, "name");
559
+ if (name) attrs.push(["name", normalizeText(name, MAX_ACTION_LABEL_CHARS)]);
560
+ return formatTag(node.role, attrs, false);
561
+ }
562
+ return `${formatTag(node.role, attrs, true)}${escapeText(
563
+ normalizeText(label, MAX_ACTION_LABEL_CHARS)
564
+ )}</${node.role}>`;
565
+ }
566
+ function actionLabel(node) {
567
+ return firstNonEmpty(
568
+ singleTextChild(node),
569
+ attrValue(node, "name"),
570
+ attrValue(node, "value"),
571
+ attrValue(node, "placeholder")
572
+ );
573
+ }
574
+ function attrValue(node, name) {
575
+ return node.attrs.find(([attr]) => attr === name)?.[1] ?? null;
576
+ }
577
+ function attrFromAttrs(attrs, name) {
578
+ return attrs.find(([attr]) => attr === name)?.[1] ?? null;
579
+ }
580
+ function shouldSkipNode(node, parent) {
581
+ if (SKIP_ROLES.has(node.role)) return true;
582
+ if (node.role !== "StaticText") return false;
583
+ return Boolean(parent?.name && node.name && parent.name === node.name);
584
+ }
585
+ function isTextRole(role) {
586
+ return role === "StaticText" || role === "InlineTextBox";
587
+ }
588
+ function isRenderedNode(child) {
589
+ return child.kind === "node";
590
+ }
591
+ function hasVisibleTextOrInteractive(child) {
592
+ if (child.kind === "text") return normalizeRawText(child.text) !== "";
593
+ if (ACTION_ROLES.has(child.role)) return true;
594
+ return child.children.some(hasVisibleTextOrInteractive);
595
+ }
596
+ function tagNameForRole(role) {
597
+ const normalized = normalizeRole(role).replace(/[^a-zA-Z0-9_.:-]/g, "-");
598
+ return /^[a-zA-Z_:]/.test(normalized) ? normalized : "node";
599
+ }
600
+ function normalizeRole(role) {
601
+ if (role === "RootWebArea") return "document";
602
+ if (role === "textField") return "textbox";
603
+ return role || "node";
604
+ }
605
+ function primitiveToString(value) {
606
+ return value === null ? null : String(value);
607
+ }
608
+ function nodeTextValue(node) {
609
+ if (!node) return null;
610
+ const value = primitiveToString(node.properties.value ?? node.value);
611
+ return value ? normalizeRawText(value) : null;
612
+ }
613
+ function firstNonEmpty(...values) {
614
+ for (const value of values) {
615
+ const normalized = normalizeRawText(value ?? "");
616
+ if (normalized) return truncate(normalized, MAX_LABEL_CHARS);
617
+ }
618
+ return null;
619
+ }
620
+ function normalizeText(value, maxChars) {
621
+ return truncate(value.replace(/\s+/g, " ").trim(), maxChars);
622
+ }
623
+ function normalizeRawText(value) {
624
+ return value.replace(/\s+/g, " ").trim();
625
+ }
626
+ function truncate(value, maxChars) {
627
+ return value.length > maxChars ? `${value.slice(0, maxChars - 1)}\u2026` : value;
628
+ }
629
+ function uniqueAttributeName(name, usedNames) {
630
+ if (!usedNames.has(name)) return name;
631
+ let index = 2;
632
+ while (usedNames.has(`${name}-${index}`)) index += 1;
633
+ return `${name}-${index}`;
634
+ }
635
+ function sanitizeAttributeName(name) {
636
+ const sanitized = name.replace(/[^a-zA-Z0-9_.:-]/g, "-");
637
+ return /^[a-zA-Z_:]/.test(sanitized) ? sanitized : `attr-${sanitized}`;
638
+ }
639
+ function escapeText(value) {
640
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
641
+ }
642
+ function escapeAttribute(value) {
643
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
644
+ }
645
+ export {
646
+ renderChildrenTruncationNotice,
647
+ renderFrame,
648
+ renderNode,
649
+ renderSnapshot,
650
+ renderSnapshotFrames
651
+ };
@@ -0,0 +1,2 @@
1
+
2
+ export { }