@jini-ai/agentic 0.1.2 → 0.3.6
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 +92 -0
- package/dist/a2ui/agent-to-renderer.d.ts +36 -2
- package/dist/a2ui/agent-to-renderer.d.ts.map +1 -1
- package/dist/a2ui/agent-to-renderer.js +23 -1
- package/dist/a2ui/agent-to-renderer.js.map +1 -1
- package/dist/a2ui/interpreter.d.ts +24 -1
- package/dist/a2ui/interpreter.d.ts.map +1 -1
- package/dist/a2ui/interpreter.js +115 -65
- package/dist/a2ui/interpreter.js.map +1 -1
- package/dist/a2ui/json-pointer.d.ts.map +1 -1
- package/dist/a2ui/json-pointer.js +28 -3
- package/dist/a2ui/json-pointer.js.map +1 -1
- package/dist/a2ui/renderer-to-agent.d.ts +35 -11
- package/dist/a2ui/renderer-to-agent.d.ts.map +1 -1
- package/dist/a2ui/renderer-to-agent.js +15 -2
- package/dist/a2ui/renderer-to-agent.js.map +1 -1
- package/dist/a2ui/resolve.d.ts +1 -1
- package/dist/a2ui/resolve.d.ts.map +1 -1
- package/dist/a2ui/resolve.js +29 -3
- package/dist/a2ui/resolve.js.map +1 -1
- package/dist/a2ui/tree.d.ts +27 -2
- package/dist/a2ui/tree.d.ts.map +1 -1
- package/dist/a2ui/tree.js +48 -9
- package/dist/a2ui/tree.js.map +1 -1
- package/dist/ag-ui.d.ts.map +1 -1
- package/dist/ag-ui.js +30 -1
- package/dist/ag-ui.js.map +1 -1
- package/dist/core/a2ui/agent-to-renderer.d.ts +613 -0
- package/dist/core/a2ui/agent-to-renderer.d.ts.map +1 -0
- package/dist/core/a2ui/agent-to-renderer.js +191 -0
- package/dist/core/a2ui/agent-to-renderer.js.map +1 -0
- package/dist/core/a2ui/catalog.d.ts +73 -0
- package/dist/core/a2ui/catalog.d.ts.map +1 -0
- package/dist/core/a2ui/catalog.js +304 -0
- package/dist/core/a2ui/catalog.js.map +1 -0
- package/dist/core/a2ui/common-types.d.ts +401 -0
- package/dist/core/a2ui/common-types.d.ts.map +1 -0
- package/dist/core/a2ui/common-types.js +130 -0
- package/dist/core/a2ui/common-types.js.map +1 -0
- package/dist/core/a2ui/index.d.ts +16 -0
- package/dist/core/a2ui/index.d.ts.map +1 -0
- package/dist/core/a2ui/index.js +16 -0
- package/dist/core/a2ui/index.js.map +1 -0
- package/dist/core/a2ui/interpreter.d.ts +71 -0
- package/dist/core/a2ui/interpreter.d.ts.map +1 -0
- package/dist/core/a2ui/interpreter.js +341 -0
- package/dist/core/a2ui/interpreter.js.map +1 -0
- package/dist/core/a2ui/json-pointer.d.ts +48 -0
- package/dist/core/a2ui/json-pointer.d.ts.map +1 -0
- package/dist/core/a2ui/json-pointer.js +174 -0
- package/dist/core/a2ui/json-pointer.js.map +1 -0
- package/dist/core/a2ui/renderer-to-agent.d.ts +342 -0
- package/dist/core/a2ui/renderer-to-agent.d.ts.map +1 -0
- package/dist/core/a2ui/renderer-to-agent.js +111 -0
- package/dist/core/a2ui/renderer-to-agent.js.map +1 -0
- package/dist/core/a2ui/resolve.d.ts +28 -0
- package/dist/core/a2ui/resolve.d.ts.map +1 -0
- package/dist/core/a2ui/resolve.js +144 -0
- package/dist/core/a2ui/resolve.js.map +1 -0
- package/dist/core/a2ui/tree.d.ts +66 -0
- package/dist/core/a2ui/tree.d.ts.map +1 -0
- package/dist/core/a2ui/tree.js +74 -0
- package/dist/core/a2ui/tree.js.map +1 -0
- package/dist/core/ag-ui.d.ts +82 -0
- package/dist/core/ag-ui.d.ts.map +1 -0
- package/dist/core/ag-ui.js +85 -0
- package/dist/core/ag-ui.js.map +1 -0
- package/dist/core/capability.d.ts +81 -0
- package/dist/core/capability.d.ts.map +1 -0
- package/dist/core/capability.js +79 -0
- package/dist/core/capability.js.map +1 -0
- package/dist/core/dom/dom-page-driver.d.ts +36 -0
- package/dist/core/dom/dom-page-driver.d.ts.map +1 -0
- package/dist/core/dom/dom-page-driver.js +601 -0
- package/dist/core/dom/dom-page-driver.js.map +1 -0
- package/dist/core/dom/index.d.ts +17 -0
- package/dist/core/dom/index.d.ts.map +1 -0
- package/dist/core/dom/index.js +17 -0
- package/dist/core/dom/index.js.map +1 -0
- package/dist/core/dom/model-context.d.ts +69 -0
- package/dist/core/dom/model-context.d.ts.map +1 -0
- package/dist/core/dom/model-context.js +44 -0
- package/dist/core/dom/model-context.js.map +1 -0
- package/dist/core/element-handles.d.ts +120 -0
- package/dist/core/element-handles.d.ts.map +1 -0
- package/dist/core/element-handles.js +39 -0
- package/dist/core/element-handles.js.map +1 -0
- package/dist/core/gen-ui/encoder.d.ts +50 -0
- package/dist/core/gen-ui/encoder.d.ts.map +1 -0
- package/dist/core/gen-ui/encoder.js +97 -0
- package/dist/core/gen-ui/encoder.js.map +1 -0
- package/dist/core/gen-ui/events.d.ts +59 -0
- package/dist/core/gen-ui/events.d.ts.map +1 -0
- package/dist/core/gen-ui/events.js +15 -0
- package/dist/core/gen-ui/events.js.map +1 -0
- package/dist/core/gen-ui/index.d.ts +18 -0
- package/dist/core/gen-ui/index.d.ts.map +1 -0
- package/dist/core/gen-ui/index.js +17 -0
- package/dist/core/gen-ui/index.js.map +1 -0
- package/dist/core/guards.d.ts +96 -0
- package/dist/core/guards.d.ts.map +1 -0
- package/dist/core/guards.js +186 -0
- package/dist/core/guards.js.map +1 -0
- package/dist/core/handle.d.ts +89 -0
- package/dist/core/handle.d.ts.map +1 -0
- package/dist/core/handle.js +88 -0
- package/dist/core/handle.js.map +1 -0
- package/dist/core/index.d.ts +75 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +74 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/list-handles.d.ts +43 -0
- package/dist/core/list-handles.d.ts.map +1 -0
- package/dist/core/list-handles.js +57 -0
- package/dist/core/list-handles.js.map +1 -0
- package/dist/core/mcp-ui-apps.d.ts +223 -0
- package/dist/core/mcp-ui-apps.d.ts.map +1 -0
- package/dist/core/mcp-ui-apps.js +252 -0
- package/dist/core/mcp-ui-apps.js.map +1 -0
- package/dist/core/page-capabilities.d.ts +14 -0
- package/dist/core/page-capabilities.d.ts.map +1 -0
- package/dist/core/page-capabilities.js +129 -0
- package/dist/core/page-capabilities.js.map +1 -0
- package/dist/core/page-driver.d.ts +120 -0
- package/dist/core/page-driver.d.ts.map +1 -0
- package/dist/core/page-driver.js +2 -0
- package/dist/core/page-driver.js.map +1 -0
- package/dist/core/page-executor.d.ts +94 -0
- package/dist/core/page-executor.d.ts.map +1 -0
- package/dist/core/page-executor.js +320 -0
- package/dist/core/page-executor.js.map +1 -0
- package/dist/core/webmcp.d.ts +202 -0
- package/dist/core/webmcp.d.ts.map +1 -0
- package/dist/core/webmcp.js +173 -0
- package/dist/core/webmcp.js.map +1 -0
- package/dist/dom/dom-page-driver.d.ts +9 -2
- package/dist/dom/dom-page-driver.d.ts.map +1 -1
- package/dist/dom/dom-page-driver.js +84 -40
- package/dist/dom/dom-page-driver.js.map +1 -1
- package/dist/dom/index.d.ts +1 -1
- package/dist/dom/index.d.ts.map +1 -1
- package/dist/dom/index.js +1 -1
- package/dist/dom/index.js.map +1 -1
- package/dist/element-handles.d.ts +22 -1
- package/dist/element-handles.d.ts.map +1 -1
- package/dist/gen-ui/encoder.d.ts +8 -8
- package/dist/gen-ui/encoder.d.ts.map +1 -1
- package/dist/gen-ui/encoder.js +2 -2
- package/dist/gen-ui/encoder.js.map +1 -1
- package/dist/gen-ui/events.d.ts +10 -10
- package/dist/gen-ui/events.d.ts.map +1 -1
- package/dist/gen-ui/index.d.ts +3 -3
- package/dist/gen-ui/index.d.ts.map +1 -1
- package/dist/gen-ui/index.js +2 -2
- package/dist/gen-ui/index.js.map +1 -1
- package/dist/guards.d.ts +8 -3
- package/dist/guards.d.ts.map +1 -1
- package/dist/guards.js +6 -1
- package/dist/guards.js.map +1 -1
- package/dist/index.d.ts +4 -62
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -61
- package/dist/index.js.map +1 -1
- package/dist/mcp-ui-apps.d.ts +19 -0
- package/dist/mcp-ui-apps.d.ts.map +1 -1
- package/dist/mcp-ui-apps.js +50 -5
- package/dist/mcp-ui-apps.js.map +1 -1
- package/dist/page-capabilities.js +6 -6
- package/dist/page-capabilities.js.map +1 -1
- package/dist/page-driver.d.ts +15 -2
- package/dist/page-driver.d.ts.map +1 -1
- package/dist/page-executor.d.ts +11 -4
- package/dist/page-executor.d.ts.map +1 -1
- package/dist/page-executor.js +47 -15
- package/dist/page-executor.js.map +1 -1
- package/package.json +16 -9
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module guards
|
|
3
|
+
*
|
|
4
|
+
* The refusals that must hold on every surface, expressed over plain descriptors rather than
|
|
5
|
+
* DOM nodes so they are testable in Node and reusable from any framework binding.
|
|
6
|
+
*
|
|
7
|
+
* Both guards here answer review findings that an allowlist alone does not cover: a generic
|
|
8
|
+
* filler will happily type into a password or card-number field even when that field carries a
|
|
9
|
+
* legitimate handle, and `data-agent-label` is page-authored text that reaches a model as if
|
|
10
|
+
* the host had written it.
|
|
11
|
+
*/
|
|
12
|
+
/** The subset of an input element these guards need. Framework- and DOM-free by design. */
|
|
13
|
+
export interface FieldDescriptor {
|
|
14
|
+
/** The `type` attribute, lowercased. */
|
|
15
|
+
readonly type?: string | undefined;
|
|
16
|
+
/** The `autocomplete` attribute, lowercased. */
|
|
17
|
+
readonly autocomplete?: string | undefined;
|
|
18
|
+
readonly name?: string | undefined;
|
|
19
|
+
readonly id?: string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* **Every** human-visible naming of the field the driver could resolve — `aria-label`,
|
|
22
|
+
* `placeholder`, and the text of each associated `<label>`.
|
|
23
|
+
*
|
|
24
|
+
* Carried because `name`/`id` are the *machine* names, and a form builder or CMS that emits
|
|
25
|
+
* `name="field_47"` leaves the guard nothing to judge while the page still plainly reads
|
|
26
|
+
* "Card number" to the user. A field whose sensitivity is stated only in its label was
|
|
27
|
+
* invisible to every refusal here until this existed.
|
|
28
|
+
*
|
|
29
|
+
* Plural, and required to be exhaustive, because the singular form this replaced took whichever
|
|
30
|
+
* source resolved *first*: a page could then mask a sensitive field behind a benign one —
|
|
31
|
+
* `<label for=f>Card number</label><input id=f name=field_47 placeholder="Enter value">` reached
|
|
32
|
+
* the guard as the single label "Enter value", and its value was reported back in full.
|
|
33
|
+
*/
|
|
34
|
+
readonly accessibleLabels?: readonly string[] | undefined;
|
|
35
|
+
readonly readOnly?: boolean | undefined;
|
|
36
|
+
readonly disabled?: boolean | undefined;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The refusals that are about *secrecy* — the field's contents are not an agent's to know.
|
|
40
|
+
*
|
|
41
|
+
* Separated from the two writability refusals below because reading and writing are different
|
|
42
|
+
* questions with different answers. A read-only field's value is perfectly safe to read back; a
|
|
43
|
+
* password's is not safe to read even though it is, mechanically, just as readable. Folding both
|
|
44
|
+
* into one list would either leak the password or hide the read-only field's value for no reason.
|
|
45
|
+
*/
|
|
46
|
+
export type FieldReadRefusal = 'denied-type' | 'denied-autocomplete' | 'suspicious-name';
|
|
47
|
+
export type FieldRefusal = FieldReadRefusal | 'not-text' | 'read-only' | 'disabled';
|
|
48
|
+
/**
|
|
49
|
+
* Why this field's current value must not be reported back to a caller, or `null` when reading
|
|
50
|
+
* it is safe.
|
|
51
|
+
*
|
|
52
|
+
* Reading is the strictly more dangerous direction: writing into a password box gives an agent
|
|
53
|
+
* nothing, whereas reading one out hands over the secret itself. So every secrecy signal the fill
|
|
54
|
+
* guard uses applies here identically — type, autocomplete token, and a name or id that indicates
|
|
55
|
+
* a credential or anti-forgery token — and none of them may be relaxed for reads.
|
|
56
|
+
*
|
|
57
|
+
* @param field - The field's attributes.
|
|
58
|
+
* @returns The first secrecy refusal, or `null` when the value may be reported.
|
|
59
|
+
*/
|
|
60
|
+
export declare function findFieldReadRefusal(field: FieldDescriptor): FieldReadRefusal | null;
|
|
61
|
+
/**
|
|
62
|
+
* Why an automated fill must be refused for this field, or `null` when it is safe to write.
|
|
63
|
+
*
|
|
64
|
+
* Fails closed on anything that looks like a credential, payment instrument, one-time code, or
|
|
65
|
+
* anti-forgery token, independent of whether the field carries an agent handle. Tagging a
|
|
66
|
+
* password box does not make it fillable. Every secrecy refusal is also a fill refusal — a field
|
|
67
|
+
* whose contents an agent may not read is certainly not one it may overwrite — and two further
|
|
68
|
+
* refusals cover fields that are simply not writable.
|
|
69
|
+
*
|
|
70
|
+
* @param field - The field's attributes.
|
|
71
|
+
* @returns The first refusal reason, or `null` when the field may be filled.
|
|
72
|
+
*/
|
|
73
|
+
export declare function findFieldFillRefusal(field: FieldDescriptor): FieldRefusal | null;
|
|
74
|
+
/** Model-readable reason for a refusal, so a blocked fill explains itself instead of failing opaquely. */
|
|
75
|
+
export declare function describeFieldRefusal(refusal: FieldRefusal): string;
|
|
76
|
+
/** Model-readable reason a value was withheld, so a caller knows the field has contents it may not see rather than assuming it is empty. */
|
|
77
|
+
export declare function describeFieldReadRefusal(refusal: FieldReadRefusal): string;
|
|
78
|
+
/** Default cap on page-authored text handed to a model. */
|
|
79
|
+
export declare const MAX_AGENT_LABEL_LENGTH = 200;
|
|
80
|
+
export interface NormalizedLabel {
|
|
81
|
+
readonly text: string;
|
|
82
|
+
readonly truncated: boolean;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Normalizes page-authored text before it reaches a model.
|
|
86
|
+
*
|
|
87
|
+
* Strips control characters (including the bidirectional overrides used to make text render
|
|
88
|
+
* differently from how it reads), collapses whitespace, and caps the length. The result is still
|
|
89
|
+
* untrusted *content* — this only bounds it; it does not make it safe to follow as instruction.
|
|
90
|
+
*
|
|
91
|
+
* @param raw - Text read off the page, e.g. a `data-agent-label`.
|
|
92
|
+
* @param maxLength - Cap, defaulting to {@link MAX_AGENT_LABEL_LENGTH}.
|
|
93
|
+
* @returns The bounded text and whether anything was cut.
|
|
94
|
+
*/
|
|
95
|
+
export declare function normalizeAgentLabel(raw: string, maxLength?: number): NormalizedLabel;
|
|
96
|
+
//# sourceMappingURL=guards.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../../src/core/guards.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,2FAA2F;AAC3F,MAAM,WAAW,eAAe;IAC9B,wCAAwC;IACxC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,gDAAgD;IAChD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAC1D,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACzC;AAuDD;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GACxB,aAAa,GACb,qBAAqB,GACrB,iBAAiB,CAAC;AAYtB,MAAM,MAAM,YAAY,GAAG,gBAAgB,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,CAAC;AAEpF;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB,GAAG,IAAI,CA+BpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,eAAe,GAAG,YAAY,GAAG,IAAI,CAQhF;AAiBD,0GAA0G;AAC1G,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAElE;AAED,4IAA4I;AAC5I,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAE1E;AAED,2DAA2D;AAC3D,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAa1C,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,SAAyB,GAAG,eAAe,CAKpG"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module guards
|
|
3
|
+
*
|
|
4
|
+
* The refusals that must hold on every surface, expressed over plain descriptors rather than
|
|
5
|
+
* DOM nodes so they are testable in Node and reusable from any framework binding.
|
|
6
|
+
*
|
|
7
|
+
* Both guards here answer review findings that an allowlist alone does not cover: a generic
|
|
8
|
+
* filler will happily type into a password or card-number field even when that field carries a
|
|
9
|
+
* legitimate handle, and `data-agent-label` is page-authored text that reaches a model as if
|
|
10
|
+
* the host had written it.
|
|
11
|
+
*/
|
|
12
|
+
/** Input types no automated caller may ever write to. */
|
|
13
|
+
const DENIED_TYPES = new Set(['password', 'hidden', 'file', 'image']);
|
|
14
|
+
/** Autocomplete tokens covering credentials, payment instruments and one-time codes. */
|
|
15
|
+
const DENIED_AUTOCOMPLETE = new Set([
|
|
16
|
+
'current-password', 'new-password', 'one-time-code',
|
|
17
|
+
'cc-number', 'cc-exp', 'cc-exp-month', 'cc-exp-year', 'cc-csc', 'cc-name', 'cc-type',
|
|
18
|
+
]);
|
|
19
|
+
/**
|
|
20
|
+
* Substrings in `name`/`id` that indicate a secret even when type/autocomplete are unset.
|
|
21
|
+
*
|
|
22
|
+
* Matched against {@link squashSeparators}'d text, so the multi-word entries catch the separator
|
|
23
|
+
* spellings people actually use. `api_key`, `api-key` and `apiKey` are all the same field; a
|
|
24
|
+
* literal-substring match sees only the last one, which is the least common of the three.
|
|
25
|
+
*/
|
|
26
|
+
const SUSPICIOUS_NAME = /(?:password|passwd|secret|token|csrf|xsrf|otp|cvv|cvc|ssn|creditcard|cardnumber|apikey|ccnumber|cardnum|accountnumber|routing|iban|sortcode|securitycode|passphrase|privatekey|clientsecret|accesskey|secretkey|recoverycode|backupcode|mfa)/i;
|
|
27
|
+
// Deliberately NOT added as bare substrings, even though both are real PCI/banking terms
|
|
28
|
+
// ("pin", "PAN" — Primary Account Number): they collide with ordinary field names once
|
|
29
|
+
// separators are squashed out. `shipping` contains "pin"; `company` contains "pan". Adding
|
|
30
|
+
// either here would refuse a large share of legitimate, non-sensitive fields. See
|
|
31
|
+
// guards.test.ts's "does not refuse ordinary fields that happen to contain pin/pan as a
|
|
32
|
+
// substring" test — it exists specifically so a future edit cannot add either back silently.
|
|
33
|
+
/**
|
|
34
|
+
* Drops everything that is not a letter or digit, so separator conventions do not decide whether
|
|
35
|
+
* a guard fires. Over-matching is the safe direction here and is the point: this guard fails
|
|
36
|
+
* closed, and the cost of a false positive is a field an agent must leave to the user.
|
|
37
|
+
*
|
|
38
|
+
* Normalizes before squashing rather than after, because the naive squash used to *delete* any
|
|
39
|
+
* character outside ASCII `[a-z0-9]` instead of folding it. Deleting, not folding, is the bug:
|
|
40
|
+
* replacing one letter of a trigger word with a look-alike codepoint (fullwidth, accented, …)
|
|
41
|
+
* used to remove that letter from the squashed string entirely, so `password` (fullwidth
|
|
42
|
+
* U+FF50 `p`) squashed to `assword` and matched nothing. NFKC folds that class of look-alike —
|
|
43
|
+
* fullwidth/halfwidth forms, and the Latin compatibility block — to its ordinary ASCII form
|
|
44
|
+
* before the letter can be deleted. The NFD pass afterward strips combining marks so an accented
|
|
45
|
+
* letter (`á` → `a` + combining acute) folds the same way once the mark itself is dropped.
|
|
46
|
+
*
|
|
47
|
+
* This does NOT close the general homoglyph class. NFKC has no cross-script confusables table:
|
|
48
|
+
* Cyrillic `а` (U+0430) and Greek `ο` (U+03BF) stay distinct codepoints from Latin `a`/`o` after
|
|
49
|
+
* NFKC, so `pаssword` (Cyrillic а) still squashes to something other than `password` and still
|
|
50
|
+
* bypasses `SUSPICIOUS_NAME`. Closing that would need an explicit confusables-folding table
|
|
51
|
+
* (e.g. Unicode TR39's) mapping each look-alike script's letters onto Latin before squashing —
|
|
52
|
+
* deliberately not built here; see the "known limit" test in guards.test.ts for the concrete gap.
|
|
53
|
+
*/
|
|
54
|
+
function squashSeparators(value) {
|
|
55
|
+
const foldedWidth = value.normalize('NFKC');
|
|
56
|
+
const withoutMarks = foldedWidth.normalize('NFD').replace(/\p{M}/gu, '');
|
|
57
|
+
return withoutMarks.replace(/[^a-z0-9]/gi, '');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Input types that hold no text.
|
|
61
|
+
*
|
|
62
|
+
* Writing to one succeeds mechanically and accomplishes nothing: setting `value` on a checkbox
|
|
63
|
+
* changes the string submitted *if* it is ticked, and leaves `checked` alone. The caller is told
|
|
64
|
+
* the fill worked and the box is still clear — a false success, which is worse than a refusal
|
|
65
|
+
* because it is never retried.
|
|
66
|
+
*/
|
|
67
|
+
const NON_TEXT_TYPES = new Set(['checkbox', 'radio', 'submit', 'reset', 'button']);
|
|
68
|
+
/**
|
|
69
|
+
* Why this field's current value must not be reported back to a caller, or `null` when reading
|
|
70
|
+
* it is safe.
|
|
71
|
+
*
|
|
72
|
+
* Reading is the strictly more dangerous direction: writing into a password box gives an agent
|
|
73
|
+
* nothing, whereas reading one out hands over the secret itself. So every secrecy signal the fill
|
|
74
|
+
* guard uses applies here identically — type, autocomplete token, and a name or id that indicates
|
|
75
|
+
* a credential or anti-forgery token — and none of them may be relaxed for reads.
|
|
76
|
+
*
|
|
77
|
+
* @param field - The field's attributes.
|
|
78
|
+
* @returns The first secrecy refusal, or `null` when the value may be reported.
|
|
79
|
+
*/
|
|
80
|
+
export function findFieldReadRefusal(field) {
|
|
81
|
+
const type = field.type?.toLowerCase();
|
|
82
|
+
if (type !== undefined && DENIED_TYPES.has(type))
|
|
83
|
+
return 'denied-type';
|
|
84
|
+
const autocomplete = field.autocomplete?.toLowerCase();
|
|
85
|
+
if (autocomplete !== undefined) {
|
|
86
|
+
// The real grammar is space-separated tokens, but a comma-joined value
|
|
87
|
+
// (`"new-password,current-password"`) is markup an attacker-controlled page can trivially
|
|
88
|
+
// emit even though real browsers won't autofill it correctly either — splitting on
|
|
89
|
+
// whitespace alone turned the whole string into one unrecognized token, so the field still
|
|
90
|
+
// worked as a plain text input while looking unguarded.
|
|
91
|
+
for (const token of autocomplete.split(/[\s,]+/)) {
|
|
92
|
+
if (DENIED_AUTOCOMPLETE.has(token))
|
|
93
|
+
return 'denied-autocomplete';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (SUSPICIOUS_NAME.test(squashSeparators(field.name ?? ''))
|
|
97
|
+
|| SUSPICIOUS_NAME.test(squashSeparators(field.id ?? ''))
|
|
98
|
+
// `name`/`id` are the *machine* names; a form builder or CMS that emits `name="field_47"`
|
|
99
|
+
// next to a visible "Card number" label left the guard nothing to judge until this existed.
|
|
100
|
+
//
|
|
101
|
+
// Each label is squashed and matched on its own rather than as one joined string: squashing
|
|
102
|
+
// deletes the separators, so a join would let two innocuous labels form a trigger word across
|
|
103
|
+
// their boundary ("Address" + "Snapshot" -> "addresssnapshot", which contains "ssn"). This
|
|
104
|
+
// guard is deliberately over-inclusive, but not at the cost of matching text no page wrote.
|
|
105
|
+
|| (field.accessibleLabels ?? []).some((label) => SUSPICIOUS_NAME.test(squashSeparators(label)))) {
|
|
106
|
+
return 'suspicious-name';
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Why an automated fill must be refused for this field, or `null` when it is safe to write.
|
|
112
|
+
*
|
|
113
|
+
* Fails closed on anything that looks like a credential, payment instrument, one-time code, or
|
|
114
|
+
* anti-forgery token, independent of whether the field carries an agent handle. Tagging a
|
|
115
|
+
* password box does not make it fillable. Every secrecy refusal is also a fill refusal — a field
|
|
116
|
+
* whose contents an agent may not read is certainly not one it may overwrite — and two further
|
|
117
|
+
* refusals cover fields that are simply not writable.
|
|
118
|
+
*
|
|
119
|
+
* @param field - The field's attributes.
|
|
120
|
+
* @returns The first refusal reason, or `null` when the field may be filled.
|
|
121
|
+
*/
|
|
122
|
+
export function findFieldFillRefusal(field) {
|
|
123
|
+
const secrecy = findFieldReadRefusal(field);
|
|
124
|
+
if (secrecy !== null)
|
|
125
|
+
return secrecy;
|
|
126
|
+
const type = field.type?.toLowerCase();
|
|
127
|
+
if (type !== undefined && NON_TEXT_TYPES.has(type))
|
|
128
|
+
return 'not-text';
|
|
129
|
+
if (field.readOnly === true)
|
|
130
|
+
return 'read-only';
|
|
131
|
+
if (field.disabled === true)
|
|
132
|
+
return 'disabled';
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const FIELD_REFUSAL_MESSAGES = {
|
|
136
|
+
'denied-type': 'this field type can never be filled by an agent',
|
|
137
|
+
'denied-autocomplete': 'this field holds a credential or payment instrument',
|
|
138
|
+
'suspicious-name': 'this field name indicates a secret or anti-forgery token',
|
|
139
|
+
'not-text': 'this control holds no text — activate it with the click verb instead of filling it',
|
|
140
|
+
'read-only': 'this field is read-only',
|
|
141
|
+
disabled: 'this field is disabled',
|
|
142
|
+
};
|
|
143
|
+
const FIELD_READ_REFUSAL_MESSAGES = {
|
|
144
|
+
'denied-type': 'this field type is never readable by an agent',
|
|
145
|
+
'denied-autocomplete': 'this field holds a credential or payment instrument',
|
|
146
|
+
'suspicious-name': 'this field name indicates a secret or anti-forgery token',
|
|
147
|
+
};
|
|
148
|
+
/** Model-readable reason for a refusal, so a blocked fill explains itself instead of failing opaquely. */
|
|
149
|
+
export function describeFieldRefusal(refusal) {
|
|
150
|
+
return FIELD_REFUSAL_MESSAGES[refusal];
|
|
151
|
+
}
|
|
152
|
+
/** Model-readable reason a value was withheld, so a caller knows the field has contents it may not see rather than assuming it is empty. */
|
|
153
|
+
export function describeFieldReadRefusal(refusal) {
|
|
154
|
+
return FIELD_READ_REFUSAL_MESSAGES[refusal];
|
|
155
|
+
}
|
|
156
|
+
/** Default cap on page-authored text handed to a model. */
|
|
157
|
+
export const MAX_AGENT_LABEL_LENGTH = 200;
|
|
158
|
+
/**
|
|
159
|
+
* C0/C1 control characters plus the Unicode bidirectional and invisible formatting ranges.
|
|
160
|
+
*
|
|
161
|
+
* The bidi overrides matter as much as the control codes: `U+202E` and friends let page text
|
|
162
|
+
* render one way and read another, which is a direct way to smuggle a different instruction
|
|
163
|
+
* into whatever reads the label back.
|
|
164
|
+
*/
|
|
165
|
+
const CONTROL_AND_BIDI =
|
|
166
|
+
// eslint-disable-next-line no-control-regex
|
|
167
|
+
/[\u0000-\u001F\u007F-\u009F\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u2069\uFEFF]/g;
|
|
168
|
+
/**
|
|
169
|
+
* Normalizes page-authored text before it reaches a model.
|
|
170
|
+
*
|
|
171
|
+
* Strips control characters (including the bidirectional overrides used to make text render
|
|
172
|
+
* differently from how it reads), collapses whitespace, and caps the length. The result is still
|
|
173
|
+
* untrusted *content* — this only bounds it; it does not make it safe to follow as instruction.
|
|
174
|
+
*
|
|
175
|
+
* @param raw - Text read off the page, e.g. a `data-agent-label`.
|
|
176
|
+
* @param maxLength - Cap, defaulting to {@link MAX_AGENT_LABEL_LENGTH}.
|
|
177
|
+
* @returns The bounded text and whether anything was cut.
|
|
178
|
+
*/
|
|
179
|
+
export function normalizeAgentLabel(raw, maxLength = MAX_AGENT_LABEL_LENGTH) {
|
|
180
|
+
const stripped = raw.replace(CONTROL_AND_BIDI, ' ');
|
|
181
|
+
const collapsed = stripped.replace(/\s+/g, ' ').trim();
|
|
182
|
+
if (collapsed.length <= maxLength)
|
|
183
|
+
return { text: collapsed, truncated: false };
|
|
184
|
+
return { text: collapsed.slice(0, maxLength), truncated: true };
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=guards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guards.js","sourceRoot":"","sources":["../../src/core/guards.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA6BH,yDAAyD;AACzD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEtE,wFAAwF;AACxF,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC;IAClC,kBAAkB,EAAE,cAAc,EAAE,eAAe;IACnD,WAAW,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;CACrF,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,eAAe,GACnB,+OAA+O,CAAC;AAElP,yFAAyF;AACzF,uFAAuF;AACvF,2FAA2F;AAC3F,kFAAkF;AAClF,wFAAwF;AACxF,6FAA6F;AAE7F;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,SAAS,gBAAgB,CAAC,KAAa;IACrC,MAAM,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,YAAY,GAAG,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACzE,OAAO,YAAY,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;AACjD,CAAC;AAeD;;;;;;;GAOG;AACH,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;AAInF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAsB;IACzD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC;IACvC,IAAI,IAAI,KAAK,SAAS,IAAI,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,aAAa,CAAC;IAEvE,MAAM,YAAY,GAAG,KAAK,CAAC,YAAY,EAAE,WAAW,EAAE,CAAC;IACvD,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,uEAAuE;QACvE,0FAA0F;QAC1F,mFAAmF;QACnF,2FAA2F;QAC3F,wDAAwD;QACxD,KAAK,MAAM,KAAK,IAAI,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjD,IAAI,mBAAmB,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,OAAO,qBAAqB,CAAC;QACnE,CAAC;IACH,CAAC;IAED,IACE,eAAe,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;WACrD,eAAe,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QACzD,0FAA0F;QAC1F,4FAA4F;QAC5F,EAAE;QACF,4FAA4F;QAC5F,8FAA8F;QAC9F,2FAA2F;QAC3F,4FAA4F;WACzF,CAAC,KAAK,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,EAChG,CAAC;QACD,OAAO,iBAAiB,CAAC;IAC3B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAsB;IACzD,MAAM,OAAO,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAC5C,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,OAAO,CAAC;IACrC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC;IACvC,IAAI,IAAI,KAAK,SAAS,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,UAAU,CAAC;IACtE,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI;QAAE,OAAO,WAAW,CAAC;IAChD,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI;QAAE,OAAO,UAAU,CAAC;IAC/C,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,sBAAsB,GAAiC;IAC3D,aAAa,EAAE,iDAAiD;IAChE,qBAAqB,EAAE,qDAAqD;IAC5E,iBAAiB,EAAE,0DAA0D;IAC7E,UAAU,EAAE,oFAAoF;IAChG,WAAW,EAAE,yBAAyB;IACtC,QAAQ,EAAE,wBAAwB;CACnC,CAAC;AAEF,MAAM,2BAA2B,GAAqC;IACpE,aAAa,EAAE,+CAA+C;IAC9D,qBAAqB,EAAE,qDAAqD;IAC5E,iBAAiB,EAAE,0DAA0D;CAC9E,CAAC;AAEF,0GAA0G;AAC1G,MAAM,UAAU,oBAAoB,CAAC,OAAqB;IACxD,OAAO,sBAAsB,CAAC,OAAO,CAAC,CAAC;AACzC,CAAC;AAED,4IAA4I;AAC5I,MAAM,UAAU,wBAAwB,CAAC,OAAyB;IAChE,OAAO,2BAA2B,CAAC,OAAO,CAAC,CAAC;AAC9C,CAAC;AAED,2DAA2D;AAC3D,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,gBAAgB;AACpB,4CAA4C;AAC5C,yFAAyF,CAAC;AAO5F;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAW,EAAE,SAAS,GAAG,sBAAsB;IACjF,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;IACpD,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACvD,IAAI,SAAS,CAAC,MAAM,IAAI,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAChF,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAClE,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module handle
|
|
3
|
+
*
|
|
4
|
+
* `agentHandle('save')` — the attribute props a component spreads onto its root element to
|
|
5
|
+
* publish itself under this package's `data-agent-*` markup convention (see
|
|
6
|
+
* `element-handles.ts`), without the caller having to know the attribute names or re-derive the
|
|
7
|
+
* handle-validity rule itself.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately pure data, no DOM: this returns a plain object of string attributes, spreadable by
|
|
10
|
+
* React (`<button {...agentHandle('save')}>`), Vue, Svelte, or any renderer that accepts arbitrary
|
|
11
|
+
* attribute props on an element. That is what lets it live in this package's universal root
|
|
12
|
+
* rather than `./dom` — nothing here touches `document` or any browser global.
|
|
13
|
+
*/
|
|
14
|
+
import { AGENT_ELEMENT_ATTRIBUTE, AGENT_LABEL_ATTRIBUTE, AGENT_PAGE_ATTRIBUTE, AGENT_ROLE_ATTRIBUTE, type AgentElementRole } from './element-handles.js';
|
|
15
|
+
/** Optional markup to attach alongside the handle itself. */
|
|
16
|
+
export interface AgentHandleOptions {
|
|
17
|
+
/** What verb applies to this element — see {@link AgentElementRole}. */
|
|
18
|
+
readonly role?: AgentElementRole;
|
|
19
|
+
/** Stable, page-authored ontology for this element (e.g. `"Full name"`). Never live text. */
|
|
20
|
+
readonly label?: string;
|
|
21
|
+
/** Which published page this element belongs to, for a multi-page host. */
|
|
22
|
+
readonly page?: string;
|
|
23
|
+
}
|
|
24
|
+
/** The attribute props {@link agentHandle} returns, ready to spread onto an element. */
|
|
25
|
+
export type AgentHandleProps = {
|
|
26
|
+
readonly [AGENT_ELEMENT_ATTRIBUTE]: string;
|
|
27
|
+
} & Partial<Record<typeof AGENT_ROLE_ATTRIBUTE | typeof AGENT_LABEL_ATTRIBUTE | typeof AGENT_PAGE_ATTRIBUTE, string>>;
|
|
28
|
+
/**
|
|
29
|
+
* Builds the attribute props that publish `handle` as an agent-addressable element.
|
|
30
|
+
*
|
|
31
|
+
* @param handle - The handle to publish. Must satisfy {@link isValidElementHandle} — this
|
|
32
|
+
* function reuses that exact rule rather than a second, possibly-diverging check, since an
|
|
33
|
+
* adversarial probe already confirmed it rejects quotes, brackets, backslashes, unicode and
|
|
34
|
+
* overlong handles (see `element-handles.ts`).
|
|
35
|
+
* @param options - Optional role/label/page markup.
|
|
36
|
+
* @returns Attribute props to spread onto the element that should carry this handle.
|
|
37
|
+
* @throws If `handle` is not a valid element handle — never publishes a handle a caller (`page.*`
|
|
38
|
+
* capabilities, `resolveHandleSelector`) could not later resolve.
|
|
39
|
+
*/
|
|
40
|
+
export declare function agentHandle(handle: string, options?: AgentHandleOptions): AgentHandleProps;
|
|
41
|
+
/**
|
|
42
|
+
* The handle for one sub-element published under `base` — `<base>-<action>`.
|
|
43
|
+
*
|
|
44
|
+
* The one join rule every multi-control component under a single host-supplied base handle needs
|
|
45
|
+
* (a settings tab, a card, a form), extracted here so each such component imports it rather than
|
|
46
|
+
* re-deriving the same template literal. First established as `sourceConfigActionHandle` in
|
|
47
|
+
* `../../features/source-config-list/agent-handles.ts` before this package had a generic version —
|
|
48
|
+
* that feature's own doc comment explains the naming split this generalizes ("a host names a
|
|
49
|
+
* component; the component names its own parts").
|
|
50
|
+
*
|
|
51
|
+
* @param base - The component's own handle, as published by the host. Must already be a valid
|
|
52
|
+
* element handle — callers own that, same as every other `agentHandle()` base.
|
|
53
|
+
* @param action - The sub-element's name. Always a literal chosen by the component, never host data.
|
|
54
|
+
* @returns `<base>-<action>`.
|
|
55
|
+
* @complexity O(1).
|
|
56
|
+
*/
|
|
57
|
+
export declare function agentSubHandle(base: string, action: string): string;
|
|
58
|
+
/** What to publish about one sub-element — see {@link agentHandleProps}. */
|
|
59
|
+
export interface AgentHandlePropsOptions extends AgentHandleOptions {
|
|
60
|
+
/**
|
|
61
|
+
* The sub-element's name, appended to `base` via {@link agentSubHandle}. Omit when the element
|
|
62
|
+
* IS `base` — the component's own root.
|
|
63
|
+
*/
|
|
64
|
+
readonly action?: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Builds the `data-agent-*` attribute props for one element of a component that takes a single
|
|
68
|
+
* OPTIONAL base handle from its host, or nothing at all when the host published no base.
|
|
69
|
+
*
|
|
70
|
+
* This is the seam a component with more than one agent-addressable element should use: the host
|
|
71
|
+
* hands the component ONE base handle (`agentHandle?: string` in that component's own props, named
|
|
72
|
+
* identically to this function's own `handle.ts` sibling so a caller cannot mistake which handle a
|
|
73
|
+
* component wants), and the component derives every sub-element's handle from it via `action`,
|
|
74
|
+
* exactly the "host names a component, the component names its own parts" split
|
|
75
|
+
* `source-config-list/agent-handles.ts` established first. Returning `{}` rather than throwing on
|
|
76
|
+
* an `undefined` base is what keeps the host prop opt-in: a host that never passes `agentHandle`
|
|
77
|
+
* renders exactly the markup it did before this existed — additive, never a behavior change.
|
|
78
|
+
*
|
|
79
|
+
* @param base - The component's own handle from the host, or `undefined` when it published none.
|
|
80
|
+
* @param options - Role, stable label, optional page, and the optional `action` naming this
|
|
81
|
+
* sub-element (see {@link AgentHandlePropsOptions}).
|
|
82
|
+
* @returns Spreadable attribute props, or `{}` when `base` is `undefined`.
|
|
83
|
+
* @throws If `base` is defined but not a valid element handle — via `agentHandle()`, deliberately
|
|
84
|
+
* unguarded so a bad host-supplied base fails loudly at first render rather than silently
|
|
85
|
+
* answering to a handle the host never wrote.
|
|
86
|
+
* @complexity O(1).
|
|
87
|
+
*/
|
|
88
|
+
export declare function agentHandleProps(base: string | undefined, options?: AgentHandlePropsOptions): AgentHandleProps | Record<string, never>;
|
|
89
|
+
//# sourceMappingURL=handle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handle.d.ts","sourceRoot":"","sources":["../../src/core/handle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EAEpB,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAE9B,6DAA6D;AAC7D,MAAM,WAAW,kBAAkB;IACjC,wEAAwE;IACxE,QAAQ,CAAC,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACjC,6FAA6F;IAC7F,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wFAAwF;AACxF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAC5C,GAAG,OAAO,CACT,MAAM,CACJ,OAAO,oBAAoB,GAAG,OAAO,qBAAqB,GAAG,OAAO,oBAAoB,EACxF,MAAM,CACP,CACF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,gBAAgB,CAY9F;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED,4EAA4E;AAC5E,MAAM,WAAW,uBAAwB,SAAQ,kBAAkB;IACjE;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,GAAE,uBAA4B,GACpC,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAI1C"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module handle
|
|
3
|
+
*
|
|
4
|
+
* `agentHandle('save')` — the attribute props a component spreads onto its root element to
|
|
5
|
+
* publish itself under this package's `data-agent-*` markup convention (see
|
|
6
|
+
* `element-handles.ts`), without the caller having to know the attribute names or re-derive the
|
|
7
|
+
* handle-validity rule itself.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately pure data, no DOM: this returns a plain object of string attributes, spreadable by
|
|
10
|
+
* React (`<button {...agentHandle('save')}>`), Vue, Svelte, or any renderer that accepts arbitrary
|
|
11
|
+
* attribute props on an element. That is what lets it live in this package's universal root
|
|
12
|
+
* rather than `./dom` — nothing here touches `document` or any browser global.
|
|
13
|
+
*/
|
|
14
|
+
import { AGENT_ELEMENT_ATTRIBUTE, AGENT_LABEL_ATTRIBUTE, AGENT_PAGE_ATTRIBUTE, AGENT_ROLE_ATTRIBUTE, isValidElementHandle, } from './element-handles.js';
|
|
15
|
+
/**
|
|
16
|
+
* Builds the attribute props that publish `handle` as an agent-addressable element.
|
|
17
|
+
*
|
|
18
|
+
* @param handle - The handle to publish. Must satisfy {@link isValidElementHandle} — this
|
|
19
|
+
* function reuses that exact rule rather than a second, possibly-diverging check, since an
|
|
20
|
+
* adversarial probe already confirmed it rejects quotes, brackets, backslashes, unicode and
|
|
21
|
+
* overlong handles (see `element-handles.ts`).
|
|
22
|
+
* @param options - Optional role/label/page markup.
|
|
23
|
+
* @returns Attribute props to spread onto the element that should carry this handle.
|
|
24
|
+
* @throws If `handle` is not a valid element handle — never publishes a handle a caller (`page.*`
|
|
25
|
+
* capabilities, `resolveHandleSelector`) could not later resolve.
|
|
26
|
+
*/
|
|
27
|
+
export function agentHandle(handle, options = {}) {
|
|
28
|
+
if (!isValidElementHandle(handle)) {
|
|
29
|
+
throw new Error(`invalid element handle "${handle.slice(0, 128)}": `
|
|
30
|
+
+ 'handles are lowercase words joined by single hyphens, and are never CSS selectors');
|
|
31
|
+
}
|
|
32
|
+
const props = { [AGENT_ELEMENT_ATTRIBUTE]: handle };
|
|
33
|
+
if (options.role !== undefined)
|
|
34
|
+
props[AGENT_ROLE_ATTRIBUTE] = options.role;
|
|
35
|
+
if (options.label !== undefined)
|
|
36
|
+
props[AGENT_LABEL_ATTRIBUTE] = options.label;
|
|
37
|
+
if (options.page !== undefined)
|
|
38
|
+
props[AGENT_PAGE_ATTRIBUTE] = options.page;
|
|
39
|
+
return props;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The handle for one sub-element published under `base` — `<base>-<action>`.
|
|
43
|
+
*
|
|
44
|
+
* The one join rule every multi-control component under a single host-supplied base handle needs
|
|
45
|
+
* (a settings tab, a card, a form), extracted here so each such component imports it rather than
|
|
46
|
+
* re-deriving the same template literal. First established as `sourceConfigActionHandle` in
|
|
47
|
+
* `../../features/source-config-list/agent-handles.ts` before this package had a generic version —
|
|
48
|
+
* that feature's own doc comment explains the naming split this generalizes ("a host names a
|
|
49
|
+
* component; the component names its own parts").
|
|
50
|
+
*
|
|
51
|
+
* @param base - The component's own handle, as published by the host. Must already be a valid
|
|
52
|
+
* element handle — callers own that, same as every other `agentHandle()` base.
|
|
53
|
+
* @param action - The sub-element's name. Always a literal chosen by the component, never host data.
|
|
54
|
+
* @returns `<base>-<action>`.
|
|
55
|
+
* @complexity O(1).
|
|
56
|
+
*/
|
|
57
|
+
export function agentSubHandle(base, action) {
|
|
58
|
+
return `${base}-${action}`;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Builds the `data-agent-*` attribute props for one element of a component that takes a single
|
|
62
|
+
* OPTIONAL base handle from its host, or nothing at all when the host published no base.
|
|
63
|
+
*
|
|
64
|
+
* This is the seam a component with more than one agent-addressable element should use: the host
|
|
65
|
+
* hands the component ONE base handle (`agentHandle?: string` in that component's own props, named
|
|
66
|
+
* identically to this function's own `handle.ts` sibling so a caller cannot mistake which handle a
|
|
67
|
+
* component wants), and the component derives every sub-element's handle from it via `action`,
|
|
68
|
+
* exactly the "host names a component, the component names its own parts" split
|
|
69
|
+
* `source-config-list/agent-handles.ts` established first. Returning `{}` rather than throwing on
|
|
70
|
+
* an `undefined` base is what keeps the host prop opt-in: a host that never passes `agentHandle`
|
|
71
|
+
* renders exactly the markup it did before this existed — additive, never a behavior change.
|
|
72
|
+
*
|
|
73
|
+
* @param base - The component's own handle from the host, or `undefined` when it published none.
|
|
74
|
+
* @param options - Role, stable label, optional page, and the optional `action` naming this
|
|
75
|
+
* sub-element (see {@link AgentHandlePropsOptions}).
|
|
76
|
+
* @returns Spreadable attribute props, or `{}` when `base` is `undefined`.
|
|
77
|
+
* @throws If `base` is defined but not a valid element handle — via `agentHandle()`, deliberately
|
|
78
|
+
* unguarded so a bad host-supplied base fails loudly at first render rather than silently
|
|
79
|
+
* answering to a handle the host never wrote.
|
|
80
|
+
* @complexity O(1).
|
|
81
|
+
*/
|
|
82
|
+
export function agentHandleProps(base, options = {}) {
|
|
83
|
+
if (base === undefined)
|
|
84
|
+
return {};
|
|
85
|
+
const { action, ...rest } = options;
|
|
86
|
+
return agentHandle(action === undefined ? base : agentSubHandle(base, action), rest);
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=handle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handle.js","sourceRoot":"","sources":["../../src/core/handle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,GAErB,MAAM,sBAAsB,CAAC;AAsB9B;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,UAA8B,EAAE;IAC1E,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CACb,2BAA2B,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK;cAClD,mFAAmF,CACtF,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAA2B,EAAE,CAAC,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5E,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;QAAE,KAAK,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAC3E,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS;QAAE,KAAK,CAAC,qBAAqB,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC;IAC9E,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;QAAE,KAAK,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAC3E,OAAO,KAAyB,CAAC;AACnC,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,MAAc;IACzD,OAAO,GAAG,IAAI,IAAI,MAAM,EAAE,CAAC;AAC7B,CAAC;AAWD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAwB,EACxB,UAAmC,EAAE;IAErC,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IAClC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IACpC,OAAO,WAAW,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;AACvF,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jini-ai/agentic — the framework-free half of agent control.
|
|
3
|
+
*
|
|
4
|
+
* Everything an outside caller needs to drive a Jini frontend, with no React, no DOM, and no
|
|
5
|
+
* transport: the capability vocabulary, the two shipped manifests, the `data-agent-*` markup
|
|
6
|
+
* convention, and the refusals that must hold everywhere.
|
|
7
|
+
*
|
|
8
|
+
* Framework bindings (`@jini-ai/chat-react`, via `@jini-ai/agentic/dom`'s `createDomPageDriver`, and
|
|
9
|
+
* any Vue/Svelte sibling) import from here and add only the wiring their framework needs.
|
|
10
|
+
* Server-side hosts (an HTTP route table, an MCP stdio server) import the same manifests, so the
|
|
11
|
+
* two can never drift apart.
|
|
12
|
+
*
|
|
13
|
+
* What is deliberately NOT here: how an action reaches a page. That is a transport concern —
|
|
14
|
+
* same-document in a real site like a CMS, `postMessage` when a host embeds an untrusted
|
|
15
|
+
* preview in a sandboxed frame. The verbs are identical either way. Also not here: any product's
|
|
16
|
+
* OWN capabilities (`@jini-ai/chat-core`'s `chat.*` verbs stay in chat-core, which depends on this
|
|
17
|
+
* package for the vocabulary rather than the other way around) — see this package's
|
|
18
|
+
* source-map.md for why the split is real rather than a wholesale relocation.
|
|
19
|
+
*/
|
|
20
|
+
export { findCapability, findCapabilityInputError, availableCapabilities, type CapabilityDef, type CapabilityInputSchema, type CapabilityRisk, type CapabilitySurface, } from './capability.js';
|
|
21
|
+
export type { PageDriver, FindElementsFilter, PageSummary } from './page-driver.js';
|
|
22
|
+
export { executePageCapability, projectElementState, DEFAULT_HIGHLIGHT_MS, MAX_HIGHLIGHT_MS, MAX_STATEFUL_ELEMENTS, type FindElementsResult, type PageElementResult, type PageActivitySnapshot, type PageWriteObservation, } from './page-executor.js';
|
|
23
|
+
export { PAGE_CAPABILITIES } from './page-capabilities.js';
|
|
24
|
+
export { AGENT_ELEMENT_ATTRIBUTE, AGENT_ROLE_ATTRIBUTE, AGENT_LABEL_ATTRIBUTE, AGENT_PAGE_ATTRIBUTE, AGENT_ELEMENT_ROLES, isValidElementHandle, resolveHandleSelector, type AgentElementRole, type AgentElementDescriptor, type AgentElementRawState, type AgentElementState, } from './element-handles.js';
|
|
25
|
+
export { agentHandle, agentSubHandle, agentHandleProps, type AgentHandleOptions, type AgentHandleProps, type AgentHandlePropsOptions, } from './handle.js';
|
|
26
|
+
export { buildAgentListHandles } from './list-handles.js';
|
|
27
|
+
export { findFieldFillRefusal, findFieldReadRefusal, describeFieldRefusal, describeFieldReadRefusal, normalizeAgentLabel, MAX_AGENT_LABEL_LENGTH, type FieldDescriptor, type FieldRefusal, type FieldReadRefusal, type NormalizedLabel, } from './guards.js';
|
|
28
|
+
/**
|
|
29
|
+
* Protocol adapters, named for what they derive from.
|
|
30
|
+
*
|
|
31
|
+
* Each is a ONE-WAY projection out of the neutral core: they import `capability.js`, and nothing
|
|
32
|
+
* imports back. That direction is the whole point — if the core ever imported an adapter, the
|
|
33
|
+
* protocol we happened to build against would leak into the vocabulary and the layer would stop
|
|
34
|
+
* being neutral. None of them opens a connection or touches a browser global; a framework
|
|
35
|
+
* binding owns transport and host access.
|
|
36
|
+
*/
|
|
37
|
+
export { toWebMcpTool, toWebMcpTools, isValidWebMcpToolName, WebMcpConfirmationRequiredError, InvalidWebMcpToolNameError, type WebMcpToolRegistration, type WebMcpToolAnnotations, type WebMcpRegisterToolOptions, type WebMcpUserInteraction, type RequestUserInteraction, type ToWebMcpToolOptions, } from './webmcp.js';
|
|
38
|
+
/**
|
|
39
|
+
* Genuine AG-UI: the {@link CapabilityDef} → AG-UI *frontend tool* projection. Real conformance —
|
|
40
|
+
* it emits the protocol's own `parameters` field name (not `inputSchema`) and its canonical
|
|
41
|
+
* `TOOL_CALL_START`/`TOOL_CALL_ARGS`/`TOOL_CALL_END` event names, all three of which appear in
|
|
42
|
+
* AG-UI's published `EventType` enum.
|
|
43
|
+
*/
|
|
44
|
+
export { toAgUiTool, toAgUiTools, createAgUiToolResult, AG_UI_TOOL_CALL_EVENTS, type AgUiTool, type AgUiToolResultMessage, } from './ag-ui.js';
|
|
45
|
+
/**
|
|
46
|
+
* Jini's **own** run-stream surface protocol, from `./gen-ui/` — six event kinds and the encoder
|
|
47
|
+
* that produces them (folded in 2026-07-26 from the standalone `@jini-ai/agui` package, plan §3a).
|
|
48
|
+
*
|
|
49
|
+
* Renamed from `./agui/` on 2026-07-27, because this is NOT AG-UI. The real Agent-User Interaction
|
|
50
|
+
* Protocol (https://github.com/ag-ui-protocol/ag-ui) carries a 33-member `SCREAMING_SNAKE` event
|
|
51
|
+
* enum — `RUN_STARTED`, `TEXT_MESSAGE_CONTENT`, `STATE_DELTA`, `STEP_STARTED`, `REASONING_*` — and
|
|
52
|
+
* shares **zero** event names with the six dotted-lowercase kinds in `./gen-ui/events.ts`, which
|
|
53
|
+
* are a de-branded port of one product's internal run-event adapter. Carrying the AG-UI name on
|
|
54
|
+
* them asserted a conformance that does not exist.
|
|
55
|
+
*
|
|
56
|
+
* `./ag-ui.ts` above was wrongly folded in with them on 2026-07-26 and moved back out on the 27th:
|
|
57
|
+
* it is the one file here that really does speak AG-UI, so merging it with these six was the exact
|
|
58
|
+
* inversion of the truth.
|
|
59
|
+
*
|
|
60
|
+
* The exported symbols were renamed `Agui`/`AGUI` → `GenUi` on 2026-07-29, finishing what the
|
|
61
|
+
* directory move started: with genuine AG-UI (`./ag-ui.ts`, exported just above as `toAgUiTool`
|
|
62
|
+
* and friends) living in the same barrel, two unrelated protocols were reading as one.
|
|
63
|
+
*
|
|
64
|
+
* `@jini-ai/http-kit` used to mount this encoder behind a route still named `/api/runs/:runId/
|
|
65
|
+
* agui-stream` — left un-renamed at the time on the assumption it was a wire contract an
|
|
66
|
+
* already-deployed client might be calling. A 2026-08-18 audit found zero callers of that route
|
|
67
|
+
* anywhere (no client in this repo or any consuming product ever requested it, and `@jini-ai/http-kit` has never
|
|
68
|
+
* actually been published to npm, so no external integrator could depend on it either); the route,
|
|
69
|
+
* its registrar, and its tests were removed outright rather than renamed. This `gen-ui/` module
|
|
70
|
+
* itself was untouched — it is `@jini-ai/agentic`'s own public export and its removal is a separate
|
|
71
|
+
* decision.
|
|
72
|
+
*/
|
|
73
|
+
export { createGenUiEncoder, type GenUiEncodeContext, type GenUiEncoder, type GenUiAgentMessageEvent, type GenUiEvent, type GenUiEventBase, type GenUiEventKind, type GenUiRunLifecycleEvent, type GenUiStateUpdateEvent, type GenUiSurfaceRequestedEvent, type GenUiSurfaceRespondedEvent, type GenUiToolCallEvent, } from './gen-ui/index.js';
|
|
74
|
+
export { MCP_UI_VIEW_METHODS, MCP_UI_HOST_NOTIFICATIONS, MCP_UI_VIEW_NOTIFICATIONS, MCP_UI_HOST_REQUESTS, MCP_UI_SANDBOX_NOTE, JINI_PAGE_ACTION_METHOD, JSON_RPC_ERROR_CODES, isJsonRpcMessage, isJsonRpcRequest, createJsonRpcRequest, createJsonRpcNotification, createJsonRpcResult, createJsonRpcError, createPageActionRequest, type JsonRpcMessage, type JsonRpcRequest, type JsonRpcNotification, type JsonRpcResponse, type JsonRpcError, } from './mcp-ui-apps.js';
|
|
75
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EACL,cAAc,EACd,wBAAwB,EACxB,qBAAqB,EACrB,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpF,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,EAChB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,GAC1B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,GACvB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,GAC7B,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,wBAAwB,EACxB,mBAAmB,EACnB,sBAAsB,EACtB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,aAAa,CAAC;AAErB;;;;;;;;GAQG;AACH,OAAO,EACL,YAAY,EACZ,aAAa,EACb,qBAAqB,EACrB,+BAA+B,EAC/B,0BAA0B,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,yBAAyB,EAC9B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,GACzB,MAAM,aAAa,CAAC;AAErB;;;;;GAKG;AACH,OAAO,EACL,UAAU,EACV,WAAW,EACX,oBAAoB,EACpB,sBAAsB,EACtB,KAAK,QAAQ,EACb,KAAK,qBAAqB,GAC3B,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EACL,kBAAkB,EAClB,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,sBAAsB,EAC3B,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,GACxB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,mBAAmB,EACnB,yBAAyB,EACzB,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACnB,uBAAuB,EACvB,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACpB,yBAAyB,EACzB,mBAAmB,EACnB,kBAAkB,EAClB,uBAAuB,EACvB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,YAAY,GAClB,MAAM,kBAAkB,CAAC"}
|