copilot-liku-cli 0.0.4 → 0.0.8
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/QUICKSTART.md +24 -0
- package/README.md +85 -33
- package/package.json +23 -14
- package/scripts/postinstall.js +63 -0
- package/src/cli/commands/window.js +66 -0
- package/src/main/agents/base-agent.js +15 -7
- package/src/main/agents/builder.js +211 -0
- package/src/main/agents/index.js +7 -4
- package/src/main/agents/orchestrator.js +13 -0
- package/src/main/agents/producer.js +891 -0
- package/src/main/agents/researcher.js +78 -0
- package/src/main/agents/state-manager.js +134 -2
- package/src/main/agents/verifier.js +201 -0
- package/src/main/ai-service.js +349 -35
- package/src/main/index.js +680 -110
- package/src/main/inspect-service.js +24 -1
- package/src/main/python-bridge.js +395 -0
- package/src/main/system-automation.js +849 -131
- package/src/main/ui-automation/core/ui-provider.js +99 -0
- package/src/main/ui-automation/core/uia-host.js +214 -0
- package/src/main/ui-automation/index.js +30 -0
- package/src/main/ui-automation/interactions/element-click.js +6 -6
- package/src/main/ui-automation/interactions/high-level.js +28 -6
- package/src/main/ui-automation/interactions/index.js +21 -0
- package/src/main/ui-automation/interactions/pattern-actions.js +236 -0
- package/src/main/ui-automation/window/index.js +6 -0
- package/src/main/ui-automation/window/manager.js +173 -26
- package/src/main/ui-watcher.js +401 -58
- package/src/main/visual-awareness.js +18 -1
- package/src/native/windows-uia/Program.cs +89 -0
- package/src/native/windows-uia/build.ps1 +24 -0
- package/src/native/windows-uia-dotnet/Program.cs +920 -0
- package/src/native/windows-uia-dotnet/WindowsUIA.csproj +11 -0
- package/src/native/windows-uia-dotnet/build.ps1 +24 -0
- package/src/renderer/chat/chat.js +915 -671
- package/src/renderer/chat/index.html +2 -4
- package/src/renderer/chat/preload.js +8 -1
- package/src/renderer/overlay/overlay.js +157 -8
- package/src/renderer/overlay/preload.js +4 -0
- package/src/shared/inspect-types.js +82 -6
- package/ARCHITECTURE.md +0 -411
- package/CONFIGURATION.md +0 -302
- package/CONTRIBUTING.md +0 -225
- package/ELECTRON_README.md +0 -121
- package/PROJECT_STATUS.md +0 -229
- package/TESTING.md +0 -274
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} Bounds
|
|
7
|
+
* @property {number} x
|
|
8
|
+
* @property {number} y
|
|
9
|
+
* @property {number} width
|
|
10
|
+
* @property {number} height
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} UIElement
|
|
15
|
+
* @property {string} id
|
|
16
|
+
* @property {string} name
|
|
17
|
+
* @property {string} role
|
|
18
|
+
* @property {Bounds} bounds
|
|
19
|
+
* @property {boolean} isClickable
|
|
20
|
+
* @property {boolean} isFocusable
|
|
21
|
+
* @property {UIElement[]} children
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
class UIProvider {
|
|
25
|
+
constructor() {
|
|
26
|
+
const binDir = path.join(__dirname, '..', '..', '..', '..', 'bin');
|
|
27
|
+
const candidates = [
|
|
28
|
+
path.join(binDir, 'WindowsUIA.exe'),
|
|
29
|
+
path.join(binDir, 'windows-uia.exe')
|
|
30
|
+
];
|
|
31
|
+
this.binaryPath = candidates.find(filePath => fs.existsSync(filePath)) || candidates[0];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Fetches the UI tree from the native binary.
|
|
36
|
+
* @returns {Promise<UIElement>}
|
|
37
|
+
*/
|
|
38
|
+
async getUITree() {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
if (!fs.existsSync(this.binaryPath)) {
|
|
41
|
+
return reject(new Error('UIAutomation binary not found. Build it with: powershell -ExecutionPolicy Bypass -File src/native/windows-uia/build.ps1'));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const child = spawn(this.binaryPath);
|
|
45
|
+
let output = '';
|
|
46
|
+
let errorOutput = '';
|
|
47
|
+
|
|
48
|
+
child.stdout.on('data', (data) => {
|
|
49
|
+
output += data.toString();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
child.stderr.on('data', (data) => {
|
|
53
|
+
errorOutput += data.toString();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
child.on('close', (code) => {
|
|
57
|
+
if (code !== 0) {
|
|
58
|
+
return reject(new Error(`Process exited with code ${code}: ${errorOutput}`));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const parsed = JSON.parse(output);
|
|
63
|
+
const uiTree = this.parseNode(parsed);
|
|
64
|
+
resolve(uiTree);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
reject(new Error(`Failed to parse JSON output: ${err.message}`));
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
child.on('error', (err) => {
|
|
71
|
+
reject(new Error(`Failed to start subprocess: ${err.message}`));
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Parses the OS-specific JSON node into a unified UIElement.
|
|
78
|
+
* @param {Object} node
|
|
79
|
+
* @returns {UIElement}
|
|
80
|
+
*/
|
|
81
|
+
parseNode(node) {
|
|
82
|
+
return {
|
|
83
|
+
id: node.id || '',
|
|
84
|
+
name: node.name || '',
|
|
85
|
+
role: node.role || '',
|
|
86
|
+
bounds: {
|
|
87
|
+
x: node.bounds?.x || 0,
|
|
88
|
+
y: node.bounds?.y || 0,
|
|
89
|
+
width: node.bounds?.width || 0,
|
|
90
|
+
height: node.bounds?.height || 0
|
|
91
|
+
},
|
|
92
|
+
isClickable: !!node.isClickable,
|
|
93
|
+
isFocusable: !!node.isFocusable,
|
|
94
|
+
children: (node.children || []).map(child => this.parseNode(child))
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = { UIProvider };
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent .NET UIA host — spawns WindowsUIA.exe once, communicates
|
|
3
|
+
* via newline-delimited JSON (JSONL) over stdin/stdout.
|
|
4
|
+
*
|
|
5
|
+
* Protocol:
|
|
6
|
+
* stdin → {"cmd":"elementFromPoint","x":500,"y":300}
|
|
7
|
+
* stdout ← {"ok":true,"cmd":"elementFromPoint","element":{…}}
|
|
8
|
+
*
|
|
9
|
+
* Supported commands: getTree, elementFromPoint, exit.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { spawn } = require('child_process');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { EventEmitter } = require('events');
|
|
16
|
+
|
|
17
|
+
const STARTUP_TIMEOUT_MS = 5000;
|
|
18
|
+
const REQUEST_TIMEOUT_MS = 8000;
|
|
19
|
+
|
|
20
|
+
class UIAHost extends EventEmitter {
|
|
21
|
+
constructor() {
|
|
22
|
+
super();
|
|
23
|
+
const binDir = path.join(__dirname, '..', '..', '..', '..', 'bin');
|
|
24
|
+
this._binaryPath = path.join(binDir, 'WindowsUIA.exe');
|
|
25
|
+
this._proc = null;
|
|
26
|
+
this._buffer = '';
|
|
27
|
+
this._pending = null; // { resolve, reject, timer }
|
|
28
|
+
this._alive = false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Ensure the host process is running. Idempotent. */
|
|
32
|
+
async start() {
|
|
33
|
+
if (this._alive && this._proc && !this._proc.killed) return;
|
|
34
|
+
|
|
35
|
+
if (!fs.existsSync(this._binaryPath)) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`UIA host binary not found at ${this._binaryPath}. ` +
|
|
38
|
+
'Build with: powershell -ExecutionPolicy Bypass -File src/native/windows-uia-dotnet/build.ps1'
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this._proc = spawn(this._binaryPath, [], {
|
|
43
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
44
|
+
windowsHide: true
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
this._buffer = '';
|
|
48
|
+
this._alive = true;
|
|
49
|
+
|
|
50
|
+
this._proc.stdout.on('data', (chunk) => this._onData(chunk));
|
|
51
|
+
this._proc.stderr.on('data', (chunk) => {
|
|
52
|
+
this.emit('stderr', chunk.toString());
|
|
53
|
+
});
|
|
54
|
+
this._proc.on('exit', (code) => {
|
|
55
|
+
this._alive = false;
|
|
56
|
+
this._rejectPending(new Error(`UIA host exited with code ${code}`));
|
|
57
|
+
this.emit('exit', code);
|
|
58
|
+
});
|
|
59
|
+
this._proc.on('error', (err) => {
|
|
60
|
+
this._alive = false;
|
|
61
|
+
this._rejectPending(err);
|
|
62
|
+
this.emit('error', err);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Send a command and await the JSON response. */
|
|
67
|
+
async send(cmd) {
|
|
68
|
+
await this.start();
|
|
69
|
+
|
|
70
|
+
if (this._pending) {
|
|
71
|
+
throw new Error('UIAHost: concurrent request not supported (previous call still pending)');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const timer = setTimeout(() => {
|
|
76
|
+
this._pending = null;
|
|
77
|
+
reject(new Error(`UIAHost: command "${cmd.cmd}" timed out after ${REQUEST_TIMEOUT_MS}ms`));
|
|
78
|
+
}, REQUEST_TIMEOUT_MS);
|
|
79
|
+
|
|
80
|
+
this._pending = { resolve, reject, timer };
|
|
81
|
+
|
|
82
|
+
const line = JSON.stringify(cmd) + '\n';
|
|
83
|
+
this._proc.stdin.write(line);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Convenience: elementFromPoint(x, y) → rich element payload */
|
|
88
|
+
async elementFromPoint(x, y) {
|
|
89
|
+
const resp = await this.send({ cmd: 'elementFromPoint', x, y });
|
|
90
|
+
if (!resp.ok) throw new Error(resp.error || 'elementFromPoint failed');
|
|
91
|
+
return resp.element;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Convenience: getTree() → foreground window tree */
|
|
95
|
+
async getTree() {
|
|
96
|
+
const resp = await this.send({ cmd: 'getTree' });
|
|
97
|
+
if (!resp.ok) throw new Error(resp.error || 'getTree failed');
|
|
98
|
+
return resp.tree;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Set value on element at (x,y) using ValuePattern. */
|
|
102
|
+
async setValue(x, y, value) {
|
|
103
|
+
const resp = await this.send({ cmd: 'setValue', x, y, value });
|
|
104
|
+
if (!resp.ok) throw new Error(resp.error || 'setValue failed');
|
|
105
|
+
return resp;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Scroll element at (x,y) using ScrollPattern. direction: up|down|left|right. amount: percent (0-100) or -1 for small increment. */
|
|
109
|
+
async scroll(x, y, direction = 'down', amount = -1) {
|
|
110
|
+
const resp = await this.send({ cmd: 'scroll', x, y, direction, amount });
|
|
111
|
+
if (!resp.ok) throw new Error(resp.error || 'scroll failed');
|
|
112
|
+
return resp;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Expand/collapse element at (x,y). action: expand|collapse|toggle. */
|
|
116
|
+
async expandCollapse(x, y, action = 'toggle') {
|
|
117
|
+
const resp = await this.send({ cmd: 'expandCollapse', x, y, action });
|
|
118
|
+
if (!resp.ok) throw new Error(resp.error || 'expandCollapse failed');
|
|
119
|
+
return resp;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Get text from element at (x,y) using TextPattern → ValuePattern → Name fallback. */
|
|
123
|
+
async getText(x, y) {
|
|
124
|
+
const resp = await this.send({ cmd: 'getText', x, y });
|
|
125
|
+
if (!resp.ok) throw new Error(resp.error || 'getText failed');
|
|
126
|
+
return resp;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Subscribe to UIA events (focus, structure, property). Returns initial snapshot. */
|
|
130
|
+
async subscribeEvents() {
|
|
131
|
+
const resp = await this.send({ cmd: 'subscribeEvents' });
|
|
132
|
+
if (!resp.ok) throw new Error(resp.error || 'subscribeEvents failed');
|
|
133
|
+
return resp;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Unsubscribe from all UIA events. */
|
|
137
|
+
async unsubscribeEvents() {
|
|
138
|
+
const resp = await this.send({ cmd: 'unsubscribeEvents' });
|
|
139
|
+
if (!resp.ok) throw new Error(resp.error || 'unsubscribeEvents failed');
|
|
140
|
+
return resp;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Gracefully shut down the host process. */
|
|
144
|
+
async stop() {
|
|
145
|
+
if (!this._alive || !this._proc) return;
|
|
146
|
+
try {
|
|
147
|
+
await this.send({ cmd: 'exit' });
|
|
148
|
+
} catch { /* ignore */ }
|
|
149
|
+
this._alive = false;
|
|
150
|
+
if (this._proc && !this._proc.killed) {
|
|
151
|
+
this._proc.kill();
|
|
152
|
+
}
|
|
153
|
+
this._proc = null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
get isAlive() {
|
|
157
|
+
return this._alive;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── internal ─────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
_onData(chunk) {
|
|
163
|
+
this._buffer += chunk.toString();
|
|
164
|
+
let nl;
|
|
165
|
+
while ((nl = this._buffer.indexOf('\n')) !== -1) {
|
|
166
|
+
const line = this._buffer.slice(0, nl).trim();
|
|
167
|
+
this._buffer = this._buffer.slice(nl + 1);
|
|
168
|
+
if (!line) continue;
|
|
169
|
+
try {
|
|
170
|
+
const json = JSON.parse(line);
|
|
171
|
+
// Phase 4: route unsolicited event messages before pending resolution
|
|
172
|
+
if (json.type === 'event') {
|
|
173
|
+
this.emit('uia-event', json);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
this._resolvePending(json);
|
|
177
|
+
} catch (e) {
|
|
178
|
+
this.emit('parseError', line, e);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
_resolvePending(json) {
|
|
184
|
+
if (!this._pending) return;
|
|
185
|
+
const { resolve, timer } = this._pending;
|
|
186
|
+
clearTimeout(timer);
|
|
187
|
+
this._pending = null;
|
|
188
|
+
resolve(json);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
_rejectPending(err) {
|
|
192
|
+
if (!this._pending) return;
|
|
193
|
+
const { reject, timer } = this._pending;
|
|
194
|
+
clearTimeout(timer);
|
|
195
|
+
this._pending = null;
|
|
196
|
+
reject(err);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Singleton for shared use
|
|
201
|
+
let _shared = null;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get or create the shared UIAHost instance.
|
|
205
|
+
* @returns {UIAHost}
|
|
206
|
+
*/
|
|
207
|
+
function getSharedUIAHost() {
|
|
208
|
+
if (!_shared) {
|
|
209
|
+
_shared = new UIAHost();
|
|
210
|
+
}
|
|
211
|
+
return _shared;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = { UIAHost, getSharedUIAHost };
|
|
@@ -28,6 +28,8 @@ const { CONFIG, CONTROL_TYPES } = require('./config');
|
|
|
28
28
|
|
|
29
29
|
// Core utilities
|
|
30
30
|
const { sleep, debug, log, executePowerShellScript } = require('./core');
|
|
31
|
+
const { UIProvider } = require('./core/ui-provider');
|
|
32
|
+
const { UIAHost, getSharedUIAHost } = require('./core/uia-host');
|
|
31
33
|
|
|
32
34
|
// Element operations
|
|
33
35
|
const {
|
|
@@ -64,7 +66,10 @@ const {
|
|
|
64
66
|
const {
|
|
65
67
|
getActiveWindow,
|
|
66
68
|
findWindows,
|
|
69
|
+
resolveWindowTarget,
|
|
67
70
|
focusWindow,
|
|
71
|
+
bringWindowToFront,
|
|
72
|
+
sendWindowToBack,
|
|
68
73
|
minimizeWindow,
|
|
69
74
|
maximizeWindow,
|
|
70
75
|
restoreWindow,
|
|
@@ -87,6 +92,15 @@ const {
|
|
|
87
92
|
waitAndClick,
|
|
88
93
|
clickAndWaitFor,
|
|
89
94
|
selectFromDropdown,
|
|
95
|
+
// Pattern-based interactions (Phase 3)
|
|
96
|
+
normalizePatternName,
|
|
97
|
+
hasPattern,
|
|
98
|
+
setElementValue,
|
|
99
|
+
scrollElement,
|
|
100
|
+
expandElement,
|
|
101
|
+
collapseElement,
|
|
102
|
+
toggleExpandCollapse,
|
|
103
|
+
getElementText,
|
|
90
104
|
} = require('./interactions');
|
|
91
105
|
|
|
92
106
|
// Screenshot
|
|
@@ -106,6 +120,9 @@ module.exports = {
|
|
|
106
120
|
debug,
|
|
107
121
|
log,
|
|
108
122
|
executePowerShellScript,
|
|
123
|
+
UIProvider,
|
|
124
|
+
UIAHost,
|
|
125
|
+
getSharedUIAHost,
|
|
109
126
|
|
|
110
127
|
// Element operations
|
|
111
128
|
findElements,
|
|
@@ -135,7 +152,10 @@ module.exports = {
|
|
|
135
152
|
// Window operations
|
|
136
153
|
getActiveWindow,
|
|
137
154
|
findWindows,
|
|
155
|
+
resolveWindowTarget,
|
|
138
156
|
focusWindow,
|
|
157
|
+
bringWindowToFront,
|
|
158
|
+
sendWindowToBack,
|
|
139
159
|
minimizeWindow,
|
|
140
160
|
maximizeWindow,
|
|
141
161
|
restoreWindow,
|
|
@@ -157,6 +177,16 @@ module.exports = {
|
|
|
157
177
|
clickAndWaitFor,
|
|
158
178
|
selectFromDropdown,
|
|
159
179
|
|
|
180
|
+
// Pattern-based interactions (Phase 3)
|
|
181
|
+
normalizePatternName,
|
|
182
|
+
hasPattern,
|
|
183
|
+
setElementValue,
|
|
184
|
+
scrollElement,
|
|
185
|
+
expandElement,
|
|
186
|
+
collapseElement,
|
|
187
|
+
toggleExpandCollapse,
|
|
188
|
+
getElementText,
|
|
189
|
+
|
|
160
190
|
// Screenshot
|
|
161
191
|
screenshot,
|
|
162
192
|
screenshotActiveWindow,
|
|
@@ -50,10 +50,10 @@ async function click(criteria, options = {}) {
|
|
|
50
50
|
return { success: false, element: null, error: findResult?.error || 'Element not found' };
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
// Calculate
|
|
53
|
+
// Calculate click point — prefer UIA clickPoint over bounds-center
|
|
54
54
|
const bounds = element.bounds;
|
|
55
|
-
const x = bounds.x + bounds.width / 2;
|
|
56
|
-
const y = bounds.y + bounds.height / 2;
|
|
55
|
+
const x = element.clickPoint?.x ?? (bounds.x + bounds.width / 2);
|
|
56
|
+
const y = element.clickPoint?.y ?? (bounds.y + bounds.height / 2);
|
|
57
57
|
|
|
58
58
|
// Focus window if needed
|
|
59
59
|
if (focusWindow && element.windowHwnd) {
|
|
@@ -132,11 +132,11 @@ async function clickElement(element, options = {}) {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
const bounds = element.bounds;
|
|
135
|
-
const centerX = bounds.x + bounds.width / 2;
|
|
136
|
-
const centerY = bounds.y + bounds.height / 2;
|
|
135
|
+
const centerX = element.clickPoint?.x ?? (bounds.x + bounds.width / 2);
|
|
136
|
+
const centerY = element.clickPoint?.y ?? (bounds.y + bounds.height / 2);
|
|
137
137
|
|
|
138
138
|
// Strategy 1: Try Invoke pattern for buttons
|
|
139
|
-
if (useInvoke && element.patterns?.includes('InvokePatternIdentifiers.Pattern')) {
|
|
139
|
+
if (useInvoke && (element.patterns?.includes('InvokePatternIdentifiers.Pattern') || element.patterns?.includes('Invoke'))) {
|
|
140
140
|
log(`Attempting Invoke pattern for "${element.name}"`);
|
|
141
141
|
const invokeResult = await invokeElement(element);
|
|
142
142
|
if (invokeResult.success) {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const { findElement, findElements, waitForElement } = require('../elements');
|
|
9
9
|
const { click, clickByText } = require('./element-click');
|
|
10
|
+
const { setElementValue, expandElement } = require('./pattern-actions');
|
|
10
11
|
const { typeText, sendKeys } = require('../keyboard');
|
|
11
12
|
const { focusWindow, findWindows } = require('../window');
|
|
12
13
|
const { log, sleep } = require('../core/helpers');
|
|
@@ -21,9 +22,18 @@ const { log, sleep } = require('../core/helpers');
|
|
|
21
22
|
* @returns {Promise<{success: boolean}>}
|
|
22
23
|
*/
|
|
23
24
|
async function fillField(criteria, text, options = {}) {
|
|
24
|
-
const { clear = true } = options;
|
|
25
|
+
const { clear = true, preferPattern = true } = options;
|
|
26
|
+
|
|
27
|
+
// Strategy 1: Try ValuePattern (fast, no focus/click needed)
|
|
28
|
+
if (preferPattern) {
|
|
29
|
+
const patternResult = await setElementValue(criteria, text);
|
|
30
|
+
if (patternResult.success) {
|
|
31
|
+
log(`fillField: ValuePattern succeeded for "${text.slice(0, 30)}"`);
|
|
32
|
+
return { success: true, method: 'ValuePattern' };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
25
35
|
|
|
26
|
-
// Click
|
|
36
|
+
// Strategy 2: Click + type (fallback)
|
|
27
37
|
const clickResult = await click(criteria);
|
|
28
38
|
if (!clickResult.success) {
|
|
29
39
|
return { success: false };
|
|
@@ -39,7 +49,7 @@ async function fillField(criteria, text, options = {}) {
|
|
|
39
49
|
|
|
40
50
|
// Type text
|
|
41
51
|
const typeResult = await typeText(text);
|
|
42
|
-
return { success: typeResult.success };
|
|
52
|
+
return { success: typeResult.success, method: 'sendKeys' };
|
|
43
53
|
}
|
|
44
54
|
|
|
45
55
|
/**
|
|
@@ -52,9 +62,21 @@ async function fillField(criteria, text, options = {}) {
|
|
|
52
62
|
* @returns {Promise<{success: boolean}>}
|
|
53
63
|
*/
|
|
54
64
|
async function selectDropdownItem(dropdownCriteria, itemCriteria, options = {}) {
|
|
55
|
-
const { itemWait = 1000 } = options;
|
|
65
|
+
const { itemWait = 1000, preferPattern = true } = options;
|
|
66
|
+
|
|
67
|
+
// Strategy 1: Try ExpandCollapsePattern to open
|
|
68
|
+
if (preferPattern) {
|
|
69
|
+
const expandResult = await expandElement(dropdownCriteria);
|
|
70
|
+
if (expandResult.success) {
|
|
71
|
+
log(`selectDropdownItem: ExpandCollapsePattern expanded (${expandResult.stateBefore} → ${expandResult.stateAfter})`);
|
|
72
|
+
await sleep(itemWait);
|
|
73
|
+
const itemQuery = typeof itemCriteria === 'string' ? { text: itemCriteria } : itemCriteria;
|
|
74
|
+
const itemResult = await click(itemQuery);
|
|
75
|
+
return { success: itemResult.success, method: 'ExpandCollapsePattern' };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
56
78
|
|
|
57
|
-
// Click
|
|
79
|
+
// Strategy 2: Click to open (fallback)
|
|
58
80
|
const openResult = await click(dropdownCriteria);
|
|
59
81
|
if (!openResult.success) {
|
|
60
82
|
log('selectDropdownItem: Failed to open dropdown', 'warn');
|
|
@@ -69,7 +91,7 @@ async function selectDropdownItem(dropdownCriteria, itemCriteria, options = {})
|
|
|
69
91
|
: itemCriteria;
|
|
70
92
|
|
|
71
93
|
const itemResult = await click(itemQuery);
|
|
72
|
-
return { success: itemResult.success };
|
|
94
|
+
return { success: itemResult.success, method: 'click' };
|
|
73
95
|
}
|
|
74
96
|
|
|
75
97
|
/**
|
|
@@ -25,6 +25,17 @@ const {
|
|
|
25
25
|
selectFromDropdown,
|
|
26
26
|
} = require('./high-level');
|
|
27
27
|
|
|
28
|
+
const {
|
|
29
|
+
normalizePatternName,
|
|
30
|
+
hasPattern,
|
|
31
|
+
setElementValue,
|
|
32
|
+
scrollElement,
|
|
33
|
+
expandElement,
|
|
34
|
+
collapseElement,
|
|
35
|
+
toggleExpandCollapse,
|
|
36
|
+
getElementText,
|
|
37
|
+
} = require('./pattern-actions');
|
|
38
|
+
|
|
28
39
|
module.exports = {
|
|
29
40
|
// Element clicks
|
|
30
41
|
click,
|
|
@@ -44,4 +55,14 @@ module.exports = {
|
|
|
44
55
|
waitAndClick,
|
|
45
56
|
clickAndWaitFor,
|
|
46
57
|
selectFromDropdown,
|
|
58
|
+
|
|
59
|
+
// Pattern-based interactions (Phase 3)
|
|
60
|
+
normalizePatternName,
|
|
61
|
+
hasPattern,
|
|
62
|
+
setElementValue,
|
|
63
|
+
scrollElement,
|
|
64
|
+
expandElement,
|
|
65
|
+
collapseElement,
|
|
66
|
+
toggleExpandCollapse,
|
|
67
|
+
getElementText,
|
|
47
68
|
};
|