Skip to main content

Intent-Based Actions

UI Bridge lets you drive the UI with a natural language instruction instead of an element id, and lets you register named intents that describe a high-level goal in the same language.

Those are two related but separate mechanisms, and it is worth being precise about which does what:

MechanismWhat it isEntry point
NL actionsParse an instruction, resolve the target element, perform the actionNLActionExecutor / POST /ai/execute
Intent registryA named, described, searchable record of a goal, executed by feeding its description back through the NL executorPOST /ai/intents/*

Overview​

Instead of writing:

await actions.executeAction('ui-login-form-submit-button', { action: 'click' });

You can write:

await executor.execute({ instruction: 'click the Sign In button' });

The parser maps the instruction onto an action verb plus a target description, and the search engine resolves that description against the registry with a confidence score.

Supported Actions​

parseNLInstruction recognises 13 action verbs. Each is matched by a set of phrasings, and each phrasing carries its own parse confidence:

ActionExample phrasings
click"click the Save button", "press Save", "tap Save"
doubleClick"double-click the row"
rightClick"right-click the file", "context click the file"
type"type "user@example.com" into the email field"
select"select United States from the country dropdown"
check"check the terms checkbox"
uncheck"uncheck remember me"
clear"clear the search input"
hover"hover over the menu"
focus"focus the email field"
scroll"scroll to the footer"
wait"wait for the results table"
assert"assert the banner is visible"

There is no submit or navigate verb. Submitting a form is click on its submit control; page navigation is POST /control/page/navigate, and state navigation is POST /control/states/navigate.

Usage​

Basic Execution​

import { createNLActionExecutor, createActionExecutor, getGlobalRegistry } from '@qontinui/ui-bridge';

const registry = getGlobalRegistry();
const executor = createNLActionExecutor();

executor.setActionExecutor(createActionExecutor(registry));
executor.updateElements(registry.getAllElements());

await executor.execute({ instruction: 'click the login button' });
await executor.execute({ instruction: 'type "user@example.com" into email field' });
await executor.execute({ instruction: 'select "United States" from country dropdown' });

updateElements must be called whenever the registry changes — the executor searches a snapshot, not the live DOM. The server handlers do this for you on every request.

With Context and a Threshold​

NLActionRequest accepts three optional fields alongside the instruction:

const result = await executor.execute({
instruction: 'submit the form',
context: 'the registration form in the main panel',
timeout: 5000,
confidenceThreshold: 0.8,
});

context is free text used to disambiguate; confidenceThreshold overrides the executor default of 0.7.

Over HTTP​

POST /ai/execute
Content-Type: application/json

{ "instruction": "click the Sign In button", "confidenceThreshold": 0.8 }

The handler settles the DOM, refreshes the element set, and runs the same executor. Passing withDiff: true additionally returns the semantic diff the action produced.

The Response​

execute resolves to an NLActionResponse:

interface NLActionResponse {
success: boolean;
executedAction: string; // human-readable description of what was done
elementUsed: AIDiscoveredElement;
confidence: number;
elementState: ElementState;
durationMs: number;
timestamp: number;

// present when success is false
error?: string;
errorCode?: string;
suggestions?: string[];
alternatives?: SearchResult[];
failureInfo?: StructuredFailureInfo;
}

Registered Intents​

An Intent is a description record, not a callback. It has no patterns array and no handler function — handler is an optional identifier string:

interface Intent {
id: string;
name: string;
description: string;
tags?: string[];
params?: Record<string, IntentParam>;
handler?: string;
}

Register one over HTTP:

POST /ai/intents/register
Content-Type: application/json

{
"id": "add-to-cart",
"name": "Add to cart",
"description": "click the Add to Cart button",
"tags": ["commerce", "cart"]
}

The other four routes are GET /ai/intents (list), POST /ai/intents/find (search by name, description or tag, with a confidence score), POST /ai/intents/execute (by intentId) and POST /ai/intents/execute-from-query (best match for a query string).

How execution actually works is worth stating plainly, because it bounds what an intent can express: executeIntent looks the intent up, then runs its description through NLActionExecutor.execute as if you had typed it. So an intent's description must be a valid NL instruction, and an intent cannot orchestrate a multi-step sequence or run arbitrary code. params is carried on the record for callers to read; the executor does not interpolate it.

Error Handling​

Failures are typed. The executor emits a UiBridgeErrorCode — for this path most often UB-PARSE-ERROR, UB-VALIDATION-ERROR, UB-ELEM-NOT-FOUND or UB-LOW-CONFIDENCE:

const result = await executor.execute({ instruction: 'click the checkout button' });

if (!result.success) {
console.log(result.errorCode); // 'UB-ELEM-NOT-FOUND'
console.log(result.error); // 'Could not find element matching: "checkout button"'
console.log(result.failureInfo?.suggestedActions); // ranked RecoverySuggestion[]
console.log(result.alternatives); // candidates that scored below threshold
}

See Structured Failure Feedback for the full code vocabulary.

Configuration​

NLActionExecutorConfig — every field is optional and merges over DEFAULT_EXECUTOR_CONFIG:

import { createNLActionExecutor } from '@qontinui/ui-bridge';

const executor = createNLActionExecutor({
defaultConfidenceThreshold: 0.7, // minimum match confidence
defaultTimeout: 5000, // default action timeout in ms
maxAlternatives: 3, // alternatives returned on failure
searchConfig: { fuzzyThreshold: 0.8 }, // forwarded to the SearchEngine
verbose: false,
});

There is no retry setting: retry policy belongs to the caller, and the retryRecommended flag on a structured failure tells you whether retrying is worth it.