@swmansion/argent 0.20.1-next.10 → 0.20.1-next.12
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/dist/cli-cmds.mjs +1 -0
- package/dist/installer.mjs +1 -0
- package/dist/mcp-server.mjs +2 -1
- package/dist/tool-server.cjs +102 -57
- package/package.json +1 -1
- package/skills/argent-create-flow/references/flow-yaml.md +1 -1
- package/skills/argent-create-flow/references/live-authoring.md +11 -11
- package/skills/argent-create-flow/references/reliability-and-recovery.md +1 -1
- package/skills/argent-device-interact/SKILL.md +7 -5
- package/skills/argent-tv-interact/SKILL.md +1 -1
package/dist/cli-cmds.mjs
CHANGED
|
@@ -1435,6 +1435,7 @@ var FAILURE_CODES = {
|
|
|
1435
1435
|
CHROMIUM_ELECTRON_EXITED_BEFORE_READY: "CHROMIUM_ELECTRON_EXITED_BEFORE_READY",
|
|
1436
1436
|
KEYBOARD_KEY_UNSUPPORTED: "KEYBOARD_KEY_UNSUPPORTED",
|
|
1437
1437
|
KEYBOARD_CHARACTER_UNSUPPORTED: "KEYBOARD_CHARACTER_UNSUPPORTED",
|
|
1438
|
+
KEYBOARD_TEXT_AND_KEY_COMBINED: "KEYBOARD_TEXT_AND_KEY_COMBINED",
|
|
1438
1439
|
SECRET_PLACEHOLDER_UNKNOWN: "SECRET_PLACEHOLDER_UNKNOWN",
|
|
1439
1440
|
SCREENSHOT_DIFF_INPUT_INVALID: "SCREENSHOT_DIFF_INPUT_INVALID",
|
|
1440
1441
|
BOOT_DEVICE_TARGET_SELECTION_INVALID: "BOOT_DEVICE_TARGET_SELECTION_INVALID",
|
package/dist/installer.mjs
CHANGED
|
@@ -16584,6 +16584,7 @@ var FAILURE_CODES = {
|
|
|
16584
16584
|
CHROMIUM_ELECTRON_EXITED_BEFORE_READY: "CHROMIUM_ELECTRON_EXITED_BEFORE_READY",
|
|
16585
16585
|
KEYBOARD_KEY_UNSUPPORTED: "KEYBOARD_KEY_UNSUPPORTED",
|
|
16586
16586
|
KEYBOARD_CHARACTER_UNSUPPORTED: "KEYBOARD_CHARACTER_UNSUPPORTED",
|
|
16587
|
+
KEYBOARD_TEXT_AND_KEY_COMBINED: "KEYBOARD_TEXT_AND_KEY_COMBINED",
|
|
16587
16588
|
SECRET_PLACEHOLDER_UNKNOWN: "SECRET_PLACEHOLDER_UNKNOWN",
|
|
16588
16589
|
SCREENSHOT_DIFF_INPUT_INVALID: "SCREENSHOT_DIFF_INPUT_INVALID",
|
|
16589
16590
|
BOOT_DEVICE_TARGET_SELECTION_INVALID: "BOOT_DEVICE_TARGET_SELECTION_INVALID",
|
package/dist/mcp-server.mjs
CHANGED
|
@@ -16641,6 +16641,7 @@ var FAILURE_CODES = {
|
|
|
16641
16641
|
CHROMIUM_ELECTRON_EXITED_BEFORE_READY: "CHROMIUM_ELECTRON_EXITED_BEFORE_READY",
|
|
16642
16642
|
KEYBOARD_KEY_UNSUPPORTED: "KEYBOARD_KEY_UNSUPPORTED",
|
|
16643
16643
|
KEYBOARD_CHARACTER_UNSUPPORTED: "KEYBOARD_CHARACTER_UNSUPPORTED",
|
|
16644
|
+
KEYBOARD_TEXT_AND_KEY_COMBINED: "KEYBOARD_TEXT_AND_KEY_COMBINED",
|
|
16644
16645
|
SECRET_PLACEHOLDER_UNKNOWN: "SECRET_PLACEHOLDER_UNKNOWN",
|
|
16645
16646
|
SCREENSHOT_DIFF_INPUT_INVALID: "SCREENSHOT_DIFF_INPUT_INVALID",
|
|
16646
16647
|
BOOT_DEVICE_TARGET_SELECTION_INVALID: "BOOT_DEVICE_TARGET_SELECTION_INVALID",
|
|
@@ -19973,7 +19974,7 @@ async function startMcpServer(options) {
|
|
|
19973
19974
|
...content,
|
|
19974
19975
|
{
|
|
19975
19976
|
type: "text",
|
|
19976
|
-
text: "Auto-screenshot skipped: the input contains a {{secret:\u2026}} placeholder, and a screenshot of this screen could reveal the typed secret. Submit or navigate away
|
|
19977
|
+
text: "Auto-screenshot skipped: the input contains a {{secret:\u2026}} placeholder, and a screenshot of this screen could reveal the typed secret. The secret is already typed \u2014 do not send the typing step again, or the field will hold two copies of it. Submit or navigate away, then verify the resulting screen as usual. Only this call is covered: the next call is screenshotted normally, and captures the secret if the field is still on screen. To cover the submit as well, put the typing and the submit in ONE `run-sequence` the next time you type a secret."
|
|
19977
19978
|
}
|
|
19978
19979
|
];
|
|
19979
19980
|
} else if (autoScreenshotOn && udid && shouldAutoScreenshot(params.name)) {
|
package/dist/tool-server.cjs
CHANGED
|
@@ -758,6 +758,7 @@ var init_failure_codes = __esm({
|
|
|
758
758
|
CHROMIUM_ELECTRON_EXITED_BEFORE_READY: "CHROMIUM_ELECTRON_EXITED_BEFORE_READY",
|
|
759
759
|
KEYBOARD_KEY_UNSUPPORTED: "KEYBOARD_KEY_UNSUPPORTED",
|
|
760
760
|
KEYBOARD_CHARACTER_UNSUPPORTED: "KEYBOARD_CHARACTER_UNSUPPORTED",
|
|
761
|
+
KEYBOARD_TEXT_AND_KEY_COMBINED: "KEYBOARD_TEXT_AND_KEY_COMBINED",
|
|
761
762
|
SECRET_PLACEHOLDER_UNKNOWN: "SECRET_PLACEHOLDER_UNKNOWN",
|
|
762
763
|
SCREENSHOT_DIFF_INPUT_INVALID: "SCREENSHOT_DIFF_INPUT_INVALID",
|
|
763
764
|
BOOT_DEVICE_TARGET_SELECTION_INVALID: "BOOT_DEVICE_TARGET_SELECTION_INVALID",
|
|
@@ -122653,6 +122654,7 @@ Fails if the device backend is not reachable \u2014 the simulator-server for iOS
|
|
|
122653
122654
|
|
|
122654
122655
|
// ../tool-server/src/tools/keyboard/index.ts
|
|
122655
122656
|
init_zod();
|
|
122657
|
+
init_src();
|
|
122656
122658
|
|
|
122657
122659
|
// ../tool-server/src/utils/secrets.ts
|
|
122658
122660
|
init_src();
|
|
@@ -122813,21 +122815,6 @@ async function typeSimulatorServer(registry2, device, params) {
|
|
|
122813
122815
|
}
|
|
122814
122816
|
keysPressed++;
|
|
122815
122817
|
};
|
|
122816
|
-
let namedKeyCode;
|
|
122817
|
-
if (params.key) {
|
|
122818
|
-
const lower = params.key.toLowerCase();
|
|
122819
|
-
namedKeyCode = Object.hasOwn(NAMED_KEYS, lower) ? NAMED_KEYS[lower] : void 0;
|
|
122820
|
-
if (namedKeyCode == null) {
|
|
122821
|
-
throw new InvalidToolInputError(
|
|
122822
|
-
`Unknown key "${params.key}". Supported: ${Object.keys(NAMED_KEYS).join(", ")}`,
|
|
122823
|
-
{
|
|
122824
|
-
error_code: FAILURE_CODES.KEYBOARD_KEY_UNSUPPORTED,
|
|
122825
|
-
failure_stage: "keyboard_named_key_simulator",
|
|
122826
|
-
error_kind: "unsupported"
|
|
122827
|
-
}
|
|
122828
|
-
);
|
|
122829
|
-
}
|
|
122830
|
-
}
|
|
122831
122818
|
if (params.text) {
|
|
122832
122819
|
for (const char of params.text) {
|
|
122833
122820
|
const press = charToKeyPress(char);
|
|
@@ -122841,7 +122828,19 @@ async function typeSimulatorServer(registry2, device, params) {
|
|
|
122841
122828
|
await sleep8(delay2);
|
|
122842
122829
|
}
|
|
122843
122830
|
}
|
|
122844
|
-
if (
|
|
122831
|
+
if (params.key) {
|
|
122832
|
+
const lower = params.key.toLowerCase();
|
|
122833
|
+
const namedKeyCode = Object.hasOwn(NAMED_KEYS, lower) ? NAMED_KEYS[lower] : void 0;
|
|
122834
|
+
if (namedKeyCode == null) {
|
|
122835
|
+
throw new InvalidToolInputError(
|
|
122836
|
+
`Unknown key "${params.key}". Supported: ${Object.keys(NAMED_KEYS).join(", ")}`,
|
|
122837
|
+
{
|
|
122838
|
+
error_code: FAILURE_CODES.KEYBOARD_KEY_UNSUPPORTED,
|
|
122839
|
+
failure_stage: "keyboard_named_key_simulator",
|
|
122840
|
+
error_kind: "unsupported"
|
|
122841
|
+
}
|
|
122842
|
+
);
|
|
122843
|
+
}
|
|
122845
122844
|
await pressKeyCode(namedKeyCode);
|
|
122846
122845
|
}
|
|
122847
122846
|
return { typed: params.text ?? params.key ?? "", keys: keysPressed };
|
|
@@ -122890,15 +122889,14 @@ function makeIosRemoteImpl(registry2) {
|
|
|
122890
122889
|
init_adb();
|
|
122891
122890
|
async function typeAndroidPhone(device, params) {
|
|
122892
122891
|
let keysPressed = 0;
|
|
122893
|
-
if (params.text) assertTypeableAndroidText(params.text);
|
|
122894
|
-
if (params.key) {
|
|
122895
|
-
await injectAndroidNamedKey(device.id, params.key);
|
|
122896
|
-
keysPressed++;
|
|
122897
|
-
}
|
|
122898
122892
|
if (params.text) {
|
|
122899
122893
|
await injectAndroidText(device.id, params.text);
|
|
122900
122894
|
keysPressed += params.text.length;
|
|
122901
122895
|
}
|
|
122896
|
+
if (params.key) {
|
|
122897
|
+
await injectAndroidNamedKey(device.id, params.key);
|
|
122898
|
+
keysPressed++;
|
|
122899
|
+
}
|
|
122902
122900
|
return { typed: params.text ?? params.key ?? "", keys: keysPressed };
|
|
122903
122901
|
}
|
|
122904
122902
|
function makeAndroidImpl(registry2) {
|
|
@@ -122987,21 +122985,6 @@ var sleep9 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
122987
122985
|
async function runChromium(api, params) {
|
|
122988
122986
|
const delay2 = params.delayMs ?? 50;
|
|
122989
122987
|
let keysPressed = 0;
|
|
122990
|
-
let named;
|
|
122991
|
-
if (params.key) {
|
|
122992
|
-
const lower = params.key.toLowerCase();
|
|
122993
|
-
named = Object.hasOwn(CHROMIUM_NAMED_KEYS, lower) ? CHROMIUM_NAMED_KEYS[lower] : void 0;
|
|
122994
|
-
if (!named) {
|
|
122995
|
-
throw new InvalidToolInputError(
|
|
122996
|
-
`Unknown key "${params.key}". Supported: ${Object.keys(CHROMIUM_NAMED_KEYS).join(", ")}`,
|
|
122997
|
-
{
|
|
122998
|
-
error_code: FAILURE_CODES.KEYBOARD_KEY_UNSUPPORTED,
|
|
122999
|
-
failure_stage: "keyboard_named_key_chromium",
|
|
123000
|
-
error_kind: "unsupported"
|
|
123001
|
-
}
|
|
123002
|
-
);
|
|
123003
|
-
}
|
|
123004
|
-
}
|
|
123005
122988
|
if (params.text) {
|
|
123006
122989
|
for (const char of params.text) {
|
|
123007
122990
|
const desc = charToChromiumKey(char);
|
|
@@ -123029,7 +123012,19 @@ async function runChromium(api, params) {
|
|
|
123029
123012
|
await sleep9(delay2);
|
|
123030
123013
|
}
|
|
123031
123014
|
}
|
|
123032
|
-
if (
|
|
123015
|
+
if (params.key) {
|
|
123016
|
+
const lower = params.key.toLowerCase();
|
|
123017
|
+
const named = Object.hasOwn(CHROMIUM_NAMED_KEYS, lower) ? CHROMIUM_NAMED_KEYS[lower] : void 0;
|
|
123018
|
+
if (!named) {
|
|
123019
|
+
throw new InvalidToolInputError(
|
|
123020
|
+
`Unknown key "${params.key}". Supported: ${Object.keys(CHROMIUM_NAMED_KEYS).join(", ")}`,
|
|
123021
|
+
{
|
|
123022
|
+
error_code: FAILURE_CODES.KEYBOARD_KEY_UNSUPPORTED,
|
|
123023
|
+
failure_stage: "keyboard_named_key_chromium",
|
|
123024
|
+
error_kind: "unsupported"
|
|
123025
|
+
}
|
|
123026
|
+
);
|
|
123027
|
+
}
|
|
123033
123028
|
await api.dispatchKeyEvent({
|
|
123034
123029
|
type: "keyDown",
|
|
123035
123030
|
key: named.key,
|
|
@@ -123128,7 +123123,7 @@ async function injectViaInputd(subcommands) {
|
|
|
123128
123123
|
async function injectVegaButtons(buttons) {
|
|
123129
123124
|
await injectViaInputd(remoteButtonsToKeycodes(buttons).map((code) => `button_press ${code}`));
|
|
123130
123125
|
}
|
|
123131
|
-
function
|
|
123126
|
+
async function injectVegaNamedKey(name) {
|
|
123132
123127
|
const lower = name.toLowerCase();
|
|
123133
123128
|
const code = Object.hasOwn(NAMED_KEYCODES, lower) ? NAMED_KEYCODES[lower] : void 0;
|
|
123134
123129
|
if (!code) {
|
|
@@ -123141,10 +123136,7 @@ function resolveVegaNamedKeycode(name) {
|
|
|
123141
123136
|
}
|
|
123142
123137
|
);
|
|
123143
123138
|
}
|
|
123144
|
-
|
|
123145
|
-
}
|
|
123146
|
-
async function injectVegaNamedKey(name) {
|
|
123147
|
-
await injectViaInputd([`button_press ${resolveVegaNamedKeycode(name)}`]);
|
|
123139
|
+
await injectViaInputd([`button_press ${code}`]);
|
|
123148
123140
|
}
|
|
123149
123141
|
async function injectVegaText(text) {
|
|
123150
123142
|
if (/[\n\r]/.test(text)) {
|
|
@@ -123160,7 +123152,6 @@ async function injectVegaText(text) {
|
|
|
123160
123152
|
// ../tool-server/src/tools/keyboard/platforms/vega.ts
|
|
123161
123153
|
async function runVega2(params) {
|
|
123162
123154
|
let keysPressed = 0;
|
|
123163
|
-
if (params.key) resolveVegaNamedKeycode(params.key);
|
|
123164
123155
|
if (params.text) {
|
|
123165
123156
|
await injectVegaText(params.text);
|
|
123166
123157
|
keysPressed += [...params.text].length;
|
|
@@ -123182,10 +123173,10 @@ var zodSchema24 = external_exports.object({
|
|
|
123182
123173
|
"Target device id from `list-devices` (iOS UDID, Android serial, Vega serial, or Chromium id)."
|
|
123183
123174
|
),
|
|
123184
123175
|
text: external_exports.string().optional().describe(
|
|
123185
|
-
'Text to type character by character. Handles uppercase and common punctuation. To type a credential without its plaintext ever entering your context, use a secret placeholder: `{{secret:<NAME>}}` \u2014 e.g. text: "{{secret:APP_PASSWORD}}". The value is resolved on the machine running the tool-server, from the first source that defines the name: the `ARGENT_SECRET_<NAME>` environment variable, `.argent/secrets.env` in the project, the project\'s `.env.local` / `.env` (only their `ARGENT_SECRET_`-prefixed keys), then `~/.argent/secrets.env`. Nothing else on the host is reachable. Placeholders can be embedded in longer text and are never echoed back resolved. If the secret you need is not set, the failure lists the available names and every source it looked in \u2014 ask the user to add it to one of them (a secrets file applies immediately; an env var needs a restart), NEVER ask the user to paste the secret value into the conversation.'
|
|
123176
|
+
'Text to type character by character. Cannot be combined with `key` in one call \u2014 one call per action; to type and then press a key, put two `keyboard` steps in one `run-sequence`. Handles uppercase and common punctuation. To type a credential without its plaintext ever entering your context, use a secret placeholder: `{{secret:<NAME>}}` \u2014 e.g. text: "{{secret:APP_PASSWORD}}". The value is resolved on the machine running the tool-server, from the first source that defines the name: the `ARGENT_SECRET_<NAME>` environment variable, `.argent/secrets.env` in the project, the project\'s `.env.local` / `.env` (only their `ARGENT_SECRET_`-prefixed keys), then `~/.argent/secrets.env`. Nothing else on the host is reachable. Placeholders can be embedded in longer text and are never echoed back resolved. If the secret you need is not set, the failure lists the available names and every source it looked in \u2014 ask the user to add it to one of them (a secrets file applies immediately; an env var needs a restart), NEVER ask the user to paste the secret value into the conversation.'
|
|
123186
123177
|
),
|
|
123187
123178
|
key: external_exports.string().optional().describe(
|
|
123188
|
-
"Named key to press: enter, escape, backspace, tab, space, arrow-up, arrow-down, arrow-left, arrow-right, f1\u2013f12.
|
|
123179
|
+
"Named key to press: enter, escape, backspace, tab, space, arrow-up, arrow-down, arrow-left, arrow-right, f1\u2013f12. Cannot be combined with `text` in one call \u2014 one call per action; to type and then press a key, put two `keyboard` steps in one `run-sequence`. Not supported on TV targets \u2014 move focus with `tv-remote` (up/down/left/right) instead."
|
|
123189
123180
|
),
|
|
123190
123181
|
delayMs: external_exports.number().optional().describe(
|
|
123191
123182
|
"Delay in ms between key presses (default 50). Ignored on Android phones/tablets (typed via `adb input text`, which has no per-key cadence), on Vega (text/keys injected in a single shot), and on TV targets (Apple TV / Android TV type the whole string at the daemon's own cadence)."
|
|
@@ -123214,25 +123205,29 @@ function createKeyboardTool(registry2) {
|
|
|
123214
123205
|
// Treat both text and key as sensitive. `key` is an unrestricted string at
|
|
123215
123206
|
// this boundary, so a value must not reach the event log before execution
|
|
123216
123207
|
// validates whether it is a supported named key.
|
|
123208
|
+
//
|
|
123209
|
+
// `startedMsg` still words a text+key request because it renders BEFORE
|
|
123210
|
+
// `execute` rejects the combination — and likewise words `{ key: "" }` as
|
|
123211
|
+
// a key press, which `execute` also rejects. `completedMsg` runs only
|
|
123212
|
+
// after a call that succeeded, so it sees neither. Each formatter
|
|
123213
|
+
// therefore has to word a different set of shapes, and the empty
|
|
123214
|
+
// request — neither parameter, a documented no-op — reaches both.
|
|
123217
123215
|
startedMsg: ({ params }) => {
|
|
123218
123216
|
if (params.text === void 0) return "Pressing a key";
|
|
123219
123217
|
if (params.key === void 0) return "Entering text";
|
|
123220
123218
|
return "Entering text and pressing a key";
|
|
123221
123219
|
},
|
|
123222
|
-
completedMsg: ({ params }) =>
|
|
123223
|
-
if (params.text === void 0) return "Pressed a key";
|
|
123224
|
-
if (params.key === void 0) return "Entered text";
|
|
123225
|
-
return "Entered text and pressed a key";
|
|
123226
|
-
},
|
|
123220
|
+
completedMsg: ({ params }) => params.text === void 0 ? "Pressed a key" : "Entered text",
|
|
123227
123221
|
failedMsg: ({ failureSignal: failureSignal2 }) => `Failed to use keyboard: ${failureSignal2.error_code}`
|
|
123228
123222
|
},
|
|
123229
123223
|
description: `Type text or press special keys on the device (iOS simulator, Android emulator or device, Chromium app, Vega Virtual Device, or Apple TV / Android TV) using keyboard events.
|
|
123230
123224
|
Use when you need to enter text or trigger a named key such as enter, escape, or arrow keys. On Vega and Apple TV / Android TV, prefer the remote tools for D-pad navigation; use keyboard to type into a focused text field (e.g. a search or login box).
|
|
123231
|
-
Returns { typed: string, keys: number }. Fails if an unsupported key name is provided or the device's input backend is not reachable.
|
|
123232
|
-
|
|
123225
|
+
Returns { typed: string, keys: number }. Fails if text and key are both given in one call (rejected before anything is typed), if an unsupported key name is provided, or if the device's input backend is not reachable.
|
|
123226
|
+
A failure is not rolled back. An unsupported key name is always rejected before anything is sent. Un-typeable text is not: the iOS simulator and Chromium reject it mid-string and leave the characters before it in the field (Android, Vega and TV targets check the whole string up front). A transport failure partway also leaves the text already sent. On a retry, read the field's actual contents \u2014 do not assume it is unchanged.
|
|
123227
|
+
- text: types a string (supports uppercase, digits, common punctuation). To type a credential, use \`{{secret:<NAME>}}\` \u2014 resolved server-side from the \`ARGENT_SECRET_<NAME>\` env var or an argent secrets file (\`.argent/secrets.env\` in the project, \`~/.argent/secrets.env\`, or an \`ARGENT_SECRET_\`-prefixed key in the project's \`.env\`/\`.env.local\`), so the plaintext never enters agent context; the result echoes the placeholder, not the value, and the after-typing auto-screenshot is skipped. To submit after typing a secret, put both steps in ONE \`run-sequence\` \u2014 that keeps the skip covering the Enter, which a second bare \`keyboard\` call would not.
|
|
123233
123228
|
- key: presses a single named key (enter, escape, backspace, tab, arrow-up/down/left/right, f1\u2013f12) \u2014 NOT supported on TV targets; move focus with \`tv-remote\` instead.
|
|
123234
123229
|
On a TV target (runtimeKind 'tv') only \`text\` applies \u2014 focus a text field first (with \`tv-remote\`), then type into it (injected HID keyboard on Apple TV, \`adb input text\` on Android TV).
|
|
123235
|
-
|
|
123230
|
+
One call does one action: pass text OR key, never both. To type and then press a key, send two \`keyboard\` steps in one \`run-sequence\` \u2014 { text: "hello" } then { key: "enter" } \u2014 which also keeps it to a single round-trip.`,
|
|
123236
123231
|
zodSchema: zodSchema24,
|
|
123237
123232
|
capability: capability16,
|
|
123238
123233
|
searchHint: "type text keyboard input named key enter escape arrow tv vega fire tv search field hid leanback",
|
|
@@ -123241,6 +123236,46 @@ Provide text, key, or both \u2014 when both are given, the text is typed first a
|
|
|
123241
123236
|
// async and a tvOS udid must never resolve simulator-server.
|
|
123242
123237
|
services: () => ({}),
|
|
123243
123238
|
execute: async (services, params, options) => {
|
|
123239
|
+
if (params.text !== void 0 && params.key !== void 0) {
|
|
123240
|
+
throw new InvalidToolInputError(
|
|
123241
|
+
// Says what did NOT happen, so the caller retries instead of first
|
|
123242
|
+
// inspecting the field — and spells the retry out with a literal
|
|
123243
|
+
// example rather than an ellipsis the Android backend can't type.
|
|
123244
|
+
//
|
|
123245
|
+
// The TV caveat is carried statically rather than by probing the
|
|
123246
|
+
// target: this guard runs above the dispatch precisely so a combined
|
|
123247
|
+
// request reaches no device, and distinguishing a TV kind is an async
|
|
123248
|
+
// probe. Without it the prescribed `{ key: "enter" }` is a retry that
|
|
123249
|
+
// cannot succeed on a TV, where `key` is rejected outright
|
|
123250
|
+
// (platforms/tv.ts) — which is the diagnosis this guard would
|
|
123251
|
+
// otherwise pre-empt.
|
|
123252
|
+
'keyboard takes `text` or `key`, not both \u2014 nothing was typed. To type and then press a key, send two `keyboard` steps in one `run-sequence`: { text: "hello" } followed by { key: "enter" }. On a TV target (Apple TV / Android TV) `key` is not supported at all \u2014 type with `text` and move focus with `tv-remote` (up/down/left/right/select).' + // The one-`run-sequence` form and two bare calls are NOT equivalent
|
|
123253
|
+
// once the text carries a placeholder, and this message is where an
|
|
123254
|
+
// agent converts a combined secret call — the tool description's
|
|
123255
|
+
// caveat is read long before that moment, if at all. The check is
|
|
123256
|
+
// syntactic (the same `.includes` flow-utils.ts uses), so the guard
|
|
123257
|
+
// still resolves nothing.
|
|
123258
|
+
(params.text.includes(SECRET_PLACEHOLDER_MARKER) ? " This `text` carries a `" + SECRET_PLACEHOLDER_MARKER + '...}}` placeholder, so keep both steps in that ONE `run-sequence` rather than splitting them into two bare calls: the auto-screenshot skip is decided per tool call from the whole request, and a separate { key: "enter" } call carries no placeholder \u2014 its screenshot is taken after the key lands and can capture the still-visible secret.' : ""),
|
|
123259
|
+
{
|
|
123260
|
+
error_code: FAILURE_CODES.KEYBOARD_TEXT_AND_KEY_COMBINED,
|
|
123261
|
+
failure_stage: "keyboard_text_and_key_combined"
|
|
123262
|
+
}
|
|
123263
|
+
);
|
|
123264
|
+
}
|
|
123265
|
+
if (params.key === "") {
|
|
123266
|
+
throw new InvalidToolInputError(
|
|
123267
|
+
// Names the omission as the alternative, because a caller that sent an
|
|
123268
|
+
// empty string usually built the value from something absent.
|
|
123269
|
+
"`key` is an empty string, which names no key \u2014 nothing was pressed. Pass a named key (enter, escape, backspace, tab, space, arrow-up, arrow-down, arrow-left, arrow-right, f1\u2013f12), or omit `key` if there is nothing to press.",
|
|
123270
|
+
{
|
|
123271
|
+
// The same code an unknown name gets, because that is what this is:
|
|
123272
|
+
// one telemetry bucket for every unusable `key` value.
|
|
123273
|
+
error_code: FAILURE_CODES.KEYBOARD_KEY_UNSUPPORTED,
|
|
123274
|
+
failure_stage: "keyboard_named_key_empty",
|
|
123275
|
+
error_kind: "unsupported"
|
|
123276
|
+
}
|
|
123277
|
+
);
|
|
123278
|
+
}
|
|
123244
123279
|
if (params.text === void 0) return dispatch(services, params, options);
|
|
123245
123280
|
const { text, secrets } = resolveSecretPlaceholders(params.text);
|
|
123246
123281
|
if (secrets.length === 0) return dispatch(services, params, options);
|
|
@@ -130065,7 +130100,8 @@ function createRunSequenceTool(registry2) {
|
|
|
130065
130100
|
Use when you need sequential actions and do NOT need to observe the screen between them
|
|
130066
130101
|
(e.g. scrolling multiple times, typing then pressing enter, rotating back and forth).
|
|
130067
130102
|
Returns { completed, total, steps } with per-step results. Fails if an unrecognised tool name is used in a step (error returned at that step, execution stops).
|
|
130068
|
-
|
|
130103
|
+
One screenshot is captured automatically after the whole sequence (not per step) \u2014 call screenshot separately only for a baseline BEFORE it, or to observe an intermediate step.
|
|
130104
|
+
That single capture is also why a secret belongs in this call rather than in two bare ones: the skip is decided from the whole request, so a \`{{secret:...}}\` in any step suppresses the capture that would otherwise follow the submit.
|
|
130069
130105
|
|
|
130070
130106
|
ONLY use this when every step is known in advance. If any step depends on the
|
|
130071
130107
|
result of a previous one (e.g. tapping a menu item that only appears after
|
|
@@ -130081,7 +130117,7 @@ Allowed tools and their args (udid is auto-injected, do NOT include it in args):
|
|
|
130081
130117
|
gesture-pinch: { centerX: number, centerY: number, startDistance: number, endDistance: number, endCenterX?: number, endCenterY?: number, angle?: number, durationMs?: number } [ios/android]
|
|
130082
130118
|
gesture-rotate: { centerX: number, centerY: number, radius?: number, radiusX?: number, radiusY?: number, startAngle: number, endAngle: number, durationMs?: number } [ios/android]
|
|
130083
130119
|
button: { button: "home"|"back"|"power"|"volumeUp"|"volumeDown"|"appSwitch"|"actionButton" } [ios/android]
|
|
130084
|
-
keyboard: { text?: string, key?: string, delayMs?: number } (key
|
|
130120
|
+
keyboard: { text?: string, key?: string, delayMs?: number } (text OR key per step, never both; TV: text only) [ios/android/chromium/vega/tv]
|
|
130085
130121
|
text supports {{secret:<NAME>}} placeholders, resolved server-side from ARGENT_SECRET_<NAME> env vars or an argent secrets file \u2014 credentials never enter agent context
|
|
130086
130122
|
rotate: { orientation: "Portrait"|"LandscapeLeft"|"LandscapeRight"|"PortraitUpsideDown" } [ios/android]
|
|
130087
130123
|
shake: { count?: number } [ios sim/android emu]
|
|
@@ -130096,9 +130132,10 @@ Example \u2014 scroll down three times (use gesture-scroll with positive deltaY
|
|
|
130096
130132
|
{ "tool": "gesture-swipe", "args": { "fromX": 0.5, "fromY": 0.7, "toX": 0.5, "toY": 0.3 } }
|
|
130097
130133
|
]}
|
|
130098
130134
|
|
|
130099
|
-
Example \u2014 type text and submit (
|
|
130135
|
+
Example \u2014 type text and submit (two keyboard steps; one call cannot carry both):
|
|
130100
130136
|
{ "udid": "<UDID>", "steps": [
|
|
130101
|
-
{ "tool": "keyboard", "args": { "text": "hello world"
|
|
130137
|
+
{ "tool": "keyboard", "args": { "text": "hello world" } },
|
|
130138
|
+
{ "tool": "keyboard", "args": { "key": "enter" } }
|
|
130102
130139
|
]}
|
|
130103
130140
|
|
|
130104
130141
|
Example \u2014 TV: move focus right twice then activate (one tv-remote step with a path is cheaper):
|
|
@@ -150723,6 +150760,14 @@ async function resolveOutputDir(params, options) {
|
|
|
150723
150760
|
if (params.outputDir && (probe3 === void 0 || probe3.presentOnHost)) {
|
|
150724
150761
|
return params.outputDir;
|
|
150725
150762
|
}
|
|
150763
|
+
if (params.outputDir) {
|
|
150764
|
+
try {
|
|
150765
|
+
await import_promises13.default.mkdir(params.outputDir);
|
|
150766
|
+
return params.outputDir;
|
|
150767
|
+
} catch (err) {
|
|
150768
|
+
if (err.code === "EEXIST") return params.outputDir;
|
|
150769
|
+
}
|
|
150770
|
+
}
|
|
150726
150771
|
const dir = import_path10.default.join(
|
|
150727
150772
|
import_node_os13.default.tmpdir(),
|
|
150728
150773
|
"argent-screenshot-diff",
|
package/package.json
CHANGED
|
@@ -110,7 +110,7 @@ An Android app that needs a non-launcher activity has no `launch:` form. Record
|
|
|
110
110
|
|
|
111
111
|
In a `scroll-to` map, put the selector under `target:`. The map supports `up`, `down`, `left`, and `right` directions. The default is `down`; set it explicitly to reach a target above the viewport or along a horizontal carousel. If the target is already visible, the step is a safe no-op. `tap`, `type`, and `long-press` do not auto-scroll. Add `scroll-to` when the target can be off-screen. Use `within` for a nested scroller.
|
|
112
112
|
|
|
113
|
-
`type` presses Enter unless `submit: false`. A polished focus tap plus keyboard call usually needs `submit: false`. Store external values as `{{secret:NAME}}`. The runner uses the first source that defines the name: environment `ARGENT_SECRET_NAME`; project `.argent/secrets.env`; project `.env.local`, then `.env`; then `~/.argent/secrets.env`. The two `secrets.env` files accept the bare `NAME`, but the shared dotenv files expose only `ARGENT_SECRET_`-prefixed keys, so a bare `NAME=…` in `.env` or `.env.local` stays unresolved. The runner redacts every resolved value, so do not use a placeholder for content a report must show.
|
|
113
|
+
`type` presses Enter in a second `keyboard` call unless `submit: false`. A polished focus tap plus one text-only `keyboard` call usually needs `submit: false`. Store external values as `{{secret:NAME}}`. The runner uses the first source that defines the name: environment `ARGENT_SECRET_NAME`; project `.argent/secrets.env`; project `.env.local`, then `.env`; then `~/.argent/secrets.env`. The two `secrets.env` files accept the bare `NAME`, but the shared dotenv files expose only `ARGENT_SECRET_`-prefixed keys, so a bare `NAME=…` in `.env` or `.env.local` stays unresolved. The runner redacts every resolved value, so do not use a placeholder for content a report must show.
|
|
114
114
|
|
|
115
115
|
A **selector-less gesture** — a coordinate `tap`/`long-press`, or a `pinch`/`rotate` with no `on:` — resolves no frame, so a tree source it cannot read does not fail it. It settles best effort, dispatches anyway, and the step **passes carrying a warning** that quotes the source's own error. That green says the gesture was sent, not that it landed: one aimed at a moving element can miss it entirely. Restore the tree source, usually by relaunching the app so the instrumentation loads. Accept the warning only where the app serves no tree at all, and put an explicit `wait:` before a gesture that follows a transition. The first such gesture proves the outage and later ones spend that verdict without paying the settle window again. A tree read that comes back, or a relaunch, retires that verdict — which only makes the next gesture pay a fresh window, and it warns again if the source is still down.
|
|
116
116
|
|
|
@@ -111,7 +111,7 @@ Never tap the on-screen keyboard through the recorder. Some platforms expose it
|
|
|
111
111
|
|
|
112
112
|
### Typing
|
|
113
113
|
|
|
114
|
-
Record the focus tap, then record `keyboard`. Verify the complete value with `describe` or an app validation marker.
|
|
114
|
+
Record the focus tap, then record `keyboard` with `text`. A `keyboard` call carries `text` or `key`, never both. To submit, record a second `keyboard` step with `key: "enter"`. Verify the complete value with `describe` or an app validation marker.
|
|
115
115
|
|
|
116
116
|
**Never `describe` or `screenshot` a non-secure field you just filled from `{{secret:…}}`.** Only a password field is redacted; a plain text input hands the resolved value back into your context, and an API key or token typed into one is the ordinary case. Submit or navigate away first, then verify the resulting screen.
|
|
117
117
|
|
|
@@ -144,16 +144,16 @@ Stop immediately. Restore the last valid screen with direct MCP calls, not `flow
|
|
|
144
144
|
|
|
145
145
|
Call `flow-finish-recording`, then read the saved YAML. Apply only meaning-preserving conversions:
|
|
146
146
|
|
|
147
|
-
| Recorded form
|
|
148
|
-
|
|
|
149
|
-
| focus tap + `tool: keyboard`
|
|
150
|
-
| keyboard
|
|
151
|
-
| `tool: await-ui-element`
|
|
152
|
-
| element-seeking movement
|
|
153
|
-
| coordinate tap or long-press
|
|
154
|
-
| `tool: gesture-pinch`
|
|
155
|
-
| `tool: gesture-rotate`
|
|
156
|
-
| sibling `tool: flow-execute`
|
|
147
|
+
| Recorded form | Finished form |
|
|
148
|
+
| ----------------------------------------- | ------------------------------------------------------------------ |
|
|
149
|
+
| focus tap + `tool: keyboard` | `type:` |
|
|
150
|
+
| text `keyboard` + `key: enter` `keyboard` | submitted `type:` without Enter in its text |
|
|
151
|
+
| `tool: await-ui-element` | `await:` or `assert:` |
|
|
152
|
+
| element-seeking movement | `scroll-to:` |
|
|
153
|
+
| coordinate tap or long-press | strict selector after the fallback gate |
|
|
154
|
+
| `tool: gesture-pinch` | selector-based `pinch:` with `scale = endDistance / startDistance` |
|
|
155
|
+
| `tool: gesture-rotate` | selector-based `rotate:` with `by = endAngle - startAngle` |
|
|
156
|
+
| sibling `tool: flow-execute` | recorder-captured `run:` |
|
|
157
157
|
|
|
158
158
|
Only these unrecorded insertions are allowed, at states observed live:
|
|
159
159
|
|
|
@@ -59,7 +59,7 @@ Apple system apps cannot load the instrumentation, and nothing in the launch pat
|
|
|
59
59
|
|
|
60
60
|
- Raw `tool: await-ui-element` accessibility checks.
|
|
61
61
|
- Point taps or long-presses derived from `describe`, each named by an echo.
|
|
62
|
-
- A point focus tap plus raw keyboard with `delayMs: 500
|
|
62
|
+
- A point focus tap plus a raw text-only `keyboard` with `delayMs: 500`, and a second raw `keyboard` with `key: "enter"` to submit.
|
|
63
63
|
- Raw swipes with `settle: true` because `scroll-to` needs the missing flow tree. Momentum-free scrolling keeps later coordinate taps valid.
|
|
64
64
|
|
|
65
65
|
Every point tap or long-press in such a flow passes **carrying a warning**. The app loads no instrumentation, so every tree read fails and each [selector-less gesture](flow-yaml.md#directives) dispatches unsettled. Nothing here repairs it. Accept the warnings, read each green as "the gesture was sent, not that it landed", and put an explicit `wait:` or a raw `tool: await-ui-element` before a gesture that follows a transition. Raw `tool:` steps never take that settle, so they never warn.
|
|
@@ -72,7 +72,7 @@ Common schemes: `messages://`, `settings://`, `maps://?q=<query>`, `tel://<numbe
|
|
|
72
72
|
| Rotation | `gesture-rotate` | Two-finger rotation with auto-interpolation |
|
|
73
73
|
| Custom gesture | `gesture-custom` | Arbitrary touch sequences, optional interpolation |
|
|
74
74
|
| Hardware key | `button` | Home, back, power, volume, appSwitch, actionButton |
|
|
75
|
-
| Type text | `keyboard` | Every platform.
|
|
75
|
+
| Type text | `keyboard` | Every platform. Text or one named key per call, never both |
|
|
76
76
|
| Rotate device | `rotate` | Orientation changes |
|
|
77
77
|
| Shake device | `shake` | Shake handlers (sim/emu only), Undo-typing prompt, RN dev menu |
|
|
78
78
|
| Wait for UI | `await-ui-element` | Block until an element is visible/hidden/exists/contains text |
|
|
@@ -168,15 +168,17 @@ Values: `home`, `back`, `power`, `volumeUp`, `volumeDown`, `appSwitch`, `actionB
|
|
|
168
168
|
### keyboard — Type text or press special keys
|
|
169
169
|
|
|
170
170
|
```json
|
|
171
|
-
{ "udid": "<UDID>", "text": "search query"
|
|
171
|
+
{ "udid": "<UDID>", "text": "search query" }
|
|
172
172
|
```
|
|
173
173
|
|
|
174
|
+
One call does one action. `text` and `key` are mutually exclusive, and a call that carries both is rejected with nothing typed. To type and then submit, send two `keyboard` steps in one `run-sequence` (§ 8) — `{ "text": "search query" }`, then `{ "key": "enter" }`. Two separate calls do the same work, but cost an extra round-trip.
|
|
175
|
+
|
|
174
176
|
Special keys: `enter`, `escape`, `backspace`, `tab`, `space`, `arrow-up`, `arrow-down`, `arrow-left`, `arrow-right`, `f1`–`f12`. Optional: `"delayMs": 100` between keystrokes (default 50ms) — applies to the iOS simulator and Chromium; it is ignored on Android phones/tablets (typed via `adb input text`, no per-key cadence), on Vega, and on TV targets.
|
|
175
177
|
|
|
176
178
|
**Typing secrets.** To enter a credential without its plaintext ever entering your context, transcript, or logs, use a secret placeholder in `text` (works in `keyboard`, `paste`, `run-sequence` keyboard steps, and flow `type` steps):
|
|
177
179
|
|
|
178
180
|
```json
|
|
179
|
-
{ "udid": "<UDID>", "text": "{{secret:APP_PASSWORD}}"
|
|
181
|
+
{ "udid": "<UDID>", "text": "{{secret:APP_PASSWORD}}" }
|
|
180
182
|
```
|
|
181
183
|
|
|
182
184
|
The placeholder is resolved on the machine running the tool-server, from the first of these that defines the name:
|
|
@@ -191,7 +193,7 @@ The placeholder is resolved on the machine running the tool-server, from the fir
|
|
|
191
193
|
Rules:
|
|
192
194
|
|
|
193
195
|
- The result echoes the placeholder, never the value. An unknown name fails with the list of available secret _names_ and every source it looked in, with paths — read that list before asking the user anything.
|
|
194
|
-
- The auto-screenshot after the call is skipped so the typed value cannot re-enter your context as pixels. Do **not** `describe` or `screenshot` a non-secure field you just filled with a secret — submit or navigate away first, then verify the resulting screen.
|
|
196
|
+
- The auto-screenshot after the call is skipped so the typed value cannot re-enter your context as pixels. Do **not** `describe` or `screenshot` a non-secure field you just filled with a secret — submit or navigate away first, then verify the resulting screen. To submit, put the text step and the Enter step in **one `run-sequence`**. The skip covers a whole batch that contains the placeholder, but a second bare `keyboard` call gets its own screenshot of the filled field.
|
|
195
197
|
- Nothing outside those sources is reachable; never ask the user to paste a secret value into the conversation. Ask them to put it in a secrets file instead — a file edit applies to the next call, while an exported env var only reaches a tool-server started afterwards.
|
|
196
198
|
- The project sources are found by walking up from the tool-server's working directory. If a project file is not being picked up, the failure's source list shows the paths actually consulted; `~/.argent/secrets.env` needs no project and always applies.
|
|
197
199
|
|
|
@@ -316,7 +318,7 @@ Scroll down three times:
|
|
|
316
318
|
}
|
|
317
319
|
```
|
|
318
320
|
|
|
319
|
-
Type into a focused field and submit:
|
|
321
|
+
Type into a focused field and submit. This is the only way to mix text and a key, because one `keyboard` call cannot carry both:
|
|
320
322
|
|
|
321
323
|
```json
|
|
322
324
|
{
|
|
@@ -21,7 +21,7 @@ description: Control and inspect TV apps via argent — Apple TV (tvOS), Android
|
|
|
21
21
|
|
|
22
22
|
- `describe {udid}` — focus view: the focused / `[selected]` element + focusable elements with labels and normalized frames. The discovery tool — call before and after navigating. Empty tree → see the per-platform notes.
|
|
23
23
|
- `tv-remote {udid, button}` — D-pad / remote. `button` is one key **or a whole path** (run in one call). Keys: `up`/`down`/`left`/`right`, `select`, `back`, `menu`, `home`, `playPause`, plus media keys `rewind`/`fastForward`/`next`/`previous`/`volumeUp`/`volumeDown`/`mute`. Single: `{button:"down"}`; repeat: `{button:"down", repeat:3}`; path: `{button:["up","right","select"]}`.
|
|
24
|
-
- `keyboard {udid, text}` — type into the focused field (focus it with `tv-remote` first). Named `key` presses (e.g. `{key:"enter"}`) work on Vega; on Apple TV / Android TV move focus with `tv-remote` instead.
|
|
24
|
+
- `keyboard {udid, text}` — type into the focused field (focus it with `tv-remote` first). One call carries `text` or `key`, never both — to type and then press a key, send two `keyboard` steps in one `run-sequence`. Named `key` presses (e.g. `{key:"enter"}`) work on Vega; on Apple TV / Android TV move focus with `tv-remote` instead.
|
|
25
25
|
- `launch-app` / `restart-app` / `reinstall-app {udid, bundleId}` — `bundleId` from the app manifest. Vega `reinstall-app` takes `appPath` = a `.vpkg`.
|
|
26
26
|
- `screenshot {udid, scale?}` — Apple TV via `xcrun simctl io` (downscaled); Android TV / Vega host-side via `adb` / `screencap`.
|
|
27
27
|
|