@trusty-squire/mcp 1.1.13-rc.8 → 1.1.13
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/README.md +139 -62
- package/assets/login/vnc-input.js +9 -0
- package/assets/login/vnc.html +214 -202
- package/assets/screencaps/connect-walkthrough.png +0 -0
- package/assets/screencaps/connect-walkthrough.svg +1 -1
- package/dist/api-client.d.ts +26 -3
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +19 -31
- package/dist/api-client.js.map +1 -1
- package/dist/bin.js +2 -3
- package/dist/bin.js.map +1 -1
- package/dist/bot/browser.d.ts +134 -77
- package/dist/bot/browser.d.ts.map +1 -1
- package/dist/bot/browser.js +1788 -1543
- package/dist/bot/browser.js.map +1 -1
- package/dist/bot/compact-observation-v2.d.ts +167 -0
- package/dist/bot/compact-observation-v2.d.ts.map +1 -0
- package/dist/bot/compact-observation-v2.js +1038 -0
- package/dist/bot/compact-observation-v2.js.map +1 -0
- package/dist/bot/credential-shape.d.ts +2 -0
- package/dist/bot/credential-shape.d.ts.map +1 -1
- package/dist/bot/credential-shape.js +21 -8
- package/dist/bot/credential-shape.js.map +1 -1
- package/dist/bot/element-fingerprint.d.ts +9 -0
- package/dist/bot/element-fingerprint.d.ts.map +1 -0
- package/dist/bot/element-fingerprint.js +147 -0
- package/dist/bot/element-fingerprint.js.map +1 -0
- package/dist/bot/email-verification.d.ts +7 -1
- package/dist/bot/email-verification.d.ts.map +1 -1
- package/dist/bot/email-verification.js +48 -14
- package/dist/bot/email-verification.js.map +1 -1
- package/dist/bot/google-login.d.ts +28 -52
- package/dist/bot/google-login.d.ts.map +1 -1
- package/dist/bot/google-login.js +246 -1119
- package/dist/bot/google-login.js.map +1 -1
- package/dist/bot/install-completion.d.ts +1 -2
- package/dist/bot/install-completion.d.ts.map +1 -1
- package/dist/bot/install-completion.js +1 -2
- package/dist/bot/install-completion.js.map +1 -1
- package/dist/bot/login-state.d.ts +13 -7
- package/dist/bot/login-state.d.ts.map +1 -1
- package/dist/bot/login-state.js +50 -114
- package/dist/bot/login-state.js.map +1 -1
- package/dist/bot/oauth-providers.d.ts +1 -1
- package/dist/bot/oauth-providers.d.ts.map +1 -1
- package/dist/bot/oauth-providers.js +3 -3
- package/dist/bot/oauth-providers.js.map +1 -1
- package/dist/bot/oauth-scope.d.ts +4 -0
- package/dist/bot/oauth-scope.d.ts.map +1 -0
- package/dist/bot/oauth-scope.js +58 -0
- package/dist/bot/oauth-scope.js.map +1 -0
- package/dist/bot/operator-browser-watchdog.d.ts +127 -0
- package/dist/bot/operator-browser-watchdog.d.ts.map +1 -0
- package/dist/bot/operator-browser-watchdog.js +438 -0
- package/dist/bot/operator-browser-watchdog.js.map +1 -0
- package/dist/bot/owner-process-reaper-worker.d.ts +2 -0
- package/dist/bot/owner-process-reaper-worker.d.ts.map +1 -0
- package/dist/bot/owner-process-reaper-worker.js +17 -0
- package/dist/bot/owner-process-reaper-worker.js.map +1 -0
- package/dist/bot/owner-process-reaper.d.ts +103 -0
- package/dist/bot/owner-process-reaper.d.ts.map +1 -0
- package/dist/bot/owner-process-reaper.js +1058 -0
- package/dist/bot/owner-process-reaper.js.map +1 -0
- package/dist/bot/pay-operator.d.ts +13 -2
- package/dist/bot/pay-operator.d.ts.map +1 -1
- package/dist/bot/pay-operator.js +319 -123
- package/dist/bot/pay-operator.js.map +1 -1
- package/dist/bot/profile.d.ts +10 -1
- package/dist/bot/profile.d.ts.map +1 -1
- package/dist/bot/profile.js +137 -11
- package/dist/bot/profile.js.map +1 -1
- package/dist/bot/promote-to-skill.js +1 -1
- package/dist/bot/promote-to-skill.js.map +1 -1
- package/dist/bot/provision-session.d.ts +60 -164
- package/dist/bot/provision-session.d.ts.map +1 -1
- package/dist/bot/provision-session.js +2049 -1546
- package/dist/bot/provision-session.js.map +1 -1
- package/dist/bot/remote-login-display.d.ts +61 -0
- package/dist/bot/remote-login-display.d.ts.map +1 -0
- package/dist/bot/remote-login-display.js +765 -0
- package/dist/bot/remote-login-display.js.map +1 -0
- package/dist/bot/session/hosts.d.ts +8 -0
- package/dist/bot/session/hosts.d.ts.map +1 -0
- package/dist/bot/session/hosts.js +36 -0
- package/dist/bot/session/hosts.js.map +1 -0
- package/dist/bot/session/lifecycle.d.ts +56 -0
- package/dist/bot/session/lifecycle.d.ts.map +1 -0
- package/dist/bot/session/lifecycle.js +962 -0
- package/dist/bot/session/lifecycle.js.map +1 -0
- package/dist/bot/session/model.d.ts +175 -0
- package/dist/bot/session/model.d.ts.map +1 -0
- package/dist/bot/session/model.js +77 -0
- package/dist/bot/session/model.js.map +1 -0
- package/dist/install/cli.d.ts +29 -9
- package/dist/install/cli.d.ts.map +1 -1
- package/dist/install/cli.js +243 -322
- package/dist/install/cli.js.map +1 -1
- package/dist/install/interactive.d.ts +1 -0
- package/dist/install/interactive.d.ts.map +1 -1
- package/dist/install/interactive.js +19 -5
- package/dist/install/interactive.js.map +1 -1
- package/dist/install/ui.d.ts.map +1 -1
- package/dist/install/ui.js +3 -15
- package/dist/install/ui.js.map +1 -1
- package/dist/operator-env.js +1 -1
- package/dist/operator-env.js.map +1 -1
- package/dist/server-instance-registry.d.ts +103 -0
- package/dist/server-instance-registry.d.ts.map +1 -0
- package/dist/server-instance-registry.js +392 -0
- package/dist/server-instance-registry.js.map +1 -0
- package/dist/server.d.ts +15 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +164 -62
- package/dist/server.js.map +1 -1
- package/dist/session-guard.d.ts +28 -0
- package/dist/session-guard.d.ts.map +1 -0
- package/dist/session-guard.js +84 -0
- package/dist/session-guard.js.map +1 -0
- package/dist/session.d.ts +57 -13
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +246 -127
- package/dist/session.js.map +1 -1
- package/dist/tools/audit-log.d.ts +16 -2
- package/dist/tools/audit-log.d.ts.map +1 -1
- package/dist/tools/audit-log.js +223 -13
- package/dist/tools/audit-log.js.map +1 -1
- package/dist/tools/audit-rollup.d.ts +80 -0
- package/dist/tools/audit-rollup.d.ts.map +1 -0
- package/dist/tools/audit-rollup.js +0 -0
- package/dist/tools/audit-rollup.js.map +1 -0
- package/dist/tools/fetch-credential.d.ts +37 -0
- package/dist/tools/fetch-credential.d.ts.map +1 -0
- package/dist/tools/fetch-credential.js +153 -0
- package/dist/tools/fetch-credential.js.map +1 -0
- package/dist/tools/index.d.ts +3 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +7 -3
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/operate-pay.d.ts +4 -4
- package/dist/tools/operate-pay.d.ts.map +1 -1
- package/dist/tools/operate-pay.js +163 -96
- package/dist/tools/operate-pay.js.map +1 -1
- package/dist/tools/provision-drive.d.ts +43 -35
- package/dist/tools/provision-drive.d.ts.map +1 -1
- package/dist/tools/provision-drive.js +268 -164
- package/dist/tools/provision-drive.js.map +1 -1
- package/dist/tools/use-credential.d.ts.map +1 -1
- package/dist/tools/use-credential.js +3 -2
- package/dist/tools/use-credential.js.map +1 -1
- package/package.json +4 -6
- package/dist/bot/operator-direct-identity.d.ts +0 -26
- package/dist/bot/operator-direct-identity.d.ts.map +0 -1
- package/dist/bot/operator-direct-identity.js +0 -120
- package/dist/bot/operator-direct-identity.js.map +0 -1
- package/dist/bot/operator-profile-pool.d.ts +0 -97
- package/dist/bot/operator-profile-pool.d.ts.map +0 -1
- package/dist/bot/operator-profile-pool.js +0 -855
- package/dist/bot/operator-profile-pool.js.map +0 -1
- package/dist/bot/xvfb.d.ts +0 -13
- package/dist/bot/xvfb.d.ts.map +0 -1
- package/dist/bot/xvfb.js +0 -146
- package/dist/bot/xvfb.js.map +0 -1
|
@@ -6,51 +6,44 @@
|
|
|
6
6
|
// in place; the consent-at-install prompt is the remaining hardening.
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { constants, generateKeyPairSync, privateDecrypt } from "node:crypto";
|
|
9
|
-
import { startProvisionSession, observe, captureScreenshot, act, cartAdd, formSelectMany, TargetStaleError, extractCredentials, captchaGate, awaitVerification, finishProvisionSession, finishProvisionSessionWithPreparation, observedHostsForSession, currentProvisionUrl, stashSecretSlot, readSecretSlotValue, getSessionUserEmail, generatePassword, rememberRecipe, verifyActiveRecipePostcondition, verifySavedRecipePostcondition, captureAndPromoteSession, replayOperatorRecipe, emitProvisionMeasurement, checkoutShapeSignatureForSession, manualCardEntryBlockReason, } from "../bot/provision-session.js";
|
|
9
|
+
import { startProvisionSession, observe, captureScreenshot, observeQuery, act, cartAdd, cartClear, formSelectMany, TargetStaleError, extractCredentials, captchaGate, awaitVerification, finishProvisionSession, finishProvisionSessionWithPreparation, observedHostsForSession, currentProvisionUrl, stashSecretSlot, readSecretSlotValue, getSessionUserEmail, generatePassword, rememberRecipe, verifyActiveRecipePostcondition, verifySavedRecipePostcondition, captureAndPromoteSession, replayOperatorRecipe, emitProvisionMeasurement, checkoutShapeSignatureForSession, manualCardEntryBlockReason, } from "../bot/provision-session.js";
|
|
10
10
|
import { signSkillForPublish } from "../skill-cli/signing.js";
|
|
11
11
|
import { readRecipe, readRecipeForTask, readRecipeForCheckoutShape, readRecipeFromFile, renderOperatorRecipeHint, recipeEntryUrl, fillTemplate, operatorRecipeDomain, checkoutShapeKey, isCheckoutShapeKey, isSameRecipeDomain, OperatorVerbSchema, RecipeHoleSchema, PostconditionSchema, } from "../bot/operator-recipe.js";
|
|
12
12
|
import { isMaskedDisplay } from "../bot/credential-shape.js";
|
|
13
13
|
import { renderSkillHint, serviceSlugFromUrl } from "../bot/skill-hint.js";
|
|
14
14
|
import { clientFromEnv, generateProvisionId } from "../skill-registry-client.js";
|
|
15
15
|
import { openSessionStorage } from "../session.js";
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// deployments set consent_operator_inbox_otp=true.
|
|
16
|
+
import { servingAccountId } from "../session-guard.js";
|
|
17
|
+
// Read the install-time inbox-read preference. Inbox reads default on; an
|
|
18
|
+
// explicit false in the saved advanced configuration remains an opt-out.
|
|
20
19
|
async function readInboxConsent() {
|
|
21
20
|
try {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
const accountId = resolveAccountId();
|
|
22
|
+
if (accountId === undefined)
|
|
23
|
+
return true;
|
|
24
|
+
const data = await (await openSessionStorage()).read(accountId);
|
|
25
|
+
return data?.consent_operator_inbox_otp !== false;
|
|
25
26
|
}
|
|
26
27
|
catch {
|
|
27
|
-
return
|
|
28
|
+
return true;
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
|
-
// The account
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
const data = await (await openSessionStorage()).read();
|
|
41
|
-
const id = data?.account_id;
|
|
42
|
-
return id !== undefined && id.length > 0 ? id : undefined;
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
return undefined;
|
|
46
|
-
}
|
|
31
|
+
// The account THIS server adopted, as published by its SessionGuard. Never
|
|
32
|
+
// re-resolve it here from TRUSTY_SQUIRE_ACCOUNT_ID or the store's
|
|
33
|
+
// current-account pointer: a server launched from a pre-pin config binds by
|
|
34
|
+
// fallback, and re-resolving after another account connects would hand this
|
|
35
|
+
// tool the wrong account — applying that account's consent setting and
|
|
36
|
+
// attributing registry reads/publishes to it. That is exactly the "acting as
|
|
37
|
+
// an account it never bound to" defect this work exists to remove.
|
|
38
|
+
function resolveAccountId() {
|
|
39
|
+
return servingAccountId();
|
|
47
40
|
}
|
|
48
41
|
// Best-effort: ask the registry for a known route for this service so the agent
|
|
49
42
|
// drives on rails instead of ad-hoc. Returns undefined on any miss (no skill,
|
|
50
43
|
// no registry configured, network error) — the agent just drives without it.
|
|
51
44
|
async function resolveRouteHint(serviceUrl) {
|
|
52
45
|
try {
|
|
53
|
-
const accountId =
|
|
46
|
+
const accountId = resolveAccountId();
|
|
54
47
|
if (accountId === undefined || accountId.length === 0)
|
|
55
48
|
return undefined;
|
|
56
49
|
const client = clientFromEnv(accountId);
|
|
@@ -90,7 +83,7 @@ async function autoPromoteProvision(sessionId) {
|
|
|
90
83
|
: "";
|
|
91
84
|
return `rejected:${promoted.error_kind ?? "unknown"}${detail}`;
|
|
92
85
|
}
|
|
93
|
-
const accountId =
|
|
86
|
+
const accountId = resolveAccountId();
|
|
94
87
|
if (accountId === undefined || accountId.length === 0)
|
|
95
88
|
return "produced:no_account";
|
|
96
89
|
const client = clientFromEnv(accountId);
|
|
@@ -126,7 +119,7 @@ async function autoPromoteProvision(sessionId) {
|
|
|
126
119
|
export async function publishRecipeToRegistry(file) {
|
|
127
120
|
try {
|
|
128
121
|
const recipe = await readRecipeFromFile(file);
|
|
129
|
-
const accountId =
|
|
122
|
+
const accountId = resolveAccountId();
|
|
130
123
|
if (accountId === undefined || accountId.length === 0)
|
|
131
124
|
return "skipped:no_account";
|
|
132
125
|
const client = clientFromEnv(accountId);
|
|
@@ -159,7 +152,7 @@ export async function resolveRecipeForTask(verb, serviceUrl) {
|
|
|
159
152
|
return await readRecipeForTask(verb, serviceUrl);
|
|
160
153
|
}
|
|
161
154
|
catch (localErr) {
|
|
162
|
-
const accountId =
|
|
155
|
+
const accountId = resolveAccountId();
|
|
163
156
|
if (accountId === undefined || accountId.length === 0)
|
|
164
157
|
throw localErr;
|
|
165
158
|
const client = clientFromEnv(accountId);
|
|
@@ -183,7 +176,7 @@ export async function resolveCheckoutLegRecipe(verb, signature) {
|
|
|
183
176
|
return await readRecipeForCheckoutShape(verb, signature);
|
|
184
177
|
}
|
|
185
178
|
catch {
|
|
186
|
-
const accountId =
|
|
179
|
+
const accountId = resolveAccountId();
|
|
187
180
|
if (accountId === undefined || accountId.length === 0)
|
|
188
181
|
return null;
|
|
189
182
|
const client = clientFromEnv(accountId);
|
|
@@ -278,15 +271,15 @@ const startSchema = z.object({
|
|
|
278
271
|
// Operate tasks that act AS the user (drive a gated app on an existing
|
|
279
272
|
// account) set this so start fails closed to a connect hand-back if no live
|
|
280
273
|
// Google session exists — rather than driving into a mid-task login wall.
|
|
281
|
-
require_live_identity: z.boolean().optional(),
|
|
282
274
|
});
|
|
283
|
-
const OBSERVE_DELTA_CONTRACT = "
|
|
275
|
+
const OBSERVE_DELTA_CONTRACT = "The following observation rules are V1-only (TRUSTY_SQUIRE_OBSERVE_V2=off or shadow). " +
|
|
276
|
+
"In V1 compact observations, elements are carried in `el_table`: a TAB-delimited table whose FIRST line is the " +
|
|
284
277
|
"header (tab-joined column names, a subset of ref,label,tag,role,type,value_len,checked,href,testId," +
|
|
285
278
|
"topmost,occluded_by, always starting ref,label,tag) and each following line is ONE element (tab-joined " +
|
|
286
279
|
"cells in header order). An empty cell = that field is absent for that element; value_len is a number, " +
|
|
287
280
|
"checked/topmost are true/false; a tab, newline, carriage-return or backslash inside a cell is " +
|
|
288
281
|
"backslash-escaped (\\t \\n \\r \\\\). el_table is absent when the emit has no element rows. " +
|
|
289
|
-
"stable refs remain reusable across observes while their controls still exist. " +
|
|
282
|
+
"In V1, stable refs remain reusable across observes while their controls still exist. " +
|
|
290
283
|
"The first observe, a URL change, or high churn returns delta:false as a full resync: discard the prior " +
|
|
291
284
|
"element map and rebuild it from this el_table (or from snapshot_file — the table may omit collapsed " +
|
|
292
285
|
"chrome links, which the file keeps); a delta:false with NO snapshot_file already has the complete, " +
|
|
@@ -295,17 +288,40 @@ const OBSERVE_DELTA_CONTRACT = "Compact observations carry their elements in `el
|
|
|
295
288
|
"in removed, and retain the remaining elements counted by unchanged. text_unchanged:true means reuse the " +
|
|
296
289
|
"prior text because text is empty. snapshot_file points to the complete current snapshot (all elements, " +
|
|
297
290
|
"with path). An empty delta (no el_table) means nothing changed, not an empty page. " +
|
|
298
|
-
'detail:"full" instead returns the legacy `elements` JSON array (every field), never el_table. ' +
|
|
291
|
+
'In V1, detail:"full" instead returns the legacy `elements` JSON array (every field), never el_table. ' +
|
|
299
292
|
"If a control you can see in `text`/the screenshot has NO row in el_table (a bare unlabeled clickable " +
|
|
300
293
|
'div — e.g. some SPA "Add To Cart" buttons), it has no ref: click it with operate_act click/js_click ' +
|
|
301
294
|
'target=`text="…"` or `css=…` (see operate_act). `click` respects actionability and throws if an overlay ' +
|
|
302
295
|
"intercepts; dismiss the overlay or deliberately use `js_click`, which directly dispatches through a " +
|
|
303
|
-
"transparent overlay. "
|
|
296
|
+
"transparent overlay. Under default compact-v2, only refs and @labels from the current action map are " +
|
|
297
|
+
"accepted. A ref is a durable element fingerprint: it stays valid across acts and benign re-renders on the same " +
|
|
298
|
+
"document, so one observation can drive several acts. On opaque `stale_ref`, call operate_observe and choose a " +
|
|
299
|
+
"new ref; do not retry the old one or use V1 replacement candidates or locator fallback. ";
|
|
300
|
+
const COMPACT_V2_CONTRACT = "When format is `compact-v2`, use its action map: session_id is the continuation handle; `url` is the live page URL; `stage` is a finite enum; " +
|
|
301
|
+
"semantic carries the page title and primary visible heading; safe_table rows use [ref,role,facts?], where role is " +
|
|
302
|
+
"b=button,l=link,t=textbox,s=select,c=checkbox,r=radio,tb=tab,m=menuitem,f=file. ref is an opaque durable " +
|
|
303
|
+
"element handle. facts is a pipe-delimited string: an optional first unkeyed segment is the row's @label alias, " +
|
|
304
|
+
"a slug of its short label that operate_act also accepts as a target. " +
|
|
305
|
+
"The label is followed by any present s=<state bitset>, a=<action>, " +
|
|
306
|
+
"f=<field>, q=<choice position>/<choice total>, and x=<frame> segments. Fact-only rows begin with a keyed segment. " +
|
|
307
|
+
"State bitset codes are c=checked,u=unchecked,d=disabled,r=required; frame codes are x=s for a same-origin child " +
|
|
308
|
+
"and x=x for a cross-origin child, while an omitted x means the main frame. Actions are search,close,next,previous,submit," +
|
|
309
|
+
"continue,login,signup,add_to_cart,view_cart,checkout,payment,destructive; fields are email,password,username,name,phone,search,address," +
|
|
310
|
+
"city,region,postal,country,date,quantity,promo,payment. Short labels are included for viewport-prioritized controls; " +
|
|
311
|
+
"labels are exactly what the page renders. The row form omits field " +
|
|
312
|
+
"values purely as a size budget: read a value off the page with operate_screenshot, or with an explicitly " +
|
|
313
|
+
"selected V1 session. For a named product/control from the task, " +
|
|
314
|
+
"call operate_observe_query with those task words; it returns matching actionable refs with labels " +
|
|
315
|
+
"and code-owned facts. Use overflow.next_cursor to page. `detail:full` keeps the V2 format while V2 is enabled; " +
|
|
316
|
+
"set TRUSTY_SQUIRE_OBSERVE_V2=off for the legacy format. A delta:true delta retains the preceding V2 table, then upserts tuple rows in safe_table, " +
|
|
317
|
+
"removes refs in removed, and updates stage or semantic only when either changed. Omitted semantic title/heading remains from the preceding V2 page. " +
|
|
318
|
+
"A delta with none of those fields means the view is unchanged. ";
|
|
304
319
|
export const provisionStartTool = {
|
|
305
320
|
name: "operate_start",
|
|
306
321
|
description: "Begin an interactive website task: opens a scoped browser on the " +
|
|
307
322
|
"user's machine at service_url and returns the initial compact observation " +
|
|
308
|
-
"
|
|
323
|
+
"(legacy el_table/delta or compact-v2 safe_table). " +
|
|
324
|
+
COMPACT_V2_CONTRACT +
|
|
309
325
|
OBSERVE_DELTA_CONTRACT +
|
|
310
326
|
"YOU are the planner — read the observation, then drive the signup, setup, or " +
|
|
311
327
|
"checkout with operate_act (and operate_pay for a purchase), re-read with " +
|
|
@@ -327,7 +343,6 @@ export const provisionStartTool = {
|
|
|
327
343
|
},
|
|
328
344
|
allowed_hosts: { type: "array", items: { type: "string" } },
|
|
329
345
|
extra_allowed_hosts: { type: "array", items: { type: "string" } },
|
|
330
|
-
require_live_identity: { type: "boolean" },
|
|
331
346
|
},
|
|
332
347
|
},
|
|
333
348
|
async handler(args, api) {
|
|
@@ -339,7 +354,6 @@ export const provisionStartTool = {
|
|
|
339
354
|
consentInboxRead,
|
|
340
355
|
...(args.proxy !== undefined ? { proxyUrl: args.proxy } : {}),
|
|
341
356
|
...(extra.length > 0 ? { extraAllowedHosts: extra } : {}),
|
|
342
|
-
...(args.require_live_identity === true ? { requireLiveIdentity: true } : {}),
|
|
343
357
|
...(hint !== undefined ? { hint } : {}),
|
|
344
358
|
// Thread the api-client so the captcha gate can spend a vaulted 2Captcha key.
|
|
345
359
|
...(api !== null ? { api } : {}),
|
|
@@ -348,24 +362,19 @@ export const provisionStartTool = {
|
|
|
348
362
|
};
|
|
349
363
|
const observeSchema = z.object({
|
|
350
364
|
session_id: z.string().min(1),
|
|
351
|
-
// Payload verbosity
|
|
352
|
-
//
|
|
353
|
-
// screen+accessibility+full-field payload on a genuinely ambiguous step.
|
|
365
|
+
// Payload verbosity within the selected observation mode. In V2 both values
|
|
366
|
+
// return the compact action map; in V1, full requests the legacy expanded payload.
|
|
354
367
|
detail: z.enum(["compact", "full"]).optional(),
|
|
355
368
|
});
|
|
356
369
|
export const provisionObserveTool = {
|
|
357
370
|
name: "operate_observe",
|
|
358
|
-
description: "Re-read the current page of an operate session.
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
"
|
|
362
|
-
"
|
|
363
|
-
"behind the dedicated captcha flow. Path is retained only in snapshot_file, " +
|
|
364
|
-
"and redundant screen/accessibility trees are omitted. " +
|
|
371
|
+
description: "Re-read the current page of an operate session. The default compact-v2 mode returns the compact " +
|
|
372
|
+
'safe_table action map; `detail:"full"` stays in that format and does not restore legacy fields. ' +
|
|
373
|
+
COMPACT_V2_CONTRACT +
|
|
374
|
+
"Only explicitly selected V1 modes use el_table, reusable stable refs, locator fallbacks, snapshot_file, " +
|
|
375
|
+
"or the legacy expanded elements payload. " +
|
|
365
376
|
OBSERVE_DELTA_CONTRACT +
|
|
366
|
-
|
|
367
|
-
'detail:"full" for the legacy screen+accessibility+full-field payload on a ' +
|
|
368
|
-
"genuinely ambiguous step.",
|
|
377
|
+
'In V1 only, pass detail:"full" for the legacy screen+accessibility+full-field payload on a genuinely ambiguous step.',
|
|
369
378
|
inputSchema: observeSchema,
|
|
370
379
|
jsonInputSchema: {
|
|
371
380
|
type: "object",
|
|
@@ -396,18 +405,19 @@ const screenshotSchema = z.object({
|
|
|
396
405
|
});
|
|
397
406
|
export const provisionScreenshotTool = {
|
|
398
407
|
name: "operate_screenshot",
|
|
399
|
-
description: "
|
|
408
|
+
description: "WARNING: EXPENSIVE — a screenshot is a full image and costs far more context than any " +
|
|
409
|
+
"observation. Reach for it ONLY when the DOM serialization (Compact V2 safe_table, " +
|
|
410
|
+
"operate_observe, operate_observe_query) is NOT sufficient to determine the page state; " +
|
|
411
|
+
"if the tables already tell you what the page is doing, do not take one. " +
|
|
412
|
+
"Debugging tool: capture a screenshot of what the operate session's browser actually RENDERS — " +
|
|
400
413
|
"the whole page (default: viewport; full_page:true for the whole scrollable page) or ONE specific " +
|
|
401
414
|
"frame in isolation via frame_index or frame_url_contains, so a cross-origin challenge iframe (a " +
|
|
402
415
|
"3-D Secure ACS frame, a captcha) can be captured on its own even when it won't show clearly inside " +
|
|
403
|
-
"a full-page shot. Use this when
|
|
416
|
+
"a full-page shot. Use this when safe_table from Compact V2, or text/el_table from an explicitly " +
|
|
417
|
+
"selected V1 session, isn't enough to tell what state " +
|
|
404
418
|
"a stuck page is actually in — a challenge that never advances, an unexpected layout, a captcha you " +
|
|
405
419
|
"need to SEE. Read-only: never navigates, clicks, types, submits, or steals focus; it only reads " +
|
|
406
|
-
"pixels.
|
|
407
|
-
"when a frame included in this capture still holds a sealed secret or card-shaped value. It fails closed " +
|
|
408
|
-
"when that frame cannot be checked, while capture-time masking remains a second fence. A historical fill " +
|
|
409
|
-
"does not block an isolated 3-D Secure/challenge frame or a page navigated away from the sealed form. " +
|
|
410
|
-
"If you need to see a page WHILE a card fill is in progress, finish or cancel the payment step first.",
|
|
420
|
+
"pixels. The image is the page's real pixels, whatever the page is showing.",
|
|
411
421
|
inputSchema: screenshotSchema,
|
|
412
422
|
jsonInputSchema: {
|
|
413
423
|
type: "object",
|
|
@@ -429,6 +439,50 @@ export const provisionScreenshotTool = {
|
|
|
429
439
|
});
|
|
430
440
|
},
|
|
431
441
|
};
|
|
442
|
+
const observeQuerySchema = z.object({
|
|
443
|
+
session_id: z.string().min(1),
|
|
444
|
+
// This string is matched only inside the live session; returned rows remain
|
|
445
|
+
// the compact-v2 enum-only action map.
|
|
446
|
+
query: z.string().max(160).default(""),
|
|
447
|
+
role: z
|
|
448
|
+
.enum(["button", "link", "textbox", "select", "checkbox", "radio", "tab", "menuitem", "file"])
|
|
449
|
+
.optional(),
|
|
450
|
+
cursor: z.string().max(1_024).optional(),
|
|
451
|
+
});
|
|
452
|
+
export const provisionObserveQueryTool = {
|
|
453
|
+
name: "operate_observe_query",
|
|
454
|
+
description: "Page compact-v2 overflow or named-control lookup. Supply the product/control words already in the task; " +
|
|
455
|
+
"matching happens only inside the live browser and returns actionable opaque refs plus finite role/state/action " +
|
|
456
|
+
"enums. Use overflow.next_cursor to page controls, or hint_overflow.next_cursor with an empty query to page " +
|
|
457
|
+
"trusted start routing metadata; never read a snapshot file.",
|
|
458
|
+
inputSchema: observeQuerySchema,
|
|
459
|
+
jsonInputSchema: {
|
|
460
|
+
type: "object",
|
|
461
|
+
required: ["session_id"],
|
|
462
|
+
properties: {
|
|
463
|
+
session_id: { type: "string" },
|
|
464
|
+
query: { type: "string" },
|
|
465
|
+
role: {
|
|
466
|
+
type: "string",
|
|
467
|
+
enum: [
|
|
468
|
+
"button",
|
|
469
|
+
"link",
|
|
470
|
+
"textbox",
|
|
471
|
+
"select",
|
|
472
|
+
"checkbox",
|
|
473
|
+
"radio",
|
|
474
|
+
"tab",
|
|
475
|
+
"menuitem",
|
|
476
|
+
"file",
|
|
477
|
+
],
|
|
478
|
+
},
|
|
479
|
+
cursor: { type: "string" },
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
async handler(args) {
|
|
483
|
+
return await observeQuery(args.session_id, args.query, args.role, args.cursor);
|
|
484
|
+
},
|
|
485
|
+
};
|
|
432
486
|
// Shared credential destination shape. Both operate_act(kind:"extract") and
|
|
433
487
|
// the legacy operate_extract alias validate and execute this exact contract.
|
|
434
488
|
// The credentials terminal also reuses it below.
|
|
@@ -459,7 +513,8 @@ const storeJsonProps = {
|
|
|
459
513
|
const formSelectionsSchema = z
|
|
460
514
|
.record(z.string().min(1).max(200), z.string().min(1).max(4096))
|
|
461
515
|
.refine((value) => Object.keys(value).length > 0, "Provide at least one selection")
|
|
462
|
-
.refine((value) => Object.keys(value).length <= 12, "At most 12 selections per call")
|
|
516
|
+
.refine((value) => Object.keys(value).length <= 12, "At most 12 selections per call")
|
|
517
|
+
.describe("Map each current Compact V2 @e: ref or @label, or V1 observed label/ref, to its visible option text.");
|
|
463
518
|
const actSchema = z
|
|
464
519
|
.object({
|
|
465
520
|
session_id: z.string().min(1),
|
|
@@ -479,6 +534,7 @@ const actSchema = z
|
|
|
479
534
|
"scroll",
|
|
480
535
|
"upload",
|
|
481
536
|
"cart_add",
|
|
537
|
+
"cart_clear",
|
|
482
538
|
"select_many",
|
|
483
539
|
"extract",
|
|
484
540
|
"solve_captcha",
|
|
@@ -496,6 +552,7 @@ const actSchema = z
|
|
|
496
552
|
path: z.string().min(1).max(4096).optional(),
|
|
497
553
|
url: z.string().url().optional(),
|
|
498
554
|
key: z.string().min(1).max(40).optional(),
|
|
555
|
+
provider: z.enum(["google", "github"]).optional(),
|
|
499
556
|
// allow_host: a bare hostname to cross into mid-session.
|
|
500
557
|
host: z.string().min(1).max(253).optional(),
|
|
501
558
|
// type_secret: the sealed slot whose value to type into `target`.
|
|
@@ -661,6 +718,7 @@ const ACTION_KINDS = [
|
|
|
661
718
|
"scroll",
|
|
662
719
|
"upload",
|
|
663
720
|
"cart_add",
|
|
721
|
+
"cart_clear",
|
|
664
722
|
"select_many",
|
|
665
723
|
"extract",
|
|
666
724
|
"solve_captcha",
|
|
@@ -682,13 +740,21 @@ const ACTION_REPAIR_BY_KIND = {
|
|
|
682
740
|
safe_alternative: "Retry cart_add with the same stable idempotency_key for the same product_identity and options_hash. Do not replace it with click: cart_add reserves the mutation and exact-line post-verifies the cart." +
|
|
683
741
|
manualCardRecovery,
|
|
684
742
|
},
|
|
743
|
+
cart_clear: {
|
|
744
|
+
example: {
|
|
745
|
+
session_id: "<session_id>",
|
|
746
|
+
kind: "cart_clear",
|
|
747
|
+
},
|
|
748
|
+
safe_alternative: "Retry cart_clear with only session_id — it empties the cart so a following cart_add reaches a known quantity, regardless of what accumulated in the persistent browser profile's cart across earlier sessions." +
|
|
749
|
+
manualCardRecovery,
|
|
750
|
+
},
|
|
685
751
|
select_many: {
|
|
686
752
|
example: {
|
|
687
753
|
session_id: "<session_id>",
|
|
688
754
|
kind: "select_many",
|
|
689
|
-
selections: { "
|
|
755
|
+
selections: { "@e:<current-handle>": "Visible option label" },
|
|
690
756
|
},
|
|
691
|
-
safe_alternative: "Retry select_many with selections in the intended order. It selects sequentially, re-observes after each success, and returns partial results; do not replace it with parallel select calls." +
|
|
757
|
+
safe_alternative: "Retry select_many with selections in the intended order. Compact V2 requires current @e: handles; V1 accepts observed labels or refs. It selects sequentially, re-observes after each success, and returns partial results; do not replace it with parallel select calls." +
|
|
692
758
|
manualCardRecovery,
|
|
693
759
|
},
|
|
694
760
|
extract: {
|
|
@@ -698,7 +764,7 @@ const ACTION_REPAIR_BY_KIND = {
|
|
|
698
764
|
into_slot: "sealed_secret",
|
|
699
765
|
secret_label: "API key",
|
|
700
766
|
},
|
|
701
|
-
safe_alternative: "Retry extract after navigating to the credential page. Use into_slot with optional secret_label to
|
|
767
|
+
safe_alternative: "Retry extract after navigating to the credential page. Use into_slot with optional secret_label to hold a value in a session slot for a later type_secret, or store with a service to vault it; never put credential values in arguments." +
|
|
702
768
|
manualCardRecovery,
|
|
703
769
|
},
|
|
704
770
|
solve_captcha: {
|
|
@@ -715,9 +781,9 @@ const ACTION_REPAIR_BY_KIND = {
|
|
|
715
781
|
kind: "await_verification",
|
|
716
782
|
sender: "service.example",
|
|
717
783
|
into_slot: "otp",
|
|
718
|
-
grant_inbox_consent:
|
|
784
|
+
grant_inbox_consent: true,
|
|
719
785
|
},
|
|
720
|
-
safe_alternative: "Retry await_verification with sender scoped to the service and prefer into_slot so the OTP stays sealed.
|
|
786
|
+
safe_alternative: "Retry await_verification with sender scoped to the service and prefer into_slot so the OTP stays sealed. Inbox reading is on by default; pass grant_inbox_consent:false to opt out for this session." +
|
|
721
787
|
manualCardRecovery,
|
|
722
788
|
},
|
|
723
789
|
login_prepare_signup: {
|
|
@@ -818,9 +884,17 @@ function buildAction(args) {
|
|
|
818
884
|
case "js_click":
|
|
819
885
|
return { kind: "js_click", target: need(args.target, "target") };
|
|
820
886
|
case "oauth_click":
|
|
821
|
-
return {
|
|
887
|
+
return {
|
|
888
|
+
kind: "oauth_click",
|
|
889
|
+
target: need(args.target, "target"),
|
|
890
|
+
...(args.provider === undefined ? {} : { provider: args.provider }),
|
|
891
|
+
};
|
|
822
892
|
case "oauth_login":
|
|
823
|
-
return {
|
|
893
|
+
return {
|
|
894
|
+
kind: "oauth_login",
|
|
895
|
+
target: need(args.target, "target"),
|
|
896
|
+
...(args.provider === undefined ? {} : { provider: args.provider }),
|
|
897
|
+
};
|
|
824
898
|
case "type":
|
|
825
899
|
return {
|
|
826
900
|
kind: "type",
|
|
@@ -867,6 +941,7 @@ function buildAction(args) {
|
|
|
867
941
|
case "upload":
|
|
868
942
|
return { kind: "upload", target: need(args.target, "target"), path: need(args.path, "path") };
|
|
869
943
|
case "cart_add":
|
|
944
|
+
case "cart_clear":
|
|
870
945
|
case "select_many":
|
|
871
946
|
case "extract":
|
|
872
947
|
case "solve_captcha":
|
|
@@ -880,29 +955,34 @@ function buildAction(args) {
|
|
|
880
955
|
async function handleCartAdd(args) {
|
|
881
956
|
return await cartAdd(args.session_id, args.product_identity, args.options_hash, args.idempotency_key);
|
|
882
957
|
}
|
|
958
|
+
async function handleCartClear(args) {
|
|
959
|
+
return await cartClear(args.session_id);
|
|
960
|
+
}
|
|
883
961
|
async function handleFormSelectMany(args) {
|
|
884
962
|
return await formSelectMany(args.session_id, args.selections);
|
|
885
963
|
}
|
|
886
964
|
async function handleExtract(args, api) {
|
|
887
965
|
const extracted = await extractCredentials(args.session_id);
|
|
888
|
-
//
|
|
889
|
-
//
|
|
890
|
-
//
|
|
891
|
-
//
|
|
966
|
+
// Slot transfer: capture the primary secret into a session-local slot so a
|
|
967
|
+
// later type_secret can enter it into another site's form without the host
|
|
968
|
+
// having to relay it. Mutually exclusive with store (a slotted secret is
|
|
969
|
+
// being shuttled, not vaulted, in this call).
|
|
892
970
|
if (args.into_slot !== undefined) {
|
|
893
971
|
const values = extracted.credentials;
|
|
894
|
-
// A still-masked display (Google's OAuth secret shows "GOCSPX-••••" with a
|
|
895
|
-
// copy button) must NOT be sealed — the slot would hold junk. Reject any
|
|
896
|
-
// value with mask glyphs (canonical isMaskedDisplay) or the truncated-capture
|
|
897
|
-
// marker, and prefer a full value over the masked api_key when the page has both.
|
|
898
972
|
const norm = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
899
|
-
const candidates = Object.entries(values).filter(([k, v]) => !k.endsWith("_truncated") && typeof v === "string" && v.length >= 8
|
|
973
|
+
const candidates = Object.entries(values).filter(([k, v]) => !k.endsWith("_truncated") && typeof v === "string" && v.length >= 8);
|
|
974
|
+
// A value that still LOOKS masked is never refused — it is simply ranked
|
|
975
|
+
// last, so a fully revealed sibling wins when the page shows both.
|
|
976
|
+
const ranked = [
|
|
977
|
+
...candidates.filter(([, v]) => !isMaskedDisplay(v)),
|
|
978
|
+
...candidates.filter(([, v]) => isMaskedDisplay(v)),
|
|
979
|
+
];
|
|
900
980
|
// When the page shows several credentials (Google's client ID + secret),
|
|
901
981
|
// a secret_label picks the right one by field name; otherwise take the
|
|
902
982
|
// first full value. Falling back avoids a hard fail when the label misses.
|
|
903
983
|
const wantKey = args.secret_label !== undefined ? norm(args.secret_label) : null;
|
|
904
|
-
const matched = wantKey !== null ?
|
|
905
|
-
const full = (matched ??
|
|
984
|
+
const matched = wantKey !== null ? ranked.find(([k]) => norm(k).includes(wantKey)) : undefined;
|
|
985
|
+
const full = (matched ?? ranked[0])?.[1];
|
|
906
986
|
if (typeof full !== "string" || full.length === 0) {
|
|
907
987
|
return {
|
|
908
988
|
session_id: extracted.session_id,
|
|
@@ -910,21 +990,17 @@ async function handleExtract(args, api) {
|
|
|
910
990
|
candidate_count: extracted.candidate_count,
|
|
911
991
|
sealed: false,
|
|
912
992
|
slot: null,
|
|
913
|
-
blocked_reason: "
|
|
914
|
-
'
|
|
993
|
+
blocked_reason: "no credential value was found on this page — navigate to the keys/settings " +
|
|
994
|
+
'page, then operate_act { kind: "extract" } again',
|
|
915
995
|
};
|
|
916
996
|
}
|
|
917
997
|
const handle = stashSecretSlot(args.session_id, args.into_slot, full);
|
|
918
|
-
// Strip raw credential VALUES from the response — host gets the handle only.
|
|
919
998
|
return {
|
|
920
999
|
session_id: extracted.session_id,
|
|
921
1000
|
url: extracted.url,
|
|
922
1001
|
candidate_count: extracted.candidate_count,
|
|
923
1002
|
sealed: true,
|
|
924
1003
|
slot: handle,
|
|
925
|
-
...(extracted.blocked_reason !== undefined
|
|
926
|
-
? { blocked_reason: extracted.blocked_reason }
|
|
927
|
-
: {}),
|
|
928
1004
|
};
|
|
929
1005
|
}
|
|
930
1006
|
if (args.store === undefined || Object.keys(extracted.credentials).length === 0) {
|
|
@@ -943,18 +1019,19 @@ async function handleAwaitVerification(args) {
|
|
|
943
1019
|
return await awaitVerification(args.session_id, {
|
|
944
1020
|
...(args.sender !== undefined ? { sender: args.sender } : {}),
|
|
945
1021
|
...(args.into_slot !== undefined ? { intoSlot: args.into_slot } : {}),
|
|
946
|
-
...(args.grant_inbox_consent
|
|
1022
|
+
...(args.grant_inbox_consent !== undefined ? { grantConsent: args.grant_inbox_consent } : {}),
|
|
947
1023
|
});
|
|
948
1024
|
}
|
|
949
1025
|
export const provisionActTool = {
|
|
950
1026
|
name: "operate_act",
|
|
951
|
-
description: "Take one action in an operate session
|
|
952
|
-
"
|
|
1027
|
+
description: "Take one action in an operate session. Under compact-v2 the default follow-up is a compact delta " +
|
|
1028
|
+
"when its action map is unchanged; call operate_observe or operate_observe_query when you need a new map. " +
|
|
1029
|
+
"kinds: click (target=element ref, preferably a safe_table row's ref), " +
|
|
953
1030
|
"type (target + text; model-supplied card-number-shaped text is refused — card payment " +
|
|
954
1031
|
"must use operate_pay, which fills a vaulted card without exposing it to the model), " +
|
|
955
1032
|
"" +
|
|
956
|
-
"TARGET FALLBACK (clicking or typing only) — when a control you can SEE (in the " +
|
|
957
|
-
"observation text or screenshot) has NO ref in el_table (a bare click-handler " +
|
|
1033
|
+
"V1-ONLY TARGET FALLBACK (clicking or typing only) — when a control you can SEE (in the " +
|
|
1034
|
+
"V1 observation text or screenshot) has NO ref in el_table (a bare click-handler " +
|
|
958
1035
|
'<div> with no role/label, e.g. a SPA "Add To Cart"), pass target as a locator ' +
|
|
959
1036
|
'instead of a ref: `text="Add To Cart"` (a matching clickable or typeable ' +
|
|
960
1037
|
"element, case-insensitive, hidden descendants ignored, open shadow roots " +
|
|
@@ -965,8 +1042,9 @@ export const provisionActTool = {
|
|
|
965
1042
|
"exact text= or a css= selector). `click` is actionability-checked and throws " +
|
|
966
1043
|
"if an overlay intercepts; dismiss the overlay or deliberately use `js_click`, " +
|
|
967
1044
|
"the explicit direct DOM dispatch that fires through a transparent overlay. " +
|
|
968
|
-
"Prefer a real ref when one exists; reach for " +
|
|
969
|
-
"
|
|
1045
|
+
"Prefer a real ref when one exists; reach for text=/css= only when none does. Compact V2 never " +
|
|
1046
|
+
"accepts locator fallback: it requires an opaque handle from the current snapshot/page/generation-" +
|
|
1047
|
+
"scoped action map, with membership as the sole action boundary. " +
|
|
970
1048
|
"Observed child-frame refs and locator matches retain their frame origin. " +
|
|
971
1049
|
"Same-registrable-domain frames are reachable; other frame actions must pass " +
|
|
972
1050
|
"the goto/allow_host domain scope, opaque frames are refused, and type_secret " +
|
|
@@ -997,14 +1075,20 @@ export const provisionActTool = {
|
|
|
997
1075
|
"already signed into). " +
|
|
998
1076
|
"cart_add (product_identity + options_hash + idempotency_key — reserve an " +
|
|
999
1077
|
"idempotent cart mutation, exact-line post-verify it, and return its postcondition), " +
|
|
1078
|
+
"cart_clear (no arguments beyond session_id — empty the cart so a following cart_add " +
|
|
1079
|
+
"reaches a KNOWN quantity; the browser profile persists across sessions, so a cart " +
|
|
1080
|
+
"can carry quantity over from an earlier run. Call cart_clear before the first cart_add " +
|
|
1081
|
+
"whenever the cart's starting quantity is not already known to be zero), " +
|
|
1000
1082
|
"select_many (ordered selections map — select sequentially, re-observe after " +
|
|
1001
|
-
"each success, and retain partial results
|
|
1083
|
+
"each success, and retain partial results; Compact V2 keys are current safe_table @e: refs or @labels, " +
|
|
1084
|
+
"while V1 keys may be observed labels or refs), extract (into_slot/secret_label/store — " +
|
|
1002
1085
|
"reveal masked keys and extract credentials from the current page, sealed slot or vaulted), " +
|
|
1003
1086
|
"solve_captcha (detect and drive the in-session captcha gate; settled=false carries a " +
|
|
1004
1087
|
"needs_user{gate,message,remedy} — FAIL FAST and relay it to the user), " +
|
|
1005
1088
|
"await_verification (sender/into_slot/grant_inbox_consent — read the user's OWN inbox " +
|
|
1006
|
-
"through their signed-in browser session for an email verification code/link
|
|
1007
|
-
"into a slot
|
|
1089
|
+
"through their signed-in browser session for an email verification code/link; pass into_slot " +
|
|
1090
|
+
"to seal the code into a slot and fill it with type_secret instead of handling it yourself). " +
|
|
1091
|
+
"Sealed username/password login lifecycle, never exposing raw " +
|
|
1008
1092
|
"values: login_prepare_signup (login_slot/password_slot/password_length — seal the user's " +
|
|
1009
1093
|
"captured email and a generated strong password into session slots; fill the signup form " +
|
|
1010
1094
|
"with type_secret using the returned slots), login_store_signup (service + login_hosts, " +
|
|
@@ -1018,12 +1102,15 @@ export const provisionActTool = {
|
|
|
1018
1102
|
"Squire-known address, contact, product_query, or quantity input; type_secret and " +
|
|
1019
1103
|
"operate_pay record credential and card provenance from their sealed sources. " +
|
|
1020
1104
|
"When repairing a replay fallback field, pass its replay_step_index and replay_hole. " +
|
|
1021
|
-
"
|
|
1022
|
-
"
|
|
1023
|
-
"
|
|
1105
|
+
"Under default compact-v2, target is either an @e: ref from the current action map or the @label alias " +
|
|
1106
|
+
"of exactly one of its rows; a @label matching several rows returns ambiguous_target listing their refs, never " +
|
|
1107
|
+
"a guess. Refs survive acts and benign re-renders on the same document; a real navigation retires them. On " +
|
|
1108
|
+
"opaque `stale_ref`, call operate_observe and choose a new ref; do not retry the old one. " +
|
|
1109
|
+
"In V1, stable target refs remain reusable while their element exists; a stale @e: " +
|
|
1110
|
+
"ref returns {status:target_stale, replacement_candidates, retry_policy:do_not_retry_old_ref}. " +
|
|
1024
1111
|
'detail (default "compact") controls the returned payload: "none" skips it ' +
|
|
1025
1112
|
"entirely for chained fills (then operate_observe before the next ref action), " +
|
|
1026
|
-
'"full" returns the legacy screen+accessibility payload. ' +
|
|
1113
|
+
'in V1, "full" returns the legacy screen+accessibility payload. ' +
|
|
1027
1114
|
"For legible product/variant hints on a cart-affecting action, pass product_identity and options_hash together; returned checkout_state is best-effort informational state and never a payment charge input. " +
|
|
1028
1115
|
OBSERVE_DELTA_CONTRACT,
|
|
1029
1116
|
inputSchema: actSchema,
|
|
@@ -1050,6 +1137,7 @@ export const provisionActTool = {
|
|
|
1050
1137
|
"scroll",
|
|
1051
1138
|
"upload",
|
|
1052
1139
|
"cart_add",
|
|
1140
|
+
"cart_clear",
|
|
1053
1141
|
"select_many",
|
|
1054
1142
|
"extract",
|
|
1055
1143
|
"solve_captcha",
|
|
@@ -1065,6 +1153,11 @@ export const provisionActTool = {
|
|
|
1065
1153
|
path: { type: "string" },
|
|
1066
1154
|
url: { type: "string" },
|
|
1067
1155
|
key: { type: "string" },
|
|
1156
|
+
provider: {
|
|
1157
|
+
type: "string",
|
|
1158
|
+
enum: ["google", "github"],
|
|
1159
|
+
description: "OAuth provider for oauth_login/oauth_click. Declare it for JavaScript-driven controls whose label or destination does not identify the provider.",
|
|
1160
|
+
},
|
|
1068
1161
|
host: { type: "string" },
|
|
1069
1162
|
slot: { type: "string" },
|
|
1070
1163
|
product_identity: { type: "string", minLength: 1 },
|
|
@@ -1075,7 +1168,7 @@ export const provisionActTool = {
|
|
|
1075
1168
|
minProperties: 1,
|
|
1076
1169
|
maxProperties: 12,
|
|
1077
1170
|
additionalProperties: { type: "string" },
|
|
1078
|
-
description: "Map each
|
|
1171
|
+
description: "Map each current Compact V2 @e: ref or @label, or V1 observed label/ref, to its visible option text.",
|
|
1079
1172
|
},
|
|
1080
1173
|
into_slot: { type: "string" },
|
|
1081
1174
|
secret_label: { type: "string" },
|
|
@@ -1111,52 +1204,57 @@ export const provisionActTool = {
|
|
|
1111
1204
|
},
|
|
1112
1205
|
schemaRepair: actionSchemaRepair,
|
|
1113
1206
|
async handler(args, api) {
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
// exact point a small model tried a forbidden manual PAN entry.
|
|
1139
|
-
if (args.kind === "type") {
|
|
1140
|
-
const reason = manualCardEntryBlockReason(args.text ?? "");
|
|
1141
|
-
if (reason !== null) {
|
|
1142
|
-
return {
|
|
1143
|
-
status: "manual_card_entry_refused",
|
|
1144
|
-
reason,
|
|
1145
|
-
safe_alternative: "operate_pay",
|
|
1146
|
-
missing_prerequisite: "verified_cart_total",
|
|
1147
|
-
};
|
|
1207
|
+
const result = await (async () => {
|
|
1208
|
+
switch (args.kind) {
|
|
1209
|
+
case "cart_add":
|
|
1210
|
+
return await handleCartAdd(args);
|
|
1211
|
+
case "cart_clear":
|
|
1212
|
+
return await handleCartClear(args);
|
|
1213
|
+
case "select_many":
|
|
1214
|
+
return await handleFormSelectMany(args);
|
|
1215
|
+
case "extract":
|
|
1216
|
+
return await handleExtract(args, api);
|
|
1217
|
+
case "solve_captcha":
|
|
1218
|
+
return await handleCaptcha(args);
|
|
1219
|
+
case "await_verification":
|
|
1220
|
+
return await handleAwaitVerification(args);
|
|
1221
|
+
case "login_prepare_signup":
|
|
1222
|
+
return await handlePrepareLogin(args);
|
|
1223
|
+
case "login_store_signup":
|
|
1224
|
+
return await handleStoreLogin(args, api);
|
|
1225
|
+
case "login_load_saved":
|
|
1226
|
+
return await handleSealVaultCredential({
|
|
1227
|
+
...args,
|
|
1228
|
+
fields: args.fields ?? ["login", "password"],
|
|
1229
|
+
slot_prefix: args.slot_prefix ?? "vault",
|
|
1230
|
+
}, api);
|
|
1148
1231
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1232
|
+
// Keep the defense-in-depth guard in act() for internal/replay callers,
|
|
1233
|
+
// while this public tool surface makes the safe recovery explicit at the
|
|
1234
|
+
// exact point a small model tried a forbidden manual PAN entry.
|
|
1235
|
+
if (args.kind === "type") {
|
|
1236
|
+
const reason = manualCardEntryBlockReason(args.text ?? "");
|
|
1237
|
+
if (reason !== null) {
|
|
1238
|
+
return {
|
|
1239
|
+
status: "manual_card_entry_refused",
|
|
1240
|
+
reason,
|
|
1241
|
+
safe_alternative: "operate_pay",
|
|
1242
|
+
missing_prerequisite: "verified_cart_total",
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
try {
|
|
1247
|
+
return await act(args.session_id, buildAction(args), args.detail ?? "compact", args.product_identity !== undefined && args.options_hash !== undefined
|
|
1248
|
+
? { productIdentity: args.product_identity, optionsHash: args.options_hash }
|
|
1249
|
+
: undefined);
|
|
1250
|
+
}
|
|
1251
|
+
catch (err) {
|
|
1252
|
+
if (err instanceof TargetStaleError)
|
|
1253
|
+
return err.result;
|
|
1254
|
+
throw err;
|
|
1255
|
+
}
|
|
1256
|
+
})();
|
|
1257
|
+
return result;
|
|
1160
1258
|
},
|
|
1161
1259
|
};
|
|
1162
1260
|
// The former standalone tool objects in the following sections are NOT part of
|
|
@@ -1194,13 +1292,12 @@ export const operateCartAddTool = {
|
|
|
1194
1292
|
};
|
|
1195
1293
|
const formSelectManySchema = z.object({
|
|
1196
1294
|
session_id: z.string().min(1),
|
|
1197
|
-
// A label/ref → visible option map. Labels deliberately match the existing
|
|
1198
|
-
// select targeting grammar, while refs remain useful when labels collide.
|
|
1199
1295
|
selections: formSelectionsSchema,
|
|
1200
1296
|
});
|
|
1201
1297
|
export const operateFormSelectManyTool = {
|
|
1202
1298
|
name: "operate_form_select_many",
|
|
1203
|
-
description: "Select several related form options sequentially from a
|
|
1299
|
+
description: "Select several related form options sequentially from a target-to-option map. " +
|
|
1300
|
+
"Compact V2 requires current @e: handles; V1 accepts observed labels or refs. " +
|
|
1204
1301
|
"Selections run in order; after every successful selection the browser is re-observed " +
|
|
1205
1302
|
"before the next one resolves, so variant changes cannot poison later refs. Each field " +
|
|
1206
1303
|
"reports selected or failed independently; successful selections are not rolled back when " +
|
|
@@ -1217,7 +1314,7 @@ export const operateFormSelectManyTool = {
|
|
|
1217
1314
|
minProperties: 1,
|
|
1218
1315
|
maxProperties: 12,
|
|
1219
1316
|
additionalProperties: { type: "string" },
|
|
1220
|
-
description: "Map each
|
|
1317
|
+
description: "Map each current Compact V2 @e: ref or @label, or V1 observed label/ref, to its visible option text.",
|
|
1221
1318
|
},
|
|
1222
1319
|
},
|
|
1223
1320
|
},
|
|
@@ -1241,7 +1338,8 @@ export const provisionExtractTool = {
|
|
|
1241
1338
|
"the page is a login wall / anti-bot interstitial with NO credential present " +
|
|
1242
1339
|
"(do not treat the empty result as a real key) — drive an interactive login " +
|
|
1243
1340
|
"or hand back to the user. Call when you have navigated to the keys page. " +
|
|
1244
|
-
"With `into_slot`, a still
|
|
1341
|
+
"With `into_slot`, a value that still looks masked is ranked behind a fully " +
|
|
1342
|
+
"revealed sibling but never refused; pass " +
|
|
1245
1343
|
'`secret_label` (e.g. "client secret") to pick the right one when the page ' +
|
|
1246
1344
|
"shows several credentials.",
|
|
1247
1345
|
inputSchema: extractSchema,
|
|
@@ -1311,8 +1409,9 @@ export const provisionAwaitVerificationTool = {
|
|
|
1311
1409
|
"operate_act and continue. The session stays live; this is a resumable " +
|
|
1312
1410
|
"hand-back, not a failure. Scoped search-and-extract — reads only the matching " +
|
|
1313
1411
|
"recent mail, never the whole inbox. If a needs_user(verification_code) says " +
|
|
1314
|
-
"inbox reading
|
|
1315
|
-
"grant_inbox_consent:true."
|
|
1412
|
+
"inbox reading is disabled, ask the user; on an explicit yes retry with " +
|
|
1413
|
+
"grant_inbox_consent:true. Pass grant_inbox_consent:false to disable inbox reading " +
|
|
1414
|
+
"for the current session.",
|
|
1316
1415
|
inputSchema: verifySchema,
|
|
1317
1416
|
jsonInputSchema: {
|
|
1318
1417
|
type: "object",
|
|
@@ -1394,6 +1493,7 @@ const finishOutcomeSchema = z.union([
|
|
|
1394
1493
|
}),
|
|
1395
1494
|
]);
|
|
1396
1495
|
async function handleFinishOutcome(sessionId, outcome, api) {
|
|
1496
|
+
let successfulOutcome = false;
|
|
1397
1497
|
const { finish, prepared } = await finishProvisionSessionWithPreparation(sessionId, async () => {
|
|
1398
1498
|
if (outcome.kind === "credentials") {
|
|
1399
1499
|
const extracted = await extractCredentials(sessionId);
|
|
@@ -1404,7 +1504,8 @@ async function handleFinishOutcome(sessionId, outcome, api) {
|
|
|
1404
1504
|
const autoPromote = stored !== null && blocked === undefined
|
|
1405
1505
|
? await autoPromoteProvision(sessionId)
|
|
1406
1506
|
: undefined;
|
|
1407
|
-
|
|
1507
|
+
successfulOutcome = stored !== null && blocked === undefined;
|
|
1508
|
+
emitProvisionMeasurement(sessionId, successfulOutcome ? "success" : "fail");
|
|
1408
1509
|
return {
|
|
1409
1510
|
kind: "credentials",
|
|
1410
1511
|
candidate_count: extracted.candidate_count,
|
|
@@ -1417,14 +1518,18 @@ async function handleFinishOutcome(sessionId, outcome, api) {
|
|
|
1417
1518
|
? undefined
|
|
1418
1519
|
: ((await verifyActiveRecipePostcondition(sessionId, outcome.verify_recipe)) ??
|
|
1419
1520
|
(await verifySavedRecipePostcondition(sessionId, await readRecipe(outcome.verify_recipe))));
|
|
1420
|
-
|
|
1521
|
+
successfulOutcome =
|
|
1522
|
+
outcome.verify_recipe === undefined
|
|
1523
|
+
? outcome.data?.confirmed === "true"
|
|
1524
|
+
: verified?.confirmed === true;
|
|
1525
|
+
emitProvisionMeasurement(sessionId, successfulOutcome ? "success" : "fail");
|
|
1421
1526
|
return {
|
|
1422
1527
|
kind: "result",
|
|
1423
1528
|
summary: (outcome.summary ?? "").slice(0, 4000),
|
|
1424
1529
|
...(verified !== undefined ? { verified } : {}),
|
|
1425
1530
|
...(outcome.data !== undefined ? { data: outcome.data } : {}),
|
|
1426
1531
|
};
|
|
1427
|
-
});
|
|
1532
|
+
}, () => successfulOutcome);
|
|
1428
1533
|
return { ...prepared, url: finish.url };
|
|
1429
1534
|
}
|
|
1430
1535
|
// Not part of the default MCP tool surface (dropped from OPERATE_TOOLS). Kept
|
|
@@ -1445,7 +1550,8 @@ export const provisionFinishTaskTool = {
|
|
|
1445
1550
|
"operate_extract's store), for signups/key-provisioning. kind='result' " +
|
|
1446
1551
|
"reports a `summary` (+ optional `data` map) for any other task — a design " +
|
|
1447
1552
|
"review's findings, extracted data, or 'task done' (put confirmed:true in " +
|
|
1448
|
-
"data
|
|
1553
|
+
"data only after a clean success; false or omitted preserves prior login state). " +
|
|
1554
|
+
"Use operate_finish instead to abort without an outcome. For a soft " +
|
|
1449
1555
|
"no-match with kind='result' (for example, no exact or authentic item in " +
|
|
1450
1556
|
"stock), first relay the closest candidates and why they were rejected, then " +
|
|
1451
1557
|
"let the user choose a substitute, broader search, or another site before " +
|
|
@@ -1491,9 +1597,11 @@ export const provisionFinishTool = {
|
|
|
1491
1597
|
description: "Finish an operate task and close its session. outcome.kind='none' closes " +
|
|
1492
1598
|
"without a reported outcome (and remains the default for compatibility); " +
|
|
1493
1599
|
"'credentials' extracts and vault-stores a credential using required `store`; " +
|
|
1494
|
-
"'result' reports required `summary` or `data
|
|
1495
|
-
"
|
|
1496
|
-
"
|
|
1600
|
+
"'result' reports required `summary` or `data`; it succeeds only when verify_recipe " +
|
|
1601
|
+
"confirms or data.confirmed is true. " +
|
|
1602
|
+
"All credential values remain server-side; after Chrome closes, its private per-session " +
|
|
1603
|
+
"profile is destroyed. An explicit successful outcome atomically saves portable login state " +
|
|
1604
|
+
"for a later task; no-outcome and failed closes preserve the prior snapshot.",
|
|
1497
1605
|
inputSchema: finishSchema,
|
|
1498
1606
|
jsonInputSchema: {
|
|
1499
1607
|
type: "object",
|
|
@@ -1661,7 +1769,6 @@ const useSchema = z
|
|
|
1661
1769
|
verb: OperatorVerbSchema.optional(),
|
|
1662
1770
|
service_url: z.string().url().optional(),
|
|
1663
1771
|
params: z.record(z.string().max(2000)).optional(),
|
|
1664
|
-
require_live_identity: z.boolean().optional(),
|
|
1665
1772
|
// After the host repairs one missed step, resume the same live session at
|
|
1666
1773
|
// replay.next_index instead of starting over.
|
|
1667
1774
|
session_id: z.string().min(1).optional(),
|
|
@@ -1721,7 +1828,6 @@ export const provisionUseTool = {
|
|
|
1721
1828
|
verb: { type: "string", enum: OperatorVerbSchema.options },
|
|
1722
1829
|
service_url: { type: "string" },
|
|
1723
1830
|
params: { type: "object" },
|
|
1724
|
-
require_live_identity: { type: "boolean" },
|
|
1725
1831
|
session_id: { type: "string" },
|
|
1726
1832
|
resume_from: { type: "integer" },
|
|
1727
1833
|
leg: { type: "string", enum: ["checkout"] },
|
|
@@ -1748,7 +1854,6 @@ export const provisionUseTool = {
|
|
|
1748
1854
|
const cold = await startProvisionSession({
|
|
1749
1855
|
serviceUrl: args.service_url,
|
|
1750
1856
|
consentInboxRead: await readInboxConsent(),
|
|
1751
|
-
...(args.require_live_identity === true ? { requireLiveIdentity: true } : {}),
|
|
1752
1857
|
...(api !== null ? { api } : {}),
|
|
1753
1858
|
});
|
|
1754
1859
|
if (cold.needs_user !== undefined)
|
|
@@ -1789,7 +1894,6 @@ export const provisionUseTool = {
|
|
|
1789
1894
|
const cold = await startProvisionSession({
|
|
1790
1895
|
serviceUrl: args.service_url,
|
|
1791
1896
|
consentInboxRead: await readInboxConsent(),
|
|
1792
|
-
...(args.require_live_identity === true ? { requireLiveIdentity: true } : {}),
|
|
1793
1897
|
...(api !== null ? { api } : {}),
|
|
1794
1898
|
});
|
|
1795
1899
|
if (cold.needs_user !== undefined)
|
|
@@ -1814,7 +1918,6 @@ export const provisionUseTool = {
|
|
|
1814
1918
|
consentInboxRead,
|
|
1815
1919
|
...(recipe.allowed_hosts.length > 0 ? { extraAllowedHosts: recipe.allowed_hosts } : {}),
|
|
1816
1920
|
hint: renderOperatorRecipeHint(recipe),
|
|
1817
|
-
...(args.require_live_identity === true ? { requireLiveIdentity: true } : {}),
|
|
1818
1921
|
...(api !== null ? { api } : {}),
|
|
1819
1922
|
});
|
|
1820
1923
|
if (started.needs_user !== undefined)
|
|
@@ -2128,13 +2231,14 @@ export const operateLoginTool = {
|
|
|
2128
2231
|
// operate_act's kinds and operate_recipe_save/run delegate to, and as direct handles
|
|
2129
2232
|
// for tests that pin down that folded behavior.
|
|
2130
2233
|
// operate_screenshot (2026-08-23) is a deliberate, narrow addition to this cut: a
|
|
2131
|
-
// read-only debugging instrument (page
|
|
2234
|
+
// read-only debugging instrument (the page's or one frame's real pixels) with no
|
|
2132
2235
|
// alias/kind it could fold into — operate_act's kinds all DO something; this only
|
|
2133
2236
|
// looks.
|
|
2134
2237
|
export const OPERATE_TOOLS = [
|
|
2135
2238
|
provisionStartTool,
|
|
2136
2239
|
provisionObserveTool,
|
|
2137
2240
|
provisionScreenshotTool,
|
|
2241
|
+
provisionObserveQueryTool,
|
|
2138
2242
|
provisionActTool,
|
|
2139
2243
|
operateRecipeSaveTool,
|
|
2140
2244
|
operateRecipeRunTool,
|