gsd-pi 2.7.1 → 2.8.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 (53) hide show
  1. package/README.md +12 -5
  2. package/dist/loader.js +0 -0
  3. package/dist/modes/interactive/theme/dark.json +85 -0
  4. package/dist/modes/interactive/theme/light.json +84 -0
  5. package/dist/modes/interactive/theme/theme-schema.json +335 -0
  6. package/dist/modes/interactive/theme/theme.d.ts +78 -0
  7. package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  8. package/dist/modes/interactive/theme/theme.js +949 -0
  9. package/dist/modes/interactive/theme/theme.js.map +1 -0
  10. package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  11. package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js +1 -1
  12. package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  13. package/node_modules/@gsd/pi-coding-agent/src/modes/interactive/interactive-mode.ts +1 -1
  14. package/node_modules/cliui/CHANGELOG.md +121 -0
  15. package/node_modules/color-convert/CHANGELOG.md +54 -0
  16. package/node_modules/esprima/ChangeLog +235 -0
  17. package/node_modules/mz/HISTORY.md +66 -0
  18. package/node_modules/proper-lockfile/CHANGELOG.md +108 -0
  19. package/node_modules/source-map/CHANGELOG.md +301 -0
  20. package/node_modules/thenify/History.md +11 -0
  21. package/node_modules/thenify-all/History.md +11 -0
  22. package/node_modules/y18n/CHANGELOG.md +100 -0
  23. package/node_modules/yargs/CHANGELOG.md +88 -0
  24. package/node_modules/yargs-parser/CHANGELOG.md +263 -0
  25. package/package.json +5 -2
  26. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  27. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +1 -1
  28. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  29. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +1 -1
  30. package/src/resources/extensions/browser-tools/capture.ts +165 -0
  31. package/src/resources/extensions/browser-tools/evaluate-helpers.ts +184 -0
  32. package/src/resources/extensions/browser-tools/index.ts +47 -4985
  33. package/src/resources/extensions/browser-tools/lifecycle.ts +265 -0
  34. package/src/resources/extensions/browser-tools/package.json +5 -1
  35. package/src/resources/extensions/browser-tools/refs.ts +264 -0
  36. package/src/resources/extensions/browser-tools/settle.ts +197 -0
  37. package/src/resources/extensions/browser-tools/state.ts +408 -0
  38. package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +652 -0
  39. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +614 -0
  40. package/src/resources/extensions/browser-tools/tools/assertions.ts +342 -0
  41. package/src/resources/extensions/browser-tools/tools/forms.ts +801 -0
  42. package/src/resources/extensions/browser-tools/tools/inspection.ts +492 -0
  43. package/src/resources/extensions/browser-tools/tools/intent.ts +614 -0
  44. package/src/resources/extensions/browser-tools/tools/interaction.ts +865 -0
  45. package/src/resources/extensions/browser-tools/tools/navigation.ts +232 -0
  46. package/src/resources/extensions/browser-tools/tools/pages.ts +303 -0
  47. package/src/resources/extensions/browser-tools/tools/refs.ts +541 -0
  48. package/src/resources/extensions/browser-tools/tools/screenshot.ts +83 -0
  49. package/src/resources/extensions/browser-tools/tools/session.ts +400 -0
  50. package/src/resources/extensions/browser-tools/tools/wait.ts +247 -0
  51. package/src/resources/extensions/browser-tools/utils.ts +660 -0
  52. package/src/resources/extensions/gsd/git-service.ts +3 -0
  53. package/src/resources/extensions/shared/interview-ui.ts +1 -1
