@exponential_app/engine 0.0.0-stage → 0.1.1
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/LICENSE +201 -0
- package/NOTICE +54 -0
- package/README.md +35 -2
- package/dist/capabilities.js +29 -0
- package/dist/capabilities.js.map +9 -0
- package/dist/chunks/index-3nf9f7pv.js +315 -0
- package/dist/chunks/index-3nf9f7pv.js.map +10 -0
- package/dist/chunks/index-3rgr16n2.js +443 -0
- package/dist/chunks/index-3rgr16n2.js.map +9 -0
- package/dist/chunks/index-79swfbsm.js +366 -0
- package/dist/chunks/index-79swfbsm.js.map +10 -0
- package/dist/chunks/index-knntt4cb.js +259 -0
- package/dist/chunks/index-knntt4cb.js.map +10 -0
- package/dist/chunks/index-m2845dn6.js +72 -0
- package/dist/chunks/index-m2845dn6.js.map +10 -0
- package/dist/chunks/index-pqpnz4ns.js +2213 -0
- package/dist/chunks/index-pqpnz4ns.js.map +9 -0
- package/dist/chunks/index-q4zws02e.js +2301 -0
- package/dist/chunks/index-q4zws02e.js.map +10 -0
- package/dist/chunks/index-s3xjr701.js +170 -0
- package/dist/chunks/index-s3xjr701.js.map +10 -0
- package/dist/chunks/index-sk8b0dh4.js +126 -0
- package/dist/chunks/index-sk8b0dh4.js.map +10 -0
- package/dist/chunks/index-tvsykgcv.js +3015 -0
- package/dist/chunks/index-tvsykgcv.js.map +19 -0
- package/dist/chunks/index-v0m7r4y3.js +6869 -0
- package/dist/chunks/index-v0m7r4y3.js.map +13 -0
- package/dist/consent.js +24 -0
- package/dist/consent.js.map +9 -0
- package/dist/index.js +654 -0
- package/dist/index.js.map +12 -0
- package/dist/lifecycle.js +11 -0
- package/dist/lifecycle.js.map +9 -0
- package/dist/outbox.js +27 -0
- package/dist/outbox.js.map +9 -0
- package/dist/pdf.js +15 -0
- package/dist/pdf.js.map +9 -0
- package/dist/providers.js +26 -0
- package/dist/providers.js.map +9 -0
- package/dist/runner.js +24 -0
- package/dist/runner.js.map +9 -0
- package/dist/tools.js +38 -0
- package/dist/tools.js.map +9 -0
- package/dist/types/domain-contract/src/app-records.d.ts +3148 -0
- package/dist/types/domain-contract/src/app-share.d.ts +222 -0
- package/dist/types/domain-contract/src/apps.d.ts +612 -0
- package/dist/types/eengine/fixtures/providers.json +55 -0
- package/dist/types/eengine/src/broker.d.ts +80 -0
- package/dist/types/eengine/src/capabilities.d.ts +88 -0
- package/dist/types/eengine/src/consent.d.ts +50 -0
- package/dist/types/eengine/src/index.d.ts +12 -0
- package/dist/types/eengine/src/instance.d.ts +131 -0
- package/dist/types/eengine/src/lifecycle.d.ts +64 -0
- package/dist/types/eengine/src/outbox.d.ts +3 -0
- package/dist/types/eengine/src/pdf.d.ts +36 -0
- package/dist/types/eengine/src/providers.d.ts +38 -0
- package/dist/types/eengine/src/runner.d.ts +82 -0
- package/dist/types/eengine/src/stores.d.ts +125 -0
- package/dist/types/eengine/src/synced.d.ts +59 -0
- package/dist/types/eengine/src/tools.d.ts +79 -0
- package/dist/types/eengine/src/web/index.d.ts +20 -0
- package/dist/types/eengine/src/web/mcp.d.ts +27 -0
- package/dist/types/eengine/src/web/platform.d.ts +91 -0
- package/dist/types/eengine/src/web/stores.d.ts +125 -0
- package/dist/types/emcp/fixtures/automations.json +521 -0
- package/dist/types/emcp/fixtures/hub.json +498 -0
- package/dist/types/emcp/src/access.d.ts +55 -0
- package/dist/types/emcp/src/audit.d.ts +22 -0
- package/dist/types/emcp/src/automations.d.ts +111 -0
- package/dist/types/emcp/src/exposes.d.ts +242 -0
- package/dist/types/emcp/src/hub.d.ts +63 -0
- package/dist/types/emcp/src/index.d.ts +7 -0
- package/dist/types/emcp/src/pure.d.ts +11 -0
- package/dist/types/emcp/src/upstream.d.ts +288 -0
- package/dist/types/emcp/src/vault.d.ts +34 -0
- package/dist/web/index.js +791 -0
- package/dist/web/index.js.map +13 -0
- package/fixtures/bundle.json +1165 -0
- package/fixtures/capabilities.json +434 -0
- package/fixtures/consent.json +146 -0
- package/fixtures/lifecycle.json +125 -0
- package/fixtures/outbox.json +22 -0
- package/fixtures/providers.json +55 -0
- package/fixtures/runner.json +2167 -0
- package/package.json +92 -4
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "VAPP-260 (Engine E0, epic VAPP-259, docs/spec/apps.md §13.1): THE CAPABILITY CATALOG AND THE ONE BROKER. A CAPABILITY is a native tool provider the HOST implements per platform (E5) and mounts on the Engine's in-process MCP Proxy hub (packages/emcp hub.json) under its `name` (a hub server name: `hubServerNameValid`, never a `reservedNames` entry, so no capability can shadow the package's own functions or the Engine's internals). It declares SCOPES (`scopes`): a scope id `<capability>.<scope>` (`scopePattern`) is what a package's capability manifest names (package format 5 `capabilities: [{id, why, origins?}]`) and what a grant is kept for; each scope lists its TOOLS and its CONSENT: `ask` = the Engine's consent sheet once, the answer persisted (consent.json); `picker` = the platform's own picker or sheet IS the consent, every call, so nothing persists and the call needs the app in the foreground; `hostOnly: true` = a scope of the host itself (eDE's `shell.edit`, editing its ede-ui documents), NEVER in a manifest (the validator refuses it) and never granted to an app. `origins: true` = the manifest entry must list `origins` (exact `https://host[:port]` origins) and a call's `url` must be on one. NAMES: an agent calls `<capability>__<tool>` (exposes.json's qualified name, server = capability); app UI calls the function `<capability>.<tool>` (it must be in the package's `functions` allowlist like any other function); BOTH parse to the same `{capability, tool}` (`parseCases`), anything else is `unknown`. THE BROKER (`decideCases`, `brokerDecide`) is ONE function for UI and agent calls: the FIRST of: `unknown` (no such capability or tool: the agent answer is MCP's unknown-tool error -32602, never a hint) · `denied/hostOnly` · `denied/notDeclared` (the manifest is the CEILING: no scope declared = no call, no sheet) · `unavailable` (this host does not provide the capability, e.g. `camera` on a desktop without one) · `denied/originNotDeclared` (an `origins` scope whose `url` argument is missing or off the list) · for `picker`: `denied/needsForeground` in the background, else `call` · for `ask`: the stored grant `granted` = `call`, `denied` = `denied/consentDenied`, undecided = `ask` in the foreground (the Engine shows the consent sheet; consent.json `consentAnswer` turns the answer into the call's verdict) and `denied/consentNeeded` in the background (never a sheet behind the user's back). An agent's tools/list (`listCases`, `agentToolList`) = every tool of a scope the manifest declares and the host provides, `hostOnly` excluded, sorted by qualified name; a denied scope stays listed (the call answers `consentDenied`, so the agent can tell the person). THE AUDIT (`auditCases`, `brokerAuditRow`): every call, refused ones included, writes ONE row in audit.json's shape to the host's sink: `appId` = the package id, `clientId` = `ui` for app UI or the run id for an agent, `tool` = the qualified name `<capability>__<tool>` for both, `status` = `denied` for every refusal and `unknown`/`unavailable`, else audit.json's status of the call's outcome (`ok`, `isError` -> failed, `invalidParams` -> invalid, `rpcError`/`unreachable`/`timeout` -> upstream), `error` = the refusal's reason (`unknownTool` for unknown, `unavailable`), null on ok and failed; `argsHash` = audit.json's hash of the arguments as received. No hub tool writes the package, the grants or the host config: a grant changes only in consent.json's `grantWrite`. CAPABILITIES V1 (VAPP-265, E5): the TOOLS (`tools`, sorted by name) lock every tool's name, its app-facing `inputSchema`/`outputSchema` (what app UI and agents see: an agent's tools/list lists them as `<capability>__<tool>` with the same description and schemas) and, for `runs: native`, the `nativeOutputSchema` a host's native provider answers; `runs: engine` = the Engine answers it itself (`print_pdf`, no native call; the capability must still be provided). A host's native capability implements EVERY native tool of its capability (one it cannot serve answers isError `unavailable`). THE BRIDGE (`bridgeCases`, `bridgeCall`) runs between the broker's `call` verdict and the native, the same for UI and agents: (1) VALIDATE the arguments (`validateCases`) against `inputSchema` with the subset `type` (one or a list; `integer` = an integral number) · `properties` · `required` · `additionalProperties: false` · `minimum`/`maximum` · `minLength`/`maxLength` (code points) · `minItems`/`maxItems` · `items` · `enum` · `format` (`date-time` = RFC 3339 with a zone; `token` = 1 to 64 of `[A-Za-z0-9._-]`; `uri` = an absolute http(s) URL; `base64` = the standard alphabet, padded) · `dependentRequired` · `anyOf`/`oneOf` of `required` sets; checked in that order per value: type, then for an object required (listed order), unknown keys (sorted), each property (schema order), dependentRequired (its key order; the path is the MISSING key's), anyOf, oneOf; for an array minItems, maxItems, then each item; the FIRST violation answers isError `invalidParams: <JSON pointer, / for the root> <keyword>` (audit outcome `invalidParams`) and the native never runs; a missing top-level property with a `default` gets it. (2) MAP to the native arguments: photos.*, files_open = the validated arguments; files_save = `{name, mimeType, data}`; share_send = the given `title`/`text`/`url` plus `file: {name, mimeType, data}` for a `fileId`; print_print = `{name, mimeType, data}` (`name` default `document.<pdf|png|jpg>`); notifications_post = `{id: \"<app>/<id>\", title, body|null, at|null, tap}` where `tap` = lifecycle.json's `{type: notificationTap, app, screen, params ({} default)}` or null without a `screen` (a `screen` must be one of the package's screens, else `invalidParams: /screen enum`) and an absent `id` is generated (`n-` + 32 lowercase hex), so a host hands the tap back as the instance's event and ids never collide across apps; notifications_cancel = `{id: \"<app>/<id>\"}`. A `fileId` resolves through the host's BlobStore (`v1.context` = the app and its screens; missing = isError `notFound: <fileId>`), `data` = its bytes in base64; `text` = its UTF-8 bytes. `mimeType` = the given one, else `v1.mimeByExtension` of the name's last `.ext` (case-insensitive), else sniffed from the bytes (`%PDF-` application/pdf, FF D8 FF image/jpeg, the PNG signature image/png, `GIF8` image/gif), else text/plain for a `text` and application/octet-stream; print_print takes only a sniffed PDF, PNG or JPEG (else isError `unsupportedFile`). (3) CHECK the native's result: an `isError` result passes through as is (audit `failed`); otherwise its `structuredContent` must match `nativeOutputSchema` (else isError `badResult: <pointer> <keyword>`, nothing stored). (4) STORE every object in it that carries `data` (depth first, in order): BlobStore `put(app, name, mimeType, bytes)` (ids in the cases: `blob-1`, `blob-2`, …), the object loses `data` and gains `size` (bytes) and `fileId`; notifications_post answers `{id}` (the app's own id). Every answer is an MCP call result `{content: [{type: text, text: <the structuredContent as JSON>}], structuredContent}`, errors `{content: [{type: text, text: <reason>}], isError: true}`. THE PDF (`pdf`, `pdfCases`, `print_pdf`): ONE layout both twins write byte for byte (sha256 + size locked): A4 (`pdf.page` points), margins 56, content width 483, the cursor y from 786 down to 56; fonts F1 Helvetica and F2 Helvetica-Bold (Type1, WinAnsiEncoding; `pdf.widths` = their widths per WinAnsi code in 1/1000 em, `pdf.winAnsi` = the code points 0x80-0x9F map to; ASCII 0x20-0x7E and 0xA0-0xFF are themselves; a tab is a space, other controls drop, anything else is `?`). A text's width at size s = sum(widths) × s / 1000, compared exactly (sum × s ≤ 483000). Lines: a paragraph per `\\n`, words split on runs of spaces, greedy, a word wider than the line broken at the longest fitting prefix (one character at least); an empty paragraph is one empty line. A line at size s and leading L: if the page has content and y − L < 56, a new page; then `BT /F<n> <s> Tf 56 <y − s> Td (<text>) Tj ET` (an empty line writes nothing) and y −= L. heading: y −= 12 first unless the page is empty, F2 18/24, then y −= 4. text: F1 11/15, then y −= 6. image (a JPEG: SOF0-SOF3 width, height and 1/3/4 components = DeviceGray/RGB/CMYK, DCTDecode as is; a PNG: 8-bit, colour type 0 or 2, not interlaced, its IDATs concatenated with FlateDecode + DecodeParms Predictor 15; anything else isError `unsupportedImage: <fileId>`, a missing file `notFound: <fileId>`): scale = min(483/w, 730/h, 1), if the page has content and y − h·scale < 56 a new page, `q <w·scale> 0 0 <h·scale> 56 <y − h·scale> cm /Im<k> Do Q`, y −= h·scale; its caption: y −= 4, F1 9/12 lines; then y −= 6. pageBreak: a new page unless the page is empty. The last page is dropped if it is empty and not the only one. Numbers print rounded to 2 decimals, shortest form (`12`, `12.5`). Text strings escape `(`, `)` and `\\` and write bytes ≥ 0x80 as 3-digit octal. The file: `%PDF-1.4\\n%` E2 E3 CF D3 `\\n`; objects `<n> 0 obj\\n<body>\\nendobj\\n`: 1 the Catalog, 2 Pages (Kids, Count), 3 F1, 4 F2, 5 Info (`/Title <FEFF…>` UTF-16BE uppercase hex, `/Producer (Exponential Engine)`), then one XObject per image file in first-use order (`/Im1`…, a file used twice is one object), then per page its Page (MediaBox, `/Resources << /Font << /F1 3 0 R /F2 4 0 R >> /XObject << … >> >>`, the XObject dict only with the page's images in number order) and its content; a stream body is `<< … /Length <n> >>\\nstream\\n<data>\\nendstream` (content data = the page's operators joined by `\\n`); then `xref\\n0 <size>\\n0000000000 65535 f \\n`, one `<10-digit offset> 00000 n \\n` per object, `trailer\\n<< /Size <size> /Root 1 0 R /Info 5 0 R >>\\nstartxref\\n<offset>\\n%%EOF\\n`. The file is stored as `name` (default the title with `/\\:*?\"<>|` and controls replaced by `-`, + `.pdf`), application/pdf; the answer `{file, pages}`. EDE (#VAPP-273): Exponential Desktop's own capabilities over ede-protocol, `windows` (`windows.read`: `list`), `focus` (`focus.window`: `window`) and `launch` (`launch.app`: `app`), each `ask`, provided by eDE's shell only (`host: ede` marks them: every other host answers `unavailable`, and a platform's native suite replays only the tools of capabilities without a `host`); the host-only `shell.edit` has no v1 tool here: eDE's shell serves `shell__edit` to its own agent from its own provider, and the broker refuses it for every app. Their bridge is the plain one: the validated arguments are the native's, the native's result is the answer.",
|
|
3
|
+
"scopePattern": "^[a-z][a-z0-9]*\\.[a-z][a-z0-9-]*$",
|
|
4
|
+
"reservedNames": ["app", "bind", "consent", "engine", "exponential", "records", "upstream"],
|
|
5
|
+
"capabilities": [
|
|
6
|
+
{
|
|
7
|
+
"name": "camera",
|
|
8
|
+
"label": "Camera",
|
|
9
|
+
"scopes": [{ "id": "camera.capture", "verb": "use your camera", "consent": "ask", "tools": ["capture"] }]
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"name": "clipboard",
|
|
13
|
+
"label": "Clipboard",
|
|
14
|
+
"scopes": [
|
|
15
|
+
{ "id": "clipboard.read", "verb": "read your clipboard", "consent": "ask", "tools": ["read"] },
|
|
16
|
+
{ "id": "clipboard.write", "verb": "write to your clipboard", "consent": "ask", "tools": ["write"] }
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "files",
|
|
21
|
+
"label": "Files",
|
|
22
|
+
"scopes": [
|
|
23
|
+
{ "id": "files.read", "verb": "open files you pick", "consent": "picker", "tools": ["open"] },
|
|
24
|
+
{ "id": "files.write", "verb": "save files where you pick", "consent": "picker", "tools": ["save"] }
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "focus",
|
|
29
|
+
"label": "Focus",
|
|
30
|
+
"host": "ede",
|
|
31
|
+
"scopes": [{ "id": "focus.window", "verb": "switch to your windows", "consent": "ask", "tools": ["window"] }]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "http",
|
|
35
|
+
"label": "Network",
|
|
36
|
+
"scopes": [{ "id": "http.fetch", "verb": "connect to {origins}", "consent": "ask", "origins": true, "tools": ["fetch"] }]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "launch",
|
|
40
|
+
"label": "Launch",
|
|
41
|
+
"host": "ede",
|
|
42
|
+
"scopes": [{ "id": "launch.app", "verb": "start applications", "consent": "ask", "tools": ["app"] }]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "location",
|
|
46
|
+
"label": "Location",
|
|
47
|
+
"scopes": [{ "id": "location.read", "verb": "use your location", "consent": "ask", "tools": ["current"] }]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "notifications",
|
|
51
|
+
"label": "Notifications",
|
|
52
|
+
"scopes": [{ "id": "notifications.post", "verb": "send you notifications", "consent": "ask", "tools": ["cancel", "post"] }]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "photos",
|
|
56
|
+
"label": "Photos",
|
|
57
|
+
"scopes": [{ "id": "photos.read", "verb": "access your photos", "consent": "ask", "tools": ["get", "list", "pick"] }]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "print",
|
|
61
|
+
"label": "Print",
|
|
62
|
+
"scopes": [{ "id": "print.pdf", "verb": "print and make PDFs", "consent": "picker", "tools": ["pdf", "print"] }]
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"name": "share",
|
|
66
|
+
"label": "Share",
|
|
67
|
+
"scopes": [{ "id": "share.send", "verb": "share with other apps", "consent": "picker", "tools": ["send"] }]
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "shell",
|
|
71
|
+
"label": "Desktop",
|
|
72
|
+
"scopes": [{ "id": "shell.edit", "verb": "change your desktop", "consent": "ask", "hostOnly": true, "tools": ["edit"] }]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "windows",
|
|
76
|
+
"label": "Windows",
|
|
77
|
+
"host": "ede",
|
|
78
|
+
"scopes": [{ "id": "windows.read", "verb": "see your open windows", "consent": "ask", "tools": ["list"] }]
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
"parseCases": [
|
|
82
|
+
{ "from": "agent", "name": "photos__pick", "expected": { "capability": "photos", "tool": "pick" } },
|
|
83
|
+
{ "from": "ui", "name": "photos_pick", "expected": { "capability": "photos", "tool": "pick" } },
|
|
84
|
+
{ "from": "agent", "name": "photos_pick", "expected": null },
|
|
85
|
+
{ "from": "ui", "name": "photos__pick", "expected": null },
|
|
86
|
+
{ "from": "ui", "name": "records_upsert", "expected": null },
|
|
87
|
+
{ "from": "agent", "name": "app__navigate", "expected": null },
|
|
88
|
+
{ "from": "agent", "name": "photos__", "expected": null },
|
|
89
|
+
{ "from": "ui", "name": "photos", "expected": null },
|
|
90
|
+
{ "from": "agent", "name": "Photos__pick", "expected": null }
|
|
91
|
+
],
|
|
92
|
+
"decideCases": [
|
|
93
|
+
{
|
|
94
|
+
"name": "a granted ask scope calls",
|
|
95
|
+
"manifest": [{ "id": "photos.read", "why": "Pick the photos for an album." }],
|
|
96
|
+
"grants": { "photos.read": "granted" },
|
|
97
|
+
"provided": ["photos", "share"],
|
|
98
|
+
"foreground": true,
|
|
99
|
+
"call": { "from": "ui", "name": "photos_pick", "arguments": {} },
|
|
100
|
+
"expected": { "verdict": "call", "capability": "photos", "tool": "pick", "scope": "photos.read", "reason": null }
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "an agent reaches the same scope through the same broker",
|
|
104
|
+
"manifest": [{ "id": "photos.read", "why": "Pick the photos for an album." }],
|
|
105
|
+
"grants": { "photos.read": "granted" },
|
|
106
|
+
"provided": ["photos"],
|
|
107
|
+
"foreground": false,
|
|
108
|
+
"call": { "from": "agent", "name": "photos__list", "arguments": { "limit": 10 } },
|
|
109
|
+
"expected": { "verdict": "call", "capability": "photos", "tool": "list", "scope": "photos.read", "reason": null }
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"name": "an undecided ask scope asks in the foreground",
|
|
113
|
+
"manifest": [{ "id": "photos.read", "why": "Pick the photos for an album." }],
|
|
114
|
+
"grants": {},
|
|
115
|
+
"provided": ["photos"],
|
|
116
|
+
"foreground": true,
|
|
117
|
+
"call": { "from": "agent", "name": "photos__pick", "arguments": {} },
|
|
118
|
+
"expected": { "verdict": "ask", "capability": "photos", "tool": "pick", "scope": "photos.read", "reason": null }
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"name": "an undecided ask scope never asks in the background",
|
|
122
|
+
"manifest": [{ "id": "photos.read", "why": "Pick the photos for an album." }],
|
|
123
|
+
"grants": {},
|
|
124
|
+
"provided": ["photos"],
|
|
125
|
+
"foreground": false,
|
|
126
|
+
"call": { "from": "agent", "name": "photos__pick", "arguments": {} },
|
|
127
|
+
"expected": { "verdict": "denied", "capability": "photos", "tool": "pick", "scope": "photos.read", "reason": "consentNeeded" }
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"name": "a denied scope stays denied without a sheet",
|
|
131
|
+
"manifest": [{ "id": "notifications.post", "why": "Remind you of due pages." }],
|
|
132
|
+
"grants": { "notifications.post": "denied" },
|
|
133
|
+
"provided": ["notifications"],
|
|
134
|
+
"foreground": true,
|
|
135
|
+
"call": { "from": "ui", "name": "notifications_post", "arguments": { "title": "Hi" } },
|
|
136
|
+
"expected": { "verdict": "denied", "capability": "notifications", "tool": "post", "scope": "notifications.post", "reason": "consentDenied" }
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"name": "the manifest is the ceiling: an undeclared scope is denied, even with a stale grant",
|
|
140
|
+
"manifest": [{ "id": "photos.read", "why": "Pick the photos for an album." }],
|
|
141
|
+
"grants": { "camera.capture": "granted" },
|
|
142
|
+
"provided": ["photos", "camera"],
|
|
143
|
+
"foreground": true,
|
|
144
|
+
"call": { "from": "ui", "name": "camera_capture", "arguments": {} },
|
|
145
|
+
"expected": { "verdict": "denied", "capability": "camera", "tool": "capture", "scope": "camera.capture", "reason": "notDeclared" }
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"name": "a declared capability the host lacks is unavailable",
|
|
149
|
+
"manifest": [{ "id": "camera.capture", "why": "Scan a receipt." }],
|
|
150
|
+
"grants": {},
|
|
151
|
+
"provided": ["photos"],
|
|
152
|
+
"foreground": true,
|
|
153
|
+
"call": { "from": "ui", "name": "camera_capture", "arguments": {} },
|
|
154
|
+
"expected": { "verdict": "unavailable", "capability": "camera", "tool": "capture", "scope": "camera.capture", "reason": "unavailable" }
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"name": "a picker scope calls in the foreground with no stored grant",
|
|
158
|
+
"manifest": [{ "id": "share.send", "why": "Share a finished album." }],
|
|
159
|
+
"grants": {},
|
|
160
|
+
"provided": ["share"],
|
|
161
|
+
"foreground": true,
|
|
162
|
+
"call": { "from": "ui", "name": "share_send", "arguments": { "fileId": "f1" } },
|
|
163
|
+
"expected": { "verdict": "call", "capability": "share", "tool": "send", "scope": "share.send", "reason": null }
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"name": "a picker scope needs the foreground",
|
|
167
|
+
"manifest": [{ "id": "print.pdf", "why": "Print an album." }],
|
|
168
|
+
"grants": {},
|
|
169
|
+
"provided": ["print"],
|
|
170
|
+
"foreground": false,
|
|
171
|
+
"call": { "from": "agent", "name": "print__pdf", "arguments": {} },
|
|
172
|
+
"expected": { "verdict": "denied", "capability": "print", "tool": "pdf", "scope": "print.pdf", "reason": "needsForeground" }
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"name": "a host-only scope is never an app's",
|
|
176
|
+
"manifest": [{ "id": "shell.edit", "why": "Rearrange the panel." }],
|
|
177
|
+
"grants": { "shell.edit": "granted" },
|
|
178
|
+
"provided": ["shell"],
|
|
179
|
+
"foreground": true,
|
|
180
|
+
"call": { "from": "agent", "name": "shell__edit", "arguments": {} },
|
|
181
|
+
"expected": { "verdict": "denied", "capability": "shell", "tool": "edit", "scope": "shell.edit", "reason": "hostOnly" }
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"name": "an unknown tool of a known capability is unknown",
|
|
185
|
+
"manifest": [{ "id": "photos.read", "why": "Pick the photos for an album." }],
|
|
186
|
+
"grants": { "photos.read": "granted" },
|
|
187
|
+
"provided": ["photos"],
|
|
188
|
+
"foreground": true,
|
|
189
|
+
"call": { "from": "agent", "name": "photos__delete", "arguments": {} },
|
|
190
|
+
"expected": { "verdict": "unknown", "capability": null, "tool": null, "scope": null, "reason": "unknownTool" }
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"name": "an unknown capability is unknown",
|
|
194
|
+
"manifest": [],
|
|
195
|
+
"grants": {},
|
|
196
|
+
"provided": [],
|
|
197
|
+
"foreground": true,
|
|
198
|
+
"call": { "from": "ui", "name": "bluetooth_scan", "arguments": {} },
|
|
199
|
+
"expected": { "verdict": "unknown", "capability": null, "tool": null, "scope": null, "reason": "unknownTool" }
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"name": "http calls a declared origin",
|
|
203
|
+
"manifest": [{ "id": "http.fetch", "why": "Load exchange rates.", "origins": ["https://api.example.com"] }],
|
|
204
|
+
"grants": { "http.fetch": "granted" },
|
|
205
|
+
"provided": ["http"],
|
|
206
|
+
"foreground": false,
|
|
207
|
+
"call": { "from": "ui", "name": "http_fetch", "arguments": { "url": "https://api.example.com/v1/rates?base=EUR" } },
|
|
208
|
+
"expected": { "verdict": "call", "capability": "http", "tool": "fetch", "scope": "http.fetch", "reason": null }
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"name": "http refuses an origin the manifest does not list",
|
|
212
|
+
"manifest": [{ "id": "http.fetch", "why": "Load exchange rates.", "origins": ["https://api.example.com"] }],
|
|
213
|
+
"grants": { "http.fetch": "granted" },
|
|
214
|
+
"provided": ["http"],
|
|
215
|
+
"foreground": true,
|
|
216
|
+
"call": { "from": "agent", "name": "http__fetch", "arguments": { "url": "https://evil.example.net/x" } },
|
|
217
|
+
"expected": { "verdict": "denied", "capability": "http", "tool": "fetch", "scope": "http.fetch", "reason": "originNotDeclared" }
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"name": "http compares the whole origin, port and scheme included",
|
|
221
|
+
"manifest": [{ "id": "http.fetch", "why": "Load exchange rates.", "origins": ["https://api.example.com"] }],
|
|
222
|
+
"grants": { "http.fetch": "granted" },
|
|
223
|
+
"provided": ["http"],
|
|
224
|
+
"foreground": true,
|
|
225
|
+
"call": { "from": "ui", "name": "http_fetch", "arguments": { "url": "http://api.example.com:8080/v1" } },
|
|
226
|
+
"expected": { "verdict": "denied", "capability": "http", "tool": "fetch", "scope": "http.fetch", "reason": "originNotDeclared" }
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"name": "http without a url is refused before consent",
|
|
230
|
+
"manifest": [{ "id": "http.fetch", "why": "Load exchange rates.", "origins": ["https://api.example.com"] }],
|
|
231
|
+
"grants": {},
|
|
232
|
+
"provided": ["http"],
|
|
233
|
+
"foreground": true,
|
|
234
|
+
"call": { "from": "ui", "name": "http_fetch", "arguments": {} },
|
|
235
|
+
"expected": { "verdict": "denied", "capability": "http", "tool": "fetch", "scope": "http.fetch", "reason": "originNotDeclared" }
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"name": "eDE: a granted windows scope lists for an agent in the background",
|
|
239
|
+
"manifest": [{ "id": "windows.read", "why": "Show the open windows." }],
|
|
240
|
+
"grants": { "windows.read": "granted" },
|
|
241
|
+
"provided": ["windows", "focus", "launch"],
|
|
242
|
+
"foreground": false,
|
|
243
|
+
"call": { "from": "agent", "name": "windows__list", "arguments": {} },
|
|
244
|
+
"expected": { "verdict": "call", "capability": "windows", "tool": "list", "scope": "windows.read", "reason": null }
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
"name": "eDE: launch is denied until the manifest declares it",
|
|
248
|
+
"manifest": [{ "id": "windows.read", "why": "Show the open windows." }],
|
|
249
|
+
"grants": {},
|
|
250
|
+
"provided": ["windows", "focus", "launch"],
|
|
251
|
+
"foreground": true,
|
|
252
|
+
"call": { "from": "ui", "name": "launch_app", "arguments": {"app": "org.gnome.Calculator"} },
|
|
253
|
+
"expected": { "verdict": "denied", "capability": "launch", "tool": "app", "scope": "launch.app", "reason": "notDeclared" }
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"name": "eDE: focus asks once in the foreground like any ask scope",
|
|
257
|
+
"manifest": [{ "id": "focus.window", "why": "Switch to a window from the list." }],
|
|
258
|
+
"grants": {},
|
|
259
|
+
"provided": ["windows", "focus", "launch"],
|
|
260
|
+
"foreground": true,
|
|
261
|
+
"call": { "from": "ui", "name": "focus_window", "arguments": {"window": "w3"} },
|
|
262
|
+
"expected": { "verdict": "ask", "capability": "focus", "tool": "window", "scope": "focus.window", "reason": null }
|
|
263
|
+
}
|
|
264
|
+
],
|
|
265
|
+
"listCases": [
|
|
266
|
+
{
|
|
267
|
+
"name": "declared and provided scopes, host-only and undeclared ones left out",
|
|
268
|
+
"manifest": [
|
|
269
|
+
{ "id": "photos.read", "why": "Pick the photos for an album." },
|
|
270
|
+
{ "id": "share.send", "why": "Share a finished album." },
|
|
271
|
+
{ "id": "camera.capture", "why": "Scan a receipt." },
|
|
272
|
+
{ "id": "shell.edit", "why": "Rearrange the panel." }
|
|
273
|
+
],
|
|
274
|
+
"provided": ["photos", "share", "shell", "notifications"],
|
|
275
|
+
"expected": ["photos__get", "photos__list", "photos__pick", "share__send"]
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"name": "nothing declared lists nothing",
|
|
279
|
+
"manifest": [],
|
|
280
|
+
"provided": ["photos", "share"],
|
|
281
|
+
"expected": []
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
"name": "eDE's scopes list; shell.edit stays host-only",
|
|
285
|
+
"manifest": [{ "id": "windows.read", "why": "Show the open windows." }, { "id": "focus.window", "why": "Switch to a window from the list." }, { "id": "launch.app", "why": "Start the calculator from the tile." }, { "id": "shell.edit", "why": "Rearrange the panel." }],
|
|
286
|
+
"provided": ["windows", "focus", "launch", "shell"],
|
|
287
|
+
"expected": ["focus__window", "launch__app", "windows__list"]
|
|
288
|
+
}
|
|
289
|
+
],
|
|
290
|
+
"auditCases": [
|
|
291
|
+
{
|
|
292
|
+
"name": "a UI call that answered",
|
|
293
|
+
"appId": "at.exponential.photobook",
|
|
294
|
+
"client": null,
|
|
295
|
+
"call": { "from": "ui", "name": "photos_pick", "arguments": {} },
|
|
296
|
+
"verdict": { "verdict": "call", "capability": "photos", "tool": "pick", "scope": "photos.read", "reason": null },
|
|
297
|
+
"outcome": "ok",
|
|
298
|
+
"expected": { "appId": "at.exponential.photobook", "clientId": "ui", "tool": "photos__pick", "status": "ok", "error": null, "argsHash": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" }
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
"name": "an agent call refused in the background",
|
|
302
|
+
"appId": "at.exponential.photobook",
|
|
303
|
+
"client": "run-7",
|
|
304
|
+
"call": { "from": "agent", "name": "photos__pick", "arguments": {} },
|
|
305
|
+
"verdict": { "verdict": "denied", "capability": "photos", "tool": "pick", "scope": "photos.read", "reason": "consentNeeded" },
|
|
306
|
+
"outcome": null,
|
|
307
|
+
"expected": { "appId": "at.exponential.photobook", "clientId": "run-7", "tool": "photos__pick", "status": "denied", "error": "consentNeeded", "argsHash": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" }
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
"name": "an unknown tool is audited under its name as called",
|
|
311
|
+
"appId": "at.exponential.photobook",
|
|
312
|
+
"client": "run-7",
|
|
313
|
+
"call": { "from": "agent", "name": "photos__delete", "arguments": {} },
|
|
314
|
+
"verdict": { "verdict": "unknown", "capability": null, "tool": null, "scope": null, "reason": "unknownTool" },
|
|
315
|
+
"outcome": null,
|
|
316
|
+
"expected": { "appId": "at.exponential.photobook", "clientId": "run-7", "tool": "photos__delete", "status": "denied", "error": "unknownTool", "argsHash": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" }
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
"name": "a UI call to an unknown function is audited in the qualified spelling",
|
|
320
|
+
"appId": "at.exponential.photobook",
|
|
321
|
+
"client": null,
|
|
322
|
+
"call": { "from": "ui", "name": "bluetooth_scan", "arguments": {} },
|
|
323
|
+
"verdict": { "verdict": "unknown", "capability": null, "tool": null, "scope": null, "reason": "unknownTool" },
|
|
324
|
+
"outcome": null,
|
|
325
|
+
"expected": { "appId": "at.exponential.photobook", "clientId": "ui", "tool": "bluetooth__scan", "status": "denied", "error": "unknownTool", "argsHash": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" }
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
"name": "a call whose tool errored is failed",
|
|
329
|
+
"appId": "at.exponential.photobook",
|
|
330
|
+
"client": null,
|
|
331
|
+
"call": { "from": "ui", "name": "print_pdf", "arguments": { "albumId": "a1" } },
|
|
332
|
+
"verdict": { "verdict": "call", "capability": "print", "tool": "pdf", "scope": "print.pdf", "reason": null },
|
|
333
|
+
"outcome": "isError",
|
|
334
|
+
"expected": { "appId": "at.exponential.photobook", "clientId": "ui", "tool": "print__pdf", "status": "failed", "error": null, "argsHash": "sha256:ce39d0a61f73d8feae9df9f8d80691a8a8f891c473b2f9f9744ccb3003228468" }
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
"name": "an unavailable capability",
|
|
338
|
+
"appId": "at.exponential.photobook",
|
|
339
|
+
"client": null,
|
|
340
|
+
"call": { "from": "ui", "name": "camera_capture", "arguments": {} },
|
|
341
|
+
"verdict": { "verdict": "unavailable", "capability": "camera", "tool": "capture", "scope": "camera.capture", "reason": "unavailable" },
|
|
342
|
+
"outcome": null,
|
|
343
|
+
"expected": { "appId": "at.exponential.photobook", "clientId": "ui", "tool": "camera__capture", "status": "denied", "error": "unavailable", "argsHash": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" }
|
|
344
|
+
}
|
|
345
|
+
],
|
|
346
|
+
"v1": {"context": {"app": "acme.album", "screens": ["albums", "page"]}, "blobs": {"f1": "aGVsbG8=", "pdf1": "JVBERi0xLjQK"}, "blobIds": "blob-<n>", "mimeByExtension": {"csv": "text/csv", "gif": "image/gif", "heic": "image/heic", "html": "text/html", "jpeg": "image/jpeg", "jpg": "image/jpeg", "json": "application/json", "md": "text/markdown", "pdf": "application/pdf", "png": "image/png", "svg": "image/svg+xml", "txt": "text/plain", "webp": "image/webp", "zip": "application/zip"}},
|
|
347
|
+
"tools": [
|
|
348
|
+
{"name": "files_open", "runs": "native", "description": "Let the person pick files to open. `accept` = MIME types (`image/*`) or extensions (`.csv`). Answers the files, each stored as an app file; an empty list when they cancel.", "inputSchema": {"type": "object", "properties": {"accept": {"type": "array", "maxItems": 20, "items": {"type": "string", "minLength": 1, "maxLength": 100}}, "multiple": {"type": "boolean", "default": false}}, "additionalProperties": false}, "nativeOutputSchema": {"type": "object", "properties": {"files": {"type": "array", "maxItems": 100, "items": {"type": "object", "properties": {"name": {"type": "string", "minLength": 1, "maxLength": 255}, "mimeType": {"type": "string", "minLength": 1, "maxLength": 100}, "data": {"type": "string", "format": "base64"}}, "required": ["name", "mimeType", "data"], "additionalProperties": false}}}, "required": ["files"], "additionalProperties": false}, "outputSchema": {"type": "object", "properties": {"files": {"type": "array", "maxItems": 100, "items": {"type": "object", "properties": {"name": {"type": "string", "minLength": 1, "maxLength": 255}, "mimeType": {"type": "string", "minLength": 1, "maxLength": 100}, "size": {"type": "integer", "minimum": 0}, "fileId": {"type": "string", "minLength": 1, "maxLength": 200}}, "required": ["name", "mimeType", "size", "fileId"], "additionalProperties": false}}}, "required": ["files"], "additionalProperties": false}},
|
|
349
|
+
{"name": "files_save", "runs": "native", "description": "Let the person save an app file (`fileId`) or a text (`text`) where they pick. `saved` = false when they cancel.", "inputSchema": {"type": "object", "properties": {"name": {"type": "string", "minLength": 1, "maxLength": 255}, "fileId": {"type": "string", "minLength": 1, "maxLength": 200}, "text": {"type": "string", "maxLength": 1000000}, "mimeType": {"type": "string", "minLength": 1, "maxLength": 100}}, "required": ["name"], "additionalProperties": false, "oneOf": [{"required": ["fileId"]},{"required": ["text"]}]}, "nativeOutputSchema": {"type": "object", "properties": {"saved": {"type": "boolean"}}, "required": ["saved"], "additionalProperties": false}, "outputSchema": {"type": "object", "properties": {"saved": {"type": "boolean"}}, "required": ["saved"], "additionalProperties": false}},
|
|
350
|
+
{"name": "focus_window", "runs": "native", "description": "Bring a window of this desktop to the front (its id from windows_list); a minimized one comes back first, a parked one brings its workspace along. `focused` = false when the window is gone.", "inputSchema": {"type": "object", "properties": {"window": {"type": "string", "format": "token"}}, "required": ["window"], "additionalProperties": false}, "nativeOutputSchema": {"type": "object", "properties": {"focused": {"type": "boolean"}}, "required": ["focused"], "additionalProperties": false}, "outputSchema": {"type": "object", "properties": {"focused": {"type": "boolean"}}, "required": ["focused"], "additionalProperties": false}},
|
|
351
|
+
{"name": "launch_app", "runs": "native", "description": "Start an installed application by its id (a desktop entry's, `org.gnome.Calculator`, with or without `.desktop`). `launched` = false when nothing is installed under that id.", "inputSchema": {"type": "object", "properties": {"app": {"type": "string", "minLength": 1, "maxLength": 200}}, "required": ["app"], "additionalProperties": false}, "nativeOutputSchema": {"type": "object", "properties": {"launched": {"type": "boolean"}}, "required": ["launched"], "additionalProperties": false}, "outputSchema": {"type": "object", "properties": {"launched": {"type": "boolean"}}, "required": ["launched"], "additionalProperties": false}},
|
|
352
|
+
{"name": "notifications_cancel", "runs": "native", "description": "Withdraw a notification this app posted or scheduled.", "inputSchema": {"type": "object", "properties": {"id": {"type": "string", "format": "token"}}, "required": ["id"], "additionalProperties": false}, "nativeOutputSchema": {"type": "object", "properties": {"cancelled": {"type": "boolean"}}, "required": ["cancelled"], "additionalProperties": false}, "outputSchema": {"type": "object", "properties": {"cancelled": {"type": "boolean"}}, "required": ["cancelled"], "additionalProperties": false}},
|
|
353
|
+
{"name": "notifications_post", "runs": "native", "description": "Post a notification now, or at `at` (an ISO time). A tap opens `screen` with `params`. Posting an `id` again replaces it.", "inputSchema": {"type": "object", "properties": {"id": {"type": "string", "format": "token"}, "title": {"type": "string", "minLength": 1, "maxLength": 200}, "body": {"type": "string", "minLength": 0, "maxLength": 1000}, "at": {"type": "string", "format": "date-time"}, "screen": {"type": "string", "minLength": 1, "maxLength": 100}, "params": {"type": "object"}}, "required": ["title"], "additionalProperties": false, "dependentRequired": {"params": ["screen"]}}, "nativeOutputSchema": {"type": "object", "properties": {}, "additionalProperties": false}, "outputSchema": {"type": "object", "properties": {"id": {"type": "string", "format": "token"}}, "required": ["id"], "additionalProperties": false}},
|
|
354
|
+
{"name": "photos_get", "runs": "native", "description": "Read one photo of the library by its id (from list or pick), stored as an app file.", "inputSchema": {"type": "object", "properties": {"id": {"type": "string", "minLength": 1, "maxLength": 500}}, "required": ["id"], "additionalProperties": false}, "nativeOutputSchema": {"type": "object", "properties": {"photo": {"type": "object", "properties": {"id": {"type": "string", "minLength": 1, "maxLength": 500}, "name": {"type": "string", "minLength": 1, "maxLength": 255}, "mimeType": {"type": "string", "minLength": 1, "maxLength": 100}, "takenAt": {"type": ["string", "null"], "format": "date-time"}, "data": {"type": "string", "format": "base64"}}, "required": ["id", "name", "mimeType", "takenAt", "data"], "additionalProperties": false}}, "required": ["photo"], "additionalProperties": false}, "outputSchema": {"type": "object", "properties": {"photo": {"type": "object", "properties": {"id": {"type": "string", "minLength": 1, "maxLength": 500}, "name": {"type": "string", "minLength": 1, "maxLength": 255}, "mimeType": {"type": "string", "minLength": 1, "maxLength": 100}, "takenAt": {"type": ["string", "null"], "format": "date-time"}, "size": {"type": "integer", "minimum": 0}, "fileId": {"type": "string", "minLength": 1, "maxLength": 200}}, "required": ["id", "name", "mimeType", "takenAt", "size", "fileId"], "additionalProperties": false}}, "required": ["photo"], "additionalProperties": false}},
|
|
355
|
+
{"name": "photos_list", "runs": "native", "description": "The library's photos, newest first, without their bytes. Pass the answered cursor for the next page; null = the end.", "inputSchema": {"type": "object", "properties": {"limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, "cursor": {"type": "string", "minLength": 1, "maxLength": 200}}, "additionalProperties": false}, "nativeOutputSchema": {"type": "object", "properties": {"photos": {"type": "array", "maxItems": 100, "items": {"type": "object", "properties": {"id": {"type": "string", "minLength": 1, "maxLength": 500}, "name": {"type": "string", "minLength": 1, "maxLength": 255}, "mimeType": {"type": "string", "minLength": 1, "maxLength": 100}, "takenAt": {"type": ["string", "null"], "format": "date-time"}}, "required": ["id", "name", "mimeType", "takenAt"], "additionalProperties": false}}, "cursor": {"type": ["string", "null"], "maxLength": 200}}, "required": ["photos", "cursor"], "additionalProperties": false}, "outputSchema": {"type": "object", "properties": {"photos": {"type": "array", "maxItems": 100, "items": {"type": "object", "properties": {"id": {"type": "string", "minLength": 1, "maxLength": 500}, "name": {"type": "string", "minLength": 1, "maxLength": 255}, "mimeType": {"type": "string", "minLength": 1, "maxLength": 100}, "takenAt": {"type": ["string", "null"], "format": "date-time"}}, "required": ["id", "name", "mimeType", "takenAt"], "additionalProperties": false}}, "cursor": {"type": ["string", "null"], "maxLength": 200}}, "required": ["photos", "cursor"], "additionalProperties": false}},
|
|
356
|
+
{"name": "photos_pick", "runs": "native", "description": "Let the person pick photos in the platform's photo picker. Answers the picked photos, each stored as an app file; an empty list when they cancel.", "inputSchema": {"type": "object", "properties": {"limit": {"type": "integer", "minimum": 1, "maximum": 50, "default": 1}}, "additionalProperties": false}, "nativeOutputSchema": {"type": "object", "properties": {"photos": {"type": "array", "maxItems": 50, "items": {"type": "object", "properties": {"id": {"type": "string", "minLength": 1, "maxLength": 500}, "name": {"type": "string", "minLength": 1, "maxLength": 255}, "mimeType": {"type": "string", "minLength": 1, "maxLength": 100}, "takenAt": {"type": ["string", "null"], "format": "date-time"}, "data": {"type": "string", "format": "base64"}}, "required": ["id", "name", "mimeType", "takenAt", "data"], "additionalProperties": false}}}, "required": ["photos"], "additionalProperties": false}, "outputSchema": {"type": "object", "properties": {"photos": {"type": "array", "maxItems": 50, "items": {"type": "object", "properties": {"id": {"type": "string", "minLength": 1, "maxLength": 500}, "name": {"type": "string", "minLength": 1, "maxLength": 255}, "mimeType": {"type": "string", "minLength": 1, "maxLength": 100}, "takenAt": {"type": ["string", "null"], "format": "date-time"}, "size": {"type": "integer", "minimum": 0}, "fileId": {"type": "string", "minLength": 1, "maxLength": 200}}, "required": ["id", "name", "mimeType", "takenAt", "size", "fileId"], "additionalProperties": false}}}, "required": ["photos"], "additionalProperties": false}},
|
|
357
|
+
{"name": "print_pdf", "runs": "engine", "description": "Lay out a document (headings, text, app images, page breaks) as an A4 PDF, stored as an app file. No dialog: save, share or print the file next.", "inputSchema": {"type": "object", "properties": {"title": {"type": "string", "minLength": 1, "maxLength": 200}, "name": {"type": "string", "minLength": 1, "maxLength": 255}, "blocks": {"type": "array", "minItems": 1, "maxItems": 2000, "items": {"type": "object", "properties": {"heading": {"type": "string", "minLength": 1, "maxLength": 500}, "text": {"type": "string", "minLength": 0, "maxLength": 20000}, "image": {"type": "string", "minLength": 1, "maxLength": 200}, "caption": {"type": "string", "minLength": 1, "maxLength": 500}, "pageBreak": {"type": "boolean", "enum": [true]}}, "additionalProperties": false, "oneOf": [{"required": ["heading"]},{"required": ["text"]},{"required": ["image"]},{"required": ["pageBreak"]}], "dependentRequired": {"caption": ["image"]}}}}, "required": ["title", "blocks"], "additionalProperties": false}, "nativeOutputSchema": null, "outputSchema": {"type": "object", "properties": {"file": {"type": "object", "properties": {"name": {"type": "string", "minLength": 1, "maxLength": 255}, "mimeType": {"type": "string", "minLength": 1, "maxLength": 100}, "size": {"type": "integer", "minimum": 0}, "fileId": {"type": "string", "minLength": 1, "maxLength": 200}}, "required": ["name", "mimeType", "size", "fileId"], "additionalProperties": false}, "pages": {"type": "integer", "minimum": 1}}, "required": ["file", "pages"], "additionalProperties": false}},
|
|
358
|
+
{"name": "print_print", "runs": "native", "description": "Open the platform's print dialog for an app file (a PDF, PNG or JPEG). `printed` = false when they cancel.", "inputSchema": {"type": "object", "properties": {"fileId": {"type": "string", "minLength": 1, "maxLength": 200}, "name": {"type": "string", "minLength": 1, "maxLength": 255}}, "required": ["fileId"], "additionalProperties": false}, "nativeOutputSchema": {"type": "object", "properties": {"printed": {"type": "boolean"}}, "required": ["printed"], "additionalProperties": false}, "outputSchema": {"type": "object", "properties": {"printed": {"type": "boolean"}}, "required": ["printed"], "additionalProperties": false}},
|
|
359
|
+
{"name": "share_send", "runs": "native", "description": "Open the platform's share sheet with a text, a link and/or an app file. `shared` = false when they dismiss it (true where the platform cannot tell).", "inputSchema": {"type": "object", "properties": {"title": {"type": "string", "minLength": 1, "maxLength": 200}, "text": {"type": "string", "minLength": 1, "maxLength": 10000}, "url": {"type": "string", "format": "uri"}, "fileId": {"type": "string", "minLength": 1, "maxLength": 200}, "name": {"type": "string", "minLength": 1, "maxLength": 255}, "mimeType": {"type": "string", "minLength": 1, "maxLength": 100}}, "additionalProperties": false, "anyOf": [{"required": ["text"]},{"required": ["url"]},{"required": ["fileId"]}], "dependentRequired": {"fileId": ["name"], "mimeType": ["fileId"]}}, "nativeOutputSchema": {"type": "object", "properties": {"shared": {"type": "boolean"}}, "required": ["shared"], "additionalProperties": false}, "outputSchema": {"type": "object", "properties": {"shared": {"type": "boolean"}}, "required": ["shared"], "additionalProperties": false}},
|
|
360
|
+
{"name": "windows_list", "runs": "native", "description": "The open windows of this desktop, protected ones left out: each window's id, title, app (its desktop entry id), workspace, whether it is focused and whether it is minimized.", "inputSchema": {"type": "object", "properties": {}, "additionalProperties": false}, "nativeOutputSchema": {"type": "object", "properties": {"windows": {"type": "array", "maxItems": 500, "items": {"type": "object", "properties": {"id": {"type": "string", "format": "token"}, "title": {"type": "string", "maxLength": 500}, "app": {"type": "string", "maxLength": 200}, "workspace": {"type": "integer", "minimum": 0}, "focused": {"type": "boolean"}, "minimized": {"type": "boolean"}}, "required": ["id", "title", "app", "workspace", "focused", "minimized"], "additionalProperties": false}}}, "required": ["windows"], "additionalProperties": false}, "outputSchema": {"type": "object", "properties": {"windows": {"type": "array", "maxItems": 500, "items": {"type": "object", "properties": {"id": {"type": "string", "format": "token"}, "title": {"type": "string", "maxLength": 500}, "app": {"type": "string", "maxLength": 200}, "workspace": {"type": "integer", "minimum": 0}, "focused": {"type": "boolean"}, "minimized": {"type": "boolean"}}, "required": ["id", "title", "app", "workspace", "focused", "minimized"], "additionalProperties": false}}}, "required": ["windows"], "additionalProperties": false}}
|
|
361
|
+
],
|
|
362
|
+
"validateCases": [
|
|
363
|
+
{"tool": "photos_pick", "arguments": {}, "expected": {"arguments": {"limit": 1}}},
|
|
364
|
+
{"tool": "photos_pick", "arguments": {"limit": 51}, "expected": {"error": "invalidParams: /limit maximum"}},
|
|
365
|
+
{"tool": "photos_pick", "arguments": {"limit": 2.5}, "expected": {"error": "invalidParams: /limit type"}},
|
|
366
|
+
{"tool": "photos_pick", "arguments": {"max": 3}, "expected": {"error": "invalidParams: /max additionalProperties"}},
|
|
367
|
+
{"tool": "photos_pick", "arguments": [], "expected": {"error": "invalidParams: / type"}},
|
|
368
|
+
{"tool": "photos_list", "arguments": {}, "expected": {"arguments": {"limit": 50}}},
|
|
369
|
+
{"tool": "photos_get", "arguments": {}, "expected": {"error": "invalidParams: /id required"}},
|
|
370
|
+
{"tool": "files_open", "arguments": {"accept": ["image/*", ".csv"]}, "expected": {"arguments": {"accept": ["image/*", ".csv"], "multiple": false}}},
|
|
371
|
+
{"tool": "files_open", "arguments": {"accept": "image/*"}, "expected": {"error": "invalidParams: /accept type"}},
|
|
372
|
+
{"tool": "files_save", "arguments": {"name": "a.txt"}, "expected": {"error": "invalidParams: / oneOf"}},
|
|
373
|
+
{"tool": "files_save", "arguments": {"name": "a.txt", "text": "x", "fileId": "f1"}, "expected": {"error": "invalidParams: / oneOf"}},
|
|
374
|
+
{"tool": "files_save", "arguments": {"fileId": "f1"}, "expected": {"error": "invalidParams: /name required"}},
|
|
375
|
+
{"tool": "share_send", "arguments": {}, "expected": {"error": "invalidParams: / anyOf"}},
|
|
376
|
+
{"tool": "share_send", "arguments": {"fileId": "f1"}, "expected": {"error": "invalidParams: /name dependentRequired"}},
|
|
377
|
+
{"tool": "share_send", "arguments": {"url": "ftp://example.com/x"}, "expected": {"error": "invalidParams: /url format"}},
|
|
378
|
+
{"tool": "share_send", "arguments": {"url": "https://example.com/a?b=1"}, "expected": {"arguments": {"url": "https://example.com/a?b=1"}}},
|
|
379
|
+
{"tool": "notifications_post", "arguments": {"title": "Hi", "id": "has space"}, "expected": {"error": "invalidParams: /id format"}},
|
|
380
|
+
{"tool": "notifications_post", "arguments": {"title": "Hi", "at": "tomorrow"}, "expected": {"error": "invalidParams: /at format"}},
|
|
381
|
+
{"tool": "notifications_post", "arguments": {"title": ""}, "expected": {"error": "invalidParams: /title minLength"}},
|
|
382
|
+
{"tool": "notifications_post", "arguments": {"title": "Hi", "params": {"id": "p1"}}, "expected": {"error": "invalidParams: /screen dependentRequired"}},
|
|
383
|
+
{"tool": "notifications_post", "arguments": {"title": "Zoë", "at": "2026-11-07T09:00:00+01:00"}, "expected": {"arguments": {"title": "Zoë", "at": "2026-11-07T09:00:00+01:00"}}},
|
|
384
|
+
{"tool": "print_pdf", "arguments": {"title": "A", "blocks": []}, "expected": {"error": "invalidParams: /blocks minItems"}},
|
|
385
|
+
{"tool": "print_pdf", "arguments": {"title": "A", "blocks": [{"heading": "x", "text": "y"}]}, "expected": {"error": "invalidParams: /blocks/0 oneOf"}},
|
|
386
|
+
{"tool": "print_pdf", "arguments": {"title": "A", "blocks": [{"text": "y", "caption": "c"}]}, "expected": {"error": "invalidParams: /blocks/0/image dependentRequired"}},
|
|
387
|
+
{"tool": "print_pdf", "arguments": {"title": "A", "blocks": [{"pageBreak": false}]}, "expected": {"error": "invalidParams: /blocks/0/pageBreak enum"}},
|
|
388
|
+
{"tool": "print_print", "arguments": {"fileId": 7}, "expected": {"error": "invalidParams: /fileId type"}},
|
|
389
|
+
{"tool": "focus_window", "arguments": {}, "expected": {"error": "invalidParams: /window required"}},
|
|
390
|
+
{"tool": "focus_window", "arguments": {"window": "w3"}, "expected": {"arguments": {"window": "w3"}}},
|
|
391
|
+
{"tool": "launch_app", "arguments": {"app": ""}, "expected": {"error": "invalidParams: /app minLength"}},
|
|
392
|
+
{"tool": "windows_list", "arguments": {"since": 1}, "expected": {"error": "invalidParams: /since additionalProperties"}}
|
|
393
|
+
],
|
|
394
|
+
"bridgeCases": [
|
|
395
|
+
{"name": "a picked photo lands in the app's files", "tool": "photos_pick", "arguments": {"limit": 2}, "native": {"limit": 2}, "nativeResult": {"structuredContent": {"photos": [{"id": "ph-1", "name": "IMG_0001.jpg", "mimeType": "image/jpeg", "takenAt": "2026-11-01T10:00:00Z", "data": "aGk="}]}}, "puts": [{"name": "IMG_0001.jpg", "mimeType": "image/jpeg", "base64": "aGk="}], "expected": {"structuredContent": {"photos": [{"id": "ph-1", "name": "IMG_0001.jpg", "mimeType": "image/jpeg", "takenAt": "2026-11-01T10:00:00Z", "size": 2, "fileId": "blob-1"}]}}},
|
|
396
|
+
{"name": "a cancelled picker answers an empty list", "tool": "photos_pick", "arguments": {}, "native": {"limit": 1}, "nativeResult": {"structuredContent": {"photos": []}}, "puts": [], "expected": {"structuredContent": {"photos": []}}},
|
|
397
|
+
{"name": "list answers references without bytes", "tool": "photos_list", "arguments": {"limit": 1}, "native": {"limit": 1}, "nativeResult": {"structuredContent": {"photos": [{"id": "ph-9", "name": "IMG_0009.heic", "mimeType": "image/heic", "takenAt": null}], "cursor": "c1"}}, "puts": [], "expected": {"structuredContent": {"photos": [{"id": "ph-9", "name": "IMG_0009.heic", "mimeType": "image/heic", "takenAt": null}], "cursor": "c1"}}},
|
|
398
|
+
{"name": "a native error passes through", "tool": "photos_get", "arguments": {"id": "gone"}, "native": {"id": "gone"}, "nativeResult": {"isError": "notFound"}, "puts": [], "expected": {"isError": "notFound", "outcome": "isError"}},
|
|
399
|
+
{"name": "opened files land in the app's files", "tool": "files_open", "arguments": {"multiple": true}, "native": {"multiple": true}, "nativeResult": {"structuredContent": {"files": [{"name": "a.csv", "mimeType": "text/csv", "data": "YSxi"},{"name": "b.txt", "mimeType": "text/plain", "data": ""}]}}, "puts": [{"name": "a.csv", "mimeType": "text/csv", "base64": "YSxi"},{"name": "b.txt", "mimeType": "text/plain", "base64": ""}], "expected": {"structuredContent": {"files": [{"name": "a.csv", "mimeType": "text/csv", "size": 3, "fileId": "blob-1"},{"name": "b.txt", "mimeType": "text/plain", "size": 0, "fileId": "blob-2"}]}}},
|
|
400
|
+
{"name": "saving an app file hands the native its bytes", "tool": "files_save", "arguments": {"name": "notes.txt", "fileId": "f1"}, "native": {"name": "notes.txt", "mimeType": "text/plain", "data": "aGVsbG8="}, "nativeResult": {"structuredContent": {"saved": true}}, "puts": [], "expected": {"structuredContent": {"saved": true}}},
|
|
401
|
+
{"name": "saving a text: the type follows the name", "tool": "files_save", "arguments": {"name": "export.CSV", "text": "a,b\n1,2"}, "native": {"name": "export.CSV", "mimeType": "text/csv", "data": "YSxiCjEsMg=="}, "nativeResult": {"structuredContent": {"saved": false}}, "puts": [], "expected": {"structuredContent": {"saved": false}}},
|
|
402
|
+
{"name": "a text with an unknown extension is text/plain", "tool": "files_save", "arguments": {"name": "README", "text": "hi"}, "native": {"name": "README", "mimeType": "text/plain", "data": "aGk="}, "nativeResult": {"structuredContent": {"saved": true}}, "puts": [], "expected": {"structuredContent": {"saved": true}}},
|
|
403
|
+
{"name": "a given type wins", "tool": "files_save", "arguments": {"name": "notes.txt", "fileId": "f1", "mimeType": "text/markdown"}, "native": {"name": "notes.txt", "mimeType": "text/markdown", "data": "aGVsbG8="}, "nativeResult": {"structuredContent": {"saved": true}}, "puts": [], "expected": {"structuredContent": {"saved": true}}},
|
|
404
|
+
{"name": "no extension: the bytes tell", "tool": "files_save", "arguments": {"name": "scan", "fileId": "pdf1"}, "native": {"name": "scan", "mimeType": "application/pdf", "data": "JVBERi0xLjQK"}, "nativeResult": {"structuredContent": {"saved": true}}, "puts": [], "expected": {"structuredContent": {"saved": true}}},
|
|
405
|
+
{"name": "an unknown app file is notFound before the native runs", "tool": "files_save", "arguments": {"name": "x.txt", "fileId": "f9"}, "native": null, "nativeResult": null, "puts": [], "expected": {"isError": "notFound: f9", "outcome": "isError"}},
|
|
406
|
+
{"name": "sharing a text and a link", "tool": "share_send", "arguments": {"text": "Our album", "url": "https://example.com/a"}, "native": {"text": "Our album", "url": "https://example.com/a"}, "nativeResult": {"structuredContent": {"shared": false}}, "puts": [], "expected": {"structuredContent": {"shared": false}}},
|
|
407
|
+
{"name": "sharing an app file", "tool": "share_send", "arguments": {"title": "Hi", "fileId": "f1", "name": "hello.txt"}, "native": {"title": "Hi", "file": {"name": "hello.txt", "mimeType": "text/plain", "data": "aGVsbG8="}}, "nativeResult": {"structuredContent": {"shared": true}}, "puts": [], "expected": {"structuredContent": {"shared": true}}},
|
|
408
|
+
{"name": "printing a PDF", "tool": "print_print", "arguments": {"fileId": "pdf1"}, "native": {"name": "document.pdf", "mimeType": "application/pdf", "data": "JVBERi0xLjQK"}, "nativeResult": {"structuredContent": {"printed": true}}, "puts": [], "expected": {"structuredContent": {"printed": true}}},
|
|
409
|
+
{"name": "printing a named PNG", "tool": "print_print", "arguments": {"fileId": "png1", "name": "Swatch.png"}, "blobs": {"png1": "iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAIAAAB7QOjdAAAAD0lEQVR4nGP4z8DAwPAfAAcAAf9+CLHQAAAAAElFTkSuQmCC"}, "native": {"name": "Swatch.png", "mimeType": "image/png", "data": "iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAIAAAB7QOjdAAAAD0lEQVR4nGP4z8DAwPAfAAcAAf9+CLHQAAAAAElFTkSuQmCC"}, "nativeResult": {"structuredContent": {"printed": false}}, "puts": [], "expected": {"structuredContent": {"printed": false}}},
|
|
410
|
+
{"name": "only PDFs and images print", "tool": "print_print", "arguments": {"fileId": "f1"}, "native": null, "nativeResult": null, "puts": [], "expected": {"isError": "unsupportedFile", "outcome": "isError"}},
|
|
411
|
+
{"name": "a scheduled notification carries its tap", "tool": "notifications_post", "arguments": {"id": "due-1", "title": "Page due", "body": "The lake page is due.", "at": "2026-11-07T09:00:00Z", "screen": "page", "params": {"id": "p2"}}, "native": {"id": "acme.album/due-1", "title": "Page due", "body": "The lake page is due.", "at": "2026-11-07T09:00:00Z", "tap": {"type": "notificationTap", "app": "acme.album", "screen": "page", "params": {"id": "p2"}}}, "nativeResult": {"structuredContent": {}}, "puts": [], "expected": {"structuredContent": {"id": "due-1"}}},
|
|
412
|
+
{"name": "a plain notification now, no tap", "tool": "notifications_post", "arguments": {"id": "n1", "title": "Hi"}, "native": {"id": "acme.album/n1", "title": "Hi", "body": null, "at": null, "tap": null}, "nativeResult": {"structuredContent": {}}, "puts": [], "expected": {"structuredContent": {"id": "n1"}}},
|
|
413
|
+
{"name": "a tap opens a declared screen only", "tool": "notifications_post", "arguments": {"title": "Hi", "screen": "nope"}, "native": null, "nativeResult": null, "puts": [], "expected": {"isError": "invalidParams: /screen enum", "outcome": "invalidParams"}},
|
|
414
|
+
{"name": "cancel names the app's own id", "tool": "notifications_cancel", "arguments": {"id": "due-1"}, "native": {"id": "acme.album/due-1"}, "nativeResult": {"structuredContent": {"cancelled": true}}, "puts": [], "expected": {"structuredContent": {"cancelled": true}}},
|
|
415
|
+
{"name": "invalid arguments never reach the native", "tool": "photos_pick", "arguments": {"limit": 0}, "native": null, "nativeResult": null, "puts": [], "expected": {"isError": "invalidParams: /limit minimum", "outcome": "invalidParams"}},
|
|
416
|
+
{"name": "a native result off the schema is refused, nothing stored", "tool": "photos_pick", "arguments": {}, "native": {"limit": 1}, "nativeResult": {"structuredContent": {"photos": [{"id": "x"}]}}, "puts": [], "expected": {"isError": "badResult: /photos/0/name required", "outcome": "isError"}},
|
|
417
|
+
{"name": "bytes that are not base64 are refused", "tool": "photos_get", "arguments": {"id": "ph-1"}, "native": {"id": "ph-1"}, "nativeResult": {"structuredContent": {"photo": {"id": "ph-1", "name": "IMG_0001.jpg", "mimeType": "image/jpeg", "takenAt": "2026-11-01T10:00:00Z", "data": "a b"}}}, "puts": [], "expected": {"isError": "badResult: /photo/data format", "outcome": "isError"}},
|
|
418
|
+
{"name": "a native result without structuredContent is refused", "tool": "share_send", "arguments": {"text": "x"}, "native": {"text": "x"}, "nativeResult": {"content": [{"type": "text", "text": "ok"}]}, "puts": [], "expected": {"isError": "badResult: / type", "outcome": "isError"}},
|
|
419
|
+
{"name": "eDE: the window list passes through as the native answers it", "tool": "windows_list", "arguments": {}, "native": {}, "nativeResult": {"structuredContent": {"windows": [{"id": "w1", "title": "Firefox", "app": "firefox", "workspace": 0, "focused": true, "minimized": false}, {"id": "w2", "title": "", "app": "org.gnome.Calculator", "workspace": 1, "focused": false, "minimized": true}]}}, "puts": [], "expected": {"structuredContent": {"windows": [{"id": "w1", "title": "Firefox", "app": "firefox", "workspace": 0, "focused": true, "minimized": false}, {"id": "w2", "title": "", "app": "org.gnome.Calculator", "workspace": 1, "focused": false, "minimized": true}]}}},
|
|
420
|
+
{"name": "eDE: focus names the window as given", "tool": "focus_window", "arguments": {"window": "w2"}, "native": {"window": "w2"}, "nativeResult": {"structuredContent": {"focused": true}}, "puts": [], "expected": {"structuredContent": {"focused": true}}},
|
|
421
|
+
{"name": "eDE: a launch of an unknown id is not an error, just not launched", "tool": "launch_app", "arguments": {"app": "nope"}, "native": {"app": "nope"}, "nativeResult": {"structuredContent": {"launched": false}}, "puts": [], "expected": {"structuredContent": {"launched": false}}}
|
|
422
|
+
],
|
|
423
|
+
"pdf": {"page": [595,842], "margin": 56, "fonts": {"F1": "Helvetica", "F2": "Helvetica-Bold"}, "widths": {"Helvetica": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,278,278,355,556,556,889,667,191,333,333,389,584,278,333,278,278,556,556,556,556,556,556,556,556,556,556,278,278,584,584,584,556,1015,667,667,722,722,667,611,778,722,278,500,667,556,833,722,778,667,778,722,667,611,722,667,944,667,667,611,278,278,278,469,556,333,556,556,500,556,556,278,556,556,222,222,500,222,833,556,556,556,556,333,500,278,556,500,722,500,500,500,334,260,334,584,0,556,0,222,556,333,1000,556,556,333,1000,667,333,1000,0,611,0,0,222,222,333,333,350,556,1000,333,1000,500,333,944,0,500,667,278,333,556,556,556,556,260,556,333,737,370,556,584,333,737,333,400,584,333,333,333,556,537,278,333,333,365,556,834,834,834,611,667,667,667,667,667,667,1000,722,667,667,667,667,278,278,278,278,722,722,778,778,778,778,778,584,778,722,722,722,722,667,667,611,556,556,556,556,556,556,889,500,556,556,556,556,278,278,278,278,556,556,556,556,556,556,556,584,611,556,556,556,556,500,556,500], "Helvetica-Bold": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,278,333,474,556,556,889,722,238,333,333,389,584,278,333,278,278,556,556,556,556,556,556,556,556,556,556,333,333,584,584,584,611,975,722,722,722,722,667,611,778,722,278,556,722,611,833,722,778,667,778,722,667,611,722,667,944,667,667,611,333,278,333,584,556,333,556,611,556,611,556,333,611,611,278,278,556,278,889,611,611,611,611,389,556,333,611,556,778,556,556,500,389,280,389,584,0,556,0,278,556,500,1000,556,556,333,1000,667,333,1000,0,611,0,0,278,278,500,500,350,556,1000,333,1000,556,333,944,0,500,667,278,333,556,556,556,556,280,556,333,737,370,556,584,333,737,333,400,584,333,333,333,611,556,278,333,333,365,556,834,834,834,611,722,722,722,722,722,722,1000,722,667,667,667,667,278,278,278,278,722,722,778,778,778,778,778,584,778,722,722,722,722,667,667,611,556,556,556,556,556,556,889,556,556,556,556,556,278,278,278,278,611,611,611,611,611,611,611,584,611,611,611,611,611,556,611,556]}, "winAnsi": {"338": 140, "339": 156, "352": 138, "353": 154, "376": 159, "381": 142, "382": 158, "402": 131, "710": 136, "732": 152, "8211": 150, "8212": 151, "8216": 145, "8217": 146, "8218": 130, "8220": 147, "8221": 148, "8222": 132, "8224": 134, "8225": 135, "8226": 149, "8230": 133, "8240": 137, "8249": 139, "8250": 155, "8364": 128, "8482": 153}},
|
|
424
|
+
"pdfCases": [
|
|
425
|
+
{"name": "a heading and two paragraphs", "arguments": {"title": "Album", "blocks": [{"heading": "Summer 2026"},{"text": "Three days at the lake.\n\nThe last evening, (quiet) \\ calm."}]}, "expected": {"name": "Album.pdf", "pages": 1, "size": 993, "sha256": "30cb9bbe2443101cc399cb3656612147d55941f70b9d51068a18e17e2fc05234"}},
|
|
426
|
+
{"name": "long text wraps and flows onto a second page", "arguments": {"title": "Long/Story: one", "blocks": [{"heading": "Chapter one"},{"text": "The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. The lake was still in the morning and we walked along the shore until the light came over the hills, then sat on the jetty with coffee and watched the boats go out one by one. "}]}, "expected": {"name": "Long-Story- one.pdf", "pages": 2, "size": 8067, "sha256": "bfeaa0f562eba046e427125d94023d9b326a9fd9b49aec4fac5fc18cc8435522"}},
|
|
427
|
+
{"name": "a word wider than the page breaks by characters", "arguments": {"title": "W", "name": "wide.pdf", "blocks": [{"text": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}]}, "expected": {"name": "wide.pdf", "pages": 1, "size": 1107, "sha256": "b384a9c571494ed51773f8ef797eae8eaef186e7712cfee6ceb02e8962fe7e35"}},
|
|
428
|
+
{"name": "images with a caption, reused, and a page break", "arguments": {"title": "Photos", "blocks": [{"image": "jpg1", "caption": "A red and a blue pixel"},{"pageBreak": true},{"image": "png1"},{"image": "jpg1"},{"pageBreak": true}]}, "blobs": {"jpg1": "/9j/4AAQSkZJRgABAQAASABIAAD/4QBMRXhpZgAATU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAAqADAAQAAAABAAAAAQAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAAQACAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMABgYGBgYGCgYGCg4KCgoOEg4ODg4SFxISEhISFxwXFxcXFxccHBwcHBwcHCIiIiIiIicnJycnLCwsLCwsLCwsLP/bAEMBBwcHCwoLEwoKEy4fGh8uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLv/dAAQAAf/aAAwDAQACEQMRAD8A8Du/+Pqb/fb+dV6sXf8Ax9Tf77fzqvX2B94f/9k=", "png1": "iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAIAAAB7QOjdAAAAD0lEQVR4nGP4z8DAwPAfAAcAAf9+CLHQAAAAAElFTkSuQmCC"}, "expected": {"name": "Photos.pdf", "pages": 2, "size": 2524, "sha256": "f448125e89f27b9086ddf3254b8cbeed58cbc63da906cbc514e97cfb397b3708"}},
|
|
429
|
+
{"name": "WinAnsi text: what it cannot encode becomes ?", "arguments": {"title": "Zoë – 😀", "blocks": [{"heading": "Café € — “quoted”"},{"text": "Zoë 😀 naïve…\tTab"}]}, "expected": {"name": "Zoë – 😀.pdf", "pages": 1, "size": 960, "sha256": "659a57c2d57028a1e6d805d427047fdeb978cd4ab100ab8732beb6a93304c467"}},
|
|
430
|
+
{"name": "a missing image is notFound", "arguments": {"title": "x", "blocks": [{"image": "nope"}]}, "expected": {"isError": "notFound: nope"}},
|
|
431
|
+
{"name": "a palette PNG is unsupported", "arguments": {"title": "x", "blocks": [{"image": "pal1"}]}, "blobs": {"pal1": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAMAAAAoyzS7AAAACklEQVR4nGNgAAAAAgABSK+kcQAAAABJRU5ErkJggg=="}, "expected": {"isError": "unsupportedImage: pal1"}},
|
|
432
|
+
{"name": "a file that is no image is unsupported", "arguments": {"title": "x", "blocks": [{"image": "f1"}]}, "expected": {"isError": "unsupportedImage: f1"}}
|
|
433
|
+
]
|
|
434
|
+
}
|