@eidra-umain/greenlight 0.1.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.
- package/README.md +391 -0
- package/dist/browser/browser.d.ts +24 -0
- package/dist/browser/browser.d.ts.map +1 -0
- package/dist/browser/browser.js +44 -0
- package/dist/browser/browser.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +140 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/run.d.ts +9 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +277 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/config.d.ts +48 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +107 -0
- package/dist/config.js.map +1 -0
- package/dist/globals.d.ts +21 -0
- package/dist/globals.d.ts.map +1 -0
- package/dist/globals.js +24 -0
- package/dist/globals.js.map +1 -0
- package/dist/parser/loader.d.ts +7 -0
- package/dist/parser/loader.d.ts.map +1 -0
- package/dist/parser/loader.js +43 -0
- package/dist/parser/loader.js.map +1 -0
- package/dist/parser/schema.d.ts +42 -0
- package/dist/parser/schema.d.ts.map +1 -0
- package/dist/parser/schema.js +33 -0
- package/dist/parser/schema.js.map +1 -0
- package/dist/parser/steps.d.ts +13 -0
- package/dist/parser/steps.d.ts.map +1 -0
- package/dist/parser/steps.js +44 -0
- package/dist/parser/steps.js.map +1 -0
- package/dist/parser/variables.d.ts +18 -0
- package/dist/parser/variables.d.ts.map +1 -0
- package/dist/parser/variables.js +44 -0
- package/dist/parser/variables.js.map +1 -0
- package/dist/pilot/a11y-parser.d.ts +26 -0
- package/dist/pilot/a11y-parser.d.ts.map +1 -0
- package/dist/pilot/a11y-parser.js +195 -0
- package/dist/pilot/a11y-parser.js.map +1 -0
- package/dist/pilot/assertions.d.ts +30 -0
- package/dist/pilot/assertions.d.ts.map +1 -0
- package/dist/pilot/assertions.js +219 -0
- package/dist/pilot/assertions.js.map +1 -0
- package/dist/pilot/checkbox.d.ts +12 -0
- package/dist/pilot/checkbox.d.ts.map +1 -0
- package/dist/pilot/checkbox.js +104 -0
- package/dist/pilot/checkbox.js.map +1 -0
- package/dist/pilot/executor.d.ts +17 -0
- package/dist/pilot/executor.d.ts.map +1 -0
- package/dist/pilot/executor.js +462 -0
- package/dist/pilot/executor.js.map +1 -0
- package/dist/pilot/form-fields.d.ts +34 -0
- package/dist/pilot/form-fields.d.ts.map +1 -0
- package/dist/pilot/form-fields.js +139 -0
- package/dist/pilot/form-fields.js.map +1 -0
- package/dist/pilot/llm.d.ts +49 -0
- package/dist/pilot/llm.d.ts.map +1 -0
- package/dist/pilot/llm.js +188 -0
- package/dist/pilot/llm.js.map +1 -0
- package/dist/pilot/locator.d.ts +58 -0
- package/dist/pilot/locator.d.ts.map +1 -0
- package/dist/pilot/locator.js +248 -0
- package/dist/pilot/locator.js.map +1 -0
- package/dist/pilot/message-builder.d.ts +31 -0
- package/dist/pilot/message-builder.d.ts.map +1 -0
- package/dist/pilot/message-builder.js +112 -0
- package/dist/pilot/message-builder.js.map +1 -0
- package/dist/pilot/network.d.ts +23 -0
- package/dist/pilot/network.d.ts.map +1 -0
- package/dist/pilot/network.js +92 -0
- package/dist/pilot/network.js.map +1 -0
- package/dist/pilot/pilot.d.ts +27 -0
- package/dist/pilot/pilot.d.ts.map +1 -0
- package/dist/pilot/pilot.js +249 -0
- package/dist/pilot/pilot.js.map +1 -0
- package/dist/pilot/prompts.d.ts +8 -0
- package/dist/pilot/prompts.d.ts.map +1 -0
- package/dist/pilot/prompts.js +163 -0
- package/dist/pilot/prompts.js.map +1 -0
- package/dist/pilot/providers/anthropic.d.ts +6 -0
- package/dist/pilot/providers/anthropic.d.ts.map +1 -0
- package/dist/pilot/providers/anthropic.js +45 -0
- package/dist/pilot/providers/anthropic.js.map +1 -0
- package/dist/pilot/providers/gemini.d.ts +6 -0
- package/dist/pilot/providers/gemini.d.ts.map +1 -0
- package/dist/pilot/providers/gemini.js +55 -0
- package/dist/pilot/providers/gemini.js.map +1 -0
- package/dist/pilot/providers/index.d.ts +10 -0
- package/dist/pilot/providers/index.d.ts.map +1 -0
- package/dist/pilot/providers/index.js +25 -0
- package/dist/pilot/providers/index.js.map +1 -0
- package/dist/pilot/providers/openai-compatible.d.ts +7 -0
- package/dist/pilot/providers/openai-compatible.d.ts.map +1 -0
- package/dist/pilot/providers/openai-compatible.js +34 -0
- package/dist/pilot/providers/openai-compatible.js.map +1 -0
- package/dist/pilot/providers/types.d.ts +12 -0
- package/dist/pilot/providers/types.d.ts.map +1 -0
- package/dist/pilot/providers/types.js +2 -0
- package/dist/pilot/providers/types.js.map +1 -0
- package/dist/pilot/response-parser.d.ts +31 -0
- package/dist/pilot/response-parser.d.ts.map +1 -0
- package/dist/pilot/response-parser.js +188 -0
- package/dist/pilot/response-parser.js.map +1 -0
- package/dist/pilot/state.d.ts +19 -0
- package/dist/pilot/state.d.ts.map +1 -0
- package/dist/pilot/state.js +67 -0
- package/dist/pilot/state.js.map +1 -0
- package/dist/pilot/trace.d.ts +16 -0
- package/dist/pilot/trace.d.ts.map +1 -0
- package/dist/pilot/trace.js +117 -0
- package/dist/pilot/trace.js.map +1 -0
- package/dist/planner/hasher.d.ts +14 -0
- package/dist/planner/hasher.d.ts.map +1 -0
- package/dist/planner/hasher.js +21 -0
- package/dist/planner/hasher.js.map +1 -0
- package/dist/planner/plan-generator.d.ts +23 -0
- package/dist/planner/plan-generator.d.ts.map +1 -0
- package/dist/planner/plan-generator.js +56 -0
- package/dist/planner/plan-generator.js.map +1 -0
- package/dist/planner/plan-runner.d.ts +16 -0
- package/dist/planner/plan-runner.d.ts.map +1 -0
- package/dist/planner/plan-runner.js +375 -0
- package/dist/planner/plan-runner.js.map +1 -0
- package/dist/planner/plan-store.d.ts +22 -0
- package/dist/planner/plan-store.d.ts.map +1 -0
- package/dist/planner/plan-store.js +71 -0
- package/dist/planner/plan-store.js.map +1 -0
- package/dist/planner/plan-types.d.ts +64 -0
- package/dist/planner/plan-types.d.ts.map +1 -0
- package/dist/planner/plan-types.js +7 -0
- package/dist/planner/plan-types.js.map +1 -0
- package/dist/reporter/types.d.ts +130 -0
- package/dist/reporter/types.d.ts.map +1 -0
- package/dist/reporter/types.js +5 -0
- package/dist/reporter/types.js.map +1 -0
- package/dist/types.d.ts +51 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +23 -0
- package/dist/types.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form field capture and formatting for Playwright pages.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Capture metadata about all visible form fields on the page.
|
|
6
|
+
* Extracts label, placeholder, input type, required status, and select options
|
|
7
|
+
* directly from the DOM — information not available in the a11y tree.
|
|
8
|
+
*/
|
|
9
|
+
export async function captureFormFields(page) {
|
|
10
|
+
return page.evaluate(() => {
|
|
11
|
+
const fields = [];
|
|
12
|
+
const inputs = document.querySelectorAll("input, textarea, select");
|
|
13
|
+
for (const el of inputs) {
|
|
14
|
+
// Skip hidden inputs and submit buttons
|
|
15
|
+
if (el instanceof HTMLInputElement) {
|
|
16
|
+
if (el.type === "hidden" || el.type === "submit" || el.type === "button")
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
// Skip invisible elements
|
|
20
|
+
const style = window.getComputedStyle(el);
|
|
21
|
+
if (style.display === "none" || style.visibility === "hidden")
|
|
22
|
+
continue;
|
|
23
|
+
// Find label: explicit <label for="id">, wrapping <label>, or aria-label
|
|
24
|
+
let label;
|
|
25
|
+
if (el.id) {
|
|
26
|
+
const labelEl = document.querySelector(`label[for="${el.id}"]`);
|
|
27
|
+
if (labelEl)
|
|
28
|
+
label = labelEl.textContent.trim() || undefined;
|
|
29
|
+
}
|
|
30
|
+
if (!label) {
|
|
31
|
+
const labelEl = el.closest("label");
|
|
32
|
+
if (labelEl) {
|
|
33
|
+
// Get label text excluding the input's own text
|
|
34
|
+
const clone = labelEl.cloneNode(true);
|
|
35
|
+
clone.querySelectorAll("input, textarea, select").forEach((c) => { c.remove(); });
|
|
36
|
+
label = clone.textContent.trim() || undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (!label) {
|
|
40
|
+
const ariaLabel = el.getAttribute("aria-label");
|
|
41
|
+
if (ariaLabel) {
|
|
42
|
+
label = ariaLabel;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (!label) {
|
|
46
|
+
const ariaLabelledBy = el.getAttribute("aria-labelledby");
|
|
47
|
+
if (ariaLabelledBy) {
|
|
48
|
+
const labelledBy = document.getElementById(ariaLabelledBy);
|
|
49
|
+
if (labelledBy)
|
|
50
|
+
label = labelledBy.textContent.trim() || undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Detect autocomplete/typeahead/combobox patterns
|
|
54
|
+
let autocomplete = false;
|
|
55
|
+
const role = el.getAttribute("role");
|
|
56
|
+
const ariaAuto = el.getAttribute("aria-autocomplete");
|
|
57
|
+
const ariaExpanded = el.hasAttribute("aria-expanded");
|
|
58
|
+
const ariaOwns = el.getAttribute("aria-owns") ?? el.getAttribute("aria-controls");
|
|
59
|
+
const htmlAutocomplete = el.getAttribute("autocomplete");
|
|
60
|
+
// Explicit ARIA combobox or autocomplete
|
|
61
|
+
if (role === "combobox" || ariaAuto === "list" || ariaAuto === "both") {
|
|
62
|
+
autocomplete = true;
|
|
63
|
+
}
|
|
64
|
+
// Has aria-expanded (toggle pattern) or aria-owns/controls a listbox
|
|
65
|
+
if (ariaExpanded && ariaOwns) {
|
|
66
|
+
autocomplete = true;
|
|
67
|
+
}
|
|
68
|
+
// Parent or wrapper has combobox role
|
|
69
|
+
if (el.closest("[role='combobox']")) {
|
|
70
|
+
autocomplete = true;
|
|
71
|
+
}
|
|
72
|
+
// Adjacent or nearby listbox/datalist
|
|
73
|
+
if (el.getAttribute("list")) {
|
|
74
|
+
autocomplete = true; // HTML5 <datalist>
|
|
75
|
+
}
|
|
76
|
+
// Common CSS class patterns for autocomplete widgets
|
|
77
|
+
const classes = el.className + " " + (el.closest("[class]")?.className ?? "");
|
|
78
|
+
if (/autocomplete|typeahead|combobox|autosuggest|searchbox/i.test(classes)) {
|
|
79
|
+
autocomplete = true;
|
|
80
|
+
}
|
|
81
|
+
// Browser autocomplete="off" often indicates a custom autocomplete widget
|
|
82
|
+
// (the site disables native autocomplete because it has its own)
|
|
83
|
+
if (htmlAutocomplete === "off" && el instanceof HTMLInputElement && el.type === "text") {
|
|
84
|
+
// Only flag as autocomplete if there are other hints (class patterns, nearby listboxes)
|
|
85
|
+
const parent = el.parentElement;
|
|
86
|
+
if (parent && (parent.querySelector("[role='listbox'], [role='option'], .dropdown, .suggestions, .autocomplete-results") ??
|
|
87
|
+
/autocomplete|typeahead|combobox|autosuggest/i.test(parent.className))) {
|
|
88
|
+
autocomplete = true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const field = {
|
|
92
|
+
label,
|
|
93
|
+
placeholder: el.placeholder || undefined,
|
|
94
|
+
inputType: el instanceof HTMLSelectElement ? "select" : (el.type || "text"),
|
|
95
|
+
tag: el.tagName.toLowerCase(),
|
|
96
|
+
required: el.required || el.getAttribute("aria-required") === "true",
|
|
97
|
+
};
|
|
98
|
+
if (autocomplete) {
|
|
99
|
+
field.autocomplete = true;
|
|
100
|
+
}
|
|
101
|
+
// Collect select options
|
|
102
|
+
if (el instanceof HTMLSelectElement) {
|
|
103
|
+
field.options = Array.from(el.options)
|
|
104
|
+
.filter((o) => o.value !== "")
|
|
105
|
+
.map((o) => o.text.trim());
|
|
106
|
+
}
|
|
107
|
+
fields.push(field);
|
|
108
|
+
}
|
|
109
|
+
return fields;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Format form field metadata as readable text for LLM consumption.
|
|
114
|
+
*/
|
|
115
|
+
export function formatFormFields(fields) {
|
|
116
|
+
if (fields.length === 0)
|
|
117
|
+
return "(no form fields found)";
|
|
118
|
+
return fields
|
|
119
|
+
.map((f, i) => {
|
|
120
|
+
const parts = [`${String(i + 1)}. <${f.tag}>`];
|
|
121
|
+
if (f.label)
|
|
122
|
+
parts.push(`label="${f.label}"`);
|
|
123
|
+
if (f.placeholder)
|
|
124
|
+
parts.push(`placeholder="${f.placeholder}"`);
|
|
125
|
+
parts.push(`type="${f.inputType}"`);
|
|
126
|
+
if (f.required)
|
|
127
|
+
parts.push("[required]");
|
|
128
|
+
if (f.autocomplete)
|
|
129
|
+
parts.push("[autocomplete]");
|
|
130
|
+
if (f.ref)
|
|
131
|
+
parts.push(`ref=${f.ref}`);
|
|
132
|
+
if (f.options && f.options.length > 0) {
|
|
133
|
+
parts.push(`options: [${f.options.map((o) => `"${o}"`).join(", ")}]`);
|
|
134
|
+
}
|
|
135
|
+
return parts.join(" ");
|
|
136
|
+
})
|
|
137
|
+
.join("\n");
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=form-fields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-fields.js","sourceRoot":"","sources":["../../src/pilot/form-fields.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-agnostic LLM client.
|
|
3
|
+
* Uses pluggable providers for chat completions.
|
|
4
|
+
*/
|
|
5
|
+
import type { RunConfig } from "../types.js";
|
|
6
|
+
import type { Action, PageState } from "../reporter/types.js";
|
|
7
|
+
import type { Page } from "playwright";
|
|
8
|
+
import type { PlannedStep } from "./response-parser.js";
|
|
9
|
+
import type { LLMProvider } from "./providers/index.js";
|
|
10
|
+
/** Re-export ChatMessage so existing imports still work. */
|
|
11
|
+
export type { ChatMessage } from "./providers/index.js";
|
|
12
|
+
/** Configuration for the LLM client. */
|
|
13
|
+
export interface LLMClientConfig {
|
|
14
|
+
apiKey: string;
|
|
15
|
+
provider: LLMProvider;
|
|
16
|
+
plannerModel: string;
|
|
17
|
+
pilotModel: string;
|
|
18
|
+
}
|
|
19
|
+
/** The LLM client interface. */
|
|
20
|
+
export interface LLMClient {
|
|
21
|
+
/**
|
|
22
|
+
* Pre-plan all steps by sending the full test spec to the LLM.
|
|
23
|
+
* The LLM interprets each step, potentially splitting compound steps
|
|
24
|
+
* into multiple atomic actions. Returns a flat list of planned steps.
|
|
25
|
+
*/
|
|
26
|
+
planSteps(steps: string[]): Promise<PlannedStep[]>;
|
|
27
|
+
/** Resolve a single step using the page state and a11y tree. */
|
|
28
|
+
resolveStep(step: string, pageState: PageState): Promise<Action>;
|
|
29
|
+
/**
|
|
30
|
+
* Expand a compound step into multiple atomic actions using live page state.
|
|
31
|
+
* Used for steps like "fill in the form" that need to see the actual form
|
|
32
|
+
* fields before they can be decomposed into individual type/select/click actions.
|
|
33
|
+
*/
|
|
34
|
+
expandStep(step: string, pageState: PageState, page: Page): Promise<PlannedStep[]>;
|
|
35
|
+
/** Reset conversation history (call between test cases). */
|
|
36
|
+
resetHistory(): void;
|
|
37
|
+
}
|
|
38
|
+
/** Resolve the API key from environment variables. */
|
|
39
|
+
export declare function resolveApiKey(): string;
|
|
40
|
+
/** Resolve LLM client config from RunConfig and environment. */
|
|
41
|
+
export declare function resolveLLMConfig(runConfig: RunConfig): LLMClientConfig;
|
|
42
|
+
/**
|
|
43
|
+
* Create an LLM client that maintains conversation history within a test case.
|
|
44
|
+
* The system prompt is sent once. Each step adds a user message and the LLM's
|
|
45
|
+
* response to the history, giving the model context about prior actions.
|
|
46
|
+
* Call resetHistory() between test cases.
|
|
47
|
+
*/
|
|
48
|
+
export declare function createLLMClient(config: LLMClientConfig): LLMClient;
|
|
49
|
+
//# sourceMappingURL=llm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../src/pilot/llm.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C,OAAO,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAG7D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAUtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAGpE,4DAA4D;AAC5D,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAEvD,wCAAwC;AACxC,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,WAAW,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;CAClB;AAED,gCAAgC;AAChC,MAAM,WAAW,SAAS;IACzB;;;;OAIG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAA;IAClD,gEAAgE;IAChE,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAChE;;;;OAIG;IACH,UAAU,CACT,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,IAAI,GACR,OAAO,CAAC,WAAW,EAAE,CAAC,CAAA;IACzB,4DAA4D;IAC5D,YAAY,IAAI,IAAI,CAAA;CACpB;AAED,sDAAsD;AACtD,wBAAgB,aAAa,IAAI,MAAM,CAQtC;AAED,gEAAgE;AAChE,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,SAAS,GAAG,eAAe,CAYtE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CA8MlE"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-agnostic LLM client.
|
|
3
|
+
* Uses pluggable providers for chat completions.
|
|
4
|
+
*/
|
|
5
|
+
import { resolveModelConfig } from "../types.js";
|
|
6
|
+
import { formatA11yTree } from "./a11y-parser.js";
|
|
7
|
+
import { captureFormFields, formatFormFields } from "./form-fields.js";
|
|
8
|
+
import { globals } from "../globals.js";
|
|
9
|
+
import { SYSTEM_PROMPT, PLAN_SYSTEM_PROMPT, EXPAND_SYSTEM_PROMPT, } from "./prompts.js";
|
|
10
|
+
import { buildUserMessage, buildCompactMessage } from "./message-builder.js";
|
|
11
|
+
import { parseActionResponse, parsePlanResponse } from "./response-parser.js";
|
|
12
|
+
import { createProvider } from "./providers/index.js";
|
|
13
|
+
/** Resolve the API key from environment variables. */
|
|
14
|
+
export function resolveApiKey() {
|
|
15
|
+
const key = process.env.LLM_API_KEY ?? process.env.OPENROUTER_API_KEY;
|
|
16
|
+
if (!key) {
|
|
17
|
+
throw new Error("No API key found. Set LLM_API_KEY or OPENROUTER_API_KEY environment variable.");
|
|
18
|
+
}
|
|
19
|
+
return key;
|
|
20
|
+
}
|
|
21
|
+
/** Resolve LLM client config from RunConfig and environment. */
|
|
22
|
+
export function resolveLLMConfig(runConfig) {
|
|
23
|
+
const modelConfig = resolveModelConfig(runConfig.model);
|
|
24
|
+
const provider = createProvider(runConfig.provider, runConfig.llmBaseUrl);
|
|
25
|
+
return {
|
|
26
|
+
apiKey: resolveApiKey(),
|
|
27
|
+
provider,
|
|
28
|
+
plannerModel: modelConfig.planner,
|
|
29
|
+
pilotModel: modelConfig.pilot,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create an LLM client that maintains conversation history within a test case.
|
|
34
|
+
* The system prompt is sent once. Each step adds a user message and the LLM's
|
|
35
|
+
* response to the history, giving the model context about prior actions.
|
|
36
|
+
* Call resetHistory() between test cases.
|
|
37
|
+
*/
|
|
38
|
+
export function createLLMClient(config) {
|
|
39
|
+
let history = [];
|
|
40
|
+
const cache = new Map();
|
|
41
|
+
let prevPageState = null;
|
|
42
|
+
let prevFormattedTree = "";
|
|
43
|
+
async function chat(messages, model) {
|
|
44
|
+
return config.provider.chatCompletion(messages, {
|
|
45
|
+
apiKey: config.apiKey,
|
|
46
|
+
model,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
resetHistory() {
|
|
51
|
+
history = [];
|
|
52
|
+
prevPageState = null;
|
|
53
|
+
prevFormattedTree = "";
|
|
54
|
+
},
|
|
55
|
+
async planSteps(steps) {
|
|
56
|
+
const userMessage = steps
|
|
57
|
+
.map((s, i) => `${String(i + 1)}. ${s}`)
|
|
58
|
+
.join("\n");
|
|
59
|
+
const content = await chat([
|
|
60
|
+
{ role: "system", content: PLAN_SYSTEM_PROMPT },
|
|
61
|
+
{ role: "user", content: userMessage },
|
|
62
|
+
], config.plannerModel);
|
|
63
|
+
return parsePlanResponse(content);
|
|
64
|
+
},
|
|
65
|
+
async expandStep(step, pageState, page) {
|
|
66
|
+
const tree = formatA11yTree(pageState.a11yTree);
|
|
67
|
+
const formFields = await captureFormFields(page);
|
|
68
|
+
const formFieldsText = formatFormFields(formFields);
|
|
69
|
+
if (globals.debug) {
|
|
70
|
+
console.log(`\n [expand] Detected ${String(formFields.length)} form fields:`);
|
|
71
|
+
for (const f of formFields) {
|
|
72
|
+
const parts = [` <${f.tag}>`];
|
|
73
|
+
if (f.label)
|
|
74
|
+
parts.push(`label="${f.label}"`);
|
|
75
|
+
if (f.placeholder)
|
|
76
|
+
parts.push(`placeholder="${f.placeholder}"`);
|
|
77
|
+
parts.push(`type="${f.inputType}"`);
|
|
78
|
+
if (f.required)
|
|
79
|
+
parts.push("[required]");
|
|
80
|
+
if (f.autocomplete)
|
|
81
|
+
parts.push("[autocomplete]");
|
|
82
|
+
if (f.options && f.options.length > 0) {
|
|
83
|
+
parts.push(`options: [${f.options
|
|
84
|
+
.slice(0, 5)
|
|
85
|
+
.map((o) => `"${o}"`)
|
|
86
|
+
.join(", ")}${f.options.length > 5 ? ", ..." : ""}]`);
|
|
87
|
+
}
|
|
88
|
+
console.log(parts.join(" "));
|
|
89
|
+
}
|
|
90
|
+
const autoFields = formFields.filter((f) => f.autocomplete);
|
|
91
|
+
if (autoFields.length > 0) {
|
|
92
|
+
console.log(` [expand] ${String(autoFields.length)} autocomplete field(s) detected`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const userMessage = [
|
|
96
|
+
`Original step: ${step}`,
|
|
97
|
+
"",
|
|
98
|
+
`Current URL: ${pageState.url}`,
|
|
99
|
+
`Page title: ${pageState.title}`,
|
|
100
|
+
"",
|
|
101
|
+
"Accessibility tree:",
|
|
102
|
+
tree,
|
|
103
|
+
"",
|
|
104
|
+
"Form fields on the page (with label, placeholder, type, and options):",
|
|
105
|
+
formFieldsText,
|
|
106
|
+
].join("\n");
|
|
107
|
+
if (globals.debug) {
|
|
108
|
+
console.log(` [expand] Sending expansion request to LLM...`);
|
|
109
|
+
}
|
|
110
|
+
const content = await chat([
|
|
111
|
+
{ role: "system", content: EXPAND_SYSTEM_PROMPT },
|
|
112
|
+
{ role: "user", content: userMessage },
|
|
113
|
+
], config.plannerModel);
|
|
114
|
+
if (globals.debug) {
|
|
115
|
+
console.log(` [expand] LLM raw response:`);
|
|
116
|
+
for (const line of content.trim().split("\n")) {
|
|
117
|
+
console.log(` ${line}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const expanded = parsePlanResponse(content);
|
|
121
|
+
if (globals.debug) {
|
|
122
|
+
console.log(` [expand] Parsed into ${String(expanded.length)} sub-steps:`);
|
|
123
|
+
for (const es of expanded) {
|
|
124
|
+
const label = es.action
|
|
125
|
+
? JSON.stringify(es.action)
|
|
126
|
+
: "(needs page)";
|
|
127
|
+
console.log(` - ${es.step} → ${label}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Add expansion exchange to history for context in subsequent steps
|
|
131
|
+
history.push({
|
|
132
|
+
role: "user",
|
|
133
|
+
content: `Expanded step: ${step}\nResult:\n${content}`,
|
|
134
|
+
}, {
|
|
135
|
+
role: "assistant",
|
|
136
|
+
content: "OK, form has been filled and submitted.",
|
|
137
|
+
});
|
|
138
|
+
return expanded;
|
|
139
|
+
},
|
|
140
|
+
async resolveStep(step, pageState) {
|
|
141
|
+
// Check cache: same step on same page → same action
|
|
142
|
+
const cacheKey = `${step}\0${pageState.url}`;
|
|
143
|
+
const cached = cache.get(cacheKey);
|
|
144
|
+
if (cached)
|
|
145
|
+
return cached;
|
|
146
|
+
// Try to build a compact message if we have prior state.
|
|
147
|
+
// Three modes:
|
|
148
|
+
// "unchanged" — page identical, skip tree + visible text
|
|
149
|
+
// "tree-only" — tree changed, skip visible text
|
|
150
|
+
// full — first step or after navigation
|
|
151
|
+
let userMessage;
|
|
152
|
+
let compactMode = "full";
|
|
153
|
+
if (prevPageState && history.length > 0) {
|
|
154
|
+
const compact = buildCompactMessage(step, pageState, prevPageState, prevFormattedTree);
|
|
155
|
+
if (compact) {
|
|
156
|
+
userMessage = compact.message;
|
|
157
|
+
compactMode = compact.mode;
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
userMessage = buildUserMessage(step, pageState);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
userMessage = buildUserMessage(step, pageState);
|
|
165
|
+
}
|
|
166
|
+
if (globals.debug) {
|
|
167
|
+
console.log(` [resolve] Mode: ${compactMode} (${String(userMessage.length)} chars)`);
|
|
168
|
+
}
|
|
169
|
+
// Build messages: system + history + new user message
|
|
170
|
+
const messages = [
|
|
171
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
172
|
+
...history,
|
|
173
|
+
{ role: "user", content: userMessage },
|
|
174
|
+
];
|
|
175
|
+
const content = await chat(messages, config.pilotModel);
|
|
176
|
+
const action = parseActionResponse(content);
|
|
177
|
+
// Cache the result for identical future requests
|
|
178
|
+
cache.set(cacheKey, action);
|
|
179
|
+
// Append this exchange to history for subsequent steps
|
|
180
|
+
history.push({ role: "user", content: userMessage }, { role: "assistant", content: content });
|
|
181
|
+
// Track page state for compact messages on subsequent steps
|
|
182
|
+
prevPageState = pageState;
|
|
183
|
+
prevFormattedTree = formatA11yTree(pageState.a11yTree);
|
|
184
|
+
return action;
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=llm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm.js","sourceRoot":"","sources":["../../src/pilot/llm.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locator resolution — translates a11y tree refs into Playwright locators.
|
|
3
|
+
*/
|
|
4
|
+
import type { Page, Locator } from "playwright";
|
|
5
|
+
import type { A11yNode, Action, ResolvedSelector } from "../reporter/types.js";
|
|
6
|
+
export type AriaRole = Parameters<Page["getByRole"]>[0];
|
|
7
|
+
/**
|
|
8
|
+
* Find an A11yNode by its ref ID, searching the tree recursively.
|
|
9
|
+
*/
|
|
10
|
+
export declare function findNodeByRef(nodes: A11yNode[], ref: string): A11yNode | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Find the path from root to a node by ref.
|
|
13
|
+
* Returns the chain of ancestor nodes including the target, or undefined.
|
|
14
|
+
*/
|
|
15
|
+
export declare function findNodePath(nodes: A11yNode[], ref: string, path?: A11yNode[]): A11yNode[] | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Given a locator, return it if it matches exactly one element,
|
|
18
|
+
* or return the first visible match if there are several.
|
|
19
|
+
* Returns undefined if the locator matches nothing.
|
|
20
|
+
*/
|
|
21
|
+
export declare function pickVisible(locator: Locator): Promise<Locator | undefined>;
|
|
22
|
+
export declare function roleLocator(scope: Page | Locator, node: A11yNode): Locator;
|
|
23
|
+
/**
|
|
24
|
+
* Resolve an element ref to a Playwright locator using the a11y tree hierarchy.
|
|
25
|
+
*
|
|
26
|
+
* Primary strategy: chain getByRole calls from ancestor → target using the
|
|
27
|
+
* same tree structure that ariaSnapshot reported. This disambiguates elements
|
|
28
|
+
* that share the same role+name but live under different parents.
|
|
29
|
+
*
|
|
30
|
+
* Fallback strategies (tried in order if chained locator doesn't match):
|
|
31
|
+
* 1. Direct getByRole (ignoring hierarchy)
|
|
32
|
+
* 2. getByLabel (for form inputs)
|
|
33
|
+
* 3. getByPlaceholder (for text inputs)
|
|
34
|
+
* Returns the first locator that resolves to a single visible element.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveLocator(page: Page, nodes: A11yNode[], ref: string): Promise<Locator>;
|
|
37
|
+
/**
|
|
38
|
+
* Resolve an element by its visible text content.
|
|
39
|
+
* Used as a fallback when the element isn't in the accessibility tree
|
|
40
|
+
* (e.g. due to missing ARIA roles in the page markup).
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveByText(page: Page, text: string): Promise<Locator>;
|
|
43
|
+
/**
|
|
44
|
+
* Resolve a locator from an action's ref or text field.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveActionTarget(page: Page, action: Action, a11yTree: A11yNode[]): Promise<Locator>;
|
|
47
|
+
/**
|
|
48
|
+
* Extract a CSS selector from a resolved Playwright locator.
|
|
49
|
+
* Evaluates in-browser to build a unique path-based selector.
|
|
50
|
+
*/
|
|
51
|
+
export declare function extractCssSelector(locator: Locator): Promise<string | undefined>;
|
|
52
|
+
/**
|
|
53
|
+
* Extract selector info from a resolved action for the plan recorder.
|
|
54
|
+
* For ref-based actions: returns role + name from the a11y node.
|
|
55
|
+
* For text-based actions: extracts a CSS selector from the DOM element.
|
|
56
|
+
*/
|
|
57
|
+
export declare function extractSelectorInfo(page: Page, action: Action, a11yTree: A11yNode[], locator: Locator): Promise<ResolvedSelector | undefined>;
|
|
58
|
+
//# sourceMappingURL=locator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locator.d.ts","sourceRoot":"","sources":["../../src/pilot/locator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAE9E,MAAM,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAEvD;;GAEG;AACH,wBAAgB,aAAa,CAC5B,KAAK,EAAE,QAAQ,EAAE,EACjB,GAAG,EAAE,MAAM,GACT,QAAQ,GAAG,SAAS,CAStB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC3B,KAAK,EAAE,QAAQ,EAAE,EACjB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,QAAQ,EAAO,GACnB,QAAQ,EAAE,GAAG,SAAS,CAUxB;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAchF;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,IAAI,GAAG,OAAO,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAM1E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,cAAc,CACnC,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,QAAQ,EAAE,EACjB,GAAG,EAAE,MAAM,GACT,OAAO,CAAC,OAAO,CAAC,CA2DlB;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAmB9E;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACxC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAAE,GAClB,OAAO,CAAC,OAAO,CAAC,CAQlB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACvC,OAAO,EAAE,OAAO,GACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAmC7B;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACxC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAAE,EACpB,OAAO,EAAE,OAAO,GACd,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAqCvC"}
|