@@ -176,7 +176,7 @@ export class InteractiveMode {
176
176
  private pendingTools = new Map<string, ToolExecutionComponent>();
177
177
 
178
178
  // Tool output expansion state
179
- private toolOutputExpanded = false;
179
+ private toolOutputExpanded = true;
180
180
 
181
181
  // Thinking block visibility state
182
182
  private hideThinkingBlock = false;
@@ -0,0 +1,165 @@
1
+ /**
2
+ * browser-tools — page state capture
3
+ *
4
+ * Functions for capturing compact page state, screenshots, and summaries.
5
+ * Used by tool implementations for post-action feedback.
6
+ */
7
+
8
+ import type { Frame, Page } from "playwright";
9
+ import sharp from "sharp";
10
+ import type { CompactPageState, CompactSelectorState } from "./state.js";
11
+ import { formatCompactStateSummary } from "./utils.js";
12
+
13
+ // Anthropic API rejects images > 2000px in multi-image requests.
14
+ // Cap at 1568px (recommended optimal size) to stay well within limits.
15
+ const MAX_SCREENSHOT_DIM = 1568;
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Compact page state capture
19
+ // ---------------------------------------------------------------------------
20
+
21
+ export async function captureCompactPageState(
22
+ p: Page,
23
+ options: { selectors?: string[]; includeBodyText?: boolean; target?: Page | Frame } = {},
24
+ ): Promise<CompactPageState> {
25
+ const selectors = Array.from(new Set((options.selectors ?? []).filter(Boolean)));
26
+ const target = options.target ?? p;
27
+ const domState = await target.evaluate(({ selectors, includeBodyText }) => {
28
+ const selectorStates: Record<string, {
29
+ exists: boolean;
30
+ visible: boolean;
31
+ value: string;
32
+ checked: boolean | null;
33
+ text: string;
34
+ }> = {};
35
+ for (const selector of selectors) {
36
+ let el: Element | null = null;
37
+ try {
38
+ el = document.querySelector(selector);
39
+ } catch {
40
+ el = null;
41
+ }
42
+ if (!el) {
43
+ selectorStates[selector] = {
44
+ exists: false,
45
+ visible: false,
46
+ value: "",
47
+ checked: null,
48
+ text: "",
49
+ };
50
+ continue;
51
+ }
52
+ const htmlEl = el as HTMLElement;
53
+ const style = window.getComputedStyle(htmlEl);
54
+ const rect = htmlEl.getBoundingClientRect();
55
+ const visible = style.display !== "none" && style.visibility !== "hidden" && rect.width > 0 && rect.height > 0;
56
+ const input = el as HTMLInputElement;
57
+ selectorStates[selector] = {
58
+ exists: true,
59
+ visible,
60
+ value:
61
+ el instanceof HTMLInputElement ||
62
+ el instanceof HTMLTextAreaElement ||
63
+ el instanceof HTMLSelectElement
64
+ ? el.value
65
+ : htmlEl.getAttribute("value") || "",
66
+ checked: el instanceof HTMLInputElement && ["checkbox", "radio"].includes(input.type) ? input.checked : null,
67
+ text: (htmlEl.innerText || htmlEl.textContent || "").trim().replace(/\s+/g, " ").slice(0, 160),
68
+ };
69
+ }
70
+
71
+ const focused = document.activeElement as HTMLElement | null;
72
+ const focusedDesc = focused && focused !== document.body && focused !== document.documentElement
73
+ ? `${focused.tagName.toLowerCase()}${focused.id ? '#' + focused.id : ''}${focused.getAttribute('aria-label') ? ' "' + focused.getAttribute('aria-label') + '"' : ''}`
74
+ : "";
75
+ const headings = Array.from(document.querySelectorAll('h1,h2,h3')).slice(0, 5).map((h) => (h.textContent || '').trim().replace(/\s+/g, ' ').slice(0, 80));
76
+ const dialog = document.querySelector('[role="dialog"]:not([hidden]),dialog[open]');
77
+ const dialogTitle = dialog?.querySelector('[role="heading"],[aria-label]')?.textContent?.trim().slice(0, 80) ?? "";
78
+ const bodyText = includeBodyText
79
+ ? (document.body?.innerText || document.body?.textContent || "").trim().replace(/\s+/g, ' ').slice(0, 4000)
80
+ : "";
81
+ return {
82
+ url: window.location.href,
83
+ title: document.title,
84
+ focus: focusedDesc,
85
+ headings,
86
+ bodyText,
87
+ counts: {
88
+ landmarks: document.querySelectorAll('[role="main"],[role="banner"],[role="navigation"],[role="contentinfo"],[role="complementary"],[role="search"],[role="form"],[role="dialog"],[role="alert"],main,header,nav,footer,aside,section,form,dialog').length,
89
+ buttons: document.querySelectorAll('button,[role="button"]').length,
90
+ links: document.querySelectorAll('a[href]').length,
91
+ inputs: document.querySelectorAll('input,textarea,select').length,
92
+ },
93
+ dialog: {
94
+ count: document.querySelectorAll('[role="dialog"]:not([hidden]),dialog[open]').length,
95
+ title: dialogTitle,
96
+ },
97
+ selectorStates,
98
+ };
99
+ }, { selectors, includeBodyText: options.includeBodyText === true });
100
+ // URL and title always come from the Page, not the frame
101
+ return { ...domState, url: p.url(), title: await p.title() };
102
+ }
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // Post-action summary
106
+ // ---------------------------------------------------------------------------
107
+
108
+ /** Lightweight page summary after an action. Returns ~50-150 tokens instead of full tree. */
109
+ export async function postActionSummary(p: Page, target?: Page | Frame): Promise<string> {
110
+ try {
111
+ const state = await captureCompactPageState(p, { target });
112
+ return formatCompactStateSummary(state);
113
+ } catch {
114
+ return "[summary unavailable]";
115
+ }
116
+ }
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // Screenshot helpers
120
+ // ---------------------------------------------------------------------------
121
+
122
+ /**
123
+ * If either dimension of the image buffer exceeds MAX_SCREENSHOT_DIM,
124
+ * downscale proportionally using sharp. Returns the original buffer
125
+ * unchanged if already within limits.
126
+ *
127
+ * `page` parameter is retained for ToolDeps signature stability (D008)
128
+ * but is no longer used — all processing is server-side via sharp.
129
+ */
130
+ export async function constrainScreenshot(
131
+ _page: Page,
132
+ buffer: Buffer,
133
+ mimeType: string,
134
+ quality: number,
135
+ ): Promise<Buffer> {
136
+ const { width, height } = await sharp(buffer).metadata();
137
+
138
+ if (
139
+ width !== undefined &&
140
+ height !== undefined &&
141
+ width <= MAX_SCREENSHOT_DIM &&
142
+ height <= MAX_SCREENSHOT_DIM
143
+ ) {
144
+ return buffer;
145
+ }
146
+
147
+ const resizer = sharp(buffer).resize(MAX_SCREENSHOT_DIM, MAX_SCREENSHOT_DIM, { fit: "inside" });
148
+
149
+ if (mimeType === "image/png") {
150
+ return Buffer.from(await resizer.png().toBuffer());
151
+ }
152
+ return Buffer.from(await resizer.jpeg({ quality }).toBuffer());
153
+ }
154
+
155
+ /** Capture a JPEG screenshot for error debugging. Returns base64 or null. */
156
+ export async function captureErrorScreenshot(p: Page | null): Promise<{ data: string; mimeType: string } | null> {
157
+ if (!p) return null;
158
+ try {
159
+ let buf = await p.screenshot({ type: "jpeg", quality: 60, scale: "css" });
160
+ buf = await constrainScreenshot(p, buf, "image/jpeg", 60);
161
+ return { data: buf.toString("base64"), mimeType: "image/jpeg" };
162
+ } catch {
163
+ return null;
164
+ }
165
+ }
@@ -0,0 +1,184 @@
1
+ /**
2
+ * browser-tools — browser-side evaluate helpers
3
+ *
4
+ * Exports a single string constant `EVALUATE_HELPERS_SOURCE` containing an IIFE
5
+ * that attaches utility functions to `window.__pi`. This is injected into every
6
+ * new BrowserContext via `context.addInitScript()` so that `page.evaluate()`
7
+ * callbacks can reference `window.__pi.cssPath(el)` etc. instead of redeclaring
8
+ * the same functions inline.
9
+ *
10
+ * The `simpleHash` function uses the djb2 algorithm identical to
11
+ * `computeContentHash` / `computeStructuralSignature` in `core.js`.
12
+ *
13
+ * Functions provided (9):
14
+ * cssPath, simpleHash, isVisible, isEnabled, inferRole,
15
+ * accessibleName, isInteractiveEl, domPath, selectorHints
16
+ */
17
+
18
+ export const EVALUATE_HELPERS_SOURCE = `(function() {
19
+ var pi = window.__pi = window.__pi || {};
20
+
21
+ // -----------------------------------------------------------------------
22
+ // 1. simpleHash — djb2 hash matching core.js computeContentHash
23
+ // -----------------------------------------------------------------------
24
+ pi.simpleHash = function simpleHash(str) {
25
+ if (!str) return "0";
26
+ var h = 5381;
27
+ for (var i = 0; i < str.length; i++) {
28
+ h = ((h << 5) - h + str.charCodeAt(i)) | 0;
29
+ }
30
+ return (h >>> 0).toString(16);
31
+ };
32
+
33
+ // -----------------------------------------------------------------------
34
+ // 2. isVisible
35
+ // -----------------------------------------------------------------------
36
+ pi.isVisible = function isVisible(el) {
37
+ var style = window.getComputedStyle(el);
38
+ if (style.display === "none" || style.visibility === "hidden") return false;
39
+ var rect = el.getBoundingClientRect();
40
+ return rect.width > 0 && rect.height > 0;
41
+ };
42
+
43
+ // -----------------------------------------------------------------------
44
+ // 3. isEnabled
45
+ // -----------------------------------------------------------------------
46
+ pi.isEnabled = function isEnabled(el) {
47
+ var disabledAttr = el.getAttribute("disabled") !== null;
48
+ var ariaDisabled = (el.getAttribute("aria-disabled") || "").toLowerCase() === "true";
49
+ return !disabledAttr && !ariaDisabled;
50
+ };
51
+
52
+ // -----------------------------------------------------------------------
53
+ // 4. inferRole
54
+ // -----------------------------------------------------------------------
55
+ pi.inferRole = function inferRole(el) {
56
+ var explicit = (el.getAttribute("role") || "").trim();
57
+ if (explicit) return explicit;
58
+ var tag = el.tagName.toLowerCase();
59
+ if (tag === "a" && el.getAttribute("href")) return "link";
60
+ if (tag === "button") return "button";
61
+ if (tag === "select") return "combobox";
62
+ if (tag === "textarea") return "textbox";
63
+ if (tag === "input") {
64
+ var type = (el.getAttribute("type") || "text").toLowerCase();
65
+ if (["button", "submit", "reset"].indexOf(type) !== -1) return "button";
66
+ if (type === "checkbox") return "checkbox";
67
+ if (type === "radio") return "radio";
68
+ if (type === "search") return "searchbox";
69
+ return "textbox";
70
+ }
71
+ return "";
72
+ };
73
+
74
+ // -----------------------------------------------------------------------
75
+ // 5. accessibleName
76
+ // -----------------------------------------------------------------------
77
+ pi.accessibleName = function accessibleName(el) {
78
+ var ariaLabel = el.getAttribute("aria-label");
79
+ if (ariaLabel && ariaLabel.trim()) return ariaLabel.trim();
80
+ var labelledBy = el.getAttribute("aria-labelledby");
81
+ if (labelledBy && labelledBy.trim()) {
82
+ var text = labelledBy.trim().split(/\\s+/).map(function(id) {
83
+ var ref = document.getElementById(id);
84
+ return ref ? (ref.textContent || "").trim() : "";
85
+ }).join(" ").trim();
86
+ if (text) return text;
87
+ }
88
+ var placeholder = el.getAttribute("placeholder");
89
+ if (placeholder && placeholder.trim()) return placeholder.trim();
90
+ var alt = el.getAttribute("alt");
91
+ if (alt && alt.trim()) return alt.trim();
92
+ var value = el.value;
93
+ if (value && typeof value === "string" && value.trim()) return value.trim().slice(0, 80);
94
+ return (el.textContent || "").trim().replace(/\\s+/g, " ").slice(0, 80);
95
+ };
96
+
97
+ // -----------------------------------------------------------------------
98
+ // 6. isInteractiveEl
99
+ // -----------------------------------------------------------------------
100
+ var interactiveRoles = {
101
+ button: 1, link: 1, textbox: 1, searchbox: 1, combobox: 1,
102
+ checkbox: 1, radio: 1, "switch": 1, menuitem: 1,
103
+ menuitemcheckbox: 1, menuitemradio: 1, tab: 1, option: 1,
104
+ slider: 1, spinbutton: 1
105
+ };
106
+ pi.isInteractiveEl = function isInteractiveEl(el) {
107
+ var tag = el.tagName.toLowerCase();
108
+ var role = pi.inferRole(el);
109
+ if (["button", "input", "select", "textarea", "summary", "option"].indexOf(tag) !== -1) return true;
110
+ if (tag === "a" && !!el.getAttribute("href")) return true;
111
+ if (interactiveRoles[role]) return true;
112
+ if (el.tabIndex >= 0) return true;
113
+ if (el.isContentEditable) return true;
114
+ return false;
115
+ };
116
+
117
+ // -----------------------------------------------------------------------
118
+ // 7. cssPath
119
+ // -----------------------------------------------------------------------
120
+ pi.cssPath = function cssPath(el) {
121
+ if (el.id) return "#" + CSS.escape(el.id);
122
+ var parts = [];
123
+ var current = el;
124
+ while (current && current.nodeType === Node.ELEMENT_NODE && current !== document.body) {
125
+ var tag = current.tagName.toLowerCase();
126
+ var part = tag;
127
+ var parent = current.parentElement;
128
+ if (parent) {
129
+ var siblings = Array.from(parent.children).filter(function(c) {
130
+ return c.tagName === current.tagName;
131
+ });
132
+ if (siblings.length > 1) {
133
+ var idx = siblings.indexOf(current) + 1;
134
+ part += ":nth-of-type(" + idx + ")";
135
+ }
136
+ }
137
+ parts.unshift(part);
138
+ current = current.parentElement;
139
+ }
140
+ return "body > " + parts.join(" > ");
141
+ };
142
+
143
+ // -----------------------------------------------------------------------
144
+ // 8. domPath
145
+ // -----------------------------------------------------------------------
146
+ pi.domPath = function domPath(el) {
147
+ var path = [];
148
+ var current = el;
149
+ while (current && current !== document.documentElement) {
150
+ var parent = current.parentElement;
151
+ if (!parent) break;
152
+ var idx = Array.from(parent.children).indexOf(current);
153
+ path.unshift(idx);
154
+ current = parent;
155
+ }
156
+ return path;
157
+ };
158
+
159
+ // -----------------------------------------------------------------------
160
+ // 9. selectorHints
161
+ // -----------------------------------------------------------------------
162
+ pi.selectorHints = function selectorHints(el) {
163
+ var hints = [];
164
+ if (el.id) hints.push("#" + CSS.escape(el.id));
165
+ var nameAttr = el.getAttribute("name");
166
+ if (nameAttr) hints.push(el.tagName.toLowerCase() + '[name="' + CSS.escape(nameAttr) + '"]');
167
+ var aria = el.getAttribute("aria-label");
168
+ if (aria) hints.push(el.tagName.toLowerCase() + '[aria-label="' + CSS.escape(aria) + '"]');
169
+ var placeholder = el.getAttribute("placeholder");
170
+ if (placeholder) hints.push(el.tagName.toLowerCase() + '[placeholder="' + CSS.escape(placeholder) + '"]');
171
+ var cls = Array.from(el.classList).slice(0, 2);
172
+ if (cls.length > 0) hints.push(el.tagName.toLowerCase() + "." + cls.map(function(c) { return CSS.escape(c); }).join("."));
173
+ hints.push(pi.cssPath(el));
174
+ var seen = {};
175
+ var unique = [];
176
+ for (var i = 0; i < hints.length; i++) {
177
+ if (!seen[hints[i]]) {
178
+ seen[hints[i]] = true;
179
+ unique.push(hints[i]);
180
+ }
181
+ }
182
+ return unique.slice(0, 6);
183
+ };
184
+ })();`;