@unfenced-ai/sdk 0.1.0

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.
Files changed (59) hide show
  1. package/LICENSE +30 -0
  2. package/README.md +171 -0
  3. package/dist/client.d.ts +385 -0
  4. package/dist/client.d.ts.map +1 -0
  5. package/dist/client.js +349 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/credentials.d.ts +14 -0
  8. package/dist/credentials.d.ts.map +1 -0
  9. package/dist/credentials.js +17 -0
  10. package/dist/credentials.js.map +1 -0
  11. package/dist/fetch.d.ts +16 -0
  12. package/dist/fetch.d.ts.map +1 -0
  13. package/dist/fetch.js +387 -0
  14. package/dist/fetch.js.map +1 -0
  15. package/dist/http.d.ts +76 -0
  16. package/dist/http.d.ts.map +1 -0
  17. package/dist/http.js +250 -0
  18. package/dist/http.js.map +1 -0
  19. package/dist/index.d.ts +53 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +41 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/library.d.ts +17 -0
  24. package/dist/library.d.ts.map +1 -0
  25. package/dist/library.js +25 -0
  26. package/dist/library.js.map +1 -0
  27. package/dist/memory.d.ts +13 -0
  28. package/dist/memory.d.ts.map +1 -0
  29. package/dist/memory.js +45 -0
  30. package/dist/memory.js.map +1 -0
  31. package/dist/permissions.d.ts +29 -0
  32. package/dist/permissions.d.ts.map +1 -0
  33. package/dist/permissions.js +69 -0
  34. package/dist/permissions.js.map +1 -0
  35. package/dist/protocol.d.ts +296 -0
  36. package/dist/protocol.d.ts.map +1 -0
  37. package/dist/protocol.js +89 -0
  38. package/dist/protocol.js.map +1 -0
  39. package/dist/session.d.ts +97 -0
  40. package/dist/session.d.ts.map +1 -0
  41. package/dist/session.js +88 -0
  42. package/dist/session.js.map +1 -0
  43. package/dist/sessions.d.ts +131 -0
  44. package/dist/sessions.d.ts.map +1 -0
  45. package/dist/sessions.js +197 -0
  46. package/dist/sessions.js.map +1 -0
  47. package/dist/token-usage.d.ts +11 -0
  48. package/dist/token-usage.d.ts.map +1 -0
  49. package/dist/token-usage.js +34 -0
  50. package/dist/token-usage.js.map +1 -0
  51. package/dist/types.d.ts +1409 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +2 -0
  54. package/dist/types.js.map +1 -0
  55. package/dist/url-identity.d.ts +8 -0
  56. package/dist/url-identity.d.ts.map +1 -0
  57. package/dist/url-identity.js +18 -0
  58. package/dist/url-identity.js.map +1 -0
  59. package/package.json +44 -0
