TaskSultan keeps every engine a Robot can drive behind a declared Capability. An automation does not open a browser or a workbook itself. It declares which engines it needs and calls a facade the Robot provides. That keeps generated automations small. It also lets the Orchestrator place a job only on a robot that can actually run it.
#The declared list
RobotCapability (framework/types.ts) is a closed union of eight tokens:
| Capability | The facade the Robot provides |
|---|---|
web |
ctx.page, ctx.browser, ctx.context (Playwright) |
excel |
ctx.excel |
files |
ctx.files |
api |
ctx.api |
database |
ctx.db |
email |
ctx.email |
pdf |
ctx.pdf |
desktop |
ctx.desktop |
The list is closed on purpose. A token that is not in the union does not compile, which is how the platform keeps its vocabulary honest.
#How an automation declares what it needs
Each automation carries a capabilities array in its defineAutomation call. The array is optional, but a capability
that gates a facade has to be declared or the facade refuses by name.
import { defineAutomation } from '../framework';
export default defineAutomation({
id: 'process-orders-excel',
displayName: 'Process orders (Excel queue)',
capabilities: ['web', 'excel'],
url: 'http://localhost:4300',
getTransaction: async (ctx) => null,
processTransaction: async (ctx, tx) => {
await ctx.page.goto('/orders');
const table = await ctx.excel.readTable('orders.xlsx');
ctx.log.info(`Read ${table.rowCount} rows`);
},
});
#What happens when the Robot cannot provide one
There are two refusal points and they fire at different times.
The first is placement. The declared capabilities travel into the packaged manifest (runtime/packager.ts writes
capabilities: def.capabilities ?? []) and the Orchestrator's dispatcher will not place a job on a robot that does not
declare them. The refusal names what was missing:
no eligible robot for capabilities [web, excel]
For desktop the message is longer, because a desktop run needs a robot sitting on an interactive session. The
dispatcher says so rather than leaving the operator to guess.
The second is at the facade. When a run reaches a facade the Robot did not supply, the facade throws a
SystemException carrying a stable code rather than returning an empty answer. For files, email, api,
database, pdf and desktop there are two codes, because there are two ways to arrive at a facade with no engine
behind it:
*_NOT_DECLAREDwhen the automation never listed the capability;*_UNAVAILABLEwhen it did declare it but the Robot supplied no provider.
So DESKTOP_NOT_DECLARED, FILES_NOT_DECLARED and the rest sit beside DESKTOP_UNAVAILABLE, FILES_UNAVAILABLE
and the rest.
Two capabilities do not follow that shape. web is always present, because the runner launches the browser itself,
so there is no WEB_NOT_DECLARED code. excel has a single code, EXCEL_UNAVAILABLE: the runner does not check the
declaration before supplying the facade. A bare runner with no Robot refuses with that one code.
#Things that catch people out
Declaring a capability you do not use is harmless. Not declaring one you do use is not. A ctx.files call in an
automation whose capabilities array omits 'files' fails with FILES_NOT_DECLARED, even though the TypeScript
looks fine and nothing else about the file changed.
That refusal is the product working. A facade that returned an empty workbook, or a control with no name, would turn a missing engine into a green run with no evidence. A run that guesses is a run you cannot put in front of an auditor.