@zackbart/connecta 0.12.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +173 -0
- package/README.md +4 -1
- package/dist/apps-shell.d.ts +13 -11
- package/dist/apps-shell.d.ts.map +1 -1
- package/dist/apps-shell.js +221 -30
- package/dist/apps-shell.js.map +1 -1
- package/dist/catalog-service.d.ts +41 -0
- package/dist/catalog-service.d.ts.map +1 -1
- package/dist/catalog-service.js +94 -5
- package/dist/catalog-service.js.map +1 -1
- package/dist/connectors/api.d.ts +5 -4
- package/dist/connectors/api.d.ts.map +1 -1
- package/dist/connectors/api.js.map +1 -1
- package/dist/connectors/remote-mcp.d.ts +5 -4
- package/dist/connectors/remote-mcp.d.ts.map +1 -1
- package/dist/connectors/remote-mcp.js.map +1 -1
- package/dist/execute.d.ts +12 -4
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +142 -20
- package/dist/execute.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +14 -4
- package/dist/meta-tools.js.map +1 -1
- package/dist/providers/mixpanel.d.ts +21 -0
- package/dist/providers/mixpanel.d.ts.map +1 -0
- package/dist/providers/mixpanel.js +183 -0
- package/dist/providers/mixpanel.js.map +1 -0
- package/dist/skills.d.ts +7 -9
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +60 -25
- package/dist/skills.js.map +1 -1
- package/dist/types.d.ts +26 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/code-mode.md +32 -20
- package/documentation/connectors.md +116 -4
- package/documentation/mcp-ui-design.md +8 -8
- package/documentation/meta-tools.md +80 -8
- package/documentation/mixpanel.md +72 -0
- package/documentation/program-ui-read-calls.md +213 -0
- package/ethos.md +10 -4
- package/package.json +5 -1
- package/src/apps-shell.ts +221 -30
- package/src/catalog-service.ts +139 -4
- package/src/connectors/api.ts +5 -3
- package/src/connectors/remote-mcp.ts +5 -3
- package/src/execute.ts +215 -21
- package/src/index.ts +1 -0
- package/src/meta-tools.ts +19 -4
- package/src/providers/mixpanel.ts +220 -0
- package/src/skills.ts +66 -24
- package/src/types.ts +27 -6
- package/src/version.ts +1 -1
- package/templates/node/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,179 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.13.0 — 2026-08-03
|
|
6
|
+
|
|
7
|
+
Everything an agent reads before it calls anything got more selective. Connecta
|
|
8
|
+
ships its first maintained prebuilt connection — `mixpanel()` behind
|
|
9
|
+
`./providers/mixpanel`, one import returning one ordinary `Connector` with the
|
|
10
|
+
provider's endpoint, region, auth, and rate-limit defaults already right — and
|
|
11
|
+
the ethos now names a maintained prebuilt connection the preferred authoring
|
|
12
|
+
path, with `remoteMcp()` and `api()` staying equal first-class primitives for
|
|
13
|
+
everything nobody maintains. Connector guides became structured: a guide can
|
|
14
|
+
declare a bounded `summary` and mark itself `required`, and discovery carries
|
|
15
|
+
`guideSummary` with `guideRequired`/`guideRequiredReasons`, so an agent fetches
|
|
16
|
+
a guide when it changes the call and skips it when a complete read-only schema
|
|
17
|
+
already says everything. A connector-scoped search whose catalog is down now
|
|
18
|
+
returns a typed `catalogError` instead of advice to retry later. And the tool
|
|
19
|
+
descriptions stopped inviting the two failures the new cold-agent benchmark
|
|
20
|
+
kept catching: a top-level search that duplicates the one the program was about
|
|
21
|
+
to run, and a program guessing at a result shape it never read.
|
|
22
|
+
|
|
23
|
+
No API breaks. `usageGuide` still accepts a plain markdown string and means
|
|
24
|
+
exactly what it did; every new discovery field is additive and absent unless
|
|
25
|
+
earned. What changed under existing deployments is text, and it is worth
|
|
26
|
+
knowing about: the served tool descriptions and the MCP `instructions` string
|
|
27
|
+
are rewritten, and `skills({})` now summarizes a connector guide from its first
|
|
28
|
+
body line rather than its heading — a guide opening `# Acme` that used to list
|
|
29
|
+
as "Acme" now lists as the sentence beneath it. The built-in usage skill also
|
|
30
|
+
grew, because the per-connector guides section is now appended unconditionally
|
|
31
|
+
rather than only where a guide exists; that keeps the shared guide
|
|
32
|
+
byte-identical across every deployment, so an agent that read it once in a task
|
|
33
|
+
never needs a second local copy, at the cost of one paragraph in deployments
|
|
34
|
+
with no guides. Upgrading is a version bump and nothing else — there is no new
|
|
35
|
+
configuration, no new dependency, and no behavior a deployment must opt into
|
|
36
|
+
(#294, #295, #296, #297, PRs #298–#302).
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **A maintained Mixpanel connection at `./providers/mixpanel`.**
|
|
41
|
+
`mixpanel(id, { purpose, region?, auth?, instructions?, title?,
|
|
42
|
+
maxResultBytes? })` proxies Mixpanel's hosted MCP server with the endpoint
|
|
43
|
+
chosen by data residency (`us`, `eu`, `in`, default `us`), OAuth by default
|
|
44
|
+
and static headers for service accounts, HTTPS required, and a rolling
|
|
45
|
+
600-calls-per-hour admission budget matching the provider's published limit.
|
|
46
|
+
It carries a maintained usage guide — start at `Get-Projects`, then
|
|
47
|
+
`Get-Business-Context`; discover names instead of guessing spellings; fetch
|
|
48
|
+
`Get-Query-Schema` before `Run-Query` — to which a deployment may append its
|
|
49
|
+
own account instructions. The subpath is an optional import, not a
|
|
50
|
+
dependency: nothing new installs with core.
|
|
51
|
+
- **Fill-in-only vetted safety annotations.** The connection classifies 63
|
|
52
|
+
Mixpanel tools — 35 read-only, 28 writes split into additive creates and
|
|
53
|
+
destructive edits — but only where the downstream is silent. An explicit
|
|
54
|
+
`destructiveHint: true` or `readOnlyHint: false` from Mixpanel on an
|
|
55
|
+
allowlisted read name wins, because that is the downstream saying this
|
|
56
|
+
release's allowlist is wrong. Tools this release has never seen fail closed
|
|
57
|
+
to approval-visible rather than being assumed safe.
|
|
58
|
+
- **`ConnectorUsageGuide`.** A connector may now declare
|
|
59
|
+
`usageGuide: { content, summary?, required? }` instead of a bare string.
|
|
60
|
+
`summary` is the bounded line discovery shows; `required: true` says correct
|
|
61
|
+
use always depends on conventions no tool schema can carry. Mutations and
|
|
62
|
+
truncated schemas already require review and do not need the flag.
|
|
63
|
+
- **`guideSummary`, `guideRequired`, and `guideRequiredReasons` on discovery.**
|
|
64
|
+
`search_tools`, `connecta.search`, and `connecta.describe` now say what a
|
|
65
|
+
guide covers and whether it must be read first, with the reason named:
|
|
66
|
+
`connector_required` and `approval_required` stand however far a schema is
|
|
67
|
+
expanded, while `schema_truncated` clears once describe returns the exact
|
|
68
|
+
shape. A connector-scoped search that matches nothing still surfaces the
|
|
69
|
+
connector's guide, so an agent that searched the wrong terms learns the
|
|
70
|
+
vocabulary instead of concluding the connector is empty.
|
|
71
|
+
- **A scoped `catalogError`.** A search explicitly scoped to one connector
|
|
72
|
+
whose catalog is unavailable now returns the classified failure — `code`,
|
|
73
|
+
bounded `message`, `retryable`, and `retryAfterMs` when known — so an agent
|
|
74
|
+
can tell a transient outage from one an operator has to clear. Exactly four
|
|
75
|
+
fields, pinned by a test: an unscoped search still gets only
|
|
76
|
+
`unavailableConnectorCount`, because one connector's failure is not another
|
|
77
|
+
search's context.
|
|
78
|
+
|
|
79
|
+
### Changed
|
|
80
|
+
|
|
81
|
+
- **Routing guidance no longer invites redundant discovery.** Top-level
|
|
82
|
+
`search_tools` is now reserved for a single unreduced read or for
|
|
83
|
+
write-capable work; anything involving reduction, dependent steps, loops, or
|
|
84
|
+
joins is one `execute_code` program that searches and calls inside the run.
|
|
85
|
+
Search guidance also tells an agent to scope to an obvious connector id and
|
|
86
|
+
to require purpose, input, truncation, safety, and output fit rather than
|
|
87
|
+
taking the first lexical match. Measured against the shipped text over five
|
|
88
|
+
repetitions, route compliance went from 9/30 to 25/30.
|
|
89
|
+
- **`call_destructive_tool` carries the guide note.** Destructive multi-step
|
|
90
|
+
work keeps its route through top-level discovery — `execute_code` admits only
|
|
91
|
+
read-only tools — and its description now says to inspect the address and
|
|
92
|
+
fetch any guide it names before a consequential call.
|
|
93
|
+
- **The ethos prefers prebuilt connections, and refuses a registry.** The
|
|
94
|
+
decisions table accepts prebuilt connections as the preferred authoring path
|
|
95
|
+
*when connecta maintains one*, fenced: exactly one ordinary `Connector` with
|
|
96
|
+
no extra privileges, never a bundle or preset, tools hand-written or proxied
|
|
97
|
+
rather than generated from a schema document, and vetted annotations that
|
|
98
|
+
only fill in downstream silence. A provider registry or integration
|
|
99
|
+
marketplace is refused outright — prebuilt connections are imports, not
|
|
100
|
+
listings, and discovery happens in documentation, never at runtime.
|
|
101
|
+
- **The usage skill is byte-identical everywhere.** The per-connector guides
|
|
102
|
+
section is appended unconditionally and rewritten to route on
|
|
103
|
+
`guideRequired`/`guideSummary` rather than telling agents to read every
|
|
104
|
+
guide before first use. Guide-free deployments still pay no fixed
|
|
105
|
+
tool-description cost — the conditional notes in the meta-tool descriptions
|
|
106
|
+
remain absent.
|
|
107
|
+
- **Guide summaries prefer substance over headings.** `skills({})` reads a
|
|
108
|
+
guide's first meaningful body line, falling back to the heading and then to
|
|
109
|
+
the connector description, so a listing describes what a guide says rather
|
|
110
|
+
than what it is titled.
|
|
111
|
+
|
|
112
|
+
### Fixed
|
|
113
|
+
|
|
114
|
+
- **The Mixpanel connection could not have booted.** Its call-admission rule
|
|
115
|
+
paired a budget with `retryAfterMs` — a queue setting without a queue, which
|
|
116
|
+
the admission controller refuses at construction. No suite caught it because
|
|
117
|
+
every suite stubbed remote-MCP before the registry saw the connector; a new
|
|
118
|
+
suite now boots two accounts through the real `createConnecta` with `fetch`
|
|
119
|
+
rigged to throw, proving the boot and the per-account address, catalog,
|
|
120
|
+
storage, credential, and budget namespaces.
|
|
121
|
+
- **Tool descriptions claimed a waiver describe already performs.** Three
|
|
122
|
+
descriptions said schema expansion never clears a guide requirement while
|
|
123
|
+
`describe` cleared `schema_truncated` on every call. The text moved to the
|
|
124
|
+
behavior rather than the reverse.
|
|
125
|
+
- **Package-surface guards derive from `src/providers/`** instead of naming
|
|
126
|
+
`mixpanel.ts`, so the next provider fails them only for a real reason.
|
|
127
|
+
|
|
128
|
+
### Internal
|
|
129
|
+
|
|
130
|
+
- **A cold-agent benchmark lane in `eval/`.** Fourteen cases across two lanes —
|
|
131
|
+
eight measuring whether an agent meeting a connector cold discovers, routes,
|
|
132
|
+
and recovers, six measuring route compliance for the description rewrite —
|
|
133
|
+
with a comparator that refuses to compare runs whose harness, scoring, or
|
|
134
|
+
sandbox fingerprints differ, and reports a `productSha256` over `src/**`. A
|
|
135
|
+
baseline and a candidate cut from one working tree record the same commit and
|
|
136
|
+
the same dirty flag, and only the fingerprint says whether the candidate
|
|
137
|
+
measured changed code. It also reports host-routing probes separately from
|
|
138
|
+
foreign calls, so a contaminated run announces itself instead of reading as a
|
|
139
|
+
product regression. `eval/` is not in the published `files` list; deployments
|
|
140
|
+
never see it.
|
|
141
|
+
|
|
142
|
+
## 0.12.2 — 2026-08-02
|
|
143
|
+
|
|
144
|
+
Rendered programs can now bind a small, explicit set of read-only connector
|
|
145
|
+
calls into their view. `connecta.ui(html, { reads })` validates every binding
|
|
146
|
+
against the request-local catalog before accepting the UI, and the trusted
|
|
147
|
+
Apps shell exposes only those names and declared view arguments through
|
|
148
|
+
`connecta.read`. The program markup never receives connector addresses or a
|
|
149
|
+
general tool channel, and mutation-capable tools remain unavailable. Existing
|
|
150
|
+
`connecta.ui(html)` calls are byte-for-byte unchanged and remain display-only;
|
|
151
|
+
deployments whose views do not need refresh, pagination, or drill-down reads
|
|
152
|
+
can ignore this release. No deployment configuration changes are required
|
|
153
|
+
(#287, #289, PR #290).
|
|
154
|
+
|
|
155
|
+
### Added
|
|
156
|
+
|
|
157
|
+
- **Bounded read bindings for rendered views.** Programs may bind 1–32 names
|
|
158
|
+
to catalog addresses proven read-only, with optional fixed arguments and an
|
|
159
|
+
allowlist of view-supplied arguments. The manifest shares the existing
|
|
160
|
+
emitted-byte budget and binding does not dispatch a call or spend the
|
|
161
|
+
program's host-call budget.
|
|
162
|
+
- **A narrow `connecta.read(name, args)` view API.** The trusted outer shell
|
|
163
|
+
translates bound names into ordinary `call_tool` requests, reusing its
|
|
164
|
+
current authorization, catalog, and safety checks. It rejects unknown names,
|
|
165
|
+
undeclared arguments, fixed-argument overrides, and more than eight
|
|
166
|
+
concurrent reads.
|
|
167
|
+
|
|
168
|
+
### Changed
|
|
169
|
+
|
|
170
|
+
- **The program UI resource advances to `/v2`.** The new shell requires the
|
|
171
|
+
host's MCP Apps server-tools capability and makes only `call_tool` visible to
|
|
172
|
+
the app; the other six meta-tools remain model-only. Display-only views do
|
|
173
|
+
not receive the read bridge.
|
|
174
|
+
- **The UI security contract is explicit.** The new design record defines the
|
|
175
|
+
data flow, threat model, validation rules, executor parity, and the boundary
|
|
176
|
+
between accepted reads and still-gated mutations.
|
|
177
|
+
|
|
5
178
|
## 0.12.1 — 2026-08-01
|
|
6
179
|
|
|
7
180
|
Instructions, not behavior. Calling-side feedback and a first-hand run of the
|
package/README.md
CHANGED
|
@@ -72,7 +72,10 @@ For an agent setting this up, the contract is:
|
|
|
72
72
|
2. Keep the required `executor: quickJsExecutor()` configuration; without an
|
|
73
73
|
executor the deployment refuses to boot.
|
|
74
74
|
3. Keep secrets in environment variables or a secret store, never source.
|
|
75
|
-
4. Add
|
|
75
|
+
4. Add connectors explicitly: import a maintained prebuilt provider
|
|
76
|
+
constructor when one exists, otherwise write a deliberate `remoteMcp()` or
|
|
77
|
+
`api()` connector. There is no registry to browse and nothing registers
|
|
78
|
+
itself.
|
|
76
79
|
5. Run `npm run typecheck`, start the server, and run
|
|
77
80
|
`CONNECTA_TOKEN=... npm run doctor`. Doctor checks health, the executor, and
|
|
78
81
|
the exact seven-tool model-facing surface, then executes a harmless sandbox
|
package/dist/apps-shell.d.ts
CHANGED
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A build-time string constant, not a file read at startup: the core is
|
|
5
5
|
* Web-API-only so it runs unchanged on Workers, and the same bytes have to
|
|
6
|
-
* serve everywhere.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* serve everywhere. It renders whatever HTML a program handed `connecta.ui`
|
|
7
|
+
* inside a nested `srcdoc` frame. The one-argument form forwards no channel;
|
|
8
|
+
* an explicitly bound view gets only named read calls through the trusted
|
|
9
|
+
* shell, never a raw host channel.
|
|
10
10
|
*
|
|
11
11
|
* The address carries a version segment because hosts are permitted to
|
|
12
|
-
* prefetch and cache templates by URI: change these bytes, bump
|
|
12
|
+
* prefetch and cache templates by URI: change these bytes, bump the version.
|
|
13
13
|
*/
|
|
14
14
|
/** The only `ui://` URI in the system. No program input reaches it. */
|
|
15
|
-
export declare const PROGRAM_UI_RESOURCE_URI = "ui://connecta/program-ui/
|
|
15
|
+
export declare const PROGRAM_UI_RESOURCE_URI = "ui://connecta/program-ui/v2";
|
|
16
16
|
/** The mimeType the Apps spec requires of an HTML template. */
|
|
17
17
|
export declare const PROGRAM_UI_MIME_TYPE = "text/html;profile=mcp-app";
|
|
18
18
|
/**
|
|
@@ -29,10 +29,12 @@ export declare const MCP_APPS_EXTENSION = "io.modelcontextprotocol/ui";
|
|
|
29
29
|
* Apps postMessage dialect (`ui/initialize`, `ui/notifications/initialized`,
|
|
30
30
|
* `ui/notifications/tool-result`, `ui/notifications/size-changed`,
|
|
31
31
|
* `ui/resource-teardown`), lifts `_meta["connecta/ui"].html` out of the
|
|
32
|
-
* delivered tool result, and puts it in a frame.
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
32
|
+
* delivered tool result, and puts it in a frame. An optional read manifest
|
|
33
|
+
* installs one narrow `connecta.read(name, args)` bridge in that inner frame;
|
|
34
|
+
* the outer shell maps declared names to the existing `call_tool` meta-tool.
|
|
35
|
+
* It declares no CSP domains, so the host applies its restrictive default and
|
|
36
|
+
* the `srcdoc` frame inherits `default-src 'none'` — program markup still gets
|
|
37
|
+
* no direct network.
|
|
36
38
|
*/
|
|
37
|
-
export declare const PROGRAM_UI_SHELL_HTML = "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>connecta program view</title>\n <style>\n html,\n body {\n margin: 0;\n padding: 0;\n background: transparent;\n }\n #program-view {\n display: block;\n width: 100%;\n min-height: 480px;\n border: 0;\n }\n </style>\n </head>\n <body>\n <iframe\n id=\"program-view\"\n title=\"Program-rendered view\"\n sandbox=\"allow-scripts\"\n srcdoc=\"\"\n ></iframe>\n <script>\n (function () {\n \"use strict\";\n // The host frame is the only peer this shell speaks to, in either\n // direction. The payload frame below is sandboxed to scripts alone,\n // with no same-origin escape, and is never handed a reply path:\n // anything it posts fails the source check and is dropped. There is\n // no bridge from program HTML to the host, by construction rather\n // than by validation.\n var host = window.parent;\n var view = document.getElementById(\"program-view\");\n var initializeId = \"connecta-ui-initialize\";\n var lastWidth = 0;\n var lastHeight = 0;\n\n function send(message) {\n if (!host || host === window) return;\n host.postMessage(message, \"*\");\n }\n\n function notify(method, params) {\n send({ jsonrpc: \"2.0\", method: method, params: params });\n }\n\n // Program views are fixed-height by construction. The shell has no\n // bridge to the payload frame \u2014 that is the security posture, not an\n // omission \u2014 so it can never learn the payload's content height, and\n // what it reports here is its own box: the min-height above, unless\n // the host has given it more. Taller content scrolls inside the inner\n // frame rather than growing the view. Raising the min-height is the\n // only lever; a content-height signal would cost the isolation.\n function reportSize() {\n var width = Math.ceil(document.documentElement.clientWidth);\n var height = Math.ceil(document.documentElement.scrollHeight);\n if (width === lastWidth && height === lastHeight) return;\n lastWidth = width;\n lastHeight = height;\n notify(\"ui/notifications/size-changed\", {\n width: width,\n height: height\n });\n }\n\n function payloadHtml(result) {\n if (!result || typeof result !== \"object\") return null;\n var meta = result._meta;\n if (!meta || typeof meta !== \"object\") return null;\n var payload = meta[\"connecta/ui\"];\n if (!payload || typeof payload !== \"object\") return null;\n var html = payload.html;\n return typeof html === \"string\" && html.length > 0 ? html : null;\n }\n\n function render(params) {\n var html =\n payloadHtml(params) ||\n payloadHtml(params && params.result) ||\n payloadHtml(params && params.toolResult);\n if (html === null) return;\n view.srcdoc = html;\n reportSize();\n }\n\n window.addEventListener(\"message\", function (event) {\n if (event.source !== host) return;\n var message = event.data;\n if (!message || message.jsonrpc !== \"2.0\") return;\n if (message.method === \"ui/notifications/tool-result\") {\n render(message.params);\n return;\n }\n if (message.method === \"ui/resource-teardown\") {\n // A host->view request, not a notification: the host waits for\n // this reply before it tears the view down. There is nothing to\n // release, so answer immediately rather than make it time out.\n if (message.id !== undefined && message.id !== null) {\n send({ jsonrpc: \"2.0\", id: message.id, result: {} });\n }\n return;\n }\n // Only a completed handshake earns \"initialized\". A JSON-RPC error\n // response carries the same id, and announcing initialization on one\n // would assert a handshake that never happened.\n if (message.id === initializeId && message.result !== undefined) {\n notify(\"ui/notifications/initialized\", {});\n }\n });\n\n window.addEventListener(\"resize\", reportSize);\n view.addEventListener(\"load\", reportSize);\n\n // Every field here is required by the Apps initialize schema, and a\n // conforming host rejects the request outright when one is missing \u2014\n // which would strand the shell before any tool result arrives.\n send({\n jsonrpc: \"2.0\",\n id: initializeId,\n method: \"ui/initialize\",\n params: {\n appInfo: { name: \"connecta program view\", version: \"1\" },\n appCapabilities: {},\n protocolVersion: \"2026-01-26\"\n }\n });\n reportSize();\n })();\n </script>\n </body>\n</html>\n";
|
|
39
|
+
export declare const PROGRAM_UI_SHELL_HTML = "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>connecta program view</title>\n <style>\n html,\n body {\n margin: 0;\n padding: 0;\n background: transparent;\n }\n #program-view {\n display: block;\n width: 100%;\n min-height: 480px;\n border: 0;\n }\n </style>\n </head>\n <body>\n <iframe\n id=\"program-view\"\n title=\"Program-rendered view\"\n sandbox=\"allow-scripts\"\n srcdoc=\"\"\n ></iframe>\n <script>\n (function () {\n \"use strict\";\n // The outer shell is the only host peer. The payload frame is\n // sandboxed to scripts alone, with no same-origin escape. Its one\n // optional message dialect is handled below and translated into\n // bounded call_tool requests; raw JSON-RPC is never forwarded.\n var host = window.parent;\n var view = document.getElementById(\"program-view\");\n var initializeId = \"connecta-ui-initialize\";\n var lastWidth = 0;\n var lastHeight = 0;\n var reads = null;\n var hostCanCallTools = false;\n var nextHostRequestId = 0;\n var pendingHostReads = Object.create(null);\n var activeHostReads = 0;\n var maxActiveHostReads = 8;\n\n function send(message) {\n if (!host || host === window) return;\n host.postMessage(message, \"*\");\n }\n\n function notify(method, params) {\n send({ jsonrpc: \"2.0\", method: method, params: params });\n }\n\n // Program views are fixed-height by construction. The shell has no\n // content-height bridge to the payload frame, so it can never learn\n // the payload's content height, and\n // what it reports here is its own box: the min-height above, unless\n // the host has given it more. Taller content scrolls inside the inner\n // frame rather than growing the view. Raising the min-height is the\n // only lever; a content-height signal would cost the isolation.\n function reportSize() {\n var width = Math.ceil(document.documentElement.clientWidth);\n var height = Math.ceil(document.documentElement.scrollHeight);\n if (width === lastWidth && height === lastHeight) return;\n lastWidth = width;\n lastHeight = height;\n notify(\"ui/notifications/size-changed\", {\n width: width,\n height: height\n });\n }\n\n function payload(result) {\n if (!result || typeof result !== \"object\") return null;\n var meta = result._meta;\n if (!meta || typeof meta !== \"object\") return null;\n var value = meta[\"connecta/ui\"];\n if (!value || typeof value !== \"object\") return null;\n return typeof value.html === \"string\" && value.html.length > 0\n ? value\n : null;\n }\n\n // This function is serialized into the opaque-origin payload frame.\n // It knows no addresses and has no host channel of its own: one named\n // read request goes to the trusted outer shell and one correlated\n // result comes back.\n function payloadReadBridge() {\n \"use strict\";\n var pending = Object.create(null);\n var nextId = 0;\n\n function read(name, args) {\n return new Promise(function (resolve, reject) {\n var id = String(++nextId);\n pending[id] = { resolve: resolve, reject: reject };\n try {\n window.parent.postMessage({\n type: \"connecta/read\",\n id: id,\n name: name,\n args: args === undefined ? {} : args\n }, \"*\");\n } catch (error) {\n delete pending[id];\n reject(error);\n }\n });\n }\n\n Object.defineProperty(globalThis, \"connecta\", {\n value: Object.freeze({ read: read }),\n configurable: false,\n enumerable: true,\n writable: false\n });\n\n window.addEventListener(\"message\", function (event) {\n if (event.source !== window.parent) return;\n var message = event.data;\n if (!message || message.type !== \"connecta/read-result\") return;\n var waiter = pending[message.id];\n if (!waiter) return;\n delete pending[message.id];\n if (message.ok) waiter.resolve(message.value);\n else waiter.reject(new Error(message.error || \"Read failed\"));\n });\n }\n\n function htmlWithReadBridge(html) {\n var script =\n \"<scr\" + \"ipt>(\" + payloadReadBridge.toString() + \")();</scr\" + \"ipt>\";\n var head = /<head(?:\\s[^>]*)?>/i.exec(html);\n if (head) {\n var at = (head.index || 0) + head[0].length;\n return html.slice(0, at) + script + html.slice(at);\n }\n var document = /<html(?:\\s[^>]*)?>/i.exec(html);\n if (document) {\n var afterHtml = (document.index || 0) + document[0].length;\n return html.slice(0, afterHtml) + \"<head>\" + script + \"</head>\" + html.slice(afterHtml);\n }\n return script + html;\n }\n\n function render(params) {\n var value =\n payload(params) ||\n payload(params && params.result) ||\n payload(params && params.toolResult);\n if (value === null) return;\n reads = value.reads && typeof value.reads === \"object\"\n ? value.reads\n : null;\n view.srcdoc = reads\n ? htmlWithReadBridge(value.html)\n : value.html;\n reportSize();\n }\n\n function readError(message, fallback) {\n if (message && typeof message.message === \"string\") return message.message;\n if (message && message.data && typeof message.data.message === \"string\") {\n return message.data.message;\n }\n return fallback;\n }\n\n function finishInnerRead(innerId, ok, value) {\n if (!view.contentWindow) return;\n view.contentWindow.postMessage(ok\n ? { type: \"connecta/read-result\", id: innerId, ok: true, value: value }\n : { type: \"connecta/read-result\", id: innerId, ok: false, error: value }, \"*\");\n }\n\n function beginInnerRead(message) {\n if (!hostCanCallTools) {\n finishInnerRead(message && message.id, false, \"This host does not support app-initiated server tool calls\");\n return;\n }\n if (!message || typeof message.id !== \"string\" || typeof message.name !== \"string\") return;\n if (!reads || !Object.prototype.hasOwnProperty.call(reads, message.name)) {\n finishInnerRead(message.id, false, \"Unknown read binding\");\n return;\n }\n if (activeHostReads >= maxActiveHostReads) {\n finishInnerRead(message.id, false, \"Too many concurrent reads\");\n return;\n }\n var binding = reads[message.name];\n if (!binding || typeof binding !== \"object\" || typeof binding.address !== \"string\") {\n finishInnerRead(message.id, false, \"Invalid read binding\");\n return;\n }\n var supplied = message.args;\n if (!supplied || typeof supplied !== \"object\" || Array.isArray(supplied)) {\n finishInnerRead(message.id, false, \"Read arguments must be an object\");\n return;\n }\n var allowed = Array.isArray(binding.viewArgs) ? binding.viewArgs : [];\n var suppliedKeys = Object.keys(supplied);\n for (var i = 0; i < suppliedKeys.length; i++) {\n var key = suppliedKeys[i];\n if (allowed.indexOf(key) === -1) {\n finishInnerRead(message.id, false, \"Undeclared read argument \" + JSON.stringify(key));\n return;\n }\n }\n var args = Object.create(null);\n var fixed = binding.fixedArgs && typeof binding.fixedArgs === \"object\"\n ? binding.fixedArgs\n : {};\n Object.keys(fixed).forEach(function (key) { args[key] = fixed[key]; });\n suppliedKeys.forEach(function (key) { args[key] = supplied[key]; });\n\n var hostId = \"connecta-ui-read-\" + String(++nextHostRequestId);\n pendingHostReads[hostId] = { innerId: message.id };\n activeHostReads++;\n send({\n jsonrpc: \"2.0\",\n id: hostId,\n method: \"tools/call\",\n params: {\n name: \"call_tool\",\n arguments: {\n address: binding.address,\n args: args,\n resultMode: \"value\"\n }\n }\n });\n }\n\n function finishHostRead(message) {\n var pending = pendingHostReads[message.id];\n if (!pending) return false;\n delete pendingHostReads[message.id];\n activeHostReads--;\n if (message.error) {\n finishInnerRead(pending.innerId, false, readError(message.error, \"Host rejected read\"));\n return true;\n }\n var toolResult = message.result;\n if (!toolResult || typeof toolResult !== \"object\") {\n finishInnerRead(pending.innerId, false, \"Host returned an invalid tool result\");\n return true;\n }\n var structured = toolResult.structuredContent;\n if (toolResult.isError || (structured && structured.ok === false)) {\n var detail = structured && structured.error;\n var content = Array.isArray(toolResult.content)\n ? toolResult.content.find(function (block) { return block && block.type === \"text\"; })\n : null;\n finishInnerRead(\n pending.innerId,\n false,\n readError(detail, content && content.text ? content.text : \"Read failed\")\n );\n return true;\n }\n var value = structured && structured.ok === true &&\n Object.prototype.hasOwnProperty.call(structured, \"data\")\n ? structured.data\n : structured !== undefined\n ? structured\n : toolResult;\n finishInnerRead(pending.innerId, true, value);\n return true;\n }\n\n window.addEventListener(\"message\", function (event) {\n var message = event.data;\n if (event.source === view.contentWindow) {\n if (message && message.type === \"connecta/read\") beginInnerRead(message);\n return;\n }\n if (event.source !== host) return;\n if (!message || message.jsonrpc !== \"2.0\") return;\n if (message.id !== undefined && finishHostRead(message)) return;\n if (message.method === \"ui/notifications/tool-result\") {\n render(message.params);\n return;\n }\n if (message.method === \"ui/resource-teardown\") {\n // A host->view request, not a notification: the host waits for\n // this reply before it tears the view down. There is nothing to\n // release, so answer immediately rather than make it time out.\n if (message.id !== undefined && message.id !== null) {\n send({ jsonrpc: \"2.0\", id: message.id, result: {} });\n }\n return;\n }\n // Only a completed handshake earns \"initialized\". A JSON-RPC error\n // response carries the same id, and announcing initialization on one\n // would assert a handshake that never happened.\n if (message.id === initializeId && message.result !== undefined) {\n var capabilities = message.result.hostCapabilities;\n hostCanCallTools = Boolean(capabilities && capabilities.serverTools);\n notify(\"ui/notifications/initialized\", {});\n }\n });\n\n window.addEventListener(\"resize\", reportSize);\n view.addEventListener(\"load\", reportSize);\n\n // Every field here is required by the Apps initialize schema, and a\n // conforming host rejects the request outright when one is missing \u2014\n // which would strand the shell before any tool result arrives.\n send({\n jsonrpc: \"2.0\",\n id: initializeId,\n method: \"ui/initialize\",\n params: {\n appInfo: { name: \"connecta program view\", version: \"1\" },\n appCapabilities: {},\n protocolVersion: \"2026-01-26\"\n }\n });\n reportSize();\n })();\n </script>\n </body>\n</html>\n";
|
|
38
40
|
//# sourceMappingURL=apps-shell.d.ts.map
|
package/dist/apps-shell.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apps-shell.d.ts","sourceRoot":"","sources":["../src/apps-shell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,uEAAuE;AACvE,eAAO,MAAM,uBAAuB,gCAAgC,CAAC;AAErE,+DAA+D;AAC/D,eAAO,MAAM,oBAAoB,8BAA8B,CAAC;AAEhE;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AAEjD,gEAAgE;AAChE,eAAO,MAAM,kBAAkB,+BAA+B,CAAC;AAE/D
|
|
1
|
+
{"version":3,"file":"apps-shell.d.ts","sourceRoot":"","sources":["../src/apps-shell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,uEAAuE;AACvE,eAAO,MAAM,uBAAuB,gCAAgC,CAAC;AAErE,+DAA+D;AAC/D,eAAO,MAAM,oBAAoB,8BAA8B,CAAC;AAEhE;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AAEjD,gEAAgE;AAChE,eAAO,MAAM,kBAAkB,+BAA+B,CAAC;AAE/D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,07ZAsUjC,CAAC"}
|
package/dist/apps-shell.js
CHANGED
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A build-time string constant, not a file read at startup: the core is
|
|
5
5
|
* Web-API-only so it runs unchanged on Workers, and the same bytes have to
|
|
6
|
-
* serve everywhere.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* serve everywhere. It renders whatever HTML a program handed `connecta.ui`
|
|
7
|
+
* inside a nested `srcdoc` frame. The one-argument form forwards no channel;
|
|
8
|
+
* an explicitly bound view gets only named read calls through the trusted
|
|
9
|
+
* shell, never a raw host channel.
|
|
10
10
|
*
|
|
11
11
|
* The address carries a version segment because hosts are permitted to
|
|
12
|
-
* prefetch and cache templates by URI: change these bytes, bump
|
|
12
|
+
* prefetch and cache templates by URI: change these bytes, bump the version.
|
|
13
13
|
*/
|
|
14
14
|
/** The only `ui://` URI in the system. No program input reaches it. */
|
|
15
|
-
export const PROGRAM_UI_RESOURCE_URI = "ui://connecta/program-ui/
|
|
15
|
+
export const PROGRAM_UI_RESOURCE_URI = "ui://connecta/program-ui/v2";
|
|
16
16
|
/** The mimeType the Apps spec requires of an HTML template. */
|
|
17
17
|
export const PROGRAM_UI_MIME_TYPE = "text/html;profile=mcp-app";
|
|
18
18
|
/**
|
|
@@ -29,10 +29,12 @@ export const MCP_APPS_EXTENSION = "io.modelcontextprotocol/ui";
|
|
|
29
29
|
* Apps postMessage dialect (`ui/initialize`, `ui/notifications/initialized`,
|
|
30
30
|
* `ui/notifications/tool-result`, `ui/notifications/size-changed`,
|
|
31
31
|
* `ui/resource-teardown`), lifts `_meta["connecta/ui"].html` out of the
|
|
32
|
-
* delivered tool result, and puts it in a frame.
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
32
|
+
* delivered tool result, and puts it in a frame. An optional read manifest
|
|
33
|
+
* installs one narrow `connecta.read(name, args)` bridge in that inner frame;
|
|
34
|
+
* the outer shell maps declared names to the existing `call_tool` meta-tool.
|
|
35
|
+
* It declares no CSP domains, so the host applies its restrictive default and
|
|
36
|
+
* the `srcdoc` frame inherits `default-src 'none'` — program markup still gets
|
|
37
|
+
* no direct network.
|
|
36
38
|
*/
|
|
37
39
|
export const PROGRAM_UI_SHELL_HTML = `<!doctype html>
|
|
38
40
|
<html lang="en">
|
|
@@ -65,17 +67,21 @@ export const PROGRAM_UI_SHELL_HTML = `<!doctype html>
|
|
|
65
67
|
<script>
|
|
66
68
|
(function () {
|
|
67
69
|
"use strict";
|
|
68
|
-
// The
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
// no bridge from program HTML to the host, by construction rather
|
|
73
|
-
// than by validation.
|
|
70
|
+
// The outer shell is the only host peer. The payload frame is
|
|
71
|
+
// sandboxed to scripts alone, with no same-origin escape. Its one
|
|
72
|
+
// optional message dialect is handled below and translated into
|
|
73
|
+
// bounded call_tool requests; raw JSON-RPC is never forwarded.
|
|
74
74
|
var host = window.parent;
|
|
75
75
|
var view = document.getElementById("program-view");
|
|
76
76
|
var initializeId = "connecta-ui-initialize";
|
|
77
77
|
var lastWidth = 0;
|
|
78
78
|
var lastHeight = 0;
|
|
79
|
+
var reads = null;
|
|
80
|
+
var hostCanCallTools = false;
|
|
81
|
+
var nextHostRequestId = 0;
|
|
82
|
+
var pendingHostReads = Object.create(null);
|
|
83
|
+
var activeHostReads = 0;
|
|
84
|
+
var maxActiveHostReads = 8;
|
|
79
85
|
|
|
80
86
|
function send(message) {
|
|
81
87
|
if (!host || host === window) return;
|
|
@@ -87,8 +93,8 @@ export const PROGRAM_UI_SHELL_HTML = `<!doctype html>
|
|
|
87
93
|
}
|
|
88
94
|
|
|
89
95
|
// Program views are fixed-height by construction. The shell has no
|
|
90
|
-
// bridge to the payload frame
|
|
91
|
-
//
|
|
96
|
+
// content-height bridge to the payload frame, so it can never learn
|
|
97
|
+
// the payload's content height, and
|
|
92
98
|
// what it reports here is its own box: the min-height above, unless
|
|
93
99
|
// the host has given it more. Taller content scrolls inside the inner
|
|
94
100
|
// frame rather than growing the view. Raising the min-height is the
|
|
@@ -105,30 +111,213 @@ export const PROGRAM_UI_SHELL_HTML = `<!doctype html>
|
|
|
105
111
|
});
|
|
106
112
|
}
|
|
107
113
|
|
|
108
|
-
function
|
|
114
|
+
function payload(result) {
|
|
109
115
|
if (!result || typeof result !== "object") return null;
|
|
110
116
|
var meta = result._meta;
|
|
111
117
|
if (!meta || typeof meta !== "object") return null;
|
|
112
|
-
var
|
|
113
|
-
if (!
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
var value = meta["connecta/ui"];
|
|
119
|
+
if (!value || typeof value !== "object") return null;
|
|
120
|
+
return typeof value.html === "string" && value.html.length > 0
|
|
121
|
+
? value
|
|
122
|
+
: null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// This function is serialized into the opaque-origin payload frame.
|
|
126
|
+
// It knows no addresses and has no host channel of its own: one named
|
|
127
|
+
// read request goes to the trusted outer shell and one correlated
|
|
128
|
+
// result comes back.
|
|
129
|
+
function payloadReadBridge() {
|
|
130
|
+
"use strict";
|
|
131
|
+
var pending = Object.create(null);
|
|
132
|
+
var nextId = 0;
|
|
133
|
+
|
|
134
|
+
function read(name, args) {
|
|
135
|
+
return new Promise(function (resolve, reject) {
|
|
136
|
+
var id = String(++nextId);
|
|
137
|
+
pending[id] = { resolve: resolve, reject: reject };
|
|
138
|
+
try {
|
|
139
|
+
window.parent.postMessage({
|
|
140
|
+
type: "connecta/read",
|
|
141
|
+
id: id,
|
|
142
|
+
name: name,
|
|
143
|
+
args: args === undefined ? {} : args
|
|
144
|
+
}, "*");
|
|
145
|
+
} catch (error) {
|
|
146
|
+
delete pending[id];
|
|
147
|
+
reject(error);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
Object.defineProperty(globalThis, "connecta", {
|
|
153
|
+
value: Object.freeze({ read: read }),
|
|
154
|
+
configurable: false,
|
|
155
|
+
enumerable: true,
|
|
156
|
+
writable: false
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
window.addEventListener("message", function (event) {
|
|
160
|
+
if (event.source !== window.parent) return;
|
|
161
|
+
var message = event.data;
|
|
162
|
+
if (!message || message.type !== "connecta/read-result") return;
|
|
163
|
+
var waiter = pending[message.id];
|
|
164
|
+
if (!waiter) return;
|
|
165
|
+
delete pending[message.id];
|
|
166
|
+
if (message.ok) waiter.resolve(message.value);
|
|
167
|
+
else waiter.reject(new Error(message.error || "Read failed"));
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function htmlWithReadBridge(html) {
|
|
172
|
+
var script =
|
|
173
|
+
"<scr" + "ipt>(" + payloadReadBridge.toString() + ")();</scr" + "ipt>";
|
|
174
|
+
var head = /<head(?:\\s[^>]*)?>/i.exec(html);
|
|
175
|
+
if (head) {
|
|
176
|
+
var at = (head.index || 0) + head[0].length;
|
|
177
|
+
return html.slice(0, at) + script + html.slice(at);
|
|
178
|
+
}
|
|
179
|
+
var document = /<html(?:\\s[^>]*)?>/i.exec(html);
|
|
180
|
+
if (document) {
|
|
181
|
+
var afterHtml = (document.index || 0) + document[0].length;
|
|
182
|
+
return html.slice(0, afterHtml) + "<head>" + script + "</head>" + html.slice(afterHtml);
|
|
183
|
+
}
|
|
184
|
+
return script + html;
|
|
116
185
|
}
|
|
117
186
|
|
|
118
187
|
function render(params) {
|
|
119
|
-
var
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (
|
|
124
|
-
|
|
188
|
+
var value =
|
|
189
|
+
payload(params) ||
|
|
190
|
+
payload(params && params.result) ||
|
|
191
|
+
payload(params && params.toolResult);
|
|
192
|
+
if (value === null) return;
|
|
193
|
+
reads = value.reads && typeof value.reads === "object"
|
|
194
|
+
? value.reads
|
|
195
|
+
: null;
|
|
196
|
+
view.srcdoc = reads
|
|
197
|
+
? htmlWithReadBridge(value.html)
|
|
198
|
+
: value.html;
|
|
125
199
|
reportSize();
|
|
126
200
|
}
|
|
127
201
|
|
|
202
|
+
function readError(message, fallback) {
|
|
203
|
+
if (message && typeof message.message === "string") return message.message;
|
|
204
|
+
if (message && message.data && typeof message.data.message === "string") {
|
|
205
|
+
return message.data.message;
|
|
206
|
+
}
|
|
207
|
+
return fallback;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function finishInnerRead(innerId, ok, value) {
|
|
211
|
+
if (!view.contentWindow) return;
|
|
212
|
+
view.contentWindow.postMessage(ok
|
|
213
|
+
? { type: "connecta/read-result", id: innerId, ok: true, value: value }
|
|
214
|
+
: { type: "connecta/read-result", id: innerId, ok: false, error: value }, "*");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function beginInnerRead(message) {
|
|
218
|
+
if (!hostCanCallTools) {
|
|
219
|
+
finishInnerRead(message && message.id, false, "This host does not support app-initiated server tool calls");
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (!message || typeof message.id !== "string" || typeof message.name !== "string") return;
|
|
223
|
+
if (!reads || !Object.prototype.hasOwnProperty.call(reads, message.name)) {
|
|
224
|
+
finishInnerRead(message.id, false, "Unknown read binding");
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (activeHostReads >= maxActiveHostReads) {
|
|
228
|
+
finishInnerRead(message.id, false, "Too many concurrent reads");
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
var binding = reads[message.name];
|
|
232
|
+
if (!binding || typeof binding !== "object" || typeof binding.address !== "string") {
|
|
233
|
+
finishInnerRead(message.id, false, "Invalid read binding");
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
var supplied = message.args;
|
|
237
|
+
if (!supplied || typeof supplied !== "object" || Array.isArray(supplied)) {
|
|
238
|
+
finishInnerRead(message.id, false, "Read arguments must be an object");
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
var allowed = Array.isArray(binding.viewArgs) ? binding.viewArgs : [];
|
|
242
|
+
var suppliedKeys = Object.keys(supplied);
|
|
243
|
+
for (var i = 0; i < suppliedKeys.length; i++) {
|
|
244
|
+
var key = suppliedKeys[i];
|
|
245
|
+
if (allowed.indexOf(key) === -1) {
|
|
246
|
+
finishInnerRead(message.id, false, "Undeclared read argument " + JSON.stringify(key));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
var args = Object.create(null);
|
|
251
|
+
var fixed = binding.fixedArgs && typeof binding.fixedArgs === "object"
|
|
252
|
+
? binding.fixedArgs
|
|
253
|
+
: {};
|
|
254
|
+
Object.keys(fixed).forEach(function (key) { args[key] = fixed[key]; });
|
|
255
|
+
suppliedKeys.forEach(function (key) { args[key] = supplied[key]; });
|
|
256
|
+
|
|
257
|
+
var hostId = "connecta-ui-read-" + String(++nextHostRequestId);
|
|
258
|
+
pendingHostReads[hostId] = { innerId: message.id };
|
|
259
|
+
activeHostReads++;
|
|
260
|
+
send({
|
|
261
|
+
jsonrpc: "2.0",
|
|
262
|
+
id: hostId,
|
|
263
|
+
method: "tools/call",
|
|
264
|
+
params: {
|
|
265
|
+
name: "call_tool",
|
|
266
|
+
arguments: {
|
|
267
|
+
address: binding.address,
|
|
268
|
+
args: args,
|
|
269
|
+
resultMode: "value"
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function finishHostRead(message) {
|
|
276
|
+
var pending = pendingHostReads[message.id];
|
|
277
|
+
if (!pending) return false;
|
|
278
|
+
delete pendingHostReads[message.id];
|
|
279
|
+
activeHostReads--;
|
|
280
|
+
if (message.error) {
|
|
281
|
+
finishInnerRead(pending.innerId, false, readError(message.error, "Host rejected read"));
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
var toolResult = message.result;
|
|
285
|
+
if (!toolResult || typeof toolResult !== "object") {
|
|
286
|
+
finishInnerRead(pending.innerId, false, "Host returned an invalid tool result");
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
var structured = toolResult.structuredContent;
|
|
290
|
+
if (toolResult.isError || (structured && structured.ok === false)) {
|
|
291
|
+
var detail = structured && structured.error;
|
|
292
|
+
var content = Array.isArray(toolResult.content)
|
|
293
|
+
? toolResult.content.find(function (block) { return block && block.type === "text"; })
|
|
294
|
+
: null;
|
|
295
|
+
finishInnerRead(
|
|
296
|
+
pending.innerId,
|
|
297
|
+
false,
|
|
298
|
+
readError(detail, content && content.text ? content.text : "Read failed")
|
|
299
|
+
);
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
var value = structured && structured.ok === true &&
|
|
303
|
+
Object.prototype.hasOwnProperty.call(structured, "data")
|
|
304
|
+
? structured.data
|
|
305
|
+
: structured !== undefined
|
|
306
|
+
? structured
|
|
307
|
+
: toolResult;
|
|
308
|
+
finishInnerRead(pending.innerId, true, value);
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
|
|
128
312
|
window.addEventListener("message", function (event) {
|
|
129
|
-
if (event.source !== host) return;
|
|
130
313
|
var message = event.data;
|
|
314
|
+
if (event.source === view.contentWindow) {
|
|
315
|
+
if (message && message.type === "connecta/read") beginInnerRead(message);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (event.source !== host) return;
|
|
131
319
|
if (!message || message.jsonrpc !== "2.0") return;
|
|
320
|
+
if (message.id !== undefined && finishHostRead(message)) return;
|
|
132
321
|
if (message.method === "ui/notifications/tool-result") {
|
|
133
322
|
render(message.params);
|
|
134
323
|
return;
|
|
@@ -146,6 +335,8 @@ export const PROGRAM_UI_SHELL_HTML = `<!doctype html>
|
|
|
146
335
|
// response carries the same id, and announcing initialization on one
|
|
147
336
|
// would assert a handshake that never happened.
|
|
148
337
|
if (message.id === initializeId && message.result !== undefined) {
|
|
338
|
+
var capabilities = message.result.hostCapabilities;
|
|
339
|
+
hostCanCallTools = Boolean(capabilities && capabilities.serverTools);
|
|
149
340
|
notify("ui/notifications/initialized", {});
|
|
150
341
|
}
|
|
151
342
|
});
|
package/dist/apps-shell.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apps-shell.js","sourceRoot":"","sources":["../src/apps-shell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,uEAAuE;AACvE,MAAM,CAAC,MAAM,uBAAuB,GAAG,6BAA6B,CAAC;AAErE,+DAA+D;AAC/D,MAAM,CAAC,MAAM,oBAAoB,GAAG,2BAA2B,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,aAAa,CAAC;AAEjD,gEAAgE;AAChE,MAAM,CAAC,MAAM,kBAAkB,GAAG,4BAA4B,CAAC;AAE/D
|
|
1
|
+
{"version":3,"file":"apps-shell.js","sourceRoot":"","sources":["../src/apps-shell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,uEAAuE;AACvE,MAAM,CAAC,MAAM,uBAAuB,GAAG,6BAA6B,CAAC;AAErE,+DAA+D;AAC/D,MAAM,CAAC,MAAM,oBAAoB,GAAG,2BAA2B,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,aAAa,CAAC;AAEjD,gEAAgE;AAChE,MAAM,CAAC,MAAM,kBAAkB,GAAG,4BAA4B,CAAC;AAE/D;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsUpC,CAAC"}
|