@silbercue/chrome 0.2.0

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 (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +229 -0
  3. package/build/cache/a11y-tree.d.ts +252 -0
  4. package/build/cache/a11y-tree.js +1956 -0
  5. package/build/cache/index.d.ts +8 -0
  6. package/build/cache/index.js +4 -0
  7. package/build/cache/selector-cache.d.ts +47 -0
  8. package/build/cache/selector-cache.js +119 -0
  9. package/build/cache/session-defaults.d.ts +27 -0
  10. package/build/cache/session-defaults.js +130 -0
  11. package/build/cache/tab-state-cache.d.ts +39 -0
  12. package/build/cache/tab-state-cache.js +171 -0
  13. package/build/cdp/cdp-client.d.ts +25 -0
  14. package/build/cdp/cdp-client.js +146 -0
  15. package/build/cdp/chrome-launcher.d.ts +85 -0
  16. package/build/cdp/chrome-launcher.js +502 -0
  17. package/build/cdp/console-collector.d.ts +53 -0
  18. package/build/cdp/console-collector.js +147 -0
  19. package/build/cdp/debug.d.ts +1 -0
  20. package/build/cdp/debug.js +6 -0
  21. package/build/cdp/dialog-handler.d.ts +54 -0
  22. package/build/cdp/dialog-handler.js +129 -0
  23. package/build/cdp/dom-watcher.d.ts +45 -0
  24. package/build/cdp/dom-watcher.js +195 -0
  25. package/build/cdp/emulation.d.ts +12 -0
  26. package/build/cdp/emulation.js +17 -0
  27. package/build/cdp/index.d.ts +11 -0
  28. package/build/cdp/index.js +6 -0
  29. package/build/cdp/network-collector.d.ts +77 -0
  30. package/build/cdp/network-collector.js +257 -0
  31. package/build/cdp/protocol.d.ts +20 -0
  32. package/build/cdp/protocol.js +1 -0
  33. package/build/cdp/session-manager.d.ts +62 -0
  34. package/build/cdp/session-manager.js +205 -0
  35. package/build/cdp/settle.d.ts +16 -0
  36. package/build/cdp/settle.js +71 -0
  37. package/build/cli/license-commands.d.ts +19 -0
  38. package/build/cli/license-commands.js +199 -0
  39. package/build/cli/top-level-commands.d.ts +49 -0
  40. package/build/cli/top-level-commands.js +222 -0
  41. package/build/hooks/index.d.ts +2 -0
  42. package/build/hooks/index.js +1 -0
  43. package/build/hooks/pro-hooks.d.ts +126 -0
  44. package/build/hooks/pro-hooks.js +17 -0
  45. package/build/index.d.ts +4 -0
  46. package/build/index.js +86 -0
  47. package/build/license/free-tier-config.d.ts +14 -0
  48. package/build/license/free-tier-config.js +18 -0
  49. package/build/license/index.d.ts +4 -0
  50. package/build/license/index.js +2 -0
  51. package/build/license/license-status.d.ts +15 -0
  52. package/build/license/license-status.js +9 -0
  53. package/build/overlay/session-overlay.d.ts +22 -0
  54. package/build/overlay/session-overlay.js +372 -0
  55. package/build/plan/index.d.ts +7 -0
  56. package/build/plan/index.js +4 -0
  57. package/build/plan/plan-conditions.d.ts +12 -0
  58. package/build/plan/plan-conditions.js +242 -0
  59. package/build/plan/plan-executor.d.ts +49 -0
  60. package/build/plan/plan-executor.js +259 -0
  61. package/build/plan/plan-state-store.d.ts +24 -0
  62. package/build/plan/plan-state-store.js +43 -0
  63. package/build/plan/plan-variables.d.ts +16 -0
  64. package/build/plan/plan-variables.js +71 -0
  65. package/build/registry.d.ts +124 -0
  66. package/build/registry.js +884 -0
  67. package/build/server.d.ts +1 -0
  68. package/build/server.js +245 -0
  69. package/build/tools/click.d.ts +34 -0
  70. package/build/tools/click.js +293 -0
  71. package/build/tools/configure-session.d.ts +15 -0
  72. package/build/tools/configure-session.js +45 -0
  73. package/build/tools/console-logs.d.ts +18 -0
  74. package/build/tools/console-logs.js +44 -0
  75. package/build/tools/dom-snapshot.d.ts +13 -0
  76. package/build/tools/dom-snapshot.js +259 -0
  77. package/build/tools/element-utils.d.ts +23 -0
  78. package/build/tools/element-utils.js +133 -0
  79. package/build/tools/error-utils.d.ts +8 -0
  80. package/build/tools/error-utils.js +27 -0
  81. package/build/tools/evaluate.d.ts +34 -0
  82. package/build/tools/evaluate.js +217 -0
  83. package/build/tools/file-upload.d.ts +20 -0
  84. package/build/tools/file-upload.js +174 -0
  85. package/build/tools/fill-form.d.ts +39 -0
  86. package/build/tools/fill-form.js +256 -0
  87. package/build/tools/handle-dialog.d.ts +15 -0
  88. package/build/tools/handle-dialog.js +48 -0
  89. package/build/tools/index.d.ts +35 -0
  90. package/build/tools/index.js +18 -0
  91. package/build/tools/navigate.d.ts +18 -0
  92. package/build/tools/navigate.js +111 -0
  93. package/build/tools/network-monitor.d.ts +18 -0
  94. package/build/tools/network-monitor.js +66 -0
  95. package/build/tools/observe.d.ts +44 -0
  96. package/build/tools/observe.js +339 -0
  97. package/build/tools/press-key.d.ts +33 -0
  98. package/build/tools/press-key.js +155 -0
  99. package/build/tools/read-page.d.ts +22 -0
  100. package/build/tools/read-page.js +100 -0
  101. package/build/tools/run-plan.d.ts +205 -0
  102. package/build/tools/run-plan.js +215 -0
  103. package/build/tools/screenshot.d.ts +16 -0
  104. package/build/tools/screenshot.js +283 -0
  105. package/build/tools/scroll.d.ts +28 -0
  106. package/build/tools/scroll.js +143 -0
  107. package/build/tools/switch-tab.d.ts +26 -0
  108. package/build/tools/switch-tab.js +355 -0
  109. package/build/tools/tab-status.d.ts +7 -0
  110. package/build/tools/tab-status.js +50 -0
  111. package/build/tools/type.d.ts +31 -0
  112. package/build/tools/type.js +247 -0
  113. package/build/tools/virtual-desk.d.ts +7 -0
  114. package/build/tools/virtual-desk.js +108 -0
  115. package/build/tools/visual-constants.d.ts +3 -0
  116. package/build/tools/visual-constants.js +10 -0
  117. package/build/tools/wait-for.d.ts +26 -0
  118. package/build/tools/wait-for.js +323 -0
  119. package/build/transport/index.d.ts +3 -0
  120. package/build/transport/index.js +2 -0
  121. package/build/transport/pipe-transport.d.ts +18 -0
  122. package/build/transport/pipe-transport.js +63 -0
  123. package/build/transport/transport.d.ts +8 -0
  124. package/build/transport/transport.js +1 -0
  125. package/build/transport/websocket-transport.d.ts +22 -0
  126. package/build/transport/websocket-transport.js +200 -0
  127. package/build/types.d.ts +21 -0
  128. package/build/types.js +1 -0
  129. package/package.json +62 -0
@@ -0,0 +1,355 @@
1
+ import { z } from "zod";
2
+ import { settle } from "../cdp/settle.js";
3
+ import { DEVICE_METRICS_OVERRIDE, isHeadless } from "../cdp/emulation.js";
4
+ import { wrapCdpError } from "./error-utils.js";
5
+ import { injectOverlay } from "../overlay/session-overlay.js";
6
+ export const switchTabSchema = z.object({
7
+ action: z
8
+ .enum(["open", "switch", "close"])
9
+ .optional()
10
+ .default("switch")
11
+ .describe("Action: open (new tab), switch (to existing tab, default), close (close tab)"),
12
+ url: z
13
+ .string()
14
+ .optional()
15
+ .describe("URL to navigate to (for open action, defaults to about:blank)"),
16
+ tab: z
17
+ .string()
18
+ .optional()
19
+ .describe("Tab ID or tab number (1-based index, e.g. '2') to switch to or close (defaults to active tab for close)"),
20
+ });
21
+ /**
22
+ * Resolve a tab that may be a 1-based numeric index (e.g. "2")
23
+ * or a CDP targetId (32-char hex). Returns the actual targetId or undefined.
24
+ */
25
+ function resolveTabId(tabId, pageTabs) {
26
+ if (/^\d{1,3}$/.test(tabId)) {
27
+ const idx = parseInt(tabId, 10) - 1;
28
+ return pageTabs[idx]?.targetId;
29
+ }
30
+ return pageTabs.find((t) => t.targetId === tabId)?.targetId;
31
+ }
32
+ /**
33
+ * Mutex for serialising session-switching operations (H3).
34
+ * Prevents race conditions when parallel tool-calls try to switch tabs concurrently.
35
+ */
36
+ let _switchLock = Promise.resolve();
37
+ function withSwitchLock(fn) {
38
+ const prev = _switchLock;
39
+ let release;
40
+ _switchLock = new Promise((res) => {
41
+ release = res;
42
+ });
43
+ return prev.then(fn).finally(() => release());
44
+ }
45
+ /** Visible for testing — resets the internal lock to a resolved promise. */
46
+ export function _resetSwitchLock() {
47
+ _switchLock = Promise.resolve();
48
+ }
49
+ /**
50
+ * Tracks the tab the user was on before open/switch navigated away.
51
+ * Used by close to return to the origin tab instead of picking a random one.
52
+ */
53
+ let _originTabId;
54
+ /** Visible for testing — resets the origin tab tracking. */
55
+ export function _resetOriginTab() {
56
+ _originTabId = undefined;
57
+ }
58
+ /** Visible for testing — reads the current origin tab. */
59
+ export function _getOriginTabId() {
60
+ return _originTabId;
61
+ }
62
+ /** FR-014: Hint appended to responses where the active tab changes. */
63
+ const STALE_REFS_HINT = "\n\nNote: Element refs from the previous tab are no longer valid. Call read_page for fresh refs.";
64
+ /**
65
+ * Attach to a target, enable CDP domains, re-attach TabStateCache listeners,
66
+ * and propagate the new sessionId. Shared by all three actions.
67
+ */
68
+ async function activateSession(cdpClient, targetId, tabStateCache, onSessionChange) {
69
+ // 1. Attach to target -> new sessionId
70
+ const { sessionId: newSessionId } = await cdpClient.send("Target.attachToTarget", { targetId, flatten: true });
71
+ // 2. Enable CDP domains on the new session
72
+ await cdpClient.send("Runtime.enable", {}, newSessionId);
73
+ await cdpClient.send("Page.enable", {}, newSessionId);
74
+ await cdpClient.send("Page.setLifecycleEventsEnabled", { enabled: true }, newSessionId);
75
+ await cdpClient.send("DOM.enable", {}, newSessionId);
76
+ await cdpClient.send("Accessibility.enable", {}, newSessionId);
77
+ // BUG-015 fix: Keep renderer alive when window is occluded on macOS (per-tab).
78
+ if (!isHeadless()) {
79
+ await cdpClient.send("Emulation.setFocusEmulationEnabled", { enabled: true }, newSessionId);
80
+ }
81
+ if (isHeadless()) {
82
+ await cdpClient.send("Emulation.setDeviceMetricsOverride", DEVICE_METRICS_OVERRIDE, newSessionId);
83
+ }
84
+ // 3. Re-attach cache listeners to new session
85
+ tabStateCache.detachFromClient();
86
+ tabStateCache.attachToClient(cdpClient, newSessionId);
87
+ tabStateCache.setActiveTarget(targetId);
88
+ // 4. Propagate new session to ToolRegistry
89
+ onSessionChange(newSessionId);
90
+ // 5. Inject session overlay into the new tab
91
+ await injectOverlay(cdpClient, newSessionId);
92
+ return newSessionId;
93
+ }
94
+ export async function switchTabHandler(params, cdpClient, sessionId, tabStateCache, onSessionChange, sessionManager) {
95
+ const start = performance.now();
96
+ const method = "switch_tab";
97
+ // H3: Serialise session-switching to prevent race conditions
98
+ return withSwitchLock(async () => {
99
+ try {
100
+ switch (params.action) {
101
+ case "open":
102
+ return await handleOpen(params, cdpClient, tabStateCache, onSessionChange, start, method, sessionManager);
103
+ case "switch":
104
+ return await handleSwitch(params, cdpClient, tabStateCache, onSessionChange, start, method, sessionManager);
105
+ case "close":
106
+ return await handleClose(params, cdpClient, sessionId, tabStateCache, onSessionChange, start, method);
107
+ }
108
+ }
109
+ catch (err) {
110
+ const elapsedMs = Math.round(performance.now() - start);
111
+ return {
112
+ content: [{ type: "text", text: wrapCdpError(err, "switch_tab") }],
113
+ isError: true,
114
+ _meta: { elapsedMs, method },
115
+ };
116
+ }
117
+ });
118
+ }
119
+ async function handleOpen(params, cdpClient, tabStateCache, onSessionChange, start, method, sessionManager) {
120
+ const url = params.url ?? "about:blank";
121
+ // Remember origin tab before switching away
122
+ _originTabId = tabStateCache.activeTargetId ?? undefined;
123
+ // Create new tab
124
+ const { targetId } = await cdpClient.send("Target.createTarget", {
125
+ url,
126
+ });
127
+ // Activate session on the new tab
128
+ const newSessionId = await activateSession(cdpClient, targetId, tabStateCache, onSessionChange);
129
+ // C1: Re-initialize SessionManager for new tab's OOPIF auto-attach
130
+ if (sessionManager) {
131
+ await sessionManager.reinit(cdpClient, newSessionId);
132
+ }
133
+ // If a real URL was specified, settle after navigation (about:blank needs no settle)
134
+ if (params.url && params.url !== "about:blank") {
135
+ const frameTree = await cdpClient.send("Page.getFrameTree", {}, newSessionId);
136
+ const mainFrameId = frameTree.frameTree.frame.id;
137
+ await settle({
138
+ cdpClient,
139
+ sessionId: newSessionId,
140
+ frameId: mainFrameId,
141
+ });
142
+ }
143
+ // Fetch state for response
144
+ const { state } = await tabStateCache.getOrFetch(cdpClient, targetId, newSessionId);
145
+ const elapsedMs = Math.round(performance.now() - start);
146
+ const originLine = _originTabId
147
+ ? `\nOrigin tab: ${_originTabId} — use switch_tab(action: "close") to return`
148
+ : "";
149
+ return {
150
+ content: [
151
+ {
152
+ type: "text",
153
+ text: `Tab opened: ${targetId}\nURL: ${state.url}\nTitle: ${state.title}${originLine}${STALE_REFS_HINT}`,
154
+ },
155
+ ],
156
+ _meta: { elapsedMs, method },
157
+ };
158
+ }
159
+ async function handleSwitch(params, cdpClient, tabStateCache, onSessionChange, start, method, sessionManager) {
160
+ if (!params.tab) {
161
+ // No tab: list available tabs so the LLM can pick one
162
+ const { targetInfos } = await cdpClient.send("Target.getTargets");
163
+ const pageTabs = targetInfos.filter((t) => t.type === "page");
164
+ const activeId = tabStateCache.activeTargetId;
165
+ const lines = pageTabs.map((t, i) => {
166
+ const marker = t.targetId === activeId ? "★" : " ";
167
+ return `${marker} Tab ${i + 1}: ${t.targetId} | ${t.title} | ${t.url}`;
168
+ });
169
+ return {
170
+ content: [{ type: "text", text: `Tabs (${pageTabs.length} open):\n${lines.join("\n")}` }],
171
+ _meta: { elapsedMs: Math.round(performance.now() - start), method },
172
+ };
173
+ }
174
+ // Verify tab exists before switching (supports numeric index or targetId)
175
+ const { targetInfos } = await cdpClient.send("Target.getTargets");
176
+ const pageTabs = targetInfos.filter((t) => t.type === "page");
177
+ const resolvedId = resolveTabId(params.tab, pageTabs);
178
+ if (!resolvedId) {
179
+ return {
180
+ content: [{ type: "text", text: `Tab not found: ${params.tab}. Use virtual_desk to discover available tabs.` }],
181
+ isError: true,
182
+ _meta: { elapsedMs: Math.round(performance.now() - start), method },
183
+ };
184
+ }
185
+ // Remember origin tab before switching away
186
+ _originTabId = tabStateCache.activeTargetId ?? undefined;
187
+ // C1: Remember previous state for rollback if attachToTarget fails
188
+ const previousTargetId = tabStateCache.activeTargetId;
189
+ // Bring tab to front visually + ensure its window is in foreground
190
+ await cdpClient.send("Target.activateTarget", { targetId: resolvedId });
191
+ try {
192
+ const { windowId } = await cdpClient.send("Browser.getWindowForTarget", { targetId: resolvedId });
193
+ await cdpClient.send("Browser.setWindowBounds", {
194
+ windowId,
195
+ bounds: { windowState: "normal" },
196
+ });
197
+ }
198
+ catch {
199
+ /* best-effort — window focus is not critical */
200
+ }
201
+ // C1: Activate CDP session — rollback on failure
202
+ let newSessionId;
203
+ try {
204
+ newSessionId = await activateSession(cdpClient, resolvedId, tabStateCache, onSessionChange);
205
+ }
206
+ catch (attachErr) {
207
+ // C1: Rollback — re-activate the previous tab visually
208
+ if (previousTargetId) {
209
+ try {
210
+ await cdpClient.send("Target.activateTarget", { targetId: previousTargetId });
211
+ }
212
+ catch {
213
+ /* best-effort rollback */
214
+ }
215
+ }
216
+ throw attachErr;
217
+ }
218
+ // C1: Re-initialize SessionManager for new tab's OOPIF auto-attach
219
+ if (sessionManager) {
220
+ await sessionManager.reinit(cdpClient, newSessionId);
221
+ }
222
+ // Fetch state for response
223
+ const { state } = await tabStateCache.getOrFetch(cdpClient, resolvedId, newSessionId);
224
+ const elapsedMs = Math.round(performance.now() - start);
225
+ return {
226
+ content: [
227
+ {
228
+ type: "text",
229
+ text: `Switched to tab: ${resolvedId}\nURL: ${state.url}\nTitle: ${state.title}${STALE_REFS_HINT}`,
230
+ },
231
+ ],
232
+ _meta: { elapsedMs, method },
233
+ };
234
+ }
235
+ async function handleClose(params, cdpClient, sessionId, tabStateCache, onSessionChange, start, method) {
236
+ // Get all page tabs to check if this is the last one
237
+ const { targetInfos } = await cdpClient.send("Target.getTargets");
238
+ const pageTabs = targetInfos.filter((t) => t.type === "page");
239
+ // Resolve tab (supports numeric index or targetId)
240
+ let targetTab;
241
+ if (params.tab) {
242
+ targetTab = resolveTabId(params.tab, pageTabs);
243
+ if (!targetTab) {
244
+ return {
245
+ content: [{ type: "text", text: `Tab not found: ${params.tab}` }],
246
+ isError: true,
247
+ _meta: { elapsedMs: Math.round(performance.now() - start), method },
248
+ };
249
+ }
250
+ }
251
+ else {
252
+ targetTab = tabStateCache.activeTargetId ?? undefined;
253
+ }
254
+ if (!targetTab) {
255
+ return {
256
+ content: [{ type: "text", text: "No active tab to close" }],
257
+ isError: true,
258
+ _meta: { elapsedMs: Math.round(performance.now() - start), method },
259
+ };
260
+ }
261
+ const isLastTab = pageTabs.length <= 1;
262
+ const isActiveTab = targetTab === tabStateCache.activeTargetId;
263
+ let newActiveTab;
264
+ if (isLastTab) {
265
+ // Create a new about:blank tab before closing the last one
266
+ const { targetId: blankTabId } = await cdpClient.send("Target.createTarget", { url: "about:blank" });
267
+ newActiveTab = blankTabId;
268
+ }
269
+ else if (isActiveTab) {
270
+ // Prefer origin tab (the tab we came from) if it still exists
271
+ const originAlive = _originTabId &&
272
+ _originTabId !== targetTab &&
273
+ pageTabs.some((t) => t.targetId === _originTabId);
274
+ if (originAlive) {
275
+ newActiveTab = _originTabId;
276
+ }
277
+ else {
278
+ // Fallback: pick the next available tab
279
+ newActiveTab = pageTabs.find((t) => t.targetId !== targetTab)?.targetId;
280
+ }
281
+ }
282
+ // C2/H1: If switching to a new tab, complete the switch BEFORE closing & cleanup.
283
+ // Architecture: activateTarget (visual) -> attachToTarget (session) -> close old tab.
284
+ if (newActiveTab) {
285
+ // H1: Activate the new tab visually first
286
+ await cdpClient.send("Target.activateTarget", { targetId: newActiveTab });
287
+ // H1: Then attach session
288
+ let newSessionId;
289
+ try {
290
+ newSessionId = await activateSession(cdpClient, newActiveTab, tabStateCache, onSessionChange);
291
+ }
292
+ catch (attachErr) {
293
+ // C2: activateSession failed — don't leave activeTargetId on a tab we're about to close.
294
+ // Find any other surviving tab to fall back to.
295
+ const fallback = pageTabs.find((t) => t.targetId !== targetTab && t.targetId !== newActiveTab);
296
+ if (fallback) {
297
+ try {
298
+ await cdpClient.send("Target.activateTarget", { targetId: fallback.targetId });
299
+ const fallbackSessionId = await activateSession(cdpClient, fallback.targetId, tabStateCache, onSessionChange);
300
+ // Still close the intended target
301
+ await cdpClient.send("Target.closeTarget", { targetId: targetTab });
302
+ tabStateCache.invalidate(targetTab);
303
+ _originTabId = undefined; // Reset after close
304
+ const { state } = await tabStateCache.getOrFetch(cdpClient, fallback.targetId, fallbackSessionId);
305
+ const elapsedMs = Math.round(performance.now() - start);
306
+ return {
307
+ content: [
308
+ {
309
+ type: "text",
310
+ text: `Tab closed: ${targetTab}\nActive tab: ${fallback.targetId} (fallback)\nURL: ${state.url}\nTitle: ${state.title}${STALE_REFS_HINT}`,
311
+ },
312
+ ],
313
+ _meta: { elapsedMs, method },
314
+ };
315
+ }
316
+ catch {
317
+ /* fallback also failed, propagate original error */
318
+ }
319
+ }
320
+ throw attachErr;
321
+ }
322
+ // Now safely close the old tab and clean up
323
+ await cdpClient.send("Target.closeTarget", { targetId: targetTab });
324
+ tabStateCache.invalidate(targetTab);
325
+ const usedOrigin = newActiveTab === _originTabId;
326
+ _originTabId = undefined; // Reset after close
327
+ const { state } = await tabStateCache.getOrFetch(cdpClient, newActiveTab, newSessionId);
328
+ const elapsedMs = Math.round(performance.now() - start);
329
+ const activeLine = usedOrigin
330
+ ? `Returned to origin tab: ${newActiveTab}`
331
+ : `Active tab: ${newActiveTab} (origin tab no longer available)`;
332
+ return {
333
+ content: [
334
+ {
335
+ type: "text",
336
+ text: `Tab closed: ${targetTab}\n${activeLine}\nURL: ${state.url}\nTitle: ${state.title}${STALE_REFS_HINT}`,
337
+ },
338
+ ],
339
+ _meta: { elapsedMs, method },
340
+ };
341
+ }
342
+ // Non-active tab was closed, no switch needed
343
+ await cdpClient.send("Target.closeTarget", { targetId: targetTab });
344
+ tabStateCache.invalidate(targetTab);
345
+ const elapsedMs = Math.round(performance.now() - start);
346
+ return {
347
+ content: [
348
+ {
349
+ type: "text",
350
+ text: `Tab closed: ${targetTab}`,
351
+ },
352
+ ],
353
+ _meta: { elapsedMs, method },
354
+ };
355
+ }
@@ -0,0 +1,7 @@
1
+ import { z } from "zod";
2
+ import type { CdpClient } from "../cdp/cdp-client.js";
3
+ import type { ToolResponse, ConnectionStatus } from "../types.js";
4
+ import type { TabStateCache } from "../cache/tab-state-cache.js";
5
+ export declare const tabStatusSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
6
+ export type TabStatusParams = z.infer<typeof tabStatusSchema>;
7
+ export declare function tabStatusHandler(_params: TabStatusParams, cdpClient: CdpClient, sessionId: string | undefined, tabStateCache: TabStateCache, connectionStatus?: ConnectionStatus): Promise<ToolResponse>;
@@ -0,0 +1,50 @@
1
+ import { z } from "zod";
2
+ import { wrapCdpError } from "./error-utils.js";
3
+ export const tabStatusSchema = z.object({});
4
+ export async function tabStatusHandler(_params, cdpClient, sessionId, tabStateCache, connectionStatus) {
5
+ const start = performance.now();
6
+ // If disconnected or reconnecting, report status immediately
7
+ if (connectionStatus && connectionStatus !== "connected") {
8
+ const elapsedMs = Math.round(performance.now() - start);
9
+ return {
10
+ content: [{ type: "text", text: `Connection: ${connectionStatus} — tool calls may fail until reconnected` }],
11
+ isError: true,
12
+ _meta: { elapsedMs, method: "tab_status" },
13
+ };
14
+ }
15
+ const activeTarget = tabStateCache.activeTargetId;
16
+ if (!activeTarget) {
17
+ return {
18
+ content: [{ type: "text", text: "No active tab. Use virtual_desk to discover available tabs, or navigate to a page first." }],
19
+ isError: true,
20
+ _meta: { elapsedMs: Math.round(performance.now() - start), method: "tab_status" },
21
+ };
22
+ }
23
+ try {
24
+ const { state, cacheHit } = await tabStateCache.getOrFetch(cdpClient, activeTarget, sessionId);
25
+ const elapsedMs = Math.round(performance.now() - start);
26
+ const lines = [
27
+ `URL: ${state.url}`,
28
+ `Title: ${state.title}`,
29
+ `DOM: ${state.domReady ? "ready" : "loading"}`,
30
+ ];
31
+ if (state.consoleErrors.length > 0) {
32
+ lines.push(`Errors (${state.consoleErrors.length}):`);
33
+ for (const err of state.consoleErrors) {
34
+ lines.push(` - ${err.slice(0, 200)}`);
35
+ }
36
+ }
37
+ return {
38
+ content: [{ type: "text", text: lines.join("\n") }],
39
+ _meta: { elapsedMs, method: "tab_status", cacheHit },
40
+ };
41
+ }
42
+ catch (err) {
43
+ const elapsedMs = Math.round(performance.now() - start);
44
+ return {
45
+ content: [{ type: "text", text: wrapCdpError(err, "tab_status") }],
46
+ isError: true,
47
+ _meta: { elapsedMs, method: "tab_status" },
48
+ };
49
+ }
50
+ }
@@ -0,0 +1,31 @@
1
+ import { z } from "zod";
2
+ import type { CdpClient } from "../cdp/cdp-client.js";
3
+ import type { SessionManager } from "../cdp/session-manager.js";
4
+ import type { ToolResponse } from "../types.js";
5
+ export declare const typeSchema: z.ZodObject<{
6
+ ref: z.ZodOptional<z.ZodString>;
7
+ selector: z.ZodOptional<z.ZodString>;
8
+ text: z.ZodString;
9
+ clear: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
10
+ }, "strip", z.ZodTypeAny, {
11
+ text: string;
12
+ clear: boolean;
13
+ ref?: string | undefined;
14
+ selector?: string | undefined;
15
+ }, {
16
+ text: string;
17
+ ref?: string | undefined;
18
+ selector?: string | undefined;
19
+ clear?: boolean | undefined;
20
+ }>;
21
+ export type TypeParams = z.infer<typeof typeSchema>;
22
+ /**
23
+ * Story 16.5: Optional human-type callback injected via the `enhanceTool`
24
+ * Pro-Hook. When present, this replaces the raw `Input.insertText` with a
25
+ * realistic per-character typing sequence from the Pro-Repo Human Touch
26
+ * module. The Free-Repo itself does NOT contain any Human-Touch logic.
27
+ */
28
+ export type HumanTypeFn = (cdpClient: CdpClient, sessionId: string, text: string) => Promise<void>;
29
+ /** Exported for unit tests — clears the streak state. */
30
+ export declare function _resetTypeStreaks(): void;
31
+ export declare function typeHandler(params: TypeParams, cdpClient: CdpClient, sessionId?: string, sessionManager?: SessionManager): Promise<ToolResponse>;