@@ -0,0 +1,1409 @@
1
+ /**
2
+ * The public domain and wire types of the Unfenced SDK.
3
+ *
4
+ * Split out of the client so a consumer that only wants the shapes — a dashboard
5
+ * that may take no runtime dependency on this package — can `import type` them,
6
+ * and so the client modules share one copy. Every name here is re-exported
7
+ * unchanged from the package entry (`index.ts`); this is a move, not a redesign.
8
+ */
9
+ import type { ExtractedDoc, FetchFailureMeta, FetchMeta, OutputFormat, Tier } from "./protocol.js";
10
+ export interface UnfencedOptions {
11
+ /** Where the server is. Defaults to http://127.0.0.1:8787. */
12
+ baseUrl?: string;
13
+ /** The access token, if the server sets UNFENCED_TOKEN. */
14
+ apiKey?: string;
15
+ /**
16
+ * Injectable fetch (for tests / non-standard runtimes). Defaults to global.
17
+ *
18
+ * Declared as what this client actually calls rather than as `typeof fetch`.
19
+ * It only ever passes a string URL, so demanding a function that also handles
20
+ * `URL` and `Request` objects asked callers to satisfy a contract the library
21
+ * never exercises — and made an ordinary test double, typed by the one shape
22
+ * it receives, fail to typecheck. The global `fetch` still satisfies this.
23
+ */
24
+ fetch?: (url: string, init?: RequestInit) => Promise<Response>;
25
+ /** Extra headers sent on every request — e.g. to attribute the calling client. */
26
+ headers?: Record<string, string>;
27
+ /**
28
+ * Cancel every request this client makes.
29
+ *
30
+ * Combined with each call's own deadline rather than replacing it, so a
31
+ * caller that aborts a whole task does not have to reach into per-call
32
+ * options, and a per-call `timeoutMs` still bounds a request this never
33
+ * aborts.
34
+ */
35
+ signal?: AbortSignal;
36
+ }
37
+ /** The protocol calls this `OutputFormat`; `Format` is the name the SDK shipped. */
38
+ export type Format = OutputFormat;
39
+ export interface FetchOptions {
40
+ format?: Format;
41
+ /**
42
+ * Stable creation key for safe replay if the POST acknowledgement is lost.
43
+ * When omitted, the SDK generates one per fetch call and reuses it only for
44
+ * its own bounded transport retry. Batch calls derive a per-URL suffix from
45
+ * this base key. The server retains keys for the job's normal 30-minute
46
+ * result lifetime, process-locally.
47
+ */
48
+ idempotencyKey?: string;
49
+ /** Batch only: maximum concurrent fetch jobs, 1-32. Default 3, matching the production tenant share. */
50
+ concurrency?: number;
51
+ /** Skip straight to a rendered browser instead of trying plain HTTP first. */
52
+ forceRender?: boolean;
53
+ /** Route this fetch through a proxy, e.g. "http://user:pass@host:8080". */
54
+ proxy?: string;
55
+ /**
56
+ * How long this fetch may take in total, in ms. Default 120s.
57
+ * Integer 0..2147483647. Zero returns timeout without submitting a job;
58
+ * invalid durations reject with RangeError before work starts.
59
+ *
60
+ * The WALL CLOCK, not the gap between polls. It used to bound only the poll
61
+ * loop's own check, which ran after each request returned — so a hung `POST
62
+ * /jobs` never reached the check at all and a hung `GET /jobs/:id` blocked
63
+ * forever. Against this repository's own documented failure (a Cloudflare
64
+ * quick tunnel dead at the edge while cloudflared is alive) `timeoutMs: 5000`
65
+ * meant "until the OS gives up". Every request now carries an AbortSignal cut
66
+ * from the remaining budget.
67
+ */
68
+ timeoutMs?: number;
69
+ /** Ignore our own back-off for this host — for a deliberate retest. */
70
+ force?: boolean;
71
+ /**
72
+ * Whether to tell the site what we are.
73
+ *
74
+ * `auto` (default) uses what has been learned about this host: sites that
75
+ * serve agents a purpose-built rendering get told, sites that refuse bots do
76
+ * not. Some publishers hand a declared agent strictly more than they hand a
77
+ * browser — g2 answers one with markdown carrying scores and summaries the
78
+ * human page does not show.
79
+ */
80
+ identify?: "auto" | "agent" | "stealth";
81
+ /** Ignore robots.txt, report its verdict, or obey it. Defaults to the identity-aware server policy. */
82
+ robots?: "ignore" | "report" | "obey";
83
+ /** Cap the content at this many words, counted in the page's own script. */
84
+ maxWords?: number;
85
+ /**
86
+ * The language to present, e.g. "en-US" (default), "he-IL", "ja-JP".
87
+ *
88
+ * Sets `Accept-Language` and the browser's own language, so the site chooses
89
+ * content in it. Without it the worker's geography decided: the same call
90
+ * answered in Hebrew from one worker and English from another, which is
91
+ * irreproducible and invisible to the caller.
92
+ */
93
+ locale?: string;
94
+ /**
95
+ * Collect links from the whole page rather than just the extracted article.
96
+ *
97
+ * Crawling needs the navigation the cleaner strips: a ministry front page
98
+ * yields six links from its content region and dozens from the page.
99
+ */
100
+ wholePageLinks?: boolean;
101
+ }
102
+ /**
103
+ * What a fetch actually returns, which is more than this used to say.
104
+ *
105
+ * The client passes the server's job result through untouched, and that result
106
+ * carries six fields — verified over the wire: `ok, url, format, content, doc,
107
+ * meta`. This type declared three of them. The missing one that matters is
108
+ * `content`: the clean page text the call was made to get. A consumer writing
109
+ * TypeScript could not reach `result.content` without a cast, on the primary
110
+ * method of the client library, while it sat there at runtime the whole time.
111
+ *
112
+ * The failure branch was thinner still, and it contradicted a promise the
113
+ * product makes in its own source: "an agent must be able to tell 'the site
114
+ * blocked us' from 'the page is empty' — every failure says which tier failed
115
+ * and why, in machine-readable form". `tier` was not in the type, so it did not
116
+ * say so here.
117
+ *
118
+ * `url`, `tier` and `status` are optional on a failure because not every
119
+ * failure comes from the server. A request rejected before it was sent, a job
120
+ * that never finished — those are the client's own, and they have no tier to
121
+ * report. Declaring them required would be the mirror of this bug: a type
122
+ * promising a field that is sometimes absent.
123
+ */
124
+ /** Whose fault it was. Mirrors the engine's own `Fault`. */
125
+ export type Fault = "site" | "network" | "us" | "caller" | "policy";
126
+ /** A door the server found and did not open. Mirrors the engine's `Offer`. */
127
+ export interface Offer {
128
+ need: "payment" | "compute" | "human";
129
+ because: string;
130
+ site: string;
131
+ ticket: string;
132
+ expiresAt: string;
133
+ expiresInMs: number;
134
+ quote: {
135
+ micros: number;
136
+ cents: number;
137
+ currency: "USD";
138
+ estimateMs: number;
139
+ payee: "unfenced" | "site";
140
+ capped: boolean;
141
+ };
142
+ accept: {
143
+ kind: "call";
144
+ tool: string;
145
+ arguments: Record<string, unknown>;
146
+ } | {
147
+ kind: "visit";
148
+ url: string;
149
+ why: string;
150
+ };
151
+ for: {
152
+ url: string;
153
+ title: string | null;
154
+ why: string;
155
+ };
156
+ }
157
+ /**
158
+ * Four outcomes: delivered content, a refusal, an offer, or an archived
159
+ * receipt whose full content has expired.
160
+ *
161
+ * `ok` is gone rather than kept alongside `outcome`. Keeping it would compile
162
+ * everywhere and route every offer into whichever branch each caller already
163
+ * had for failure — silently, and discovered by whoever is holding the bill.
164
+ */
165
+ export type FetchResult = {
166
+ outcome: "delivered";
167
+ /** The URL as asked for; `meta.finalUrl` is where it ended up. */
168
+ url: string;
169
+ format: Format;
170
+ /** The page, in the requested format. The reason for the call. */
171
+ content: string;
172
+ doc: ExtractedDoc;
173
+ meta: FetchMeta;
174
+ } | {
175
+ outcome: "offered";
176
+ url: string;
177
+ offer: Offer;
178
+ /** What was visible without paying: the abstract, page one, the teaser. */
179
+ partial?: ExtractedDoc;
180
+ meta?: FetchMeta;
181
+ } | {
182
+ outcome: "archived";
183
+ url: string;
184
+ /** The full result aged out; only the small history receipt remains. */
185
+ entry: {
186
+ id: string;
187
+ url: string;
188
+ ok: boolean;
189
+ fetchedAt: string;
190
+ title?: string;
191
+ error?: string;
192
+ [field: string]: unknown;
193
+ };
194
+ } | {
195
+ outcome: "failed";
196
+ error: string;
197
+ fault?: Fault;
198
+ detail?: string;
199
+ /** Server advice for a refused request, when one was supplied. */
200
+ remedy?: string;
201
+ /** Server-supplied delay before retrying, in milliseconds. */
202
+ retryAfterMs?: number;
203
+ url?: string;
204
+ /** Which tier failed. Absent on a failure the client produced itself. */
205
+ tier?: Tier;
206
+ /** The HTTP status, when the site answered with one. */
207
+ status?: number;
208
+ /**
209
+ * Not `Partial<FetchMeta>`: a failure carries `retryAfter`/`retryAfterMs`,
210
+ * which a success never does, and carries none of the fields that only
211
+ * exist once a document was produced. Still optional, because a failure
212
+ * the client produced itself never reached the server and has no meta.
213
+ */
214
+ meta?: FetchFailureMeta;
215
+ };
216
+ /**
217
+ * A pointer action addressed by the words visible on the target, rather than by a
218
+ * ref from a snapshot.
219
+ *
220
+ * For the choices a page renders without declaring them as controls — they appear
221
+ * in `excerpt` and never in `controls`, so no ref exists for them. The engine
222
+ * resolves the words to an ordinary ref before any guard runs, so these are
223
+ * gated exactly as a ref-addressed action is.
224
+ */
225
+ /** A spot inside a target, as fractions from 0 to 1 of its box. */
226
+ export interface Spot {
227
+ x: number;
228
+ y: number;
229
+ }
230
+ /**
231
+ * A pointer action aimed at a SPOT rather than at a thing.
232
+ *
233
+ * For anything whose meaning is a position: a slider track, a video scrubber, a
234
+ * canvas, a map, a chart. Combine `at` with `on` or `ref` to say which thing;
235
+ * with neither it is a fraction of the window. The engine hit-tests the pixel and
236
+ * judges whatever is actually under it, so these are gated at least as strictly
237
+ * as a ref-addressed action.
238
+ */
239
+ export type AtAction = {
240
+ kind: "click";
241
+ at: Spot;
242
+ ref?: string;
243
+ on?: string;
244
+ confirm?: boolean;
245
+ button?: "left" | "middle";
246
+ } | {
247
+ kind: "dblclick";
248
+ at: Spot;
249
+ ref?: string;
250
+ on?: string;
251
+ confirm?: boolean;
252
+ } | {
253
+ kind: "rightclick";
254
+ at: Spot;
255
+ ref?: string;
256
+ on?: string;
257
+ confirm?: boolean;
258
+ } | {
259
+ kind: "hover";
260
+ at: Spot;
261
+ ref?: string;
262
+ on?: string;
263
+ }
264
+ /**
265
+ * The one whose ADDRESS is usually a spot and nothing else: a map tile, an
266
+ * empty part of a scrolling pane, a chart. `by` is how far down, `across` how
267
+ * far sideways, and the modifiers are held across the gesture — Control+wheel
268
+ * is how a map zooms.
269
+ */
270
+ | {
271
+ kind: "wheel";
272
+ at: Spot;
273
+ ref?: string;
274
+ on?: string;
275
+ within?: string;
276
+ by: number;
277
+ across?: number;
278
+ modifiers?: readonly ("Shift" | "Control" | "Meta")[];
279
+ }
280
+ /**
281
+ * THE WRITING VERBS A COORDINATE CAN AIM, mirrored from core.
282
+ *
283
+ * Rich text is the case: a contenteditable is the field, and everything a
284
+ * pixel can land on inside it is a p or a span, so a position is the only
285
+ * address the surface has. The engine has always hit-tested `at` for any kind
286
+ * and judged the element actually under it, so the capability was real over
287
+ * the wire while this type said the call did not type-check — which meant a
288
+ * spot-addressed `type` into a canvas-hosted editor, and `contentBase64` on a
289
+ * spot-addressed upload, were unreachable from this package alone.
290
+ *
291
+ * NOT the `fill_*` verbs, deliberately: a credential resolved from a
292
+ * coordinate is a secret typed into whatever happened to be under a pixel.
293
+ */
294
+ | {
295
+ kind: "type";
296
+ at: Spot;
297
+ ref?: string;
298
+ on?: string;
299
+ within?: string;
300
+ text: string;
301
+ submit?: boolean;
302
+ append?: boolean;
303
+ confirm?: boolean;
304
+ } | {
305
+ kind: "select";
306
+ at: Spot;
307
+ ref?: string;
308
+ on?: string;
309
+ within?: string;
310
+ value: string | string[];
311
+ } | {
312
+ kind: "paste";
313
+ at: Spot;
314
+ ref?: string;
315
+ on?: string;
316
+ within?: string;
317
+ text?: string;
318
+ } | {
319
+ kind: "upload";
320
+ at: Spot;
321
+ ref?: string;
322
+ on?: string;
323
+ within?: string;
324
+ filename?: string;
325
+ content?: string;
326
+ contentBase64?: string;
327
+ download?: string;
328
+ };
329
+ export type OnAction = {
330
+ kind: "click";
331
+ on: string;
332
+ within?: string;
333
+ confirm?: boolean;
334
+ button?: "left" | "middle";
335
+ } | {
336
+ kind: "dblclick";
337
+ on: string;
338
+ within?: string;
339
+ confirm?: boolean;
340
+ } | {
341
+ kind: "rightclick";
342
+ on: string;
343
+ within?: string;
344
+ confirm?: boolean;
345
+ } | {
346
+ kind: "hover";
347
+ on: string;
348
+ within?: string;
349
+ }
350
+ /** Scroll a pane by naming something inside it, when the pane itself has no
351
+ * name of its own. `by` is how far down, `across` how far sideways. */
352
+ | {
353
+ kind: "wheel";
354
+ on: string;
355
+ within?: string;
356
+ by: number;
357
+ across?: number;
358
+ }
359
+ /**
360
+ * THE WRITING VERBS BY THE WORDS ON THE PAGE, mirrored from core.
361
+ *
362
+ * A field is often labelled and never named — "Attach a file" is the words a
363
+ * person reads, and the `<input type=file>` behind it has no accessible name
364
+ * of its own; the same is true of half the text inputs on the web. Naming the
365
+ * label resolves to the field before any guard runs, exactly as it does for a
366
+ * ref, so these gate identically.
367
+ *
368
+ * `type` and `select` were the ones this package could not express, and they
369
+ * are the half of `on` that matters most: an agent could click "3 people" and
370
+ * could not type into "Full name". The wire has accepted both since the
371
+ * server's `worded()` learned them — only this declaration said otherwise.
372
+ */
373
+ | {
374
+ kind: "type";
375
+ on: string;
376
+ within?: string;
377
+ text: string;
378
+ submit?: boolean;
379
+ append?: boolean;
380
+ confirm?: boolean;
381
+ }
382
+ /** `values` is the plural spelling, for a `<select multiple>`; both land on
383
+ * core's one field, because "what to choose" is one idea. */
384
+ | {
385
+ kind: "select";
386
+ on: string;
387
+ within?: string;
388
+ value?: string;
389
+ values?: readonly string[];
390
+ } | {
391
+ kind: "paste";
392
+ on: string;
393
+ within?: string;
394
+ text?: string;
395
+ confirm?: boolean;
396
+ } | {
397
+ kind: "upload";
398
+ on: string;
399
+ within?: string;
400
+ filename?: string;
401
+ content?: string;
402
+ contentBase64?: string;
403
+ download?: string;
404
+ };
405
+ /** One control or link on a live page. */
406
+ export interface SnapshotElement {
407
+ ref: string;
408
+ role: string;
409
+ name: string;
410
+ value?: string;
411
+ /** True when `value` was cut to length: it is a PREFIX of what the field holds.
412
+ * Without it, checking a long write against what came back looks like a failed
413
+ * write on a field that is correct. */
414
+ valueTruncated?: boolean;
415
+ disabled?: boolean;
416
+ /**
417
+ * A count that sat in its own element beside the name, e.g. "232" on a
418
+ * "Summer 2026" filter — split out because textContent glues them together.
419
+ */
420
+ badge?: string;
421
+ /** True when `name` was cut to length: it is a prefix, not the whole value. */
422
+ truncated?: boolean;
423
+ /** True when this holds a credential — typing here is refused. */
424
+ secret?: boolean;
425
+ /** Whether a secret field has something in it. Never what. */
426
+ filled?: boolean;
427
+ /** True when acting here submits a form — requires confirm. */
428
+ submits?: boolean;
429
+ /**
430
+ * Why it submits: `type=submit` is written on the control, while a bare
431
+ * `<button>` inside a form is the form's default one and says nothing at all.
432
+ * Present only when `submits` is true.
433
+ */
434
+ submitsBecause?: "type=submit" | "default button in a form";
435
+ /**
436
+ * WIDGET STATE, mirrored from core's `SnapshotElement`. Absent means the page
437
+ * did not say — never that the answer is false.
438
+ */
439
+ /** Is this menu / accordion / picker currently open? */
440
+ expanded?: boolean;
441
+ /** Clicking this opens a popup rather than acting: "menu", "listbox",
442
+ * "dialog", "tree", "grid". Tells you to click and then look for the choices,
443
+ * instead of trying `select` on something that is not a `<select>`. */
444
+ hasPopup?: string;
445
+ /** Checkbox, radio, switch or menu item: is it on? */
446
+ checked?: boolean;
447
+ /** Tab, option or treeitem: is this the chosen one? */
448
+ selected?: boolean;
449
+ /** Takes MORE THAN ONE value — `<input type=file multiple>` or `<select
450
+ * multiple>`. Both REPLACE rather than accumulate, so adding items one call at
451
+ * a time silently ends with the last one. */
452
+ multiple?: boolean;
453
+ /** Which embedded document this lives in — the frame's name, or its host.
454
+ * Absent for the page's own, which is where most controls are. The merged
455
+ * list is flat, so this is the only thing separating a page's "Continue"
456
+ * from an embedded checkout's. */
457
+ frame?: string;
458
+ }
459
+ /** What an agent can see and act on, per observe. */
460
+ /**
461
+ * Why a page did or did not open carrying the account's sign-in-provider session.
462
+ *
463
+ * `carried` is the good case. `agent-scoped` and `fresh-signin` are DECISIONS,
464
+ * not faults. `held-elsewhere` means another machine legitimately holds it — a
465
+ * provider session may live in exactly one browser at a time, because a
466
+ * duplicate gets BOTH invalidated. `store-unavailable` is an outage — or a
467
+ * stored provider profile the worker could not open, which is the same answer to
468
+ * the same question — and `none-stored` an account that has never connected one.
469
+ *
470
+ * All of them render as the same logged-out page, which is why they are named: a
471
+ * caller reading this can tell "correctly refused" from "try again shortly" from
472
+ * "the owner has to sign in once", instead of guessing from a screenshot.
473
+ */
474
+ export type ProviderSessionReason = "carried" | "agent-scoped" | "fresh-signin" | "none-stored" | "held-elsewhere" | "store-unavailable";
475
+ /**
476
+ * One thing waiting on the account owner — a sign-in, a 2FA code, a consent, a
477
+ * per-action approval, or a site that needs its first provider login.
478
+ */
479
+ /**
480
+ * Every wall the server can raise, mirrored from `server/src/interrupts.ts`.
481
+ *
482
+ * Declared as a union rather than `string` so a consumer that maps kinds to
483
+ * something a person reads is checked for exhaustiveness by `tsc` instead of
484
+ * silently falling through to the raw slug. `interrupt-kinds.test.ts` binds
485
+ * this list to the producer's, because a mirror that cannot drift is worth
486
+ * more here than one more `string`.
487
+ */
488
+ export type InterruptKind = "credential" | "otp" | "captcha" | "consent" | "approval" | "grant" | "site-login" | "wait" | "watch";
489
+ export interface PendingApproval {
490
+ id: string;
491
+ kind: InterruptKind;
492
+ host: string;
493
+ url?: string | null;
494
+ reason?: string | null;
495
+ status: string;
496
+ createdAt: string;
497
+ expiresAt: string;
498
+ }
499
+ /**
500
+ * One saveable picture, video or embed, present only when `observe`/`read` was
501
+ * called with `media: true`. A content image is not a control and has no ref
502
+ * anywhere else, so this is the handle `save` is pointed at. `w`/`h` are the
503
+ * rendered size; the scan skips anything under 64x64.
504
+ */
505
+ export interface MediaItem {
506
+ ref: string;
507
+ kind: "image" | "video" | "audio" | "embed";
508
+ /** Its alt text, or a filename derived from the URL. */
509
+ name: string;
510
+ w: number;
511
+ h: number;
512
+ }
513
+ export interface PageSnapshot {
514
+ url: string;
515
+ title: string;
516
+ controls: SnapshotElement[];
517
+ links: SnapshotElement[];
518
+ excerpt: string;
519
+ truncated: {
520
+ controls: number;
521
+ links: number;
522
+ media?: number;
523
+ };
524
+ /**
525
+ * Saveable media, present ONLY when `media: true` was asked for. Each entry's
526
+ * `ref` goes straight to `save`. Off by default — it is extra payload the
527
+ * ordinary reading does not carry.
528
+ */
529
+ media?: MediaItem[];
530
+ /**
531
+ * A few examples of what the page DRAWS as clickable and never declared, so
532
+ * they carry no ref and can only be named by their words. Absent when the page
533
+ * declares everything it means, which is what keeps it a signal.
534
+ */
535
+ undeclared?: string[];
536
+ /**
537
+ * Why this reading may not be enough on its own, when it is not: a screen that is mostly
538
+ * canvas or picture, or several controls the page never named. Names see_page as the
539
+ * remedy. Absent whenever the reading stands alone, which is most pages.
540
+ */
541
+ look?: {
542
+ code: "pictorial" | "unnamed";
543
+ reason: string;
544
+ };
545
+ /**
546
+ * The embedded documents this reading could and could not see. Absent on a page
547
+ * with no frames.
548
+ *
549
+ * A skipped frame is a whole document missing from the answer with nothing to
550
+ * mark its absence — which reads exactly like a page that has no such control.
551
+ * `more.reason` says the same thing in words when this is non-empty.
552
+ */
553
+ frames?: {
554
+ included: number;
555
+ skipped: Array<{
556
+ host: string;
557
+ /** cross-origin is a legacy label; unreadable does not guess the cause. */
558
+ reason: "cross-origin" | "unreadable" | "detached" | "over-limit";
559
+ }>;
560
+ };
561
+ /** The page had not finished loading when this was read. Absent means it had.
562
+ * A thin reading with an innocent explanation, and a different remedy from
563
+ * every other thin reading: observe again in a moment. */
564
+ loading?: boolean;
565
+ /** Identifies the document these refs belong to; refs go stale on navigation. */
566
+ document: string;
567
+ }
568
+ /**
569
+ * A picture of a page with the things that can be acted on drawn onto it.
570
+ *
571
+ * `marks` and `controls` describe the same instant: mark N is controls[N - 1].
572
+ * A control with no mark is scrolled out of view or covered, and `note` says so
573
+ * when that happens — it is still listed and still actable by ref.
574
+ */
575
+ /**
576
+ * A reading, plus a picture when the reading says it needs one.
577
+ *
578
+ * `picture` is present only when the snapshot carries `look` — a screen that is
579
+ * mostly canvas or picture, or controls the page never named — and only for the
580
+ * first reading that hits a given wall on a given document.
581
+ */
582
+ export interface ReadPage {
583
+ page: PageSnapshot;
584
+ /** JPEG data URI, with every control outlined and numbered. */
585
+ picture?: string;
586
+ /** Mark N is the Nth control. Empty unless `picture` is present. */
587
+ marks?: Array<{
588
+ mark: number;
589
+ ref: string;
590
+ role: string;
591
+ name: string;
592
+ }>;
593
+ /** Why some controls have no box. */
594
+ pictureNote?: string;
595
+ }
596
+ /** One field in a batched fill. Needs a `ref` or an `on`. */
597
+ export interface FormField {
598
+ ref?: string;
599
+ /** The field's visible words — its label, or its placeholder. */
600
+ on?: string;
601
+ /** Narrow `on` to one region, when a page has two of the same field. */
602
+ within?: string;
603
+ text: string;
604
+ /** Add to what is there instead of replacing it. */
605
+ append?: boolean;
606
+ }
607
+ export interface FormFillResult {
608
+ ok: boolean;
609
+ /** One entry per field, in the order sent, whether or not it worked. */
610
+ filled: Array<{
611
+ target: string;
612
+ ok: boolean;
613
+ code?: string;
614
+ reason?: string;
615
+ }>;
616
+ page: PageSnapshot;
617
+ }
618
+ export interface SeenPage {
619
+ /** JPEG data URI of the current viewport. */
620
+ image: string;
621
+ marks: Array<{
622
+ mark: number;
623
+ ref: string;
624
+ role: string;
625
+ name: string;
626
+ }>;
627
+ controls: SnapshotElement[];
628
+ /**
629
+ * The page's links, present only when `links` was asked for.
630
+ *
631
+ * Beside `controls` rather than inside it, because `controls` has meant "the
632
+ * things this page declares as controls" since the first version and a caller
633
+ * resolving a mark to a list entry looks in both.
634
+ */
635
+ links?: SnapshotElement[];
636
+ /** What the page draws as clickable and never declared. No ref, so no box. */
637
+ undeclared?: string[];
638
+ note?: string;
639
+ }
640
+ /** A file a page handed to the browser during a session. */
641
+ export interface DownloadInfo {
642
+ filename: string;
643
+ bytes: number;
644
+ at: string;
645
+ fromUrl: string;
646
+ /** Set instead of the rest when the transfer failed. */
647
+ failed?: string;
648
+ }
649
+ /**
650
+ * WHY AN ACT ANSWERED WHAT IT ANSWERED, as a value rather than a sentence.
651
+ *
652
+ * `reason` is written for a reader and is reworded whenever a clearer sentence
653
+ * is found; this is written for a `switch` and is not. The server has sent it on
654
+ * every act since codes existed and this type did not declare it, so the field
655
+ * arrived, type-checked as nothing, and was invisible to every SDK caller — the
656
+ * same hole `credentialRequired` had one layer up, with the same cause.
657
+ *
658
+ * A MIRROR OF `ACT_CODES` in `@unfenced/core`, which is the authority. This
659
+ * package has zero dependencies on purpose — it runs in a browser — so it cannot
660
+ * import the union, and a copy that could not follow its original is exactly the
661
+ * defect this repo keeps finding. So it is BOUND BY A CHECK instead:
662
+ * `scripts/docs-check.ts` compares the two lists in both directions and goes red
663
+ * on the first one to drift.
664
+ *
665
+ * Optional because an older server answers without it, never because a newer one
666
+ * might omit it.
667
+ */
668
+ export type ActCode = "ok" | "no-effect" | "session-busy" | "permission-required" | "agent-scope-required" | "approval-required" | "confirmation-required" | "credential-required" | "credential-wrong-site" | "otp-required" | "site-login-required" | "no-credential" | "wrong-credential-kind" | "no-2fa" | "bad-totp-seed" | "provider-login" | "not-a-one-time-code" | "act-ambiguous" | "within-ambiguous" | "within-not-found" | "target-not-found" | "target-too-common" | "ref-stale" | "ref-malformed" | "wrong-element-kind" | "spot-invalid" | "bad-action" | "no-window" | "target-not-ready" | "target-covered" | "target-no-size" | "target-disabled" | "value-rejected" | "write-failed" | "page-moved" | "page-unresponsive" | "blocked-target" | "navigate-failed" | "no-history" | "no-session" | "tenant-session-limit" | "quota-exceeded" | "park-full" | "engine-error";
669
+ /**
670
+ * What an act changed, as a summary rather than a second copy of the page.
671
+ *
672
+ * The mirror of core's `PageChanges`. Attached to an act as `changes` when
673
+ * something moved and the change is describable; a navigation reports `redrawn`
674
+ * instead of listing every control twice. A SUMMARY, NOT A SUBSTITUTE — refs
675
+ * come from `page`, and a diff carries none for what did not change.
676
+ */
677
+ export interface PageChanges {
678
+ url?: {
679
+ from: string;
680
+ to: string;
681
+ };
682
+ title?: {
683
+ from: string;
684
+ to: string;
685
+ };
686
+ /** Controls that were not there before. */
687
+ appeared?: SnapshotElement[];
688
+ /** What vanished, by name — a ref that is gone cannot be acted on anyway. */
689
+ vanished?: string[];
690
+ /** Same element, different words — how a page says it accepted a choice. */
691
+ renamed?: Array<{
692
+ ref: string;
693
+ from: string;
694
+ to: string;
695
+ }>;
696
+ /** Words on the page now that were not on it before. */
697
+ newText?: string;
698
+ /** The page was replaced rather than adjusted. Read the snapshot instead. */
699
+ redrawn?: boolean;
700
+ /**
701
+ * A wall that just appeared — the act ran into a block page. `blocked` also
702
+ * rides on `page`, but a `brief:true` act drops `page` and keeps only this
703
+ * diff, so a submit that navigated into an "Access Denied" would otherwise
704
+ * lose the structured signal. Set only on the transition into a block.
705
+ */
706
+ blocked?: {
707
+ by: string;
708
+ reason: string;
709
+ };
710
+ }
711
+ export type ActEvidenceKind = "navigation" | "dom-change" | "popup" | "dialog" | "download" | "postcondition";
712
+ export interface ActEvidence {
713
+ kind: ActEvidenceKind;
714
+ }
715
+ export interface ActExpectation {
716
+ text?: string;
717
+ gone?: string;
718
+ /** A substring or `*` glob matched against the resulting URL. */
719
+ url?: string;
720
+ ms?: number;
721
+ }
722
+ export interface ActResult {
723
+ ok: boolean;
724
+ /** Privacy-safe evidence categories observed while completing the act. */
725
+ evidence?: ActEvidence[];
726
+ /** Why the action was refused — actionable, recoverable. */
727
+ reason?: string;
728
+ /**
729
+ * The same answer as `reason`, from a closed set. Present on successes too
730
+ * (`"ok"`, or `"no-effect"` when the act landed and the page did not move), so
731
+ * a caller can group every act by outcome without special-casing absence.
732
+ */
733
+ code?: ActCode;
734
+ /**
735
+ * What the page asked NATIVELY during this act: alert, confirm, prompt or
736
+ * beforeunload. Reported whether or not it changed the outcome, because a
737
+ * dismissed confirm is the case that otherwise looks like a successful no-op.
738
+ */
739
+ dialogs?: Array<{
740
+ type: string;
741
+ message: string;
742
+ handled: "accepted" | "dismissed";
743
+ }>;
744
+ /** Something worth knowing about an otherwise successful act. Not `reason`,
745
+ * which means the action was refused. */
746
+ note?: string;
747
+ /**
748
+ * Whether the page had stopped changing when this reading was taken. Sent
749
+ * only as `false`, and only when the wait after the act hit its ceiling — a
750
+ * page that settled says nothing.
751
+ *
752
+ * It sits on a successful act deliberately, like `expected.held` above: the
753
+ * act landed, and the snapshot beside it is of a page still drawing. Observe
754
+ * again if the next act depends on something that was still arriving.
755
+ */
756
+ settled?: boolean;
757
+ /** How long that wait took, in milliseconds. Sent with `settled: false` and
758
+ * never on its own. */
759
+ settleMs?: number;
760
+ /**
761
+ * Whether a post-condition supplied as `expect` held, and how long it was
762
+ * waited for. `held: false` sits on a successful act deliberately: the act
763
+ * landed and the page did something else, which is a different problem from
764
+ * the act not landing.
765
+ */
766
+ expected?: {
767
+ held: boolean;
768
+ waitedMs: number;
769
+ };
770
+ /**
771
+ * PRESENT ONLY WHEN THE PAGE DID NOT MOVE — the act landed and the page read
772
+ * exactly the same afterwards, watched for `waitedMs`.
773
+ *
774
+ * An observation, not a verdict. A slow page and a dead button are
775
+ * indistinguishable from here, so the answer is never to repeat the act:
776
+ * observe again, or look. The note says as much, and says it more strongly
777
+ * when the act submits.
778
+ */
779
+ effect?: {
780
+ changed: false;
781
+ waitedMs: number;
782
+ note: string;
783
+ };
784
+ /**
785
+ * WHAT THIS ACT CHANGED, as a summary rather than a second copy of the page.
786
+ *
787
+ * Present when something moved and the change is describable; a navigation
788
+ * reports `redrawn` instead. Asked for with `brief`, which drops `page` in its
789
+ * favour — so `changes.blocked` is where a block reached by a brief submit
790
+ * surfaces. A SUMMARY, NOT A SUBSTITUTE: refs come from `page`.
791
+ */
792
+ changes?: PageChanges;
793
+ /**
794
+ * What the server can do, attached to REFUSALS.
795
+ *
796
+ * A tool description is cached when a client connects, so one that connected
797
+ * before a capability shipped cannot see it and will misread the page as
798
+ * impossible. `targeting` lists the ways this engine accepts a target; a mode
799
+ * here that your client has no parameter for means the client is stale.
800
+ */
801
+ engine?: {
802
+ version: string;
803
+ commit: string;
804
+ targeting: readonly string[];
805
+ note: string;
806
+ };
807
+ /** The several things that read the same, when `on` did not name exactly one.
808
+ * Each carries a ref, so choosing costs one round trip. `frame` is set when
809
+ * a candidate is in an embedded document rather than the page's own — the
810
+ * distinction `within` cannot draw. */
811
+ candidates?: Array<{
812
+ ref: string;
813
+ role: string;
814
+ name: string;
815
+ within?: string;
816
+ frame?: string;
817
+ }>;
818
+ /** What a words-addressed target turned out to be, so the next act on the same
819
+ * thing can use the ref. */
820
+ resolved?: {
821
+ ref: string;
822
+ name: string;
823
+ role: string;
824
+ within?: string;
825
+ };
826
+ /** Set when the action needs { confirm: true } to proceed. */
827
+ confirmationRequired?: string;
828
+ /** Set when the site is not on the act-allowlist — carries the site to allow. */
829
+ permissionRequired?: string;
830
+ /**
831
+ * Set when the action was refused because the field holds a credential.
832
+ *
833
+ * Its own signal rather than a `reason` string, because it needs its own
834
+ * advice: do not ask the user for the password, and do not retry. The core
835
+ * has carried this since the refusal was fixed and this type did not, so an
836
+ * SDK caller could not tell a credential refusal from any other failure —
837
+ * which is the same hole one layer up.
838
+ */
839
+ credentialRequired?: string;
840
+ /**
841
+ * Set alongside `credentialRequired` when the refused field is a ONE-TIME-CODE
842
+ * field, not a password — so the remedy is "enter this 2FA code once" (via
843
+ * fill_otp, or by taking the wheel), not "add a login". Never a value.
844
+ */
845
+ otpField?: boolean;
846
+ /** Set when the action needs the account owner's approval to act on the site. */
847
+ approvalRequired?: string;
848
+ /**
849
+ * WHAT THE AUTHORITY GATE DECIDED — present on an allow as well as a refusal.
850
+ *
851
+ * MIRRORED HERE DELIBERATELY. This package hand-copies the engine's result
852
+ * shape, so a field the engine adds and this file does not arrives over the
853
+ * wire, type-checks nowhere, and is dropped before any SDK or MCP caller can
854
+ * see it — silently, which is the failure mode the engine's own comment on
855
+ * this type warns about at length. The server writes its own audit row from
856
+ * the engine's value, so a receipt does not depend on this copy; a CALLER
857
+ * wanting to show why an act was permitted does.
858
+ *
859
+ * ABSENT WHEN THE GATE DID NOT RUN — a scroll, a hover, an ordinary wheel.
860
+ * Absent is not an allow: it means no decision was taken, and a caller that
861
+ * renders it as permission is reporting something the engine never said.
862
+ */
863
+ authorization?: {
864
+ decision: "allow" | "deny" | "require-approval";
865
+ /** The hostname the gate judged. */
866
+ host?: string;
867
+ /**
868
+ * The allowlist key the verdict was decided by. Absent when the allowlist
869
+ * was never consulted — an any-site credential, or a sign-in provider a
870
+ * login is continuing on — so its presence is the stronger claim.
871
+ */
872
+ site?: string;
873
+ deniedBy?: "account" | "agent";
874
+ /**
875
+ * A PERSON CLEARED THIS ONE. Set only alongside
876
+ * `decision: "require-approval"`, and deliberately not flattened into an
877
+ * ordinary allow: "a standing grant covered it" and "a human looked at
878
+ * this act and said yes" are different claims, and the second is the one
879
+ * worth forwarding to somebody who does not trust you.
880
+ */
881
+ approved?: true;
882
+ };
883
+ /**
884
+ * A deep link that opens the dashboard already set up to clear this wall — a
885
+ * missing login opens the Add-login drawer for the host, a missing permission
886
+ * the Sites grant. Present on a credential/permission/approval refusal so the
887
+ * caller can offer the user a click instead of an instruction. Carries only the
888
+ * blocked host, never a secret.
889
+ */
890
+ setupUrl?: string;
891
+ /** Files the page handed to the browser while this action ran. */
892
+ downloads?: DownloadInfo[];
893
+ /** The page after the action. */
894
+ page?: PageSnapshot;
895
+ /**
896
+ * A marked picture of the instant `page` describes. Present only when the call
897
+ * asked for it with `see`.
898
+ *
899
+ * ONE instant, which act-then-see_page cannot promise: that is two calls, and
900
+ * a page may move between them with nothing in either answer saying so. Carries
901
+ * no reading of its own — that is `page` above.
902
+ */
903
+ view?: {
904
+ /** JPEG data URI of the viewport, with the controls outlined and numbered. */
905
+ image: string;
906
+ /** Mark N is the Nth control in `page.controls`. */
907
+ marks?: Array<{
908
+ mark: number;
909
+ ref: string;
910
+ role: string;
911
+ name: string;
912
+ }>;
913
+ /** Why some controls have no box. */
914
+ note?: string;
915
+ };
916
+ }
917
+ export type Action = {
918
+ kind: "click";
919
+ ref: string;
920
+ confirm?: boolean;
921
+ modifiers?: readonly ("Shift" | "Control" | "Meta")[];
922
+ /** Which button. `middle` opens a link in a background tab, which shows
923
+ * up in `windows` like any other popup. A right click is `rightclick`. */
924
+ button?: "left" | "middle";
925
+ } | {
926
+ kind: "type";
927
+ ref: string;
928
+ text: string;
929
+ submit?: boolean;
930
+ confirm?: boolean;
931
+ /** Add to the field instead of replacing it. Replacing is the default. */
932
+ append?: boolean;
933
+ }
934
+ /** Choose in a dropdown. One value, or several for a `<select multiple>` — it
935
+ * sets the whole selection rather than adding to it. */
936
+ | {
937
+ kind: "select";
938
+ ref: string;
939
+ value: string | string[];
940
+ } | {
941
+ kind: "press";
942
+ key: string;
943
+ confirm?: boolean;
944
+ } | {
945
+ kind: "scroll";
946
+ to: "top" | "bottom" | {
947
+ ref: string;
948
+ };
949
+ }
950
+ /** Move between the windows this session holds. 0 is the page you opened. */
951
+ | {
952
+ kind: "switch";
953
+ to: number;
954
+ }
955
+ /**
956
+ * Close one of the windows this session holds — the other half of `switch`.
957
+ *
958
+ * For a popup a flow left behind: an OAuth window that did not close itself,
959
+ * a print preview, a chat tab. `0` is refused, because window 0 is the page
960
+ * the session was opened on and closing that is closing the session.
961
+ */
962
+ | {
963
+ kind: "close_window";
964
+ to: number;
965
+ }
966
+ /** Move the pointer onto something without pressing it — for hover menus. */
967
+ | {
968
+ kind: "hover";
969
+ ref: string;
970
+ }
971
+ /**
972
+ * Scroll with a real wheel, optionally over one element.
973
+ *
974
+ * Positive `by` scrolls content downward, the way a wheel pulled toward you
975
+ * does; `across` is the sideways half, for a pane that scrolls that way. The
976
+ * modifiers are held across the gesture — Control+wheel is how a map zooms.
977
+ *
978
+ * It was declared for `on` and for `at` and NOT here, so the one address a
979
+ * caller usually has for a scrollable pane — the ref of something inside it,
980
+ * straight out of the last snapshot — did not typecheck, while the wire had
981
+ * accepted it since the verb shipped. `action-kinds.test.ts` now compares all
982
+ * three unions against core's own, so the next verb cannot arrive in two of
983
+ * three places again.
984
+ */
985
+ | {
986
+ kind: "wheel";
987
+ by: number;
988
+ across?: number;
989
+ ref?: string;
990
+ modifiers?: readonly ("Shift" | "Control" | "Meta")[];
991
+ }
992
+ /**
993
+ * Wait for the page to say something, go somewhere, or show something —
994
+ * rather than for a fixed time.
995
+ *
996
+ * `url` is a substring of the URL, or a `*` glob against the whole of it: the
997
+ * honest predicate for a sign-in or a checkout, which are defined by arriving
998
+ * somewhere. `visible` beside `ref` asks only whether the element is THERE,
999
+ * skipping the cover check a click needs. Everything sent has to hold.
1000
+ */
1001
+ | {
1002
+ kind: "wait";
1003
+ text?: string;
1004
+ gone?: string;
1005
+ ref?: string;
1006
+ url?: string;
1007
+ visible?: boolean;
1008
+ ms?: number;
1009
+ }
1010
+ /**
1011
+ * Put a file into a file input.
1012
+ *
1013
+ * `content` writes one from text you supply, `contentBase64` from bytes you
1014
+ * supply (10MB decoded, for a PNG, a PDF, a zip); `download` re-uses a file
1015
+ * this session downloaded. There is deliberately no path parameter — an
1016
+ * upload can only send bytes the caller already had.
1017
+ */
1018
+ | {
1019
+ kind: "upload";
1020
+ ref: string;
1021
+ filename?: string;
1022
+ content?: string;
1023
+ contentBase64?: string;
1024
+ download?: string;
1025
+ }
1026
+ /** Double-click an element — the real mouse, at the ref's point, like click. */
1027
+ /**
1028
+ * `confirm` is carried on all three pointer verbs, not just click.
1029
+ *
1030
+ * The guard asks one predicate over click, dblclick and rightclick before any
1031
+ * of them activates a submit control. Omitting it here — while the MCP layer
1032
+ * builds it with a SPREAD, which slips past tsc's excess-property check —
1033
+ * meant the refusal said "re-send with confirm: true" and the re-sent call
1034
+ * left byte-identical to the first. An agent doing exactly what it was told
1035
+ * could not read its way out.
1036
+ */
1037
+ | {
1038
+ kind: "dblclick";
1039
+ ref: string;
1040
+ confirm?: boolean;
1041
+ }
1042
+ /** Context-menu click — the real right button, opening the page's own menu. */
1043
+ | {
1044
+ kind: "rightclick";
1045
+ ref: string;
1046
+ confirm?: boolean;
1047
+ }
1048
+ /** Drag one element onto another with the real pointer, so drag handlers fire. */
1049
+ /**
1050
+ * Drag from one thing to another, optionally along a route.
1051
+ *
1052
+ * `via` is viewport fractions the pointer travels through. Without it a drag
1053
+ * is a straight line, which is right for dropping a card into a column and
1054
+ * cannot express a gesture whose meaning is its shape.
1055
+ */
1056
+ | {
1057
+ kind: "drag";
1058
+ /** A ref, or a SPOT as viewport fractions — a map has nothing to name. */
1059
+ from: string | {
1060
+ x: number;
1061
+ y: number;
1062
+ };
1063
+ to: string | {
1064
+ x: number;
1065
+ y: number;
1066
+ };
1067
+ via?: ReadonlyArray<{
1068
+ x: number;
1069
+ y: number;
1070
+ }>;
1071
+ }
1072
+ /**
1073
+ * Change the page's zoom, 0.25 to 3.
1074
+ *
1075
+ * `see_page` photographs the viewport and marks only what is on screen, so a
1076
+ * control below the fold is neither photographed nor markable. Zooming out is
1077
+ * how a long page becomes one picture with everything in it addressable.
1078
+ */
1079
+ | {
1080
+ kind: "zoom";
1081
+ scale: number;
1082
+ }
1083
+ /**
1084
+ * The page as a PDF, filed in the session's downloads.
1085
+ *
1086
+ * The one durable artefact an agent could not make. Carries the WHOLE page
1087
+ * rather than the viewport, and renders the site's print stylesheet, which on
1088
+ * a receipt is the layout the publisher meant it to have. Read it back with
1089
+ * `readDownload`.
1090
+ */
1091
+ | {
1092
+ kind: "print";
1093
+ filename?: string;
1094
+ }
1095
+ /**
1096
+ * Save what the page is SHOWING as a file — an image, a video, an embedded PDF.
1097
+ *
1098
+ * Fetched from inside the page, with the session it already holds, which is
1099
+ * why it reaches a signed link or a private invoice that a plain fetch cannot.
1100
+ * Aimed at an element rather than a URL; the address it resolves to is checked
1101
+ * before it is read.
1102
+ */
1103
+ | {
1104
+ kind: "save";
1105
+ ref: string;
1106
+ filename?: string;
1107
+ }
1108
+ /** Copy the current selection. Focuses `ref` first if given. Not mutating. */
1109
+ | {
1110
+ kind: "copy";
1111
+ ref?: string;
1112
+ }
1113
+ /**
1114
+ * Paste into a field. Focuses `ref`, then the paste shortcut. Credential-guarded.
1115
+ *
1116
+ * `text` puts that value on the clipboard first, so a paste no longer needs a
1117
+ * `copy` to have found the value somewhere on the page. Reach for it over
1118
+ * `type` when the field parses a PASTE — a rich-text or code editor, a tag
1119
+ * input, a card-number box. Never for a stored credential: that is
1120
+ * `fill_secret`, and the value never travels.
1121
+ */
1122
+ | {
1123
+ kind: "paste";
1124
+ ref: string;
1125
+ text?: string;
1126
+ }
1127
+ /**
1128
+ * Fill a stored credential into a field by NAME — never by value.
1129
+ *
1130
+ * `credential` is the name of a secret stored in the vault ("github-pw"); the
1131
+ * value is resolved engine-side and typed in without ever passing through the
1132
+ * caller or the model. There is no value field, deliberately: storing a secret
1133
+ * is a separate, human action (`storeCredential`), and using one is by name.
1134
+ */
1135
+ | {
1136
+ kind: "fill_secret";
1137
+ ref: string;
1138
+ credential: string;
1139
+ } | {
1140
+ kind: "fill_totp";
1141
+ ref: string;
1142
+ credential: string;
1143
+ } | {
1144
+ kind: "fill_otp";
1145
+ ref: string;
1146
+ code: string;
1147
+ }
1148
+ /** `settleMs`: how long to let the network go quiet after the document
1149
+ * commits, before the reading is taken. Default 4000, maximum 15000 — raise
1150
+ * it for a page you know draws from several slow fetches. */
1151
+ | {
1152
+ kind: "navigate";
1153
+ url: string;
1154
+ settleMs?: number;
1155
+ } | {
1156
+ kind: "back";
1157
+ }
1158
+ /** Forward, the other half of back. */
1159
+ | {
1160
+ kind: "forward";
1161
+ }
1162
+ /** Reload the current page — NOT the same as navigating to the same URL, which
1163
+ * discards the history entry and re-posts or drops what was typed. */
1164
+ | {
1165
+ kind: "reload";
1166
+ };
1167
+ export interface SessionInfo {
1168
+ connection?: ConnectionSnapshot;
1169
+ id: string;
1170
+ url: string;
1171
+ type: "ephemeral" | "agent" | "cdp";
1172
+ /** Why the provider login was or was not carried into this live session. */
1173
+ providerSession?: ProviderSessionReason;
1174
+ openedAt: string;
1175
+ lastUsedAt: string;
1176
+ /**
1177
+ * True once something has been done to this page rather than only read.
1178
+ *
1179
+ * It decides which idle clock the page gets — 5 minutes for one only read,
1180
+ * 15 once acted on — because the two differ in what closing destroys.
1181
+ */
1182
+ actedOn?: boolean;
1183
+ /** Set while the page is held open waiting on something out of band. */
1184
+ parkedUntil?: string;
1185
+ /** What it is waiting for, if it was said. */
1186
+ parkedReason?: string;
1187
+ }
1188
+ /**
1189
+ * A finished run in the recorded-session library.
1190
+ *
1191
+ * This and the two types under it are hand-mirrored from the server's
1192
+ * `packages/server/src/sessions-db.ts`, deliberately and not by oversight: this
1193
+ * client has zero dependencies and never imports the server, and cross-package
1194
+ * types here resolve through `dist/`, so importing one would tie a browser-side
1195
+ * client to a Node build. The cost is that the mirror has to be updated when the
1196
+ * server's shape moves — as it did when `api` joined the kinds and `stepCount`
1197
+ * joined the summary.
1198
+ */
1199
+ export interface ConnectionSnapshot {
1200
+ kind: "direct" | "proxy" | "unknown";
1201
+ name: string;
1202
+ country?: string;
1203
+ }
1204
+ export interface SessionSummary {
1205
+ connection?: ConnectionSnapshot;
1206
+ id: string;
1207
+ /** `api` is a run driven through this SDK / the MCP connector, as opposed to
1208
+ * one of the dashboard's own demo, console, or bridge runs. */
1209
+ kind: "demo" | "agent" | "bridge" | "api";
1210
+ title: string;
1211
+ url: string;
1212
+ status: "ok" | "blocked" | "error";
1213
+ label: string;
1214
+ startedAt: string;
1215
+ durationMs: number;
1216
+ frameCount: number;
1217
+ /** How many steps the log holds — with film and an answer, what makes a run
1218
+ * worth opening. Runs recorded before it was counted report 0. */
1219
+ stepCount: number;
1220
+ /**
1221
+ * Which of the fleet's machines produced this run, and what OS it ran.
1222
+ *
1223
+ * BOTH are absent on a row recorded before the columns existed, and `worker`
1224
+ * alone is absent on a worker with neither UNFENCED_WORKER set nor a router
1225
+ * in front of it. Absent means UNKNOWN and must be rendered that way.
1226
+ */
1227
+ worker?: string;
1228
+ workerOs?: string;
1229
+ }
1230
+ /**
1231
+ * One thing that happened, in order — the unit the trace is made of.
1232
+ *
1233
+ * The trace used to be `string[]`, one pre-formatted sentence per step, so a
1234
+ * client could print it and nothing else: which step failed, what it was aimed
1235
+ * at, and when it happened had all been flattened away before storage.
1236
+ *
1237
+ * A step never carries a secret. `chars` is why: a typed value is reported as a
1238
+ * LENGTH, so `type 11 chars into "Search"` reaches a client and the eleven
1239
+ * characters do not. There is no field that can hold a credential, a one-time
1240
+ * code, or field content, and that is the design rather than an omission.
1241
+ */
1242
+ export interface SessionStep {
1243
+ /** Milliseconds since the session started — replay time, not wall clock. */
1244
+ at: number;
1245
+ /** `open`, `click`, `type`, `navigate`, `observe`, `fill_secret`, `note`, `tool`, … */
1246
+ kind: string;
1247
+ /** The one line a person reads: `click "Sign in"`. */
1248
+ text: string;
1249
+ /** Did it do what it was asked? Absent for steps that are not attempts. */
1250
+ ok?: boolean;
1251
+ /** Why not — a refusal reason, a wall hit, an error. */
1252
+ detail?: string;
1253
+ /** Privacy-safe proof of the effect observed after an action. */
1254
+ evidence?: ActEvidence[];
1255
+ /** Whether a post-condition held, and how long it was waited. */
1256
+ expected?: {
1257
+ held: boolean;
1258
+ waitedMs: number;
1259
+ };
1260
+ /** A measured no-change observation, not a failure verdict. */
1261
+ effect?: {
1262
+ changed: false;
1263
+ waitedMs: number;
1264
+ note: string;
1265
+ };
1266
+ /** Present only when the settle ceiling was reached. */
1267
+ settled?: false;
1268
+ /** The settle wait paired with `settled: false`. */
1269
+ settleMs?: number;
1270
+ /** Where the page ended up after this step. */
1271
+ url?: string;
1272
+ /** How many characters were typed. The characters themselves never appear. */
1273
+ chars?: number;
1274
+ /**
1275
+ * WHY this step ended as it did, as a value a query can group by.
1276
+ *
1277
+ * `detail` is the sentence written for the agent and is free to be reworded;
1278
+ * this is the same answer as a literal from the engine's closed `ACT_CODES`
1279
+ * set. Typed as a string rather than that union because this package takes no
1280
+ * dependency on the engine.
1281
+ */
1282
+ code?: string;
1283
+ /** How far into the run this step happened: 1-based, monotonic, and counted
1284
+ * even after the trace stopped recording at its ceiling. */
1285
+ act?: number;
1286
+ /**
1287
+ * Index into `SessionDetail.frames` of the moment this step is visible, resolved
1288
+ * server-side after the strip was thinned, so it can be used as-is. ABSENT is
1289
+ * normal, not an error: a credential fill stops the camera on purpose, an
1290
+ * observe takes no picture, and a hand-driven run keeps no film at all.
1291
+ */
1292
+ frame?: number;
1293
+ }
1294
+ export interface SessionDetail extends SessionSummary {
1295
+ answer: string;
1296
+ /**
1297
+ * The log, structured. This is the field to read.
1298
+ *
1299
+ * It was declared as `trace` here and the wire has never sent structured
1300
+ * steps under that name: the server sends `steps: SessionStep[]` and keeps
1301
+ * `trace: string[]` beside it, deliberately and permanently, so a reader
1302
+ * older than `steps` degrades rather than crashes. A typed caller doing
1303
+ * `detail.trace[0].kind` therefore read `undefined` off a string, and nothing
1304
+ * said so — the mirror had simply been written against the wrong field.
1305
+ */
1306
+ steps: SessionStep[];
1307
+ /** The same log as plain lines, for readers older than `steps`. */
1308
+ trace: string[];
1309
+ frames: string[];
1310
+ }
1311
+ /**
1312
+ * One durable note an agent has stored for itself.
1313
+ *
1314
+ * The task-memory scratchpad, scoped to (account, agent) on the server: a short
1315
+ * `key` to find it by, a small `value` that is the note, and when it was last
1316
+ * written. Survives across sessions — it is the agent's memory of its own WORK,
1317
+ * distinct from the browser profile's memory of the WEB.
1318
+ */
1319
+ export interface MemoryEntry {
1320
+ key: string;
1321
+ value: string;
1322
+ updatedAt: string;
1323
+ }
1324
+ /** Bounded recall filters, useful when an agent keeps namespaced task notes. */
1325
+ export interface MemoryRecallOptions {
1326
+ /** Return only keys beginning with this prefix. */
1327
+ prefix?: string;
1328
+ /** Return at most this many newest notes (1..64). */
1329
+ limit?: number;
1330
+ }
1331
+ /**
1332
+ * One stored credential, as the client ever sees it: a NAME and a time.
1333
+ *
1334
+ * There is deliberately no `value`. A secret goes IN through `storeCredential`
1335
+ * and is never handed back — no client method returns a stored secret value.
1336
+ * The agent uses one only by name, via a `fill_secret` action.
1337
+ */
1338
+ export interface CredentialName {
1339
+ /** Pass this selector to open_page/account when reusing a linked workspace login. */
1340
+ browserAccount?: string;
1341
+ name: string;
1342
+ /** The account identifier (email/username) this login is for, or null for a
1343
+ * bare token. Shown back so a login is recognizable; the SECRET never is. */
1344
+ username: string | null;
1345
+ /** "password" (typed verbatim by fill_secret), "totp" (an authenticator seed the
1346
+ * engine turns into the current 2FA code via fill_totp), or "oauth" (a per-site
1347
+ * marker: this site signs in with `provider` — no fillable secret of its own). */
1348
+ kind: "password" | "totp" | "oauth";
1349
+ /** Whether a `kind:"password"` login ALSO carries a 2FA seed (fill it with
1350
+ * fill_totp under this same name). A standalone `kind:"totp"` reports false. */
1351
+ hasTotp: boolean;
1352
+ /** The site (host) this login is for, or null if tied to no site. Lets an agent
1353
+ * match a stored credential to the page it is on, rather than guess by name. */
1354
+ site: string | null;
1355
+ /** The sign-in provider (google/apple/microsoft/github), or null for a direct
1356
+ * login. On a sign-in account it names the account's provider; on an oauth
1357
+ * marker it names which provider the site signs in with. */
1358
+ provider: string | null;
1359
+ updatedAt: string;
1360
+ }
1361
+ /**
1362
+ * How the agent should choose among a person's several sign-in accounts.
1363
+ * - askMode "always": ask which account every time a site's provider is ambiguous.
1364
+ * - askMode "remember": ask once per site, then reuse that choice silently.
1365
+ * `sites` maps host → its CURATED accounts (the allow-list of account names the
1366
+ * agent may use there) and the `main` default among them ("" = ask every time).
1367
+ */
1368
+ export interface SitePref {
1369
+ accounts: string[];
1370
+ main: string;
1371
+ /**
1372
+ * The DOWNSTREAM identity each curated account resolves to on this site (account
1373
+ * name → e.g. "or@gmail.com"), learned as the agent signs in with each. Lets a
1374
+ * caller flag two accounts that resolve to the SAME underlying account, or one not
1375
+ * linked yet. A plaintext identifier, never a secret.
1376
+ */
1377
+ identities?: Record<string, string>;
1378
+ /** The profile photo URL each curated account resolves to here (account name → an
1379
+ * https avatar URL), so an account can wear its own face. A public URL, not a secret. */
1380
+ avatars?: Record<string, string>;
1381
+ }
1382
+ export interface AccountPrefs {
1383
+ askMode: "always" | "remember";
1384
+ sites: Record<string, SitePref>;
1385
+ /** sign-in account name → its profile photo URL, captured at sign-in. */
1386
+ accountAvatars?: Record<string, string>;
1387
+ }
1388
+ /**
1389
+ * What GET /permissions answers with. Three arms, and on the unavailable one
1390
+ * the arrays are ABSENT rather than empty — so no caller can count them as
1391
+ * zero and conclude the owner granted nothing.
1392
+ */
1393
+ export interface PermissionsAnswer {
1394
+ allowed?: string[];
1395
+ entries?: Array<{
1396
+ host: string;
1397
+ mode: "free" | "approve" | "read";
1398
+ }>;
1399
+ /** Explicit per-agent denies. These override every positive scope mode. */
1400
+ excludedSites?: string[];
1401
+ /** Rolling-deploy capability: explicit denies are enforced before every agent scope. */
1402
+ agentExclusionsAllScopes?: boolean;
1403
+ /** Served from the last good read; the store could not be refreshed. */
1404
+ stale?: boolean;
1405
+ /** The allowlist could not be read at all. The arrays are absent. */
1406
+ unavailable?: boolean;
1407
+ detail?: string;
1408
+ }
1409
+ //# sourceMappingURL=types.d.ts.map