nexus-windows-codex 3.1.2 → 3.1.3
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/CHANGELOG.md +23 -0
- package/README.md +38 -7
- package/lib/garde-fous.mjs +9 -1
- package/lib/mcp.mjs +13 -3
- package/lib/native-provider.mjs +2 -0
- package/lib/session.mjs +30 -5
- package/native/bin/NexusCodex.UIA.exe +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.1.3] - 2026-10-25 — Dialogs, file choosers, form fields
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `windows` also lists open dialogs (a Chrome or Edge "Open" box: owned, without WS_EX_APPWINDOW, absent before) with
|
|
14
|
+
`dialogOf`, their owner; by title the dialog is chosen, by app name the main window comes first.
|
|
15
|
+
- `setup.ps1`: one-step setup for people who never used a terminal (`irm …/setup.ps1 | iex`): installs Node.js LTS
|
|
16
|
+
through winget when Node 22+ is missing, downloads the server from npm and checks that it answers
|
|
17
|
+
(`initialize`, `tools/list`), then registers it in Codex's `config.toml` if it is not there yet; safe to run again.
|
|
18
|
+
- Codex plugin (`.codex-plugin/plugin.json`, `.mcp.json`) and the repository as its own plugin marketplace
|
|
19
|
+
(`codex plugin marketplace add pironjulien/NexusWindows-Codex`).
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- A click on a file chooser trigger ("Choose file", "Browse…", "Upload") is verified only when its dialog opens,
|
|
23
|
+
waited for about 4 s (Edge opens it after more than 1.6 s): before, the click came back "observed" and the dialog
|
|
24
|
+
appeared later; without a dialog the step is `unmet` (`fileChooser: not-opened`), never replayed.
|
|
25
|
+
- A line break typed in a form field was refused because of a nearby help button ("Why can't I send this item?"):
|
|
26
|
+
a neighbour of the field now counts only as a command button (5 words at most, no "?"); a composer's Send button
|
|
27
|
+
still requires confirmation. The refusal names the field.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- README: installation rewritten for beginners (one step, plugin, Codex app settings, `codex mcp add`); `npx` is
|
|
31
|
+
started directly by Codex on Windows, no `cmd /c` needed.
|
|
32
|
+
|
|
10
33
|
## [3.1.2] - 2026-10-25 — Docs that match the code
|
|
11
34
|
|
|
12
35
|
### Fixed
|
package/README.md
CHANGED
|
@@ -62,18 +62,47 @@ bench.
|
|
|
62
62
|
|
|
63
63
|
## Installation
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
Windows 10 or 11 and the [Codex app](https://openai.com/codex/) (or the Codex CLI). The server runs on
|
|
66
|
+
[Node.js](https://nodejs.org/) 22 or later; the npm package carries a self-contained helper: no .NET install.
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
**One step** (installs Node.js LTS if missing, downloads and checks the server, registers it in Codex): open
|
|
69
|
+
**Windows PowerShell** (Start menu, type `powershell`) and paste:
|
|
70
|
+
|
|
71
|
+
```powershell
|
|
72
|
+
irm https://raw.githubusercontent.com/pironjulien/NexusWindows-Codex/main/setup.ps1 | iex
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Then quit Codex completely and open it again. [`setup.ps1`](https://github.com/pironjulien/NexusWindows-Codex/blob/main/setup.ps1)
|
|
76
|
+
is short and can be read first; running it again only does what is missing. Node.js comes from its official package
|
|
77
|
+
through winget (Windows asks once for approval).
|
|
78
|
+
|
|
79
|
+
**Codex plugin** (the same server; Node.js 22+ required). This repository is a plugin marketplace. In the Codex app:
|
|
80
|
+
Settings → Plugins → Add → Add a marketplace, source `pironjulien/NexusWindows-Codex`, then install
|
|
81
|
+
**NexusWindows** from the Plugins directory. Or in a terminal:
|
|
82
|
+
|
|
83
|
+
```powershell
|
|
84
|
+
codex plugin marketplace add pironjulien/NexusWindows-Codex
|
|
85
|
+
codex plugin add nexus-windows@nexus-windows-codex
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**By hand** (Node.js 22+ required, from the [official installer](https://nodejs.org/)). In the Codex app: Settings →
|
|
89
|
+
Plugins → Add → Add an MCP server, name `nexus-windows`, type STDIO, command `npx`, arguments `-y` and
|
|
90
|
+
`nexus-windows-codex`. Or in a terminal:
|
|
91
|
+
|
|
92
|
+
```powershell
|
|
93
|
+
codex mcp add nexus-windows -- npx -y nexus-windows-codex
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Or in `~/.codex/config.toml` (shared by the Codex app, CLI and IDE extension):
|
|
68
97
|
|
|
69
98
|
```toml
|
|
70
99
|
[mcp_servers.nexus-windows]
|
|
71
|
-
command = "
|
|
72
|
-
args = ["
|
|
100
|
+
command = "npx"
|
|
101
|
+
args = ["-y", "nexus-windows-codex"]
|
|
73
102
|
```
|
|
74
103
|
|
|
75
|
-
|
|
76
|
-
(`
|
|
104
|
+
Restart Codex, then ask: "Open Notepad and write a shopping list: milk, eggs, bread." Other MCP clients use the same
|
|
105
|
+
command (`npx -y nexus-windows-codex`; a client that cannot start `.cmd` scripts needs `cmd /c npx -y nexus-windows-codex`).
|
|
77
106
|
|
|
78
107
|
### Codex skill (alternative, from a clone)
|
|
79
108
|
|
|
@@ -100,7 +129,7 @@ the JavaScript module cache does not reload.
|
|
|
100
129
|
|
|
101
130
|
| Tool | Purpose |
|
|
102
131
|
|---|---|
|
|
103
|
-
| `windows(filter)` | Application windows (id, app, title, pid), like the taskbar; password managers never listed |
|
|
132
|
+
| `windows(filter)` | Application windows (id, app, title, pid), like the taskbar, plus open dialogs with `dialogOf` (their owner); password managers never listed |
|
|
104
133
|
| `look(window, mode, region, marks)` | One window by id or a unique part of its title or app: UI Automation tree, plus a capture that works on covered windows with `mode="image"` or `"both"`; `region` zooms on an area of the last image; `marks` draws tree line numbers on the image; selects the window for `act` |
|
|
105
134
|
| `act(steps)` | One step, or up to 32 each with its own `expect`, on the window of the last `look`; stops at the first step not verified |
|
|
106
135
|
| `launch(app)` | Start an application (name, path, Store app id), a folder, a document or a Settings page (`ms-settings:`) and give the foreground back |
|
|
@@ -329,6 +358,8 @@ native/ .NET 8 helper: Program.cs (modes --server, --com-worker, --stop
|
|
|
329
358
|
ForegroundToken.cs, Win32.cs
|
|
330
359
|
skill/nexus-windows/ SKILL.md and references (API, per-application notes)
|
|
331
360
|
tests/ node:test suites; tests/live: real validation fixture (WinForms, Win32, WPF)
|
|
361
|
+
setup.ps1 one-step setup: Node.js if missing, server from npm checked, registered in Codex
|
|
362
|
+
.codex-plugin/, .mcp.json, .agents/plugins/marketplace.json Codex plugin and its marketplace
|
|
332
363
|
install.ps1 builds the helper and installs the skill
|
|
333
364
|
test.ps1 offline check; -Full adds the real validation
|
|
334
365
|
```
|
package/lib/garde-fous.mjs
CHANGED
|
@@ -141,6 +141,14 @@ export function keyKinds(spec) {
|
|
|
141
141
|
}).filter(Boolean);
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
// Bouton d'envoi possible d'un champ : Button ou SplitButton au libellé de commande (5 mots au plus, sans « ? »). Un lien
|
|
145
|
+
// d'aide « Pourquoi ne puis-je pas envoyer cet élément ? » à côté de « Description* » d'un formulaire n'est pas ce
|
|
146
|
+
// qu'Entrée déclenche (faux refus vu le 25/10 par NexusWindows for Claude) ; « Envoyer » d'un composeur le reste.
|
|
147
|
+
const commandButton = (e, elements) => {
|
|
148
|
+
if (!['button', 'splitbutton'].includes(e.role)) return false;
|
|
149
|
+
const label = String(labelOf(e, elements)).trim();
|
|
150
|
+
return !label.includes('?') && label.split(/\s+/).filter(Boolean).length <= 5;
|
|
151
|
+
};
|
|
144
152
|
// Éléments d'action du groupe du champ : sous-arbre de son ancêtre au plus NEIGHBOR_LEVELS au-dessus, jamais la racine.
|
|
145
153
|
function neighbors(field, elements, policy) {
|
|
146
154
|
const ancestors = field.ancestors ?? [];
|
|
@@ -149,7 +157,7 @@ function neighbors(field, elements, policy) {
|
|
|
149
157
|
let found = [];
|
|
150
158
|
for (let levels = Math.min(NEIGHBOR_LEVELS, ancestors.length - 1); levels > 0 && levels <= ancestors.length - 1; levels++) {
|
|
151
159
|
const group = ancestors[ancestors.length - levels];
|
|
152
|
-
found = elements.filter(e => e !== field && e.ancestors?.includes(group) && liveTrigger(e, policy));
|
|
160
|
+
found = elements.filter(e => e !== field && e.ancestors?.includes(group) && liveTrigger(e, policy) && commandButton(e, elements));
|
|
153
161
|
if (found.some(e => policy.confirm?.test(plain(labelOf(e, elements))))) break;
|
|
154
162
|
}
|
|
155
163
|
return found;
|
package/lib/mcp.mjs
CHANGED
|
@@ -55,7 +55,7 @@ const WRITE = { readOnlyHint: false, destructiveHint: true, idempotentHint: fals
|
|
|
55
55
|
// compte rendu d'étape commence par ✓ vérifié, = déjà fait, ~ issue inconnue (jamais rejouée), ✗ échec ou refus.
|
|
56
56
|
export const TOOLS = [
|
|
57
57
|
{ name: 'windows', title: 'List windows', annotations: { ...READ, title: 'List windows' },
|
|
58
|
-
description: 'Application windows on the desktop (id, app, title, pid), like the taskbar. Password managers are never listed. filter: part of a title or app.',
|
|
58
|
+
description: 'Application windows on the desktop (id, app, title, pid), like the taskbar, plus open dialogs (a file "Open" box…) with dialogOf: the window that owns them; look at a dialog by its id or title. Password managers are never listed. filter: part of a title or app.',
|
|
59
59
|
inputSchema: { type: 'object', additionalProperties: false, properties: { filter: { type: 'string' } } } },
|
|
60
60
|
{ name: 'look', title: 'Look at a window', annotations: { ...READ, title: 'Look at a window' },
|
|
61
61
|
description: 'See a window in the background, without bringing it forward: UI Automation tree (roles, names, ids, values, states) and, ' +
|
|
@@ -155,7 +155,9 @@ export function regionToScreen(image, region) {
|
|
|
155
155
|
|
|
156
156
|
function stepLine(step) {
|
|
157
157
|
const how = [step.technique, step.foregroundTaken ? 'foreground taken' : null].filter(Boolean).join(', ');
|
|
158
|
-
|
|
158
|
+
const chooser = step.fileChooser === 'opened' ? ': file dialog opened (look at it by its title)'
|
|
159
|
+
: step.fileChooser === 'not-opened' ? ': no file dialog opened (real click sent, effect not seen); look again' : '';
|
|
160
|
+
return `${mark(step.status)} ${step.kind} — ${step.status}${how ? ` (${how})` : ''}${chooser}${step.error ? `: ${step.error}` : ''}`;
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
export class WindowsTools {
|
|
@@ -178,7 +180,15 @@ export class WindowsTools {
|
|
|
178
180
|
else {
|
|
179
181
|
const wanted = String(window).toLowerCase();
|
|
180
182
|
found = listed.filter(w => w.title.toLowerCase() === wanted);
|
|
181
|
-
if (!found.length)
|
|
183
|
+
if (!found.length) {
|
|
184
|
+
// Par titre, un dialogue est visé comme une autre fenêtre ; par nom d'application seulement, sa fenêtre principale
|
|
185
|
+
// passe d'abord (la boîte « Ouvrir » de Chrome ne rend pas « chrome » ambigu).
|
|
186
|
+
const byTitle = listed.filter(w => w.title.toLowerCase().includes(wanted));
|
|
187
|
+
const byApp = listed.filter(w => !byTitle.includes(w) && w.app.toLowerCase().includes(wanted));
|
|
188
|
+
const mains = byApp.filter(w => !w.dialogOf);
|
|
189
|
+
found = [...byTitle, ...mains];
|
|
190
|
+
if (!found.length) found = byApp;
|
|
191
|
+
}
|
|
182
192
|
}
|
|
183
193
|
if (found.length !== 1) {
|
|
184
194
|
throw new Error(found.length ? `${found.length} windows match "${window}": ${found.map(w => `${w.id} "${w.title}"`).join(', ')}` : `No window matches "${window}"; list windows`);
|
package/lib/native-provider.mjs
CHANGED
|
@@ -11,8 +11,10 @@ export class NoSkyError extends Error {
|
|
|
11
11
|
|
|
12
12
|
export function nativeProvider(native) {
|
|
13
13
|
if (!native?.listWindows) throw new Error('The native helper is required');
|
|
14
|
+
// Dialogue (fenêtre possédée : boîte « Ouvrir » de Chrome) : son propriétaire est imposé au helper à chaque requête.
|
|
14
15
|
const list = async () => (await native.listWindows(0)).windows.map(w => ({
|
|
15
16
|
id: w.id, app: w.app, title: w.title ?? '', pid: w.pid, nativeOnly: true,
|
|
17
|
+
...(w.owner ? { owner: w.owner, dialogOf: { id: w.owner, title: w.ownerTitle ?? '' } } : {}),
|
|
16
18
|
}));
|
|
17
19
|
const refuse = what => async () => { throw new NoSkyError(what); };
|
|
18
20
|
return {
|
package/lib/session.mjs
CHANGED
|
@@ -90,6 +90,11 @@ const TYPE_CHUNK = 1; // frappe physique par graphème : l'arrêt d'urgence est
|
|
|
90
90
|
const PRESENCE_POLL_MS = 50, PRESENCE_POLLS = 33; // ≈ 1,618 s d'attente du retour du garde avant refus
|
|
91
91
|
// 'key' : touches et raccourcis Ctrl/Maj postés en Win32/WinForms (banc commun du 21/10) ; ailleurs, non applicable → Sky.
|
|
92
92
|
const BACKGROUND_KINDS = new Set(['click', 'setValue', 'typeText', 'secondary', 'select', 'menu', 'key']);
|
|
93
|
+
// Déclencheur d'un sélecteur de fichiers : « Upload », « Parcourir… » seuls, ou un verbe suivi d'un objet fichier
|
|
94
|
+
// (« Choisir un fichier: Aucun fichier… » de Chromium, « Choose files », « Ajouter des pièces jointes »). La boîte est
|
|
95
|
+
// attendue au plus ≈ 1,618 × 2,618 s : Edge l'a ouverte après plus de 1,6 s le 25/10.
|
|
96
|
+
const FILE_CHOOSER = /^\s*(?:(?:upload|browse|parcourir|t[eé]l[eé]verser)(?:\s+(?:a|the|your|un|une|des|vos))?(?:\s+(?:fichiers?|files?|documents?|images?|photos?))?|(?:choisir|s[eé]lectionner|ajouter|importer|joindre|choose|select|add|attach)(?:\s+(?:un|une|des|le|les|vos|a|the|your))?\s+(?:fichiers?|files?|documents?|images?|photos?|pi[eè]ces?\s+jointes?|attachments?))\s*(?:…|\.\.\.)?\s*(?::|$)/i;
|
|
97
|
+
const CHOOSER_MS = 4236;
|
|
93
98
|
// Champ destinataire (À, Cc, Cci, To, Destinataire, numéro) : montré à part dans le résumé de l'accord.
|
|
94
99
|
const RECIPIENT_FIELD = /^((a|to|cc|cci|bcc)( recipients?| destinataires?)?|destinataires?|recipients?|e ?mail|courriel|adresse (e ?mail|electronique)|email address|tel|telephone|numero de telephone|phone( number)?|beneficiaire|iban)( |$)/;
|
|
95
100
|
const plainName = name => String(name).normalize('NFKD').replace(/\p{M}/gu, '').toLowerCase().replace(/[^0-9a-z]+/g, ' ').trim();
|
|
@@ -893,6 +898,8 @@ export class WindowsSession {
|
|
|
893
898
|
if (matches.length === 1) field = matches[0];
|
|
894
899
|
}
|
|
895
900
|
field ??= basis.filter(e => e.focused === true).sort((a, b) => b.depth - a.depth)[0] ?? null;
|
|
901
|
+
// Le refus et le résumé de l'accord nomment le champ (« saut de ligne tapé dans « Description* » »).
|
|
902
|
+
if (spec.kind === 'typeText' && field) what = what.replace(' dans le texte', ` tapé dans « ${String(gardeFous.labelOf(field, basis)).trim().slice(0, 60)} »`);
|
|
896
903
|
for (const kind of kinds) fail(gardeFous.keyTriggers(basis, field, kind, policy));
|
|
897
904
|
}
|
|
898
905
|
if (!outward.length) return null;
|
|
@@ -1194,7 +1201,7 @@ export class WindowsSession {
|
|
|
1194
1201
|
this.#observation = observation;
|
|
1195
1202
|
return observation;
|
|
1196
1203
|
}
|
|
1197
|
-
async #settle(spec, coordinate) {
|
|
1204
|
+
async #settle(spec, coordinate, { dialogMs = 0 } = {}) {
|
|
1198
1205
|
const refresh = spec.refresh ?? (coordinate ? 'both' : 'text');
|
|
1199
1206
|
// Fenêtre liée fermée par l'action (Alt+F4, bouton Fermer, dialogue qui ferme l'application) : état terminal relu
|
|
1200
1207
|
// par le helper à toute profondeur de pile, avant chaque capture et après une capture en échec, sans recapturer le
|
|
@@ -1244,6 +1251,16 @@ export class WindowsSession {
|
|
|
1244
1251
|
let after = await capture(spec.expect && refresh === 'image' ? 'both' : refresh, change);
|
|
1245
1252
|
if (!after) return closed();
|
|
1246
1253
|
if (recovered) change = recovered;
|
|
1254
|
+
// Boîte attendue (sélecteur de fichiers) : lectures seules jusqu'à son ouverture ; Edge l'ouvre après plus de 1,6 s.
|
|
1255
|
+
if (dialogMs > 0 && change?.reason !== 'opened') {
|
|
1256
|
+
const deadline = Date.now() + dialogMs;
|
|
1257
|
+
while (Date.now() < deadline && change?.reason !== 'opened') {
|
|
1258
|
+
await sleep(100);
|
|
1259
|
+
const followed = await follow();
|
|
1260
|
+
if (followed === GONE) return closed();
|
|
1261
|
+
if (followed) { change = followed; after = await capture(refresh, change); if (!after) return closed(); }
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1247
1264
|
if (spec.expect && !checkExpectation(after.elements, spec.expect)) {
|
|
1248
1265
|
const deadline = Date.now() + (spec.timeoutMs ?? 1618);
|
|
1249
1266
|
while (Date.now() < deadline && !checkExpectation(after.elements, spec.expect)) {
|
|
@@ -1350,6 +1367,9 @@ export class WindowsSession {
|
|
|
1350
1367
|
let accord;
|
|
1351
1368
|
try { accord = await this.#confirmation(spec, before, selected, screenshot); }
|
|
1352
1369
|
catch (error) { if (error.refused) this.#record(spec.kind, error.confirmationRequired ? 'confirmation-required' : 'refused', Date.now() - start); throw error; }
|
|
1370
|
+
// Sélecteur de fichiers (<input type=file> « Choisir un fichier », « Parcourir… », « Upload ») : boîte attendue.
|
|
1371
|
+
const chooser = spec.kind === 'click' && Boolean(selected) && (spec.count ?? 1) === 1 && (spec.button ?? 'left') === 'left' &&
|
|
1372
|
+
FILE_CHOOSER.test(String(gardeFous.labelOf(selected, before.elements)));
|
|
1353
1373
|
const background = this.#backgroundPlan(spec, before, selected, coordinate, screenshot);
|
|
1354
1374
|
// Sans technique d'arrière-plan possible, les contrôles de l'entrée physique précèdent toute consommation.
|
|
1355
1375
|
if (!background) selected = this.#physicalTarget(spec, original, before) ?? selected;
|
|
@@ -1382,7 +1402,7 @@ export class WindowsSession {
|
|
|
1382
1402
|
}
|
|
1383
1403
|
}
|
|
1384
1404
|
if (outcome) {
|
|
1385
|
-
({ after, change } = await this.#settle(spec, coordinate));
|
|
1405
|
+
({ after, change } = await this.#settle(spec, coordinate, { dialogMs: chooser ? CHOOSER_MS : 0 }));
|
|
1386
1406
|
// Repli physique seulement après un « none » RELU par le helper : rien n'a pris, rien n'est parti. Après « sent »
|
|
1387
1407
|
// (transmis sans relecture possible), un état encore inchangé ne prouve rien : l'effet peut être lent (revue du
|
|
1388
1408
|
// 16/10 : effet arrivé à 2,5 s, après la fenêtre de 1,618 s ; le repli refaisait un vrai clic, double action
|
|
@@ -1417,7 +1437,7 @@ export class WindowsSession {
|
|
|
1417
1437
|
physical = await this.#runPhysical(spec, before, selected, screenshot);
|
|
1418
1438
|
outcome = physical.outcome;
|
|
1419
1439
|
attempts.push({ technique: outcome.technique, mechanism: outcome.mechanism, effect: outcome.effect });
|
|
1420
|
-
({ after, change } = await this.#settle(spec, coordinate));
|
|
1440
|
+
({ after, change } = await this.#settle(spec, coordinate, { dialogMs: chooser ? CHOOSER_MS : 0 }));
|
|
1421
1441
|
}
|
|
1422
1442
|
// Helper du bail perdu pendant l'envoi ou la relecture (fond compris) : l'entrée a pu partir sans jeton ; inconnue
|
|
1423
1443
|
// dès cette action, jamais « verified » (relecture de Codex, 21/10).
|
|
@@ -1440,8 +1460,12 @@ export class WindowsSession {
|
|
|
1440
1460
|
this.#record(spec.kind, 'unknown', Date.now() - start, outcome?.technique ?? null, outcome?.foregroundTaken === true);
|
|
1441
1461
|
throw new Error('Input or refresh outcome is unknown. Observe again before deciding; do not replay automatically.', { cause });
|
|
1442
1462
|
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1463
|
+
// Déclencheur d'un sélecteur de fichiers : le clic n'est attesté que par la boîte ouverte et suivie ; sans elle,
|
|
1464
|
+
// « unmet » (un changement d'image ne prouve rien, vu le 25/10 dans Chrome par NexusWindows for Claude).
|
|
1465
|
+
const chooserOpened = chooser && change?.reason === 'opened';
|
|
1466
|
+
const verified = chooser ? chooserOpened && (!spec.expect || checkExpectation(after.elements, spec.expect))
|
|
1467
|
+
: Boolean(spec.expect && checkExpectation(after.elements, spec.expect));
|
|
1468
|
+
const status = physical?.interrupted ? 'interrupted' : verified ? 'verified' : chooser ? 'unmet' : unconfirmed ? 'sent-unconfirmed' : spec.expect ? 'unmet' : 'observed';
|
|
1445
1469
|
const result = freeze({
|
|
1446
1470
|
status, verified: verified && !physical?.interrupted, performed: true, technique: outcome.technique, mechanism: outcome.mechanism,
|
|
1447
1471
|
foregroundTaken: outcome.foregroundTaken, attempts, julienInput: Boolean(physical?.julienInput),
|
|
@@ -1451,6 +1475,7 @@ export class WindowsSession {
|
|
|
1451
1475
|
...(Object.hasOwn(outcome, 'contextMenu') ? { contextMenu: outcome.contextMenu } : {}),
|
|
1452
1476
|
...(physical?.interrupted ? { interrupted: physical.interrupted, typed: physical.typed } : {}),
|
|
1453
1477
|
...(physical ? { presence: physical.presence ?? 'none', waitedMs: physical.waitedMs ?? 0 } : {}),
|
|
1478
|
+
...(chooser ? { fileChooser: chooserOpened ? 'opened' : 'not-opened' } : {}),
|
|
1454
1479
|
windowChange: change, durationMs: Date.now() - start, observation: after,
|
|
1455
1480
|
});
|
|
1456
1481
|
this.#record(spec.kind, status, result.durationMs, outcome.technique, outcome.foregroundTaken === true);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/native/bin/NexusCodex.UIA.exe
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 35244814 bytes
|
|
4
|
+
sha256: 0ea640868ef2c16c6eb9f0b14a8593e29de71912e2c8cc5dc7e4adcd60773ab0
|
package/package.json
CHANGED