effect-agent 0.1.0-beta.121 → 0.1.0-beta.123

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.
@@ -1,452 +0,0 @@
1
- import { Context, Effect, Layer, Schema, Scope, type Redacted } from "effect";
2
-
3
- import {
4
- InteractiveBrowserHost,
5
- InteractiveBrowserTargetUrl,
6
- InteractiveBrowserPolicy,
7
- } from "./InteractiveBrowser.ts";
8
-
9
- /** Canonical HTTPS origin, including a non-default port. Host grants match exactly. */
10
- export const CredentialOrigin = Schema.String.check(
11
- Schema.makeFilter(
12
- (value) => value.startsWith("https://") && Schema.is(InteractiveBrowserHost)(value.slice(8)),
13
- ),
14
- );
15
-
16
- export const BrowserReference = Schema.String.check(Schema.isUUID());
17
- export const CredentialKind = Schema.Literals(["login", "card"]);
18
-
19
- export const CredentialFieldRole = Schema.Literals([
20
- "username",
21
- "password",
22
- "card-name",
23
- "card-number",
24
- "card-expiry",
25
- "card-expiry-month",
26
- "card-expiry-year",
27
- "card-security-code",
28
- ]);
29
-
30
- const Label = Schema.String.check(Schema.isMaxLength(200));
31
- const LinkUrl = InteractiveBrowserTargetUrl.check(Schema.isStartsWith("https://"));
32
-
33
- export class CredentialTarget extends Schema.Class<CredentialTarget>("CredentialTarget")({
34
- topOrigin: CredentialOrigin,
35
- frameOrigin: CredentialOrigin,
36
- recipientOrigin: CredentialOrigin,
37
- document: BrowserReference,
38
- frame: BrowserReference,
39
- form: BrowserReference,
40
- }) {}
41
-
42
- export class ProtectedBrowserControl extends Schema.Class<ProtectedBrowserControl>(
43
- "ProtectedBrowserControl",
44
- )({
45
- ref: BrowserReference,
46
- target: CredentialTarget,
47
- role: Schema.Union([
48
- CredentialFieldRole,
49
- Schema.Literals([
50
- "text",
51
- "select",
52
- "radio",
53
- "checkbox",
54
- "submit",
55
- "link",
56
- "button",
57
- "unsupported",
58
- ]),
59
- ]),
60
- label: Label,
61
- /** Current native radio/checkbox state; field values are never included. */
62
- checked: Schema.optionalKey(Schema.Boolean),
63
- /**
64
- * Ordinary select choices only; credential choices and raw option values remain private.
65
- * Omitted choices or labels beyond the bounds set the observation's truncated flag.
66
- */
67
- options: Schema.optionalKey(
68
- Schema.Array(
69
- Schema.Struct({ label: Label, selected: Schema.Boolean, disabled: Schema.Boolean }),
70
- ).check(Schema.isMaxLength(256)),
71
- ),
72
- /** Resolved HTTPS destination for a native link; target.recipientOrigin is its origin. */
73
- url: Schema.optionalKey(LinkUrl),
74
- }) {}
75
-
76
- export class ProtectedBrowserObservation extends Schema.Class<ProtectedBrowserObservation>(
77
- "ProtectedBrowserObservation",
78
- )({
79
- document: BrowserReference,
80
- topOrigin: CredentialOrigin,
81
- text: Schema.String.check(Schema.isMaxLength(64 * 1024)),
82
- controls: Schema.Array(ProtectedBrowserControl).check(Schema.isMaxLength(64)),
83
- truncated: Schema.Boolean,
84
- observation: Schema.Literals(["before-exposure", "approved-after-exposure"]),
85
- }) {}
86
-
87
- export class CredentialOfferMetadata extends Schema.Class<CredentialOfferMetadata>(
88
- "CredentialOfferMetadata",
89
- )({
90
- label: Label,
91
- brand: Schema.optionalKey(Label),
92
- lastFour: Schema.optionalKey(Schema.String.check(Schema.isPattern(/^\d{4}$/))),
93
- /** Plaintext metadata disclosed only when the host authorizes listing this offer. */
94
- billingAddress: Schema.optionalKey(
95
- Schema.Struct({
96
- name: Schema.optionalKey(Label),
97
- line1: Schema.optionalKey(Label),
98
- line2: Schema.optionalKey(Label),
99
- city: Schema.optionalKey(Label),
100
- region: Schema.optionalKey(Label),
101
- postalCode: Schema.optionalKey(Label),
102
- country: Schema.optionalKey(Label),
103
- }),
104
- ),
105
- }) {}
106
-
107
- export class CredentialOffer extends Schema.Class<CredentialOffer>("CredentialOffer")({
108
- ref: BrowserReference,
109
- kind: CredentialKind,
110
- metadata: CredentialOfferMetadata,
111
- }) {}
112
-
113
- export class ListCredentialOffers extends Schema.Class<ListCredentialOffers>(
114
- "ListCredentialOffers",
115
- )({
116
- kind: CredentialKind,
117
- target: BrowserReference,
118
- }) {}
119
-
120
- export class UseCredential extends Schema.Class<UseCredential>("UseCredential")({
121
- offer: BrowserReference,
122
- fields: Schema.Array(Schema.Struct({ ref: BrowserReference, role: CredentialFieldRole })).check(
123
- Schema.isMinLength(1),
124
- Schema.isMaxLength(8),
125
- ),
126
- submit: Schema.optionalKey(BrowserReference),
127
- }) {}
128
-
129
- export class ProtectedBrowserNavigate extends Schema.Class<ProtectedBrowserNavigate>(
130
- "ProtectedBrowserNavigate",
131
- )({
132
- url: Schema.String.check(Schema.isMaxLength(8192)),
133
- }) {}
134
-
135
- export class ProtectedBrowserClick extends Schema.Class<ProtectedBrowserClick>(
136
- "ProtectedBrowserClick",
137
- )({
138
- ref: BrowserReference,
139
- }) {}
140
-
141
- /**
142
- * Non-secret text for an ordinary text control or native single select in the current observation.
143
- * Never pass credential material. Credential roles, including username, require useCredential.
144
- * Selects match one enabled option's exact observed label. Empty text clears text controls.
145
- */
146
- export class ProtectedBrowserFill extends Schema.Class<ProtectedBrowserFill>(
147
- "ProtectedBrowserFill",
148
- )(
149
- Schema.Struct({
150
- ref: BrowserReference,
151
- value: Schema.String.check(Schema.isMaxLength(8192)),
152
- }),
153
- ) {}
154
-
155
- export const CredentialDispatch = Schema.Literals([
156
- "not-dispatched",
157
- "possibly-dispatched",
158
- "dispatched",
159
- ]);
160
-
161
- export const CredentialMilestone = Schema.Literals([
162
- "none",
163
- "partial-fill",
164
- "filled",
165
- "submission-dispatched",
166
- ]);
167
-
168
- export const ProtectedObservationState = Schema.Literals([
169
- "before-exposure",
170
- "protected",
171
- "approved-after-exposure",
172
- "closed",
173
- ]);
174
-
175
- export const ProtectedCleanup = Schema.Literals(["not-requested", "confirmed", "unconfirmed"]);
176
-
177
- const Evidence = {
178
- dispatch: CredentialDispatch,
179
- milestone: CredentialMilestone,
180
- observation: ProtectedObservationState,
181
- cleanup: ProtectedCleanup,
182
- };
183
-
184
- /** No foreign message/cause or page data may enter this failure. Closure does not undo dispatch. */
185
- export class ProtectedBrowserError extends Schema.TaggedError<ProtectedBrowserError>()(
186
- "ProtectedBrowserError",
187
- {
188
- reason: Schema.Literals([
189
- "denied",
190
- "missing-credential",
191
- "stale-reference",
192
- "unsupported",
193
- "needs-attention",
194
- "busy",
195
- "closed",
196
- "limit",
197
- "timeout",
198
- "provider",
199
- "resolver",
200
- "outcome-unknown",
201
- "observation-blocked",
202
- ]),
203
- ...Evidence,
204
- },
205
- ) {}
206
-
207
- export class CredentialUseResult extends Schema.Class<CredentialUseResult>("CredentialUseResult")({
208
- ...Evidence,
209
- authentication: Schema.Literal("unverified"),
210
- }) {}
211
-
212
- const SecretText = Schema.Redacted(Schema.NonEmptyString.check(Schema.isMaxLength(1024)));
213
-
214
- /** Host-only material. Never encode it into Tool results, checkpoints, or storage. */
215
- export const LoginCredential = Schema.TaggedStruct("LoginCredential", {
216
- username: SecretText,
217
- password: SecretText,
218
- });
219
-
220
- /** Security codes are transient, never persisted. Dummy tests are not PCI compliance evidence. */
221
- export const CardCredential = Schema.TaggedStruct("CardCredential", {
222
- name: SecretText,
223
- number: Schema.Redacted(Schema.String.check(Schema.isPattern(/^\d{12,19}$/))),
224
- expiry: SecretText,
225
- expiryMonth: Schema.Redacted(Schema.String.check(Schema.isPattern(/^(0[1-9]|1[0-2])$/))),
226
- expiryYear: Schema.Redacted(Schema.String.check(Schema.isPattern(/^\d{4}$/))),
227
- securityCode: Schema.optionalKey(
228
- Schema.Redacted(Schema.String.check(Schema.isPattern(/^\d{3,4}$/))),
229
- ),
230
- });
231
-
232
- export const BrowserCredentialMaterial = Schema.Union([LoginCredential, CardCredential]);
233
- export type BrowserCredentialMaterial = typeof BrowserCredentialMaterial.Type;
234
-
235
- /** Use busy for transient host-authority unavailability; return promptly without waiting for Tool completion. */
236
- export class CredentialAccessError extends Schema.TaggedError<CredentialAccessError>()(
237
- "CredentialAccessError",
238
- {
239
- reason: Schema.Literals([
240
- "denied",
241
- "missing-credential",
242
- "needs-attention",
243
- "busy",
244
- "resolver",
245
- ]),
246
- },
247
- ) {}
248
-
249
- export interface CredentialAccessRequest {
250
- readonly caller: Redacted.Redacted<string>;
251
- readonly kind: typeof CredentialKind.Type;
252
- readonly target: CredentialTarget;
253
- }
254
-
255
- export interface CredentialUseAuthorization extends CredentialAccessRequest {
256
- readonly key: Redacted.Redacted<string>;
257
- readonly roles: ReadonlyArray<typeof CredentialFieldRole.Type>;
258
- readonly submit: boolean;
259
- }
260
-
261
- /** Exact current action proposed to host authority; no ordinary fill value or secret is included. */
262
- export const ProtectedBrowserAction = Schema.Union([
263
- Schema.TaggedStruct("Navigate", { url: ProtectedBrowserNavigate.fields.url }),
264
- Schema.TaggedStruct("Fill", {
265
- ref: BrowserReference,
266
- target: CredentialTarget,
267
- role: Schema.Literals(["text", "select"]),
268
- }),
269
- Schema.TaggedStruct("Click", {
270
- ref: BrowserReference,
271
- target: CredentialTarget,
272
- role: Schema.Literals(["button", "radio", "checkbox"]),
273
- }),
274
- Schema.TaggedStruct("Click", {
275
- ref: BrowserReference,
276
- target: CredentialTarget,
277
- role: Schema.Literal("link"),
278
- url: LinkUrl,
279
- }),
280
- Schema.TaggedStruct("Submit", { ref: BrowserReference, target: CredentialTarget }),
281
- ]);
282
-
283
- export type ProtectedBrowserAction = typeof ProtectedBrowserAction.Type;
284
-
285
- /**
286
- * Trust only these current observation origins. The top origin must be included. Other frames
287
- * supply neither text nor usable refs. This does not expand network policy or credential grants.
288
- */
289
- export class CredentialObservationGrant extends Schema.Class<CredentialObservationGrant>(
290
- "CredentialObservationGrant",
291
- )({
292
- decision: Schema.Literal("trust-recipient-no-credential-echo"),
293
- origins: Schema.Array(CredentialOrigin).check(
294
- Schema.isMinLength(1),
295
- Schema.isMaxLength(16),
296
- Schema.isUnique(),
297
- ),
298
- }) {}
299
-
300
- export const CredentialObservationDecision = Schema.Union([
301
- Schema.Literals(["trust-recipient-no-credential-echo", "deny"]),
302
- CredentialObservationGrant,
303
- ]);
304
-
305
- /**
306
- * Host-only vault and grant port. Derive caller from the authorized invocation, never Tool input.
307
- * Recheck account ownership, current grants, merchant/frame/recipient pairing, and any side
308
- * effects caused by filling. Listing authorizes metadata only. Grant checks never widen network
309
- * policy. Implementations must not log material or put it in defects/traces.
310
- */
311
- export class BrowserCredentialAccess extends Context.Service<
312
- BrowserCredentialAccess,
313
- {
314
- readonly caller: Effect.Effect<Redacted.Redacted<string>, CredentialAccessError>;
315
- readonly list: (request: CredentialAccessRequest) => Effect.Effect<
316
- ReadonlyArray<{
317
- readonly key: Redacted.Redacted<string>;
318
- readonly metadata: CredentialOfferMetadata;
319
- }>,
320
- CredentialAccessError
321
- >;
322
- readonly authorize: (
323
- request: CredentialUseAuthorization,
324
- ) => Effect.Effect<void, CredentialAccessError>;
325
- readonly resolve: (
326
- request: CredentialUseAuthorization,
327
- ) => Effect.Effect<BrowserCredentialMaterial, CredentialAccessError>;
328
- /**
329
- * Optional host authority for every ordinary navigate/fill/click, mandatory for Submit.
330
- * Recheck caller ownership, user intent, current submission/continuation grants for ALL prior
331
- * exposures, and this exact target. Exposures may come from separate credential frames/forms.
332
- * Without this hook, ordinary actions retain their observation gate and Submit is unsupported.
333
- * Observation trust alone never authorizes a new native submit. Called again on each action;
334
- * approvals are not cached. The pass revalidates caller and target after this hook returns.
335
- */
336
- readonly authorizeAction?: (request: {
337
- readonly caller: Redacted.Redacted<string>;
338
- readonly action: ProtectedBrowserAction;
339
- readonly exposures: ReadonlyArray<CredentialTarget>;
340
- }) => Effect.Effect<void, CredentialAccessError>;
341
- /**
342
- * Explicitly trust all observed destinations not to echo credentials, including transformed
343
- * or delayed echoes. Called for every observation and non-secret action after exposure.
344
- * The string grant trusts all current observed origins. Return CredentialObservationGrant
345
- * to select an explicit subset instead. This is recipient trust, not submission authority,
346
- * a universal secrecy guarantee, or a login verifier.
347
- */
348
- readonly observation: (request: {
349
- readonly caller: Redacted.Redacted<string>;
350
- readonly topOrigin: typeof CredentialOrigin.Type;
351
- readonly frameOrigins: ReadonlyArray<typeof CredentialOrigin.Type>;
352
- readonly exposures: ReadonlyArray<CredentialTarget>;
353
- /** A human may have entered secrets; explicitly authorize observation even with no vault exposures. */
354
- readonly humanExposure?: boolean;
355
- /** Origins present when the human received control, retained across durable suspension. */
356
- readonly humanOrigins?: ReadonlyArray<typeof CredentialOrigin.Type>;
357
- }) => Effect.Effect<typeof CredentialObservationDecision.Type, CredentialAccessError>;
358
- }
359
- >()("@effect-agent/sandbox/BrowserCredentialAccess") {}
360
-
361
- /**
362
- * Host-only suspended-pass state. Persist with the host's fenced controller receipt, never in
363
- * a Tool result. It contains no credential material, offers, DOM references usable after resume,
364
- * cookies, page text, or provider capability. A host must restore its own credential exposure
365
- * ledger as well. The original deadline and action allowance include time spent with the human.
366
- */
367
- export class ProtectedBrowserCheckpoint extends Schema.Class<ProtectedBrowserCheckpoint>(
368
- "ProtectedBrowserCheckpoint",
369
- )({
370
- policy: InteractiveBrowserPolicy,
371
- startedAt: Schema.Natural,
372
- actions: Schema.Natural,
373
- exposures: Schema.Array(CredentialTarget).check(Schema.isMaxLength(1000)),
374
- humanExposure: Schema.Boolean,
375
- humanOrigins: Schema.Array(CredentialOrigin).check(Schema.isMaxLength(16)),
376
- dispatch: CredentialDispatch,
377
- milestone: CredentialMilestone,
378
- }) {}
379
-
380
- /** A private ephemeral pass. No selectors, JavaScript, screenshots, viewer, or provider identity. */
381
- export interface ProtectedBrowserHandle {
382
- readonly navigate: (
383
- request: ProtectedBrowserNavigate,
384
- ) => Effect.Effect<void, ProtectedBrowserError>;
385
- readonly observe: Effect.Effect<ProtectedBrowserObservation, ProtectedBrowserError>;
386
- readonly click: (request: ProtectedBrowserClick) => Effect.Effect<void, ProtectedBrowserError>;
387
- /** Uses the same lock, budgets, current-target checks and post-exposure grant as other actions. */
388
- readonly fill: (request: ProtectedBrowserFill) => Effect.Effect<void, ProtectedBrowserError>;
389
- readonly listCredentialOffers: (
390
- request: ListCredentialOffers,
391
- ) => Effect.Effect<ReadonlyArray<CredentialOffer>, ProtectedBrowserError>;
392
- readonly useCredential: (
393
- request: UseCredential,
394
- ) => Effect.Effect<CredentialUseResult, ProtectedBrowserError>;
395
- readonly close: Effect.Effect<typeof ProtectedCleanup.Type>;
396
- }
397
-
398
- export class ProtectedBrowser extends Context.Service<
399
- ProtectedBrowser,
400
- {
401
- readonly open: (
402
- policy: InteractiveBrowserPolicy,
403
- ) => Effect.Effect<
404
- ProtectedBrowserHandle,
405
- ProtectedBrowserError,
406
- Scope.Scope | BrowserCredentialAccess
407
- >;
408
- }
409
- >()("@effect-agent/sandbox/ProtectedBrowser") {}
410
-
411
- /** Build once in an execution/Attempt Layer. Acquires lazily and shares one pass across Tools. */
412
- export class ProtectedBrowserSession extends Context.Service<
413
- ProtectedBrowserSession,
414
- {
415
- readonly get: Effect.Effect<ProtectedBrowserHandle, ProtectedBrowserError>;
416
- }
417
- >()("@effect-agent/sandbox/ProtectedBrowserSession") {
418
- static layer(policy: InteractiveBrowserPolicy) {
419
- return Layer.effect(this)(
420
- Effect.gen(function* () {
421
- const browser = yield* ProtectedBrowser;
422
- const access = yield* BrowserCredentialAccess;
423
- const scope = yield* Effect.scope;
424
-
425
- const cached = yield* Effect.cached(
426
- browser
427
- .open(policy)
428
- .pipe(
429
- Effect.provideService(BrowserCredentialAccess, access),
430
- Effect.provideService(Scope.Scope, scope),
431
- ),
432
- );
433
-
434
- return {
435
- get: Effect.suspend(() =>
436
- scope.state._tag === "Closed"
437
- ? Effect.fail(
438
- new ProtectedBrowserError({
439
- reason: "closed",
440
- dispatch: "not-dispatched",
441
- milestone: "none",
442
- observation: "closed",
443
- cleanup: "not-requested",
444
- }),
445
- )
446
- : cached,
447
- ),
448
- };
449
- }),
450
- );
451
- }
452
- }