lsd-pi 1.3.2 → 1.3.6

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 (169) hide show
  1. package/dist/resources/extensions/browser-tools/tools/codegen.js +5 -5
  2. package/dist/resources/extensions/browser-tools/tools/navigation.js +107 -178
  3. package/dist/resources/extensions/browser-tools/tools/network-mock.js +112 -167
  4. package/dist/resources/extensions/browser-tools/tools/pages.js +182 -234
  5. package/dist/resources/extensions/browser-tools/tools/refs.js +202 -461
  6. package/dist/resources/extensions/browser-tools/tools/session.js +176 -323
  7. package/dist/resources/extensions/browser-tools/tools/state-persistence.js +91 -154
  8. package/dist/resources/extensions/browser-tools/utils.js +1 -1
  9. package/dist/resources/extensions/slash-commands/extension-manifest.json +2 -2
  10. package/dist/resources/extensions/slash-commands/fast.js +73 -0
  11. package/dist/resources/extensions/slash-commands/index.js +2 -0
  12. package/dist/resources/extensions/slash-commands/plan.js +37 -12
  13. package/dist/resources/extensions/subagent/background-job-manager.js +13 -0
  14. package/dist/resources/extensions/subagent/in-process-runner.js +387 -0
  15. package/dist/resources/extensions/subagent/index.js +278 -626
  16. package/dist/resources/extensions/subagent/legacy-runner.js +503 -0
  17. package/dist/resources/extensions/voice/index.js +96 -36
  18. package/dist/resources/extensions/voice/push-to-talk.js +26 -0
  19. package/package.json +1 -1
  20. package/packages/pi-agent-core/dist/agent.d.ts +19 -0
  21. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  22. package/packages/pi-agent-core/dist/agent.js +16 -0
  23. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  24. package/packages/pi-agent-core/src/agent.ts +32 -2
  25. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +34 -1
  26. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  27. package/packages/pi-ai/dist/providers/openai-codex-responses.js +32 -4
  28. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  29. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +127 -16
  30. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -1
  31. package/packages/pi-ai/dist/providers/openai-responses.d.ts +8 -1
  32. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  33. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts +2 -0
  34. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts.map +1 -0
  35. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js +67 -0
  36. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js.map +1 -0
  37. package/packages/pi-ai/dist/providers/openai-responses.js +21 -3
  38. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  39. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  40. package/packages/pi-ai/dist/providers/simple-options.js +2 -0
  41. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  42. package/packages/pi-ai/dist/types.d.ts +5 -0
  43. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  44. package/packages/pi-ai/dist/types.js.map +1 -1
  45. package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +143 -20
  46. package/packages/pi-ai/src/providers/openai-codex-responses.ts +47 -4
  47. package/packages/pi-ai/src/providers/openai-responses.fast-mode.test.ts +73 -0
  48. package/packages/pi-ai/src/providers/openai-responses.ts +26 -3
  49. package/packages/pi-ai/src/providers/simple-options.ts +2 -0
  50. package/packages/pi-ai/src/types.ts +5 -0
  51. package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
  52. package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
  53. package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
  54. package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
  55. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  56. package/packages/pi-coding-agent/dist/core/sdk.js +4 -2
  57. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  58. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
  59. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  60. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts +2 -0
  61. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts.map +1 -0
  62. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js +35 -0
  63. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js.map +1 -0
  64. package/packages/pi-coding-agent/dist/core/settings-manager.js +12 -0
  65. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  66. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  67. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  68. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  69. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  70. package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -1
  71. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  72. package/packages/pi-coding-agent/dist/core/tool-priority.d.ts +4 -0
  73. package/packages/pi-coding-agent/dist/core/tool-priority.d.ts.map +1 -0
  74. package/packages/pi-coding-agent/dist/core/tool-priority.js +18 -0
  75. package/packages/pi-coding-agent/dist/core/tool-priority.js.map +1 -0
  76. package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts +2 -0
  77. package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts.map +1 -0
  78. package/packages/pi-coding-agent/dist/core/tool-priority.test.js +27 -0
  79. package/packages/pi-coding-agent/dist/core/tool-priority.test.js.map +1 -0
  80. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts +2 -0
  81. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts.map +1 -0
  82. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js +26 -0
  83. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js.map +1 -0
  84. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts +45 -0
  85. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts.map +1 -0
  86. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js +314 -0
  87. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js.map +1 -0
  88. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts +2 -0
  89. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts.map +1 -0
  90. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js +122 -0
  91. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js.map +1 -0
  92. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts +2 -0
  93. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  94. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +7 -0
  95. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  96. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +4 -0
  97. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  98. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +26 -2
  99. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  100. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +6 -0
  101. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  102. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +18 -4
  103. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  104. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts +13 -0
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts.map +1 -0
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js +49 -0
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js.map +1 -0
  108. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts +2 -0
  109. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts.map +1 -0
  110. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js +197 -0
  111. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js.map +1 -0
  112. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  113. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +97 -0
  114. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  115. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +7 -0
  117. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +3 -0
  119. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  122. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +35 -0
  124. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  126. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +41 -0
  127. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  128. package/packages/pi-coding-agent/package.json +1 -1
  129. package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
  130. package/packages/pi-coding-agent/src/core/sdk.ts +4 -2
  131. package/packages/pi-coding-agent/src/core/settings-manager.fast-mode.test.ts +46 -0
  132. package/packages/pi-coding-agent/src/core/settings-manager.ts +18 -0
  133. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  134. package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -1
  135. package/packages/pi-coding-agent/src/core/tool-priority.test.ts +30 -0
  136. package/packages/pi-coding-agent/src/core/tool-priority.ts +17 -0
  137. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-summary-line.test.ts +31 -0
  138. package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.test.ts +172 -0
  139. package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.ts +402 -0
  140. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +8 -0
  141. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +32 -2
  142. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1154 -1136
  143. package/packages/pi-coding-agent/src/modes/interactive/components/tool-summary-line.ts +64 -0
  144. package/packages/pi-coding-agent/src/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.ts +228 -0
  145. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +494 -398
  146. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +7 -0
  147. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +3 -0
  148. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +38 -0
  149. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +60 -1
  150. package/pkg/package.json +1 -1
  151. package/src/resources/extensions/browser-tools/tools/codegen.ts +5 -5
  152. package/src/resources/extensions/browser-tools/tools/navigation.ts +118 -196
  153. package/src/resources/extensions/browser-tools/tools/network-mock.ts +114 -205
  154. package/src/resources/extensions/browser-tools/tools/pages.ts +183 -237
  155. package/src/resources/extensions/browser-tools/tools/refs.ts +193 -507
  156. package/src/resources/extensions/browser-tools/tools/session.ts +182 -321
  157. package/src/resources/extensions/browser-tools/tools/state-persistence.ts +94 -172
  158. package/src/resources/extensions/browser-tools/utils.ts +1 -1
  159. package/src/resources/extensions/slash-commands/extension-manifest.json +2 -2
  160. package/src/resources/extensions/slash-commands/fast.ts +89 -0
  161. package/src/resources/extensions/slash-commands/index.ts +2 -0
  162. package/src/resources/extensions/slash-commands/plan.ts +42 -12
  163. package/src/resources/extensions/subagent/background-job-manager.ts +28 -0
  164. package/src/resources/extensions/subagent/in-process-runner.ts +534 -0
  165. package/src/resources/extensions/subagent/index.ts +489 -799
  166. package/src/resources/extensions/subagent/legacy-runner.ts +607 -0
  167. package/src/resources/extensions/voice/index.ts +308 -238
  168. package/src/resources/extensions/voice/push-to-talk.ts +42 -0
  169. package/src/resources/extensions/voice/tests/push-to-talk.test.ts +109 -0
@@ -2,484 +2,225 @@ import { Type } from "@sinclair/typebox";
2
2
  import { getSnapshotModeConfig, SNAPSHOT_MODES, } from "../core.js";
3
3
  import { getActiveFrame, getCurrentRefMap, setCurrentRefMap, getRefVersion, setRefVersion, getRefMetadata, setRefMetadata, } from "../state.js";
4
4
  export function registerRefTools(pi, deps) {
5
- // -------------------------------------------------------------------------
6
- // browser_snapshot_refs
7
- // -------------------------------------------------------------------------
8
5
  pi.registerTool({
9
- name: "browser_snapshot_refs",
10
- label: "Browser Snapshot Refs",
11
- description: "Capture a compact inventory of interactive elements and assign deterministic versioned refs (@vN:e1, @vN:e2, ...). Use these refs with browser_click_ref, browser_fill_ref, and browser_hover_ref.",
6
+ name: "browser_ref",
7
+ label: "Browser Ref",
8
+ description: "Manage deterministic element references: snapshot interactive elements, inspect, click, fill, or hover by ref. " +
9
+ "Use versioned refs (e.g. @v3:e2) from snapshot for reliable interaction.",
12
10
  parameters: Type.Object({
13
- selector: Type.Optional(Type.String({
14
- description: "Optional CSS selector scope for the snapshot (e.g. 'main', 'form', '#modal').",
15
- })),
16
- interactiveOnly: Type.Optional(Type.Boolean({
17
- description: "Include only interactive elements (default: true).",
18
- })),
19
- limit: Type.Optional(Type.Number({
20
- description: "Maximum number of elements to include (default: 40).",
21
- })),
22
- mode: Type.Optional(Type.String({
23
- description: "Semantic snapshot mode that pre-filters elements by category. When set, overrides interactiveOnly. Modes: interactive, form, dialog, navigation, errors, headings, visible_only.",
24
- })),
11
+ action: Type.Union([
12
+ Type.Literal("snapshot"),
13
+ Type.Literal("get"),
14
+ Type.Literal("click"),
15
+ Type.Literal("fill"),
16
+ Type.Literal("hover"),
17
+ ], { description: "'snapshot' — capture refs, 'get' — inspect ref, 'click'/'fill'/'hover' — interact by ref" }),
18
+ ref: Type.Optional(Type.String({ description: "Versioned element ref, e.g. '@v3:e1'. Required for get/click/fill/hover." })),
19
+ text: Type.Optional(Type.String({ description: "Text to fill (fill action only)." })),
20
+ clearFirst: Type.Optional(Type.Boolean({ description: "Clear existing value first (fill action, default: false)." })),
21
+ submit: Type.Optional(Type.Boolean({ description: "Press Enter after filling (fill action, default: false)." })),
22
+ slowly: Type.Optional(Type.Boolean({ description: "Type character-by-character (fill action, default: false)." })),
23
+ selector: Type.Optional(Type.String({ description: "CSS selector scope (snapshot action)." })),
24
+ interactiveOnly: Type.Optional(Type.Boolean({ description: "Include only interactive elements (snapshot action, default: true)." })),
25
+ limit: Type.Optional(Type.Number({ description: "Max elements in snapshot (default: 40)." })),
26
+ mode: Type.Optional(Type.String({ description: "Snapshot mode: interactive, form, dialog, navigation, errors, headings, visible_only." })),
25
27
  }),
26
28
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
27
29
  try {
28
- const { page: p } = await deps.ensureBrowser();
29
- const target = deps.getActiveTarget();
30
- const mode = params.mode;
31
- if (mode !== undefined) {
32
- const modeConfig = getSnapshotModeConfig(mode);
33
- if (!modeConfig) {
34
- const validModes = Object.keys(SNAPSHOT_MODES).join(", ");
35
- return {
36
- content: [{ type: "text", text: `Unknown snapshot mode: "${mode}". Valid modes: ${validModes}` }],
37
- details: { error: `Unknown mode: ${mode}`, validModes: Object.keys(SNAPSHOT_MODES) },
38
- isError: true,
39
- };
40
- }
30
+ const action = params.action;
31
+ if (action === "snapshot") {
32
+ return await snapshotAction(params);
41
33
  }
42
- const interactiveOnly = params.interactiveOnly !== false;
43
- const limit = Math.max(1, Math.min(200, Math.floor(params.limit ?? 40)));
44
- const rawNodes = await deps.buildRefSnapshot(target, {
45
- selector: params.selector,
46
- interactiveOnly,
47
- limit,
48
- mode,
49
- });
50
- const newVersion = getRefVersion() + 1;
51
- setRefVersion(newVersion);
52
- const nextMap = {};
53
- for (let i = 0; i < rawNodes.length; i += 1) {
54
- const ref = `e${i + 1}`;
55
- nextMap[ref] = { ref, ...rawNodes[i] };
34
+ else if (action === "get") {
35
+ return await getAction(params);
56
36
  }
57
- setCurrentRefMap(nextMap);
58
- const activeFrame = getActiveFrame();
59
- const frameCtx = activeFrame ? (activeFrame.name() || activeFrame.url()) : undefined;
60
- setRefMetadata({
61
- url: p.url(),
62
- timestamp: Date.now(),
63
- selectorScope: params.selector,
64
- interactiveOnly,
65
- limit,
66
- version: newVersion,
67
- frameContext: frameCtx,
68
- mode,
69
- });
70
- if (rawNodes.length === 0) {
71
- return {
72
- content: [{
73
- type: "text",
74
- text: "No elements found for ref snapshot (try interactiveOnly=false or a wider selector scope).",
75
- }],
76
- details: {
77
- count: 0,
78
- version: newVersion,
79
- metadata: getRefMetadata(),
80
- refs: {},
81
- },
82
- };
37
+ else if (action === "click") {
38
+ return await clickAction(params);
83
39
  }
84
- const versionedRefs = {};
85
- const lines = Object.values(nextMap).map((node) => {
86
- const versionedRef = deps.formatVersionedRef(newVersion, node.ref);
87
- versionedRefs[versionedRef] = node;
88
- const parts = [versionedRef, node.role || node.tag];
89
- if (node.name)
90
- parts.push(`"${node.name}"`);
91
- if (node.href)
92
- parts.push(`href="${node.href.slice(0, 80)}"`);
93
- if (!node.isVisible)
94
- parts.push("(hidden)");
95
- if (!node.isEnabled)
96
- parts.push("(disabled)");
97
- return parts.join(" ");
98
- });
99
- const modeLabel = mode ? `Mode: ${mode}\n` : "";
100
- return {
101
- content: [{
102
- type: "text",
103
- text: `Ref snapshot v${newVersion} (${rawNodes.length} element(s))\n` +
104
- `URL: ${p.url()}\n` +
105
- `Scope: ${params.selector ?? "body"}\n` +
106
- modeLabel +
107
- `Use versioned refs exactly as shown (e.g. @v${newVersion}:e1).\n\n` +
108
- lines.join("\n"),
109
- }],
110
- details: {
111
- count: rawNodes.length,
112
- version: newVersion,
113
- metadata: getRefMetadata(),
114
- refs: nextMap,
115
- versionedRefs,
116
- },
117
- };
118
- }
119
- catch (err) {
120
- return {
121
- content: [{ type: "text", text: `Snapshot refs failed: ${err.message}` }],
122
- details: { error: err.message },
123
- isError: true,
124
- };
125
- }
126
- },
127
- });
128
- // -------------------------------------------------------------------------
129
- // browser_get_ref
130
- // -------------------------------------------------------------------------
131
- pi.registerTool({
132
- name: "browser_get_ref",
133
- label: "Browser Get Ref",
134
- description: "Inspect stored metadata for one deterministic element ref (prefer versioned format, e.g. @v3:e1).",
135
- parameters: Type.Object({
136
- ref: Type.String({ description: "Reference id, preferably versioned (e.g. '@v3:e1')." }),
137
- }),
138
- async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
139
- const parsedRef = deps.parseRef(params.ref);
140
- const refMetadata = getRefMetadata();
141
- const refVersion = getRefVersion();
142
- if (parsedRef.version !== null && refMetadata && parsedRef.version !== refMetadata.version) {
143
- return {
144
- content: [{ type: "text", text: deps.staleRefGuidance(parsedRef.display, `snapshot version mismatch (have v${refMetadata.version})`) }],
145
- details: { error: "ref_stale", ref: parsedRef.display, expectedVersion: refMetadata.version, receivedVersion: parsedRef.version },
146
- isError: true,
147
- };
148
- }
149
- const currentRefMap = getCurrentRefMap();
150
- const node = currentRefMap[parsedRef.key];
151
- if (!node) {
152
- return {
153
- content: [{ type: "text", text: deps.staleRefGuidance(parsedRef.display, "ref not found") }],
154
- details: { error: "ref_not_found", ref: parsedRef.display, metadata: refMetadata },
155
- isError: true,
156
- };
157
- }
158
- const versionedRef = deps.formatVersionedRef(refMetadata?.version ?? refVersion, node.ref);
159
- return {
160
- content: [{
161
- type: "text",
162
- text: `${versionedRef}: ${node.role || node.tag}${node.name ? ` "${node.name}"` : ""}\nVisible: ${node.isVisible}\nEnabled: ${node.isEnabled}\nPath: ${node.xpathOrPath}`,
163
- }],
164
- details: { ref: versionedRef, node, metadata: refMetadata },
165
- };
166
- },
167
- });
168
- // -------------------------------------------------------------------------
169
- // browser_click_ref
170
- // -------------------------------------------------------------------------
171
- pi.registerTool({
172
- name: "browser_click_ref",
173
- label: "Browser Click Ref",
174
- description: "Click a previously snapshotted element by deterministic versioned ref (e.g. @v3:e2).",
175
- parameters: Type.Object({
176
- ref: Type.String({ description: "Reference id in versioned format, e.g. '@v3:e2'." }),
177
- }),
178
- async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
179
- const parsedRef = deps.parseRef(params.ref);
180
- const requestedRef = parsedRef.display;
181
- try {
182
- const { page: p } = await deps.ensureBrowser();
183
- const target = deps.getActiveTarget();
184
- const refMetadata = getRefMetadata();
185
- const refVersion = getRefVersion();
186
- if (parsedRef.version === null) {
187
- return {
188
- content: [{ type: "text", text: `Unversioned ref ${requestedRef} is ambiguous. Use a versioned ref (e.g. @v${refMetadata?.version ?? refVersion}:e1) from browser_snapshot_refs.` }],
189
- details: { error: "ref_unversioned", ref: requestedRef, metadata: refMetadata },
190
- isError: true,
191
- };
192
- }
193
- if (refMetadata && parsedRef.version !== refMetadata.version) {
194
- return {
195
- content: [{ type: "text", text: deps.staleRefGuidance(requestedRef, `snapshot version mismatch (have v${refMetadata.version})`) }],
196
- details: { error: "ref_stale", ref: requestedRef, expectedVersion: refMetadata.version, receivedVersion: parsedRef.version },
197
- isError: true,
198
- };
199
- }
200
- const currentRefMap = getCurrentRefMap();
201
- const ref = parsedRef.key;
202
- const node = currentRefMap[ref];
203
- if (!node) {
204
- return {
205
- content: [{ type: "text", text: deps.staleRefGuidance(requestedRef, "ref not found") }],
206
- details: { error: "ref_not_found", ref: requestedRef, metadata: refMetadata },
207
- isError: true,
208
- };
40
+ else if (action === "fill") {
41
+ return await fillAction(params);
209
42
  }
210
- if (refMetadata?.url && refMetadata.url !== p.url()) {
211
- return {
212
- content: [{ type: "text", text: deps.staleRefGuidance(requestedRef, "URL changed since snapshot") }],
213
- details: { error: "ref_stale", ref: requestedRef, snapshotUrl: refMetadata.url, currentUrl: p.url() },
214
- isError: true,
215
- };
216
- }
217
- const resolved = await deps.resolveRefTarget(target, node);
218
- if (!resolved.ok) {
219
- const reason = resolved.reason;
220
- return {
221
- content: [{ type: "text", text: deps.staleRefGuidance(requestedRef, reason) }],
222
- details: { error: "ref_stale", ref: requestedRef, reason },
223
- isError: true,
224
- };
225
- }
226
- const beforeState = await deps.captureCompactPageState(p, { includeBodyText: true, target });
227
- const beforeUrl = beforeState.url;
228
- const beforeHash = deps.getUrlHash(beforeUrl);
229
- const beforeTargetState = await deps.captureClickTargetState(target, resolved.selector);
230
- await target.locator(resolved.selector).first().click({ timeout: 8000 });
231
- const settle = await deps.settleAfterActionAdaptive(p);
232
- const afterState = await deps.captureCompactPageState(p, { includeBodyText: true, target });
233
- const afterUrl = afterState.url;
234
- const afterHash = deps.getUrlHash(afterUrl);
235
- const afterTargetState = await deps.captureClickTargetState(target, resolved.selector);
236
- const targetStateChanged = beforeTargetState.exists !== afterTargetState.exists ||
237
- beforeTargetState.ariaExpanded !== afterTargetState.ariaExpanded ||
238
- beforeTargetState.ariaPressed !== afterTargetState.ariaPressed ||
239
- beforeTargetState.ariaSelected !== afterTargetState.ariaSelected ||
240
- beforeTargetState.open !== afterTargetState.open;
241
- const verification = deps.verificationFromChecks([
242
- { name: "url_changed", passed: afterUrl !== beforeUrl, value: afterUrl, expected: `!= ${beforeUrl}` },
243
- { name: "hash_changed", passed: afterHash !== beforeHash, value: afterHash, expected: `!= ${beforeHash}` },
244
- { name: "target_state_changed", passed: targetStateChanged, value: afterTargetState, expected: beforeTargetState },
245
- { name: "dialog_open", passed: afterState.dialog.count > beforeState.dialog.count, value: afterState.dialog.count, expected: `> ${beforeState.dialog.count}` },
246
- ], "Ref may now point to an inert element. Refresh refs with browser_snapshot_refs and retry.");
247
- const summary = deps.formatCompactStateSummary(afterState);
248
- const jsErrors = deps.getRecentErrors(p.url());
249
- const versionedRef = deps.formatVersionedRef(refMetadata?.version ?? refVersion, node.ref);
250
- return {
251
- content: [{
252
- type: "text",
253
- text: `Clicked ${versionedRef} (${node.role || node.tag}${node.name ? ` "${node.name}"` : ""})\n${deps.verificationLine(verification)}${jsErrors}\n\nPage summary:\n${summary}`,
254
- }],
255
- details: { ref: versionedRef, selector: resolved.selector, url: p.url(), ...settle, ...verification },
256
- };
257
- }
258
- catch (err) {
259
- const errorShot = await deps.captureErrorScreenshot(deps.getActivePageOrNull());
260
- const reason = deps.firstErrorLine(err);
261
- const content = [
262
- { type: "text", text: deps.staleRefGuidance(requestedRef, `action failed: ${reason}`) },
263
- { type: "text", text: `Click ref failed: ${err.message}` },
264
- ];
265
- if (errorShot) {
266
- content.push({ type: "image", data: errorShot.data, mimeType: errorShot.mimeType });
267
- }
268
- return {
269
- content,
270
- details: { error: err.message, ref: requestedRef, hint: "Run browser_snapshot_refs to refresh refs." },
271
- isError: true,
272
- };
273
- }
274
- },
275
- });
276
- // -------------------------------------------------------------------------
277
- // browser_hover_ref
278
- // -------------------------------------------------------------------------
279
- pi.registerTool({
280
- name: "browser_hover_ref",
281
- label: "Browser Hover Ref",
282
- description: "Hover a previously snapshotted element by deterministic versioned ref (e.g. @v3:e4).",
283
- parameters: Type.Object({
284
- ref: Type.String({ description: "Reference id in versioned format, e.g. '@v3:e4'." }),
285
- }),
286
- async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
287
- const parsedRef = deps.parseRef(params.ref);
288
- const requestedRef = parsedRef.display;
289
- try {
290
- const { page: p } = await deps.ensureBrowser();
291
- const target = deps.getActiveTarget();
292
- const refMetadata = getRefMetadata();
293
- const refVersion = getRefVersion();
294
- if (parsedRef.version === null) {
295
- return {
296
- content: [{ type: "text", text: `Unversioned ref ${requestedRef} is ambiguous. Use a versioned ref (e.g. @v${refMetadata?.version ?? refVersion}:e1) from browser_snapshot_refs.` }],
297
- details: { error: "ref_unversioned", ref: requestedRef, metadata: refMetadata },
298
- isError: true,
299
- };
300
- }
301
- if (refMetadata && parsedRef.version !== refMetadata.version) {
302
- return {
303
- content: [{ type: "text", text: deps.staleRefGuidance(requestedRef, `snapshot version mismatch (have v${refMetadata.version})`) }],
304
- details: { error: "ref_stale", ref: requestedRef, expectedVersion: refMetadata.version, receivedVersion: parsedRef.version },
305
- isError: true,
306
- };
307
- }
308
- const currentRefMap = getCurrentRefMap();
309
- const ref = parsedRef.key;
310
- const node = currentRefMap[ref];
311
- if (!node) {
312
- return {
313
- content: [{ type: "text", text: deps.staleRefGuidance(requestedRef, "ref not found") }],
314
- details: { error: "ref_not_found", ref: requestedRef, metadata: refMetadata },
315
- isError: true,
316
- };
317
- }
318
- if (refMetadata?.url && refMetadata.url !== p.url()) {
319
- return {
320
- content: [{ type: "text", text: deps.staleRefGuidance(requestedRef, "URL changed since snapshot") }],
321
- details: { error: "ref_stale", ref: requestedRef, snapshotUrl: refMetadata.url, currentUrl: p.url() },
322
- isError: true,
323
- };
324
- }
325
- const resolved = await deps.resolveRefTarget(target, node);
326
- if (!resolved.ok) {
327
- const reason = resolved.reason;
328
- return {
329
- content: [{ type: "text", text: deps.staleRefGuidance(requestedRef, reason) }],
330
- details: { error: "ref_stale", ref: requestedRef, reason },
331
- isError: true,
332
- };
43
+ else {
44
+ return await hoverAction(params);
333
45
  }
334
- await target.locator(resolved.selector).first().hover({ timeout: 8000 });
335
- const settle = await deps.settleAfterActionAdaptive(p);
336
- const afterState = await deps.captureCompactPageState(p, { includeBodyText: false, target });
337
- const summary = deps.formatCompactStateSummary(afterState);
338
- const jsErrors = deps.getRecentErrors(p.url());
339
- const versionedRef = deps.formatVersionedRef(refMetadata?.version ?? refVersion, node.ref);
340
- return {
341
- content: [{
342
- type: "text",
343
- text: `Hovered ${versionedRef} (${node.role || node.tag}${node.name ? ` "${node.name}"` : ""})${jsErrors}\n\nPage summary:\n${summary}`,
344
- }],
345
- details: { ref: versionedRef, selector: resolved.selector, url: p.url(), ...settle },
346
- };
347
46
  }
348
47
  catch (err) {
349
48
  const errorShot = await deps.captureErrorScreenshot(deps.getActivePageOrNull());
350
- const reason = deps.firstErrorLine(err);
351
- const content = [
352
- { type: "text", text: deps.staleRefGuidance(requestedRef, `action failed: ${reason}`) },
353
- { type: "text", text: `Hover ref failed: ${err.message}` },
354
- ];
355
- if (errorShot) {
49
+ const content = [{ type: "text", text: `Ref '${params.action}' failed: ${err.message}` }];
50
+ if (errorShot)
356
51
  content.push({ type: "image", data: errorShot.data, mimeType: errorShot.mimeType });
357
- }
358
- return {
359
- content,
360
- details: { error: err.message, ref: requestedRef, hint: "Run browser_snapshot_refs to refresh refs." },
361
- isError: true,
362
- };
52
+ return { content, details: { error: err.message, ref: params.ref }, isError: true };
363
53
  }
364
54
  },
365
55
  });
366
- // -------------------------------------------------------------------------
367
- // browser_fill_ref
368
- // -------------------------------------------------------------------------
369
- pi.registerTool({
370
- name: "browser_fill_ref",
371
- label: "Browser Fill Ref",
372
- description: "Fill/type text into an input-like element by deterministic versioned ref (e.g. @v3:e1).",
373
- parameters: Type.Object({
374
- ref: Type.String({ description: "Reference id in versioned format, e.g. '@v3:e1'." }),
375
- text: Type.String({ description: "Text to enter." }),
376
- clearFirst: Type.Optional(Type.Boolean({ description: "Clear existing value first (default: false)." })),
377
- submit: Type.Optional(Type.Boolean({ description: "Press Enter after typing (default: false)." })),
378
- slowly: Type.Optional(Type.Boolean({ description: "Type character-by-character (default: false)." })),
379
- }),
380
- async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
381
- const parsedRef = deps.parseRef(params.ref);
382
- const requestedRef = parsedRef.display;
383
- try {
384
- const { page: p } = await deps.ensureBrowser();
385
- const target = deps.getActiveTarget();
386
- const refMetadata = getRefMetadata();
387
- const refVersion = getRefVersion();
388
- if (parsedRef.version === null) {
389
- return {
390
- content: [{ type: "text", text: `Unversioned ref ${requestedRef} is ambiguous. Use a versioned ref (e.g. @v${refMetadata?.version ?? refVersion}:e1) from browser_snapshot_refs.` }],
391
- details: { error: "ref_unversioned", ref: requestedRef, metadata: refMetadata },
392
- isError: true,
393
- };
394
- }
395
- if (refMetadata && parsedRef.version !== refMetadata.version) {
396
- return {
397
- content: [{ type: "text", text: deps.staleRefGuidance(requestedRef, `snapshot version mismatch (have v${refMetadata.version})`) }],
398
- details: { error: "ref_stale", ref: requestedRef, expectedVersion: refMetadata.version, receivedVersion: parsedRef.version },
399
- isError: true,
400
- };
401
- }
402
- const currentRefMap = getCurrentRefMap();
403
- const ref = parsedRef.key;
404
- const node = currentRefMap[ref];
405
- if (!node) {
406
- return {
407
- content: [{ type: "text", text: deps.staleRefGuidance(requestedRef, "ref not found") }],
408
- details: { error: "ref_not_found", ref: requestedRef, metadata: refMetadata },
409
- isError: true,
410
- };
411
- }
412
- if (refMetadata?.url && refMetadata.url !== p.url()) {
413
- return {
414
- content: [{ type: "text", text: deps.staleRefGuidance(requestedRef, "URL changed since snapshot") }],
415
- details: { error: "ref_stale", ref: requestedRef, snapshotUrl: refMetadata.url, currentUrl: p.url() },
416
- isError: true,
417
- };
418
- }
419
- const resolved = await deps.resolveRefTarget(target, node);
420
- if (!resolved.ok) {
421
- const reason = resolved.reason;
422
- return {
423
- content: [{ type: "text", text: deps.staleRefGuidance(requestedRef, reason) }],
424
- details: { error: "ref_stale", ref: requestedRef, reason },
425
- isError: true,
426
- };
427
- }
428
- const locator = target.locator(resolved.selector).first();
429
- const beforeUrl = p.url();
430
- if (params.slowly) {
431
- await locator.click({ timeout: 8000 });
432
- if (params.clearFirst) {
433
- await p.keyboard.press("Control+A");
434
- await p.keyboard.press("Delete");
435
- }
436
- await p.keyboard.type(params.text);
437
- }
438
- else {
439
- if (params.clearFirst) {
440
- await locator.fill("");
441
- }
442
- await locator.fill(params.text, { timeout: 8000 });
443
- }
444
- if (params.submit) {
445
- await p.keyboard.press("Enter");
446
- }
447
- const settle = await deps.settleAfterActionAdaptive(p);
448
- const filledValue = await deps.readInputLikeValue(target, resolved.selector);
449
- const afterUrl = p.url();
450
- const verification = deps.verificationFromChecks([
451
- { name: "value_equals_expected", passed: filledValue === params.text, value: filledValue, expected: params.text },
452
- { name: "value_contains_expected", passed: typeof filledValue === "string" && filledValue.includes(params.text), value: filledValue, expected: params.text },
453
- { name: "url_changed_after_submit", passed: !!params.submit && afterUrl !== beforeUrl, value: afterUrl, expected: `!= ${beforeUrl}` },
454
- ], "Try refreshing refs and confirm this ref still targets an input-like element.");
455
- const afterState = await deps.captureCompactPageState(p, { includeBodyText: true, target });
456
- const summary = deps.formatCompactStateSummary(afterState);
457
- const jsErrors = deps.getRecentErrors(p.url());
458
- const versionedRef = deps.formatVersionedRef(refMetadata?.version ?? refVersion, node.ref);
459
- return {
460
- content: [{
461
- type: "text",
462
- text: `Filled ${versionedRef} (${node.role || node.tag}${node.name ? ` "${node.name}"` : ""}) with "${params.text}"\n${deps.verificationLine(verification)}${jsErrors}\n\nPage summary:\n${summary}`,
463
- }],
464
- details: { ref: versionedRef, selector: resolved.selector, url: p.url(), filledValue, ...settle, ...verification },
465
- };
56
+ // ── validate ref helper ──
57
+ function validateRef(ref) {
58
+ if (!ref)
59
+ return { error: "Ref 'ref' parameter is required.", details: { error: "missing_ref" } };
60
+ const parsedRef = deps.parseRef(ref);
61
+ const refMetadata = getRefMetadata();
62
+ const refVersion = getRefVersion();
63
+ if (parsedRef.version === null) {
64
+ return { error: `Unversioned ref ${parsedRef.display} is ambiguous. Use a versioned ref (e.g. @v${refMetadata?.version ?? refVersion}:e1).`, details: { error: "ref_unversioned", ref: parsedRef.display } };
65
+ }
66
+ if (refMetadata && parsedRef.version !== refMetadata.version) {
67
+ return { error: deps.staleRefGuidance(parsedRef.display, `version mismatch (have v${refMetadata.version})`), details: { error: "ref_stale", ref: parsedRef.display } };
68
+ }
69
+ const currentRefMap = getCurrentRefMap();
70
+ const node = currentRefMap[parsedRef.key];
71
+ if (!node) {
72
+ return { error: deps.staleRefGuidance(parsedRef.display, "ref not found"), details: { error: "ref_not_found", ref: parsedRef.display } };
73
+ }
74
+ const versionedRef = deps.formatVersionedRef(refMetadata?.version ?? refVersion, node.ref);
75
+ return { parsedRef, node, versionedRef };
76
+ }
77
+ // ── snapshot ──
78
+ async function snapshotAction(params) {
79
+ const { page: p } = await deps.ensureBrowser();
80
+ const target = deps.getActiveTarget();
81
+ const mode = params.mode;
82
+ if (mode !== undefined) {
83
+ const modeConfig = getSnapshotModeConfig(mode);
84
+ if (!modeConfig) {
85
+ return { content: [{ type: "text", text: `Unknown snapshot mode: "${mode}". Valid: ${Object.keys(SNAPSHOT_MODES).join(", ")}` }], details: { error: "unknown_mode" }, isError: true };
466
86
  }
467
- catch (err) {
468
- const errorShot = await deps.captureErrorScreenshot(deps.getActivePageOrNull());
469
- const reason = deps.firstErrorLine(err);
470
- const content = [
471
- { type: "text", text: deps.staleRefGuidance(requestedRef, `action failed: ${reason}`) },
472
- { type: "text", text: `Fill ref failed: ${err.message}` },
473
- ];
474
- if (errorShot) {
475
- content.push({ type: "image", data: errorShot.data, mimeType: errorShot.mimeType });
476
- }
477
- return {
478
- content,
479
- details: { error: err.message, ref: requestedRef, hint: "Run browser_snapshot_refs to refresh refs." },
480
- isError: true,
481
- };
87
+ }
88
+ const interactiveOnly = params.interactiveOnly !== false;
89
+ const limit = Math.max(1, Math.min(200, Math.floor(params.limit ?? 40)));
90
+ const rawNodes = await deps.buildRefSnapshot(target, { selector: params.selector, interactiveOnly, limit, mode });
91
+ const newVersion = getRefVersion() + 1;
92
+ setRefVersion(newVersion);
93
+ const nextMap = {};
94
+ for (let i = 0; i < rawNodes.length; i++) {
95
+ nextMap[`e${i + 1}`] = { ref: `e${i + 1}`, ...rawNodes[i] };
96
+ }
97
+ setCurrentRefMap(nextMap);
98
+ const activeFrame = getActiveFrame();
99
+ setRefMetadata({ url: p.url(), timestamp: Date.now(), selectorScope: params.selector, interactiveOnly, limit, version: newVersion, frameContext: activeFrame ? (activeFrame.name() || activeFrame.url()) : undefined, mode });
100
+ if (rawNodes.length === 0) {
101
+ return { content: [{ type: "text", text: "No elements found (try interactiveOnly=false or wider scope)." }], details: { count: 0, version: newVersion, refs: {} } };
102
+ }
103
+ const versionedRefs = {};
104
+ const lines = Object.values(nextMap).map((node) => {
105
+ const vr = deps.formatVersionedRef(newVersion, node.ref);
106
+ versionedRefs[vr] = node;
107
+ const parts = [vr, node.role || node.tag];
108
+ if (node.name)
109
+ parts.push(`"${node.name}"`);
110
+ if (node.href)
111
+ parts.push(`href="${node.href.slice(0, 80)}"`);
112
+ if (!node.isVisible)
113
+ parts.push("(hidden)");
114
+ if (!node.isEnabled)
115
+ parts.push("(disabled)");
116
+ return parts.join(" ");
117
+ });
118
+ return {
119
+ content: [{ type: "text", text: `Ref snapshot v${newVersion} (${rawNodes.length} elements)\nURL: ${p.url()}\nScope: ${params.selector ?? "body"}\n${mode ? `Mode: ${mode}\n` : ""}Use versioned refs (e.g. @v${newVersion}:e1).\n\n${lines.join("\n")}` }],
120
+ details: { count: rawNodes.length, version: newVersion, metadata: getRefMetadata(), refs: nextMap, versionedRefs },
121
+ };
122
+ }
123
+ // ── get ──
124
+ async function getAction(params) {
125
+ const result = validateRef(params.ref);
126
+ if ("error" in result)
127
+ return { content: [{ type: "text", text: result.error }], details: result.details, isError: true };
128
+ const { node, versionedRef } = result;
129
+ return {
130
+ content: [{ type: "text", text: `${versionedRef}: ${node.role || node.tag}${node.name ? ` "${node.name}"` : ""}\nVisible: ${node.isVisible}\nEnabled: ${node.isEnabled}\nPath: ${node.xpathOrPath}` }],
131
+ details: { ref: versionedRef, node, metadata: getRefMetadata() },
132
+ };
133
+ }
134
+ // ── click ──
135
+ async function clickAction(params) {
136
+ const result = validateRef(params.ref);
137
+ if ("error" in result)
138
+ return { content: [{ type: "text", text: result.error }], details: result.details, isError: true };
139
+ const { node, versionedRef } = result;
140
+ const { page: p } = await deps.ensureBrowser();
141
+ const target = deps.getActiveTarget();
142
+ const refMetadata = getRefMetadata();
143
+ if (refMetadata?.url && refMetadata.url !== p.url()) {
144
+ return { content: [{ type: "text", text: deps.staleRefGuidance(params.ref, "URL changed since snapshot") }], details: { error: "ref_stale" }, isError: true };
145
+ }
146
+ const resolved = await deps.resolveRefTarget(target, node);
147
+ if (!resolved.ok)
148
+ return { content: [{ type: "text", text: deps.staleRefGuidance(params.ref, resolved.reason) }], details: { error: "ref_stale" }, isError: true };
149
+ const beforeState = await deps.captureCompactPageState(p, { includeBodyText: true, target });
150
+ await target.locator(resolved.selector).first().click({ timeout: 8000 });
151
+ await deps.settleAfterActionAdaptive(p);
152
+ const afterState = await deps.captureCompactPageState(p, { includeBodyText: true, target });
153
+ const summary = deps.formatCompactStateSummary(afterState);
154
+ const jsErrors = deps.getRecentErrors(p.url());
155
+ return {
156
+ content: [{ type: "text", text: `Clicked ${versionedRef} (${node.role || node.tag}${node.name ? ` "${node.name}"` : ""})${jsErrors}\n\nPage summary:\n${summary}` }],
157
+ details: { ref: versionedRef, selector: resolved.selector, url: p.url() },
158
+ };
159
+ }
160
+ // ── hover ──
161
+ async function hoverAction(params) {
162
+ const result = validateRef(params.ref);
163
+ if ("error" in result)
164
+ return { content: [{ type: "text", text: result.error }], details: result.details, isError: true };
165
+ const { node, versionedRef } = result;
166
+ const { page: p } = await deps.ensureBrowser();
167
+ const target = deps.getActiveTarget();
168
+ const refMetadata = getRefMetadata();
169
+ if (refMetadata?.url && refMetadata.url !== p.url()) {
170
+ return { content: [{ type: "text", text: deps.staleRefGuidance(params.ref, "URL changed since snapshot") }], details: { error: "ref_stale" }, isError: true };
171
+ }
172
+ const resolved = await deps.resolveRefTarget(target, node);
173
+ if (!resolved.ok)
174
+ return { content: [{ type: "text", text: deps.staleRefGuidance(params.ref, resolved.reason) }], details: { error: "ref_stale" }, isError: true };
175
+ await target.locator(resolved.selector).first().hover({ timeout: 8000 });
176
+ await deps.settleAfterActionAdaptive(p);
177
+ const afterState = await deps.captureCompactPageState(p, { includeBodyText: false, target });
178
+ const summary = deps.formatCompactStateSummary(afterState);
179
+ const jsErrors = deps.getRecentErrors(p.url());
180
+ return {
181
+ content: [{ type: "text", text: `Hovered ${versionedRef} (${node.role || node.tag}${node.name ? ` "${node.name}"` : ""})${jsErrors}\n\nPage summary:\n${summary}` }],
182
+ details: { ref: versionedRef, selector: resolved.selector, url: p.url() },
183
+ };
184
+ }
185
+ // ── fill ──
186
+ async function fillAction(params) {
187
+ const result = validateRef(params.ref);
188
+ if ("error" in result)
189
+ return { content: [{ type: "text", text: result.error }], details: result.details, isError: true };
190
+ const { node, versionedRef } = result;
191
+ const { page: p } = await deps.ensureBrowser();
192
+ const target = deps.getActiveTarget();
193
+ const refMetadata = getRefMetadata();
194
+ if (refMetadata?.url && refMetadata.url !== p.url()) {
195
+ return { content: [{ type: "text", text: deps.staleRefGuidance(params.ref, "URL changed since snapshot") }], details: { error: "ref_stale" }, isError: true };
196
+ }
197
+ const resolved = await deps.resolveRefTarget(target, node);
198
+ if (!resolved.ok)
199
+ return { content: [{ type: "text", text: deps.staleRefGuidance(params.ref, resolved.reason) }], details: { error: "ref_stale" }, isError: true };
200
+ const locator = target.locator(resolved.selector).first();
201
+ if (params.slowly) {
202
+ await locator.click({ timeout: 8000 });
203
+ if (params.clearFirst) {
204
+ await p.keyboard.press("Control+A");
205
+ await p.keyboard.press("Delete");
482
206
  }
483
- },
484
- });
207
+ await p.keyboard.type(params.text ?? "");
208
+ }
209
+ else {
210
+ if (params.clearFirst)
211
+ await locator.fill("");
212
+ await locator.fill(params.text ?? "", { timeout: 8000 });
213
+ }
214
+ if (params.submit)
215
+ await p.keyboard.press("Enter");
216
+ await deps.settleAfterActionAdaptive(p);
217
+ const filledValue = await deps.readInputLikeValue(target, resolved.selector);
218
+ const afterState = await deps.captureCompactPageState(p, { includeBodyText: true, target });
219
+ const summary = deps.formatCompactStateSummary(afterState);
220
+ const jsErrors = deps.getRecentErrors(p.url());
221
+ return {
222
+ content: [{ type: "text", text: `Filled ${versionedRef} with "${params.text}" → value: "${filledValue}"${jsErrors}\n\nPage summary:\n${summary}` }],
223
+ details: { ref: versionedRef, selector: resolved.selector, url: p.url(), filledValue },
224
+ };
225
+ }
485
226
  }