diff --git a/AGENTS.md b/AGENTS.md index 82af45f9..1f2eb1c0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -141,14 +141,14 @@ an unclassified, double-classified, stale, or reasonless entry. Promise-based and name no Effect type: a deployment author should never need a second async paradigm to write a connector, so convert at the boundary, not in the caller. Effect is a hard dependency pinned to one exact version, - never a range, and it is the version Alchemy pins, so a deployment that uses - both resolves one Effect rather than two. `effect/unstable/*` modules are - allowed, which is exactly why the pin is exact: they break in minor releases, + never a range, and compatible with Alchemy's peer requirement, so a deployment + that uses both resolves one Effect rather than two. Modules tagged + `@stability unstable` are allowed behind subpaths: they can break in minor releases, so an upgrade is its own deliberate pull request, never a drive-by in another change. `effect/testing` stays out of `src/` (see import-graph purity). The MCP edges do not move with the core — `@modelcontextprotocol/server` upward, - the SDK client downstream; Effect's `McpServer` is parked until the Effect - core is stable and those edges are re-proven. + the SDK client downstream. Replacing either requires separate proof of the + wire contracts and request lifetimes; the stable core release is not that proof. - **Style.** There is no formatter. Match the surrounding code. The docs voice is precise, occasionally wry, and always explains *why* — don't flatten it into boilerplate. diff --git a/CHANGELOG.md b/CHANGELOG.md index 1600741f..6d64caba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,44 @@ All notable changes to this package are documented here. ## Unreleased +The runtime now uses stable Effect v4, with fixes for cancellation, resumed-write +limits, storage failures, and operator pages. Deployment APIs and the eight MCP +tools keep their existing contracts; no configuration migration is required. +Duplicate API tool names now fail at construction, and doctor refuses redirects +instead of forwarding deployment credentials. Artifact render checks use the +deployment's viewer theme. + +### Changed + +- Pin Effect to `4.0.0`, replacing `4.0.0-rc.117`, and align the optional + Alchemy setup instructions with the stable pin. +- Require TypeScript 5.9.3 or newer within 5.x for repository and Node-template + development, matching Effect v4's TypeScript 5.9 minimum. + +### Fixed + +- Keep queued catalog mutations alive across Worker requests while preserving + cancellation and storage ordering (#623). +- Release failed access-token reservations before lookup publication, close + failed OAuth callback transports, and persist accepted rotating refresh tokens + after owner cancellation without reviving disconnected generations (#624) + (#625) (#626). +- Count historical writes across divergent resumptions and bound resumed write + answers by the journal limit, retaining truthful outcomes for writes already + sent (#627) (#628). +- Roll back rejected file-storage mutations, preserve prototype-named keys, + expire entries at their TTL boundary, and quarantine invalid snapshot roots + without losing their original bytes (#629) (#630) (#631) (#632). +- Ignore superseded artifact-list responses, preserve newly created tokens when + an older list finishes, and reload a confined artifact shell when signing in + again (#633) (#634) (#635). +- Advance artifact listings through empty continuation pages, refuse already + cancelled render checks, and validate Markdown with the viewer's theme across + writes and refreshes (#636) (#637) (#638). +- Prevent doctor redirects from forwarding Cloudflare Access credentials, + reject unreadable successful Notion responses, and reject duplicate API tool + names before classification and dispatch can disagree (#639) (#640) (#641). + ## 0.26.3 — 2026-09-28 This patch lets downstream OAuth connections use a public client metadata diff --git a/bin/connecta.mjs b/bin/connecta.mjs index 5502589a..746fcfc4 100755 --- a/bin/connecta.mjs +++ b/bin/connecta.mjs @@ -132,10 +132,18 @@ const DOCTOR_TIMEOUT_MS = 10_000; async function doctorFetch(url, init = {}) { try { - return await fetch(url, { + const response = await fetch(url, { ...init, + redirect: "manual", signal: AbortSignal.timeout(DOCTOR_TIMEOUT_MS), }); + if ([301, 302, 303, 307, 308].includes(response.status)) { + await response.body?.cancel().catch(() => {}); + throw new Error( + `HTTP ${response.status} redirect refused: doctor sends authentication only to the configured deployment URL.`, + ); + } + return response; } catch (error) { if ( error instanceof Error && diff --git a/documentation/architecture.md b/documentation/architecture.md index c3ca93bd..1c8b63ec 100644 --- a/documentation/architecture.md +++ b/documentation/architecture.md @@ -407,7 +407,8 @@ shape from building, in someone else's repository rather than this one. ## Effect inside -The core runs on Effect v4; no API a deployment touches does. `createConnecta`, +The core runs on stable Effect v4, exact-pinned to `4.0.0`; no API a deployment +touches does. `createConnecta`, `remoteMcp()`, `api()`, the `Connector` contract, and every shipped `.d.ts` are Promise-shaped and name no Effect type, so a connector author never meets a second async paradigm. The reason for the rewrite is the first section of this @@ -569,8 +570,48 @@ the operator and activity routes cost about +100 KB gzip on `./ui` and +130 KB on `./activity`, and matching the wire format meant opting out of most of what it does: unowned paths fall through rather than 404, a wrong method is a JSON 405, and the content-type and size checks run before a body is read. The root -entry may import only `effect` itself (`test/purity.test.ts`); nothing in -`src/` uses `effect/unstable/*`, which would have to stay behind a subpath. +entry may import only `effect` itself (`test/purity.test.ts`). Effect v4's +area imports, such as `effect/http-api` and `effect/ai`, would have to stay +behind a subpath. APIs tagged `@stability unstable` can still change in minor +releases, so stable v4 keeps the exact pin. + +The stable `4.0.0` release was re-evaluated on 2026-10-01 with +[`scripts/probes/effect-v4-evaluation.mjs`](https://github.com/zackbart/connecta/blob/main/scripts/probes/effect-v4-evaluation.mjs). +Run `npm run build`, then `node scripts/probes/effect-v4-evaluation.mjs output.json`. +The [raw observations](https://github.com/zackbart/connecta/blob/main/scripts/probes/effect-v4-evaluation-2026-10-01.json) +are synthetic Node requests and neutral esbuild bundles, not live-client or +workerd lifetime verification. Bundle figures add representative prototypes +alongside the current code; they do not claim savings from removing old code. + +Two native input schemas with validation and JSON Schema rendering add 62,148 +bytes gzip to the root, taking it from 303,083 to 365,231 against a 339,526 +cap. Effect can reject excess properties when both decoding and rendering use +`onExcessProperty: "error"`; its default strips them. The generated schemas +still differ from the exact meta-tool goldens, including record rendering and +composed numeric bounds. A Schema replacement would need to preserve that +contract, memoized rendering, and the raw-argument checks used by resumable +writes. Both MCP SDK packages still depend on Zod, so changing our inputs alone +does not remove Zod from an installation. + +A one-endpoint `HttpApi` prototype adds 106,634 bytes gzip to `./ui` and 106,453 +to `./activity`. Even plain `HttpRouter` adds 45,324 to `./ui`, taking it to +130,023 against a 128,983 cap. Both default handlers return an empty 404 for a +wrong method and an unowned path; our routes need a JSON 405 for the former +and module fallthrough for the latter. Their `toWebHandler` also builds its +layer immediately and owns a runner, so adopting it would require proving +global-scope safety and preserving our scheduler and response-lifetime rules. +The existing Effect route programs keep those rules without this routing layer. + +Effect's MCP server has a separate compatibility gate. Its `2026-07-28` +adapter accepted a stateless `tools/list`, but its `2025-06-18` adapter refused +a fresh request with 400. Initialization issued a session, and that session's +next request returned 404 on a fresh handler; Connecta served the same fresh +legacy list with 200 JSON and no session. The stable package exports no MCP +client, so it also cannot replace our downstream SDK and OAuth implementation. +[Issue #622](https://github.com/zackbart/connecta/issues/622) records the server +parity requirements. These measurements support keeping the current MCP, +validation, and routing implementations; stable v4 alone does not establish a +replacement's benefit or compatibility. The core's cost is recorded rather than guessed. Against 0.24.4 the root entry grew from 235,346 to 279,526 bytes gzip, the Worker example from 263,949 to diff --git a/documentation/auth.md b/documentation/auth.md index be987267..95ea5035 100644 --- a/documentation/auth.md +++ b/documentation/auth.md @@ -64,9 +64,10 @@ also work on older adapters without `compareAndSet`; **new issuance requires atomic `compareAndSet`**, because counting records before writing admits too many concurrent creates. Active capacity defaults to 100, configurable with `accessTokens(storage, { maxActive: 200 })`, up to 1,000. A durable reservation -counts before a secret is written. An interrupted create can consume capacity -without returning a token; it is never automatically retried or released after -an uncertain write. Avoid creating new tokens through old-version instances once +counts before a secret is written. Failures before lookup publication release +their reservation; an uncertain lookup write retains its metadata and capacity +so an operator can revoke it, even when creation returned no secret. Creation is +never automatically retried. Avoid creating new tokens through old-version instances once new-version issuance has started, since those instances do not honor reservations. Secrets contain 256 random bits and only their SHA-256 digests persist. Creation diff --git a/examples/worker/README.md b/examples/worker/README.md index e8bab101..9a4d8c4a 100644 --- a/examples/worker/README.md +++ b/examples/worker/README.md @@ -118,7 +118,7 @@ Keep this example's Worker on Wrangler. A copied deployment may use [Alchemy](https://alchemy.run) to manage its KV namespace, optional D1 databases and R2 bucket, and the Access application without adding Alchemy to connecta or changing this template. In that deployment, install `alchemy@2.0.0-beta.79` -and `effect@4.0.0-rc.117` at exact versions. The latter is connecta's own +and `effect@4.0.0` at exact versions. The latter is connecta's own Effect pin; check `npm ls effect` for one resolved copy after installation. The following `alchemy.run.ts` is a resource stack, not a Worker deployment: @@ -193,8 +193,9 @@ deleting the state store loses Alchemy's record of what it owns. Verification for this optional path stops at the API and types. The fenced snippet was copied to an isolated `alchemy.run.ts` and passed `tsc --noEmit` -with TypeScript 5.9.3, Alchemy 2.0.0-beta.79, and Effect 4.0.0-rc.117; no -Alchemy deploy or adoption was run. +with TypeScript 5.9.3, Alchemy 2.0.0-beta.79, and Effect 4.0.0, with +`npm ls effect` confirming one resolved copy. No Alchemy deploy or adoption +was run. [Alchemy's Worker resource](https://alchemy.run/cloudflare/compute/workers/) can declare `WorkerLoader`, domains, Browser Rendering, and cron wiring, but those properties are coupled to its script deploy. Its current full deploy diff --git a/package-lock.json b/package-lock.json index 47b1f39a..bd51d990 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@cfworker/json-schema": "^4.1.1", "@modelcontextprotocol/client": "2.0.0", "@modelcontextprotocol/server": "2.0.0", - "effect": "4.0.0-rc.117", + "effect": "4.0.0", "zod": "^4.4.3" }, "bin": { @@ -31,7 +31,7 @@ "preact": "^10.29.8", "quickjs-emscripten": "^0.32.0", "tsx": "^4.23.1", - "typescript": "^5.6.0", + "typescript": "^5.9.3", "vitest": "^4.1.6", "wrangler": "^4.114.0" }, @@ -3156,9 +3156,9 @@ } }, "node_modules/effect": { - "version": "4.0.0-rc.117", - "resolved": "https://registry.npmjs.org/effect/-/effect-4.0.0-rc.117.tgz", - "integrity": "sha512-UUyi9QiOW4nySFIBM3KnYbNMd9EZliOdKfBcyLD0mPMPheO2fkuXS5y0opjWc9oqzlJxPQICEGm8gGw3kh9j/w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/effect/-/effect-4.0.0.tgz", + "integrity": "sha512-ooc1TG5t+FfzgYnFz2ff6BBKyZ7EwBRVXC7c4RhQUAD6/TZ2gTXXMeb4WX7a19ozQo4J73/QW+S00YAIresoMQ==", "license": "MIT" }, "node_modules/error-stack-parser-es": { diff --git a/package.json b/package.json index 17d66d46..ffc9f0c4 100644 --- a/package.json +++ b/package.json @@ -154,7 +154,7 @@ "@cfworker/json-schema": "^4.1.1", "@modelcontextprotocol/client": "2.0.0", "@modelcontextprotocol/server": "2.0.0", - "effect": "4.0.0-rc.117", + "effect": "4.0.0", "zod": "^4.4.3" }, "peerDependencies": { @@ -186,7 +186,7 @@ "preact": "^10.29.8", "quickjs-emscripten": "^0.32.0", "tsx": "^4.23.1", - "typescript": "^5.6.0", + "typescript": "^5.9.3", "vitest": "^4.1.6", "wrangler": "^4.114.0" } diff --git a/scripts/probes/effect-v4-evaluation-2026-10-01.json b/scripts/probes/effect-v4-evaluation-2026-10-01.json new file mode 100644 index 00000000..48e60225 --- /dev/null +++ b/scripts/probes/effect-v4-evaluation-2026-10-01.json @@ -0,0 +1,446 @@ +{ + "measuredAt": "2026-10-01T13:49:09.810Z", + "sourceCommit": "6f6ddba33d0a0d0870bab70becbb2fcd14d51278", + "node": "v26.10.0", + "effect": "4.0.0", + "limitations": [ + "Node request probes, not workerd lifetime verification or live client interoperability.", + "Synthetic MCP tool, two representative native input schemas, and one HTTP route.", + "Incremental bundle costs alongside current code; full replacement savings are not measured.", + "HttpRouter.toWebHandler uses its own runner; integration with Connecta's single runner is not proven." + ], + "observations": { + "mcp": [ + { + "label": "connecta legacy fresh tools/list", + "status": 200, + "contentType": "application/json", + "session": null, + "body": "{\"result\":{\"tools\":[{\"name\":\"skills\",\"description\":\"List or fetch on-demand guidance. Fetch usage only when the always-loaded instructions are insufficient or a program needs repair.\",\"inputSchema\":{\"type\":\"object\",\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"name\":{\"type\":\"string\"}}},\"annotations\":{\"readOnlyHint\":true,\"destructiveHint\":false,\"idempotentHint\":true,\"openWorldHint\":false}},{\"name\":\"search_tools\",\"description\":\"Use top-level search for catalog inspection or approval-required work before call_destructive_tool. Unknown-address read-only work belongs in one execute_code program that searches, calls, and returns the answer. Use 2–4 action/object terms and includeSchemas=\\\"compact\\\"; the default limit is 8. Set connector when known. safety=\\\"readOnly\\\" finds tools that run unasked; \\\"approvalRequired\\\" finds the fail-closed complement. These filters grant no authority. Empty query browses.\",\"inputSchema\":{\"type\":\"object\",\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"query\":{\"type\":\"string\"},\"connector\":{\"type\":\"string\"},\"safety\":{\"type\":\"string\",\"enum\":[\"readOnly\",\"approvalRequired\",\"all\"]},\"limit\":{\"type\":\"integer\",\"exclusiveMinimum\":0,\"maximum\":100},\"offset\":{\"type\":\"integer\",\"minimum\":0,\"maximum\":9007199254740991},\"fullDescriptions\":{\"type\":\"boolean\"},\"includeSchemas\":{\"type\":\"string\",\"enum\":[\"compact\",\"json\",\"typescript\"]}}},\"annotations\":{\"readOnlyHint\":true,\"destructiveHint\":false,\"openWorldHint\":true}},{\"name\":\"call_tool\",\"description\":\"Call one known-address tool explicitly annotated readOnlyHint: true. Use execute_code for unknown-address, multiple, dependent, or reduced read-only work. Unannotated or write-capable tools fail closed to call_destructive_tool. A truncated result carries a get_result action.\",\"inputSchema\":{\"type\":\"object\",\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"address\":{\"type\":\"string\"},\"args\":{\"type\":\"object\",\"propertyNames\":{\"type\":\"string\"},\"additionalProperties\":{}},\"resultMode\":{\"type\":\"string\",\"enum\":[\"mcp\",\"value\"]},\"timeoutMs\":{\"type\":\"integer\",\"exclusiveMinimum\":0,\"maximum\":9007199254740991},\"diagnostics\":{\"type\":\"boolean\"}},\"required\":[\"address\"],\"additionalProperties\":false},\"annotations\":{\"readOnlyHint\":true,\"destructiveHint\":false,\"openWorldHint\":true}},{\"name\":\"call_destructive_tool\",\"description\":\"Call any tool not explicitly annotated readOnlyHint: true. Include a short reason for the human reviewer after checking the schema and consequences. The reason grants no authority and is not sent downstream.\",\"inputSchema\":{\"type\":\"object\",\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"address\":{\"type\":\"string\"},\"args\":{\"type\":\"object\",\"propertyNames\":{\"type\":\"string\"},\"additionalProperties\":{}},\"resultMode\":{\"type\":\"string\",\"enum\":[\"mcp\",\"value\"]},\"timeoutMs\":{\"type\":\"integer\",\"exclusiveMinimum\":0,\"maximum\":9007199254740991},\"diagnostics\":{\"type\":\"boolean\"},\"reason\":{\"type\":\"string\",\"maxLength\":500}},\"required\":[\"address\"],\"additionalProperties\":false},\"annotations\":{\"destructiveHint\":true,\"readOnlyHint\":false,\"openWorldHint\":true}},{\"name\":\"authorize_connector\",\"description\":\"Use after auth_required. Returns an OAuth or operator-credential handoff, or reports required deployment configuration. force=true restarts OAuth only; this tool never accepts credentials.\",\"inputSchema\":{\"type\":\"object\",\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"connector\":{\"type\":\"string\"},\"force\":{\"type\":\"boolean\"}},\"required\":[\"connector\"]},\"annotations\":{\"readOnlyHint\":false,\"destructiveHint\":false,\"openWorldHint\":true}},{\"name\":\"get_result\",\"description\":\"Page a truncated direct-call result by id and byte offset. A program result is never paged; reduce it inside execute_code. Returns a one-line JSON header (offset, bytes, totalBytes, hasMore, nextAction) and then the page as raw text. maxBytes is an upper bound, clamped to the result's inline cap.\",\"inputSchema\":{\"type\":\"object\",\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"id\":{\"type\":\"string\"},\"offset\":{\"type\":\"integer\",\"minimum\":0,\"maximum\":9007199254740991},\"maxBytes\":{\"type\":\"integer\",\"minimum\":1,\"maximum\":9007199254740991}},\"required\":[\"id\"]},\"annotations\":{\"readOnlyHint\":true,\"destructiveHint\":false,\"idempotentHint\":true,\"openWorldHint\":false}},{\"name\":\"execute_code\",\"description\":\"Use the configured services below to answer the task. A known address uses call_tool. Unknown-address and wider work, writes included, uses one execute_code program for discovery, calls, and reduction. Do not return catalog matches alone. Writes pause for resume_execution. Keep an authorized batch in one program; approval \\\"tool\\\" covers later same-address calls, other approval-required writes pause separately. Limits: 20 host calls, 10 writes, 15s/host call.\\n\\nConnectors: none.\\n\\nRead guides with top-level skills. Write async () => { ... } using the global connecta:\\n- connecta.search({ connector, query, includeSchemas: \\\"json\\\" }) returns { tools }. Search operations separately; choose by connectorTitle and schema.required/.properties, never guessed fields. Compact schemas are text.\\n- connecta.describe({ address }) clarifies schemas.\\n- connecta.call(address, args) returns the provider value directly.\\n- Use Promise.all for independent calls, or Promise.allSettled to retain failures. Check status; missing values are unknown, never false or zero.\\n- connecta.emit(block): { type: \\\"text\\\", text } or { type: \\\"image\\\" | \\\"audio\\\", data (base64), mimeType }; success-only, 32 blocks/4000000 bytes. console.log(...) is captured.\\n\\nNo portable ambient capabilities. Return reduced JSON. Sample unfamiliar reads. Reduce a one-time write's full result here, or use a direct call and get_result paging; never repeat it to recover output. Never guess fields or use the whole text as an id. Top-level skills({ name: \\\"usage\\\" }): repair; skills({ name: \\\"investigate\\\" }): task planning.\",\"inputSchema\":{\"type\":\"object\",\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"code\":{\"type\":\"string\",\"description\":\"One complete zero-argument JavaScript async arrow: async () => { ... }. Use the provided connecta global to discover, call, and return the reduced answer. At most 65,536 UTF-8 bytes.\"},\"diagnostics\":{\"description\":\"Add request-local, payload-free timing and result-size summaries.\",\"type\":\"boolean\"}},\"required\":[\"code\"]},\"annotations\":{\"readOnlyHint\":true,\"destructiveHint\":false,\"openWorldHint\":true}},{\"name\":\"resume_execution\",\"description\":\"Approve and run the write a paused execute_code program stopped at. Repeat the paused result's token, address, and args exactly. The program replays from its journal — earlier reads are answered from it, not repeated — sends the write once, and continues to its result or its next pause. approval \\\"tool\\\" also approves later calls to that tool in this run. A mismatch approves and sends nothing. reason is for the human reviewer and is not sent downstream.\",\"inputSchema\":{\"type\":\"object\",\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"token\":{\"type\":\"string\",\"maxLength\":512},\"address\":{\"type\":\"string\"},\"args\":{\"type\":\"object\",\"propertyNames\":{\"type\":\"string\"},\"additionalProperties\":{}},\"approval\":{\"type\":\"string\",\"enum\":[\"call\",\"tool\"]},\"reason\":{\"type\":\"string\",\"maxLength\":500}},\"required\":[\"token\",\"address\",\"args\"],\"additionalProperties\":false},\"annotations\":{\"readOnlyHint\":false,\"destructiveHint\":true,\"openWorldHint\":true}}]},\"jsonrpc\":\"2.0\",\"id\":1}" + }, + { + "label": "effect legacy fresh tools/list", + "status": 400, + "contentType": null, + "session": null, + "body": "" + }, + { + "label": "effect legacy initialize", + "status": 200, + "contentType": "application/json", + "session": "synthetic-session-issued", + "body": "{\"jsonrpc\":\"2.0\",\"id\":3,\"result\":{\"protocolVersion\":\"2025-06-18\",\"capabilities\":{\"logging\":{},\"tools\":{\"listChanged\":true},\"completions\":{}},\"serverInfo\":{\"name\":\"probe\",\"version\":\"1\"}}}\n" + }, + { + "label": "effect legacy same handler tools/list", + "status": 200, + "contentType": "application/json", + "session": null, + "body": "{\"jsonrpc\":\"2.0\",\"id\":4,\"result\":{\"tools\":[{\"name\":\"probe\",\"description\":\"Synthetic read\",\"inputSchema\":{\"type\":\"object\",\"properties\":{}},\"annotations\":{\"readOnlyHint\":true,\"destructiveHint\":true,\"idempotentHint\":false,\"openWorldHint\":true}}]}}\n" + }, + { + "label": "effect legacy fresh handler tools/list", + "status": 404, + "contentType": null, + "session": null, + "body": "" + }, + { + "label": "effect modern tools/list", + "status": 200, + "contentType": "application/json", + "session": null, + "body": "{\"jsonrpc\":\"2.0\",\"id\":6,\"result\":{\"_meta\":{\"io.modelcontextprotocol/serverInfo\":{\"name\":\"probe\",\"version\":\"1\"}},\"resultType\":\"complete\",\"ttlMs\":0,\"cacheScope\":\"private\",\"tools\":[{\"name\":\"probe\",\"description\":\"Synthetic read\",\"inputSchema\":{\"type\":\"object\",\"properties\":{}},\"annotations\":{\"readOnlyHint\":true,\"destructiveHint\":true,\"idempotentHint\":false,\"openWorldHint\":true}}]}}\n" + } + ], + "http": [ + { + "label": "router", + "method": "GET", + "path": "/ui/data", + "status": 200, + "contentType": "application/json", + "body": "{\"ok\":true}" + }, + { + "label": "router", + "method": "POST", + "path": "/ui/data", + "status": 404, + "contentType": null, + "body": "" + }, + { + "label": "router", + "method": "GET", + "path": "/unowned", + "status": 404, + "contentType": null, + "body": "" + }, + { + "label": "httpapi", + "method": "GET", + "path": "/ui/data", + "status": 200, + "contentType": "application/json", + "body": "{\"ok\":true}" + }, + { + "label": "httpapi", + "method": "POST", + "path": "/ui/data", + "status": 404, + "contentType": null, + "body": "" + }, + { + "label": "httpapi", + "method": "GET", + "path": "/unowned", + "status": 404, + "contentType": null, + "body": "" + } + ], + "schema": { + "originalCall": { + "type": "object", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "address": { + "type": "string" + }, + "args": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "resultMode": { + "type": "string", + "enum": [ + "mcp", + "value" + ] + }, + "timeoutMs": { + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "diagnostics": { + "type": "boolean" + } + }, + "required": [ + "address" + ], + "additionalProperties": false + }, + "originalSearch": { + "type": "object", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "query": { + "type": "string" + }, + "connector": { + "type": "string" + }, + "safety": { + "type": "string", + "enum": [ + "readOnly", + "approvalRequired", + "all" + ] + }, + "limit": { + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 100 + }, + "offset": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "fullDescriptions": { + "type": "boolean" + }, + "includeSchemas": { + "type": "string", + "enum": [ + "compact", + "json", + "typescript" + ] + } + } + }, + "effectCall": { + "dialect": "draft-2020-12", + "schema": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "args": { + "type": "object" + }, + "resultMode": { + "type": "string", + "enum": [ + "mcp", + "value" + ] + }, + "timeoutMs": { + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "diagnostics": { + "type": "boolean" + } + }, + "required": [ + "address" + ], + "additionalProperties": false + }, + "definitions": {} + }, + "effectSearch": { + "dialect": "draft-2020-12", + "schema": { + "type": "object", + "properties": { + "query": { + "type": "string" + }, + "connector": { + "type": "string" + }, + "safety": { + "type": "string", + "enum": [ + "readOnly", + "approvalRequired", + "all" + ] + }, + "limit": { + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991, + "allOf": [ + { + "maximum": 100 + } + ] + }, + "offset": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991, + "allOf": [ + { + "minimum": 0 + } + ] + }, + "fullDescriptions": { + "type": "boolean" + }, + "includeSchemas": { + "type": "string", + "enum": [ + "compact", + "json", + "typescript" + ] + } + }, + "additionalProperties": true + }, + "definitions": {} + }, + "validation": [ + { + "input": { + "address": "probe", + "extra": true + }, + "accepted": false, + "error": "Expected no excess property\n at [\"extra\"]" + }, + { + "input": { + "address": "probe", + "timeoutMs": 9007199254740992 + }, + "accepted": false, + "error": "Expected an integer\n at [\"timeoutMs\"]" + }, + { + "input": { + "address": "probe", + "timeoutMs": 0 + }, + "accepted": false, + "error": "Expected a value greater than 0\n at [\"timeoutMs\"]" + }, + { + "input": { + "address": "probe", + "args": { + "nested": true + } + }, + "value": { + "address": "probe", + "args": { + "nested": true + } + }, + "accepted": true + } + ], + "defaultExcess": { + "address": "probe" + } + } + }, + "sizes": [ + { + "entry": "root", + "probe": "baseline", + "gzip": 303083, + "delta": 0, + "nodeImports": [] + }, + { + "entry": "root", + "probe": "schema", + "gzip": 365231, + "delta": 62148, + "nodeImports": [] + }, + { + "entry": "root", + "probe": "router", + "gzip": 348407, + "delta": 45324, + "nodeImports": [] + }, + { + "entry": "root", + "probe": "httpapi", + "gzip": 408672, + "delta": 105589, + "nodeImports": [] + }, + { + "entry": "root", + "probe": "mcp", + "gzip": 405991, + "delta": 102908, + "nodeImports": [] + }, + { + "entry": "ui", + "probe": "baseline", + "gzip": 84699, + "delta": 0, + "nodeImports": [] + }, + { + "entry": "ui", + "probe": "schema", + "gzip": 146618, + "delta": 61919, + "nodeImports": [] + }, + { + "entry": "ui", + "probe": "router", + "gzip": 130023, + "delta": 45324, + "nodeImports": [] + }, + { + "entry": "ui", + "probe": "httpapi", + "gzip": 191333, + "delta": 106634, + "nodeImports": [] + }, + { + "entry": "ui", + "probe": "mcp", + "gzip": 188975, + "delta": 104276, + "nodeImports": [] + }, + { + "entry": "activity", + "probe": "baseline", + "gzip": 36202, + "delta": 0, + "nodeImports": [] + }, + { + "entry": "activity", + "probe": "schema", + "gzip": 98181, + "delta": 61979, + "nodeImports": [] + }, + { + "entry": "activity", + "probe": "router", + "gzip": 81371, + "delta": 45169, + "nodeImports": [] + }, + { + "entry": "activity", + "probe": "httpapi", + "gzip": 142655, + "delta": 106453, + "nodeImports": [] + }, + { + "entry": "activity", + "probe": "mcp", + "gzip": 140204, + "delta": 104002, + "nodeImports": [] + }, + { + "entry": "standalone", + "probe": "schema", + "gzip": 75469 + }, + { + "entry": "standalone", + "probe": "router", + "gzip": 74896 + }, + { + "entry": "standalone", + "probe": "httpapi", + "gzip": 136209 + }, + { + "entry": "standalone", + "probe": "mcp", + "gzip": 134315 + } + ] +} diff --git a/scripts/probes/effect-v4-evaluation.mjs b/scripts/probes/effect-v4-evaluation.mjs new file mode 100644 index 00000000..9b66b105 --- /dev/null +++ b/scripts/probes/effect-v4-evaluation.mjs @@ -0,0 +1,189 @@ +// Local evaluation of Effect 4.0.0's MCP, Schema, and HTTP replacements. +// Synthetic requests only. Never deploys or contacts a provider. +// Run npm run build, then node scripts/probes/effect-v4-evaluation.mjs output.json. +// Bundle figures are incremental probes alongside existing implementations, +// not measurements of complete replacements or of removed dependency savings. +import { execFileSync } from "node:child_process"; +import { mkdtemp, readFile, rm, symlink, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +const root = resolve(dirname(fileURLToPath(import.meta.url)), "../.."); +const work = await mkdtemp(join(tmpdir(), "connecta-effect-evaluation-")); +const prototypes = { + mcp: `import { Effect, Layer, Context } from 'effect'; +import { McpServer, McpProtocol, McpSchema } from 'effect/ai'; +import { HttpRouter } from 'effect/http'; + +const registration = Layer.effectDiscard(Effect.gen(function* () { + const server = yield* McpServer.McpServer; + yield* server.addTool({ + tool: new McpSchema.Tool({ name: 'probe', description: 'Synthetic read', inputSchema: { type: 'object', properties: {} }, annotations: { readOnlyHint: true } }), + annotations: Context.empty(), + handle: () => Effect.succeed(new McpSchema.CallToolResult({ content: [{ type: 'text', text: 'ok' }] })), + }); +})); +export function makeServer(protocols = [McpProtocol.v2025_06_18, McpProtocol.v2026_07_28]) { + const server = McpServer.layerHttp({ name: 'probe', version: '1', path: '/mcp', protocols }); + return HttpRouter.toWebHandler(registration.pipe(Layer.provideMerge(server)), { disableLogger: true }); +} +`, + schema: `import { Schema } from 'effect'; +const optional = Schema.optionalKey; +const integer = Schema.Number.check(Schema.isInt(), Schema.isBetween({ minimum: -Number.MAX_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER })); +const positive = Schema.Number.check(Schema.isInt(), Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER)); +export const call = Schema.Struct({ + address: Schema.String, + args: optional(Schema.Record(Schema.String, Schema.Unknown)), + resultMode: optional(Schema.Literals(['mcp', 'value'])), + timeoutMs: optional(positive), + diagnostics: optional(Schema.Boolean), +}); +export const search = Schema.Struct({ + query: optional(Schema.String), connector: optional(Schema.String), + safety: optional(Schema.Literals(['readOnly', 'approvalRequired', 'all'])), + limit: optional(positive.check(Schema.isLessThanOrEqualTo(100))), + offset: optional(integer.check(Schema.isGreaterThanOrEqualTo(0))), + fullDescriptions: optional(Schema.Boolean), + includeSchemas: optional(Schema.Literals(['compact', 'json', 'typescript'])), +}); +export const rendered = [Schema.toJsonSchemaDocument(call, { onExcessProperty: 'error' }), Schema.toJsonSchemaDocument(search)]; +export const validate = Schema.decodeUnknownSync(call, { onExcessProperty: 'error' }); +`, + router: `import { Effect } from 'effect'; +import { HttpRouter, HttpServerResponse } from 'effect/http'; +export function makeRouter() { + return HttpRouter.toWebHandler(HttpRouter.add('GET', '/ui/data', Effect.succeed(HttpServerResponse.jsonUnsafe({ ok: true }))), { disableLogger: true }); +} +`, + httpapi: `import { Effect, Layer, Schema } from 'effect'; +import { HttpRouter, HttpServer } from 'effect/http'; +import { HttpApi, HttpApiGroup, HttpApiEndpoint, HttpApiBuilder } from 'effect/http-api'; +const api = HttpApi.make('probe').add(HttpApiGroup.make('operator').add(HttpApiEndpoint.get('data', '/ui/data', { success: Schema.Struct({ ok: Schema.Boolean }) }))); +const handlers = HttpApiBuilder.group(api, 'operator', h => h.handle('data', () => Effect.succeed({ ok: true }))); +export function makeApi() { + return HttpRouter.toWebHandler(HttpApiBuilder.layer(api).pipe(Layer.provide(handlers), Layer.provide(HttpServer.layerServices)), { disableLogger: true }); +} +`, +}; +const probeSource = `import { makeServer } from './mcp.mjs'; +import { makeRouter } from './router.mjs'; +import { makeApi } from './httpapi.mjs'; +import { call, search, rendered, validate } from './schema.mjs'; +import { createConnecta } from __CONNECTA_ENTRY__; +import { Schema } from 'effect'; +import { writeFileSync } from 'node:fs'; + +const results = { mcp: [], http: [], schema: {} }; +let id = 0; +function request(method, params = {}, headers = {}) { + return new Request('https://probe.test/mcp', { method: 'POST', headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream', 'mcp-protocol-version': '2025-06-18', ...headers }, body: JSON.stringify({ jsonrpc: '2.0', id: ++id, method, params }) }); +} +async function inspect(label, fetcher, req) { + const response = await fetcher(req); + const body = await response.text(); + const result = { label, status: response.status, contentType: response.headers.get('content-type'), session: response.headers.get('mcp-session-id'), body }; + results.mcp.push(result); + return result; +} +const deployment = createConnecta({ connectors: [], executor: { execute: async () => ({ result: null }) }, logger: 'silent' }); +const originalList = await inspect('connecta legacy fresh tools/list', req => deployment.fetch(req), request('tools/list')); +const original = JSON.parse(originalList.body).result.tools; +const first = makeServer(); +const second = makeServer(); +try { + await inspect('effect legacy fresh tools/list', first.handler, request('tools/list')); + const initialized = await inspect('effect legacy initialize', first.handler, request('initialize', { protocolVersion: '2025-06-18', capabilities: {}, clientInfo: { name: 'probe', version: '1' } })); + if (initialized.session) { + await inspect('effect legacy same handler tools/list', first.handler, request('tools/list', {}, { 'mcp-session-id': initialized.session })); + await inspect('effect legacy fresh handler tools/list', second.handler, request('tools/list', {}, { 'mcp-session-id': initialized.session })); + } + await inspect('effect modern tools/list', second.handler, request('tools/list', { _meta: { 'io.modelcontextprotocol/protocolVersion': '2026-07-28', 'io.modelcontextprotocol/clientCapabilities': {} } }, { 'mcp-protocol-version': '2026-07-28', 'mcp-method': 'tools/list' })); +} finally { await first.dispose(); await second.dispose(); } +for (const [label, make] of [['router', makeRouter], ['httpapi', makeApi]]) { + const app = make(); + try { + for (const [method, path] of [['GET', '/ui/data'], ['POST', '/ui/data'], ['GET', '/unowned']]) { + const response = await app.handler(new Request('https://probe.test' + path, { method })); + results.http.push({ label, method, path, status: response.status, contentType: response.headers.get('content-type'), body: await response.text() }); + } + } finally { await app.dispose(); } +} +results.schema.originalCall = original.find(x => x.name === 'call_tool').inputSchema; +results.schema.originalSearch = original.find(x => x.name === 'search_tools').inputSchema; +results.schema.effectCall = rendered[0]; results.schema.effectSearch = rendered[1]; +results.schema.validation = [{ address: 'probe', extra: true }, { address: 'probe', timeoutMs: Number.MAX_SAFE_INTEGER + 1 }, { address: 'probe', timeoutMs: 0 }, { address: 'probe', args: { nested: true } }].map(input => { try { return { input, value: validate(input), accepted: true }; } catch (error) { return { input, accepted: false, error: error.message }; } }); +results.schema.defaultExcess = Schema.decodeUnknownSync(call)({ address: 'probe', extra: true }); +await deployment.close(); +writeFileSync('__EVALUATION_DIR__/results.json', JSON.stringify(results, null, 2)); +console.log(JSON.stringify({ mcp: results.mcp.map(({body, ...x}) => ({ ...x, body: body.slice(0,300) })), http: results.http, schema: results.schema }, null, 2)); +`; +const measureSource = `import { build } from 'esbuild'; +import { gzipSync } from 'node:zlib'; +import { writeFileSync } from 'node:fs'; +const root = __ROOT__; +const probes = __WORK__; +const results = []; +for (const [entry, file] of [['root', 'index'], ['ui', 'ui'], ['activity', 'activity']]) { + let baseline; + for (const probe of [null, 'schema', 'router', 'httpapi', 'mcp']) { + const code = \`export * from '\${root}/src/\${file}.ts';\\n\${probe ? \`export * as probe from '\${probes}/\${probe}.mjs';\` : ''}\`; + const built = await build({ stdin: { contents: code, resolveDir: root, sourcefile: 'evaluation.js', loader: 'js' }, bundle: true, format: 'esm', platform: 'neutral', conditions: ['workerd', 'worker', 'browser', 'import'], mainFields: ['module', 'main'], minify: true, write: false, metafile: true, external: ['@clerk/backend', '@cloudflare/codemode', 'quickjs-emscripten', 'cloudflare:*', 'node:*'], logLevel: 'silent' }); + const gzip = gzipSync(built.outputFiles[0].contents, { level: 9 }).length; + if (probe === null) baseline = gzip; + results.push({ entry, probe: probe ?? 'baseline', gzip, delta: gzip - baseline, nodeImports: Object.values(built.metafile.outputs).flatMap(x => x.imports.filter(i => i.external && i.path.startsWith('node:')).map(i => i.path)) }); + } +} +for (const probe of ['schema', 'router', 'httpapi', 'mcp']) { + const built = await build({ entryPoints: [\`\${probes}/\${probe}.mjs\`], bundle: true, format: 'esm', platform: 'neutral', conditions: ['workerd', 'worker', 'browser', 'import'], mainFields: ['module', 'main'], minify: true, write: false, external: ['node:*'], logLevel: 'silent' }); + results.push({ entry: 'standalone', probe, gzip: gzipSync(built.outputFiles[0].contents, {level:9}).length }); +} +writeFileSync(\`\${probes}/sizes.json\`, JSON.stringify(results,null,2)); +console.table(results.map(({nodeImports,...r})=>r)); +`; + +try { + await symlink(join(root, "node_modules"), join(work, "node_modules"), "dir"); + for (const [name, source] of Object.entries(prototypes)) { + await writeFile(join(work, `${name}.mjs`), source); + } + await writeFile(join(work, "probe.mjs"), probeSource + .replace("__CONNECTA_ENTRY__", JSON.stringify(pathToFileURL(join(root, "dist/index.js")).href)) + .replaceAll("__EVALUATION_DIR__", work)); + await writeFile(join(work, "measure.mjs"), measureSource + .replace("__ROOT__", JSON.stringify(root)) + .replace("__WORK__", JSON.stringify(work))); + execFileSync(process.execPath, [join(work, "probe.mjs")], { encoding: "utf8", timeout: 60_000 }); + execFileSync(process.execPath, [join(work, "measure.mjs")], { encoding: "utf8", timeout: 60_000 }); + const observations = JSON.parse(await readFile(join(work, "results.json"), "utf8")); + // Session ids are synthetic and needed only between requests, not in the report. + for (const row of observations.mcp) { + if (row.session !== null) row.session = "synthetic-session-issued"; + } + const sizes = JSON.parse(await readFile(join(work, "sizes.json"), "utf8")); + const effect = JSON.parse(await readFile(join(root, "node_modules/effect/package.json"), "utf8")).version; + const report = { + measuredAt: new Date().toISOString(), + sourceCommit: execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }).trim(), + node: process.version, + effect, + limitations: [ + "Node request probes, not workerd lifetime verification or live client interoperability.", + "Synthetic MCP tool, two representative native input schemas, and one HTTP route.", + "Incremental bundle costs alongside current code; full replacement savings are not measured.", + "HttpRouter.toWebHandler uses its own runner; integration with Connecta's single runner is not proven.", + ], + observations, + sizes, + }; + const output = JSON.stringify(report, null, 2) + "\n"; + if (process.argv[2]) { + await writeFile(resolve(process.argv[2]), output); + console.log(`Saved evaluation to ${resolve(process.argv[2])}`); + } else { + console.log(output); + } +} finally { + await rm(work, { recursive: true, force: true }); +} diff --git a/src/access-tokens.ts b/src/access-tokens.ts index e3ff8a2e..c6417780 100644 --- a/src/access-tokens.ts +++ b/src/access-tokens.ts @@ -256,8 +256,17 @@ export class AccessTokenManager { ? {} : { principal: { ...createdBy } }), }; - await this.updateActive(record.id, true); - await this.storage.set(recordKey(record.id), JSON.stringify(record)); + try { + await this.updateActive(record.id, true); + await this.storage.set(recordKey(record.id), JSON.stringify(record)); + } catch (error) { + // No lookup write has started, so this secret can never authenticate. + // A failed record or reservation write may still have committed: revoke + // whatever metadata exists and release the reservation even on a miss. + const revoked = await this.revoke(record.id, record.createdBy); + if (!revoked) await this.updateActive(record.id, false); + throw error; + } // A failed lookup write may have committed. Keep its reservation and // metadata, so a manager can revoke it without ever returning the secret. await this.storage.set( diff --git a/src/artifacts/connector.ts b/src/artifacts/connector.ts index 22dec622..40ea562b 100644 --- a/src/artifacts/connector.ts +++ b/src/artifacts/connector.ts @@ -10,6 +10,7 @@ import { api } from "../connectors/api.js"; import { callerOf } from "../connector-caller.js"; import { ConnectorCallError } from "../errors.js"; +import { resolveTheme, type ResolvedTheme } from "../branding.js"; import type { Connector, ConnectorContext, JsonSchema } from "../types.js"; import { buildFrameDocument, frameCsp, type ArtifactGlobal } from "./document.js"; import { buildGuide, GUIDE_SUMMARY } from "./guide.js"; @@ -114,6 +115,9 @@ export async function runRenderCheck( input: { document: string; csp: string; kind: ArtifactKind }, callSignal: AbortSignal | undefined, ): Promise { + if (callSignal?.aborted) { + return { ok: false, unavailable: "The render check's call was cancelled; nothing was saved." }; + } const controller = new AbortController(); let timer: ReturnType | undefined; const onAbort = () => controller.abort(callSignal?.reason); @@ -223,6 +227,8 @@ export interface ArtifactsConnectorOptions { renderCheck?: ArtifactRenderCheck; /** Where pages are served, when not the deployment's own origin. */ origin?: string; + /** Resolved deployment theme, available after the module binds to core. */ + theme?: () => ResolvedTheme | undefined; } export function artifactsConnector(options: ArtifactsConnectorOptions): Connector { @@ -258,6 +264,7 @@ export function artifactsConnector(options: ArtifactsConnectorOptions): Connecto document: buildFrameDocument({ kind: head.kind, source, + theme: options.theme?.() ?? resolveTheme(), global: globalFor(artifactId, head, head.view, liveOf(head), false), data, }), @@ -556,6 +563,7 @@ export function artifactsConnector(options: ArtifactsConnectorOptions): Connecto document: buildFrameDocument({ kind: args.kind, source: args.source, + theme: options.theme?.() ?? resolveTheme(), global: { id: "preview", title: "Preview", diff --git a/src/artifacts/module.ts b/src/artifacts/module.ts index 4601b497..0c8049e4 100644 --- a/src/artifacts/module.ts +++ b/src/artifacts/module.ts @@ -1,5 +1,6 @@ // artifacts(): the typed module a deployment passes as `ConnectaConfig.artifacts`. +import { resolveBranding, type ResolvedTheme } from "../branding.js"; import type { ArtifactsModule } from "../module-contracts.js"; import { artifactsConnector, type ArtifactRenderCheck } from "./connector.js"; import { ArtifactOperations } from "./operations.js"; @@ -86,17 +87,22 @@ export function artifacts(options: ArtifactsOptions): RefreshableArtifacts { limits, }); const refresh = new ArtifactRefreshService(operations); + let theme: ResolvedTheme | undefined; const connector = artifactsConnector({ operations, refresh, allowlist, limits, + theme: () => theme, ...(options.renderCheck ? { renderCheck: options.renderCheck } : {}), }); return Object.freeze({ connector, handle: artifactRoutes({ operations, allowlist }), - bindRefresh: (runtime: ArtifactRefreshRuntime) => refresh.bind(runtime), + bindRefresh: (runtime: ArtifactRefreshRuntime) => { + refresh.bind(runtime); + theme = resolveBranding(runtime.branding).theme; + }, refresh: (id: string, by: import("./types.js").ArtifactActor) => refresh.run(id, { manual: by }), runDue: () => refresh.runDue(), }); diff --git a/src/artifacts/operations.ts b/src/artifacts/operations.ts index 7936529a..e3b4456f 100644 --- a/src/artifacts/operations.ts +++ b/src/artifacts/operations.ts @@ -1123,9 +1123,11 @@ export class ArtifactOperations { let after = options.cursor; let scanned = 0; for (;;) { + const limit = Math.min(100, LIST_SCAN_LIMIT - scanned); + const previous = after; const page = await this.#store.heads({ ...(after === undefined ? {} : { after }), - limit: Math.min(100, LIST_SCAN_LIMIT - scanned), + limit, }); for (const item of page.heads) { scanned++; @@ -1143,8 +1145,15 @@ export class ArtifactOperations { } } if (page.next === undefined) return { ok: true, items }; + if (previous !== undefined && page.next <= previous) { + return fail("unavailable", "Artifact head scan did not advance; retry the listing."); + } + // Listed keys can disappear before the store reads them. Its cursor + // still consumed the whole page, including those missing head records. + after = page.next; + scanned += Math.max(0, limit - page.heads.length); if (scanned >= LIST_SCAN_LIMIT) { - return after === undefined ? { ok: true, items } : { ok: true, items, nextCursor: after }; + return { ok: true, items, nextCursor: after }; } } } diff --git a/src/artifacts/refresh.ts b/src/artifacts/refresh.ts index 4dff4907..c1fb47ed 100644 --- a/src/artifacts/refresh.ts +++ b/src/artifacts/refresh.ts @@ -34,6 +34,8 @@ export interface ArtifactRefreshRuntime { /** The core's bounded read-only program runner; no sandbox code enters this subpath. */ execute(program: string, owner: NonNullable["owner"], signal: AbortSignal): Promise<{ content: { type: string; text?: string }[]; isError?: boolean }>; claimMs: number; + /** UI branding shared by the viewer and every render check. */ + branding?: import("../types.js").ConnectaBranding; } export type RefreshOutcome = diff --git a/src/auth/downstream-oauth.ts b/src/auth/downstream-oauth.ts index 22774d0f..1b64626b 100644 --- a/src/auth/downstream-oauth.ts +++ b/src/auth/downstream-oauth.ts @@ -476,6 +476,8 @@ interface OAuthRefreshFlight { * letting a contender redeem the retired token. */ acceptedTokens?: OAuthTokens; + /** SDK and cancellation recovery join one commit of this accepted answer. */ + persistence?: Promise; persist: (tokens: OAuthTokens) => Promise; } @@ -738,7 +740,7 @@ export class OAuthRefreshCoordinator { stopObservingOwnerAbort: () => {}, mutationId: {}, writing: false, - persist: (tokens) => provider.saveTokens(tokens), + persist: (tokens) => provider.saveAcceptedRefreshTokens(tokens, generation, flight), }; this.flights.set(generation, flight); this.advanceStateRevision(); @@ -1284,6 +1286,7 @@ export class KvOAuthProvider implements OAuthClientProvider { issuer?: string, writtenAt?: number, binding?: string, + commitAcceptedRefresh = false, ): Promise { const generation = await this.writeGeneration(); await this.storeInGeneration(key, generation, () => { @@ -1298,7 +1301,7 @@ export class KvOAuthProvider implements OAuthClientProvider { return isModernGeneration(generation) || issuer !== undefined ? JSON.stringify(stored) : serializeLegacy(value); - }); + }, undefined, commitAcceptedRefresh); } /** @@ -1314,9 +1317,10 @@ export class KvOAuthProvider implements OAuthClientProvider { generation: string, serialize: () => string, expected?: string, + commitAcceptedRefresh = false, ): Promise { if ( - this.signal?.aborted || + (!commitAcceptedRefresh && this.signal?.aborted) || generation.startsWith(RESETTING_GENERATION_PREFIX) || generation.startsWith(DISCONNECTED_GENERATION_PREFIX) || (await this.generation()) !== generation @@ -1332,7 +1336,7 @@ export class KvOAuthProvider implements OAuthClientProvider { sealed: await this.sealer.seal(physicalKey, plaintext), } satisfies SealedOAuthValue) : plaintext; - if (this.signal?.aborted) return; + if (!commitAcceptedRefresh && this.signal?.aborted) return; if (expected === undefined) { await this.storage.set(physicalKey, serialized); } else if (!(await this.replaceExactly(physicalKey, expected, serialized))) { @@ -1342,7 +1346,7 @@ export class KvOAuthProvider implements OAuthClientProvider { // before this set, remove the now-unreachable residue ourselves. The epoch // key already provides correctness; this second check is physical hygiene. const current = await this.generation(); - if (current !== generation || this.signal?.aborted) { + if (current !== generation || (!commitAcceptedRefresh && this.signal?.aborted)) { try { // On cancellation the generation may still be active. A newer flow // could have written this key while the old storage set was pending. @@ -1812,13 +1816,46 @@ export class KvOAuthProvider implements OAuthClientProvider { async saveTokens( tokens: OAuthTokens, ctx?: OAuthClientInformationContext, + ): Promise { + const owned = this.refreshFlight; + if (owned?.flight.acceptedTokens !== undefined) { + await this.persistAcceptedTokens(tokens, ctx, owned); + } else { + await this.persistTokens(tokens, ctx, false, owned); + } + } + + /** @internal Commit an already redeemed rotation even after its request leaves. */ + async saveAcceptedRefreshTokens( + tokens: OAuthTokens, + generation: string, + flight: OAuthRefreshFlight, + ): Promise { + await this.persistAcceptedTokens(tokens, undefined, { generation, flight }); + } + + private persistAcceptedTokens( + tokens: OAuthTokens, + ctx: OAuthClientInformationContext | undefined, + owned: { generation: string; flight: OAuthRefreshFlight }, + ): Promise { + // The SDK can save the same response while cancellation recovery is + // writing it. A second write could land after the next rotation, so both + // paths await this exact flight's one commit, including its failure. + return owned.flight.persistence ??= this.persistTokens(tokens, ctx, true, owned); + } + + private async persistTokens( + tokens: OAuthTokens, + ctx: OAuthClientInformationContext | undefined, + commitAcceptedRefresh: boolean, + owned: { generation: string; flight: OAuthRefreshFlight } | undefined, ): Promise { // A retired flight (the owner was cancelled or superseded after the // token response) still writes: the authorization server has already // consumed the old refresh token, so dropping the rotated one would leave a // dead credential. writeValue itself refuses when the generation moved. // Only the coordinator bookkeeping belongs to the exact live flight. - const owned = this.refreshFlight; const coordinated = owned !== undefined && this.refreshCoordinator?.beginMutation(owned.generation, owned.flight) === true; @@ -1828,6 +1865,9 @@ export class KvOAuthProvider implements OAuthClientProvider { tokens, (value) => JSON.stringify(value), ctx?.issuer, + undefined, + undefined, + commitAcceptedRefresh, ); if (coordinated) this.refreshCoordinator?.succeedMutation(owned.generation, owned.flight); this.refreshFailure = undefined; diff --git a/src/connectors/api.ts b/src/connectors/api.ts index 386c1797..06576d69 100644 --- a/src/connectors/api.ts +++ b/src/connectors/api.ts @@ -128,7 +128,16 @@ function checkToolContract(id: string, tool: ApiTool): void { /** A static connector; every tool passes {@link checkToolContract} first. */ export function api(id: string, opts: ApiOptions): Connector { - for (const t of opts.tools) checkToolContract(id, t); + const names = new Set(); + for (const tool of opts.tools) { + if (names.has(tool.name)) { + throw new Error( + `api() tool "${id}.${tool.name}" is declared more than once; discovery and dispatch must use one definition.`, + ); + } + names.add(tool.name); + checkToolContract(id, tool); + } const defs: ToolDef[] = opts.tools.map((t) => ({ name: t.name, description: t.description, diff --git a/src/connectors/remote-mcp.ts b/src/connectors/remote-mcp.ts index 3fb66e42..9cba3f32 100644 --- a/src/connectors/remote-mcp.ts +++ b/src/connectors/remote-mcp.ts @@ -1631,15 +1631,20 @@ export function remoteMcp(id: string, opts: RemoteMcpOptions): Connector { const leased = state.transport; const t = (leased ?? buildTransport(ctx, provider)) as StreamableHTTPClientTransport; - if (callbackParams !== undefined) { - await t.finishAuth(callbackParams); - } else { - await t.finishAuth(code); + try { + if (callbackParams !== undefined) { + await t.finishAuth(callbackParams); + } else { + await t.finishAuth(code); + } + await provider.clearPending(); + // Reset so the next use reconnects with the freshly stored tokens. + closeHalf(state); + } finally { + // This exchange-only transport has no lease in the request scope. + // It must close even when redemption or pending-state cleanup fails. + if (!leased) detach(closeConnection(null, t, ctx.logger)); } - await provider.clearPending(); - // Reset so the next use reconnects with the freshly stored tokens. - closeHalf(state); - if (!leased) detach(closeConnection(null, t, ctx.logger)); }, }; diff --git a/src/index.ts b/src/index.ts index 44a512f8..aad86c72 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1032,6 +1032,7 @@ export function createConnecta(config: ConnectaConfig): Connecta { registry.scoped({ connectorIds: [] }), config.publicUrl!, executor, logger, undefined, refreshConfig, ); config.artifacts.bindRefresh({ + ...(config.ui?.branding ? { branding: config.ui.branding } : {}), claimMs: refreshRunner.claimMs, execute: async (program, owner, signal) => { if (!owner) throw new Error("Refresh owner is missing; reconfigure this program."); diff --git a/src/operator-ui/app/artifacts.tsx b/src/operator-ui/app/artifacts.tsx index 82fb71be..01fb76b9 100644 --- a/src/operator-ui/app/artifacts.tsx +++ b/src/operator-ui/app/artifacts.tsx @@ -10,6 +10,7 @@ import { productDescription } from "./config.js"; import { Badge, CopyButton, NoticeLine, StateBlock } from "./parts.js"; import { loadArtifacts, + markArtifactFrameConfined, retryCollection, setArtifactArchived, setArtifactQuery, @@ -156,6 +157,7 @@ function ArtifactFrame({ view }: { view: UiArtifactView }) { policy.httpEquiv = "Content-Security-Policy"; policy.content = "frame-src 'none'"; document.head.append(policy); + markArtifactFrameConfined(); element.contentWindow?.postMessage({ type: "document", html: view.document }, "*"); }; window.addEventListener("message", onMessage); diff --git a/src/operator-ui/app/store.ts b/src/operator-ui/app/store.ts index d3694ab4..f6121e12 100644 --- a/src/operator-ui/app/store.ts +++ b/src/operator-ui/app/store.ts @@ -705,8 +705,11 @@ async function artifactRead( return res; } +let artifactRevision = 0; export async function loadArtifacts(reset: boolean): Promise { - const current = fence(); + const identityCurrent = fence(); + const revision = ++artifactRevision; + const current = () => identityCurrent() && revision === artifactRevision; set({ artifactPhase: "loading", artifactNotice: null, @@ -745,7 +748,19 @@ export async function loadArtifacts(reset: boolean): Promise { }); } +let artifactFrameConfined = false; + +/** The frame navigation policy cannot be relaxed within this document. */ +export function markArtifactFrameConfined(): void { + artifactFrameConfined = true; +} + async function loadArtifactView(): Promise { + if (artifactFrameConfined) { + // A replacement identity needs a fresh bootstrap and a fresh CSP policy. + window.location.reload(); + return; + } const current = fence(); const request = artifactViewRequest(window.location.pathname, window.location.search); set({ artifactPhase: "loading", artifactNotice: null }); @@ -1041,13 +1056,25 @@ export async function refreshConnector(id: string, quiet = false): Promise /* Access tokens ----------------------------------------------------------- */ +let tokenRevision = 0; export async function loadAccessTokens(): Promise { - const current = fence(); + const identityCurrent = fence(); + const revision = ++tokenRevision; + const current = () => identityCurrent() && revision === tokenRevision; + const existing = new Map(state.tokens.map(token => [token.id, token])); set({ tokenPhase: "loading", tokenNotice: null }); try { const payload = await operatorRequest("/ui/access-tokens", "GET", current); if (!current()) return; - set({ tokenPhase: "ready", tokens: payload?.accessTokens ?? [] }); + const tokens = payload?.accessTokens ?? []; + // Creation, rename and revoke replace record objects. Prefer records + // changed locally since this read began over its possibly older snapshot. + const changed = state.tokens.filter(token => existing.get(token.id) !== token); + const changedIds = new Set(changed.map(token => token.id)); + set({ tokenPhase: "ready", tokens: [ + ...changed, + ...tokens.filter(token => !changedIds.has(token.id)), + ] }); } catch { if (!current()) return; set({ diff --git a/src/operator-ui/generated.ts b/src/operator-ui/generated.ts index 547902f3..05a90e6a 100644 --- a/src/operator-ui/generated.ts +++ b/src/operator-ui/generated.ts @@ -1,4 +1,4 @@ // Generated by scripts/build-operator-ui.mjs. Do not edit. // Source: src/operator-ui/app/main.tsx and src/operator-ui/browser.css. export const OPERATOR_UI_CSS: string = "/* src/operator-ui/tokens.css */\n:root {\n color-scheme: light;\n --accent: #2f5fe0;\n --radius: 10px;\n --sans:\n ui-sans-serif,\n system-ui,\n -apple-system,\n \"Segoe UI\",\n Roboto,\n \"Helvetica Neue\",\n Arial,\n sans-serif;\n --mono:\n ui-monospace,\n \"SF Mono\",\n Menlo,\n Monaco,\n \"Cascadia Code\",\n Consolas,\n monospace;\n --bg: #f6f7f9;\n --surface: #ffffff;\n --surface-2: #f1f3f6;\n --border: #e2e5ea;\n --border-strong: #cdd2da;\n --text: #131820;\n --muted: #5b6472;\n --ok: #12734a;\n --warn: #9a5b06;\n --danger: #bb3226;\n --on-accent: #ffffff;\n --link: var(--accent);\n --tint: color-mix(in srgb, var(--accent) 8%, var(--surface));\n --shell: 68rem;\n --pad: 1.25rem;\n --gap: 1rem;\n}\n@media (prefers-color-scheme: dark) {\n html:not([data-scheme=light]) {\n color-scheme: dark;\n }\n}\nhtml[data-scheme=dark] {\n color-scheme: dark;\n}\n@media (prefers-color-scheme: dark) {\n html:not([data-scheme=light]) {\n --bg: #0d1016;\n --surface: #151a21;\n --surface-2: #1c222c;\n --border: #262d39;\n --border-strong: #38414f;\n --text: #e6eaf1;\n --muted: #97a1b2;\n --ok: #4cc48c;\n --warn: #e2a33f;\n --danger: #f4776c;\n --link: color-mix(in srgb, var(--accent) 55%, #ffffff);\n --tint: color-mix(in srgb, var(--accent) 16%, var(--surface));\n }\n}\nhtml[data-scheme=dark] {\n --bg: #0d1016;\n --surface: #151a21;\n --surface-2: #1c222c;\n --border: #262d39;\n --border-strong: #38414f;\n --text: #e6eaf1;\n --muted: #97a1b2;\n --ok: #4cc48c;\n --warn: #e2a33f;\n --danger: #f4776c;\n --link: color-mix(in srgb, var(--accent) 55%, #ffffff);\n --tint: color-mix(in srgb, var(--accent) 16%, var(--surface));\n}\n\n/* src/operator-ui/page.css */\n* {\n box-sizing: border-box;\n}\nhtml {\n background: var(--bg);\n color: var(--text);\n font-family: var(--sans);\n font-size: 16px;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n}\nbody {\n margin: 0;\n min-height: 100vh;\n}\n:is(h1, h2, h3, p, ul, ol) {\n margin: 0;\n padding: 0;\n}\n:is(h1, h2, h3) {\n font-weight: 600;\n line-height: 1.3;\n}\nh1 {\n font-size: 1.5rem;\n letter-spacing: -.01em;\n}\nh2 {\n font-size: 1rem;\n}\n:is(ul, ol) {\n list-style: none;\n}\na {\n color: var(--link);\n text-decoration-thickness: 1px;\n text-underline-offset: 2px;\n}\nbutton,\ninput {\n font: inherit;\n}\n:is(a, button, input, summary):focus-visible {\n border-radius: calc(var(--radius) / 2);\n outline: 2px solid var(--link);\n outline-offset: 2px;\n}\n.skip-link {\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n left: var(--pad);\n padding: .5rem .75rem;\n position: fixed;\n top: -4rem;\n z-index: 10;\n}\n.skip-link:focus {\n top: var(--pad);\n}\n.shell {\n margin: 0 auto;\n max-width: var(--shell);\n padding-left: var(--pad);\n padding-right: var(--pad);\n}\n.cap,\n.meta {\n color: var(--muted);\n font-size: .875rem;\n}\n.mono {\n font-family: var(--mono);\n font-size: .8125rem;\n}\n.visually-hidden {\n clip-path: inset(50%);\n height: 1px;\n overflow: hidden;\n position: absolute;\n white-space: nowrap;\n width: 1px;\n}\n.masthead {\n align-items: center;\n background: var(--surface);\n border-bottom: 1px solid var(--border);\n display: flex;\n gap: var(--gap);\n justify-content: space-between;\n max-width: none;\n padding-bottom: .75rem;\n padding-top: .75rem;\n position: sticky;\n top: 0;\n z-index: 5;\n}\n.masthead-inner {\n align-items: center;\n display: flex;\n gap: var(--gap);\n justify-content: space-between;\n margin: 0 auto;\n max-width: var(--shell);\n width: 100%;\n}\n.mast-nav,\n.mast-actions,\n.page-nav,\n.session-actions {\n align-items: center;\n display: flex;\n flex-wrap: wrap;\n gap: .25rem;\n}\n.mast-nav {\n gap: var(--gap);\n}\n.mast-actions {\n gap: var(--gap);\n}\n.navlink {\n background: none;\n border: 0;\n border-radius: calc(var(--radius) - 2px);\n color: var(--muted);\n cursor: pointer;\n font-size: .9375rem;\n padding: .35rem .6rem;\n text-decoration: none;\n}\n.navlink:hover {\n background: var(--surface-2);\n color: var(--text);\n}\n.navlink[aria-current=page] {\n background: var(--tint);\n color: var(--link);\n font-weight: 500;\n}\n.brand,\n.product {\n padding-left: 0;\n padding-right: 0;\n text-decoration: none;\n}\n.brand {\n color: var(--text);\n font-weight: 600;\n}\n.brand:hover,\n.product:hover {\n background: none;\n}\n.product {\n color: var(--muted);\n}\n.page {\n padding-bottom: 4rem;\n padding-top: 2rem;\n}\n.lead {\n margin-bottom: 1.5rem;\n}\n.lead-copy {\n color: var(--muted);\n display: grid;\n gap: .5rem;\n margin-top: .35rem;\n}\n.lead-copy p {\n max-width: 60ch;\n}\n.btn {\n background: var(--surface);\n border: 1px solid var(--border-strong);\n border-radius: calc(var(--radius) - 2px);\n color: var(--text);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n font-size: .875rem;\n gap: .35rem;\n padding: .35rem .7rem;\n text-decoration: none;\n white-space: nowrap;\n}\n.btn:hover {\n background: var(--surface-2);\n}\n.btn:disabled {\n cursor: not-allowed;\n opacity: .55;\n}\n.btn.primary {\n background: var(--accent);\n border-color: var(--accent);\n color: var(--on-accent);\n}\n.btn.primary:hover {\n background: color-mix(in srgb, var(--accent) 88%, black);\n}\n.btn.danger {\n color: var(--danger);\n}\n.btn.danger:hover {\n background: color-mix(in srgb, var(--danger) 10%, var(--surface));\n}\n.btn.quiet {\n background: none;\n border-color: var(--border);\n color: var(--muted);\n}\n.btn.quiet:hover {\n background: var(--surface-2);\n color: var(--text);\n}\n.badge {\n align-items: center;\n background: var(--surface-2);\n border-radius: 999px;\n color: var(--muted);\n display: inline-flex;\n font-size: .75rem;\n gap: .3rem;\n line-height: 1.6;\n padding: .1rem .5rem;\n white-space: nowrap;\n}\n.badge.ok {\n background: color-mix(in srgb, var(--ok) 12%, var(--surface));\n color: var(--ok);\n}\n.badge.warn {\n background: color-mix(in srgb, var(--warn) 14%, var(--surface));\n color: var(--warn);\n}\n.badge.danger {\n background: color-mix(in srgb, var(--danger) 12%, var(--surface));\n color: var(--danger);\n}\n.badge.accent {\n background: var(--tint);\n color: var(--link);\n}\n.msg {\n background: color-mix(in srgb, var(--danger) 8%, var(--surface));\n border: 1px solid color-mix(in srgb, var(--danger) 28%, var(--surface));\n border-radius: calc(var(--radius) - 2px);\n color: var(--danger);\n font-size: .875rem;\n padding: .5rem .75rem;\n}\n.status-page {\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n display: grid;\n gap: .75rem;\n margin: 1.5rem auto 0;\n max-width: 34rem;\n padding: 1.5rem;\n}\n.status-head {\n align-items: center;\n display: flex;\n gap: .6rem;\n}\n.status-mark {\n align-items: center;\n background: var(--surface-2);\n border-radius: 50%;\n color: var(--muted);\n display: inline-flex;\n flex: none;\n height: 2rem;\n justify-content: center;\n width: 2rem;\n}\n.status-mark svg {\n height: 1.25rem;\n width: 1.25rem;\n}\n.status-mark.ok {\n background: color-mix(in srgb, var(--ok) 14%, var(--surface));\n color: var(--ok);\n}\n.status-mark.danger {\n background: color-mix(in srgb, var(--danger) 12%, var(--surface));\n color: var(--danger);\n}\n.status-label {\n color: var(--muted);\n font-size: .875rem;\n font-weight: 600;\n}\n.status-label.ok {\n color: var(--ok);\n}\n.status-label.danger {\n color: var(--danger);\n}\n.status-page h1 {\n overflow-wrap: anywhere;\n}\n.status-copy {\n color: var(--muted);\n}\n.status-actions {\n display: flex;\n flex-wrap: wrap;\n gap: .5rem;\n margin-top: .25rem;\n}\n.status-details {\n border-top: 1px solid var(--border);\n color: var(--muted);\n font-size: .875rem;\n margin-top: .25rem;\n padding-top: .75rem;\n}\n.status-details > summary {\n cursor: pointer;\n width: fit-content;\n}\n.status-details > p {\n margin-top: .5rem;\n}\n.status-details pre {\n background: var(--surface-2);\n border: 1px solid var(--border);\n border-radius: calc(var(--radius) / 2);\n color: var(--text);\n font-family: var(--mono);\n font-size: .75rem;\n line-height: 1.55;\n margin: .5rem 0 0;\n max-height: 18rem;\n overflow: auto;\n overflow-wrap: anywhere;\n padding: .5rem .6rem;\n white-space: pre-wrap;\n}\n@media (max-width: 40rem) {\n :root {\n --pad: 1rem;\n }\n .status-page {\n margin-top: 0;\n padding: 1.25rem var(--pad);\n }\n}\n\n/* src/operator-ui/browser.css */\n.masthead-inner {\n min-height: calc(.9375rem * 1.5 + .7rem);\n}\ninput:not([type=checkbox], [type=radio], [type=hidden]) {\n background: var(--surface);\n border: 1px solid var(--border-strong);\n border-radius: calc(var(--radius) - 2px);\n color: var(--text);\n min-height: 2.25rem;\n padding: .3rem .6rem;\n width: 100%;\n}\ninput::placeholder {\n color: var(--muted);\n}\n.row {\n align-items: center;\n display: flex;\n flex-wrap: wrap;\n gap: .5rem;\n}\n.row input:not([type=checkbox], [type=radio]) {\n flex: 1 1 14rem;\n width: auto;\n}\ninput:disabled {\n opacity: .6;\n}\n.check {\n align-items: center;\n cursor: pointer;\n display: inline-flex;\n gap: .4rem;\n}\n.check input {\n accent-color: var(--accent);\n margin: 0;\n}\n.actions {\n display: flex;\n flex-wrap: wrap;\n gap: .5rem;\n}\n.dot {\n border-radius: 50%;\n display: inline-block;\n flex: none;\n height: .5rem;\n width: .5rem;\n background: var(--muted);\n}\n.dot.ok,\n.dot.success,\n.dot.approved {\n background: var(--ok);\n}\n.dot.auth_required,\n.dot.warn,\n.dot.paused {\n background: var(--warn);\n}\n.dot.error,\n.dot.timeout {\n background: var(--danger);\n}\n.dot.loading {\n background: var(--border-strong);\n}\n.section {\n display: grid;\n gap: .75rem;\n margin-top: 1.75rem;\n}\n.section-head {\n align-items: baseline;\n display: flex;\n gap: var(--gap);\n justify-content: space-between;\n}\n.unavailable {\n background: var(--surface);\n border: 1px dashed var(--border-strong);\n border-radius: var(--radius);\n color: var(--muted);\n padding: 1.5rem var(--pad);\n}\n.state-block {\n align-items: center;\n background: var(--surface);\n border: 1px dashed var(--border-strong);\n border-radius: var(--radius);\n color: var(--muted);\n display: flex;\n flex-direction: column;\n gap: .5rem;\n padding: 1.75rem var(--pad);\n text-align: center;\n}\n.state-block.error {\n border-color: color-mix(in srgb, var(--danger) 35%, var(--border));\n border-style: solid;\n}\n.state-title {\n color: var(--text);\n font-weight: 600;\n}\n.state-copy {\n max-width: 52ch;\n}\n.state-block .btn {\n margin-top: .25rem;\n}\n.collection {\n display: grid;\n gap: .75rem;\n}\n.msg.warn {\n background: color-mix(in srgb, var(--warn) 9%, var(--surface));\n border-color: color-mix(in srgb, var(--warn) 32%, var(--surface));\n color: var(--text);\n}\n.error-notice {\n color: var(--danger);\n}\n.notice:empty {\n block-size: 0;\n margin: 0;\n}\n.summary {\n color: var(--muted);\n font-size: .875rem;\n}\n.summary .sep {\n opacity: .5;\n}\n.summary .warn {\n color: var(--warn);\n}\n.summary .danger {\n color: var(--danger);\n}\n.endpoint {\n align-items: center;\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n display: flex;\n gap: .75rem;\n justify-content: space-between;\n padding: .5rem .5rem .5rem .75rem;\n}\n.endpoint code {\n flex: 1 1 auto;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n.endpoints,\n.endpoint-block {\n display: grid;\n gap: .35rem;\n}\n.endpoints {\n gap: .6rem;\n}\n.endpoint-label {\n flex: none;\n}\n.setup-list {\n display: grid;\n gap: .6rem;\n margin-top: .5rem;\n}\n.setup-item {\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: calc(var(--radius) - 2px);\n display: grid;\n gap: .35rem;\n padding: .5rem .6rem .6rem .75rem;\n}\n.setup-head {\n align-items: center;\n display: flex;\n gap: .5rem;\n justify-content: space-between;\n}\n.setup-code,\n.fix-prompt-text {\n background: var(--surface-2);\n border-radius: calc(var(--radius) / 2);\n color: var(--text);\n font-family: var(--mono);\n font-size: .75rem;\n line-height: 1.55;\n margin: 0;\n overflow-wrap: anywhere;\n padding: .5rem .6rem;\n white-space: pre-wrap;\n}\n.fix-prompt {\n align-items: baseline;\n display: flex;\n flex-wrap: wrap;\n gap: .35rem .75rem;\n}\n.fix-prompt > details {\n flex: 1 1 12rem;\n}\n.fix-prompt > details[open] {\n flex-basis: 100%;\n}\n.fix-prompt-preview > .meta {\n margin-top: .4rem;\n}\n.fix-prompt-preview .fix-prompt-text {\n border: 1px solid var(--border);\n margin-top: .4rem;\n max-height: 18rem;\n overflow: auto;\n}\n.rows {\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n overflow: hidden;\n}\n.rows > * + * {\n border-top: 1px solid var(--border);\n}\n.conn.open > .conn-head {\n background: var(--surface-2);\n}\n.conn-head {\n align-items: center;\n display: flex;\n gap: .75rem;\n justify-content: space-between;\n padding: .7rem var(--pad);\n position: relative;\n}\n.conn-head:hover {\n background: var(--surface-2);\n}\n.conn-main {\n align-items: center;\n display: flex;\n gap: .6rem;\n min-width: 0;\n}\n.conn-name {\n font-size: 1rem;\n font-weight: 500;\n overflow-wrap: anywhere;\n}\n.conn-toggle {\n background: none;\n border: 0;\n color: var(--text);\n cursor: pointer;\n font: inherit;\n padding: 0;\n text-align: left;\n}\n.conn-toggle::after {\n content: \"\";\n inset: 0;\n position: absolute;\n}\n.conn-toggle:focus-visible {\n outline: none;\n}\n.conn-toggle:focus-visible::after {\n border-radius: calc(var(--radius) - 2px);\n outline: 2px solid var(--link);\n outline-offset: -2px;\n}\n.conn-id {\n color: var(--muted);\n}\n.conn-badges {\n align-items: center;\n display: flex;\n flex-wrap: wrap;\n gap: .35rem;\n justify-content: flex-end;\n}\n.conn-caret {\n border-right: 1.5px solid var(--muted);\n border-bottom: 1.5px solid var(--muted);\n flex: none;\n height: .4rem;\n rotate: -45deg;\n transform-origin: center;\n width: .4rem;\n}\n.conn.open .conn-caret {\n rotate: 45deg;\n}\n.conn-body {\n background: var(--surface);\n border-top: 1px solid var(--border);\n display: grid;\n gap: .75rem;\n padding: var(--pad);\n}\n.conn-body[hidden] {\n display: none;\n}\n.conn-note {\n color: var(--muted);\n max-width: 70ch;\n}\n.subcard {\n background: var(--surface-2);\n border: 1px solid var(--border);\n border-radius: calc(var(--radius) - 2px);\n display: grid;\n gap: .5rem;\n padding: .75rem;\n}\n.subcard-head {\n align-items: baseline;\n display: flex;\n flex-wrap: wrap;\n gap: .5rem;\n justify-content: space-between;\n}\n.subcard-head h3 {\n font-size: .9375rem;\n}\n.confirm {\n background: color-mix(in srgb, var(--danger) 6%, var(--surface));\n border: 1px solid color-mix(in srgb, var(--danger) 28%, var(--surface));\n border-radius: calc(var(--radius) - 2px);\n display: grid;\n gap: .5rem;\n padding: .6rem .75rem;\n}\n.confirm p {\n max-width: 70ch;\n}\ndetails > .subcard,\ndetails > .tool-list {\n margin-top: .5rem;\n}\n.tool-list {\n display: grid;\n gap: .4rem;\n max-height: 22rem;\n overflow: auto;\n}\n.filter {\n flex: 0 1 18rem;\n min-width: 10rem;\n width: auto;\n}\n.connector-drift {\n display: grid;\n gap: .15rem;\n}\n.tool {\n display: grid;\n gap: .1rem;\n border-left: 2px solid var(--border-strong);\n padding-left: .6rem;\n}\n.tool code {\n font-family: var(--mono);\n font-size: .8125rem;\n overflow-wrap: anywhere;\n}\n.tool-head {\n align-items: center;\n display: flex;\n flex-wrap: wrap;\n gap: .25rem .5rem;\n}\n.tool-legend {\n margin-bottom: .15rem;\n}\n.td {\n color: var(--muted);\n font-size: .8125rem;\n}\ndetails > summary {\n cursor: pointer;\n}\n.disclosure {\n color: var(--link);\n font-size: .875rem;\n list-style: none;\n}\n.disclosure::-webkit-details-marker {\n display: none;\n}\n.disclosure::before {\n content: \"▸ \";\n}\ndetails[open] > .disclosure::before {\n content: \"▾ \";\n}\n.drift-counts {\n display: grid;\n gap: .5rem;\n grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));\n margin-top: .5rem;\n}\n.drift-count {\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: calc(var(--radius) - 2px);\n display: grid;\n gap: .1rem;\n padding: .4rem .6rem;\n}\n.drift-count.flagged {\n border-color: color-mix(in srgb, var(--warn) 45%, var(--surface));\n}\n.drift-count-value {\n font-size: 1.125rem;\n font-weight: 600;\n}\n.drift-count.flagged .drift-count-value {\n color: var(--warn);\n}\n.drift-count-label {\n color: var(--muted);\n font-size: .75rem;\n}\n.credential-fields,\n.credential-field-summary {\n display: grid;\n gap: .5rem;\n}\n.credential-field {\n display: grid;\n gap: .25rem;\n}\n.credential-field label {\n color: var(--muted);\n font-size: .8125rem;\n}\n.credential-field-summary > div {\n display: flex;\n gap: .5rem;\n justify-content: space-between;\n}\n.credential-form {\n display: grid;\n gap: .5rem;\n}\n.credential-form .actions {\n justify-content: flex-end;\n}\n.activity-list {\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n overflow: hidden;\n}\n.activity-list > * + * {\n border-top: 1px solid var(--border);\n}\n.activity-item {\n align-items: center;\n display: grid;\n gap: .5rem var(--gap);\n grid-template-columns: minmax(0, 14rem) minmax(0, 1fr) auto;\n padding: .6rem var(--pad);\n}\n.activity-stamp {\n align-items: center;\n display: flex;\n gap: .6rem;\n min-width: 0;\n}\n.activity-time {\n display: block;\n font-size: .8125rem;\n}\n.activity-actor {\n color: var(--muted);\n font-size: .8125rem;\n}\n.activity-actor-id {\n color: var(--muted);\n}\n.activity-address {\n font-family: var(--mono);\n font-size: .8125rem;\n overflow-wrap: anywhere;\n}\n.activity-detail {\n color: var(--muted);\n font-size: .8125rem;\n}\n.activity-result {\n display: grid;\n gap: .15rem;\n justify-items: end;\n}\n.activity-more {\n justify-self: start;\n}\n.artifact-row {\n align-items: center;\n display: grid;\n gap: .25rem var(--gap);\n grid-template-columns: minmax(0, 1fr) auto;\n padding: .6rem var(--pad);\n}\n.artifact-row .artifact-title {\n font-weight: 600;\n overflow-wrap: anywhere;\n}\n.artifact-row .artifact-meta,\n.artifact-meta {\n color: var(--muted);\n font-size: .8125rem;\n}\n.artifact-badges {\n display: flex;\n flex-wrap: wrap;\n gap: .35rem;\n justify-content: flex-end;\n}\n.artifact-head {\n display: grid;\n gap: .35rem;\n margin-bottom: .75rem;\n}\n.navlink.inline {\n font-size: inherit;\n padding: 0 .2rem;\n}\n.artifact-banner {\n background: color-mix(in srgb, var(--warn) 12%, var(--surface));\n border: 1px solid color-mix(in srgb, var(--warn) 35%, var(--border));\n border-radius: var(--radius);\n color: var(--text);\n padding: .5rem .75rem;\n}\n.artifact-frame {\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n display: block;\n height: calc(100vh - 14rem);\n min-height: 28rem;\n width: 100%;\n}\n.gate-form {\n max-width: 36rem;\n}\n@media (max-width: 40rem) {\n .masthead {\n position: static;\n }\n .masthead-inner {\n flex-wrap: wrap;\n row-gap: .35rem;\n }\n #operatorNav,\n #operatorNav .mast-actions {\n display: contents;\n }\n .session-actions {\n margin-left: auto;\n }\n .page-nav {\n flex: 1 0 100%;\n flex-wrap: nowrap;\n margin-left: -.6rem;\n order: 3;\n overflow-x: auto;\n }\n .conn-head {\n align-items: flex-start;\n flex-direction: column;\n gap: .4rem;\n }\n .conn-badges {\n justify-content: flex-start;\n }\n .endpoint {\n flex-wrap: wrap;\n }\n .section-head {\n flex-wrap: wrap;\n }\n .filter {\n flex: 1 1 100%;\n }\n .activity-item {\n grid-template-columns: minmax(0, 1fr);\n }\n .activity-result {\n justify-items: start;\n }\n .artifact-row {\n grid-template-columns: minmax(0, 1fr);\n }\n .artifact-badges {\n justify-content: flex-start;\n }\n}\n.token-create,\n.token-reveal {\n margin-bottom: 24px;\n}\n.token-create > label {\n display: block;\n margin-bottom: 8px;\n}\n.token-create input {\n flex: 1;\n min-width: 0;\n}\n.token-reveal {\n border: 1px solid var(--rule);\n padding: 20px;\n}\n.token-reveal-head,\n.token-card-head {\n display: flex;\n justify-content: space-between;\n gap: 16px;\n}\n.token-secret {\n overflow-wrap: anywhere;\n margin: 12px 0;\n}\n.token-card {\n border-top: 1px solid var(--rule);\n padding: 20px 0;\n}\n.token-card.revoked {\n color: var(--muted);\n}\n"; -export const OPERATOR_UI_SCRIPT: string = "\"use strict\";\n(() => {\n // node_modules/preact/dist/preact.module.js\n var n;\n var l;\n var u;\n var t;\n var i;\n var r;\n var o;\n var e;\n var f;\n var c;\n var a;\n var s;\n var h;\n var p;\n var v;\n var y;\n var d = {};\n var w = [];\n var _ = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;\n var g = Array.isArray;\n function m(n2, l3) {\n for (var u4 in l3) n2[u4] = l3[u4];\n return n2;\n }\n function b(n2) {\n n2 && n2.parentNode && n2.parentNode.removeChild(n2);\n }\n function k(l3, u4, t3) {\n var i3, r3, o3, e3 = {};\n for (o3 in u4) \"key\" == o3 ? i3 = u4[o3] : \"ref\" == o3 ? r3 = u4[o3] : e3[o3] = u4[o3];\n if (arguments.length > 2 && (e3.children = arguments.length > 3 ? n.call(arguments, 2) : t3), \"function\" == typeof l3 && null != l3.defaultProps) for (o3 in l3.defaultProps) void 0 === e3[o3] && (e3[o3] = l3.defaultProps[o3]);\n return x(l3, e3, i3, r3, null);\n }\n function x(n2, t3, i3, r3, o3) {\n var e3 = { type: n2, props: t3, key: i3, ref: r3, __k: null, __: null, __b: 0, __e: null, __c: null, constructor: void 0, __v: null == o3 ? ++u : o3, __i: -1, __u: 0 };\n return null == o3 && null != l.vnode && l.vnode(e3), e3;\n }\n function S(n2) {\n return n2.children;\n }\n function C(n2, l3) {\n this.props = n2, this.context = l3;\n }\n function $(n2, l3) {\n if (null == l3) return n2.__ ? $(n2.__, n2.__i + 1) : null;\n for (var u4; l3 < n2.__k.length; l3++) if (null != (u4 = n2.__k[l3]) && null != u4.__e) return u4.__e;\n return \"function\" == typeof n2.type ? $(n2) : null;\n }\n function I(n2) {\n if (n2.__P && n2.__d) {\n var u4 = n2.__v, t3 = u4.__e, i3 = [], r3 = [], o3 = m({}, u4);\n o3.__v = u4.__v + 1, l.vnode && l.vnode(o3), q(n2.__P, o3, u4, n2.__n, n2.__P.namespaceURI, 32 & u4.__u ? [t3] : null, i3, null == t3 ? $(u4) : t3, !!(32 & u4.__u), r3), o3.__v = u4.__v, o3.__.__k[o3.__i] = o3, D(i3, o3, r3), u4.__e = u4.__ = null, o3.__e != t3 && P(o3);\n }\n }\n function P(n2) {\n if (null != (n2 = n2.__) && null != n2.__c) return n2.__e = n2.__c.base = null, n2.__k.some(function(l3) {\n if (null != l3 && null != l3.__e) return n2.__e = n2.__c.base = l3.__e;\n }), P(n2);\n }\n function A(n2) {\n (!n2.__d && (n2.__d = true) && i.push(n2) && !H.__r++ || r != l.debounceRendering) && ((r = l.debounceRendering) || o)(H);\n }\n function H() {\n try {\n for (var n2, l3 = 1; i.length; ) i.length > l3 && i.sort(e), n2 = i.shift(), l3 = i.length, I(n2);\n } finally {\n i.length = H.__r = 0;\n }\n }\n function L(n2, l3, u4, t3, i3, r3, o3, e3, f4, c3, a3) {\n var s3, h3, p3, v3, y3, _3, g2 = t3 && t3.__k || w, m3 = l3.length;\n for (f4 = T(u4, l3, g2, f4, m3), s3 = 0; s3 < m3; s3++) null != (p3 = u4.__k[s3]) && (h3 = -1 != p3.__i && g2[p3.__i] || d, p3.__i = s3, _3 = q(n2, p3, h3, i3, r3, o3, e3, f4, c3, a3), v3 = p3.__e, p3.ref && h3.ref != p3.ref && (h3.ref && J(h3.ref, null, p3), a3.push(p3.ref, p3.__c || v3, p3)), null == y3 && null != v3 && (y3 = v3), 4 & p3.__u ? (f4 = j(p3, f4, n2), h3.__e && (h3.__e = null)) : \"function\" == typeof p3.type && void 0 !== _3 ? f4 = _3 : v3 && (f4 = v3.nextSibling), p3.__u &= -7);\n return u4.__e = y3, f4;\n }\n function T(n2, l3, u4, t3, i3) {\n var r3, o3, e3, f4, c3, a3 = u4.length, s3 = a3, h3 = 0;\n for (n2.__k = new Array(i3), r3 = 0; r3 < i3; r3++) null != (o3 = l3[r3]) && \"boolean\" != typeof o3 && \"function\" != typeof o3 ? (\"string\" == typeof o3 || \"number\" == typeof o3 || \"bigint\" == typeof o3 || o3.constructor == String ? o3 = n2.__k[r3] = x(null, o3, null, null, null) : g(o3) ? o3 = n2.__k[r3] = x(S, { children: o3 }, null, null, null) : void 0 === o3.constructor && o3.__b > 0 ? o3 = n2.__k[r3] = x(o3.type, o3.props, o3.key, o3.ref ? o3.ref : null, o3.__v) : n2.__k[r3] = o3, f4 = r3 + h3, o3.__ = n2, o3.__b = n2.__b + 1, e3 = null, -1 != (c3 = o3.__i = O(o3, u4, f4, s3)) && (s3--, (e3 = u4[c3]) && (e3.__u |= 2)), null == e3 || null == e3.__v ? (-1 == c3 && (i3 > a3 ? h3-- : i3 < a3 && h3++), \"function\" != typeof o3.type && (o3.__u |= 4)) : c3 != f4 && (c3 == f4 - 1 ? h3-- : c3 == f4 + 1 ? h3++ : (c3 > f4 ? h3-- : h3++, o3.__u |= 4))) : n2.__k[r3] = null;\n if (s3) for (r3 = 0; r3 < a3; r3++) null != (e3 = u4[r3]) && 0 == (2 & e3.__u) && (e3.__e == t3 && (t3 = $(e3)), K(e3, e3));\n return t3;\n }\n function j(n2, l3, u4) {\n var t3, i3;\n if (\"function\" == typeof n2.type) {\n for (t3 = n2.__k, i3 = 0; t3 && i3 < t3.length; i3++) t3[i3] && (t3[i3].__ = n2, l3 = j(t3[i3], l3, u4));\n return l3;\n }\n n2.__e != l3 && (l3 && n2.type && !l3.parentNode && (l3 = $(n2)), l3 = u4.insertBefore(n2.__e, l3 || null));\n do {\n l3 = l3 && l3.nextSibling;\n } while (null != l3 && 8 == l3.nodeType);\n return l3;\n }\n function O(n2, l3, u4, t3) {\n var i3, r3, o3, e3 = n2.key, f4 = n2.type, c3 = l3[u4], a3 = null != c3 && 0 == (2 & c3.__u);\n if (null === c3 && null == e3 || a3 && e3 == c3.key && f4 == c3.type) return u4;\n if (t3 > (a3 ? 1 : 0)) {\n for (i3 = u4 - 1, r3 = u4 + 1; i3 >= 0 || r3 < l3.length; ) if (null != (c3 = l3[o3 = i3 >= 0 ? i3-- : r3++]) && 0 == (2 & c3.__u) && e3 == c3.key && f4 == c3.type) return o3;\n }\n return -1;\n }\n function z(n2, l3, u4) {\n \"-\" == l3[0] ? n2.setProperty(l3, null == u4 ? \"\" : u4) : n2[l3] = null == u4 ? \"\" : \"number\" != typeof u4 || _.test(l3) ? u4 : u4 + \"px\";\n }\n function N(n2, l3, u4, t3, i3) {\n var r3, o3;\n n: if (\"style\" == l3) if (\"string\" == typeof u4) n2.style.cssText = u4;\n else {\n if (\"string\" == typeof t3 && (n2.style.cssText = t3 = \"\"), t3) for (l3 in t3) u4 && l3 in u4 || z(n2.style, l3, \"\");\n if (u4) for (l3 in u4) t3 && u4[l3] == t3[l3] || z(n2.style, l3, u4[l3]);\n }\n else if (\"o\" == l3[0] && \"n\" == l3[1]) r3 = l3 != (l3 = l3.replace(s, \"$1\")), o3 = l3.toLowerCase(), l3 = o3 in n2 || \"onFocusOut\" == l3 || \"onFocusIn\" == l3 ? o3.slice(2) : l3.slice(2), n2.l || (n2.l = {}), n2.l[l3 + r3] = u4, u4 ? t3 ? u4[a] = t3[a] : (u4[a] = h, n2.addEventListener(l3, r3 ? v : p, r3)) : n2.removeEventListener(l3, r3 ? v : p, r3);\n else {\n if (\"http://www.w3.org/2000/svg\" == i3) l3 = l3.replace(/xlink(H|:h)/, \"h\").replace(/sName$/, \"s\");\n else if (\"width\" != l3 && \"height\" != l3 && \"href\" != l3 && \"list\" != l3 && \"form\" != l3 && \"tabIndex\" != l3 && \"download\" != l3 && \"rowSpan\" != l3 && \"colSpan\" != l3 && \"role\" != l3 && \"popover\" != l3 && l3 in n2) try {\n n2[l3] = null == u4 ? \"\" : u4;\n break n;\n } catch (n3) {\n }\n \"function\" == typeof u4 || (null == u4 || false === u4 && \"-\" != l3[4] ? n2.removeAttribute(l3) : n2.setAttribute(l3, \"popover\" == l3 && 1 == u4 ? \"\" : u4));\n }\n }\n function V(n2) {\n return function(u4) {\n if (this.l) {\n var t3 = this.l[u4.type + n2];\n if (null == u4[c]) u4[c] = h++;\n else if (u4[c] < t3[a]) return;\n return t3(l.event ? l.event(u4) : u4);\n }\n };\n }\n function q(n2, u4, t3, i3, r3, o3, e3, f4, c3, a3) {\n var s3, h3, p3, v3, y3, d3, _3, k3, x2, M, I2, P2, A3, H2, T3, j3, F = u4.type;\n if (void 0 !== u4.constructor) return null;\n 128 & t3.__u && (c3 = !!(32 & t3.__u), o3 = [f4 = u4.__e = t3.__e]), (s3 = l.__b) && s3(u4);\n n: if (\"function\" == typeof F) {\n h3 = e3.length;\n try {\n if (x2 = u4.props, M = F.prototype && F.prototype.render, I2 = (s3 = F.contextType) && i3[s3.__c], P2 = s3 ? I2 ? I2.props.value : s3.__ : i3, t3.__c ? k3 = (p3 = u4.__c = t3.__c).__ = p3.__E : (M ? u4.__c = p3 = new F(x2, P2) : (u4.__c = p3 = new C(x2, P2), p3.constructor = F, p3.render = Q), I2 && I2.sub(p3), p3.state || (p3.state = {}), p3.__n = i3, v3 = p3.__d = true, p3.__h = [], p3._sb = []), M && null == p3.__s && (p3.__s = p3.state), M && null != F.getDerivedStateFromProps && (p3.__s == p3.state && (p3.__s = m({}, p3.__s)), m(p3.__s, F.getDerivedStateFromProps(x2, p3.__s))), y3 = p3.props, d3 = p3.state, p3.__v = u4, v3) M && null == F.getDerivedStateFromProps && null != p3.componentWillMount && p3.componentWillMount(), M && null != p3.componentDidMount && p3.__h.push(p3.componentDidMount);\n else {\n if (M && null == F.getDerivedStateFromProps && x2 !== y3 && null != p3.componentWillReceiveProps && p3.componentWillReceiveProps(x2, P2), u4.__v == t3.__v || !p3.__e && null != p3.shouldComponentUpdate && false === p3.shouldComponentUpdate(x2, p3.__s, P2)) {\n u4.__v != t3.__v && (p3.props = x2, p3.state = p3.__s, p3.__d = false), u4.__e = t3.__e, u4.__k = t3.__k, u4.__k.some(function(n3) {\n n3 && (n3.__ = u4);\n }), w.push.apply(p3.__h, p3._sb), p3._sb = [], p3.__h.length && e3.push(p3), f4 = $(t3);\n break n;\n }\n null != p3.componentWillUpdate && p3.componentWillUpdate(x2, p3.__s, P2), M && null != p3.componentDidUpdate && p3.__h.push(function() {\n p3.componentDidUpdate(y3, d3, _3);\n });\n }\n if (p3.context = P2, p3.props = x2, p3.__P = n2, p3.__e = false, A3 = l.__r, H2 = 0, M) p3.state = p3.__s, p3.__d = false, A3 && A3(u4), s3 = p3.render(p3.props, p3.state, p3.context), w.push.apply(p3.__h, p3._sb), p3._sb = [];\n else do {\n p3.__d = false, A3 && A3(u4), s3 = p3.render(p3.props, p3.state, p3.context), p3.state = p3.__s;\n } while (p3.__d && ++H2 < 25);\n p3.state = p3.__s, null != p3.getChildContext && (i3 = m(m({}, i3), p3.getChildContext())), M && !v3 && null != p3.getSnapshotBeforeUpdate && (_3 = p3.getSnapshotBeforeUpdate(y3, d3)), T3 = null != s3 && s3.type === S && null == s3.key ? E(s3.props.children) : s3, f4 = L(n2, g(T3) ? T3 : [T3], u4, t3, i3, r3, o3, e3, f4, c3, a3), p3.base = u4.__e, u4.__u &= -161, p3.__h.length && e3.push(p3), k3 && (p3.__E = p3.__ = null);\n } catch (n3) {\n if (e3.length = h3, u4.__v = null, c3 || null != o3) {\n if (n3.then) {\n for (u4.__u |= c3 ? 160 : 128; f4 && 8 == f4.nodeType && f4.nextSibling; ) f4 = f4.nextSibling;\n null != o3 && (o3[o3.indexOf(f4)] = null), u4.__e = f4;\n } else if (null != o3) for (j3 = o3.length; j3--; ) b(o3[j3]);\n } else u4.__e = t3.__e;\n null == u4.__k && (u4.__k = t3.__k || []), n3.then || B(u4), l.__e(n3, u4, t3);\n }\n } else null == o3 && u4.__v == t3.__v ? (u4.__k = t3.__k, u4.__e = t3.__e) : f4 = u4.__e = G(t3.__e, u4, t3, i3, r3, o3, e3, c3, a3);\n return (s3 = l.diffed) && s3(u4), 128 & u4.__u ? void 0 : f4;\n }\n function B(n2) {\n n2 && (n2.__c && (n2.__c.__e = true), n2.__k && n2.__k.some(B));\n }\n function D(n2, u4, t3) {\n for (var i3 = 0; i3 < t3.length; i3++) J(t3[i3], t3[++i3], t3[++i3]);\n l.__c && l.__c(u4, n2), n2.some(function(u5) {\n try {\n n2 = u5.__h, u5.__h = [], n2.some(function(n3) {\n n3.call(u5);\n });\n } catch (n3) {\n l.__e(n3, u5.__v);\n }\n });\n }\n function E(n2) {\n return \"object\" != typeof n2 || null == n2 || n2.__b > 0 ? n2 : g(n2) ? n2.map(E) : void 0 !== n2.constructor ? null : m({}, n2);\n }\n function G(u4, t3, i3, r3, o3, e3, f4, c3, a3) {\n var s3, h3, p3, v3, y3, w3, _3, m3 = i3.props || d, k3 = t3.props, x2 = t3.type;\n if (\"svg\" == x2 ? o3 = \"http://www.w3.org/2000/svg\" : \"math\" == x2 ? o3 = \"http://www.w3.org/1998/Math/MathML\" : o3 || (o3 = \"http://www.w3.org/1999/xhtml\"), null != e3) {\n for (s3 = 0; s3 < e3.length; s3++) if ((y3 = e3[s3]) && \"setAttribute\" in y3 == !!x2 && (x2 ? y3.localName == x2 : 3 == y3.nodeType)) {\n u4 = y3, e3[s3] = null;\n break;\n }\n }\n if (null == u4) {\n if (null == x2) return document.createTextNode(k3);\n u4 = document.createElementNS(o3, x2, k3.is && k3), c3 && (l.__m && l.__m(t3, e3), c3 = false), e3 = null;\n }\n if (null == x2) m3 === k3 || c3 && u4.data == k3 || (u4.data = k3);\n else {\n if (e3 = \"textarea\" == x2 && null != k3.defaultValue ? null : e3 && n.call(u4.childNodes), !c3 && null != e3) for (m3 = {}, s3 = 0; s3 < u4.attributes.length; s3++) m3[(y3 = u4.attributes[s3]).name] = y3.value;\n for (s3 in m3) y3 = m3[s3], \"dangerouslySetInnerHTML\" == s3 ? p3 = y3 : \"children\" == s3 || s3 in k3 || \"value\" == s3 && \"defaultValue\" in k3 || \"checked\" == s3 && \"defaultChecked\" in k3 || N(u4, s3, null, y3, o3);\n for (s3 in k3) y3 = k3[s3], \"children\" == s3 ? v3 = y3 : \"dangerouslySetInnerHTML\" == s3 ? h3 = y3 : \"value\" == s3 ? w3 = y3 : \"checked\" == s3 ? _3 = y3 : c3 && \"function\" != typeof y3 || m3[s3] === y3 || N(u4, s3, y3, m3[s3], o3);\n if (h3) c3 || p3 && (h3.__html == p3.__html || h3.__html == u4.innerHTML) || (u4.innerHTML = h3.__html), t3.__k = [];\n else if (p3 && (u4.innerHTML = \"\"), L(\"template\" == t3.type ? u4.content : u4, g(v3) ? v3 : [v3], t3, i3, r3, \"foreignObject\" == x2 ? \"http://www.w3.org/1999/xhtml\" : o3, e3, f4, e3 ? e3[0] : i3.__k && $(i3, 0), c3, a3), null != e3) for (s3 = e3.length; s3--; ) b(e3[s3]);\n c3 && \"textarea\" != x2 || (s3 = \"value\", \"progress\" == x2 && null == w3 ? u4.removeAttribute(\"value\") : null != w3 && (w3 !== u4[s3] || \"progress\" == x2 && !w3 || \"option\" == x2 && w3 != m3[s3]) && N(u4, s3, w3, m3[s3], o3), s3 = \"checked\", null != _3 && _3 != u4[s3] && N(u4, s3, _3, m3[s3], o3));\n }\n return u4;\n }\n function J(n2, u4, t3) {\n try {\n if (\"function\" == typeof n2) {\n var i3 = \"function\" == typeof n2.__u;\n i3 && n2.__u(), i3 && null == u4 || (n2.__u = n2(u4));\n } else n2.current = u4;\n } catch (n3) {\n l.__e(n3, t3);\n }\n }\n function K(n2, u4, t3) {\n var i3, r3;\n if (l.unmount && l.unmount(n2), (i3 = n2.ref) && (i3.current && i3.current != n2.__e || J(i3, null, u4)), null != (i3 = n2.__c)) {\n if (i3.componentWillUnmount) try {\n i3.componentWillUnmount();\n } catch (n3) {\n l.__e(n3, u4);\n }\n i3.base = i3.__P = i3.__n = null;\n }\n if (i3 = n2.__k) for (r3 = 0; r3 < i3.length; r3++) i3[r3] && K(i3[r3], u4, t3 || \"function\" != typeof n2.type);\n t3 || b(n2.__e), n2.__c = n2.__ = n2.__e = void 0;\n }\n function Q(n2, l3, u4) {\n return this.constructor(n2, u4);\n }\n function R(u4, t3, i3) {\n var r3, o3, e3, f4;\n t3 == document && (t3 = document.documentElement), l.__ && l.__(u4, t3), o3 = (r3 = \"function\" == typeof i3) ? null : i3 && i3.__k || t3.__k, e3 = [], f4 = [], q(t3, u4 = (!r3 && i3 || t3).__k = k(S, null, [u4]), o3 || d, d, t3.namespaceURI, !r3 && i3 ? [i3] : o3 ? null : t3.firstChild ? n.call(t3.childNodes) : null, e3, !r3 && i3 ? i3 : o3 ? o3.__e : t3.firstChild, r3, f4), D(e3, u4, f4), u4.props.children = null;\n }\n n = w.slice, l = { __e: function(n2, l3, u4, t3) {\n for (var i3, r3, o3; l3 = l3.__; ) if ((i3 = l3.__c) && !i3.__) try {\n if ((r3 = i3.constructor) && null != r3.getDerivedStateFromError && (i3.setState(r3.getDerivedStateFromError(n2)), o3 = i3.__d), null != i3.componentDidCatch && (i3.componentDidCatch(n2, t3 || {}), o3 = i3.__d), o3) return i3.__E = i3;\n } catch (l4) {\n n2 = l4;\n }\n throw n2;\n } }, u = 0, t = function(n2) {\n return null != n2 && void 0 === n2.constructor;\n }, C.prototype.setState = function(n2, l3) {\n var u4;\n u4 = null != this.__s && this.__s != this.state ? this.__s : this.__s = m({}, this.state), \"function\" == typeof n2 && (n2 = n2(m({}, u4), this.props)), n2 && m(u4, n2), null != n2 && this.__v && (l3 && this._sb.push(l3), A(this));\n }, C.prototype.forceUpdate = function(n2) {\n this.__v && (this.__e = true, n2 && this.__h.push(n2), A(this));\n }, C.prototype.render = S, i = [], o = \"function\" == typeof Promise ? Promise.prototype.then.bind(Promise.resolve()) : setTimeout, e = function(n2, l3) {\n return n2.__v.__b - l3.__v.__b;\n }, H.__r = 0, f = Math.random().toString(8), c = \"__d\" + f, a = \"__a\" + f, s = /(PointerCapture)$|Capture$/i, h = 0, p = V(false), v = V(true), y = 0;\n\n // node_modules/preact/hooks/dist/hooks.module.js\n var t2;\n var r2;\n var u2;\n var i2;\n var o2 = 0;\n var f2 = [];\n var c2 = l;\n var e2 = c2.__b;\n var a2 = c2.__r;\n var v2 = c2.diffed;\n var l2 = c2.__c;\n var m2 = c2.unmount;\n var p2 = c2.__;\n function s2(n2, t3) {\n c2.__h && c2.__h(r2, n2, o2 || t3), o2 = 0;\n var u4 = r2.__H || (r2.__H = { __: [], __h: [] });\n return n2 >= u4.__.length && u4.__.push({}), u4.__[n2];\n }\n function d2(n2) {\n return o2 = 1, y2(D2, n2);\n }\n function y2(n2, u4, i3) {\n var o3 = s2(t2++, 2);\n if (o3.t = n2, !o3.__c && (o3.__ = [i3 ? i3(u4) : D2(void 0, u4), function(n3) {\n var t3 = o3.__N ? o3.__N[0] : o3.__[0], r3 = o3.t(t3, n3);\n t3 !== r3 && (o3.__N = [r3, o3.__[1]], o3.__c.setState({}));\n }], o3.__c = r2, !r2.__f)) {\n var f4 = function(n3, t3, r3) {\n if (!o3.__c.__H) return true;\n var u5 = false, i4 = o3.__c.props !== n3;\n if (o3.__c.__H.__.some(function(n4) {\n if (n4.__N) {\n u5 = true;\n var t4 = n4.__[0];\n n4.__ = n4.__N, n4.__N = void 0, t4 !== n4.__[0] && (i4 = true);\n }\n }), c3) {\n var f5 = c3.call(this, n3, t3, r3);\n return u5 ? f5 || i4 : f5;\n }\n return !u5 || i4;\n };\n r2.__f = true;\n var c3 = r2.shouldComponentUpdate, e3 = r2.componentWillUpdate;\n r2.componentWillUpdate = function(n3, t3, r3) {\n if (this.__e) {\n var u5 = c3;\n c3 = void 0, f4(n3, t3, r3), c3 = u5;\n }\n e3 && e3.call(this, n3, t3, r3);\n }, r2.shouldComponentUpdate = f4;\n }\n return o3.__N || o3.__;\n }\n function h2(n2, u4) {\n var i3 = s2(t2++, 3);\n !c2.__s && C2(i3.__H, u4) && (i3.__ = n2, i3.u = u4, r2.__H.__h.push(i3));\n }\n function _2(n2, u4) {\n var i3 = s2(t2++, 4);\n !c2.__s && C2(i3.__H, u4) && (i3.__ = n2, i3.u = u4, r2.__h.push(i3));\n }\n function A2(n2) {\n return o2 = 5, T2(function() {\n return { current: n2 };\n }, []);\n }\n function T2(n2, r3) {\n var u4 = s2(t2++, 7);\n return C2(u4.__H, r3) && (u4.__ = n2(), u4.__H = r3, u4.__h = n2), u4.__;\n }\n function j2() {\n for (var n2; n2 = f2.shift(); ) {\n var t3 = n2.__H;\n if (n2.__P && t3) try {\n t3.__h.some(z2), t3.__h.some(B2), t3.__h = [];\n } catch (r3) {\n t3.__h = [], c2.__e(r3, n2.__v);\n }\n }\n }\n c2.__b = function(n2) {\n r2 = null, e2 && e2(n2);\n }, c2.__ = function(n2, t3) {\n n2 && t3.__k && t3.__k.__m && (n2.__m = t3.__k.__m), p2 && p2(n2, t3);\n }, c2.__r = function(n2) {\n a2 && a2(n2), t2 = 0;\n var i3 = (r2 = n2.__c).__H;\n i3 && (u2 === r2 ? (i3.__h = [], r2.__h = [], i3.__.some(function(n3) {\n n3.__N && (n3.__ = n3.__N), n3.u = n3.__N = void 0;\n })) : (i3.__h.some(z2), i3.__h.some(B2), i3.__h = [], t2 = 0)), u2 = r2;\n }, c2.diffed = function(n2) {\n v2 && v2(n2);\n var t3 = n2.__c;\n t3 && t3.__H && (t3.__H.__h.length && (1 !== f2.push(t3) && i2 === c2.requestAnimationFrame || ((i2 = c2.requestAnimationFrame) || w2)(j2)), t3.__H.__.some(function(n3) {\n n3.u && (n3.__H = n3.u, n3.u = void 0);\n })), u2 = r2 = null;\n }, c2.__c = function(n2, t3) {\n t3.some(function(n3) {\n try {\n n3.__h.some(z2), n3.__h = n3.__h.filter(function(n4) {\n return !n4.__ || B2(n4);\n });\n } catch (r3) {\n t3.some(function(n4) {\n n4.__h && (n4.__h = []);\n }), t3 = [], c2.__e(r3, n3.__v);\n }\n }), l2 && l2(n2, t3);\n }, c2.unmount = function(n2) {\n m2 && m2(n2);\n var t3, r3 = n2.__c;\n r3 && r3.__H && (r3.__H.__.some(function(n3) {\n try {\n z2(n3);\n } catch (n4) {\n t3 = n4;\n }\n }), r3.__H = void 0, t3 && c2.__e(t3, r3.__v));\n };\n var k2 = \"function\" == typeof requestAnimationFrame;\n function w2(n2) {\n var t3, r3 = function() {\n clearTimeout(u4), k2 && cancelAnimationFrame(t3), setTimeout(n2);\n }, u4 = setTimeout(r3, 35);\n k2 && (t3 = requestAnimationFrame(r3));\n }\n function z2(n2) {\n var t3 = r2, u4 = n2.__c;\n \"function\" == typeof u4 && (n2.__c = void 0, u4()), r2 = t3;\n }\n function B2(n2) {\n var t3 = r2;\n n2.__c = n2.__(), r2 = t3;\n }\n function C2(n2, t3) {\n return !n2 || n2.length !== t3.length || t3.some(function(t4, r3) {\n return t4 !== n2[r3];\n });\n }\n function D2(n2, t3) {\n return \"function\" == typeof t3 ? t3(n2) : t3;\n }\n\n // src/operator-ui/view.ts\n var OPERATOR_PAGES = [\n \"connections\",\n \"tokens\",\n \"activity\",\n \"artifacts\"\n ];\n var PAGE_META = {\n tokens: { path: \"/tokens\", label: \"Access tokens\" },\n connections: { path: \"/\", label: \"Connections\" },\n activity: { path: \"/activity\", label: \"Activity\" },\n artifacts: { path: \"/artifacts\", label: \"Artifacts\" },\n artifact: { path: \"/artifacts\", label: \"Artifact\" }\n };\n function pageDescription(page, productDescription2) {\n if (page === \"activity\") {\n return \"Every connector tool call, by who made it and how it ended. Arguments and results are never stored.\";\n }\n if (isArtifactPage(page)) {\n return \"Pages agents published for the team. Each one keeps every version.\";\n }\n return productDescription2;\n }\n function checkingCopy(page) {\n if (page === \"artifact\") return \"Loading artifact…\";\n if (page === \"artifacts\") return \"Loading artifacts…\";\n return \"Checking your session…\";\n }\n function pageForPath(path) {\n if (path.startsWith(\"/artifacts/\")) return \"artifact\";\n const match = OPERATOR_PAGES.find((page) => PAGE_META[page].path === path);\n return match ?? \"connections\";\n }\n function isArtifactPage(page) {\n return page === \"artifacts\" || page === \"artifact\";\n }\n function artifactViewRequest(pathname, search) {\n const match = /^\\/artifacts\\/([a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?)(?:\\/v\\/(\\d{1,9}))?$/.exec(pathname);\n if (!match?.[1]) return void 0;\n const params = new URLSearchParams();\n if (match[2]) {\n params.set(\"v\", match[2]);\n for (const pin of new URLSearchParams(search).getAll(\"d\")) params.append(\"d\", pin);\n }\n const query = params.toString();\n return `/artifacts/_api/view/${match[1]}${query ? `?${query}` : \"\"}`;\n }\n function info(message) {\n return { message, tone: \"info\" };\n }\n function failure(message, fix) {\n return fix ? { message, tone: \"error\", fix } : { message, tone: \"error\" };\n }\n var REFUSED_COPY = {\n oauth_disconnect: \"Disconnect didn't finish. If the service refused it, the deployment's log has its reply.\",\n oauth_reconnect: \"Authorization couldn't start. If the service refused it, the deployment's log has its reply.\",\n credential_test: \"The credential test couldn't run.\"\n };\n function oauthDoneNotice(action, answer, opened = true) {\n if (action === \"oauth_disconnect\") {\n return info(\"Disconnected. Connect again whenever you are ready.\");\n }\n if (answer?.state === \"ok\") return info(\"Connected.\");\n return info(\n opened ? \"Finish authorizing in the new tab. This page updates when you come back.\" : \"Your browser blocked the new tab. Open the authorization page from the link here.\"\n );\n }\n function credentialTestNotice(connectorId, answer) {\n return answer?.ok === true ? info(\"Credential is valid.\") : failure(\n \"Credential test failed: the service rejected the stored credential, or the test couldn't reach it. The deployment's log has the service's reply.\",\n { kind: \"credential_test_failed\", connectorId }\n );\n }\n function refusedNotice(action, connectorId, problem) {\n if (action === \"credential_test\" && (problem === \"credential_required\" || problem === \"credential_mismatch\")) {\n return failure(PROBLEM_COPY[problem], { kind: problem, connectorId });\n }\n return failure(REFUSED_COPY[action], {\n kind: action === \"credential_test\" ? \"credential_test_failed\" : \"oauth_action_failed\",\n connectorId\n });\n }\n function credentialRefusedCopy(action, status, problem) {\n if (problem === \"credential_mismatch\") return PROBLEM_COPY.credential_mismatch;\n const verb = action === \"credential_save\" ? \"saved\" : \"removed\";\n if (status === 400 || status === 413 || status === 415) {\n return action === \"credential_save\" ? \"The credential wasn't saved: a value is empty or not in the expected format. Check it and save again.\" : \"The credential wasn't removed. Refresh the page and try again.\";\n }\n if (status === 404) {\n return \"This connector no longer has a credential slot. Refresh the page to see its current setup.\";\n }\n if (status === 503) {\n return `Credential storage isn't configured on this deployment, so nothing was ${verb}.`;\n }\n return `The credential wasn't ${verb}. Try again; if it keeps failing, the deployment's log has the reason.`;\n }\n function actionFailedNotice(action, connectorId, facts, productName2) {\n if (facts.kind === \"session\") {\n return failure(\"Your session has ended. Sign in again, then retry.\");\n }\n if (facts.kind === \"forbidden\") {\n return failure(\"You don't have permission to change this connection's authentication.\");\n }\n if (facts.kind === \"network\") {\n return failure(`Couldn't reach ${productName2}. Check your connection and try again.`);\n }\n if (action === \"credential_save\" || action === \"credential_remove\") {\n return failure(credentialRefusedCopy(action, facts.status, facts.problem));\n }\n return refusedNotice(action, connectorId, facts.problem);\n }\n function connectorLoadFailureCopy(failure2, productName2) {\n return failure2 === \"session\" ? \"Your session wasn't accepted while loading this connector. Sign in again to see its status.\" : `Couldn't reach ${productName2} to load this connector. Check your connection, then refresh it.`;\n }\n function loadFailureCopy(failure2, productName2) {\n return {\n title: `Couldn't reach ${productName2}`,\n body: failure2 === \"network\" ? \"Your browser couldn't connect. Check your connection, then retry.\" : \"The deployment answered with an error. Retry in a moment; if it keeps failing, the deployment's log has the reason.\"\n };\n }\n function collectionFailureCopy(collection, facts, productName2) {\n if (facts.kind === \"network\") {\n return `Couldn't reach ${productName2}. Check your connection, then retry.`;\n }\n if (facts.kind === \"session\") return \"Your session has ended. Sign in again to see this page.\";\n if (facts.kind === \"forbidden\" || facts.status === 404) {\n if (collection === \"artifact\") return \"There is no artifact here, or this identity can't open it.\";\n return collection === \"activity\" ? \"Activity isn't available to this identity.\" : \"Artifacts aren't available to this identity.\";\n }\n return \"The deployment answered with an error. Retry in a moment; if it keeps failing, the deployment's log has the reason.\";\n }\n function confirmCopy(action, name) {\n if (action === \"oauth_disconnect\") {\n return {\n question: `Disconnect ${name}? Its stored grant and any pending authorization are removed, and its tools stop working until it is connected again.`,\n confirm: \"Disconnect\"\n };\n }\n if (action === \"oauth_restart\") {\n return {\n question: `Reconnect ${name}? Its current grant stops working until you finish authorizing in the new tab.`,\n confirm: \"Reconnect\"\n };\n }\n return {\n question: `Remove ${name}'s credential? The connector stops authenticating until a replacement is added.`,\n confirm: \"Remove\"\n };\n }\n function accessTokenUnavailableCopy(capability) {\n return capability === void 0 ? \"Access tokens are not configured for this deployment.\" : \"Token management requires an interactive sign-in and explicit permission.\";\n }\n function initialState(page) {\n return {\n page,\n generation: 0,\n session: \"loading\",\n gate: null,\n refreshing: false,\n loadFailure: null,\n pendingFocus: null,\n focusIfLost: null,\n ...identityScopedState()\n };\n }\n function identityScopedState() {\n return {\n data: null,\n connectorFilter: \"\",\n oauthNotice: null,\n oauthNoticeFor: null,\n oauthBusy: null,\n oauthBlocked: null,\n confirming: null,\n connectorFailures: {},\n credentialNotice: null,\n credentialNoticeFor: null,\n credentialEditing: null,\n credentialBusy: null,\n tokenPhase: \"idle\",\n tokenNotice: null,\n tokens: [],\n createdToken: null,\n tokenRenaming: null,\n tokenBusy: false,\n activityPhase: \"idle\",\n activityNotice: null,\n activityEvents: [],\n activityCursor: null,\n activitySearch: \"\",\n artifactPhase: \"idle\",\n artifactNotice: null,\n artifactRows: [],\n artifactCursor: null,\n artifactQuery: \"\",\n artifactArchived: false,\n artifactView: null\n };\n }\n function resetIdentity(state2, gate2 = null) {\n return {\n ...state2,\n generation: state2.generation + 1,\n session: \"gated\",\n gate: gate2,\n refreshing: false,\n loadFailure: null,\n pendingFocus: null,\n focusIfLost: null,\n ...identityScopedState()\n };\n }\n function withPage(state2, page) {\n return {\n ...state2,\n page,\n createdToken: null,\n tokenRenaming: null,\n tokenNotice: null,\n credentialEditing: null,\n credentialNotice: null,\n credentialNoticeFor: null,\n confirming: null\n };\n }\n function connectorStatusLabel(status, problem) {\n if (status === \"loading\") return \"Loading details\";\n if (status === \"ok\") return \"Connected\";\n if (status === \"auth_required\") {\n return problem === \"credential_required\" ? \"Credential needed\" : \"Authorization needed\";\n }\n return \"Unavailable\";\n }\n function toolCountLabel(count) {\n return `${count} ${count === 1 ? \"tool\" : \"tools\"}`;\n }\n function connectorStatusTone(status) {\n if (status === \"ok\") return \"ok\";\n if (status === \"auth_required\") return \"warn\";\n if (status === \"loading\") return \"neutral\";\n return \"danger\";\n }\n function summarizeConnectors(connectors) {\n const summary = {\n total: connectors.length,\n connected: 0,\n attention: 0,\n credentials: 0,\n unavailable: 0,\n loading: 0,\n tools: 0,\n drifting: 0\n };\n for (const connector of connectors) {\n if (connector.status === \"ok\") summary.connected += 1;\n else if (connector.status === \"auth_required\") {\n if (connector.problem === \"credential_required\") summary.credentials += 1;\n else summary.attention += 1;\n } else if (connector.status === \"loading\") summary.loading += 1;\n else summary.unavailable += 1;\n summary.tools += connector.toolCount || 0;\n if (driftState(connector.catalogDrift) === \"warning\") summary.drifting += 1;\n }\n return summary;\n }\n function connectorSummaryParts(summary) {\n if (summary.total > 0 && summary.loading === summary.total) {\n return [\n {\n text: `Checking ${summary.total} connector${summary.total === 1 ? \"\" : \"s\"}…`,\n tone: \"neutral\"\n }\n ];\n }\n return [\n { text: `${summary.connected} connected`, tone: \"neutral\" },\n ...summary.attention ? [\n {\n text: `${summary.attention} need${summary.attention === 1 ? \"s\" : \"\"} authorization`,\n tone: \"warn\"\n }\n ] : [],\n ...summary.credentials ? [\n {\n text: `${summary.credentials} need${summary.credentials === 1 ? \"s\" : \"\"} a credential`,\n tone: \"warn\"\n }\n ] : [],\n ...summary.unavailable ? [{ text: `${summary.unavailable} unavailable`, tone: \"danger\" }] : [],\n { text: toolCountLabel(summary.tools), tone: \"neutral\" },\n ...summary.loading ? [{ text: `${summary.loading} still loading`, tone: \"neutral\" }] : []\n ];\n }\n var TOOL_SAFETY_BADGE = {\n runs_in_programs: {\n label: \"runs in programs\",\n tone: \"ok\",\n title: \"Explicitly read-only: execute_code programs may call it without asking.\"\n },\n exempt: {\n label: \"exempt from approval\",\n tone: \"neutral\",\n title: \"Not read-only, but this deployment's config lets programs call it without pausing. Each call still counts against the write budget and appears in activity; call_tool still refuses it.\"\n },\n needs_approval: {\n label: \"asks for approval\",\n tone: \"warn\",\n title: \"Not explicitly read-only: a program pauses for resume_execution, and a direct call crosses call_destructive_tool — either way the host asks first.\"\n }\n };\n var PROBLEM_COPY = {\n connector_unavailable: \"Unavailable: its status check or catalog load failed, or did not finish in time. The deployment's log has the downstream error.\",\n oauth_required: \"Needs OAuth authorization: no grant is stored, or the stored grant expired or was revoked.\",\n credential_required: \"Needs a credential: nothing usable is stored in its credential slot.\",\n auth_required: \"Needs authorization. Its secret lives in deployment configuration, not on this page.\",\n credential_mismatch: \"The stored credential does not match the fields this connector declares, so it cannot be used.\",\n catalog_failed: \"Connected, but its tool catalog could not be loaded, so none of its tools are served.\"\n };\n function problemCopy(problem) {\n return problem ? PROBLEM_COPY[problem] ?? null : null;\n }\n function problemTone(problem) {\n return problem === \"oauth_required\" || problem === \"credential_required\" || problem === \"auth_required\" ? \"warn\" : \"danger\";\n }\n function authScopeLabel(scope) {\n return scope === \"personal\" ? \"personal auth\" : \"shared auth\";\n }\n function permissionLabel(connector) {\n if (connector.permissions?.manageSharedAuth) {\n return \"You can manage shared authentication for this connection.\";\n }\n if (connector.permissions?.connectPersonal) {\n return \"You can connect your own account to this connection.\";\n }\n return \"Authentication for this connection is managed by your deployment.\";\n }\n var DRIFT_CATEGORIES = [\n { key: \"unclassifiedTools\", label: \"Unclassified\" },\n { key: \"unservedTools\", label: \"Unserved\" },\n { key: \"annotationConflicts\", label: \"Annotation conflicts\" },\n { key: \"schemaChanges\", label: \"Schema changes\" }\n ];\n function driftTotal(drift) {\n if (!drift) return 0;\n return DRIFT_CATEGORIES.reduce((sum, { key }) => sum + (drift[key] || 0), 0);\n }\n function driftState(drift) {\n if (!drift) return \"unavailable\";\n return driftTotal(drift) > 0 ? \"warning\" : \"clean\";\n }\n function driftCounts(drift) {\n if (!drift) return [];\n return DRIFT_CATEGORIES.map(({ key, label }) => ({\n key,\n label,\n count: drift[key] || 0\n }));\n }\n function driftSummary(drift) {\n const state2 = driftState(drift);\n if (state2 === \"unavailable\") {\n return \"No catalog refresh observed yet in this runtime.\";\n }\n const observed = formatDate(drift?.observedAt);\n const when = observed ? ` · observed ${observed}` : \"\";\n if (state2 === \"clean\") return `Matches the reviewed manifest${when}`;\n const total = driftTotal(drift);\n return `${total} difference${total === 1 ? \"\" : \"s\"} from the reviewed manifest${when}`;\n }\n function safeHttpHref(url) {\n if (!url) return null;\n try {\n const protocol = new URL(url).protocol;\n return protocol === \"http:\" || protocol === \"https:\" ? url : null;\n } catch {\n return null;\n }\n }\n function formatDate(value) {\n if (!value) return \"\";\n const date = new Date(value);\n return Number.isNaN(date.valueOf()) ? \"\" : date.toLocaleString(void 0, { dateStyle: \"medium\", timeStyle: \"short\" });\n }\n var ACTOR_KINDS = {\n clerk: \"Clerk\",\n bearer: \"Bearer token\",\n \"cloudflare-access\": \"Cloudflare Access\",\n anonymous: \"Anonymous\"\n };\n function actorKindLabel(kind) {\n if (!kind) return \"Unknown caller\";\n return ACTOR_KINDS[kind] ?? kind;\n }\n function actorLabel(actor) {\n if (!actor?.kind) return \"Unknown caller\";\n const who = actor.label || actor.id;\n const kind = actorKindLabel(actor.kind);\n return who ? `${who} (${kind})` : kind;\n }\n function actorStableId(actor) {\n if (!actor?.id) return null;\n if (!actor.label && !actor.namespace) return null;\n return actor.namespace ? `${actor.namespace} · ${actor.id}` : actor.id;\n }\n function activityMatches(event, query) {\n const q2 = query.trim().toLowerCase();\n if (!q2) return true;\n return [\n event.address,\n event.connectorId,\n event.toolName,\n event.source,\n event.outcome,\n event.errorCode,\n event.friction,\n event.actor?.kind,\n event.actor?.id,\n event.actor?.namespace,\n event.actor?.label,\n activityOutcomeBadge(event.outcome).label,\n activityDetail(event),\n actorKindLabel(event.actor?.kind)\n ].some((value) => String(value ?? \"\").toLowerCase().includes(q2));\n }\n function filterActivity(events, query) {\n return events.filter((event) => activityMatches(event, query));\n }\n function activitySummary(events) {\n if (events.length === 0) return \"\";\n const tools = new Set(events.map((event) => event.address)).size;\n return `${events.length} loaded call${events.length === 1 ? \"\" : \"s\"} · ${tools} tool${tools === 1 ? \"\" : \"s\"}`;\n }\n var ACTIVITY_OUTCOMES = [\n \"success\",\n \"error\",\n \"timeout\",\n \"cancelled\",\n \"paused\",\n \"approved\"\n ];\n function activityOutcomeClass(outcome) {\n return ACTIVITY_OUTCOMES.includes(outcome) ? outcome : \"error\";\n }\n var OUTCOME_BADGE = {\n success: { label: \"Succeeded\", tone: \"ok\" },\n error: { label: \"Failed\", tone: \"danger\" },\n timeout: { label: \"Timed out\", tone: \"danger\" },\n cancelled: { label: \"Cancelled\", tone: \"neutral\" },\n paused: { label: \"Waiting for approval\", tone: \"warn\" },\n approved: { label: \"Approved\", tone: \"ok\" }\n };\n function activityOutcomeBadge(outcome) {\n return OUTCOME_BADGE[outcome] ?? { label: \"Failed\", tone: \"danger\" };\n }\n var SOURCE_LABELS = {\n execute_code: \"In a program\",\n call_tool: \"Direct call\",\n call_destructive_tool: \"Direct call, approved by the host\",\n resume_execution: \"Resumed program\",\n batch_call: \"Batch call\"\n };\n var REASON_LABELS = {\n tool_not_found: \"tool not found\",\n unknown_address: \"tool not found\",\n unknown_tool: \"tool not found\",\n ambiguous_tool_alias: \"ambiguous tool name\",\n schema_retry: \"arguments didn't match the schema\",\n invalid_args: \"arguments didn't match the schema\",\n destructive_reroute: \"needed host approval\",\n destructive_tool_requires_approval: \"needed host approval\",\n approval_required: \"needed approval\",\n auth_required: \"needed authorization\",\n result_too_large: \"result too large to return inline\",\n timeout: \"timed out\"\n };\n function reasonLabel(code) {\n return REASON_LABELS[code] ?? code.replaceAll(\"_\", \" \");\n }\n function activityDetail(event) {\n const parts = [SOURCE_LABELS[event.source] ?? event.source];\n if (event.approval === \"tool\") parts.push(\"approved for the rest of the run\");\n if (event.approval === \"call\") parts.push(\"approved for this call\");\n if (event.attempts > 1) parts.push(`${event.attempts} attempts`);\n const reasons = [event.friction, event.errorCode].filter((code) => Boolean(code)).map(reasonLabel);\n for (const reason of new Set(reasons)) parts.push(reason);\n return parts.join(\" · \");\n }\n function artifactRefreshBadge(last) {\n return last?.status === \"failed\" ? { label: \"Refresh failed\", tone: \"danger\" } : null;\n }\n function credentialProblemCopy(problem) {\n if (problem === \"credential_mismatch\") return PROBLEM_COPY.credential_mismatch;\n if (problem === \"credential_unreadable\") {\n return \"The stored credential can't be read, so it can't be used. Replace it, or remove it and add a new one.\";\n }\n return \"The stored credential can't be used. Replace it, or remove it and add a new one.\";\n }\n function credentialStateLabel(credential) {\n if (!credential.configured) return \"not configured\";\n const masked = credential.fields?.length ? \"configured\" : `configured · ••••${credential.lastFour ?? \"\"}`;\n return credential.updatedAt ? `${masked} · updated ${formatDate(credential.updatedAt)}` : masked;\n }\n function gateCopy(kind, signedIn) {\n if (kind === \"cloudflare-access\") {\n return \"Cloudflare Access admitted this browser, but the current identity cannot open deployment-wide operator pages.\";\n }\n if (kind !== \"clerk\") {\n return \"Paste an operator bearer token to open this page. Nothing is requested until you do.\";\n }\n return signedIn ? \"Signed in with Clerk, but this account cannot open deployment-wide operator pages.\" : \"Sign in with Clerk to open this operator page.\";\n }\n\n // src/operator-ui/app/config.ts\n var auth = AUTH;\n var mcpUrl = MCP_URL;\n var initialPage = INITIAL_PAGE;\n var homeUrl = HOME_URL;\n var titleSuffix = TITLE_SUFFIX;\n var productName = PRODUCT_NAME;\n var productDescription = PRODUCT_DESCRIPTION;\n var productOperatorLabel = PRODUCT_OPERATOR_LABEL;\n var TOKEN_KEY = \"connecta:token\";\n\n // src/fix-prompt.ts\n var CONNECTOR_ID_RE = /^[a-z0-9_-]+$/;\n function renderFixPrompt(spec, connectorId) {\n const id = connectorId !== void 0 && CONNECTOR_ID_RE.test(connectorId) ? connectorId : void 0;\n return [\n \"Diagnose and fix a problem in this connecta deployment (the @zackbart/connecta package). It is configured as code: connectors, credential slots, OAuth clients, pools, and inbound auth are declared in the deployment's source and environment, so the fix belongs there and not in the operator page.\",\n `Problem: ${spec.problem}` + (id ? `\nConnector id: ${id}` : \"\"),\n `Where to look:\n${spec.steps.map((step) => `- ${step}`).join(\"\\n\")}`,\n \"This prompt deliberately carries no error text, tokens, or URLs. Find the underlying cause in the deployment's own logs (lines prefixed [connecta]) or by reproducing the failure locally. Never put a secret in source, a log line, or your reply: secrets belong in the deployment's environment or its credential vault.\",\n \"Make the smallest change that fixes it, then verify it by redeploying and refreshing the connection on the operator Connections page. If the fix needs something you cannot do yourself — a provider console setting, a secret only the operator holds — name that exact step instead.\"\n ].join(\"\\n\\n\");\n }\n\n // src/operator-ui/fix-prompts.ts\n var CATALOGUE = {\n connector_unavailable: {\n problem: \"A connector is unavailable: its status check or catalog load failed, or did not finish before the operator page's deadline.\",\n steps: [\n \"Confirm the connector's downstream URL or API base in the deployment config, and that the deployment can reach it from where it runs.\",\n \"Check the credentials or headers the connector sends; a rejected credential often surfaces as a failed status rather than as an authorization prompt.\",\n \"If the downstream is slow rather than down, review the connector's timeouts and the deployment's discovery.probeTimeoutMs.\"\n ]\n },\n oauth_required: {\n problem: \"A downstream OAuth connector needs authorization: no grant is stored, or the stored grant expired or was revoked and could not be refreshed.\",\n steps: [\n \"Reauthorize from the operator page (Connect account or Reconnect OAuth) or with authorize_connector; this needs no code change if the grant simply lapsed.\",\n \"If it needs reauthorizing again soon after, check that the OAuth client requests offline access or a refresh token, and that the storage holding OAuth tokens persists across restarts and is shared by every instance.\",\n \"If authorization cannot start at all, check the connector's OAuth client configuration and the deployment's publicUrl, which forms the /oauth/callback/ redirect URI.\"\n ]\n },\n credential_required: {\n problem: \"A connector with an operator-managed credential slot has no usable credential stored.\",\n steps: [\n \"An operator with credential administration can add the credential on the operator page; that needs no code change.\",\n \"If nobody can, grant credential administration through the deployment's identity config (credentialAdministration for shared auth, personalConnection for personal auth), which is denied by default.\",\n \"Check that the connector's credential declaration (label and fields) matches what the downstream actually needs.\"\n ]\n },\n auth_required: {\n problem: \"A connector reports that it needs authorization, and its secret lives in deployment configuration rather than in an operator-managed slot.\",\n steps: [\n \"Find where the connector's secret is read (usually an environment variable or Worker secret passed into the connector config) and confirm it is set in the running environment.\",\n \"Rotate the secret at the provider if it expired or was revoked, then update the deployment's environment, not its source.\",\n \"If operators should manage this secret from the page instead, declare a credential slot on the connector and configure a credential vault.\"\n ]\n },\n credential_mismatch: {\n problem: \"The credential stored for a connector does not match the fields the connector currently declares, so it cannot be used.\",\n steps: [\n \"If the connector's credential fields changed on purpose, re-enter the credential on the operator page so the stored fields match.\",\n \"If they changed by accident, restore the previous field names in the connector's credential declaration.\"\n ]\n },\n credential_unreadable: {\n problem: \"A stored credential exists but could not be read or decrypted.\",\n steps: [\n \"Check that the credential vault's encryption key in the deployment environment is the one the credential was stored with; a rotated or missing key makes every stored value unreadable.\",\n \"Check the storage adapter backing the vault is reachable from the deployment.\",\n \"If the key was lost, remove and re-add the credential on the operator page; the old value cannot be recovered.\"\n ]\n },\n credential_test_failed: {\n problem: \"The test for a stored connector credential failed: the downstream rejected it, or the test could not reach the downstream.\",\n steps: [\n \"Confirm the stored value is current at the provider (not rotated, revoked, or scoped too narrowly), and replace it on the operator page if not.\",\n \"Check that the connector's credential test targets the same API base and auth scheme its tool calls use.\"\n ]\n },\n oauth_action_failed: {\n problem: \"Restarting or disconnecting a connector's downstream OAuth from the operator page failed.\",\n steps: [\n \"Check that the connector implements startAuth and disconnectAuth, and that the storage holding its OAuth state is writable.\",\n \"Check the connector's OAuth client configuration, including the authorization server it discovers or is given.\",\n \"Confirm the operator has the config-derived permission for this action (credentialAdministration for shared auth, personalConnection for personal auth).\"\n ]\n },\n catalog_failed: {\n problem: \"A connector reports itself connected, but loading its tool catalog failed, so none of its tools are being served.\",\n steps: [\n \"Check that the downstream still serves a complete tools list; connecta refuses a partial catalog rather than serving part of it.\",\n \"Check for tools the connector cannot accept: invalid schemas, missing descriptions, or names that collide.\",\n \"Pin or upgrade the @zackbart/connecta version if a maintained provider's catalog changed shape underneath it.\"\n ]\n },\n catalog_drift: {\n problem: \"A hosted provider's live catalog differs from the reviewed manifest shipped with connecta: new unclassified tools, unserved tools, annotation conflicts, or schema changes.\",\n steps: [\n \"Unclassified tools are withheld until a release classifies them. Check whether a newer @zackbart/connecta release has, and upgrade if so.\",\n \"If a tool the deployment depends on is now unserved or changed shape, review callers of it before upgrading.\",\n \"Report drift the release does not cover as an issue on the connecta repository, naming the provider and the kinds of drift but no tool data.\"\n ]\n }\n };\n function fixPrompt(kind, connectorId) {\n return renderFixPrompt(CATALOGUE[kind], connectorId);\n }\n var FIX_PROMPT_KINDS = Object.keys(CATALOGUE);\n\n // src/operator-ui/app/store.ts\n var state = initialState(initialPage);\n var listeners = /* @__PURE__ */ new Set();\n function getState() {\n return state;\n }\n function subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n }\n function set(patch) {\n state = { ...state, ...patch };\n for (const listener of listeners) listener();\n }\n function fence() {\n const generation = state.generation;\n return () => generation === state.generation;\n }\n function sessionToken() {\n if (auth.kind === \"cloudflare-access\") return Promise.resolve(void 0);\n return auth.kind === \"clerk\" ? Promise.resolve(window.Clerk?.session?.getToken() ?? null) : Promise.resolve(localStorage.getItem(TOKEN_KEY));\n }\n function requestHeaders(token, body = false) {\n return {\n ...token ? { Authorization: `Bearer ${token}` } : {},\n ...body ? { \"Content-Type\": \"application/json\" } : {}\n };\n }\n var NAV_HINT_KEY = \"connecta:nav\";\n function rememberNav(data) {\n try {\n sessionStorage.setItem(\n NAV_HINT_KEY,\n JSON.stringify({ activity: data.activityEnabled, artifacts: Boolean(data.artifactsEnabled) })\n );\n } catch {\n }\n }\n function forgetNav() {\n try {\n sessionStorage.removeItem(NAV_HINT_KEY);\n } catch {\n }\n }\n function navHint() {\n try {\n const hint = JSON.parse(sessionStorage.getItem(NAV_HINT_KEY) ?? \"null\");\n return { activity: hint?.activity === true, artifacts: hint?.artifacts === true };\n } catch {\n return { activity: false, artifacts: false };\n }\n }\n function gate(notice = null) {\n forgetNav();\n awaitingAuthorization.clear();\n state = resetIdentity(state, notice);\n for (const listener of listeners) listener();\n }\n var RequestFailure = class extends Error {\n kind;\n status;\n problem;\n constructor(kind, status, problem) {\n super(`Operator request failed: ${kind}`);\n this.kind = kind;\n this.status = status;\n this.problem = problem;\n }\n };\n function factsOf(error) {\n return error instanceof RequestFailure ? error : { kind: \"refused\" };\n }\n async function operatorRequest(path, method, current, body) {\n let token;\n try {\n token = await sessionToken();\n } catch {\n throw new RequestFailure(\"session\");\n }\n if (!current()) throw new RequestFailure(\"session\");\n if (!token && auth.kind !== \"cloudflare-access\") throw new RequestFailure(\"session\");\n let res;\n try {\n res = await fetch(path, {\n method,\n headers: requestHeaders(token, Boolean(body)),\n credentials: \"same-origin\",\n ...body ? { body: JSON.stringify(body) } : {}\n });\n } catch {\n throw new RequestFailure(\"network\");\n }\n if (res.status === 204) return null;\n let payload = {};\n try {\n payload = await res.json();\n } catch {\n }\n if (res.status === 401) throw new RequestFailure(\"session\", 401);\n if (res.status === 403) throw new RequestFailure(\"forbidden\", 403);\n if (!res.ok) throw new RequestFailure(\"refused\", res.status, payload.problem);\n return payload;\n }\n function unreachable(loadFailure) {\n set({ session: \"ready\", gate: null, refreshing: false, loadFailure });\n }\n async function loadData() {\n const current = fence();\n set(state.session === \"ready\" ? { refreshing: true, loadFailure: null } : { loadFailure: null });\n let token;\n try {\n token = await sessionToken();\n } catch {\n if (!current()) return;\n return gate(failure(\"Your sign-in session couldn't be read. Sign in again.\"));\n }\n if (!current()) return;\n if (!token && auth.kind !== \"cloudflare-access\") return gate(null);\n let res;\n try {\n res = await fetch(\"/ui/data\", {\n headers: requestHeaders(token),\n credentials: \"same-origin\"\n });\n } catch {\n if (!current()) return;\n return unreachable(\"network\");\n }\n if (!current()) return;\n if (res.status === 401 || res.status === 403) {\n if (auth.kind === \"clerk\") {\n return gate(\n failure(\n res.status === 403 ? `This Clerk account can't open ${productName}'s operator pages.` : \"Your Clerk session wasn't accepted. Sign out, then sign in again.\"\n )\n );\n }\n if (auth.kind === \"cloudflare-access\") {\n return gate(\n failure(\"Cloudflare Access let this browser in, but this identity isn't an operator here.\")\n );\n }\n localStorage.removeItem(TOKEN_KEY);\n return gate(failure(\"That token wasn't accepted. Paste a valid operator token.\"));\n }\n if (!res.ok) return unreachable(\"server\");\n let data;\n try {\n data = await res.json();\n } catch {\n if (!current()) return;\n return unreachable(\"server\");\n }\n if (!current()) return;\n if (!Array.isArray(data.connectors)) return unreachable(\"server\");\n set({ data, session: \"ready\", gate: null, refreshing: false, loadFailure: null });\n rememberNav(data);\n void loadConnectorDetails(data, current, token);\n }\n async function mutate(options) {\n const current = fence();\n if (options.reload) detailRevisions.set(options.reload, (detailRevisions.get(options.reload) ?? 0) + 1);\n set(options.busy);\n try {\n const payload = await options.request(current);\n if (!current()) return options.abandoned?.();\n set(options.done(payload));\n if (options.reload) void refreshConnector(options.reload);\n } catch (error) {\n if (!current()) return options.abandoned?.();\n set(options.failed(factsOf(error)));\n }\n }\n function focusHandled() {\n if (state.pendingFocus !== null || state.focusIfLost !== null) {\n set({ pendingFocus: null, focusIfLost: null });\n }\n }\n function setPage(page, focus = false) {\n state = withPage(state, page);\n if (focus) {\n state = {\n ...state,\n pendingFocus: state.session === \"ready\" ? `${page}Heading` : \"gateHeading\"\n };\n }\n for (const listener of listeners) listener();\n }\n function navigate(page, href) {\n history.pushState({ operatorPage: page }, \"\", href);\n setPage(page, true);\n }\n function setConnectorFilter(connectorFilter) {\n set({ connectorFilter });\n }\n function setActivitySearch(activitySearch) {\n set({ activitySearch });\n }\n function signInWithBearer(value) {\n gate(null);\n localStorage.setItem(TOKEN_KEY, value);\n void loadCurrent().then(() => {\n if (state.session === \"ready\") set({ pendingFocus: `${state.page}Heading` });\n });\n }\n function forgetBearer() {\n localStorage.removeItem(TOKEN_KEY);\n gate(null);\n set({ pendingFocus: \"token\" });\n }\n function askConfirm(connectorId, action) {\n set({ confirming: { connectorId, action }, pendingFocus: `confirm-cancel-${connectorId}` });\n }\n function cancelConfirm(returnFocusTo) {\n set({ confirming: null, pendingFocus: returnFocusTo });\n }\n function oauthStartPath(connector, mode) {\n return `/ui/oauth/${encodeURIComponent(connector)}?mode=${mode}`;\n }\n var awaitingAuthorization = /* @__PURE__ */ new Set();\n function openBlankTab() {\n let tab = null;\n try {\n tab = window.open(\"\", \"_blank\");\n } catch {\n return null;\n }\n if (!tab) return null;\n try {\n tab.document.title = \"Opening authorization…\";\n tab.document.body.textContent = \"Opening the authorization page…\";\n } catch {\n }\n return tab;\n }\n function oauthNoticePatch(connector, notice) {\n return {\n oauthBusy: null,\n oauthNotice: notice,\n oauthNoticeFor: connector,\n focusIfLost: `oauthNotice-${connector}`\n };\n }\n function startOAuth(connector, mode) {\n const tab = openBlankTab();\n return mutate({\n request: (current) => operatorRequest(oauthStartPath(connector, mode), \"POST\", current),\n busy: {\n oauthBusy: connector,\n oauthNotice: null,\n oauthNoticeFor: connector,\n oauthBlocked: null,\n confirming: null\n },\n done: (payload) => {\n const url = safeHttpHref(payload?.authorizationUrl);\n if (!url) {\n tab?.close();\n if (payload?.state === \"ok\") {\n return oauthNoticePatch(connector, oauthDoneNotice(\"oauth_reconnect\", payload));\n }\n return oauthNoticePatch(connector, refusedNotice(\"oauth_reconnect\", connector));\n }\n if (tab) {\n tab.opener = null;\n tab.location.replace(url);\n }\n awaitingAuthorization.add(connector);\n return {\n ...state.data ? {\n data: {\n ...state.data,\n connectors: state.data.connectors.map(\n (c3) => c3.id === connector ? { ...c3, status: \"auth_required\", tools: [], toolCount: 0, authorizationUrl: url } : c3\n )\n }\n } : {},\n ...oauthNoticePatch(connector, oauthDoneNotice(\"oauth_reconnect\", payload, Boolean(tab))),\n oauthBlocked: tab ? null : connector\n };\n },\n // Signed out or switched mid-request: the tab it opened goes too, rather\n // than sitting on \"Opening…\" with nothing left to send it anywhere.\n abandoned: () => tab?.close(),\n // The route's words never reach this notice (see `refusedNotice`).\n failed: (facts) => {\n tab?.close();\n return oauthNoticePatch(\n connector,\n actionFailedNotice(\"oauth_reconnect\", connector, facts, productName)\n );\n },\n reload: connector\n });\n }\n function disconnectOAuth(connector) {\n return mutate({\n request: (current) => operatorRequest(`/ui/oauth/${encodeURIComponent(connector)}`, \"DELETE\", current),\n busy: {\n oauthBusy: connector,\n oauthNotice: null,\n oauthNoticeFor: connector,\n oauthBlocked: null,\n confirming: null\n },\n done: () => ({\n ...state.data ? {\n data: {\n ...state.data,\n connectors: state.data.connectors.map((c3) => {\n if (c3.id !== connector) return c3;\n const { authorizationUrl: _old, ...rest } = c3;\n return { ...rest, status: \"auth_required\", tools: [], toolCount: 0 };\n })\n }\n } : {},\n ...oauthNoticePatch(connector, oauthDoneNotice(\"oauth_disconnect\", null))\n }),\n failed: (facts) => oauthNoticePatch(\n connector,\n actionFailedNotice(\"oauth_disconnect\", connector, facts, productName)\n ),\n reload: connector\n });\n }\n function editCredential(connector) {\n set({ credentialEditing: connector, credentialNotice: null, credentialNoticeFor: null });\n }\n function refuseCredential(connector, copy) {\n set({ credentialNotice: failure(copy), credentialNoticeFor: connector });\n }\n function credentialMutation(connector, action, request, done, reload = true) {\n const land = (patch) => ({\n credentialBusy: null,\n credentialNoticeFor: connector,\n focusIfLost: `credentialNotice-${connector}`,\n ...patch\n });\n return mutate({\n request,\n busy: {\n credentialBusy: connector,\n credentialNotice: null,\n credentialNoticeFor: connector,\n confirming: null\n },\n done: (payload) => land(done(payload)),\n failed: (facts) => land({ credentialNotice: actionFailedNotice(action, connector, facts, productName) }),\n reload: reload ? connector : void 0\n });\n }\n function saveCredential(connector, body) {\n return credentialMutation(\n connector,\n \"credential_save\",\n (current) => operatorRequest(\n `/ui/credentials/${encodeURIComponent(connector)}`,\n \"PUT\",\n current,\n body\n ),\n () => ({ credentialEditing: null, credentialNotice: info(\"Credential saved.\") })\n );\n }\n function removeCredential(connector) {\n return credentialMutation(\n connector,\n \"credential_remove\",\n (current) => operatorRequest(\n `/ui/credentials/${encodeURIComponent(connector)}`,\n \"DELETE\",\n current\n ),\n () => ({ credentialNotice: info(\"Credential removed.\") })\n );\n }\n function testCredential(connector) {\n return credentialMutation(\n connector,\n \"credential_test\",\n (current) => operatorRequest(\n `/ui/credentials/${encodeURIComponent(connector)}/test`,\n \"POST\",\n current\n ),\n (payload) => ({ credentialNotice: credentialTestNotice(connector, payload) }),\n false\n );\n }\n async function loadActivity(reset) {\n if (!state.data?.activityEnabled) return;\n const current = fence();\n set({\n activityPhase: \"loading\",\n activityNotice: null,\n ...reset ? { activityEvents: [], activityCursor: null } : {}\n });\n const params = new URLSearchParams({ limit: \"50\" });\n if (!reset && state.activityCursor) {\n params.set(\"cursor\", state.activityCursor);\n }\n try {\n const payload = await operatorRequest(\n `/ui/activity?${params}`,\n \"GET\",\n current\n );\n if (!current()) return;\n set({\n activityPhase: \"ready\",\n activityEvents: [\n ...reset ? [] : state.activityEvents,\n ...payload?.events ?? []\n ],\n activityCursor: payload?.nextCursor ?? null\n });\n } catch (error) {\n if (!current()) return;\n set({\n activityPhase: \"error\",\n activityNotice: failure(collectionFailureCopy(\"activity\", factsOf(error), productName))\n });\n }\n }\n async function artifactRead(path, current, collection) {\n let token;\n try {\n token = await sessionToken();\n } catch {\n if (current()) gate(failure(\"Your sign-in session couldn't be read. Sign in again.\"));\n return void 0;\n }\n if (!current()) return void 0;\n if (!token && auth.kind !== \"cloudflare-access\") {\n gate(null);\n return void 0;\n }\n let res;\n try {\n res = await fetch(path, { headers: requestHeaders(token), credentials: \"same-origin\" });\n } catch {\n if (current()) {\n set({\n session: \"ready\",\n gate: null,\n artifactPhase: \"error\",\n artifactNotice: failure(collectionFailureCopy(collection, { kind: \"network\" }, productName))\n });\n }\n return void 0;\n }\n if (!current()) return void 0;\n if (res.status === 401) {\n if (auth.kind !== \"clerk\" && auth.kind !== \"cloudflare-access\") {\n localStorage.removeItem(TOKEN_KEY);\n gate(failure(\"That token wasn't accepted. Paste a valid operator token.\"));\n } else {\n gate(failure(\"Your session wasn't accepted. Sign out, then sign in again.\"));\n }\n return void 0;\n }\n if (res.status === 403) {\n gate(failure(\"This deployment doesn't open artifact pages to this identity.\"));\n return void 0;\n }\n return res;\n }\n async function loadArtifacts(reset) {\n const current = fence();\n set({\n artifactPhase: \"loading\",\n artifactNotice: null,\n ...reset ? { artifactRows: [], artifactCursor: null } : {}\n });\n const params = new URLSearchParams();\n if (state.artifactQuery.trim()) params.set(\"q\", state.artifactQuery.trim());\n if (state.artifactArchived) params.set(\"archived\", \"1\");\n if (!reset && state.artifactCursor) params.set(\"cursor\", state.artifactCursor);\n const query = params.toString();\n const res = await artifactRead(`/artifacts/_api/list${query ? `?${query}` : \"\"}`, current, \"artifacts\");\n if (!res || !current()) return;\n if (!res.ok) {\n return set({\n session: \"ready\",\n gate: null,\n artifactPhase: \"error\",\n artifactNotice: failure(\n collectionFailureCopy(\"artifacts\", { kind: \"refused\", status: res.status }, productName)\n )\n });\n }\n let payload;\n try {\n payload = await res.json();\n } catch {\n payload = {};\n }\n if (!current()) return;\n set({\n session: \"ready\",\n gate: null,\n artifactPhase: \"ready\",\n artifactRows: [...reset ? [] : state.artifactRows, ...payload.artifacts ?? []],\n artifactCursor: payload.nextCursor ?? null\n });\n }\n async function loadArtifactView() {\n const current = fence();\n const request = artifactViewRequest(window.location.pathname, window.location.search);\n set({ artifactPhase: \"loading\", artifactNotice: null });\n if (!request) {\n return set({\n session: \"ready\",\n artifactPhase: \"error\",\n artifactNotice: failure(\"There is no artifact at this address.\")\n });\n }\n const res = await artifactRead(request, current, \"artifact\");\n if (!res || !current()) return;\n if (!res.ok) {\n return set({\n session: \"ready\",\n gate: null,\n artifactPhase: \"error\",\n artifactNotice: failure(\n collectionFailureCopy(\"artifact\", { kind: \"refused\", status: res.status }, productName)\n )\n });\n }\n let view = null;\n try {\n view = await res.json();\n } catch {\n view = null;\n }\n if (!current()) return;\n if (!view || typeof view.document !== \"string\") {\n return set({\n session: \"ready\",\n gate: null,\n artifactPhase: \"error\",\n artifactNotice: failure(\"The artifact couldn't be read. Retry in a moment.\")\n });\n }\n set({ session: \"ready\", gate: null, artifactPhase: \"ready\", artifactView: view });\n }\n function setArtifactQuery(artifactQuery) {\n set({ artifactQuery });\n }\n function setArtifactArchived(artifactArchived) {\n set({ artifactArchived });\n void loadArtifacts(true);\n }\n function loadCurrent() {\n if (state.page === \"artifacts\") return loadArtifacts(true);\n if (state.page === \"artifact\") return loadArtifactView();\n return loadData();\n }\n function retryLoad() {\n set({\n pendingFocus: state.page === \"connections\" ? \"connectorLedgerHeading\" : `${state.page}Heading`\n });\n return loadCurrent();\n }\n function retryCollection() {\n set({ pendingFocus: `${state.page}Heading` });\n if (state.page === \"activity\") return loadActivity(true);\n if (state.page === \"artifact\") return loadArtifactView();\n return loadArtifacts(true);\n }\n function signIn() {\n window.Clerk?.redirectToSignIn({\n signInFallbackRedirectUrl: window.location.href,\n signUpFallbackRedirectUrl: window.location.href\n });\n }\n function signOut() {\n if (auth.kind === \"cloudflare-access\") {\n gate(null);\n window.location.assign(\"/cdn-cgi/access/logout\");\n return;\n }\n const clerk = window.Clerk;\n gate(null);\n void clerk?.signOut({ redirectUrl: window.location.href });\n }\n var returnTimer;\n var lastReturnPass = 0;\n var RETURN_DEBOUNCE_MS = 150;\n var RETURN_MIN_INTERVAL_MS = 2e3;\n function onReturn() {\n if (typeof document !== \"undefined\" && document.visibilityState === \"hidden\") return;\n if (returnTimer !== void 0) return;\n returnTimer = setTimeout(() => {\n returnTimer = void 0;\n const now = Date.now();\n if (now - lastReturnPass < RETURN_MIN_INTERVAL_MS) return;\n lastReturnPass = now;\n recheckAuthorization();\n }, RETURN_DEBOUNCE_MS);\n }\n function recheckAuthorization() {\n if (state.session !== \"ready\" || !state.data) return;\n for (const connector of state.data.connectors) {\n if (state.oauthBusy === connector.id) continue;\n const authorizesElsewhere = connector.status === \"auth_required\" && (connector.oauth === true || Boolean(connector.authorizationUrl));\n if (authorizesElsewhere || awaitingAuthorization.has(connector.id)) {\n void refreshConnector(connector.id, true);\n }\n }\n }\n async function boot() {\n const onPop = () => setPage(pageForPath(window.location.pathname), true);\n window.addEventListener(\"popstate\", onPop);\n window.addEventListener(\"focus\", onReturn);\n if (typeof document !== \"undefined\") {\n document.addEventListener(\"visibilitychange\", onReturn);\n }\n if (auth.kind === \"clerk\") {\n const clerk = window.Clerk;\n if (!clerk) {\n return gate(failure(\"Clerk couldn't load. Check your connection and try again.\"));\n }\n try {\n await clerk.load({\n ...auth.signInUrl ? { signInUrl: auth.signInUrl } : {},\n ...auth.signUpUrl ? { signUpUrl: auth.signUpUrl } : {},\n signInFallbackRedirectUrl: window.location.href,\n signUpFallbackRedirectUrl: window.location.href,\n afterSignOutUrl: window.location.href\n });\n let sessionId = clerk.session?.id ?? null;\n clerk.addListener((resources) => {\n const next = resources.session?.id ?? null;\n if (next === sessionId) return;\n sessionId = next;\n gate(null);\n void loadCurrent();\n });\n } catch {\n return gate(failure(\"Clerk couldn't start. Reload the page to try again.\"));\n }\n }\n await loadCurrent();\n }\n async function readConnector(id, token) {\n let response;\n try {\n response = await fetch(`/ui/connectors/${encodeURIComponent(id)}`, {\n headers: requestHeaders(token),\n credentials: \"same-origin\"\n });\n } catch {\n return { kind: \"local\", failure: \"network\" };\n }\n if (response.status === 401 || response.status === 403) {\n return { kind: \"local\", failure: \"session\" };\n }\n if (!response.ok) return { kind: \"downstream\" };\n try {\n return { kind: \"detail\", detail: await response.json() };\n } catch {\n return { kind: \"downstream\" };\n }\n }\n function withoutKey(record, key) {\n const { [key]: _gone, ...rest } = record;\n return rest;\n }\n function applyDetail(id, outcome) {\n if (!state.data) return;\n const patch = {};\n const connectors = state.data.connectors.map((c3) => {\n if (c3.id !== id) return c3;\n if (outcome.kind === \"detail\") {\n const { detail } = outcome;\n return detail.status === \"auth_required\" && c3.authorizationUrl && !detail.authorizationUrl ? { ...detail, authorizationUrl: c3.authorizationUrl } : detail;\n }\n const { problem: _problem, ...rest } = c3;\n return outcome.kind === \"downstream\" ? { ...rest, status: \"error\", problem: \"connector_unavailable\" } : { ...rest, status: \"error\" };\n });\n patch.connectorFailures = outcome.kind === \"local\" ? { ...state.connectorFailures, [id]: outcome.failure } : withoutKey(state.connectorFailures, id);\n if (outcome.kind === \"detail\" && outcome.detail.status === \"ok\" && awaitingAuthorization.delete(id)) {\n if (state.oauthNoticeFor === id) {\n patch.oauthNotice = info(\"Connected.\");\n patch.oauthBlocked = null;\n }\n }\n set({ ...patch, data: { ...state.data, connectors } });\n }\n var detailGeneration = 0;\n var detailRevisions = /* @__PURE__ */ new Map();\n async function loadConnectorDetails(data, current, token) {\n const generation = ++detailGeneration;\n let next = 0;\n const worker = async () => {\n while (next < data.connectors.length && current() && generation === detailGeneration) {\n const connector = data.connectors[next++];\n const revision = (detailRevisions.get(connector.id) ?? 0) + 1;\n detailRevisions.set(connector.id, revision);\n const outcome = await readConnector(connector.id, token);\n if (!current() || generation !== detailGeneration || !state.data) return;\n if (detailRevisions.get(connector.id) !== revision) continue;\n applyDetail(connector.id, outcome);\n }\n };\n await Promise.all(Array.from({ length: Math.min(4, data.connectors.length) }, worker));\n }\n async function refreshConnector(id, quiet = false) {\n const current = fence();\n const revision = (detailRevisions.get(id) ?? 0) + 1;\n detailRevisions.set(id, revision);\n if (!quiet && state.data) {\n set({\n data: { ...state.data, connectors: state.data.connectors.map((c3) => c3.id === id ? { ...c3, status: \"loading\" } : c3) },\n connectorFailures: withoutKey(state.connectorFailures, id)\n });\n }\n let token;\n try {\n token = await sessionToken();\n } catch {\n if (!current() || quiet || detailRevisions.get(id) !== revision) return;\n return applyDetail(id, { kind: \"local\", failure: \"session\" });\n }\n if (!current()) return;\n const outcome = await readConnector(id, token);\n if (!current() || !state.data || detailRevisions.get(id) !== revision) return;\n if (quiet && outcome.kind !== \"detail\") return;\n applyDetail(id, outcome);\n }\n async function loadAccessTokens() {\n const current = fence();\n set({ tokenPhase: \"loading\", tokenNotice: null });\n try {\n const payload = await operatorRequest(\"/ui/access-tokens\", \"GET\", current);\n if (!current()) return;\n set({ tokenPhase: \"ready\", tokens: payload?.accessTokens ?? [] });\n } catch {\n if (!current()) return;\n set({\n tokenPhase: \"error\",\n tokenNotice: failure(\n \"Access tokens could not be loaded.\"\n )\n });\n }\n }\n function tokenFailure(tokenNotice) {\n return { tokenBusy: false, tokenNotice, pendingFocus: \"tokenNotice\" };\n }\n function createAccessToken(name) {\n if (state.tokenBusy) return Promise.resolve(false);\n if (!name) {\n set(tokenFailure(failure(\"Name the MCP client before creating a token.\")));\n return Promise.resolve(false);\n }\n let created = false;\n return mutate({\n request: (current) => operatorRequest(\"/ui/access-tokens\", \"POST\", current, { name }),\n busy: { tokenBusy: true, tokenNotice: null },\n done: (payload) => {\n const issued = payload?.accessToken;\n if (!payload?.token || !issued) {\n throw new Error(\"The created token was not returned.\");\n }\n created = true;\n return {\n tokenBusy: false,\n tokenPhase: \"ready\",\n tokens: [\n issued,\n ...state.tokens.filter((token) => token.id !== issued.id)\n ],\n createdToken: state.page === \"tokens\" ? payload.token : null,\n tokenNotice: info(\"Access token created.\"),\n pendingFocus: state.page === \"tokens\" ? \"tokenRevealHeading\" : null\n };\n },\n failed: () => tokenFailure(failure(\"Access token could not be created. Check the name, capacity, and storage.\"))\n }).then(() => created);\n }\n function dismissCreatedToken() {\n set({ createdToken: null });\n }\n function renameAccessToken(id) {\n set({ tokenRenaming: id });\n }\n function accessTokenMutation(id, method, body, success, fallback) {\n return mutate({\n request: (current) => operatorRequest(\n `/ui/access-tokens/${encodeURIComponent(id)}`,\n method,\n current,\n body\n ),\n busy: { tokenBusy: true, tokenNotice: null },\n done: (payload) => ({\n tokenBusy: false,\n tokenRenaming: null,\n tokenNotice: info(success),\n pendingFocus: \"tokenNotice\",\n ...payload?.accessToken ? {\n tokens: state.tokens.map(\n (token) => token.id === id ? payload.accessToken : token\n )\n } : {}\n }),\n failed: () => tokenFailure(failure(fallback))\n });\n }\n function saveAccessTokenName(id, name) {\n return accessTokenMutation(\n id,\n \"PUT\",\n { name },\n \"Access token renamed.\",\n \"Access token could not be renamed.\"\n );\n }\n function revokeAccessToken(id) {\n const named = state.tokens.find((token) => token.id === id);\n const confirmed = window.confirm(\n `Revoke ${named?.name || \"this access token\"}? Its MCP client will immediately lose access.`\n );\n if (!confirmed) return Promise.resolve();\n return accessTokenMutation(\n id,\n \"DELETE\",\n void 0,\n \"Access token revoked.\",\n \"Access token could not be revoked.\"\n );\n }\n\n // node_modules/preact/jsx-runtime/dist/jsxRuntime.module.js\n var f3 = 0;\n function u3(e3, t3, n2, o3, i3, u4) {\n t3 || (t3 = {});\n var a3, c3, p3 = t3;\n if (\"ref\" in p3) for (c3 in p3 = {}, t3) \"ref\" == c3 ? a3 = t3[c3] : p3[c3] = t3[c3];\n var l3 = { type: e3, props: p3, key: n2, ref: a3, __k: null, __: null, __b: 0, __e: null, __c: null, constructor: void 0, __v: --f3, __i: -1, __u: 0, __source: i3, __self: u4 };\n if (\"function\" == typeof e3 && (a3 = e3.defaultProps)) for (c3 in a3) void 0 === p3[c3] && (p3[c3] = a3[c3]);\n return l.vnode && l.vnode(l3), l3;\n }\n\n // src/operator-ui/app/parts.tsx\n function NoticeLine({\n id,\n notice,\n className = \"meta\"\n }) {\n return /* @__PURE__ */ u3(S, { children: [\n /* @__PURE__ */ u3(\n \"p\",\n {\n id,\n class: notice?.tone === \"error\" ? `notice ${className} error-notice` : `notice ${className}`,\n role: notice?.tone === \"error\" ? \"alert\" : \"status\",\n \"aria-live\": \"polite\",\n tabIndex: -1,\n children: notice ? notice.message : null\n }\n ),\n notice?.tone === \"error\" && notice.fix ? /* @__PURE__ */ u3(FixPrompt, { kind: notice.fix.kind, connectorId: notice.fix.connectorId }) : null\n ] });\n }\n function FixPrompt({\n kind,\n connectorId,\n name\n }) {\n return /* @__PURE__ */ u3(\"div\", { class: \"fix-prompt\", \"data-fix-prompt\": kind, children: [\n /* @__PURE__ */ u3(FixPromptButton, { kind, connectorId, ...name ? { name } : {} }),\n /* @__PURE__ */ u3(FixPromptPreview, { kind, connectorId })\n ] });\n }\n function FixPromptButton({\n kind,\n connectorId,\n name\n }) {\n return /* @__PURE__ */ u3(\n CopyButton,\n {\n value: fixPrompt(kind, connectorId),\n label: \"Copy fix prompt\",\n class: \"btn quiet\",\n ariaLabel: `Copy fix prompt for ${name ?? connectorId}`\n }\n );\n }\n function FixPromptPreview({\n kind,\n connectorId,\n standalone\n }) {\n return /* @__PURE__ */ u3(\"details\", { class: \"fix-prompt-preview\", ...standalone ? { \"data-fix-prompt\": kind } : {}, children: [\n /* @__PURE__ */ u3(\"summary\", { class: \"disclosure\", children: \"Preview prompt\" }),\n /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: \"Fixed text for a coding agent working on this deployment. It carries no error details or secrets.\" }),\n /* @__PURE__ */ u3(\"pre\", { class: \"fix-prompt-text\", children: fixPrompt(kind, connectorId) })\n ] });\n }\n function Badge({\n tone = \"neutral\",\n children\n }) {\n return /* @__PURE__ */ u3(\"span\", { class: tone === \"neutral\" ? \"badge\" : `badge ${tone}`, children });\n }\n function StateBlock({\n title,\n children,\n tone = \"neutral\",\n action,\n id\n }) {\n return /* @__PURE__ */ u3(\n \"div\",\n {\n class: tone === \"error\" ? \"state-block error\" : \"state-block\",\n role: tone === \"error\" ? \"alert\" : \"status\",\n ...id ? { id } : {},\n children: [\n title ? /* @__PURE__ */ u3(\"p\", { class: \"state-title\", children: title }) : null,\n children ? /* @__PURE__ */ u3(\"p\", { class: \"state-copy\", children }) : null,\n action ? /* @__PURE__ */ u3(\n \"button\",\n {\n class: \"btn\",\n type: \"button\",\n onClick: action.onClick,\n ...action.id ? { id: action.id } : {},\n children: action.label\n }\n ) : null\n ]\n }\n );\n }\n function LoadFailure({ state: state2 }) {\n if (!state2.loadFailure) return null;\n const copy = loadFailureCopy(state2.loadFailure, productName);\n return /* @__PURE__ */ u3(\n StateBlock,\n {\n id: \"loadFailure\",\n tone: \"error\",\n title: copy.title,\n action: { label: \"Retry\", onClick: () => void retryLoad(), id: \"retryLoad\" },\n children: copy.body\n }\n );\n }\n function Empty({ children }) {\n return /* @__PURE__ */ u3(StateBlock, { children });\n }\n function Unavailable({ children }) {\n return /* @__PURE__ */ u3(\"div\", { class: \"unavailable\", children });\n }\n function ConfirmBar({\n id,\n question,\n confirm,\n onConfirm,\n onCancel\n }) {\n return /* @__PURE__ */ u3(\n \"div\",\n {\n class: \"confirm\",\n role: \"group\",\n \"aria-labelledby\": `confirm-question-${id}`,\n onKeyDown: (event) => {\n if (event.key !== \"Escape\") return;\n event.preventDefault();\n onCancel();\n },\n children: [\n /* @__PURE__ */ u3(\"p\", { id: `confirm-question-${id}`, children: question }),\n /* @__PURE__ */ u3(\"div\", { class: \"actions\", children: [\n /* @__PURE__ */ u3(\"button\", { class: \"btn danger\", type: \"button\", onClick: onConfirm, children: confirm }),\n /* @__PURE__ */ u3(\n \"button\",\n {\n id: `confirm-cancel-${id}`,\n class: \"btn quiet\",\n type: \"button\",\n onClick: onCancel,\n children: \"Cancel\"\n }\n )\n ] })\n ]\n }\n );\n }\n function focusableId(...ids) {\n for (const id of ids) {\n const element = document.getElementById(id);\n if (element && !element.disabled) return id;\n }\n return ids[ids.length - 1] ?? \"\";\n }\n function PageLink({\n page,\n class: className,\n current,\n children\n }) {\n const href = PAGE_META[page].path;\n return /* @__PURE__ */ u3(\n \"a\",\n {\n class: className,\n href,\n ...current ? { \"aria-current\": \"page\" } : {},\n onClick: (event) => {\n if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {\n return;\n }\n event.preventDefault();\n navigate(page, href);\n },\n children\n }\n );\n }\n function CopyButton({\n value,\n label,\n class: className = \"btn\",\n ariaLabel,\n id\n }) {\n const [status, setStatus] = d2(\"idle\");\n h2(() => {\n if (status === \"idle\") return;\n const timer = window.setTimeout(() => setStatus(\"idle\"), 1600);\n return () => window.clearTimeout(timer);\n }, [status]);\n return /* @__PURE__ */ u3(\n \"button\",\n {\n class: className,\n type: \"button\",\n ...id ? { id } : {},\n ...ariaLabel && status === \"idle\" ? { \"aria-label\": ariaLabel } : {},\n onClick: () => {\n const write = navigator.clipboard?.writeText(value) ?? Promise.reject(new Error(\"no clipboard\"));\n write.then(\n () => setStatus(\"copied\"),\n () => setStatus(\"failed\")\n );\n },\n children: status === \"copied\" ? \"Copied\" : status === \"failed\" ? \"Copy failed\" : label\n }\n );\n }\n\n // src/operator-ui/app/tokens.tsx\n function CreateForm({ busy }) {\n const [name, setName] = d2(\"\");\n return /* @__PURE__ */ u3(\n \"form\",\n {\n id: \"tokenCreateForm\",\n class: \"token-create\",\n onSubmit: (event) => {\n event.preventDefault();\n void createAccessToken(name.trim()).then((created) => {\n if (created) setName(\"\");\n });\n },\n children: [\n /* @__PURE__ */ u3(\"label\", { for: \"tokenName\", children: \"Client name\" }),\n /* @__PURE__ */ u3(\"div\", { class: \"row\", children: [\n /* @__PURE__ */ u3(\n \"input\",\n {\n id: \"tokenName\",\n type: \"text\",\n maxLength: 80,\n placeholder: \"Claude desktop, ChatGPT production…\",\n autocomplete: \"off\",\n value: name,\n onInput: (event) => setName(event.currentTarget.value)\n }\n ),\n /* @__PURE__ */ u3(\"button\", { id: \"createToken\", class: \"btn\", type: \"submit\", disabled: busy, children: busy ? \"Creating…\" : \"Create token\" })\n ] })\n ]\n }\n );\n }\n function Reveal({ token }) {\n return /* @__PURE__ */ u3(\n \"section\",\n {\n id: \"tokenReveal\",\n class: \"token-reveal\",\n \"aria-labelledby\": \"tokenRevealHeading\",\n children: [\n /* @__PURE__ */ u3(\"div\", { class: \"token-reveal-head\", children: [\n /* @__PURE__ */ u3(\"h2\", { id: \"tokenRevealHeading\", tabIndex: -1, children: \"Copy this token now\" }),\n /* @__PURE__ */ u3(\"span\", { class: \"cap\", children: \"Shown once\" })\n ] }),\n /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: \"Store it in the MCP client before leaving this page. It cannot be displayed again.\" }),\n /* @__PURE__ */ u3(\"div\", { class: \"endpoint-row token-secret\", children: [\n /* @__PURE__ */ u3(\"code\", { id: \"createdToken\", class: \"mono\", children: token }),\n /* @__PURE__ */ u3(CopyButton, { value: token, label: \"Copy token\" })\n ] }),\n /* @__PURE__ */ u3(\"button\", { class: \"btn\", type: \"button\", onClick: dismissCreatedToken, children: \"I stored it\" })\n ]\n }\n );\n }\n function TokenCard({\n token,\n renaming,\n busy\n }) {\n const [name, setName] = d2(token.name);\n const revoked = Boolean(token.revokedAt);\n return /* @__PURE__ */ u3(\n \"section\",\n {\n class: revoked ? \"token-card revoked\" : \"token-card\",\n \"aria-labelledby\": `access-token-${token.id}`,\n children: [\n /* @__PURE__ */ u3(\"div\", { class: \"token-card-head\", children: [\n /* @__PURE__ */ u3(\"div\", { children: [\n /* @__PURE__ */ u3(\"h2\", { id: `access-token-${token.id}`, children: token.name }),\n /* @__PURE__ */ u3(\"p\", { class: \"mono\", children: [\n token.tokenPrefix,\n \"…\"\n ] })\n ] }),\n /* @__PURE__ */ u3(\"div\", { class: \"cap\", children: revoked ? `Revoked ${formatDate(token.revokedAt)}` : `Created ${formatDate(token.createdAt)}` })\n ] }),\n /* @__PURE__ */ u3(\"div\", { class: \"credential-actions\", children: [\n /* @__PURE__ */ u3(\n \"button\",\n {\n class: \"btn\",\n type: \"button\",\n disabled: busy,\n onClick: () => {\n setName(token.name);\n renameAccessToken(renaming ? null : token.id);\n },\n children: \"Rename\"\n }\n ),\n revoked ? null : /* @__PURE__ */ u3(\n \"button\",\n {\n class: \"btn danger\",\n type: \"button\",\n disabled: busy,\n onClick: () => void revokeAccessToken(token.id),\n children: \"Revoke\"\n }\n )\n ] }),\n renaming ? /* @__PURE__ */ u3(\n \"form\",\n {\n class: \"credential-form\",\n onSubmit: (event) => {\n event.preventDefault();\n const next = name.trim();\n if (next) void saveAccessTokenName(token.id, next);\n },\n children: [\n /* @__PURE__ */ u3(\"label\", { class: \"visually-hidden\", for: `token-name-${token.id}`, children: \"Token name\" }),\n /* @__PURE__ */ u3(\n \"input\",\n {\n id: `token-name-${token.id}`,\n type: \"text\",\n maxLength: 80,\n autocomplete: \"off\",\n value: name,\n onInput: (event) => setName(event.currentTarget.value)\n }\n ),\n /* @__PURE__ */ u3(\"button\", { class: \"btn\", type: \"submit\", disabled: busy, children: \"Save name\" }),\n /* @__PURE__ */ u3(\n \"button\",\n {\n class: \"btn\",\n type: \"button\",\n disabled: busy,\n onClick: () => renameAccessToken(null),\n children: \"Cancel\"\n }\n )\n ]\n }\n ) : null\n ]\n }\n );\n }\n function TokensPage({ state: state2 }) {\n const available = state2.data?.accessTokenManagement === \"available\";\n return /* @__PURE__ */ u3(\"section\", { id: \"tokensView\", children: /* @__PURE__ */ u3(\"div\", { class: \"lead\", children: [\n /* @__PURE__ */ u3(\"h1\", { id: \"tokensHeading\", class: \"\", tabIndex: -1, children: \"Access tokens\" }),\n /* @__PURE__ */ u3(\"div\", { class: \"lead-copy\", children: [\n /* @__PURE__ */ u3(\"p\", { class: \"activity-copy\", children: \"Create named Bearer tokens for MCP clients. Each secret is shown once; revoke it when that client should lose access.\" }),\n /* @__PURE__ */ u3(NoticeLine, { id: \"tokenNotice\", notice: state2.tokenNotice }),\n !available ? /* @__PURE__ */ u3(Unavailable, { children: accessTokenUnavailableCopy(state2.data?.accessTokenManagement) }) : /* @__PURE__ */ u3(\"div\", { id: \"tokenAvailable\", children: [\n state2.createdToken ? /* @__PURE__ */ u3(Reveal, { token: state2.createdToken }) : /* @__PURE__ */ u3(CreateForm, { busy: state2.tokenBusy }),\n /* @__PURE__ */ u3(\n \"div\",\n {\n id: \"tokenList\",\n class: \"token-ledger\",\n \"aria-busy\": state2.tokenPhase === \"loading\" ? \"true\" : \"false\",\n children: state2.tokenPhase === \"loading\" ? /* @__PURE__ */ u3(Empty, { children: \"Loading access tokens…\" }) : state2.tokenPhase === \"error\" ? /* @__PURE__ */ u3(\"p\", { class: \"empty\", children: /* @__PURE__ */ u3(\n \"button\",\n {\n class: \"btn\",\n type: \"button\",\n onClick: () => void loadAccessTokens(),\n children: \"Try loading access tokens again\"\n }\n ) }) : state2.tokens.length === 0 ? /* @__PURE__ */ u3(Empty, { children: \"No access tokens yet. Name the first MCP client above.\" }) : state2.tokens.map((token) => /* @__PURE__ */ u3(\n TokenCard,\n {\n token,\n renaming: state2.tokenRenaming === token.id,\n busy: state2.tokenBusy\n },\n token.id\n ))\n }\n )\n ] })\n ] })\n ] }) });\n }\n\n // src/operator-ui/app/activity.tsx\n function ActivityRow({ event }) {\n const outcome = activityOutcomeClass(event.outcome);\n const badge = activityOutcomeBadge(event.outcome);\n const stableId = actorStableId(event.actor);\n return /* @__PURE__ */ u3(\"article\", { class: `activity-item ${outcome}`, children: [\n /* @__PURE__ */ u3(\"div\", { class: \"activity-stamp\", children: [\n /* @__PURE__ */ u3(\"span\", { class: `dot ${outcome}`, \"aria-hidden\": \"true\" }),\n /* @__PURE__ */ u3(\"div\", { children: [\n /* @__PURE__ */ u3(\"time\", { class: \"activity-time\", dateTime: event.occurredAt, children: formatDate(event.occurredAt) }),\n /* @__PURE__ */ u3(\"div\", { class: \"activity-actor\", children: actorLabel(event.actor) }),\n stableId ? /* @__PURE__ */ u3(\"div\", { class: \"activity-actor-id mono\", children: stableId }) : null\n ] })\n ] }),\n /* @__PURE__ */ u3(\"div\", { children: [\n /* @__PURE__ */ u3(\"div\", { class: \"activity-address\", children: event.address }),\n /* @__PURE__ */ u3(\"div\", { class: \"activity-detail\", children: activityDetail(event) })\n ] }),\n /* @__PURE__ */ u3(\"div\", { class: \"activity-result\", children: [\n /* @__PURE__ */ u3(Badge, { tone: badge.tone, children: badge.label }),\n /* @__PURE__ */ u3(\"div\", { class: \"activity-detail\", children: [\n event.durationMs,\n \" ms\"\n ] })\n ] })\n ] });\n }\n function ActivityPage({ state: state2 }) {\n const data = state2.data;\n const enabled = Boolean(data?.activityEnabled);\n const loading = state2.activityPhase === \"loading\";\n const visible = filterActivity(state2.activityEvents, state2.activitySearch);\n const summary = activitySummary(state2.activityEvents);\n const failed = state2.activityPhase === \"error\";\n return /* @__PURE__ */ u3(\"section\", { id: \"activityView\", children: [\n /* @__PURE__ */ u3(\"div\", { class: \"lead\", children: [\n /* @__PURE__ */ u3(\"h1\", { id: \"activityHeading\", tabIndex: -1, children: \"Activity\" }),\n /* @__PURE__ */ u3(\"div\", { class: \"lead-copy\", children: /* @__PURE__ */ u3(\"p\", { children: pageDescription(\"activity\", productDescription) }) })\n ] }),\n !data ? (\n // Whether Activity is open to this identity is in /ui/data, which\n // has not answered yet — or could not.\n state2.loadFailure ? /* @__PURE__ */ u3(LoadFailure, { state: state2 }) : /* @__PURE__ */ u3(StateBlock, { children: \"Loading activity…\" })\n ) : !enabled ? /* @__PURE__ */ u3(Unavailable, { children: [\n \"Activity history is not configured. Add an\",\n \" \",\n /* @__PURE__ */ u3(\"span\", { class: \"mono\", children: \"activity.store\" }),\n \" with a list reader to enable this page.\"\n ] }) : /* @__PURE__ */ u3(\"div\", { id: \"activityAvailable\", class: \"collection\", children: [\n failed && state2.activityEvents.length === 0 ? null : /* @__PURE__ */ u3(\"div\", { class: \"row\", children: [\n /* @__PURE__ */ u3(\n \"input\",\n {\n id: \"activitySearch\",\n type: \"search\",\n placeholder: \"Search user, tool, or outcome…\",\n \"aria-label\": \"Search loaded activity\",\n value: state2.activitySearch,\n onInput: (event) => setActivitySearch(event.currentTarget.value)\n }\n ),\n /* @__PURE__ */ u3(\n \"button\",\n {\n id: \"refreshActivity\",\n class: \"btn\",\n type: \"button\",\n disabled: loading,\n onClick: () => void loadActivity(true),\n children: loading ? \"Loading…\" : \"Refresh\"\n }\n )\n ] }),\n /* @__PURE__ */ u3(\"p\", { id: \"activitySummary\", class: \"meta\", \"aria-live\": \"polite\", children: summary }),\n state2.activityEvents.length === 0 ? loading ? /* @__PURE__ */ u3(StateBlock, { children: \"Loading activity…\" }) : failed ? /* @__PURE__ */ u3(\n StateBlock,\n {\n id: \"activityError\",\n tone: \"error\",\n title: \"Activity couldn't be loaded\",\n action: { label: \"Retry\", onClick: () => void retryCollection() },\n children: state2.activityNotice?.message\n }\n ) : /* @__PURE__ */ u3(StateBlock, { children: state2.activitySearch.trim() ? \"No loaded activity matches this search.\" : \"No connector tool calls recorded yet.\" }) : visible.length === 0 ? /* @__PURE__ */ u3(StateBlock, { children: \"No loaded activity matches this search.\" }) : /* @__PURE__ */ u3(\n \"div\",\n {\n id: \"activityList\",\n class: \"activity-list\",\n \"aria-busy\": loading ? \"true\" : \"false\",\n children: visible.map((event, index) => /* @__PURE__ */ u3(\n ActivityRow,\n {\n event\n },\n `${event.occurredAt}-${event.address}-${index}`\n ))\n }\n ),\n state2.activityEvents.length > 0 ? /* @__PURE__ */ u3(NoticeLine, { id: \"activityNotice\", notice: state2.activityNotice }) : null,\n state2.activityCursor ? /* @__PURE__ */ u3(\n \"button\",\n {\n id: \"moreActivity\",\n class: \"btn activity-more\",\n type: \"button\",\n disabled: loading,\n onClick: () => void loadActivity(false),\n children: loading ? \"Loading…\" : \"Load older\"\n }\n ) : null\n ] })\n ] });\n }\n\n // src/operator-ui/app/artifacts.tsx\n function ArtifactRow({ row }) {\n const refresh = artifactRefreshBadge(row.freshness?.last);\n return /* @__PURE__ */ u3(\"article\", { class: \"artifact-row\", children: [\n /* @__PURE__ */ u3(\"div\", { children: [\n /* @__PURE__ */ u3(\"a\", { class: \"artifact-title\", href: `/artifacts/${row.id}`, children: row.title }),\n /* @__PURE__ */ u3(\"div\", { class: \"artifact-meta\", children: [\n /* @__PURE__ */ u3(\"span\", { class: \"mono\", children: row.id }),\n \" · version \",\n row.viewVersion,\n \" · updated\",\n \" \",\n /* @__PURE__ */ u3(\"time\", { dateTime: row.updatedAt, children: formatDate(row.updatedAt) }),\n \" by \",\n row.updatedBy.label\n ] })\n ] }),\n /* @__PURE__ */ u3(\"div\", { class: \"artifact-badges\", children: [\n /* @__PURE__ */ u3(Badge, { children: row.kind === \"markdown\" ? \"Markdown\" : \"HTML\" }),\n row.freshness?.state === \"stale\" ? /* @__PURE__ */ u3(Badge, { tone: \"warn\", children: \"Stale data\" }) : row.freshness?.state === \"current\" ? /* @__PURE__ */ u3(Badge, { children: \"Current data\" }) : null,\n refresh ? /* @__PURE__ */ u3(Badge, { tone: refresh.tone, children: refresh.label }) : null,\n row.archived ? /* @__PURE__ */ u3(Badge, { tone: \"warn\", children: \"Archived\" }) : null\n ] })\n ] });\n }\n function ArtifactsPage({ state: state2 }) {\n const loading = state2.artifactPhase === \"loading\";\n const rows = state2.artifactRows;\n return /* @__PURE__ */ u3(\"section\", { id: \"artifactsView\", children: [\n /* @__PURE__ */ u3(\"div\", { class: \"lead\", children: [\n /* @__PURE__ */ u3(\"h1\", { id: \"artifactsHeading\", tabIndex: -1, children: \"Artifacts\" }),\n /* @__PURE__ */ u3(\"div\", { class: \"lead-copy\", children: /* @__PURE__ */ u3(\"p\", { children: pageDescription(\"artifacts\", productDescription) }) })\n ] }),\n /* @__PURE__ */ u3(\"div\", { class: \"collection\", children: [\n /* @__PURE__ */ u3(\n \"form\",\n {\n class: \"row\",\n onSubmit: (event) => {\n event.preventDefault();\n void loadArtifacts(true);\n },\n children: [\n /* @__PURE__ */ u3(\n \"input\",\n {\n id: \"artifactSearch\",\n type: \"search\",\n placeholder: \"Search titles…\",\n \"aria-label\": \"Search artifact titles\",\n value: state2.artifactQuery,\n onInput: (event) => setArtifactQuery(event.currentTarget.value)\n }\n ),\n /* @__PURE__ */ u3(\"button\", { id: \"searchArtifacts\", class: \"btn\", type: \"submit\", disabled: loading, children: \"Search\" }),\n /* @__PURE__ */ u3(\"label\", { class: \"check artifact-meta\", children: [\n /* @__PURE__ */ u3(\n \"input\",\n {\n id: \"showArchived\",\n type: \"checkbox\",\n checked: state2.artifactArchived,\n onChange: (event) => setArtifactArchived(event.currentTarget.checked)\n }\n ),\n \" \",\n \"Show archived\"\n ] })\n ]\n }\n ),\n state2.artifactPhase === \"error\" && rows.length === 0 ? /* @__PURE__ */ u3(\n StateBlock,\n {\n id: \"artifactError\",\n tone: \"error\",\n title: \"Artifacts couldn't be loaded\",\n action: { label: \"Retry\", onClick: () => void retryCollection() },\n children: state2.artifactNotice?.message\n }\n ) : rows.length === 0 ? /* @__PURE__ */ u3(StateBlock, { children: loading ? \"Loading artifacts…\" : state2.artifactQuery.trim() ? \"No artifact title matches this search.\" : \"No artifacts yet. Ask an agent to publish one.\" }) : /* @__PURE__ */ u3(\"div\", { id: \"artifactList\", class: \"activity-list\", \"aria-busy\": loading ? \"true\" : \"false\", children: rows.map((row) => /* @__PURE__ */ u3(ArtifactRow, { row }, row.id)) }),\n rows.length > 0 ? /* @__PURE__ */ u3(NoticeLine, { id: \"artifactNotice\", notice: state2.artifactNotice }) : null,\n state2.artifactCursor ? /* @__PURE__ */ u3(\n \"button\",\n {\n id: \"moreArtifacts\",\n class: \"btn activity-more\",\n type: \"button\",\n disabled: loading,\n onClick: () => void loadArtifacts(false),\n children: loading ? \"Loading…\" : \"Load more\"\n }\n ) : null\n ] })\n ] });\n }\n function ArtifactFrame({ view }) {\n const frame = A2(null);\n _2(() => {\n const element = frame.current;\n if (!element) return;\n const onMessage = (event) => {\n if (event.source !== element.contentWindow || event.origin !== \"null\") return;\n const data = event.data;\n if (!data || data.type !== \"ready\") return;\n window.removeEventListener(\"message\", onMessage);\n const policy = document.createElement(\"meta\");\n policy.httpEquiv = \"Content-Security-Policy\";\n policy.content = \"frame-src 'none'\";\n document.head.append(policy);\n element.contentWindow?.postMessage({ type: \"document\", html: view.document }, \"*\");\n };\n window.addEventListener(\"message\", onMessage);\n return () => window.removeEventListener(\"message\", onMessage);\n }, [view.document]);\n return /* @__PURE__ */ u3(\n \"iframe\",\n {\n ref: frame,\n id: \"artifactFrame\",\n class: \"artifact-frame\",\n title: view.title,\n sandbox: \"allow-scripts\",\n referrerpolicy: \"no-referrer\",\n src: \"/artifacts/_frame\"\n }\n );\n }\n function ArtifactPage({ state: state2 }) {\n const view = state2.artifactView;\n const failed = state2.artifactPhase === \"error\";\n return /* @__PURE__ */ u3(\"section\", { id: \"artifactView\", children: [\n /* @__PURE__ */ u3(\"div\", { class: \"artifact-head\", children: [\n /* @__PURE__ */ u3(\"h1\", { id: \"artifactHeading\", tabIndex: -1, children: view?.title ?? \"Artifact\" }),\n view ? /* @__PURE__ */ u3(\"div\", { class: \"artifact-meta\", id: \"artifactMeta\", children: [\n view.snapshot ? \"Snapshot of \" : \"\",\n \"version \",\n view.view.version,\n view.snapshot && view.view.version !== view.latestViewVersion ? ` (latest is ${view.latestViewVersion})` : \"\",\n \" \",\n \"· updated \",\n /* @__PURE__ */ u3(\"time\", { dateTime: view.view.at, children: formatDate(view.view.at) }),\n \" by\",\n \" \",\n view.view.by.label,\n \" ·\",\n \" \",\n /* @__PURE__ */ u3(\"a\", { href: \"/artifacts\", children: \"All artifacts\" }),\n view.snapshot ? /* @__PURE__ */ u3(S, { children: [\n \" \",\n \"· \",\n /* @__PURE__ */ u3(\"a\", { href: view.url, children: \"Current version\" })\n ] }) : /* @__PURE__ */ u3(S, { children: [\n \" \",\n \"·\",\n \" \",\n /* @__PURE__ */ u3(\n CopyButton,\n {\n id: \"copySnapshot\",\n class: \"navlink inline\",\n value: view.snapshotUrl,\n label: \"Copy snapshot link\"\n }\n )\n ] })\n ] }) : null,\n view?.archived ? /* @__PURE__ */ u3(\"div\", { id: \"archivedBanner\", class: \"artifact-banner\", role: \"status\", children: \"This artifact is archived. It keeps every version, and an agent can restore it.\" }) : null,\n !view?.snapshot && view?.freshness?.state === \"stale\" ? /* @__PURE__ */ u3(\"div\", { id: \"staleBanner\", class: \"artifact-banner\", role: \"status\", children: [\n \"Data may be out of date. The last refresh \",\n view.freshness.last?.status === \"failed\" ? \"failed\" : \"is overdue\",\n \"; the last good document is still shown.\"\n ] }) : null,\n !view && !failed ? /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: pageDescription(\"artifact\", productDescription) }) : null\n ] }),\n view ? /* @__PURE__ */ u3(ArtifactFrame, { view }, view.snapshotUrl) : failed ? /* @__PURE__ */ u3(\n StateBlock,\n {\n id: \"artifactError\",\n tone: \"error\",\n title: \"This artifact couldn't be opened\",\n action: { label: \"Retry\", onClick: () => void retryCollection() },\n children: state2.artifactNotice?.message\n }\n ) : /* @__PURE__ */ u3(StateBlock, { children: \"Loading artifact…\" })\n ] });\n }\n\n // src/operator-ui/app/credentials.tsx\n function CredentialForm({\n connector,\n credential,\n busy\n }) {\n const fields = credential.fields ?? [];\n const [values, setValues] = d2({});\n const single = fields.length === 0;\n const inputId = `credential-input-${connector}`;\n const submit = () => {\n if (single) {\n const value = (values.value ?? \"\").trim();\n if (!value) return refuseCredential(connector, \"Paste a credential before saving.\");\n return void saveCredential(connector, { value });\n }\n const entries = {};\n for (const field of fields) {\n const value = (values[field.name] ?? \"\").trim();\n if (!value) {\n return refuseCredential(\n connector,\n \"Complete every credential field before saving.\"\n );\n }\n entries[field.name] = value;\n }\n void saveCredential(connector, { values: entries });\n };\n return /* @__PURE__ */ u3(\"div\", { class: \"credential-form\", \"data-credential-form\": connector, children: [\n single ? /* @__PURE__ */ u3(S, { children: [\n /* @__PURE__ */ u3(\"label\", { class: \"visually-hidden\", for: inputId, children: credential.label }),\n /* @__PURE__ */ u3(\n \"input\",\n {\n id: inputId,\n type: \"password\",\n \"aria-label\": credential.label,\n placeholder: credential.placeholder || \"Paste credential\",\n autocomplete: \"new-password\",\n autocapitalize: \"none\",\n spellcheck: false,\n value: values.value ?? \"\",\n onInput: (event) => setValues({ value: event.currentTarget.value })\n }\n )\n ] }) : /* @__PURE__ */ u3(\"div\", { class: \"credential-fields\", children: fields.map((field, index) => {\n const id = `credential-input-${connector}-${index}`;\n return /* @__PURE__ */ u3(\"div\", { class: \"credential-field\", children: [\n /* @__PURE__ */ u3(\"label\", { for: id, children: field.label }),\n /* @__PURE__ */ u3(\n \"input\",\n {\n id,\n type: field.inputType || \"password\",\n placeholder: field.placeholder || field.label,\n autocomplete: (field.inputType ?? \"password\") === \"password\" ? \"new-password\" : \"off\",\n autocapitalize: \"none\",\n spellcheck: false,\n value: values[field.name] ?? \"\",\n onInput: (event) => setValues({\n ...values,\n [field.name]: event.currentTarget.value\n })\n }\n )\n ] }, field.name);\n }) }),\n /* @__PURE__ */ u3(\"div\", { class: \"actions\", children: [\n /* @__PURE__ */ u3(\"button\", { class: \"btn primary\", type: \"button\", disabled: busy, onClick: submit, children: busy ? \"Saving…\" : \"Save\" }),\n /* @__PURE__ */ u3(\n \"button\",\n {\n class: \"btn quiet\",\n type: \"button\",\n disabled: busy,\n onClick: () => editCredential(null),\n children: \"Cancel\"\n }\n )\n ] })\n ] });\n }\n function CredentialCard({\n connector,\n credential,\n editing,\n busy,\n confirming,\n notice\n }) {\n const name = connector.title || connector.id;\n const configured = Boolean(credential.configured);\n const removable = configured || Boolean(credential.removable);\n return /* @__PURE__ */ u3(\n \"section\",\n {\n class: \"subcard\",\n id: `credential-${connector.id}`,\n \"aria-labelledby\": `credential-title-${connector.id}`,\n children: [\n /* @__PURE__ */ u3(\"div\", { class: \"subcard-head\", children: [\n /* @__PURE__ */ u3(\"h3\", { id: `credential-title-${connector.id}`, children: credential.label }),\n /* @__PURE__ */ u3(\"span\", { class: \"meta\", children: credentialStateLabel(credential) })\n ] }),\n credential.description ? /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: credential.description }) : null,\n credential.fields?.length ? /* @__PURE__ */ u3(\"div\", { class: \"credential-field-summary\", children: credential.fields.map((field) => /* @__PURE__ */ u3(\"div\", { children: [\n /* @__PURE__ */ u3(\"span\", { children: field.label }),\n /* @__PURE__ */ u3(\"span\", { class: \"meta\", children: field.configured ? `configured · ••••${field.lastFour ?? \"\"}${field.updatedAt ? ` · updated ${formatDate(field.updatedAt)}` : \"\"}` : \"not configured\" })\n ] }, field.name)) }) : null,\n credential.error ? /* @__PURE__ */ u3(\"p\", { class: \"msg\", children: credentialProblemCopy(credential.problem) }) : null,\n credential.error && credential.problem ? /* @__PURE__ */ u3(\n FixPrompt,\n {\n kind: credential.problem,\n connectorId: connector.id,\n name: connector.title || connector.id\n }\n ) : null,\n credential.notice ? /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: credential.notice }) : null,\n /* @__PURE__ */ u3(\"div\", { class: \"actions\", children: [\n /* @__PURE__ */ u3(\n \"button\",\n {\n class: removable ? \"btn\" : \"btn primary\",\n type: \"button\",\n \"aria-expanded\": editing ? \"true\" : \"false\",\n disabled: busy,\n onClick: () => editCredential(editing ? null : connector.id),\n children: removable ? \"Replace\" : \"Add credential\"\n }\n ),\n configured && credential.testable ? /* @__PURE__ */ u3(\n \"button\",\n {\n class: \"btn\",\n type: \"button\",\n disabled: busy,\n onClick: () => void testCredential(connector.id),\n children: busy ? \"Working…\" : \"Test\"\n }\n ) : null,\n removable ? /* @__PURE__ */ u3(\n \"button\",\n {\n id: `remove-credential-${connector.id}`,\n class: \"btn danger\",\n type: \"button\",\n disabled: busy,\n onClick: () => askConfirm(connector.id, \"credential_remove\"),\n children: \"Remove\"\n }\n ) : null\n ] }),\n confirming ? /* @__PURE__ */ u3(\n ConfirmBar,\n {\n id: connector.id,\n ...confirmCopy(\"credential_remove\", name),\n onConfirm: () => void removeCredential(connector.id),\n onCancel: () => cancelConfirm(\n focusableId(`remove-credential-${connector.id}`, `conn-toggle-${connector.id}`)\n )\n }\n ) : null,\n editing ? /* @__PURE__ */ u3(\n CredentialForm,\n {\n connector: connector.id,\n credential,\n busy\n }\n ) : null,\n /* @__PURE__ */ u3(NoticeLine, { id: `credentialNotice-${connector.id}`, notice })\n ]\n }\n );\n }\n\n // src/operator-ui/model.ts\n function filterUiConnectors(connectors, query) {\n const q2 = query.trim().toLowerCase();\n const filtered = [];\n for (const connector of connectors) {\n const connectorText = [\n connector.id,\n connector.title,\n connector.description,\n connector.status\n ].join(\" \").toLowerCase();\n const connectorMatches = Boolean(q2 && connectorText.includes(q2));\n const tools = connector.tools.filter(\n (tool) => !q2 || connectorMatches || `${tool.name} ${tool.description ?? \"\"}`.toLowerCase().includes(q2)\n );\n if (q2 && tools.length === 0 && !connectorMatches) continue;\n filtered.push({ connector, tools });\n }\n return filtered;\n }\n\n // src/operator-ui/setup-commands.ts\n function clientServerName(serverName, pool) {\n const base = (serverName ?? \"\").toLowerCase().replace(/[^a-z0-9_-]+/g, \"-\").replace(/-{2,}/g, \"-\").replace(/^-+|-+$/g, \"\").slice(0, 48) || \"connecta\";\n return pool ? `${base}-${pool}` : base;\n }\n function poolEndpointUrl(mcpUrl2, pool) {\n return `${mcpUrl2.replace(/\\/+$/, \"\")}/${encodeURIComponent(pool)}`;\n }\n function shellWord(value) {\n return /^[A-Za-z0-9_./:@%+=,~-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\"'\"'`)}'`;\n }\n function clientSetupCommands(name, url) {\n return [\n {\n id: \"claude\",\n label: \"Claude Code\",\n text: `claude mcp add --transport http ${shellWord(name)} ${shellWord(url)}`\n },\n {\n id: \"codex\",\n label: \"Codex\",\n text: `codex mcp add ${shellWord(name)} --url ${shellWord(url)}`\n },\n {\n id: \"json\",\n label: \"JSON config\",\n text: JSON.stringify(\n { mcpServers: { [name]: { type: \"http\", url } } },\n null,\n 2\n )\n }\n ];\n }\n\n // src/operator-ui/app/connections.tsx\n var DRIFT_HEADING = {\n clean: \"Catalog drift · none\",\n warning: \"Catalog drift · review\",\n unavailable: \"Catalog drift · not observed\"\n };\n function DriftPanel({ connector }) {\n const drift = connector.catalogDrift;\n const state2 = driftState(drift);\n return /* @__PURE__ */ u3(S, { children: [\n /* @__PURE__ */ u3(\n \"div\",\n {\n id: `drift-${connector.id}`,\n class: `connector-drift ${state2}`,\n \"data-drift\": state2,\n children: [\n /* @__PURE__ */ u3(\"p\", { class: \"cap\", children: DRIFT_HEADING[state2] }),\n /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: driftSummary(drift) }),\n state2 === \"unavailable\" ? null : /* @__PURE__ */ u3(\"ul\", { class: \"drift-counts\", children: driftCounts(drift).map(({ key, label, count }) => /* @__PURE__ */ u3(\"li\", { class: count > 0 ? \"drift-count flagged\" : \"drift-count\", children: [\n /* @__PURE__ */ u3(\"span\", { class: \"drift-count-value\", children: count }),\n /* @__PURE__ */ u3(\"span\", { class: \"drift-count-label\", children: label })\n ] }, key)) })\n ]\n }\n ),\n state2 === \"warning\" ? /* @__PURE__ */ u3(\n FixPrompt,\n {\n kind: \"catalog_drift\",\n connectorId: connector.id,\n name: connector.title || connector.id\n }\n ) : null\n ] });\n }\n function SafetyBadge({ safety }) {\n const badge = safety ? TOOL_SAFETY_BADGE[safety] : void 0;\n if (!badge) return null;\n return /* @__PURE__ */ u3(\"span\", { class: \"tool-safety\", title: badge.title, \"data-safety\": safety, children: /* @__PURE__ */ u3(Badge, { tone: badge.tone, children: badge.label }) });\n }\n function Endpoint({\n url,\n name,\n label,\n primary\n }) {\n return /* @__PURE__ */ u3(\"div\", { class: \"endpoint-block\", \"data-endpoint\": name, children: [\n /* @__PURE__ */ u3(\"div\", { class: \"endpoint\", children: [\n label ? /* @__PURE__ */ u3(\"span\", { class: \"endpoint-label cap\", children: label }) : null,\n /* @__PURE__ */ u3(\"code\", { ...primary ? { id: \"mcpUrl\" } : {}, class: \"mono\", children: url }),\n /* @__PURE__ */ u3(\n CopyButton,\n {\n value: url,\n label: \"Copy URL\",\n ...label ? { ariaLabel: `Copy URL for ${label}` } : {}\n }\n )\n ] }),\n /* @__PURE__ */ u3(\"details\", { class: \"setup\", children: [\n /* @__PURE__ */ u3(\"summary\", { class: \"disclosure\", children: [\n \"Client setup\",\n label ? ` · ${label}` : \"\"\n ] }),\n /* @__PURE__ */ u3(\"div\", { class: \"setup-list\", children: [\n clientSetupCommands(name, url).map((command) => /* @__PURE__ */ u3(\"div\", { class: \"setup-item\", \"data-setup\": command.id, children: [\n /* @__PURE__ */ u3(\"div\", { class: \"setup-head\", children: [\n /* @__PURE__ */ u3(\"span\", { class: \"cap\", children: command.label }),\n /* @__PURE__ */ u3(\n CopyButton,\n {\n value: command.text,\n label: \"Copy\",\n class: \"btn quiet\",\n ariaLabel: `Copy ${command.label} setup${label ? ` for ${label}` : \"\"}`\n }\n )\n ] }),\n /* @__PURE__ */ u3(\"pre\", { class: \"setup-code\", children: command.text })\n ] }, command.id)),\n /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: \"No token is included. Clients sign in through this deployment's inbound auth.\" })\n ] })\n ] })\n ] });\n }\n function AuthActions({\n connector,\n name,\n manage,\n state: state2\n }) {\n const id = connector.id;\n const authorization = safeHttpHref(connector.authorizationUrl);\n const busy = state2.oauthBusy === id;\n if (connector.status === \"loading\") return null;\n if (!connector.oauth || !manage) {\n return authorization && connector.status !== \"ok\" ? /* @__PURE__ */ u3(\"a\", { class: \"btn primary\", href: authorization, target: \"_blank\", rel: \"noopener noreferrer\", children: \"Authorize connector\" }) : null;\n }\n if (state2.oauthBlocked === id && authorization) {\n return /* @__PURE__ */ u3(\n \"a\",\n {\n id: `authorize-${id}`,\n class: \"btn primary\",\n href: authorization,\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n children: \"Open authorization page\"\n }\n );\n }\n if (connector.status !== \"ok\") {\n const needsAuth = connector.status === \"auth_required\";\n return /* @__PURE__ */ u3(\n \"button\",\n {\n type: \"button\",\n id: `connect-${id}`,\n class: needsAuth ? \"btn primary\" : \"btn\",\n \"aria-label\": `${needsAuth ? \"Connect\" : \"Reconnect\"} ${name}`,\n disabled: busy,\n onClick: () => void startOAuth(id, \"continue\"),\n children: busy ? \"Opening…\" : needsAuth ? \"Connect account\" : \"Reconnect\"\n }\n );\n }\n const switching = connector.authScope === \"personal\";\n return /* @__PURE__ */ u3(S, { children: [\n /* @__PURE__ */ u3(\n \"button\",\n {\n type: \"button\",\n id: `reconnect-${id}`,\n class: \"btn\",\n \"aria-label\": `${switching ? \"Switch account for\" : \"Reconnect\"} ${name}`,\n disabled: busy,\n onClick: () => askConfirm(id, \"oauth_restart\"),\n children: busy ? \"Working…\" : switching ? \"Switch account\" : \"Reconnect\"\n }\n ),\n /* @__PURE__ */ u3(\n \"button\",\n {\n type: \"button\",\n id: `disconnect-${id}`,\n class: \"btn danger\",\n \"aria-label\": `Disconnect ${name}`,\n disabled: busy,\n onClick: () => askConfirm(id, \"oauth_disconnect\"),\n children: \"Disconnect\"\n }\n )\n ] });\n }\n function ConnectorRow({\n connector,\n tools,\n forceOpen,\n state: state2\n }) {\n const [open, setOpen] = d2(false);\n const shown = open || forceOpen;\n const id = connector.id;\n const name = connector.title || id;\n const drift = driftState(connector.catalogDrift);\n const manage = Boolean(\n connector.permissions?.manageSharedAuth || connector.permissions?.connectPersonal\n );\n const local = state2.connectorFailures[id];\n const problem = connector.status === \"loading\" || local ? null : problemCopy(connector.problem);\n const confirming = state2.confirming?.connectorId === id ? state2.confirming : null;\n const oauthConfirm = confirming && confirming.action !== \"credential_remove\" ? confirming : null;\n const statusLabel = local ? \"Couldn't load\" : connectorStatusLabel(connector.status, connector.problem);\n const fixKind = problem && connector.problem && problemTone(connector.problem) === \"danger\" && connector.problem !== connector.credential?.problem ? connector.problem : null;\n const statusTone = local ? \"warn\" : connectorStatusTone(connector.status);\n return /* @__PURE__ */ u3(\"div\", { class: shown ? \"conn open\" : \"conn\", \"data-connector\": id, children: [\n /* @__PURE__ */ u3(\"div\", { class: \"conn-head\", children: [\n /* @__PURE__ */ u3(\"span\", { class: \"conn-main\", children: [\n /* @__PURE__ */ u3(\"span\", { class: `dot ${local ? \"warn\" : connector.status}`, \"aria-hidden\": \"true\" }),\n /* @__PURE__ */ u3(\"h2\", { class: \"conn-name\", children: /* @__PURE__ */ u3(\n \"button\",\n {\n type: \"button\",\n id: `conn-toggle-${id}`,\n class: \"conn-toggle\",\n \"aria-expanded\": shown ? \"true\" : \"false\",\n \"aria-controls\": `conn-body-${id}`,\n \"aria-describedby\": `conn-state-${id}`,\n onClick: () => setOpen(!shown),\n children: name\n }\n ) }),\n connector.title ? /* @__PURE__ */ u3(\"span\", { class: \"conn-id mono\", children: id }) : null\n ] }),\n /* @__PURE__ */ u3(\"span\", { class: \"conn-badges\", id: `conn-state-${id}`, children: [\n drift === \"warning\" ? /* @__PURE__ */ u3(Badge, { tone: \"warn\", children: \"drift\" }) : null,\n /* @__PURE__ */ u3(Badge, { children: authScopeLabel(connector.authScope) }),\n /* @__PURE__ */ u3(Badge, { children: connector.status === \"loading\" ? \"tools not loaded\" : toolCountLabel(connector.toolCount) }),\n /* @__PURE__ */ u3(Badge, { tone: statusTone, children: statusLabel }),\n /* @__PURE__ */ u3(\"span\", { class: \"conn-caret\", \"aria-hidden\": \"true\" })\n ] })\n ] }),\n /* @__PURE__ */ u3(\"div\", { class: \"conn-body\", id: `conn-body-${id}`, hidden: !shown, children: [\n connector.description ? /* @__PURE__ */ u3(\"p\", { class: \"conn-note\", children: connector.description }) : null,\n problem && connector.problem ? /* @__PURE__ */ u3(\n \"p\",\n {\n class: problemTone(connector.problem) === \"warn\" ? \"msg warn\" : \"msg\",\n \"data-problem\": connector.problem,\n children: problem\n }\n ) : null,\n local ? /* @__PURE__ */ u3(\"p\", { class: \"msg warn\", \"data-load-failure\": local, children: connectorLoadFailureCopy(local, productName) }) : null,\n connector.authorizationUrl && !safeHttpHref(connector.authorizationUrl) ? /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: [\n \"Authorization URL: \",\n connector.authorizationUrl\n ] }) : null,\n manage ? null : /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: permissionLabel(connector) }),\n /* @__PURE__ */ u3(\"div\", { class: \"actions\", children: [\n local ? null : /* @__PURE__ */ u3(AuthActions, { connector, name, manage, state: state2 }),\n fixKind ? /* @__PURE__ */ u3(FixPromptButton, { kind: fixKind, connectorId: id, name }) : null,\n /* @__PURE__ */ u3(\n \"button\",\n {\n class: local ? \"btn primary\" : \"btn quiet\",\n type: \"button\",\n \"aria-label\": `Refresh ${name}`,\n disabled: connector.status === \"loading\",\n onClick: () => void refreshConnector(id),\n children: \"Refresh\"\n }\n )\n ] }),\n fixKind ? /* @__PURE__ */ u3(FixPromptPreview, { kind: fixKind, connectorId: id, standalone: true }) : null,\n oauthConfirm ? /* @__PURE__ */ u3(\n ConfirmBar,\n {\n id,\n ...confirmCopy(oauthConfirm.action, name),\n onConfirm: () => {\n if (oauthConfirm.action === \"oauth_restart\") void startOAuth(id, \"restart\");\n else void disconnectOAuth(id);\n },\n onCancel: () => cancelConfirm(\n focusableId(\n oauthConfirm.action === \"oauth_restart\" ? `reconnect-${id}` : `disconnect-${id}`,\n `conn-toggle-${id}`\n )\n )\n }\n ) : null,\n /* @__PURE__ */ u3(\n NoticeLine,\n {\n id: `oauthNotice-${id}`,\n notice: state2.oauthNoticeFor === id ? state2.oauthNotice : null\n }\n ),\n connector.credential ? /* @__PURE__ */ u3(\n CredentialCard,\n {\n connector,\n credential: connector.credential,\n editing: state2.credentialEditing === id,\n busy: state2.credentialBusy === id,\n confirming: confirming?.action === \"credential_remove\",\n notice: state2.credentialNoticeFor === id ? state2.credentialNotice : null\n }\n ) : null,\n tools.length ? /* @__PURE__ */ u3(\"details\", { open: forceOpen, children: [\n /* @__PURE__ */ u3(\"summary\", { class: \"disclosure\", children: [\n \"Tools (\",\n tools.length,\n \")\"\n ] }),\n /* @__PURE__ */ u3(\"div\", { class: \"tool-list\", children: [\n tools.some((tool) => tool.safety) ? /* @__PURE__ */ u3(\"p\", { class: \"meta tool-legend\", children: \"Read-only tools run inside execute_code programs. Everything else asks the host first: a program pauses for resume_execution, and a direct call goes through call_destructive_tool — unless this deployment's config exempts the tool, in which case programs call it unasked.\" }) : null,\n tools.map((tool) => /* @__PURE__ */ u3(\"div\", { class: \"tool\", children: [\n /* @__PURE__ */ u3(\"div\", { class: \"tool-head\", children: [\n /* @__PURE__ */ u3(\"code\", { children: tool.address }),\n /* @__PURE__ */ u3(SafetyBadge, { safety: tool.safety })\n ] }),\n tool.description ? /* @__PURE__ */ u3(\"span\", { class: \"td\", children: tool.description }) : null\n ] }, tool.address))\n ] })\n ] }) : null,\n /* @__PURE__ */ u3(\"details\", { children: [\n /* @__PURE__ */ u3(\"summary\", { class: \"disclosure\", children: \"Diagnostics\" }),\n /* @__PURE__ */ u3(\"div\", { class: \"subcard\", children: [\n /* @__PURE__ */ u3(DriftPanel, { connector }),\n connector.catalogAccess ? /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: [\n \"Agents last read its catalog\",\n \" \",\n connector.catalogAccess.state === \"stale\" ? \"from a stale cache\" : \"fresh\",\n \" · \",\n formatDate(connector.catalogAccess.observedAt)\n ] }) : null\n ] })\n ] })\n ] })\n ] });\n }\n function Endpoints({\n pools,\n serverName\n }) {\n return /* @__PURE__ */ u3(\"div\", { class: \"endpoints\", children: [\n /* @__PURE__ */ u3(\n Endpoint,\n {\n url: mcpUrl,\n name: clientServerName(serverName),\n primary: true,\n ...pools.length ? { label: \"All tools\" } : {}\n }\n ),\n pools.map((pool) => /* @__PURE__ */ u3(\n Endpoint,\n {\n url: poolEndpointUrl(mcpUrl, pool),\n name: clientServerName(serverName, pool),\n label: `Pool · ${pool}`\n },\n pool\n ))\n ] });\n }\n function SummaryLine({ connectors }) {\n const parts = connectorSummaryParts(summarizeConnectors(connectors));\n return /* @__PURE__ */ u3(\"p\", { class: \"summary\", id: \"connectorSummary\", children: parts.map((part, index) => /* @__PURE__ */ u3(\"span\", { children: [\n index > 0 ? /* @__PURE__ */ u3(\"span\", { class: \"sep\", children: \" · \" }) : null,\n /* @__PURE__ */ u3(\"span\", { class: part.tone === \"neutral\" ? \"\" : part.tone, children: part.text })\n ] }, part.text)) });\n }\n function ConnectionsPage({ state: state2 }) {\n const data = state2.data;\n const query = state2.connectorFilter.trim();\n const filtered = data ? filterUiConnectors(data.connectors, query) : [];\n return /* @__PURE__ */ u3(\"section\", { id: \"connectionsView\", children: [\n /* @__PURE__ */ u3(\"div\", { class: \"lead\", children: [\n /* @__PURE__ */ u3(\"h1\", { id: \"connectionsHeading\", tabIndex: -1, children: \"Connections\" }),\n /* @__PURE__ */ u3(\"div\", { class: \"lead-copy\", children: [\n /* @__PURE__ */ u3(\"p\", { children: \"Point an MCP client at this endpoint to reach the tools below.\" }),\n /* @__PURE__ */ u3(Endpoints, { pools: data?.pools ?? [], serverName: data?.serverInfo?.name }),\n /* @__PURE__ */ u3(\"p\", { class: \"cap\", id: \"serverInfo\", children: data ? `${data.serverInfo?.name || productName} v${data.connectaVersion || \"?\"}` : productOperatorLabel }),\n data ? /* @__PURE__ */ u3(SummaryLine, { connectors: data.connectors }) : null\n ] })\n ] }),\n /* @__PURE__ */ u3(\"section\", { class: \"section\", \"aria-labelledby\": \"connectorLedgerHeading\", children: [\n /* @__PURE__ */ u3(\"div\", { class: \"section-head\", children: [\n /* @__PURE__ */ u3(\"h2\", { id: \"connectorLedgerHeading\", tabIndex: -1, children: \"Connectors\" }),\n /* @__PURE__ */ u3(\n \"input\",\n {\n id: \"filter\",\n type: \"search\",\n class: \"filter\",\n placeholder: \"Filter connectors or tools…\",\n \"aria-label\": \"Filter connectors or tools\",\n value: state2.connectorFilter,\n disabled: !data,\n onInput: (event) => setConnectorFilter(event.currentTarget.value)\n }\n )\n ] }),\n /* @__PURE__ */ u3(\n \"div\",\n {\n id: \"list\",\n class: !data || filtered.length === 0 ? \"\" : \"rows\",\n \"aria-busy\": state2.refreshing || !data && !state2.loadFailure ? \"true\" : \"false\",\n children: !data ? state2.loadFailure ? /* @__PURE__ */ u3(LoadFailure, { state: state2 }) : /* @__PURE__ */ u3(Empty, { children: \"Loading connectors…\" }) : filtered.length === 0 ? /* @__PURE__ */ u3(Empty, { children: query ? \"No connectors or tools match this filter.\" : \"No connectors are declared in this deployment.\" }) : filtered.map(({ connector, tools }) => /* @__PURE__ */ u3(\n ConnectorRow,\n {\n connector,\n tools,\n forceOpen: Boolean(query),\n state: state2\n },\n connector.id\n ))\n }\n )\n ] })\n ] });\n }\n\n // src/operator-ui/app/main.tsx\n function useOperatorState() {\n const [, bump] = y2((count) => count + 1, 0);\n const snapshot = getState();\n _2(() => {\n const unsubscribe = subscribe(() => bump(void 0));\n if (getState() !== snapshot) bump(void 0);\n return unsubscribe;\n }, []);\n return snapshot;\n }\n function visiblePages(state2) {\n const hint = state2.data ? null : navHint();\n return OPERATOR_PAGES.filter((page) => {\n if (page === \"tokens\") return state2.data?.accessTokenManagement === \"available\";\n if (page === \"activity\") return hint ? hint.activity : Boolean(state2.data?.activityEnabled);\n if (page === \"artifacts\") {\n return isArtifactPage(state2.page) || (hint ? hint.artifacts : Boolean(state2.data?.artifactsEnabled));\n }\n return true;\n });\n }\n function OperatorNav() {\n const state2 = useOperatorState();\n if (state2.session !== \"ready\") return null;\n const onArtifactPage = isArtifactPage(state2.page);\n return /* @__PURE__ */ u3(\"div\", { class: \"mast-actions\", children: [\n /* @__PURE__ */ u3(\"nav\", { class: \"page-nav\", \"aria-label\": \"Operator pages\", children: visiblePages(state2).map(\n (page) => (\n // Crossing between artifact pages and the rest is a full navigation:\n // with a dedicated artifact origin, the two live on different hosts.\n page === \"artifacts\" || onArtifactPage ? /* @__PURE__ */ u3(\n \"a\",\n {\n class: \"navlink\",\n href: page === \"artifacts\" ? PAGE_META.artifacts.path : new URL(PAGE_META[page].path, new URL(homeUrl, window.location.href)).href,\n ...state2.page === page ? { \"aria-current\": \"page\" } : {},\n children: PAGE_META[page].label\n },\n page\n ) : /* @__PURE__ */ u3(\n PageLink,\n {\n page,\n class: \"navlink\",\n current: state2.page === page,\n children: PAGE_META[page].label\n },\n page\n )\n )\n ) }),\n /* @__PURE__ */ u3(\"div\", { class: \"session-actions\", \"aria-label\": \"Session actions\", children: auth.kind === \"clerk\" || auth.kind === \"cloudflare-access\" ? /* @__PURE__ */ u3(\"button\", { class: \"navlink\", type: \"button\", onClick: signOut, children: \"Sign out\" }) : /* @__PURE__ */ u3(\"button\", { class: \"navlink\", type: \"button\", onClick: forgetBearer, children: \"Change token\" }) })\n ] });\n }\n function Gate({ state: state2 }) {\n const [token, setToken] = d2(\"\");\n const signedIn = auth.kind === \"clerk\" && Boolean(window.Clerk?.user);\n const loading = state2.session === \"loading\";\n return /* @__PURE__ */ u3(\"section\", { id: \"gate\", class: \"gate lead\", \"aria-busy\": loading ? \"true\" : \"false\", children: [\n /* @__PURE__ */ u3(\"h1\", { id: \"gateHeading\", tabIndex: -1, children: PAGE_META[state2.page].label }),\n /* @__PURE__ */ u3(\"div\", { class: \"lead-copy\", children: [\n /* @__PURE__ */ u3(\"p\", { children: pageDescription(state2.page, productDescription) }),\n loading ? /* @__PURE__ */ u3(StateBlock, { id: \"gateCopy\", children: checkingCopy(state2.page) }) : /* @__PURE__ */ u3(\"p\", { id: \"gateCopy\", class: \"meta\", children: gateCopy(auth.kind, signedIn) }),\n loading ? null : auth.kind === \"clerk\" ? /* @__PURE__ */ u3(\"div\", { id: \"clerkGate\", class: \"actions\", children: signedIn ? /* @__PURE__ */ u3(\"button\", { class: \"btn\", type: \"button\", onClick: signOut, children: \"Sign out\" }) : /* @__PURE__ */ u3(\"button\", { id: \"signin\", class: \"btn primary\", type: \"button\", onClick: signIn, children: \"Team sign in\" }) }) : auth.kind === \"cloudflare-access\" ? /* @__PURE__ */ u3(\"div\", { class: \"actions\", children: /* @__PURE__ */ u3(\"button\", { class: \"btn\", type: \"button\", onClick: signOut, children: \"Sign out of Cloudflare Access\" }) }) : /* @__PURE__ */ u3(\n \"form\",\n {\n id: \"tokenGate\",\n class: \"row gate-form\",\n onSubmit: (event) => {\n event.preventDefault();\n const value = token.trim();\n if (!value) return;\n setToken(\"\");\n signInWithBearer(value);\n },\n children: [\n /* @__PURE__ */ u3(\n \"input\",\n {\n id: \"token\",\n type: \"password\",\n placeholder: \"Bearer token\",\n autocomplete: \"off\",\n \"aria-label\": \"Bearer token\",\n value: token,\n onInput: (event) => setToken(event.currentTarget.value)\n }\n ),\n /* @__PURE__ */ u3(\"button\", { id: \"save\", class: \"btn primary\", type: \"submit\", children: \"Open operator pages\" })\n ]\n }\n ),\n /* @__PURE__ */ u3(NoticeLine, { id: \"err\", notice: state2.gate, className: \"\" })\n ] })\n ] });\n }\n function CurrentPage({ state: state2 }) {\n if (state2.page === \"tokens\") return /* @__PURE__ */ u3(TokensPage, { state: state2 });\n if (state2.page === \"activity\") return /* @__PURE__ */ u3(ActivityPage, { state: state2 });\n if (state2.page === \"artifacts\") return /* @__PURE__ */ u3(ArtifactsPage, { state: state2 });\n if (state2.page === \"artifact\") return /* @__PURE__ */ u3(ArtifactPage, { state: state2 });\n return /* @__PURE__ */ u3(ConnectionsPage, { state: state2 });\n }\n function OperatorApp() {\n const state2 = useOperatorState();\n const ready = state2.session === \"ready\";\n h2(() => {\n const label = state2.page === \"artifact\" && state2.artifactView ? state2.artifactView.title : PAGE_META[state2.page].label;\n document.title = `${label} — ${titleSuffix}`;\n }, [state2.page, state2.artifactView]);\n h2(() => {\n if (!ready) return;\n if (state2.page === \"tokens\" && state2.data?.accessTokenManagement === \"available\" && state2.tokenPhase === \"idle\") {\n void loadAccessTokens();\n }\n if (state2.page === \"activity\" && state2.data?.activityEnabled && state2.activityPhase === \"idle\") {\n void loadActivity(true);\n }\n });\n h2(() => {\n if (!state2.pendingFocus && !state2.focusIfLost) return;\n if (state2.pendingFocus) {\n document.getElementById(state2.pendingFocus)?.focus();\n } else if (state2.focusIfLost) {\n const active = document.activeElement;\n const lost = !active || active === document.body || !active.isConnected || active.disabled === true;\n if (lost) document.getElementById(state2.focusIfLost)?.focus();\n }\n focusHandled();\n }, [state2.pendingFocus, state2.focusIfLost]);\n return ready ? /* @__PURE__ */ u3(\"div\", { id: \"app\", children: /* @__PURE__ */ u3(CurrentPage, { state: state2 }) }) : /* @__PURE__ */ u3(Gate, { state: state2 });\n }\n function mount(id, view) {\n const host = document.getElementById(id);\n if (!host) return;\n host.textContent = \"\";\n R(view, host);\n }\n mount(\"operatorNav\", /* @__PURE__ */ u3(OperatorNav, {}));\n mount(\"operatorContent\", /* @__PURE__ */ u3(OperatorApp, {}));\n void boot();\n})();\n"; +export const OPERATOR_UI_SCRIPT: string = "\"use strict\";\n(() => {\n // node_modules/preact/dist/preact.module.js\n var n;\n var l;\n var u;\n var t;\n var i;\n var r;\n var o;\n var e;\n var f;\n var c;\n var a;\n var s;\n var h;\n var p;\n var v;\n var y;\n var d = {};\n var w = [];\n var _ = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;\n var g = Array.isArray;\n function m(n2, l3) {\n for (var u4 in l3) n2[u4] = l3[u4];\n return n2;\n }\n function b(n2) {\n n2 && n2.parentNode && n2.parentNode.removeChild(n2);\n }\n function k(l3, u4, t3) {\n var i3, r3, o3, e3 = {};\n for (o3 in u4) \"key\" == o3 ? i3 = u4[o3] : \"ref\" == o3 ? r3 = u4[o3] : e3[o3] = u4[o3];\n if (arguments.length > 2 && (e3.children = arguments.length > 3 ? n.call(arguments, 2) : t3), \"function\" == typeof l3 && null != l3.defaultProps) for (o3 in l3.defaultProps) void 0 === e3[o3] && (e3[o3] = l3.defaultProps[o3]);\n return x(l3, e3, i3, r3, null);\n }\n function x(n2, t3, i3, r3, o3) {\n var e3 = { type: n2, props: t3, key: i3, ref: r3, __k: null, __: null, __b: 0, __e: null, __c: null, constructor: void 0, __v: null == o3 ? ++u : o3, __i: -1, __u: 0 };\n return null == o3 && null != l.vnode && l.vnode(e3), e3;\n }\n function S(n2) {\n return n2.children;\n }\n function C(n2, l3) {\n this.props = n2, this.context = l3;\n }\n function $(n2, l3) {\n if (null == l3) return n2.__ ? $(n2.__, n2.__i + 1) : null;\n for (var u4; l3 < n2.__k.length; l3++) if (null != (u4 = n2.__k[l3]) && null != u4.__e) return u4.__e;\n return \"function\" == typeof n2.type ? $(n2) : null;\n }\n function I(n2) {\n if (n2.__P && n2.__d) {\n var u4 = n2.__v, t3 = u4.__e, i3 = [], r3 = [], o3 = m({}, u4);\n o3.__v = u4.__v + 1, l.vnode && l.vnode(o3), q(n2.__P, o3, u4, n2.__n, n2.__P.namespaceURI, 32 & u4.__u ? [t3] : null, i3, null == t3 ? $(u4) : t3, !!(32 & u4.__u), r3), o3.__v = u4.__v, o3.__.__k[o3.__i] = o3, D(i3, o3, r3), u4.__e = u4.__ = null, o3.__e != t3 && P(o3);\n }\n }\n function P(n2) {\n if (null != (n2 = n2.__) && null != n2.__c) return n2.__e = n2.__c.base = null, n2.__k.some(function(l3) {\n if (null != l3 && null != l3.__e) return n2.__e = n2.__c.base = l3.__e;\n }), P(n2);\n }\n function A(n2) {\n (!n2.__d && (n2.__d = true) && i.push(n2) && !H.__r++ || r != l.debounceRendering) && ((r = l.debounceRendering) || o)(H);\n }\n function H() {\n try {\n for (var n2, l3 = 1; i.length; ) i.length > l3 && i.sort(e), n2 = i.shift(), l3 = i.length, I(n2);\n } finally {\n i.length = H.__r = 0;\n }\n }\n function L(n2, l3, u4, t3, i3, r3, o3, e3, f4, c3, a3) {\n var s3, h3, p3, v3, y3, _3, g2 = t3 && t3.__k || w, m3 = l3.length;\n for (f4 = T(u4, l3, g2, f4, m3), s3 = 0; s3 < m3; s3++) null != (p3 = u4.__k[s3]) && (h3 = -1 != p3.__i && g2[p3.__i] || d, p3.__i = s3, _3 = q(n2, p3, h3, i3, r3, o3, e3, f4, c3, a3), v3 = p3.__e, p3.ref && h3.ref != p3.ref && (h3.ref && J(h3.ref, null, p3), a3.push(p3.ref, p3.__c || v3, p3)), null == y3 && null != v3 && (y3 = v3), 4 & p3.__u ? (f4 = j(p3, f4, n2), h3.__e && (h3.__e = null)) : \"function\" == typeof p3.type && void 0 !== _3 ? f4 = _3 : v3 && (f4 = v3.nextSibling), p3.__u &= -7);\n return u4.__e = y3, f4;\n }\n function T(n2, l3, u4, t3, i3) {\n var r3, o3, e3, f4, c3, a3 = u4.length, s3 = a3, h3 = 0;\n for (n2.__k = new Array(i3), r3 = 0; r3 < i3; r3++) null != (o3 = l3[r3]) && \"boolean\" != typeof o3 && \"function\" != typeof o3 ? (\"string\" == typeof o3 || \"number\" == typeof o3 || \"bigint\" == typeof o3 || o3.constructor == String ? o3 = n2.__k[r3] = x(null, o3, null, null, null) : g(o3) ? o3 = n2.__k[r3] = x(S, { children: o3 }, null, null, null) : void 0 === o3.constructor && o3.__b > 0 ? o3 = n2.__k[r3] = x(o3.type, o3.props, o3.key, o3.ref ? o3.ref : null, o3.__v) : n2.__k[r3] = o3, f4 = r3 + h3, o3.__ = n2, o3.__b = n2.__b + 1, e3 = null, -1 != (c3 = o3.__i = O(o3, u4, f4, s3)) && (s3--, (e3 = u4[c3]) && (e3.__u |= 2)), null == e3 || null == e3.__v ? (-1 == c3 && (i3 > a3 ? h3-- : i3 < a3 && h3++), \"function\" != typeof o3.type && (o3.__u |= 4)) : c3 != f4 && (c3 == f4 - 1 ? h3-- : c3 == f4 + 1 ? h3++ : (c3 > f4 ? h3-- : h3++, o3.__u |= 4))) : n2.__k[r3] = null;\n if (s3) for (r3 = 0; r3 < a3; r3++) null != (e3 = u4[r3]) && 0 == (2 & e3.__u) && (e3.__e == t3 && (t3 = $(e3)), K(e3, e3));\n return t3;\n }\n function j(n2, l3, u4) {\n var t3, i3;\n if (\"function\" == typeof n2.type) {\n for (t3 = n2.__k, i3 = 0; t3 && i3 < t3.length; i3++) t3[i3] && (t3[i3].__ = n2, l3 = j(t3[i3], l3, u4));\n return l3;\n }\n n2.__e != l3 && (l3 && n2.type && !l3.parentNode && (l3 = $(n2)), l3 = u4.insertBefore(n2.__e, l3 || null));\n do {\n l3 = l3 && l3.nextSibling;\n } while (null != l3 && 8 == l3.nodeType);\n return l3;\n }\n function O(n2, l3, u4, t3) {\n var i3, r3, o3, e3 = n2.key, f4 = n2.type, c3 = l3[u4], a3 = null != c3 && 0 == (2 & c3.__u);\n if (null === c3 && null == e3 || a3 && e3 == c3.key && f4 == c3.type) return u4;\n if (t3 > (a3 ? 1 : 0)) {\n for (i3 = u4 - 1, r3 = u4 + 1; i3 >= 0 || r3 < l3.length; ) if (null != (c3 = l3[o3 = i3 >= 0 ? i3-- : r3++]) && 0 == (2 & c3.__u) && e3 == c3.key && f4 == c3.type) return o3;\n }\n return -1;\n }\n function z(n2, l3, u4) {\n \"-\" == l3[0] ? n2.setProperty(l3, null == u4 ? \"\" : u4) : n2[l3] = null == u4 ? \"\" : \"number\" != typeof u4 || _.test(l3) ? u4 : u4 + \"px\";\n }\n function N(n2, l3, u4, t3, i3) {\n var r3, o3;\n n: if (\"style\" == l3) if (\"string\" == typeof u4) n2.style.cssText = u4;\n else {\n if (\"string\" == typeof t3 && (n2.style.cssText = t3 = \"\"), t3) for (l3 in t3) u4 && l3 in u4 || z(n2.style, l3, \"\");\n if (u4) for (l3 in u4) t3 && u4[l3] == t3[l3] || z(n2.style, l3, u4[l3]);\n }\n else if (\"o\" == l3[0] && \"n\" == l3[1]) r3 = l3 != (l3 = l3.replace(s, \"$1\")), o3 = l3.toLowerCase(), l3 = o3 in n2 || \"onFocusOut\" == l3 || \"onFocusIn\" == l3 ? o3.slice(2) : l3.slice(2), n2.l || (n2.l = {}), n2.l[l3 + r3] = u4, u4 ? t3 ? u4[a] = t3[a] : (u4[a] = h, n2.addEventListener(l3, r3 ? v : p, r3)) : n2.removeEventListener(l3, r3 ? v : p, r3);\n else {\n if (\"http://www.w3.org/2000/svg\" == i3) l3 = l3.replace(/xlink(H|:h)/, \"h\").replace(/sName$/, \"s\");\n else if (\"width\" != l3 && \"height\" != l3 && \"href\" != l3 && \"list\" != l3 && \"form\" != l3 && \"tabIndex\" != l3 && \"download\" != l3 && \"rowSpan\" != l3 && \"colSpan\" != l3 && \"role\" != l3 && \"popover\" != l3 && l3 in n2) try {\n n2[l3] = null == u4 ? \"\" : u4;\n break n;\n } catch (n3) {\n }\n \"function\" == typeof u4 || (null == u4 || false === u4 && \"-\" != l3[4] ? n2.removeAttribute(l3) : n2.setAttribute(l3, \"popover\" == l3 && 1 == u4 ? \"\" : u4));\n }\n }\n function V(n2) {\n return function(u4) {\n if (this.l) {\n var t3 = this.l[u4.type + n2];\n if (null == u4[c]) u4[c] = h++;\n else if (u4[c] < t3[a]) return;\n return t3(l.event ? l.event(u4) : u4);\n }\n };\n }\n function q(n2, u4, t3, i3, r3, o3, e3, f4, c3, a3) {\n var s3, h3, p3, v3, y3, d3, _3, k3, x2, M, I2, P2, A3, H2, T3, j3, F = u4.type;\n if (void 0 !== u4.constructor) return null;\n 128 & t3.__u && (c3 = !!(32 & t3.__u), o3 = [f4 = u4.__e = t3.__e]), (s3 = l.__b) && s3(u4);\n n: if (\"function\" == typeof F) {\n h3 = e3.length;\n try {\n if (x2 = u4.props, M = F.prototype && F.prototype.render, I2 = (s3 = F.contextType) && i3[s3.__c], P2 = s3 ? I2 ? I2.props.value : s3.__ : i3, t3.__c ? k3 = (p3 = u4.__c = t3.__c).__ = p3.__E : (M ? u4.__c = p3 = new F(x2, P2) : (u4.__c = p3 = new C(x2, P2), p3.constructor = F, p3.render = Q), I2 && I2.sub(p3), p3.state || (p3.state = {}), p3.__n = i3, v3 = p3.__d = true, p3.__h = [], p3._sb = []), M && null == p3.__s && (p3.__s = p3.state), M && null != F.getDerivedStateFromProps && (p3.__s == p3.state && (p3.__s = m({}, p3.__s)), m(p3.__s, F.getDerivedStateFromProps(x2, p3.__s))), y3 = p3.props, d3 = p3.state, p3.__v = u4, v3) M && null == F.getDerivedStateFromProps && null != p3.componentWillMount && p3.componentWillMount(), M && null != p3.componentDidMount && p3.__h.push(p3.componentDidMount);\n else {\n if (M && null == F.getDerivedStateFromProps && x2 !== y3 && null != p3.componentWillReceiveProps && p3.componentWillReceiveProps(x2, P2), u4.__v == t3.__v || !p3.__e && null != p3.shouldComponentUpdate && false === p3.shouldComponentUpdate(x2, p3.__s, P2)) {\n u4.__v != t3.__v && (p3.props = x2, p3.state = p3.__s, p3.__d = false), u4.__e = t3.__e, u4.__k = t3.__k, u4.__k.some(function(n3) {\n n3 && (n3.__ = u4);\n }), w.push.apply(p3.__h, p3._sb), p3._sb = [], p3.__h.length && e3.push(p3), f4 = $(t3);\n break n;\n }\n null != p3.componentWillUpdate && p3.componentWillUpdate(x2, p3.__s, P2), M && null != p3.componentDidUpdate && p3.__h.push(function() {\n p3.componentDidUpdate(y3, d3, _3);\n });\n }\n if (p3.context = P2, p3.props = x2, p3.__P = n2, p3.__e = false, A3 = l.__r, H2 = 0, M) p3.state = p3.__s, p3.__d = false, A3 && A3(u4), s3 = p3.render(p3.props, p3.state, p3.context), w.push.apply(p3.__h, p3._sb), p3._sb = [];\n else do {\n p3.__d = false, A3 && A3(u4), s3 = p3.render(p3.props, p3.state, p3.context), p3.state = p3.__s;\n } while (p3.__d && ++H2 < 25);\n p3.state = p3.__s, null != p3.getChildContext && (i3 = m(m({}, i3), p3.getChildContext())), M && !v3 && null != p3.getSnapshotBeforeUpdate && (_3 = p3.getSnapshotBeforeUpdate(y3, d3)), T3 = null != s3 && s3.type === S && null == s3.key ? E(s3.props.children) : s3, f4 = L(n2, g(T3) ? T3 : [T3], u4, t3, i3, r3, o3, e3, f4, c3, a3), p3.base = u4.__e, u4.__u &= -161, p3.__h.length && e3.push(p3), k3 && (p3.__E = p3.__ = null);\n } catch (n3) {\n if (e3.length = h3, u4.__v = null, c3 || null != o3) {\n if (n3.then) {\n for (u4.__u |= c3 ? 160 : 128; f4 && 8 == f4.nodeType && f4.nextSibling; ) f4 = f4.nextSibling;\n null != o3 && (o3[o3.indexOf(f4)] = null), u4.__e = f4;\n } else if (null != o3) for (j3 = o3.length; j3--; ) b(o3[j3]);\n } else u4.__e = t3.__e;\n null == u4.__k && (u4.__k = t3.__k || []), n3.then || B(u4), l.__e(n3, u4, t3);\n }\n } else null == o3 && u4.__v == t3.__v ? (u4.__k = t3.__k, u4.__e = t3.__e) : f4 = u4.__e = G(t3.__e, u4, t3, i3, r3, o3, e3, c3, a3);\n return (s3 = l.diffed) && s3(u4), 128 & u4.__u ? void 0 : f4;\n }\n function B(n2) {\n n2 && (n2.__c && (n2.__c.__e = true), n2.__k && n2.__k.some(B));\n }\n function D(n2, u4, t3) {\n for (var i3 = 0; i3 < t3.length; i3++) J(t3[i3], t3[++i3], t3[++i3]);\n l.__c && l.__c(u4, n2), n2.some(function(u5) {\n try {\n n2 = u5.__h, u5.__h = [], n2.some(function(n3) {\n n3.call(u5);\n });\n } catch (n3) {\n l.__e(n3, u5.__v);\n }\n });\n }\n function E(n2) {\n return \"object\" != typeof n2 || null == n2 || n2.__b > 0 ? n2 : g(n2) ? n2.map(E) : void 0 !== n2.constructor ? null : m({}, n2);\n }\n function G(u4, t3, i3, r3, o3, e3, f4, c3, a3) {\n var s3, h3, p3, v3, y3, w3, _3, m3 = i3.props || d, k3 = t3.props, x2 = t3.type;\n if (\"svg\" == x2 ? o3 = \"http://www.w3.org/2000/svg\" : \"math\" == x2 ? o3 = \"http://www.w3.org/1998/Math/MathML\" : o3 || (o3 = \"http://www.w3.org/1999/xhtml\"), null != e3) {\n for (s3 = 0; s3 < e3.length; s3++) if ((y3 = e3[s3]) && \"setAttribute\" in y3 == !!x2 && (x2 ? y3.localName == x2 : 3 == y3.nodeType)) {\n u4 = y3, e3[s3] = null;\n break;\n }\n }\n if (null == u4) {\n if (null == x2) return document.createTextNode(k3);\n u4 = document.createElementNS(o3, x2, k3.is && k3), c3 && (l.__m && l.__m(t3, e3), c3 = false), e3 = null;\n }\n if (null == x2) m3 === k3 || c3 && u4.data == k3 || (u4.data = k3);\n else {\n if (e3 = \"textarea\" == x2 && null != k3.defaultValue ? null : e3 && n.call(u4.childNodes), !c3 && null != e3) for (m3 = {}, s3 = 0; s3 < u4.attributes.length; s3++) m3[(y3 = u4.attributes[s3]).name] = y3.value;\n for (s3 in m3) y3 = m3[s3], \"dangerouslySetInnerHTML\" == s3 ? p3 = y3 : \"children\" == s3 || s3 in k3 || \"value\" == s3 && \"defaultValue\" in k3 || \"checked\" == s3 && \"defaultChecked\" in k3 || N(u4, s3, null, y3, o3);\n for (s3 in k3) y3 = k3[s3], \"children\" == s3 ? v3 = y3 : \"dangerouslySetInnerHTML\" == s3 ? h3 = y3 : \"value\" == s3 ? w3 = y3 : \"checked\" == s3 ? _3 = y3 : c3 && \"function\" != typeof y3 || m3[s3] === y3 || N(u4, s3, y3, m3[s3], o3);\n if (h3) c3 || p3 && (h3.__html == p3.__html || h3.__html == u4.innerHTML) || (u4.innerHTML = h3.__html), t3.__k = [];\n else if (p3 && (u4.innerHTML = \"\"), L(\"template\" == t3.type ? u4.content : u4, g(v3) ? v3 : [v3], t3, i3, r3, \"foreignObject\" == x2 ? \"http://www.w3.org/1999/xhtml\" : o3, e3, f4, e3 ? e3[0] : i3.__k && $(i3, 0), c3, a3), null != e3) for (s3 = e3.length; s3--; ) b(e3[s3]);\n c3 && \"textarea\" != x2 || (s3 = \"value\", \"progress\" == x2 && null == w3 ? u4.removeAttribute(\"value\") : null != w3 && (w3 !== u4[s3] || \"progress\" == x2 && !w3 || \"option\" == x2 && w3 != m3[s3]) && N(u4, s3, w3, m3[s3], o3), s3 = \"checked\", null != _3 && _3 != u4[s3] && N(u4, s3, _3, m3[s3], o3));\n }\n return u4;\n }\n function J(n2, u4, t3) {\n try {\n if (\"function\" == typeof n2) {\n var i3 = \"function\" == typeof n2.__u;\n i3 && n2.__u(), i3 && null == u4 || (n2.__u = n2(u4));\n } else n2.current = u4;\n } catch (n3) {\n l.__e(n3, t3);\n }\n }\n function K(n2, u4, t3) {\n var i3, r3;\n if (l.unmount && l.unmount(n2), (i3 = n2.ref) && (i3.current && i3.current != n2.__e || J(i3, null, u4)), null != (i3 = n2.__c)) {\n if (i3.componentWillUnmount) try {\n i3.componentWillUnmount();\n } catch (n3) {\n l.__e(n3, u4);\n }\n i3.base = i3.__P = i3.__n = null;\n }\n if (i3 = n2.__k) for (r3 = 0; r3 < i3.length; r3++) i3[r3] && K(i3[r3], u4, t3 || \"function\" != typeof n2.type);\n t3 || b(n2.__e), n2.__c = n2.__ = n2.__e = void 0;\n }\n function Q(n2, l3, u4) {\n return this.constructor(n2, u4);\n }\n function R(u4, t3, i3) {\n var r3, o3, e3, f4;\n t3 == document && (t3 = document.documentElement), l.__ && l.__(u4, t3), o3 = (r3 = \"function\" == typeof i3) ? null : i3 && i3.__k || t3.__k, e3 = [], f4 = [], q(t3, u4 = (!r3 && i3 || t3).__k = k(S, null, [u4]), o3 || d, d, t3.namespaceURI, !r3 && i3 ? [i3] : o3 ? null : t3.firstChild ? n.call(t3.childNodes) : null, e3, !r3 && i3 ? i3 : o3 ? o3.__e : t3.firstChild, r3, f4), D(e3, u4, f4), u4.props.children = null;\n }\n n = w.slice, l = { __e: function(n2, l3, u4, t3) {\n for (var i3, r3, o3; l3 = l3.__; ) if ((i3 = l3.__c) && !i3.__) try {\n if ((r3 = i3.constructor) && null != r3.getDerivedStateFromError && (i3.setState(r3.getDerivedStateFromError(n2)), o3 = i3.__d), null != i3.componentDidCatch && (i3.componentDidCatch(n2, t3 || {}), o3 = i3.__d), o3) return i3.__E = i3;\n } catch (l4) {\n n2 = l4;\n }\n throw n2;\n } }, u = 0, t = function(n2) {\n return null != n2 && void 0 === n2.constructor;\n }, C.prototype.setState = function(n2, l3) {\n var u4;\n u4 = null != this.__s && this.__s != this.state ? this.__s : this.__s = m({}, this.state), \"function\" == typeof n2 && (n2 = n2(m({}, u4), this.props)), n2 && m(u4, n2), null != n2 && this.__v && (l3 && this._sb.push(l3), A(this));\n }, C.prototype.forceUpdate = function(n2) {\n this.__v && (this.__e = true, n2 && this.__h.push(n2), A(this));\n }, C.prototype.render = S, i = [], o = \"function\" == typeof Promise ? Promise.prototype.then.bind(Promise.resolve()) : setTimeout, e = function(n2, l3) {\n return n2.__v.__b - l3.__v.__b;\n }, H.__r = 0, f = Math.random().toString(8), c = \"__d\" + f, a = \"__a\" + f, s = /(PointerCapture)$|Capture$/i, h = 0, p = V(false), v = V(true), y = 0;\n\n // node_modules/preact/hooks/dist/hooks.module.js\n var t2;\n var r2;\n var u2;\n var i2;\n var o2 = 0;\n var f2 = [];\n var c2 = l;\n var e2 = c2.__b;\n var a2 = c2.__r;\n var v2 = c2.diffed;\n var l2 = c2.__c;\n var m2 = c2.unmount;\n var p2 = c2.__;\n function s2(n2, t3) {\n c2.__h && c2.__h(r2, n2, o2 || t3), o2 = 0;\n var u4 = r2.__H || (r2.__H = { __: [], __h: [] });\n return n2 >= u4.__.length && u4.__.push({}), u4.__[n2];\n }\n function d2(n2) {\n return o2 = 1, y2(D2, n2);\n }\n function y2(n2, u4, i3) {\n var o3 = s2(t2++, 2);\n if (o3.t = n2, !o3.__c && (o3.__ = [i3 ? i3(u4) : D2(void 0, u4), function(n3) {\n var t3 = o3.__N ? o3.__N[0] : o3.__[0], r3 = o3.t(t3, n3);\n t3 !== r3 && (o3.__N = [r3, o3.__[1]], o3.__c.setState({}));\n }], o3.__c = r2, !r2.__f)) {\n var f4 = function(n3, t3, r3) {\n if (!o3.__c.__H) return true;\n var u5 = false, i4 = o3.__c.props !== n3;\n if (o3.__c.__H.__.some(function(n4) {\n if (n4.__N) {\n u5 = true;\n var t4 = n4.__[0];\n n4.__ = n4.__N, n4.__N = void 0, t4 !== n4.__[0] && (i4 = true);\n }\n }), c3) {\n var f5 = c3.call(this, n3, t3, r3);\n return u5 ? f5 || i4 : f5;\n }\n return !u5 || i4;\n };\n r2.__f = true;\n var c3 = r2.shouldComponentUpdate, e3 = r2.componentWillUpdate;\n r2.componentWillUpdate = function(n3, t3, r3) {\n if (this.__e) {\n var u5 = c3;\n c3 = void 0, f4(n3, t3, r3), c3 = u5;\n }\n e3 && e3.call(this, n3, t3, r3);\n }, r2.shouldComponentUpdate = f4;\n }\n return o3.__N || o3.__;\n }\n function h2(n2, u4) {\n var i3 = s2(t2++, 3);\n !c2.__s && C2(i3.__H, u4) && (i3.__ = n2, i3.u = u4, r2.__H.__h.push(i3));\n }\n function _2(n2, u4) {\n var i3 = s2(t2++, 4);\n !c2.__s && C2(i3.__H, u4) && (i3.__ = n2, i3.u = u4, r2.__h.push(i3));\n }\n function A2(n2) {\n return o2 = 5, T2(function() {\n return { current: n2 };\n }, []);\n }\n function T2(n2, r3) {\n var u4 = s2(t2++, 7);\n return C2(u4.__H, r3) && (u4.__ = n2(), u4.__H = r3, u4.__h = n2), u4.__;\n }\n function j2() {\n for (var n2; n2 = f2.shift(); ) {\n var t3 = n2.__H;\n if (n2.__P && t3) try {\n t3.__h.some(z2), t3.__h.some(B2), t3.__h = [];\n } catch (r3) {\n t3.__h = [], c2.__e(r3, n2.__v);\n }\n }\n }\n c2.__b = function(n2) {\n r2 = null, e2 && e2(n2);\n }, c2.__ = function(n2, t3) {\n n2 && t3.__k && t3.__k.__m && (n2.__m = t3.__k.__m), p2 && p2(n2, t3);\n }, c2.__r = function(n2) {\n a2 && a2(n2), t2 = 0;\n var i3 = (r2 = n2.__c).__H;\n i3 && (u2 === r2 ? (i3.__h = [], r2.__h = [], i3.__.some(function(n3) {\n n3.__N && (n3.__ = n3.__N), n3.u = n3.__N = void 0;\n })) : (i3.__h.some(z2), i3.__h.some(B2), i3.__h = [], t2 = 0)), u2 = r2;\n }, c2.diffed = function(n2) {\n v2 && v2(n2);\n var t3 = n2.__c;\n t3 && t3.__H && (t3.__H.__h.length && (1 !== f2.push(t3) && i2 === c2.requestAnimationFrame || ((i2 = c2.requestAnimationFrame) || w2)(j2)), t3.__H.__.some(function(n3) {\n n3.u && (n3.__H = n3.u, n3.u = void 0);\n })), u2 = r2 = null;\n }, c2.__c = function(n2, t3) {\n t3.some(function(n3) {\n try {\n n3.__h.some(z2), n3.__h = n3.__h.filter(function(n4) {\n return !n4.__ || B2(n4);\n });\n } catch (r3) {\n t3.some(function(n4) {\n n4.__h && (n4.__h = []);\n }), t3 = [], c2.__e(r3, n3.__v);\n }\n }), l2 && l2(n2, t3);\n }, c2.unmount = function(n2) {\n m2 && m2(n2);\n var t3, r3 = n2.__c;\n r3 && r3.__H && (r3.__H.__.some(function(n3) {\n try {\n z2(n3);\n } catch (n4) {\n t3 = n4;\n }\n }), r3.__H = void 0, t3 && c2.__e(t3, r3.__v));\n };\n var k2 = \"function\" == typeof requestAnimationFrame;\n function w2(n2) {\n var t3, r3 = function() {\n clearTimeout(u4), k2 && cancelAnimationFrame(t3), setTimeout(n2);\n }, u4 = setTimeout(r3, 35);\n k2 && (t3 = requestAnimationFrame(r3));\n }\n function z2(n2) {\n var t3 = r2, u4 = n2.__c;\n \"function\" == typeof u4 && (n2.__c = void 0, u4()), r2 = t3;\n }\n function B2(n2) {\n var t3 = r2;\n n2.__c = n2.__(), r2 = t3;\n }\n function C2(n2, t3) {\n return !n2 || n2.length !== t3.length || t3.some(function(t4, r3) {\n return t4 !== n2[r3];\n });\n }\n function D2(n2, t3) {\n return \"function\" == typeof t3 ? t3(n2) : t3;\n }\n\n // src/operator-ui/view.ts\n var OPERATOR_PAGES = [\n \"connections\",\n \"tokens\",\n \"activity\",\n \"artifacts\"\n ];\n var PAGE_META = {\n tokens: { path: \"/tokens\", label: \"Access tokens\" },\n connections: { path: \"/\", label: \"Connections\" },\n activity: { path: \"/activity\", label: \"Activity\" },\n artifacts: { path: \"/artifacts\", label: \"Artifacts\" },\n artifact: { path: \"/artifacts\", label: \"Artifact\" }\n };\n function pageDescription(page, productDescription2) {\n if (page === \"activity\") {\n return \"Every connector tool call, by who made it and how it ended. Arguments and results are never stored.\";\n }\n if (isArtifactPage(page)) {\n return \"Pages agents published for the team. Each one keeps every version.\";\n }\n return productDescription2;\n }\n function checkingCopy(page) {\n if (page === \"artifact\") return \"Loading artifact…\";\n if (page === \"artifacts\") return \"Loading artifacts…\";\n return \"Checking your session…\";\n }\n function pageForPath(path) {\n if (path.startsWith(\"/artifacts/\")) return \"artifact\";\n const match = OPERATOR_PAGES.find((page) => PAGE_META[page].path === path);\n return match ?? \"connections\";\n }\n function isArtifactPage(page) {\n return page === \"artifacts\" || page === \"artifact\";\n }\n function artifactViewRequest(pathname, search) {\n const match = /^\\/artifacts\\/([a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?)(?:\\/v\\/(\\d{1,9}))?$/.exec(pathname);\n if (!match?.[1]) return void 0;\n const params = new URLSearchParams();\n if (match[2]) {\n params.set(\"v\", match[2]);\n for (const pin of new URLSearchParams(search).getAll(\"d\")) params.append(\"d\", pin);\n }\n const query = params.toString();\n return `/artifacts/_api/view/${match[1]}${query ? `?${query}` : \"\"}`;\n }\n function info(message) {\n return { message, tone: \"info\" };\n }\n function failure(message, fix) {\n return fix ? { message, tone: \"error\", fix } : { message, tone: \"error\" };\n }\n var REFUSED_COPY = {\n oauth_disconnect: \"Disconnect didn't finish. If the service refused it, the deployment's log has its reply.\",\n oauth_reconnect: \"Authorization couldn't start. If the service refused it, the deployment's log has its reply.\",\n credential_test: \"The credential test couldn't run.\"\n };\n function oauthDoneNotice(action, answer, opened = true) {\n if (action === \"oauth_disconnect\") {\n return info(\"Disconnected. Connect again whenever you are ready.\");\n }\n if (answer?.state === \"ok\") return info(\"Connected.\");\n return info(\n opened ? \"Finish authorizing in the new tab. This page updates when you come back.\" : \"Your browser blocked the new tab. Open the authorization page from the link here.\"\n );\n }\n function credentialTestNotice(connectorId, answer) {\n return answer?.ok === true ? info(\"Credential is valid.\") : failure(\n \"Credential test failed: the service rejected the stored credential, or the test couldn't reach it. The deployment's log has the service's reply.\",\n { kind: \"credential_test_failed\", connectorId }\n );\n }\n function refusedNotice(action, connectorId, problem) {\n if (action === \"credential_test\" && (problem === \"credential_required\" || problem === \"credential_mismatch\")) {\n return failure(PROBLEM_COPY[problem], { kind: problem, connectorId });\n }\n return failure(REFUSED_COPY[action], {\n kind: action === \"credential_test\" ? \"credential_test_failed\" : \"oauth_action_failed\",\n connectorId\n });\n }\n function credentialRefusedCopy(action, status, problem) {\n if (problem === \"credential_mismatch\") return PROBLEM_COPY.credential_mismatch;\n const verb = action === \"credential_save\" ? \"saved\" : \"removed\";\n if (status === 400 || status === 413 || status === 415) {\n return action === \"credential_save\" ? \"The credential wasn't saved: a value is empty or not in the expected format. Check it and save again.\" : \"The credential wasn't removed. Refresh the page and try again.\";\n }\n if (status === 404) {\n return \"This connector no longer has a credential slot. Refresh the page to see its current setup.\";\n }\n if (status === 503) {\n return `Credential storage isn't configured on this deployment, so nothing was ${verb}.`;\n }\n return `The credential wasn't ${verb}. Try again; if it keeps failing, the deployment's log has the reason.`;\n }\n function actionFailedNotice(action, connectorId, facts, productName2) {\n if (facts.kind === \"session\") {\n return failure(\"Your session has ended. Sign in again, then retry.\");\n }\n if (facts.kind === \"forbidden\") {\n return failure(\"You don't have permission to change this connection's authentication.\");\n }\n if (facts.kind === \"network\") {\n return failure(`Couldn't reach ${productName2}. Check your connection and try again.`);\n }\n if (action === \"credential_save\" || action === \"credential_remove\") {\n return failure(credentialRefusedCopy(action, facts.status, facts.problem));\n }\n return refusedNotice(action, connectorId, facts.problem);\n }\n function connectorLoadFailureCopy(failure2, productName2) {\n return failure2 === \"session\" ? \"Your session wasn't accepted while loading this connector. Sign in again to see its status.\" : `Couldn't reach ${productName2} to load this connector. Check your connection, then refresh it.`;\n }\n function loadFailureCopy(failure2, productName2) {\n return {\n title: `Couldn't reach ${productName2}`,\n body: failure2 === \"network\" ? \"Your browser couldn't connect. Check your connection, then retry.\" : \"The deployment answered with an error. Retry in a moment; if it keeps failing, the deployment's log has the reason.\"\n };\n }\n function collectionFailureCopy(collection, facts, productName2) {\n if (facts.kind === \"network\") {\n return `Couldn't reach ${productName2}. Check your connection, then retry.`;\n }\n if (facts.kind === \"session\") return \"Your session has ended. Sign in again to see this page.\";\n if (facts.kind === \"forbidden\" || facts.status === 404) {\n if (collection === \"artifact\") return \"There is no artifact here, or this identity can't open it.\";\n return collection === \"activity\" ? \"Activity isn't available to this identity.\" : \"Artifacts aren't available to this identity.\";\n }\n return \"The deployment answered with an error. Retry in a moment; if it keeps failing, the deployment's log has the reason.\";\n }\n function confirmCopy(action, name) {\n if (action === \"oauth_disconnect\") {\n return {\n question: `Disconnect ${name}? Its stored grant and any pending authorization are removed, and its tools stop working until it is connected again.`,\n confirm: \"Disconnect\"\n };\n }\n if (action === \"oauth_restart\") {\n return {\n question: `Reconnect ${name}? Its current grant stops working until you finish authorizing in the new tab.`,\n confirm: \"Reconnect\"\n };\n }\n return {\n question: `Remove ${name}'s credential? The connector stops authenticating until a replacement is added.`,\n confirm: \"Remove\"\n };\n }\n function accessTokenUnavailableCopy(capability) {\n return capability === void 0 ? \"Access tokens are not configured for this deployment.\" : \"Token management requires an interactive sign-in and explicit permission.\";\n }\n function initialState(page) {\n return {\n page,\n generation: 0,\n session: \"loading\",\n gate: null,\n refreshing: false,\n loadFailure: null,\n pendingFocus: null,\n focusIfLost: null,\n ...identityScopedState()\n };\n }\n function identityScopedState() {\n return {\n data: null,\n connectorFilter: \"\",\n oauthNotice: null,\n oauthNoticeFor: null,\n oauthBusy: null,\n oauthBlocked: null,\n confirming: null,\n connectorFailures: {},\n credentialNotice: null,\n credentialNoticeFor: null,\n credentialEditing: null,\n credentialBusy: null,\n tokenPhase: \"idle\",\n tokenNotice: null,\n tokens: [],\n createdToken: null,\n tokenRenaming: null,\n tokenBusy: false,\n activityPhase: \"idle\",\n activityNotice: null,\n activityEvents: [],\n activityCursor: null,\n activitySearch: \"\",\n artifactPhase: \"idle\",\n artifactNotice: null,\n artifactRows: [],\n artifactCursor: null,\n artifactQuery: \"\",\n artifactArchived: false,\n artifactView: null\n };\n }\n function resetIdentity(state2, gate2 = null) {\n return {\n ...state2,\n generation: state2.generation + 1,\n session: \"gated\",\n gate: gate2,\n refreshing: false,\n loadFailure: null,\n pendingFocus: null,\n focusIfLost: null,\n ...identityScopedState()\n };\n }\n function withPage(state2, page) {\n return {\n ...state2,\n page,\n createdToken: null,\n tokenRenaming: null,\n tokenNotice: null,\n credentialEditing: null,\n credentialNotice: null,\n credentialNoticeFor: null,\n confirming: null\n };\n }\n function connectorStatusLabel(status, problem) {\n if (status === \"loading\") return \"Loading details\";\n if (status === \"ok\") return \"Connected\";\n if (status === \"auth_required\") {\n return problem === \"credential_required\" ? \"Credential needed\" : \"Authorization needed\";\n }\n return \"Unavailable\";\n }\n function toolCountLabel(count) {\n return `${count} ${count === 1 ? \"tool\" : \"tools\"}`;\n }\n function connectorStatusTone(status) {\n if (status === \"ok\") return \"ok\";\n if (status === \"auth_required\") return \"warn\";\n if (status === \"loading\") return \"neutral\";\n return \"danger\";\n }\n function summarizeConnectors(connectors) {\n const summary = {\n total: connectors.length,\n connected: 0,\n attention: 0,\n credentials: 0,\n unavailable: 0,\n loading: 0,\n tools: 0,\n drifting: 0\n };\n for (const connector of connectors) {\n if (connector.status === \"ok\") summary.connected += 1;\n else if (connector.status === \"auth_required\") {\n if (connector.problem === \"credential_required\") summary.credentials += 1;\n else summary.attention += 1;\n } else if (connector.status === \"loading\") summary.loading += 1;\n else summary.unavailable += 1;\n summary.tools += connector.toolCount || 0;\n if (driftState(connector.catalogDrift) === \"warning\") summary.drifting += 1;\n }\n return summary;\n }\n function connectorSummaryParts(summary) {\n if (summary.total > 0 && summary.loading === summary.total) {\n return [\n {\n text: `Checking ${summary.total} connector${summary.total === 1 ? \"\" : \"s\"}…`,\n tone: \"neutral\"\n }\n ];\n }\n return [\n { text: `${summary.connected} connected`, tone: \"neutral\" },\n ...summary.attention ? [\n {\n text: `${summary.attention} need${summary.attention === 1 ? \"s\" : \"\"} authorization`,\n tone: \"warn\"\n }\n ] : [],\n ...summary.credentials ? [\n {\n text: `${summary.credentials} need${summary.credentials === 1 ? \"s\" : \"\"} a credential`,\n tone: \"warn\"\n }\n ] : [],\n ...summary.unavailable ? [{ text: `${summary.unavailable} unavailable`, tone: \"danger\" }] : [],\n { text: toolCountLabel(summary.tools), tone: \"neutral\" },\n ...summary.loading ? [{ text: `${summary.loading} still loading`, tone: \"neutral\" }] : []\n ];\n }\n var TOOL_SAFETY_BADGE = {\n runs_in_programs: {\n label: \"runs in programs\",\n tone: \"ok\",\n title: \"Explicitly read-only: execute_code programs may call it without asking.\"\n },\n exempt: {\n label: \"exempt from approval\",\n tone: \"neutral\",\n title: \"Not read-only, but this deployment's config lets programs call it without pausing. Each call still counts against the write budget and appears in activity; call_tool still refuses it.\"\n },\n needs_approval: {\n label: \"asks for approval\",\n tone: \"warn\",\n title: \"Not explicitly read-only: a program pauses for resume_execution, and a direct call crosses call_destructive_tool — either way the host asks first.\"\n }\n };\n var PROBLEM_COPY = {\n connector_unavailable: \"Unavailable: its status check or catalog load failed, or did not finish in time. The deployment's log has the downstream error.\",\n oauth_required: \"Needs OAuth authorization: no grant is stored, or the stored grant expired or was revoked.\",\n credential_required: \"Needs a credential: nothing usable is stored in its credential slot.\",\n auth_required: \"Needs authorization. Its secret lives in deployment configuration, not on this page.\",\n credential_mismatch: \"The stored credential does not match the fields this connector declares, so it cannot be used.\",\n catalog_failed: \"Connected, but its tool catalog could not be loaded, so none of its tools are served.\"\n };\n function problemCopy(problem) {\n return problem ? PROBLEM_COPY[problem] ?? null : null;\n }\n function problemTone(problem) {\n return problem === \"oauth_required\" || problem === \"credential_required\" || problem === \"auth_required\" ? \"warn\" : \"danger\";\n }\n function authScopeLabel(scope) {\n return scope === \"personal\" ? \"personal auth\" : \"shared auth\";\n }\n function permissionLabel(connector) {\n if (connector.permissions?.manageSharedAuth) {\n return \"You can manage shared authentication for this connection.\";\n }\n if (connector.permissions?.connectPersonal) {\n return \"You can connect your own account to this connection.\";\n }\n return \"Authentication for this connection is managed by your deployment.\";\n }\n var DRIFT_CATEGORIES = [\n { key: \"unclassifiedTools\", label: \"Unclassified\" },\n { key: \"unservedTools\", label: \"Unserved\" },\n { key: \"annotationConflicts\", label: \"Annotation conflicts\" },\n { key: \"schemaChanges\", label: \"Schema changes\" }\n ];\n function driftTotal(drift) {\n if (!drift) return 0;\n return DRIFT_CATEGORIES.reduce((sum, { key }) => sum + (drift[key] || 0), 0);\n }\n function driftState(drift) {\n if (!drift) return \"unavailable\";\n return driftTotal(drift) > 0 ? \"warning\" : \"clean\";\n }\n function driftCounts(drift) {\n if (!drift) return [];\n return DRIFT_CATEGORIES.map(({ key, label }) => ({\n key,\n label,\n count: drift[key] || 0\n }));\n }\n function driftSummary(drift) {\n const state2 = driftState(drift);\n if (state2 === \"unavailable\") {\n return \"No catalog refresh observed yet in this runtime.\";\n }\n const observed = formatDate(drift?.observedAt);\n const when = observed ? ` · observed ${observed}` : \"\";\n if (state2 === \"clean\") return `Matches the reviewed manifest${when}`;\n const total = driftTotal(drift);\n return `${total} difference${total === 1 ? \"\" : \"s\"} from the reviewed manifest${when}`;\n }\n function safeHttpHref(url) {\n if (!url) return null;\n try {\n const protocol = new URL(url).protocol;\n return protocol === \"http:\" || protocol === \"https:\" ? url : null;\n } catch {\n return null;\n }\n }\n function formatDate(value) {\n if (!value) return \"\";\n const date = new Date(value);\n return Number.isNaN(date.valueOf()) ? \"\" : date.toLocaleString(void 0, { dateStyle: \"medium\", timeStyle: \"short\" });\n }\n var ACTOR_KINDS = {\n clerk: \"Clerk\",\n bearer: \"Bearer token\",\n \"cloudflare-access\": \"Cloudflare Access\",\n anonymous: \"Anonymous\"\n };\n function actorKindLabel(kind) {\n if (!kind) return \"Unknown caller\";\n return ACTOR_KINDS[kind] ?? kind;\n }\n function actorLabel(actor) {\n if (!actor?.kind) return \"Unknown caller\";\n const who = actor.label || actor.id;\n const kind = actorKindLabel(actor.kind);\n return who ? `${who} (${kind})` : kind;\n }\n function actorStableId(actor) {\n if (!actor?.id) return null;\n if (!actor.label && !actor.namespace) return null;\n return actor.namespace ? `${actor.namespace} · ${actor.id}` : actor.id;\n }\n function activityMatches(event, query) {\n const q2 = query.trim().toLowerCase();\n if (!q2) return true;\n return [\n event.address,\n event.connectorId,\n event.toolName,\n event.source,\n event.outcome,\n event.errorCode,\n event.friction,\n event.actor?.kind,\n event.actor?.id,\n event.actor?.namespace,\n event.actor?.label,\n activityOutcomeBadge(event.outcome).label,\n activityDetail(event),\n actorKindLabel(event.actor?.kind)\n ].some((value) => String(value ?? \"\").toLowerCase().includes(q2));\n }\n function filterActivity(events, query) {\n return events.filter((event) => activityMatches(event, query));\n }\n function activitySummary(events) {\n if (events.length === 0) return \"\";\n const tools = new Set(events.map((event) => event.address)).size;\n return `${events.length} loaded call${events.length === 1 ? \"\" : \"s\"} · ${tools} tool${tools === 1 ? \"\" : \"s\"}`;\n }\n var ACTIVITY_OUTCOMES = [\n \"success\",\n \"error\",\n \"timeout\",\n \"cancelled\",\n \"paused\",\n \"approved\"\n ];\n function activityOutcomeClass(outcome) {\n return ACTIVITY_OUTCOMES.includes(outcome) ? outcome : \"error\";\n }\n var OUTCOME_BADGE = {\n success: { label: \"Succeeded\", tone: \"ok\" },\n error: { label: \"Failed\", tone: \"danger\" },\n timeout: { label: \"Timed out\", tone: \"danger\" },\n cancelled: { label: \"Cancelled\", tone: \"neutral\" },\n paused: { label: \"Waiting for approval\", tone: \"warn\" },\n approved: { label: \"Approved\", tone: \"ok\" }\n };\n function activityOutcomeBadge(outcome) {\n return OUTCOME_BADGE[outcome] ?? { label: \"Failed\", tone: \"danger\" };\n }\n var SOURCE_LABELS = {\n execute_code: \"In a program\",\n call_tool: \"Direct call\",\n call_destructive_tool: \"Direct call, approved by the host\",\n resume_execution: \"Resumed program\",\n batch_call: \"Batch call\"\n };\n var REASON_LABELS = {\n tool_not_found: \"tool not found\",\n unknown_address: \"tool not found\",\n unknown_tool: \"tool not found\",\n ambiguous_tool_alias: \"ambiguous tool name\",\n schema_retry: \"arguments didn't match the schema\",\n invalid_args: \"arguments didn't match the schema\",\n destructive_reroute: \"needed host approval\",\n destructive_tool_requires_approval: \"needed host approval\",\n approval_required: \"needed approval\",\n auth_required: \"needed authorization\",\n result_too_large: \"result too large to return inline\",\n timeout: \"timed out\"\n };\n function reasonLabel(code) {\n return REASON_LABELS[code] ?? code.replaceAll(\"_\", \" \");\n }\n function activityDetail(event) {\n const parts = [SOURCE_LABELS[event.source] ?? event.source];\n if (event.approval === \"tool\") parts.push(\"approved for the rest of the run\");\n if (event.approval === \"call\") parts.push(\"approved for this call\");\n if (event.attempts > 1) parts.push(`${event.attempts} attempts`);\n const reasons = [event.friction, event.errorCode].filter((code) => Boolean(code)).map(reasonLabel);\n for (const reason of new Set(reasons)) parts.push(reason);\n return parts.join(\" · \");\n }\n function artifactRefreshBadge(last) {\n return last?.status === \"failed\" ? { label: \"Refresh failed\", tone: \"danger\" } : null;\n }\n function credentialProblemCopy(problem) {\n if (problem === \"credential_mismatch\") return PROBLEM_COPY.credential_mismatch;\n if (problem === \"credential_unreadable\") {\n return \"The stored credential can't be read, so it can't be used. Replace it, or remove it and add a new one.\";\n }\n return \"The stored credential can't be used. Replace it, or remove it and add a new one.\";\n }\n function credentialStateLabel(credential) {\n if (!credential.configured) return \"not configured\";\n const masked = credential.fields?.length ? \"configured\" : `configured · ••••${credential.lastFour ?? \"\"}`;\n return credential.updatedAt ? `${masked} · updated ${formatDate(credential.updatedAt)}` : masked;\n }\n function gateCopy(kind, signedIn) {\n if (kind === \"cloudflare-access\") {\n return \"Cloudflare Access admitted this browser, but the current identity cannot open deployment-wide operator pages.\";\n }\n if (kind !== \"clerk\") {\n return \"Paste an operator bearer token to open this page. Nothing is requested until you do.\";\n }\n return signedIn ? \"Signed in with Clerk, but this account cannot open deployment-wide operator pages.\" : \"Sign in with Clerk to open this operator page.\";\n }\n\n // src/operator-ui/app/config.ts\n var auth = AUTH;\n var mcpUrl = MCP_URL;\n var initialPage = INITIAL_PAGE;\n var homeUrl = HOME_URL;\n var titleSuffix = TITLE_SUFFIX;\n var productName = PRODUCT_NAME;\n var productDescription = PRODUCT_DESCRIPTION;\n var productOperatorLabel = PRODUCT_OPERATOR_LABEL;\n var TOKEN_KEY = \"connecta:token\";\n\n // src/fix-prompt.ts\n var CONNECTOR_ID_RE = /^[a-z0-9_-]+$/;\n function renderFixPrompt(spec, connectorId) {\n const id = connectorId !== void 0 && CONNECTOR_ID_RE.test(connectorId) ? connectorId : void 0;\n return [\n \"Diagnose and fix a problem in this connecta deployment (the @zackbart/connecta package). It is configured as code: connectors, credential slots, OAuth clients, pools, and inbound auth are declared in the deployment's source and environment, so the fix belongs there and not in the operator page.\",\n `Problem: ${spec.problem}` + (id ? `\nConnector id: ${id}` : \"\"),\n `Where to look:\n${spec.steps.map((step) => `- ${step}`).join(\"\\n\")}`,\n \"This prompt deliberately carries no error text, tokens, or URLs. Find the underlying cause in the deployment's own logs (lines prefixed [connecta]) or by reproducing the failure locally. Never put a secret in source, a log line, or your reply: secrets belong in the deployment's environment or its credential vault.\",\n \"Make the smallest change that fixes it, then verify it by redeploying and refreshing the connection on the operator Connections page. If the fix needs something you cannot do yourself — a provider console setting, a secret only the operator holds — name that exact step instead.\"\n ].join(\"\\n\\n\");\n }\n\n // src/operator-ui/fix-prompts.ts\n var CATALOGUE = {\n connector_unavailable: {\n problem: \"A connector is unavailable: its status check or catalog load failed, or did not finish before the operator page's deadline.\",\n steps: [\n \"Confirm the connector's downstream URL or API base in the deployment config, and that the deployment can reach it from where it runs.\",\n \"Check the credentials or headers the connector sends; a rejected credential often surfaces as a failed status rather than as an authorization prompt.\",\n \"If the downstream is slow rather than down, review the connector's timeouts and the deployment's discovery.probeTimeoutMs.\"\n ]\n },\n oauth_required: {\n problem: \"A downstream OAuth connector needs authorization: no grant is stored, or the stored grant expired or was revoked and could not be refreshed.\",\n steps: [\n \"Reauthorize from the operator page (Connect account or Reconnect OAuth) or with authorize_connector; this needs no code change if the grant simply lapsed.\",\n \"If it needs reauthorizing again soon after, check that the OAuth client requests offline access or a refresh token, and that the storage holding OAuth tokens persists across restarts and is shared by every instance.\",\n \"If authorization cannot start at all, check the connector's OAuth client configuration and the deployment's publicUrl, which forms the /oauth/callback/ redirect URI.\"\n ]\n },\n credential_required: {\n problem: \"A connector with an operator-managed credential slot has no usable credential stored.\",\n steps: [\n \"An operator with credential administration can add the credential on the operator page; that needs no code change.\",\n \"If nobody can, grant credential administration through the deployment's identity config (credentialAdministration for shared auth, personalConnection for personal auth), which is denied by default.\",\n \"Check that the connector's credential declaration (label and fields) matches what the downstream actually needs.\"\n ]\n },\n auth_required: {\n problem: \"A connector reports that it needs authorization, and its secret lives in deployment configuration rather than in an operator-managed slot.\",\n steps: [\n \"Find where the connector's secret is read (usually an environment variable or Worker secret passed into the connector config) and confirm it is set in the running environment.\",\n \"Rotate the secret at the provider if it expired or was revoked, then update the deployment's environment, not its source.\",\n \"If operators should manage this secret from the page instead, declare a credential slot on the connector and configure a credential vault.\"\n ]\n },\n credential_mismatch: {\n problem: \"The credential stored for a connector does not match the fields the connector currently declares, so it cannot be used.\",\n steps: [\n \"If the connector's credential fields changed on purpose, re-enter the credential on the operator page so the stored fields match.\",\n \"If they changed by accident, restore the previous field names in the connector's credential declaration.\"\n ]\n },\n credential_unreadable: {\n problem: \"A stored credential exists but could not be read or decrypted.\",\n steps: [\n \"Check that the credential vault's encryption key in the deployment environment is the one the credential was stored with; a rotated or missing key makes every stored value unreadable.\",\n \"Check the storage adapter backing the vault is reachable from the deployment.\",\n \"If the key was lost, remove and re-add the credential on the operator page; the old value cannot be recovered.\"\n ]\n },\n credential_test_failed: {\n problem: \"The test for a stored connector credential failed: the downstream rejected it, or the test could not reach the downstream.\",\n steps: [\n \"Confirm the stored value is current at the provider (not rotated, revoked, or scoped too narrowly), and replace it on the operator page if not.\",\n \"Check that the connector's credential test targets the same API base and auth scheme its tool calls use.\"\n ]\n },\n oauth_action_failed: {\n problem: \"Restarting or disconnecting a connector's downstream OAuth from the operator page failed.\",\n steps: [\n \"Check that the connector implements startAuth and disconnectAuth, and that the storage holding its OAuth state is writable.\",\n \"Check the connector's OAuth client configuration, including the authorization server it discovers or is given.\",\n \"Confirm the operator has the config-derived permission for this action (credentialAdministration for shared auth, personalConnection for personal auth).\"\n ]\n },\n catalog_failed: {\n problem: \"A connector reports itself connected, but loading its tool catalog failed, so none of its tools are being served.\",\n steps: [\n \"Check that the downstream still serves a complete tools list; connecta refuses a partial catalog rather than serving part of it.\",\n \"Check for tools the connector cannot accept: invalid schemas, missing descriptions, or names that collide.\",\n \"Pin or upgrade the @zackbart/connecta version if a maintained provider's catalog changed shape underneath it.\"\n ]\n },\n catalog_drift: {\n problem: \"A hosted provider's live catalog differs from the reviewed manifest shipped with connecta: new unclassified tools, unserved tools, annotation conflicts, or schema changes.\",\n steps: [\n \"Unclassified tools are withheld until a release classifies them. Check whether a newer @zackbart/connecta release has, and upgrade if so.\",\n \"If a tool the deployment depends on is now unserved or changed shape, review callers of it before upgrading.\",\n \"Report drift the release does not cover as an issue on the connecta repository, naming the provider and the kinds of drift but no tool data.\"\n ]\n }\n };\n function fixPrompt(kind, connectorId) {\n return renderFixPrompt(CATALOGUE[kind], connectorId);\n }\n var FIX_PROMPT_KINDS = Object.keys(CATALOGUE);\n\n // src/operator-ui/app/store.ts\n var state = initialState(initialPage);\n var listeners = /* @__PURE__ */ new Set();\n function getState() {\n return state;\n }\n function subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n }\n function set(patch) {\n state = { ...state, ...patch };\n for (const listener of listeners) listener();\n }\n function fence() {\n const generation = state.generation;\n return () => generation === state.generation;\n }\n function sessionToken() {\n if (auth.kind === \"cloudflare-access\") return Promise.resolve(void 0);\n return auth.kind === \"clerk\" ? Promise.resolve(window.Clerk?.session?.getToken() ?? null) : Promise.resolve(localStorage.getItem(TOKEN_KEY));\n }\n function requestHeaders(token, body = false) {\n return {\n ...token ? { Authorization: `Bearer ${token}` } : {},\n ...body ? { \"Content-Type\": \"application/json\" } : {}\n };\n }\n var NAV_HINT_KEY = \"connecta:nav\";\n function rememberNav(data) {\n try {\n sessionStorage.setItem(\n NAV_HINT_KEY,\n JSON.stringify({ activity: data.activityEnabled, artifacts: Boolean(data.artifactsEnabled) })\n );\n } catch {\n }\n }\n function forgetNav() {\n try {\n sessionStorage.removeItem(NAV_HINT_KEY);\n } catch {\n }\n }\n function navHint() {\n try {\n const hint = JSON.parse(sessionStorage.getItem(NAV_HINT_KEY) ?? \"null\");\n return { activity: hint?.activity === true, artifacts: hint?.artifacts === true };\n } catch {\n return { activity: false, artifacts: false };\n }\n }\n function gate(notice = null) {\n forgetNav();\n awaitingAuthorization.clear();\n state = resetIdentity(state, notice);\n for (const listener of listeners) listener();\n }\n var RequestFailure = class extends Error {\n kind;\n status;\n problem;\n constructor(kind, status, problem) {\n super(`Operator request failed: ${kind}`);\n this.kind = kind;\n this.status = status;\n this.problem = problem;\n }\n };\n function factsOf(error) {\n return error instanceof RequestFailure ? error : { kind: \"refused\" };\n }\n async function operatorRequest(path, method, current, body) {\n let token;\n try {\n token = await sessionToken();\n } catch {\n throw new RequestFailure(\"session\");\n }\n if (!current()) throw new RequestFailure(\"session\");\n if (!token && auth.kind !== \"cloudflare-access\") throw new RequestFailure(\"session\");\n let res;\n try {\n res = await fetch(path, {\n method,\n headers: requestHeaders(token, Boolean(body)),\n credentials: \"same-origin\",\n ...body ? { body: JSON.stringify(body) } : {}\n });\n } catch {\n throw new RequestFailure(\"network\");\n }\n if (res.status === 204) return null;\n let payload = {};\n try {\n payload = await res.json();\n } catch {\n }\n if (res.status === 401) throw new RequestFailure(\"session\", 401);\n if (res.status === 403) throw new RequestFailure(\"forbidden\", 403);\n if (!res.ok) throw new RequestFailure(\"refused\", res.status, payload.problem);\n return payload;\n }\n function unreachable(loadFailure) {\n set({ session: \"ready\", gate: null, refreshing: false, loadFailure });\n }\n async function loadData() {\n const current = fence();\n set(state.session === \"ready\" ? { refreshing: true, loadFailure: null } : { loadFailure: null });\n let token;\n try {\n token = await sessionToken();\n } catch {\n if (!current()) return;\n return gate(failure(\"Your sign-in session couldn't be read. Sign in again.\"));\n }\n if (!current()) return;\n if (!token && auth.kind !== \"cloudflare-access\") return gate(null);\n let res;\n try {\n res = await fetch(\"/ui/data\", {\n headers: requestHeaders(token),\n credentials: \"same-origin\"\n });\n } catch {\n if (!current()) return;\n return unreachable(\"network\");\n }\n if (!current()) return;\n if (res.status === 401 || res.status === 403) {\n if (auth.kind === \"clerk\") {\n return gate(\n failure(\n res.status === 403 ? `This Clerk account can't open ${productName}'s operator pages.` : \"Your Clerk session wasn't accepted. Sign out, then sign in again.\"\n )\n );\n }\n if (auth.kind === \"cloudflare-access\") {\n return gate(\n failure(\"Cloudflare Access let this browser in, but this identity isn't an operator here.\")\n );\n }\n localStorage.removeItem(TOKEN_KEY);\n return gate(failure(\"That token wasn't accepted. Paste a valid operator token.\"));\n }\n if (!res.ok) return unreachable(\"server\");\n let data;\n try {\n data = await res.json();\n } catch {\n if (!current()) return;\n return unreachable(\"server\");\n }\n if (!current()) return;\n if (!Array.isArray(data.connectors)) return unreachable(\"server\");\n set({ data, session: \"ready\", gate: null, refreshing: false, loadFailure: null });\n rememberNav(data);\n void loadConnectorDetails(data, current, token);\n }\n async function mutate(options) {\n const current = fence();\n if (options.reload) detailRevisions.set(options.reload, (detailRevisions.get(options.reload) ?? 0) + 1);\n set(options.busy);\n try {\n const payload = await options.request(current);\n if (!current()) return options.abandoned?.();\n set(options.done(payload));\n if (options.reload) void refreshConnector(options.reload);\n } catch (error) {\n if (!current()) return options.abandoned?.();\n set(options.failed(factsOf(error)));\n }\n }\n function focusHandled() {\n if (state.pendingFocus !== null || state.focusIfLost !== null) {\n set({ pendingFocus: null, focusIfLost: null });\n }\n }\n function setPage(page, focus = false) {\n state = withPage(state, page);\n if (focus) {\n state = {\n ...state,\n pendingFocus: state.session === \"ready\" ? `${page}Heading` : \"gateHeading\"\n };\n }\n for (const listener of listeners) listener();\n }\n function navigate(page, href) {\n history.pushState({ operatorPage: page }, \"\", href);\n setPage(page, true);\n }\n function setConnectorFilter(connectorFilter) {\n set({ connectorFilter });\n }\n function setActivitySearch(activitySearch) {\n set({ activitySearch });\n }\n function signInWithBearer(value) {\n gate(null);\n localStorage.setItem(TOKEN_KEY, value);\n void loadCurrent().then(() => {\n if (state.session === \"ready\") set({ pendingFocus: `${state.page}Heading` });\n });\n }\n function forgetBearer() {\n localStorage.removeItem(TOKEN_KEY);\n gate(null);\n set({ pendingFocus: \"token\" });\n }\n function askConfirm(connectorId, action) {\n set({ confirming: { connectorId, action }, pendingFocus: `confirm-cancel-${connectorId}` });\n }\n function cancelConfirm(returnFocusTo) {\n set({ confirming: null, pendingFocus: returnFocusTo });\n }\n function oauthStartPath(connector, mode) {\n return `/ui/oauth/${encodeURIComponent(connector)}?mode=${mode}`;\n }\n var awaitingAuthorization = /* @__PURE__ */ new Set();\n function openBlankTab() {\n let tab = null;\n try {\n tab = window.open(\"\", \"_blank\");\n } catch {\n return null;\n }\n if (!tab) return null;\n try {\n tab.document.title = \"Opening authorization…\";\n tab.document.body.textContent = \"Opening the authorization page…\";\n } catch {\n }\n return tab;\n }\n function oauthNoticePatch(connector, notice) {\n return {\n oauthBusy: null,\n oauthNotice: notice,\n oauthNoticeFor: connector,\n focusIfLost: `oauthNotice-${connector}`\n };\n }\n function startOAuth(connector, mode) {\n const tab = openBlankTab();\n return mutate({\n request: (current) => operatorRequest(oauthStartPath(connector, mode), \"POST\", current),\n busy: {\n oauthBusy: connector,\n oauthNotice: null,\n oauthNoticeFor: connector,\n oauthBlocked: null,\n confirming: null\n },\n done: (payload) => {\n const url = safeHttpHref(payload?.authorizationUrl);\n if (!url) {\n tab?.close();\n if (payload?.state === \"ok\") {\n return oauthNoticePatch(connector, oauthDoneNotice(\"oauth_reconnect\", payload));\n }\n return oauthNoticePatch(connector, refusedNotice(\"oauth_reconnect\", connector));\n }\n if (tab) {\n tab.opener = null;\n tab.location.replace(url);\n }\n awaitingAuthorization.add(connector);\n return {\n ...state.data ? {\n data: {\n ...state.data,\n connectors: state.data.connectors.map(\n (c3) => c3.id === connector ? { ...c3, status: \"auth_required\", tools: [], toolCount: 0, authorizationUrl: url } : c3\n )\n }\n } : {},\n ...oauthNoticePatch(connector, oauthDoneNotice(\"oauth_reconnect\", payload, Boolean(tab))),\n oauthBlocked: tab ? null : connector\n };\n },\n // Signed out or switched mid-request: the tab it opened goes too, rather\n // than sitting on \"Opening…\" with nothing left to send it anywhere.\n abandoned: () => tab?.close(),\n // The route's words never reach this notice (see `refusedNotice`).\n failed: (facts) => {\n tab?.close();\n return oauthNoticePatch(\n connector,\n actionFailedNotice(\"oauth_reconnect\", connector, facts, productName)\n );\n },\n reload: connector\n });\n }\n function disconnectOAuth(connector) {\n return mutate({\n request: (current) => operatorRequest(`/ui/oauth/${encodeURIComponent(connector)}`, \"DELETE\", current),\n busy: {\n oauthBusy: connector,\n oauthNotice: null,\n oauthNoticeFor: connector,\n oauthBlocked: null,\n confirming: null\n },\n done: () => ({\n ...state.data ? {\n data: {\n ...state.data,\n connectors: state.data.connectors.map((c3) => {\n if (c3.id !== connector) return c3;\n const { authorizationUrl: _old, ...rest } = c3;\n return { ...rest, status: \"auth_required\", tools: [], toolCount: 0 };\n })\n }\n } : {},\n ...oauthNoticePatch(connector, oauthDoneNotice(\"oauth_disconnect\", null))\n }),\n failed: (facts) => oauthNoticePatch(\n connector,\n actionFailedNotice(\"oauth_disconnect\", connector, facts, productName)\n ),\n reload: connector\n });\n }\n function editCredential(connector) {\n set({ credentialEditing: connector, credentialNotice: null, credentialNoticeFor: null });\n }\n function refuseCredential(connector, copy) {\n set({ credentialNotice: failure(copy), credentialNoticeFor: connector });\n }\n function credentialMutation(connector, action, request, done, reload = true) {\n const land = (patch) => ({\n credentialBusy: null,\n credentialNoticeFor: connector,\n focusIfLost: `credentialNotice-${connector}`,\n ...patch\n });\n return mutate({\n request,\n busy: {\n credentialBusy: connector,\n credentialNotice: null,\n credentialNoticeFor: connector,\n confirming: null\n },\n done: (payload) => land(done(payload)),\n failed: (facts) => land({ credentialNotice: actionFailedNotice(action, connector, facts, productName) }),\n reload: reload ? connector : void 0\n });\n }\n function saveCredential(connector, body) {\n return credentialMutation(\n connector,\n \"credential_save\",\n (current) => operatorRequest(\n `/ui/credentials/${encodeURIComponent(connector)}`,\n \"PUT\",\n current,\n body\n ),\n () => ({ credentialEditing: null, credentialNotice: info(\"Credential saved.\") })\n );\n }\n function removeCredential(connector) {\n return credentialMutation(\n connector,\n \"credential_remove\",\n (current) => operatorRequest(\n `/ui/credentials/${encodeURIComponent(connector)}`,\n \"DELETE\",\n current\n ),\n () => ({ credentialNotice: info(\"Credential removed.\") })\n );\n }\n function testCredential(connector) {\n return credentialMutation(\n connector,\n \"credential_test\",\n (current) => operatorRequest(\n `/ui/credentials/${encodeURIComponent(connector)}/test`,\n \"POST\",\n current\n ),\n (payload) => ({ credentialNotice: credentialTestNotice(connector, payload) }),\n false\n );\n }\n async function loadActivity(reset) {\n if (!state.data?.activityEnabled) return;\n const current = fence();\n set({\n activityPhase: \"loading\",\n activityNotice: null,\n ...reset ? { activityEvents: [], activityCursor: null } : {}\n });\n const params = new URLSearchParams({ limit: \"50\" });\n if (!reset && state.activityCursor) {\n params.set(\"cursor\", state.activityCursor);\n }\n try {\n const payload = await operatorRequest(\n `/ui/activity?${params}`,\n \"GET\",\n current\n );\n if (!current()) return;\n set({\n activityPhase: \"ready\",\n activityEvents: [\n ...reset ? [] : state.activityEvents,\n ...payload?.events ?? []\n ],\n activityCursor: payload?.nextCursor ?? null\n });\n } catch (error) {\n if (!current()) return;\n set({\n activityPhase: \"error\",\n activityNotice: failure(collectionFailureCopy(\"activity\", factsOf(error), productName))\n });\n }\n }\n async function artifactRead(path, current, collection) {\n let token;\n try {\n token = await sessionToken();\n } catch {\n if (current()) gate(failure(\"Your sign-in session couldn't be read. Sign in again.\"));\n return void 0;\n }\n if (!current()) return void 0;\n if (!token && auth.kind !== \"cloudflare-access\") {\n gate(null);\n return void 0;\n }\n let res;\n try {\n res = await fetch(path, { headers: requestHeaders(token), credentials: \"same-origin\" });\n } catch {\n if (current()) {\n set({\n session: \"ready\",\n gate: null,\n artifactPhase: \"error\",\n artifactNotice: failure(collectionFailureCopy(collection, { kind: \"network\" }, productName))\n });\n }\n return void 0;\n }\n if (!current()) return void 0;\n if (res.status === 401) {\n if (auth.kind !== \"clerk\" && auth.kind !== \"cloudflare-access\") {\n localStorage.removeItem(TOKEN_KEY);\n gate(failure(\"That token wasn't accepted. Paste a valid operator token.\"));\n } else {\n gate(failure(\"Your session wasn't accepted. Sign out, then sign in again.\"));\n }\n return void 0;\n }\n if (res.status === 403) {\n gate(failure(\"This deployment doesn't open artifact pages to this identity.\"));\n return void 0;\n }\n return res;\n }\n var artifactRevision = 0;\n async function loadArtifacts(reset) {\n const identityCurrent = fence();\n const revision = ++artifactRevision;\n const current = () => identityCurrent() && revision === artifactRevision;\n set({\n artifactPhase: \"loading\",\n artifactNotice: null,\n ...reset ? { artifactRows: [], artifactCursor: null } : {}\n });\n const params = new URLSearchParams();\n if (state.artifactQuery.trim()) params.set(\"q\", state.artifactQuery.trim());\n if (state.artifactArchived) params.set(\"archived\", \"1\");\n if (!reset && state.artifactCursor) params.set(\"cursor\", state.artifactCursor);\n const query = params.toString();\n const res = await artifactRead(`/artifacts/_api/list${query ? `?${query}` : \"\"}`, current, \"artifacts\");\n if (!res || !current()) return;\n if (!res.ok) {\n return set({\n session: \"ready\",\n gate: null,\n artifactPhase: \"error\",\n artifactNotice: failure(\n collectionFailureCopy(\"artifacts\", { kind: \"refused\", status: res.status }, productName)\n )\n });\n }\n let payload;\n try {\n payload = await res.json();\n } catch {\n payload = {};\n }\n if (!current()) return;\n set({\n session: \"ready\",\n gate: null,\n artifactPhase: \"ready\",\n artifactRows: [...reset ? [] : state.artifactRows, ...payload.artifacts ?? []],\n artifactCursor: payload.nextCursor ?? null\n });\n }\n var artifactFrameConfined = false;\n function markArtifactFrameConfined() {\n artifactFrameConfined = true;\n }\n async function loadArtifactView() {\n if (artifactFrameConfined) {\n window.location.reload();\n return;\n }\n const current = fence();\n const request = artifactViewRequest(window.location.pathname, window.location.search);\n set({ artifactPhase: \"loading\", artifactNotice: null });\n if (!request) {\n return set({\n session: \"ready\",\n artifactPhase: \"error\",\n artifactNotice: failure(\"There is no artifact at this address.\")\n });\n }\n const res = await artifactRead(request, current, \"artifact\");\n if (!res || !current()) return;\n if (!res.ok) {\n return set({\n session: \"ready\",\n gate: null,\n artifactPhase: \"error\",\n artifactNotice: failure(\n collectionFailureCopy(\"artifact\", { kind: \"refused\", status: res.status }, productName)\n )\n });\n }\n let view = null;\n try {\n view = await res.json();\n } catch {\n view = null;\n }\n if (!current()) return;\n if (!view || typeof view.document !== \"string\") {\n return set({\n session: \"ready\",\n gate: null,\n artifactPhase: \"error\",\n artifactNotice: failure(\"The artifact couldn't be read. Retry in a moment.\")\n });\n }\n set({ session: \"ready\", gate: null, artifactPhase: \"ready\", artifactView: view });\n }\n function setArtifactQuery(artifactQuery) {\n set({ artifactQuery });\n }\n function setArtifactArchived(artifactArchived) {\n set({ artifactArchived });\n void loadArtifacts(true);\n }\n function loadCurrent() {\n if (state.page === \"artifacts\") return loadArtifacts(true);\n if (state.page === \"artifact\") return loadArtifactView();\n return loadData();\n }\n function retryLoad() {\n set({\n pendingFocus: state.page === \"connections\" ? \"connectorLedgerHeading\" : `${state.page}Heading`\n });\n return loadCurrent();\n }\n function retryCollection() {\n set({ pendingFocus: `${state.page}Heading` });\n if (state.page === \"activity\") return loadActivity(true);\n if (state.page === \"artifact\") return loadArtifactView();\n return loadArtifacts(true);\n }\n function signIn() {\n window.Clerk?.redirectToSignIn({\n signInFallbackRedirectUrl: window.location.href,\n signUpFallbackRedirectUrl: window.location.href\n });\n }\n function signOut() {\n if (auth.kind === \"cloudflare-access\") {\n gate(null);\n window.location.assign(\"/cdn-cgi/access/logout\");\n return;\n }\n const clerk = window.Clerk;\n gate(null);\n void clerk?.signOut({ redirectUrl: window.location.href });\n }\n var returnTimer;\n var lastReturnPass = 0;\n var RETURN_DEBOUNCE_MS = 150;\n var RETURN_MIN_INTERVAL_MS = 2e3;\n function onReturn() {\n if (typeof document !== \"undefined\" && document.visibilityState === \"hidden\") return;\n if (returnTimer !== void 0) return;\n returnTimer = setTimeout(() => {\n returnTimer = void 0;\n const now = Date.now();\n if (now - lastReturnPass < RETURN_MIN_INTERVAL_MS) return;\n lastReturnPass = now;\n recheckAuthorization();\n }, RETURN_DEBOUNCE_MS);\n }\n function recheckAuthorization() {\n if (state.session !== \"ready\" || !state.data) return;\n for (const connector of state.data.connectors) {\n if (state.oauthBusy === connector.id) continue;\n const authorizesElsewhere = connector.status === \"auth_required\" && (connector.oauth === true || Boolean(connector.authorizationUrl));\n if (authorizesElsewhere || awaitingAuthorization.has(connector.id)) {\n void refreshConnector(connector.id, true);\n }\n }\n }\n async function boot() {\n const onPop = () => setPage(pageForPath(window.location.pathname), true);\n window.addEventListener(\"popstate\", onPop);\n window.addEventListener(\"focus\", onReturn);\n if (typeof document !== \"undefined\") {\n document.addEventListener(\"visibilitychange\", onReturn);\n }\n if (auth.kind === \"clerk\") {\n const clerk = window.Clerk;\n if (!clerk) {\n return gate(failure(\"Clerk couldn't load. Check your connection and try again.\"));\n }\n try {\n await clerk.load({\n ...auth.signInUrl ? { signInUrl: auth.signInUrl } : {},\n ...auth.signUpUrl ? { signUpUrl: auth.signUpUrl } : {},\n signInFallbackRedirectUrl: window.location.href,\n signUpFallbackRedirectUrl: window.location.href,\n afterSignOutUrl: window.location.href\n });\n let sessionId = clerk.session?.id ?? null;\n clerk.addListener((resources) => {\n const next = resources.session?.id ?? null;\n if (next === sessionId) return;\n sessionId = next;\n gate(null);\n void loadCurrent();\n });\n } catch {\n return gate(failure(\"Clerk couldn't start. Reload the page to try again.\"));\n }\n }\n await loadCurrent();\n }\n async function readConnector(id, token) {\n let response;\n try {\n response = await fetch(`/ui/connectors/${encodeURIComponent(id)}`, {\n headers: requestHeaders(token),\n credentials: \"same-origin\"\n });\n } catch {\n return { kind: \"local\", failure: \"network\" };\n }\n if (response.status === 401 || response.status === 403) {\n return { kind: \"local\", failure: \"session\" };\n }\n if (!response.ok) return { kind: \"downstream\" };\n try {\n return { kind: \"detail\", detail: await response.json() };\n } catch {\n return { kind: \"downstream\" };\n }\n }\n function withoutKey(record, key) {\n const { [key]: _gone, ...rest } = record;\n return rest;\n }\n function applyDetail(id, outcome) {\n if (!state.data) return;\n const patch = {};\n const connectors = state.data.connectors.map((c3) => {\n if (c3.id !== id) return c3;\n if (outcome.kind === \"detail\") {\n const { detail } = outcome;\n return detail.status === \"auth_required\" && c3.authorizationUrl && !detail.authorizationUrl ? { ...detail, authorizationUrl: c3.authorizationUrl } : detail;\n }\n const { problem: _problem, ...rest } = c3;\n return outcome.kind === \"downstream\" ? { ...rest, status: \"error\", problem: \"connector_unavailable\" } : { ...rest, status: \"error\" };\n });\n patch.connectorFailures = outcome.kind === \"local\" ? { ...state.connectorFailures, [id]: outcome.failure } : withoutKey(state.connectorFailures, id);\n if (outcome.kind === \"detail\" && outcome.detail.status === \"ok\" && awaitingAuthorization.delete(id)) {\n if (state.oauthNoticeFor === id) {\n patch.oauthNotice = info(\"Connected.\");\n patch.oauthBlocked = null;\n }\n }\n set({ ...patch, data: { ...state.data, connectors } });\n }\n var detailGeneration = 0;\n var detailRevisions = /* @__PURE__ */ new Map();\n async function loadConnectorDetails(data, current, token) {\n const generation = ++detailGeneration;\n let next = 0;\n const worker = async () => {\n while (next < data.connectors.length && current() && generation === detailGeneration) {\n const connector = data.connectors[next++];\n const revision = (detailRevisions.get(connector.id) ?? 0) + 1;\n detailRevisions.set(connector.id, revision);\n const outcome = await readConnector(connector.id, token);\n if (!current() || generation !== detailGeneration || !state.data) return;\n if (detailRevisions.get(connector.id) !== revision) continue;\n applyDetail(connector.id, outcome);\n }\n };\n await Promise.all(Array.from({ length: Math.min(4, data.connectors.length) }, worker));\n }\n async function refreshConnector(id, quiet = false) {\n const current = fence();\n const revision = (detailRevisions.get(id) ?? 0) + 1;\n detailRevisions.set(id, revision);\n if (!quiet && state.data) {\n set({\n data: { ...state.data, connectors: state.data.connectors.map((c3) => c3.id === id ? { ...c3, status: \"loading\" } : c3) },\n connectorFailures: withoutKey(state.connectorFailures, id)\n });\n }\n let token;\n try {\n token = await sessionToken();\n } catch {\n if (!current() || quiet || detailRevisions.get(id) !== revision) return;\n return applyDetail(id, { kind: \"local\", failure: \"session\" });\n }\n if (!current()) return;\n const outcome = await readConnector(id, token);\n if (!current() || !state.data || detailRevisions.get(id) !== revision) return;\n if (quiet && outcome.kind !== \"detail\") return;\n applyDetail(id, outcome);\n }\n var tokenRevision = 0;\n async function loadAccessTokens() {\n const identityCurrent = fence();\n const revision = ++tokenRevision;\n const current = () => identityCurrent() && revision === tokenRevision;\n const existing = new Map(state.tokens.map((token) => [token.id, token]));\n set({ tokenPhase: \"loading\", tokenNotice: null });\n try {\n const payload = await operatorRequest(\"/ui/access-tokens\", \"GET\", current);\n if (!current()) return;\n const tokens = payload?.accessTokens ?? [];\n const changed = state.tokens.filter((token) => existing.get(token.id) !== token);\n const changedIds = new Set(changed.map((token) => token.id));\n set({ tokenPhase: \"ready\", tokens: [\n ...changed,\n ...tokens.filter((token) => !changedIds.has(token.id))\n ] });\n } catch {\n if (!current()) return;\n set({\n tokenPhase: \"error\",\n tokenNotice: failure(\n \"Access tokens could not be loaded.\"\n )\n });\n }\n }\n function tokenFailure(tokenNotice) {\n return { tokenBusy: false, tokenNotice, pendingFocus: \"tokenNotice\" };\n }\n function createAccessToken(name) {\n if (state.tokenBusy) return Promise.resolve(false);\n if (!name) {\n set(tokenFailure(failure(\"Name the MCP client before creating a token.\")));\n return Promise.resolve(false);\n }\n let created = false;\n return mutate({\n request: (current) => operatorRequest(\"/ui/access-tokens\", \"POST\", current, { name }),\n busy: { tokenBusy: true, tokenNotice: null },\n done: (payload) => {\n const issued = payload?.accessToken;\n if (!payload?.token || !issued) {\n throw new Error(\"The created token was not returned.\");\n }\n created = true;\n return {\n tokenBusy: false,\n tokenPhase: \"ready\",\n tokens: [\n issued,\n ...state.tokens.filter((token) => token.id !== issued.id)\n ],\n createdToken: state.page === \"tokens\" ? payload.token : null,\n tokenNotice: info(\"Access token created.\"),\n pendingFocus: state.page === \"tokens\" ? \"tokenRevealHeading\" : null\n };\n },\n failed: () => tokenFailure(failure(\"Access token could not be created. Check the name, capacity, and storage.\"))\n }).then(() => created);\n }\n function dismissCreatedToken() {\n set({ createdToken: null });\n }\n function renameAccessToken(id) {\n set({ tokenRenaming: id });\n }\n function accessTokenMutation(id, method, body, success, fallback) {\n return mutate({\n request: (current) => operatorRequest(\n `/ui/access-tokens/${encodeURIComponent(id)}`,\n method,\n current,\n body\n ),\n busy: { tokenBusy: true, tokenNotice: null },\n done: (payload) => ({\n tokenBusy: false,\n tokenRenaming: null,\n tokenNotice: info(success),\n pendingFocus: \"tokenNotice\",\n ...payload?.accessToken ? {\n tokens: state.tokens.map(\n (token) => token.id === id ? payload.accessToken : token\n )\n } : {}\n }),\n failed: () => tokenFailure(failure(fallback))\n });\n }\n function saveAccessTokenName(id, name) {\n return accessTokenMutation(\n id,\n \"PUT\",\n { name },\n \"Access token renamed.\",\n \"Access token could not be renamed.\"\n );\n }\n function revokeAccessToken(id) {\n const named = state.tokens.find((token) => token.id === id);\n const confirmed = window.confirm(\n `Revoke ${named?.name || \"this access token\"}? Its MCP client will immediately lose access.`\n );\n if (!confirmed) return Promise.resolve();\n return accessTokenMutation(\n id,\n \"DELETE\",\n void 0,\n \"Access token revoked.\",\n \"Access token could not be revoked.\"\n );\n }\n\n // node_modules/preact/jsx-runtime/dist/jsxRuntime.module.js\n var f3 = 0;\n function u3(e3, t3, n2, o3, i3, u4) {\n t3 || (t3 = {});\n var a3, c3, p3 = t3;\n if (\"ref\" in p3) for (c3 in p3 = {}, t3) \"ref\" == c3 ? a3 = t3[c3] : p3[c3] = t3[c3];\n var l3 = { type: e3, props: p3, key: n2, ref: a3, __k: null, __: null, __b: 0, __e: null, __c: null, constructor: void 0, __v: --f3, __i: -1, __u: 0, __source: i3, __self: u4 };\n if (\"function\" == typeof e3 && (a3 = e3.defaultProps)) for (c3 in a3) void 0 === p3[c3] && (p3[c3] = a3[c3]);\n return l.vnode && l.vnode(l3), l3;\n }\n\n // src/operator-ui/app/parts.tsx\n function NoticeLine({\n id,\n notice,\n className = \"meta\"\n }) {\n return /* @__PURE__ */ u3(S, { children: [\n /* @__PURE__ */ u3(\n \"p\",\n {\n id,\n class: notice?.tone === \"error\" ? `notice ${className} error-notice` : `notice ${className}`,\n role: notice?.tone === \"error\" ? \"alert\" : \"status\",\n \"aria-live\": \"polite\",\n tabIndex: -1,\n children: notice ? notice.message : null\n }\n ),\n notice?.tone === \"error\" && notice.fix ? /* @__PURE__ */ u3(FixPrompt, { kind: notice.fix.kind, connectorId: notice.fix.connectorId }) : null\n ] });\n }\n function FixPrompt({\n kind,\n connectorId,\n name\n }) {\n return /* @__PURE__ */ u3(\"div\", { class: \"fix-prompt\", \"data-fix-prompt\": kind, children: [\n /* @__PURE__ */ u3(FixPromptButton, { kind, connectorId, ...name ? { name } : {} }),\n /* @__PURE__ */ u3(FixPromptPreview, { kind, connectorId })\n ] });\n }\n function FixPromptButton({\n kind,\n connectorId,\n name\n }) {\n return /* @__PURE__ */ u3(\n CopyButton,\n {\n value: fixPrompt(kind, connectorId),\n label: \"Copy fix prompt\",\n class: \"btn quiet\",\n ariaLabel: `Copy fix prompt for ${name ?? connectorId}`\n }\n );\n }\n function FixPromptPreview({\n kind,\n connectorId,\n standalone\n }) {\n return /* @__PURE__ */ u3(\"details\", { class: \"fix-prompt-preview\", ...standalone ? { \"data-fix-prompt\": kind } : {}, children: [\n /* @__PURE__ */ u3(\"summary\", { class: \"disclosure\", children: \"Preview prompt\" }),\n /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: \"Fixed text for a coding agent working on this deployment. It carries no error details or secrets.\" }),\n /* @__PURE__ */ u3(\"pre\", { class: \"fix-prompt-text\", children: fixPrompt(kind, connectorId) })\n ] });\n }\n function Badge({\n tone = \"neutral\",\n children\n }) {\n return /* @__PURE__ */ u3(\"span\", { class: tone === \"neutral\" ? \"badge\" : `badge ${tone}`, children });\n }\n function StateBlock({\n title,\n children,\n tone = \"neutral\",\n action,\n id\n }) {\n return /* @__PURE__ */ u3(\n \"div\",\n {\n class: tone === \"error\" ? \"state-block error\" : \"state-block\",\n role: tone === \"error\" ? \"alert\" : \"status\",\n ...id ? { id } : {},\n children: [\n title ? /* @__PURE__ */ u3(\"p\", { class: \"state-title\", children: title }) : null,\n children ? /* @__PURE__ */ u3(\"p\", { class: \"state-copy\", children }) : null,\n action ? /* @__PURE__ */ u3(\n \"button\",\n {\n class: \"btn\",\n type: \"button\",\n onClick: action.onClick,\n ...action.id ? { id: action.id } : {},\n children: action.label\n }\n ) : null\n ]\n }\n );\n }\n function LoadFailure({ state: state2 }) {\n if (!state2.loadFailure) return null;\n const copy = loadFailureCopy(state2.loadFailure, productName);\n return /* @__PURE__ */ u3(\n StateBlock,\n {\n id: \"loadFailure\",\n tone: \"error\",\n title: copy.title,\n action: { label: \"Retry\", onClick: () => void retryLoad(), id: \"retryLoad\" },\n children: copy.body\n }\n );\n }\n function Empty({ children }) {\n return /* @__PURE__ */ u3(StateBlock, { children });\n }\n function Unavailable({ children }) {\n return /* @__PURE__ */ u3(\"div\", { class: \"unavailable\", children });\n }\n function ConfirmBar({\n id,\n question,\n confirm,\n onConfirm,\n onCancel\n }) {\n return /* @__PURE__ */ u3(\n \"div\",\n {\n class: \"confirm\",\n role: \"group\",\n \"aria-labelledby\": `confirm-question-${id}`,\n onKeyDown: (event) => {\n if (event.key !== \"Escape\") return;\n event.preventDefault();\n onCancel();\n },\n children: [\n /* @__PURE__ */ u3(\"p\", { id: `confirm-question-${id}`, children: question }),\n /* @__PURE__ */ u3(\"div\", { class: \"actions\", children: [\n /* @__PURE__ */ u3(\"button\", { class: \"btn danger\", type: \"button\", onClick: onConfirm, children: confirm }),\n /* @__PURE__ */ u3(\n \"button\",\n {\n id: `confirm-cancel-${id}`,\n class: \"btn quiet\",\n type: \"button\",\n onClick: onCancel,\n children: \"Cancel\"\n }\n )\n ] })\n ]\n }\n );\n }\n function focusableId(...ids) {\n for (const id of ids) {\n const element = document.getElementById(id);\n if (element && !element.disabled) return id;\n }\n return ids[ids.length - 1] ?? \"\";\n }\n function PageLink({\n page,\n class: className,\n current,\n children\n }) {\n const href = PAGE_META[page].path;\n return /* @__PURE__ */ u3(\n \"a\",\n {\n class: className,\n href,\n ...current ? { \"aria-current\": \"page\" } : {},\n onClick: (event) => {\n if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {\n return;\n }\n event.preventDefault();\n navigate(page, href);\n },\n children\n }\n );\n }\n function CopyButton({\n value,\n label,\n class: className = \"btn\",\n ariaLabel,\n id\n }) {\n const [status, setStatus] = d2(\"idle\");\n h2(() => {\n if (status === \"idle\") return;\n const timer = window.setTimeout(() => setStatus(\"idle\"), 1600);\n return () => window.clearTimeout(timer);\n }, [status]);\n return /* @__PURE__ */ u3(\n \"button\",\n {\n class: className,\n type: \"button\",\n ...id ? { id } : {},\n ...ariaLabel && status === \"idle\" ? { \"aria-label\": ariaLabel } : {},\n onClick: () => {\n const write = navigator.clipboard?.writeText(value) ?? Promise.reject(new Error(\"no clipboard\"));\n write.then(\n () => setStatus(\"copied\"),\n () => setStatus(\"failed\")\n );\n },\n children: status === \"copied\" ? \"Copied\" : status === \"failed\" ? \"Copy failed\" : label\n }\n );\n }\n\n // src/operator-ui/app/tokens.tsx\n function CreateForm({ busy }) {\n const [name, setName] = d2(\"\");\n return /* @__PURE__ */ u3(\n \"form\",\n {\n id: \"tokenCreateForm\",\n class: \"token-create\",\n onSubmit: (event) => {\n event.preventDefault();\n void createAccessToken(name.trim()).then((created) => {\n if (created) setName(\"\");\n });\n },\n children: [\n /* @__PURE__ */ u3(\"label\", { for: \"tokenName\", children: \"Client name\" }),\n /* @__PURE__ */ u3(\"div\", { class: \"row\", children: [\n /* @__PURE__ */ u3(\n \"input\",\n {\n id: \"tokenName\",\n type: \"text\",\n maxLength: 80,\n placeholder: \"Claude desktop, ChatGPT production…\",\n autocomplete: \"off\",\n value: name,\n onInput: (event) => setName(event.currentTarget.value)\n }\n ),\n /* @__PURE__ */ u3(\"button\", { id: \"createToken\", class: \"btn\", type: \"submit\", disabled: busy, children: busy ? \"Creating…\" : \"Create token\" })\n ] })\n ]\n }\n );\n }\n function Reveal({ token }) {\n return /* @__PURE__ */ u3(\n \"section\",\n {\n id: \"tokenReveal\",\n class: \"token-reveal\",\n \"aria-labelledby\": \"tokenRevealHeading\",\n children: [\n /* @__PURE__ */ u3(\"div\", { class: \"token-reveal-head\", children: [\n /* @__PURE__ */ u3(\"h2\", { id: \"tokenRevealHeading\", tabIndex: -1, children: \"Copy this token now\" }),\n /* @__PURE__ */ u3(\"span\", { class: \"cap\", children: \"Shown once\" })\n ] }),\n /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: \"Store it in the MCP client before leaving this page. It cannot be displayed again.\" }),\n /* @__PURE__ */ u3(\"div\", { class: \"endpoint-row token-secret\", children: [\n /* @__PURE__ */ u3(\"code\", { id: \"createdToken\", class: \"mono\", children: token }),\n /* @__PURE__ */ u3(CopyButton, { value: token, label: \"Copy token\" })\n ] }),\n /* @__PURE__ */ u3(\"button\", { class: \"btn\", type: \"button\", onClick: dismissCreatedToken, children: \"I stored it\" })\n ]\n }\n );\n }\n function TokenCard({\n token,\n renaming,\n busy\n }) {\n const [name, setName] = d2(token.name);\n const revoked = Boolean(token.revokedAt);\n return /* @__PURE__ */ u3(\n \"section\",\n {\n class: revoked ? \"token-card revoked\" : \"token-card\",\n \"aria-labelledby\": `access-token-${token.id}`,\n children: [\n /* @__PURE__ */ u3(\"div\", { class: \"token-card-head\", children: [\n /* @__PURE__ */ u3(\"div\", { children: [\n /* @__PURE__ */ u3(\"h2\", { id: `access-token-${token.id}`, children: token.name }),\n /* @__PURE__ */ u3(\"p\", { class: \"mono\", children: [\n token.tokenPrefix,\n \"…\"\n ] })\n ] }),\n /* @__PURE__ */ u3(\"div\", { class: \"cap\", children: revoked ? `Revoked ${formatDate(token.revokedAt)}` : `Created ${formatDate(token.createdAt)}` })\n ] }),\n /* @__PURE__ */ u3(\"div\", { class: \"credential-actions\", children: [\n /* @__PURE__ */ u3(\n \"button\",\n {\n class: \"btn\",\n type: \"button\",\n disabled: busy,\n onClick: () => {\n setName(token.name);\n renameAccessToken(renaming ? null : token.id);\n },\n children: \"Rename\"\n }\n ),\n revoked ? null : /* @__PURE__ */ u3(\n \"button\",\n {\n class: \"btn danger\",\n type: \"button\",\n disabled: busy,\n onClick: () => void revokeAccessToken(token.id),\n children: \"Revoke\"\n }\n )\n ] }),\n renaming ? /* @__PURE__ */ u3(\n \"form\",\n {\n class: \"credential-form\",\n onSubmit: (event) => {\n event.preventDefault();\n const next = name.trim();\n if (next) void saveAccessTokenName(token.id, next);\n },\n children: [\n /* @__PURE__ */ u3(\"label\", { class: \"visually-hidden\", for: `token-name-${token.id}`, children: \"Token name\" }),\n /* @__PURE__ */ u3(\n \"input\",\n {\n id: `token-name-${token.id}`,\n type: \"text\",\n maxLength: 80,\n autocomplete: \"off\",\n value: name,\n onInput: (event) => setName(event.currentTarget.value)\n }\n ),\n /* @__PURE__ */ u3(\"button\", { class: \"btn\", type: \"submit\", disabled: busy, children: \"Save name\" }),\n /* @__PURE__ */ u3(\n \"button\",\n {\n class: \"btn\",\n type: \"button\",\n disabled: busy,\n onClick: () => renameAccessToken(null),\n children: \"Cancel\"\n }\n )\n ]\n }\n ) : null\n ]\n }\n );\n }\n function TokensPage({ state: state2 }) {\n const available = state2.data?.accessTokenManagement === \"available\";\n return /* @__PURE__ */ u3(\"section\", { id: \"tokensView\", children: /* @__PURE__ */ u3(\"div\", { class: \"lead\", children: [\n /* @__PURE__ */ u3(\"h1\", { id: \"tokensHeading\", class: \"\", tabIndex: -1, children: \"Access tokens\" }),\n /* @__PURE__ */ u3(\"div\", { class: \"lead-copy\", children: [\n /* @__PURE__ */ u3(\"p\", { class: \"activity-copy\", children: \"Create named Bearer tokens for MCP clients. Each secret is shown once; revoke it when that client should lose access.\" }),\n /* @__PURE__ */ u3(NoticeLine, { id: \"tokenNotice\", notice: state2.tokenNotice }),\n !available ? /* @__PURE__ */ u3(Unavailable, { children: accessTokenUnavailableCopy(state2.data?.accessTokenManagement) }) : /* @__PURE__ */ u3(\"div\", { id: \"tokenAvailable\", children: [\n state2.createdToken ? /* @__PURE__ */ u3(Reveal, { token: state2.createdToken }) : /* @__PURE__ */ u3(CreateForm, { busy: state2.tokenBusy }),\n /* @__PURE__ */ u3(\n \"div\",\n {\n id: \"tokenList\",\n class: \"token-ledger\",\n \"aria-busy\": state2.tokenPhase === \"loading\" ? \"true\" : \"false\",\n children: state2.tokenPhase === \"loading\" ? /* @__PURE__ */ u3(Empty, { children: \"Loading access tokens…\" }) : state2.tokenPhase === \"error\" ? /* @__PURE__ */ u3(\"p\", { class: \"empty\", children: /* @__PURE__ */ u3(\n \"button\",\n {\n class: \"btn\",\n type: \"button\",\n onClick: () => void loadAccessTokens(),\n children: \"Try loading access tokens again\"\n }\n ) }) : state2.tokens.length === 0 ? /* @__PURE__ */ u3(Empty, { children: \"No access tokens yet. Name the first MCP client above.\" }) : state2.tokens.map((token) => /* @__PURE__ */ u3(\n TokenCard,\n {\n token,\n renaming: state2.tokenRenaming === token.id,\n busy: state2.tokenBusy\n },\n token.id\n ))\n }\n )\n ] })\n ] })\n ] }) });\n }\n\n // src/operator-ui/app/activity.tsx\n function ActivityRow({ event }) {\n const outcome = activityOutcomeClass(event.outcome);\n const badge = activityOutcomeBadge(event.outcome);\n const stableId = actorStableId(event.actor);\n return /* @__PURE__ */ u3(\"article\", { class: `activity-item ${outcome}`, children: [\n /* @__PURE__ */ u3(\"div\", { class: \"activity-stamp\", children: [\n /* @__PURE__ */ u3(\"span\", { class: `dot ${outcome}`, \"aria-hidden\": \"true\" }),\n /* @__PURE__ */ u3(\"div\", { children: [\n /* @__PURE__ */ u3(\"time\", { class: \"activity-time\", dateTime: event.occurredAt, children: formatDate(event.occurredAt) }),\n /* @__PURE__ */ u3(\"div\", { class: \"activity-actor\", children: actorLabel(event.actor) }),\n stableId ? /* @__PURE__ */ u3(\"div\", { class: \"activity-actor-id mono\", children: stableId }) : null\n ] })\n ] }),\n /* @__PURE__ */ u3(\"div\", { children: [\n /* @__PURE__ */ u3(\"div\", { class: \"activity-address\", children: event.address }),\n /* @__PURE__ */ u3(\"div\", { class: \"activity-detail\", children: activityDetail(event) })\n ] }),\n /* @__PURE__ */ u3(\"div\", { class: \"activity-result\", children: [\n /* @__PURE__ */ u3(Badge, { tone: badge.tone, children: badge.label }),\n /* @__PURE__ */ u3(\"div\", { class: \"activity-detail\", children: [\n event.durationMs,\n \" ms\"\n ] })\n ] })\n ] });\n }\n function ActivityPage({ state: state2 }) {\n const data = state2.data;\n const enabled = Boolean(data?.activityEnabled);\n const loading = state2.activityPhase === \"loading\";\n const visible = filterActivity(state2.activityEvents, state2.activitySearch);\n const summary = activitySummary(state2.activityEvents);\n const failed = state2.activityPhase === \"error\";\n return /* @__PURE__ */ u3(\"section\", { id: \"activityView\", children: [\n /* @__PURE__ */ u3(\"div\", { class: \"lead\", children: [\n /* @__PURE__ */ u3(\"h1\", { id: \"activityHeading\", tabIndex: -1, children: \"Activity\" }),\n /* @__PURE__ */ u3(\"div\", { class: \"lead-copy\", children: /* @__PURE__ */ u3(\"p\", { children: pageDescription(\"activity\", productDescription) }) })\n ] }),\n !data ? (\n // Whether Activity is open to this identity is in /ui/data, which\n // has not answered yet — or could not.\n state2.loadFailure ? /* @__PURE__ */ u3(LoadFailure, { state: state2 }) : /* @__PURE__ */ u3(StateBlock, { children: \"Loading activity…\" })\n ) : !enabled ? /* @__PURE__ */ u3(Unavailable, { children: [\n \"Activity history is not configured. Add an\",\n \" \",\n /* @__PURE__ */ u3(\"span\", { class: \"mono\", children: \"activity.store\" }),\n \" with a list reader to enable this page.\"\n ] }) : /* @__PURE__ */ u3(\"div\", { id: \"activityAvailable\", class: \"collection\", children: [\n failed && state2.activityEvents.length === 0 ? null : /* @__PURE__ */ u3(\"div\", { class: \"row\", children: [\n /* @__PURE__ */ u3(\n \"input\",\n {\n id: \"activitySearch\",\n type: \"search\",\n placeholder: \"Search user, tool, or outcome…\",\n \"aria-label\": \"Search loaded activity\",\n value: state2.activitySearch,\n onInput: (event) => setActivitySearch(event.currentTarget.value)\n }\n ),\n /* @__PURE__ */ u3(\n \"button\",\n {\n id: \"refreshActivity\",\n class: \"btn\",\n type: \"button\",\n disabled: loading,\n onClick: () => void loadActivity(true),\n children: loading ? \"Loading…\" : \"Refresh\"\n }\n )\n ] }),\n /* @__PURE__ */ u3(\"p\", { id: \"activitySummary\", class: \"meta\", \"aria-live\": \"polite\", children: summary }),\n state2.activityEvents.length === 0 ? loading ? /* @__PURE__ */ u3(StateBlock, { children: \"Loading activity…\" }) : failed ? /* @__PURE__ */ u3(\n StateBlock,\n {\n id: \"activityError\",\n tone: \"error\",\n title: \"Activity couldn't be loaded\",\n action: { label: \"Retry\", onClick: () => void retryCollection() },\n children: state2.activityNotice?.message\n }\n ) : /* @__PURE__ */ u3(StateBlock, { children: state2.activitySearch.trim() ? \"No loaded activity matches this search.\" : \"No connector tool calls recorded yet.\" }) : visible.length === 0 ? /* @__PURE__ */ u3(StateBlock, { children: \"No loaded activity matches this search.\" }) : /* @__PURE__ */ u3(\n \"div\",\n {\n id: \"activityList\",\n class: \"activity-list\",\n \"aria-busy\": loading ? \"true\" : \"false\",\n children: visible.map((event, index) => /* @__PURE__ */ u3(\n ActivityRow,\n {\n event\n },\n `${event.occurredAt}-${event.address}-${index}`\n ))\n }\n ),\n state2.activityEvents.length > 0 ? /* @__PURE__ */ u3(NoticeLine, { id: \"activityNotice\", notice: state2.activityNotice }) : null,\n state2.activityCursor ? /* @__PURE__ */ u3(\n \"button\",\n {\n id: \"moreActivity\",\n class: \"btn activity-more\",\n type: \"button\",\n disabled: loading,\n onClick: () => void loadActivity(false),\n children: loading ? \"Loading…\" : \"Load older\"\n }\n ) : null\n ] })\n ] });\n }\n\n // src/operator-ui/app/artifacts.tsx\n function ArtifactRow({ row }) {\n const refresh = artifactRefreshBadge(row.freshness?.last);\n return /* @__PURE__ */ u3(\"article\", { class: \"artifact-row\", children: [\n /* @__PURE__ */ u3(\"div\", { children: [\n /* @__PURE__ */ u3(\"a\", { class: \"artifact-title\", href: `/artifacts/${row.id}`, children: row.title }),\n /* @__PURE__ */ u3(\"div\", { class: \"artifact-meta\", children: [\n /* @__PURE__ */ u3(\"span\", { class: \"mono\", children: row.id }),\n \" · version \",\n row.viewVersion,\n \" · updated\",\n \" \",\n /* @__PURE__ */ u3(\"time\", { dateTime: row.updatedAt, children: formatDate(row.updatedAt) }),\n \" by \",\n row.updatedBy.label\n ] })\n ] }),\n /* @__PURE__ */ u3(\"div\", { class: \"artifact-badges\", children: [\n /* @__PURE__ */ u3(Badge, { children: row.kind === \"markdown\" ? \"Markdown\" : \"HTML\" }),\n row.freshness?.state === \"stale\" ? /* @__PURE__ */ u3(Badge, { tone: \"warn\", children: \"Stale data\" }) : row.freshness?.state === \"current\" ? /* @__PURE__ */ u3(Badge, { children: \"Current data\" }) : null,\n refresh ? /* @__PURE__ */ u3(Badge, { tone: refresh.tone, children: refresh.label }) : null,\n row.archived ? /* @__PURE__ */ u3(Badge, { tone: \"warn\", children: \"Archived\" }) : null\n ] })\n ] });\n }\n function ArtifactsPage({ state: state2 }) {\n const loading = state2.artifactPhase === \"loading\";\n const rows = state2.artifactRows;\n return /* @__PURE__ */ u3(\"section\", { id: \"artifactsView\", children: [\n /* @__PURE__ */ u3(\"div\", { class: \"lead\", children: [\n /* @__PURE__ */ u3(\"h1\", { id: \"artifactsHeading\", tabIndex: -1, children: \"Artifacts\" }),\n /* @__PURE__ */ u3(\"div\", { class: \"lead-copy\", children: /* @__PURE__ */ u3(\"p\", { children: pageDescription(\"artifacts\", productDescription) }) })\n ] }),\n /* @__PURE__ */ u3(\"div\", { class: \"collection\", children: [\n /* @__PURE__ */ u3(\n \"form\",\n {\n class: \"row\",\n onSubmit: (event) => {\n event.preventDefault();\n void loadArtifacts(true);\n },\n children: [\n /* @__PURE__ */ u3(\n \"input\",\n {\n id: \"artifactSearch\",\n type: \"search\",\n placeholder: \"Search titles…\",\n \"aria-label\": \"Search artifact titles\",\n value: state2.artifactQuery,\n onInput: (event) => setArtifactQuery(event.currentTarget.value)\n }\n ),\n /* @__PURE__ */ u3(\"button\", { id: \"searchArtifacts\", class: \"btn\", type: \"submit\", disabled: loading, children: \"Search\" }),\n /* @__PURE__ */ u3(\"label\", { class: \"check artifact-meta\", children: [\n /* @__PURE__ */ u3(\n \"input\",\n {\n id: \"showArchived\",\n type: \"checkbox\",\n checked: state2.artifactArchived,\n onChange: (event) => setArtifactArchived(event.currentTarget.checked)\n }\n ),\n \" \",\n \"Show archived\"\n ] })\n ]\n }\n ),\n state2.artifactPhase === \"error\" && rows.length === 0 ? /* @__PURE__ */ u3(\n StateBlock,\n {\n id: \"artifactError\",\n tone: \"error\",\n title: \"Artifacts couldn't be loaded\",\n action: { label: \"Retry\", onClick: () => void retryCollection() },\n children: state2.artifactNotice?.message\n }\n ) : rows.length === 0 ? /* @__PURE__ */ u3(StateBlock, { children: loading ? \"Loading artifacts…\" : state2.artifactQuery.trim() ? \"No artifact title matches this search.\" : \"No artifacts yet. Ask an agent to publish one.\" }) : /* @__PURE__ */ u3(\"div\", { id: \"artifactList\", class: \"activity-list\", \"aria-busy\": loading ? \"true\" : \"false\", children: rows.map((row) => /* @__PURE__ */ u3(ArtifactRow, { row }, row.id)) }),\n rows.length > 0 ? /* @__PURE__ */ u3(NoticeLine, { id: \"artifactNotice\", notice: state2.artifactNotice }) : null,\n state2.artifactCursor ? /* @__PURE__ */ u3(\n \"button\",\n {\n id: \"moreArtifacts\",\n class: \"btn activity-more\",\n type: \"button\",\n disabled: loading,\n onClick: () => void loadArtifacts(false),\n children: loading ? \"Loading…\" : \"Load more\"\n }\n ) : null\n ] })\n ] });\n }\n function ArtifactFrame({ view }) {\n const frame = A2(null);\n _2(() => {\n const element = frame.current;\n if (!element) return;\n const onMessage = (event) => {\n if (event.source !== element.contentWindow || event.origin !== \"null\") return;\n const data = event.data;\n if (!data || data.type !== \"ready\") return;\n window.removeEventListener(\"message\", onMessage);\n const policy = document.createElement(\"meta\");\n policy.httpEquiv = \"Content-Security-Policy\";\n policy.content = \"frame-src 'none'\";\n document.head.append(policy);\n markArtifactFrameConfined();\n element.contentWindow?.postMessage({ type: \"document\", html: view.document }, \"*\");\n };\n window.addEventListener(\"message\", onMessage);\n return () => window.removeEventListener(\"message\", onMessage);\n }, [view.document]);\n return /* @__PURE__ */ u3(\n \"iframe\",\n {\n ref: frame,\n id: \"artifactFrame\",\n class: \"artifact-frame\",\n title: view.title,\n sandbox: \"allow-scripts\",\n referrerpolicy: \"no-referrer\",\n src: \"/artifacts/_frame\"\n }\n );\n }\n function ArtifactPage({ state: state2 }) {\n const view = state2.artifactView;\n const failed = state2.artifactPhase === \"error\";\n return /* @__PURE__ */ u3(\"section\", { id: \"artifactView\", children: [\n /* @__PURE__ */ u3(\"div\", { class: \"artifact-head\", children: [\n /* @__PURE__ */ u3(\"h1\", { id: \"artifactHeading\", tabIndex: -1, children: view?.title ?? \"Artifact\" }),\n view ? /* @__PURE__ */ u3(\"div\", { class: \"artifact-meta\", id: \"artifactMeta\", children: [\n view.snapshot ? \"Snapshot of \" : \"\",\n \"version \",\n view.view.version,\n view.snapshot && view.view.version !== view.latestViewVersion ? ` (latest is ${view.latestViewVersion})` : \"\",\n \" \",\n \"· updated \",\n /* @__PURE__ */ u3(\"time\", { dateTime: view.view.at, children: formatDate(view.view.at) }),\n \" by\",\n \" \",\n view.view.by.label,\n \" ·\",\n \" \",\n /* @__PURE__ */ u3(\"a\", { href: \"/artifacts\", children: \"All artifacts\" }),\n view.snapshot ? /* @__PURE__ */ u3(S, { children: [\n \" \",\n \"· \",\n /* @__PURE__ */ u3(\"a\", { href: view.url, children: \"Current version\" })\n ] }) : /* @__PURE__ */ u3(S, { children: [\n \" \",\n \"·\",\n \" \",\n /* @__PURE__ */ u3(\n CopyButton,\n {\n id: \"copySnapshot\",\n class: \"navlink inline\",\n value: view.snapshotUrl,\n label: \"Copy snapshot link\"\n }\n )\n ] })\n ] }) : null,\n view?.archived ? /* @__PURE__ */ u3(\"div\", { id: \"archivedBanner\", class: \"artifact-banner\", role: \"status\", children: \"This artifact is archived. It keeps every version, and an agent can restore it.\" }) : null,\n !view?.snapshot && view?.freshness?.state === \"stale\" ? /* @__PURE__ */ u3(\"div\", { id: \"staleBanner\", class: \"artifact-banner\", role: \"status\", children: [\n \"Data may be out of date. The last refresh \",\n view.freshness.last?.status === \"failed\" ? \"failed\" : \"is overdue\",\n \"; the last good document is still shown.\"\n ] }) : null,\n !view && !failed ? /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: pageDescription(\"artifact\", productDescription) }) : null\n ] }),\n view ? /* @__PURE__ */ u3(ArtifactFrame, { view }, view.snapshotUrl) : failed ? /* @__PURE__ */ u3(\n StateBlock,\n {\n id: \"artifactError\",\n tone: \"error\",\n title: \"This artifact couldn't be opened\",\n action: { label: \"Retry\", onClick: () => void retryCollection() },\n children: state2.artifactNotice?.message\n }\n ) : /* @__PURE__ */ u3(StateBlock, { children: \"Loading artifact…\" })\n ] });\n }\n\n // src/operator-ui/app/credentials.tsx\n function CredentialForm({\n connector,\n credential,\n busy\n }) {\n const fields = credential.fields ?? [];\n const [values, setValues] = d2({});\n const single = fields.length === 0;\n const inputId = `credential-input-${connector}`;\n const submit = () => {\n if (single) {\n const value = (values.value ?? \"\").trim();\n if (!value) return refuseCredential(connector, \"Paste a credential before saving.\");\n return void saveCredential(connector, { value });\n }\n const entries = {};\n for (const field of fields) {\n const value = (values[field.name] ?? \"\").trim();\n if (!value) {\n return refuseCredential(\n connector,\n \"Complete every credential field before saving.\"\n );\n }\n entries[field.name] = value;\n }\n void saveCredential(connector, { values: entries });\n };\n return /* @__PURE__ */ u3(\"div\", { class: \"credential-form\", \"data-credential-form\": connector, children: [\n single ? /* @__PURE__ */ u3(S, { children: [\n /* @__PURE__ */ u3(\"label\", { class: \"visually-hidden\", for: inputId, children: credential.label }),\n /* @__PURE__ */ u3(\n \"input\",\n {\n id: inputId,\n type: \"password\",\n \"aria-label\": credential.label,\n placeholder: credential.placeholder || \"Paste credential\",\n autocomplete: \"new-password\",\n autocapitalize: \"none\",\n spellcheck: false,\n value: values.value ?? \"\",\n onInput: (event) => setValues({ value: event.currentTarget.value })\n }\n )\n ] }) : /* @__PURE__ */ u3(\"div\", { class: \"credential-fields\", children: fields.map((field, index) => {\n const id = `credential-input-${connector}-${index}`;\n return /* @__PURE__ */ u3(\"div\", { class: \"credential-field\", children: [\n /* @__PURE__ */ u3(\"label\", { for: id, children: field.label }),\n /* @__PURE__ */ u3(\n \"input\",\n {\n id,\n type: field.inputType || \"password\",\n placeholder: field.placeholder || field.label,\n autocomplete: (field.inputType ?? \"password\") === \"password\" ? \"new-password\" : \"off\",\n autocapitalize: \"none\",\n spellcheck: false,\n value: values[field.name] ?? \"\",\n onInput: (event) => setValues({\n ...values,\n [field.name]: event.currentTarget.value\n })\n }\n )\n ] }, field.name);\n }) }),\n /* @__PURE__ */ u3(\"div\", { class: \"actions\", children: [\n /* @__PURE__ */ u3(\"button\", { class: \"btn primary\", type: \"button\", disabled: busy, onClick: submit, children: busy ? \"Saving…\" : \"Save\" }),\n /* @__PURE__ */ u3(\n \"button\",\n {\n class: \"btn quiet\",\n type: \"button\",\n disabled: busy,\n onClick: () => editCredential(null),\n children: \"Cancel\"\n }\n )\n ] })\n ] });\n }\n function CredentialCard({\n connector,\n credential,\n editing,\n busy,\n confirming,\n notice\n }) {\n const name = connector.title || connector.id;\n const configured = Boolean(credential.configured);\n const removable = configured || Boolean(credential.removable);\n return /* @__PURE__ */ u3(\n \"section\",\n {\n class: \"subcard\",\n id: `credential-${connector.id}`,\n \"aria-labelledby\": `credential-title-${connector.id}`,\n children: [\n /* @__PURE__ */ u3(\"div\", { class: \"subcard-head\", children: [\n /* @__PURE__ */ u3(\"h3\", { id: `credential-title-${connector.id}`, children: credential.label }),\n /* @__PURE__ */ u3(\"span\", { class: \"meta\", children: credentialStateLabel(credential) })\n ] }),\n credential.description ? /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: credential.description }) : null,\n credential.fields?.length ? /* @__PURE__ */ u3(\"div\", { class: \"credential-field-summary\", children: credential.fields.map((field) => /* @__PURE__ */ u3(\"div\", { children: [\n /* @__PURE__ */ u3(\"span\", { children: field.label }),\n /* @__PURE__ */ u3(\"span\", { class: \"meta\", children: field.configured ? `configured · ••••${field.lastFour ?? \"\"}${field.updatedAt ? ` · updated ${formatDate(field.updatedAt)}` : \"\"}` : \"not configured\" })\n ] }, field.name)) }) : null,\n credential.error ? /* @__PURE__ */ u3(\"p\", { class: \"msg\", children: credentialProblemCopy(credential.problem) }) : null,\n credential.error && credential.problem ? /* @__PURE__ */ u3(\n FixPrompt,\n {\n kind: credential.problem,\n connectorId: connector.id,\n name: connector.title || connector.id\n }\n ) : null,\n credential.notice ? /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: credential.notice }) : null,\n /* @__PURE__ */ u3(\"div\", { class: \"actions\", children: [\n /* @__PURE__ */ u3(\n \"button\",\n {\n class: removable ? \"btn\" : \"btn primary\",\n type: \"button\",\n \"aria-expanded\": editing ? \"true\" : \"false\",\n disabled: busy,\n onClick: () => editCredential(editing ? null : connector.id),\n children: removable ? \"Replace\" : \"Add credential\"\n }\n ),\n configured && credential.testable ? /* @__PURE__ */ u3(\n \"button\",\n {\n class: \"btn\",\n type: \"button\",\n disabled: busy,\n onClick: () => void testCredential(connector.id),\n children: busy ? \"Working…\" : \"Test\"\n }\n ) : null,\n removable ? /* @__PURE__ */ u3(\n \"button\",\n {\n id: `remove-credential-${connector.id}`,\n class: \"btn danger\",\n type: \"button\",\n disabled: busy,\n onClick: () => askConfirm(connector.id, \"credential_remove\"),\n children: \"Remove\"\n }\n ) : null\n ] }),\n confirming ? /* @__PURE__ */ u3(\n ConfirmBar,\n {\n id: connector.id,\n ...confirmCopy(\"credential_remove\", name),\n onConfirm: () => void removeCredential(connector.id),\n onCancel: () => cancelConfirm(\n focusableId(`remove-credential-${connector.id}`, `conn-toggle-${connector.id}`)\n )\n }\n ) : null,\n editing ? /* @__PURE__ */ u3(\n CredentialForm,\n {\n connector: connector.id,\n credential,\n busy\n }\n ) : null,\n /* @__PURE__ */ u3(NoticeLine, { id: `credentialNotice-${connector.id}`, notice })\n ]\n }\n );\n }\n\n // src/operator-ui/model.ts\n function filterUiConnectors(connectors, query) {\n const q2 = query.trim().toLowerCase();\n const filtered = [];\n for (const connector of connectors) {\n const connectorText = [\n connector.id,\n connector.title,\n connector.description,\n connector.status\n ].join(\" \").toLowerCase();\n const connectorMatches = Boolean(q2 && connectorText.includes(q2));\n const tools = connector.tools.filter(\n (tool) => !q2 || connectorMatches || `${tool.name} ${tool.description ?? \"\"}`.toLowerCase().includes(q2)\n );\n if (q2 && tools.length === 0 && !connectorMatches) continue;\n filtered.push({ connector, tools });\n }\n return filtered;\n }\n\n // src/operator-ui/setup-commands.ts\n function clientServerName(serverName, pool) {\n const base = (serverName ?? \"\").toLowerCase().replace(/[^a-z0-9_-]+/g, \"-\").replace(/-{2,}/g, \"-\").replace(/^-+|-+$/g, \"\").slice(0, 48) || \"connecta\";\n return pool ? `${base}-${pool}` : base;\n }\n function poolEndpointUrl(mcpUrl2, pool) {\n return `${mcpUrl2.replace(/\\/+$/, \"\")}/${encodeURIComponent(pool)}`;\n }\n function shellWord(value) {\n return /^[A-Za-z0-9_./:@%+=,~-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\"'\"'`)}'`;\n }\n function clientSetupCommands(name, url) {\n return [\n {\n id: \"claude\",\n label: \"Claude Code\",\n text: `claude mcp add --transport http ${shellWord(name)} ${shellWord(url)}`\n },\n {\n id: \"codex\",\n label: \"Codex\",\n text: `codex mcp add ${shellWord(name)} --url ${shellWord(url)}`\n },\n {\n id: \"json\",\n label: \"JSON config\",\n text: JSON.stringify(\n { mcpServers: { [name]: { type: \"http\", url } } },\n null,\n 2\n )\n }\n ];\n }\n\n // src/operator-ui/app/connections.tsx\n var DRIFT_HEADING = {\n clean: \"Catalog drift · none\",\n warning: \"Catalog drift · review\",\n unavailable: \"Catalog drift · not observed\"\n };\n function DriftPanel({ connector }) {\n const drift = connector.catalogDrift;\n const state2 = driftState(drift);\n return /* @__PURE__ */ u3(S, { children: [\n /* @__PURE__ */ u3(\n \"div\",\n {\n id: `drift-${connector.id}`,\n class: `connector-drift ${state2}`,\n \"data-drift\": state2,\n children: [\n /* @__PURE__ */ u3(\"p\", { class: \"cap\", children: DRIFT_HEADING[state2] }),\n /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: driftSummary(drift) }),\n state2 === \"unavailable\" ? null : /* @__PURE__ */ u3(\"ul\", { class: \"drift-counts\", children: driftCounts(drift).map(({ key, label, count }) => /* @__PURE__ */ u3(\"li\", { class: count > 0 ? \"drift-count flagged\" : \"drift-count\", children: [\n /* @__PURE__ */ u3(\"span\", { class: \"drift-count-value\", children: count }),\n /* @__PURE__ */ u3(\"span\", { class: \"drift-count-label\", children: label })\n ] }, key)) })\n ]\n }\n ),\n state2 === \"warning\" ? /* @__PURE__ */ u3(\n FixPrompt,\n {\n kind: \"catalog_drift\",\n connectorId: connector.id,\n name: connector.title || connector.id\n }\n ) : null\n ] });\n }\n function SafetyBadge({ safety }) {\n const badge = safety ? TOOL_SAFETY_BADGE[safety] : void 0;\n if (!badge) return null;\n return /* @__PURE__ */ u3(\"span\", { class: \"tool-safety\", title: badge.title, \"data-safety\": safety, children: /* @__PURE__ */ u3(Badge, { tone: badge.tone, children: badge.label }) });\n }\n function Endpoint({\n url,\n name,\n label,\n primary\n }) {\n return /* @__PURE__ */ u3(\"div\", { class: \"endpoint-block\", \"data-endpoint\": name, children: [\n /* @__PURE__ */ u3(\"div\", { class: \"endpoint\", children: [\n label ? /* @__PURE__ */ u3(\"span\", { class: \"endpoint-label cap\", children: label }) : null,\n /* @__PURE__ */ u3(\"code\", { ...primary ? { id: \"mcpUrl\" } : {}, class: \"mono\", children: url }),\n /* @__PURE__ */ u3(\n CopyButton,\n {\n value: url,\n label: \"Copy URL\",\n ...label ? { ariaLabel: `Copy URL for ${label}` } : {}\n }\n )\n ] }),\n /* @__PURE__ */ u3(\"details\", { class: \"setup\", children: [\n /* @__PURE__ */ u3(\"summary\", { class: \"disclosure\", children: [\n \"Client setup\",\n label ? ` · ${label}` : \"\"\n ] }),\n /* @__PURE__ */ u3(\"div\", { class: \"setup-list\", children: [\n clientSetupCommands(name, url).map((command) => /* @__PURE__ */ u3(\"div\", { class: \"setup-item\", \"data-setup\": command.id, children: [\n /* @__PURE__ */ u3(\"div\", { class: \"setup-head\", children: [\n /* @__PURE__ */ u3(\"span\", { class: \"cap\", children: command.label }),\n /* @__PURE__ */ u3(\n CopyButton,\n {\n value: command.text,\n label: \"Copy\",\n class: \"btn quiet\",\n ariaLabel: `Copy ${command.label} setup${label ? ` for ${label}` : \"\"}`\n }\n )\n ] }),\n /* @__PURE__ */ u3(\"pre\", { class: \"setup-code\", children: command.text })\n ] }, command.id)),\n /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: \"No token is included. Clients sign in through this deployment's inbound auth.\" })\n ] })\n ] })\n ] });\n }\n function AuthActions({\n connector,\n name,\n manage,\n state: state2\n }) {\n const id = connector.id;\n const authorization = safeHttpHref(connector.authorizationUrl);\n const busy = state2.oauthBusy === id;\n if (connector.status === \"loading\") return null;\n if (!connector.oauth || !manage) {\n return authorization && connector.status !== \"ok\" ? /* @__PURE__ */ u3(\"a\", { class: \"btn primary\", href: authorization, target: \"_blank\", rel: \"noopener noreferrer\", children: \"Authorize connector\" }) : null;\n }\n if (state2.oauthBlocked === id && authorization) {\n return /* @__PURE__ */ u3(\n \"a\",\n {\n id: `authorize-${id}`,\n class: \"btn primary\",\n href: authorization,\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n children: \"Open authorization page\"\n }\n );\n }\n if (connector.status !== \"ok\") {\n const needsAuth = connector.status === \"auth_required\";\n return /* @__PURE__ */ u3(\n \"button\",\n {\n type: \"button\",\n id: `connect-${id}`,\n class: needsAuth ? \"btn primary\" : \"btn\",\n \"aria-label\": `${needsAuth ? \"Connect\" : \"Reconnect\"} ${name}`,\n disabled: busy,\n onClick: () => void startOAuth(id, \"continue\"),\n children: busy ? \"Opening…\" : needsAuth ? \"Connect account\" : \"Reconnect\"\n }\n );\n }\n const switching = connector.authScope === \"personal\";\n return /* @__PURE__ */ u3(S, { children: [\n /* @__PURE__ */ u3(\n \"button\",\n {\n type: \"button\",\n id: `reconnect-${id}`,\n class: \"btn\",\n \"aria-label\": `${switching ? \"Switch account for\" : \"Reconnect\"} ${name}`,\n disabled: busy,\n onClick: () => askConfirm(id, \"oauth_restart\"),\n children: busy ? \"Working…\" : switching ? \"Switch account\" : \"Reconnect\"\n }\n ),\n /* @__PURE__ */ u3(\n \"button\",\n {\n type: \"button\",\n id: `disconnect-${id}`,\n class: \"btn danger\",\n \"aria-label\": `Disconnect ${name}`,\n disabled: busy,\n onClick: () => askConfirm(id, \"oauth_disconnect\"),\n children: \"Disconnect\"\n }\n )\n ] });\n }\n function ConnectorRow({\n connector,\n tools,\n forceOpen,\n state: state2\n }) {\n const [open, setOpen] = d2(false);\n const shown = open || forceOpen;\n const id = connector.id;\n const name = connector.title || id;\n const drift = driftState(connector.catalogDrift);\n const manage = Boolean(\n connector.permissions?.manageSharedAuth || connector.permissions?.connectPersonal\n );\n const local = state2.connectorFailures[id];\n const problem = connector.status === \"loading\" || local ? null : problemCopy(connector.problem);\n const confirming = state2.confirming?.connectorId === id ? state2.confirming : null;\n const oauthConfirm = confirming && confirming.action !== \"credential_remove\" ? confirming : null;\n const statusLabel = local ? \"Couldn't load\" : connectorStatusLabel(connector.status, connector.problem);\n const fixKind = problem && connector.problem && problemTone(connector.problem) === \"danger\" && connector.problem !== connector.credential?.problem ? connector.problem : null;\n const statusTone = local ? \"warn\" : connectorStatusTone(connector.status);\n return /* @__PURE__ */ u3(\"div\", { class: shown ? \"conn open\" : \"conn\", \"data-connector\": id, children: [\n /* @__PURE__ */ u3(\"div\", { class: \"conn-head\", children: [\n /* @__PURE__ */ u3(\"span\", { class: \"conn-main\", children: [\n /* @__PURE__ */ u3(\"span\", { class: `dot ${local ? \"warn\" : connector.status}`, \"aria-hidden\": \"true\" }),\n /* @__PURE__ */ u3(\"h2\", { class: \"conn-name\", children: /* @__PURE__ */ u3(\n \"button\",\n {\n type: \"button\",\n id: `conn-toggle-${id}`,\n class: \"conn-toggle\",\n \"aria-expanded\": shown ? \"true\" : \"false\",\n \"aria-controls\": `conn-body-${id}`,\n \"aria-describedby\": `conn-state-${id}`,\n onClick: () => setOpen(!shown),\n children: name\n }\n ) }),\n connector.title ? /* @__PURE__ */ u3(\"span\", { class: \"conn-id mono\", children: id }) : null\n ] }),\n /* @__PURE__ */ u3(\"span\", { class: \"conn-badges\", id: `conn-state-${id}`, children: [\n drift === \"warning\" ? /* @__PURE__ */ u3(Badge, { tone: \"warn\", children: \"drift\" }) : null,\n /* @__PURE__ */ u3(Badge, { children: authScopeLabel(connector.authScope) }),\n /* @__PURE__ */ u3(Badge, { children: connector.status === \"loading\" ? \"tools not loaded\" : toolCountLabel(connector.toolCount) }),\n /* @__PURE__ */ u3(Badge, { tone: statusTone, children: statusLabel }),\n /* @__PURE__ */ u3(\"span\", { class: \"conn-caret\", \"aria-hidden\": \"true\" })\n ] })\n ] }),\n /* @__PURE__ */ u3(\"div\", { class: \"conn-body\", id: `conn-body-${id}`, hidden: !shown, children: [\n connector.description ? /* @__PURE__ */ u3(\"p\", { class: \"conn-note\", children: connector.description }) : null,\n problem && connector.problem ? /* @__PURE__ */ u3(\n \"p\",\n {\n class: problemTone(connector.problem) === \"warn\" ? \"msg warn\" : \"msg\",\n \"data-problem\": connector.problem,\n children: problem\n }\n ) : null,\n local ? /* @__PURE__ */ u3(\"p\", { class: \"msg warn\", \"data-load-failure\": local, children: connectorLoadFailureCopy(local, productName) }) : null,\n connector.authorizationUrl && !safeHttpHref(connector.authorizationUrl) ? /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: [\n \"Authorization URL: \",\n connector.authorizationUrl\n ] }) : null,\n manage ? null : /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: permissionLabel(connector) }),\n /* @__PURE__ */ u3(\"div\", { class: \"actions\", children: [\n local ? null : /* @__PURE__ */ u3(AuthActions, { connector, name, manage, state: state2 }),\n fixKind ? /* @__PURE__ */ u3(FixPromptButton, { kind: fixKind, connectorId: id, name }) : null,\n /* @__PURE__ */ u3(\n \"button\",\n {\n class: local ? \"btn primary\" : \"btn quiet\",\n type: \"button\",\n \"aria-label\": `Refresh ${name}`,\n disabled: connector.status === \"loading\",\n onClick: () => void refreshConnector(id),\n children: \"Refresh\"\n }\n )\n ] }),\n fixKind ? /* @__PURE__ */ u3(FixPromptPreview, { kind: fixKind, connectorId: id, standalone: true }) : null,\n oauthConfirm ? /* @__PURE__ */ u3(\n ConfirmBar,\n {\n id,\n ...confirmCopy(oauthConfirm.action, name),\n onConfirm: () => {\n if (oauthConfirm.action === \"oauth_restart\") void startOAuth(id, \"restart\");\n else void disconnectOAuth(id);\n },\n onCancel: () => cancelConfirm(\n focusableId(\n oauthConfirm.action === \"oauth_restart\" ? `reconnect-${id}` : `disconnect-${id}`,\n `conn-toggle-${id}`\n )\n )\n }\n ) : null,\n /* @__PURE__ */ u3(\n NoticeLine,\n {\n id: `oauthNotice-${id}`,\n notice: state2.oauthNoticeFor === id ? state2.oauthNotice : null\n }\n ),\n connector.credential ? /* @__PURE__ */ u3(\n CredentialCard,\n {\n connector,\n credential: connector.credential,\n editing: state2.credentialEditing === id,\n busy: state2.credentialBusy === id,\n confirming: confirming?.action === \"credential_remove\",\n notice: state2.credentialNoticeFor === id ? state2.credentialNotice : null\n }\n ) : null,\n tools.length ? /* @__PURE__ */ u3(\"details\", { open: forceOpen, children: [\n /* @__PURE__ */ u3(\"summary\", { class: \"disclosure\", children: [\n \"Tools (\",\n tools.length,\n \")\"\n ] }),\n /* @__PURE__ */ u3(\"div\", { class: \"tool-list\", children: [\n tools.some((tool) => tool.safety) ? /* @__PURE__ */ u3(\"p\", { class: \"meta tool-legend\", children: \"Read-only tools run inside execute_code programs. Everything else asks the host first: a program pauses for resume_execution, and a direct call goes through call_destructive_tool — unless this deployment's config exempts the tool, in which case programs call it unasked.\" }) : null,\n tools.map((tool) => /* @__PURE__ */ u3(\"div\", { class: \"tool\", children: [\n /* @__PURE__ */ u3(\"div\", { class: \"tool-head\", children: [\n /* @__PURE__ */ u3(\"code\", { children: tool.address }),\n /* @__PURE__ */ u3(SafetyBadge, { safety: tool.safety })\n ] }),\n tool.description ? /* @__PURE__ */ u3(\"span\", { class: \"td\", children: tool.description }) : null\n ] }, tool.address))\n ] })\n ] }) : null,\n /* @__PURE__ */ u3(\"details\", { children: [\n /* @__PURE__ */ u3(\"summary\", { class: \"disclosure\", children: \"Diagnostics\" }),\n /* @__PURE__ */ u3(\"div\", { class: \"subcard\", children: [\n /* @__PURE__ */ u3(DriftPanel, { connector }),\n connector.catalogAccess ? /* @__PURE__ */ u3(\"p\", { class: \"meta\", children: [\n \"Agents last read its catalog\",\n \" \",\n connector.catalogAccess.state === \"stale\" ? \"from a stale cache\" : \"fresh\",\n \" · \",\n formatDate(connector.catalogAccess.observedAt)\n ] }) : null\n ] })\n ] })\n ] })\n ] });\n }\n function Endpoints({\n pools,\n serverName\n }) {\n return /* @__PURE__ */ u3(\"div\", { class: \"endpoints\", children: [\n /* @__PURE__ */ u3(\n Endpoint,\n {\n url: mcpUrl,\n name: clientServerName(serverName),\n primary: true,\n ...pools.length ? { label: \"All tools\" } : {}\n }\n ),\n pools.map((pool) => /* @__PURE__ */ u3(\n Endpoint,\n {\n url: poolEndpointUrl(mcpUrl, pool),\n name: clientServerName(serverName, pool),\n label: `Pool · ${pool}`\n },\n pool\n ))\n ] });\n }\n function SummaryLine({ connectors }) {\n const parts = connectorSummaryParts(summarizeConnectors(connectors));\n return /* @__PURE__ */ u3(\"p\", { class: \"summary\", id: \"connectorSummary\", children: parts.map((part, index) => /* @__PURE__ */ u3(\"span\", { children: [\n index > 0 ? /* @__PURE__ */ u3(\"span\", { class: \"sep\", children: \" · \" }) : null,\n /* @__PURE__ */ u3(\"span\", { class: part.tone === \"neutral\" ? \"\" : part.tone, children: part.text })\n ] }, part.text)) });\n }\n function ConnectionsPage({ state: state2 }) {\n const data = state2.data;\n const query = state2.connectorFilter.trim();\n const filtered = data ? filterUiConnectors(data.connectors, query) : [];\n return /* @__PURE__ */ u3(\"section\", { id: \"connectionsView\", children: [\n /* @__PURE__ */ u3(\"div\", { class: \"lead\", children: [\n /* @__PURE__ */ u3(\"h1\", { id: \"connectionsHeading\", tabIndex: -1, children: \"Connections\" }),\n /* @__PURE__ */ u3(\"div\", { class: \"lead-copy\", children: [\n /* @__PURE__ */ u3(\"p\", { children: \"Point an MCP client at this endpoint to reach the tools below.\" }),\n /* @__PURE__ */ u3(Endpoints, { pools: data?.pools ?? [], serverName: data?.serverInfo?.name }),\n /* @__PURE__ */ u3(\"p\", { class: \"cap\", id: \"serverInfo\", children: data ? `${data.serverInfo?.name || productName} v${data.connectaVersion || \"?\"}` : productOperatorLabel }),\n data ? /* @__PURE__ */ u3(SummaryLine, { connectors: data.connectors }) : null\n ] })\n ] }),\n /* @__PURE__ */ u3(\"section\", { class: \"section\", \"aria-labelledby\": \"connectorLedgerHeading\", children: [\n /* @__PURE__ */ u3(\"div\", { class: \"section-head\", children: [\n /* @__PURE__ */ u3(\"h2\", { id: \"connectorLedgerHeading\", tabIndex: -1, children: \"Connectors\" }),\n /* @__PURE__ */ u3(\n \"input\",\n {\n id: \"filter\",\n type: \"search\",\n class: \"filter\",\n placeholder: \"Filter connectors or tools…\",\n \"aria-label\": \"Filter connectors or tools\",\n value: state2.connectorFilter,\n disabled: !data,\n onInput: (event) => setConnectorFilter(event.currentTarget.value)\n }\n )\n ] }),\n /* @__PURE__ */ u3(\n \"div\",\n {\n id: \"list\",\n class: !data || filtered.length === 0 ? \"\" : \"rows\",\n \"aria-busy\": state2.refreshing || !data && !state2.loadFailure ? \"true\" : \"false\",\n children: !data ? state2.loadFailure ? /* @__PURE__ */ u3(LoadFailure, { state: state2 }) : /* @__PURE__ */ u3(Empty, { children: \"Loading connectors…\" }) : filtered.length === 0 ? /* @__PURE__ */ u3(Empty, { children: query ? \"No connectors or tools match this filter.\" : \"No connectors are declared in this deployment.\" }) : filtered.map(({ connector, tools }) => /* @__PURE__ */ u3(\n ConnectorRow,\n {\n connector,\n tools,\n forceOpen: Boolean(query),\n state: state2\n },\n connector.id\n ))\n }\n )\n ] })\n ] });\n }\n\n // src/operator-ui/app/main.tsx\n function useOperatorState() {\n const [, bump] = y2((count) => count + 1, 0);\n const snapshot = getState();\n _2(() => {\n const unsubscribe = subscribe(() => bump(void 0));\n if (getState() !== snapshot) bump(void 0);\n return unsubscribe;\n }, []);\n return snapshot;\n }\n function visiblePages(state2) {\n const hint = state2.data ? null : navHint();\n return OPERATOR_PAGES.filter((page) => {\n if (page === \"tokens\") return state2.data?.accessTokenManagement === \"available\";\n if (page === \"activity\") return hint ? hint.activity : Boolean(state2.data?.activityEnabled);\n if (page === \"artifacts\") {\n return isArtifactPage(state2.page) || (hint ? hint.artifacts : Boolean(state2.data?.artifactsEnabled));\n }\n return true;\n });\n }\n function OperatorNav() {\n const state2 = useOperatorState();\n if (state2.session !== \"ready\") return null;\n const onArtifactPage = isArtifactPage(state2.page);\n return /* @__PURE__ */ u3(\"div\", { class: \"mast-actions\", children: [\n /* @__PURE__ */ u3(\"nav\", { class: \"page-nav\", \"aria-label\": \"Operator pages\", children: visiblePages(state2).map(\n (page) => (\n // Crossing between artifact pages and the rest is a full navigation:\n // with a dedicated artifact origin, the two live on different hosts.\n page === \"artifacts\" || onArtifactPage ? /* @__PURE__ */ u3(\n \"a\",\n {\n class: \"navlink\",\n href: page === \"artifacts\" ? PAGE_META.artifacts.path : new URL(PAGE_META[page].path, new URL(homeUrl, window.location.href)).href,\n ...state2.page === page ? { \"aria-current\": \"page\" } : {},\n children: PAGE_META[page].label\n },\n page\n ) : /* @__PURE__ */ u3(\n PageLink,\n {\n page,\n class: \"navlink\",\n current: state2.page === page,\n children: PAGE_META[page].label\n },\n page\n )\n )\n ) }),\n /* @__PURE__ */ u3(\"div\", { class: \"session-actions\", \"aria-label\": \"Session actions\", children: auth.kind === \"clerk\" || auth.kind === \"cloudflare-access\" ? /* @__PURE__ */ u3(\"button\", { class: \"navlink\", type: \"button\", onClick: signOut, children: \"Sign out\" }) : /* @__PURE__ */ u3(\"button\", { class: \"navlink\", type: \"button\", onClick: forgetBearer, children: \"Change token\" }) })\n ] });\n }\n function Gate({ state: state2 }) {\n const [token, setToken] = d2(\"\");\n const signedIn = auth.kind === \"clerk\" && Boolean(window.Clerk?.user);\n const loading = state2.session === \"loading\";\n return /* @__PURE__ */ u3(\"section\", { id: \"gate\", class: \"gate lead\", \"aria-busy\": loading ? \"true\" : \"false\", children: [\n /* @__PURE__ */ u3(\"h1\", { id: \"gateHeading\", tabIndex: -1, children: PAGE_META[state2.page].label }),\n /* @__PURE__ */ u3(\"div\", { class: \"lead-copy\", children: [\n /* @__PURE__ */ u3(\"p\", { children: pageDescription(state2.page, productDescription) }),\n loading ? /* @__PURE__ */ u3(StateBlock, { id: \"gateCopy\", children: checkingCopy(state2.page) }) : /* @__PURE__ */ u3(\"p\", { id: \"gateCopy\", class: \"meta\", children: gateCopy(auth.kind, signedIn) }),\n loading ? null : auth.kind === \"clerk\" ? /* @__PURE__ */ u3(\"div\", { id: \"clerkGate\", class: \"actions\", children: signedIn ? /* @__PURE__ */ u3(\"button\", { class: \"btn\", type: \"button\", onClick: signOut, children: \"Sign out\" }) : /* @__PURE__ */ u3(\"button\", { id: \"signin\", class: \"btn primary\", type: \"button\", onClick: signIn, children: \"Team sign in\" }) }) : auth.kind === \"cloudflare-access\" ? /* @__PURE__ */ u3(\"div\", { class: \"actions\", children: /* @__PURE__ */ u3(\"button\", { class: \"btn\", type: \"button\", onClick: signOut, children: \"Sign out of Cloudflare Access\" }) }) : /* @__PURE__ */ u3(\n \"form\",\n {\n id: \"tokenGate\",\n class: \"row gate-form\",\n onSubmit: (event) => {\n event.preventDefault();\n const value = token.trim();\n if (!value) return;\n setToken(\"\");\n signInWithBearer(value);\n },\n children: [\n /* @__PURE__ */ u3(\n \"input\",\n {\n id: \"token\",\n type: \"password\",\n placeholder: \"Bearer token\",\n autocomplete: \"off\",\n \"aria-label\": \"Bearer token\",\n value: token,\n onInput: (event) => setToken(event.currentTarget.value)\n }\n ),\n /* @__PURE__ */ u3(\"button\", { id: \"save\", class: \"btn primary\", type: \"submit\", children: \"Open operator pages\" })\n ]\n }\n ),\n /* @__PURE__ */ u3(NoticeLine, { id: \"err\", notice: state2.gate, className: \"\" })\n ] })\n ] });\n }\n function CurrentPage({ state: state2 }) {\n if (state2.page === \"tokens\") return /* @__PURE__ */ u3(TokensPage, { state: state2 });\n if (state2.page === \"activity\") return /* @__PURE__ */ u3(ActivityPage, { state: state2 });\n if (state2.page === \"artifacts\") return /* @__PURE__ */ u3(ArtifactsPage, { state: state2 });\n if (state2.page === \"artifact\") return /* @__PURE__ */ u3(ArtifactPage, { state: state2 });\n return /* @__PURE__ */ u3(ConnectionsPage, { state: state2 });\n }\n function OperatorApp() {\n const state2 = useOperatorState();\n const ready = state2.session === \"ready\";\n h2(() => {\n const label = state2.page === \"artifact\" && state2.artifactView ? state2.artifactView.title : PAGE_META[state2.page].label;\n document.title = `${label} — ${titleSuffix}`;\n }, [state2.page, state2.artifactView]);\n h2(() => {\n if (!ready) return;\n if (state2.page === \"tokens\" && state2.data?.accessTokenManagement === \"available\" && state2.tokenPhase === \"idle\") {\n void loadAccessTokens();\n }\n if (state2.page === \"activity\" && state2.data?.activityEnabled && state2.activityPhase === \"idle\") {\n void loadActivity(true);\n }\n });\n h2(() => {\n if (!state2.pendingFocus && !state2.focusIfLost) return;\n if (state2.pendingFocus) {\n document.getElementById(state2.pendingFocus)?.focus();\n } else if (state2.focusIfLost) {\n const active = document.activeElement;\n const lost = !active || active === document.body || !active.isConnected || active.disabled === true;\n if (lost) document.getElementById(state2.focusIfLost)?.focus();\n }\n focusHandled();\n }, [state2.pendingFocus, state2.focusIfLost]);\n return ready ? /* @__PURE__ */ u3(\"div\", { id: \"app\", children: /* @__PURE__ */ u3(CurrentPage, { state: state2 }) }) : /* @__PURE__ */ u3(Gate, { state: state2 });\n }\n function mount(id, view) {\n const host = document.getElementById(id);\n if (!host) return;\n host.textContent = \"\";\n R(view, host);\n }\n mount(\"operatorNav\", /* @__PURE__ */ u3(OperatorNav, {}));\n mount(\"operatorContent\", /* @__PURE__ */ u3(OperatorApp, {}));\n void boot();\n})();\n"; diff --git a/src/providers/notion.ts b/src/providers/notion.ts index 378f42a5..8a896b44 100644 --- a/src/providers/notion.ts +++ b/src/providers/notion.ts @@ -263,7 +263,14 @@ async function notionRequest( response.headers, ); } - return payload ?? {}; + if (payload === null || typeof payload !== "object" || Array.isArray(payload)) { + throw new ConnectorCallError( + "connector_call_failed", + "Notion returned an unreadable or non-object JSON response for a successful status. The request was sent, but its result could not be read; do not repeat a write to recover its result.", + { retryable: false }, + ); + } + return payload; }); } diff --git a/src/registry.ts b/src/registry.ts index 1945c1b0..d09e044b 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -1267,7 +1267,15 @@ export class Registry implements RegistryView { const turn = Deferred.makeUnsafe(); this.catalogMutations.set(id, turn); try { - if (previous) await runEdge(Deferred.await(previous)); + if (previous) { + // A cross-request Deferred alone looks hung to workerd. Keep a timer + // owned by this request until the preceding mutation hands over its + // turn; the race interrupts the timer as soon as the wait settles. + await runEdge(Effect.raceFirst( + Deferred.await(previous), + Effect.forever(Effect.sleep(Duration.seconds(1))), + )); + } await runOnPartition(operation, this.opts); } finally { if (this.catalogMutations.get(id) === turn) { diff --git a/src/resumable.ts b/src/resumable.ts index 54d2f86f..a63176c1 100644 --- a/src/resumable.ts +++ b/src/resumable.ts @@ -353,7 +353,7 @@ export class RunState { settings: ResumableSettings; }): RunState { const { header } = options.claim; - return new RunState( + const run = new RunState( options.journal, options.program, { clockMs: header.clock, seed: header.seed }, @@ -362,6 +362,8 @@ export class RunState { options.claim, new ReplayState(options.entries, header.pending?.key), ); + run.writesSpent = header.writes.length; + return run; } // --- numbering and the gate sequencer --------------------------------- @@ -478,11 +480,9 @@ export class RunState { /** Answer a call from the journal, or say it goes live. */ lookup(key: string): Lookup { if (!this.replay) return { kind: "live" }; - const found = this.replay.lookup(key); - // Replayed writes spend the write budget again, as replayed calls spend - // the host-call budget: per-play totals equal per-run totals. - if (found.kind === "hit" && found.entry.write) this.writesSpent++; - return found; + // Already-sent writes spend the run budget whether this play repeats them + // or diverges before doing so. Only new dispatches spend it again. + return this.replay.lookup(key); } /** Journal a completed live call that was not a write. */ @@ -763,13 +763,53 @@ export class RunState { : undefined; if (this.claim && slot !== undefined) { const claim = this.claim; + const entryBytes = utf8Bytes(RunJournal.entryText(entry)); + let tooLarge = false; + // Reserve capacity through the same serialized CAS as the write-ahead + // marks. Concurrent answers cannot both spend the last free bytes. + const capacity = yield* this.mutateHeader((header) => { + const pendingBytes = this.unpersisted.reduce( + (sum, pending) => sum + utf8Bytes(RunJournal.entryText(pending)), 0, + ); + tooLarge = header.bytes + pendingBytes + entryBytes > MAX_JOURNAL_BYTES; + if (tooLarge) { + this.stopWith({ + kind: "failed", + failure: unknownFailure ?? failure( + "journal_too_large", + `This run recorded more than ${MAX_JOURNAL_BYTES} bytes of source and host calls, too much to keep in its journal. Writes it already sent are counted in writes and are not undone; check them before running the task again.`, + ), + }); + } + return { + ...header, + bytes: tooLarge ? header.bytes : header.bytes + entryBytes, + writes: tooLarge + ? header.writes.map((write) => + write.entry === slot ? { ...write, state } : write) + : header.writes, + }; + }); + if (capacity !== "ok") { + this.noteLocally(slot, state); + const lost = capacity === "lost" + ? failure( + "execution_claim_lost", + `The write to ${target.address} was sent (${describeState(state)}), but another resume_execution took the run over before its result could be journaled.`, + ) + : interrupted( + `The write to ${target.address} was sent (${describeState(state)}), but paused-run storage failed before its result could be journaled.`, + ); + this.stopWith({ kind: "failed", failure: lost }); + return guestFailure(lost.code, lost.message); + } + if (tooLarge) return this.halted(); const written = yield* Effect.tryPromise(() => this.journal.writeEntry(slot, entry, claim.header.expiresAt), ).pipe(Effect.as(true), Effect.catch(() => Effect.succeed(false))); const recorded: WriteState = written ? state : "unknown"; const settled = yield* this.mutateHeader((header) => ({ ...header, - bytes: header.bytes + utf8Bytes(RunJournal.entryText(entry)), writes: header.writes.map((write) => write.entry === slot ? { ...write, state: recorded } : write, ), @@ -904,7 +944,13 @@ export class RunState { finishStopped(): Effect.Effect { const stop = this.stop; if (!stop) return Effect.die(new Error("finishStopped without a stop")); - if (stop.kind === "failed") return this.finishFailed(stop.failure); + if (stop.kind === "failed") { + return this.finishFailed( + stop.failure.code === "journal_too_large" && this.writeCounts().unknown > 0 + ? unknownOutcome() + : stop.failure, + ); + } if (this.writeCounts().unknown > 0) return this.finishFailed(unknownOutcome()); return this.persistPause(stop.pending); } diff --git a/src/storage/file.ts b/src/storage/file.ts index adb325d4..7c4e6234 100644 --- a/src/storage/file.ts +++ b/src/storage/file.ts @@ -273,12 +273,16 @@ export function fileStorage( // Non-POSIX filesystem or a race on the file — leave the mode as-is. } }; - let data: Record = {}; + let data: Record = Object.create(null) as Record; try { if (existsSync(path)) { tighten(); try { - data = JSON.parse(readFileSync(path, "utf8")) as Record; + const loaded = JSON.parse(readFileSync(path, "utf8")) as unknown; + if (loaded === null || typeof loaded !== "object" || Array.isArray(loaded)) { + throw new TypeError("Expected a state object"); + } + data = Object.assign(Object.create(null), loaded) as Record; } catch (error) { // Never let a damaged state file be silently replaced by an empty one: // the next set() would persist {} over irreplaceable downstream OAuth @@ -302,7 +306,7 @@ export function fileStorage( `OAuth connectors must be re-authorized and stored credentials ` + `re-entered.`, ); - data = {}; + data = Object.create(null) as Record; } } } catch (error) { @@ -316,7 +320,7 @@ export function fileStorage( // Reads only prune memory; they do not rewrite the state file. const now = Date.now(); for (const [key, entry] of Object.entries(data)) { - if (entry.exp && now > entry.exp) delete data[key]; + if (entry.exp !== undefined && now >= entry.exp) delete data[key]; } const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`; // 0o600 on the tmp file; the atomic rename below preserves it, so the live @@ -338,7 +342,7 @@ export function fileStorage( const fresh = (key: string): Entry | null => { const e = data[key]; if (!e) return null; - if (e.exp && Date.now() > e.exp) { + if (e.exp !== undefined && Date.now() >= e.exp) { delete data[key]; return null; } @@ -355,19 +359,32 @@ export function fileStorage( async set(key, value, opts) { assertOpen(); lock.assertHeld(); + const previous = fresh(key); data[key] = { value, ...(opts?.ttlSeconds ? { exp: Date.now() + opts.ttlSeconds * 1000 } : {}), }; - persist(); + try { + persist(); + } catch (error) { + if (previous) data[key] = previous; + else delete data[key]; + throw error; + } }, async delete(key) { assertOpen(); lock.assertHeld(); + const previous = fresh(key); delete data[key]; - persist(); + try { + persist(); + } catch (error) { + if (previous) data[key] = previous; + throw error; + } }, async list(prefix) { assertOpen(); diff --git a/templates/node/package.json b/templates/node/package.json index 45395bff..6a8c7721 100644 --- a/templates/node/package.json +++ b/templates/node/package.json @@ -21,6 +21,6 @@ "devDependencies": { "@types/node": "^22.0.0", "tsx": "^4.23.1", - "typescript": "^5.6.0" + "typescript": "^5.9.3" } } diff --git a/test/access-tokens.test.ts b/test/access-tokens.test.ts index f292e6cb..68c8c506 100644 --- a/test/access-tokens.test.ts +++ b/test/access-tokens.test.ts @@ -185,6 +185,42 @@ describe("token lifecycle races", () => { expect((await manager.auth.authorize(request(fixture.bound.token), BASE)).ok).toBe(false); }); + it.each([false, true])("releases capacity after a failed metadata write, committed=%s", async committed => { + const base = memoryStorage(); + let failing = true; + const storage: KVStorage = { ...base, set: async (key, value) => { + if (failing && key.startsWith("access-token:v1:record:")) { + failing = false; + if (committed) await base.set(key, value); + throw new Error("metadata write failed"); + } + await base.set(key, value); + } }; + const manager = new AccessTokenManager(storage, { maxActive: 1 }); + await expect(manager.create("Interrupted", owner)).rejects.toThrow("metadata write failed"); + expect((await manager.list()).filter(token => !token.revokedAt)).toEqual([]); + expect(await base.list!("access-token:v1:lookup:")).toEqual([]); + const created = await new AccessTokenManager(base, { maxActive: 1 }).create("Replacement", owner); + expect((await manager.auth.authorize(request(created.token), BASE)).ok).toBe(true); + }); + + it("releases a reservation whose compareAndSet committed before rejecting", async () => { + const base = memoryStorage(); + let failing = true; + const storage: KVStorage = { ...base, compareAndSet: async (key, expected, value) => { + const committed = await base.compareAndSet!(key, expected, value); + if (failing && key === "access-token:v1:active" && committed) { + failing = false; + throw new Error("reservation answer lost"); + } + return committed; + } }; + const manager = new AccessTokenManager(storage, { maxActive: 1 }); + await expect(manager.create("Interrupted", owner)).rejects.toThrow("reservation answer lost"); + expect(await manager.list()).toEqual([]); + await expect(manager.create("Replacement", owner)).resolves.toHaveProperty("token"); + }); + it("keeps capacity and revocable metadata after an uncertain lookup write", async () => { const base = memoryStorage(); const storage: KVStorage = { ...base, set: async (key, value) => { diff --git a/test/api-connector.test.ts b/test/api-connector.test.ts index 13836ba7..8e333369 100644 --- a/test/api-connector.test.ts +++ b/test/api-connector.test.ts @@ -56,6 +56,15 @@ function makeApi() { } describe("api() connector", () => { + it("refuses duplicate names before discovery and dispatch can disagree about safety", () => { + expect(() => api("ambiguous", { + tools: [ + { name: "same", description: "Read a value", annotations: { readOnlyHint: true }, handler: () => null }, + { name: "same", description: "Write a value", annotations: { readOnlyHint: false }, handler: () => null }, + ], + })).toThrow('api() tool "ambiguous.same" is declared more than once'); + }); + it("kind is 'api' and description is preserved", () => { const c = makeApi(); expect(c.id).toBe("resend"); diff --git a/test/artifacts-connector.test.ts b/test/artifacts-connector.test.ts index 0e8005f8..d91ba6fc 100644 --- a/test/artifacts-connector.test.ts +++ b/test/artifacts-connector.test.ts @@ -384,6 +384,15 @@ describe("the render-check hook", () => { expect(await store.head("q3-bugs")).toBeNull(); }); + it("does not invoke a render hook after its caller has already aborted", async () => { + const controller = new AbortController(); + controller.abort(new Error("call deadline expired")); + const hook = vi.fn(async () => ({ ok: true })); + expect(await runRenderCheck(hook, { document: "", csp: "sandbox", kind: "html" }, controller.signal)) + .toMatchObject({ ok: false, unavailable: expect.any(String) }); + expect(hook).not.toHaveBeenCalled(); + }); + it("gives up on a hook that never answers after 20 seconds", async () => { vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] }); try { diff --git a/test/artifacts-operations.test.ts b/test/artifacts-operations.test.ts index a2886ff8..65848ee1 100644 --- a/test/artifacts-operations.test.ts +++ b/test/artifacts-operations.test.ts @@ -506,6 +506,46 @@ describe("reading", () => { expect(unlisted.ok && unlisted.documents).toEqual({}); }); + it("advances past an empty lagging-list page to later artifacts", async () => { + const base = memoryStorage(); + let listings = 0; + const store = kvArtifactStore({ ...base, list: async prefix => { + if (prefix !== "artifact:head:") return base.list!(prefix); + listings++; + if (listings > 3) throw new Error("listing did not advance"); + return [...Array.from({ length: 100 }, (_, index) => `artifact:head:a-${String(index).padStart(3, "0")}`), + ...await base.list!(prefix)].sort(); + } }); + const { ops } = setup({}, store); + await ops.create({ id: "z-live", title: "Live", kind: "markdown", source: "Hello", by: alice }); + const listed = await ops.list({ limit: 50 }); + expect(listed.ok && listed.items.map(item => item.id)).toEqual(["z-live"]); + expect(listings).toBe(2); + }); + + it("bounds scans of missing heads and returns their continuation", async () => { + const base = kvArtifactStore(memoryStorage()); + let pages = 0; + const store: ArtifactStore = { ...base, heads: async () => { + if (++pages > 12) throw new Error("missing heads escaped the scan bound"); + return { heads: [], next: `page-${String(pages).padStart(4, "0")}` }; + } }; + const { ops } = setup({}, store); + expect(await ops.list({ limit: 50 })).toEqual({ ok: true, items: [], nextCursor: "page-0010" }); + expect(pages).toBe(10); + }); + + it("refuses a nonadvancing store cursor instead of looping", async () => { + const base = kvArtifactStore(memoryStorage()); + let reads = 0; + const { ops } = setup({}, { ...base, heads: async () => { + if (++reads > 3) throw new Error("repeated a nonadvancing cursor"); + return { heads: [], next: "same" }; + } }); + expect(await ops.list({ limit: 50, cursor: "same" })).toMatchObject({ ok: false, code: "unavailable" }); + expect(reads).toBe(1); + }); + it("answers a missing artifact as not_found naming the way to list", async () => { const { ops } = setup(); expect(await ops.get("nope")).toMatchObject({ diff --git a/test/artifacts-routes.test.ts b/test/artifacts-routes.test.ts index ec6d714e..fcb03394 100644 --- a/test/artifacts-routes.test.ts +++ b/test/artifacts-routes.test.ts @@ -4,6 +4,8 @@ import { describe, expect, it, vi } from "vitest"; import { artifacts, kvArtifactStore } from "../src/artifacts.js"; +import { ArtifactOperations } from "../src/artifacts/operations.js"; +import { resolveAllowlist, resolveLimits } from "../src/artifacts/validate.js"; import { bearerToken } from "../src/auth/bearer.js"; import { createConnecta, type ConnectaConfig } from "../src/index.js"; import { memoryStorage } from "../src/storage/memory.js"; @@ -56,6 +58,53 @@ async function deploy(config: Omit, "ui" | "artifacts"> return { app, get, call }; } +describe("artifact render-check theme", () => { + it("checks Markdown writes, validation and refresh in the viewer's deployment theme", async () => { + const documents: string[] = []; + const store = kvArtifactStore(memoryStorage()); + const module = artifacts({ store, renderCheck: async input => { + documents.push(input.document); + return { ok: true }; + } }); + const app = createConnecta({ + connectors: [], publicUrl: BASE, logger: "silent", auth: bearerToken(TOKEN), artifacts: module, + executor: { execute: async () => ({ result: { value: 2 } }) }, + ui: operatorUi({ branding: { theme: { colorScheme: "dark", accent: "#0a7d55" } } }), + }); + try { + const ctx = { storage: memoryStorage(), logger: console, baseUrl: BASE }; + await module.connector.callTool("create_artifact", { + id: "themed", title: "Themed", kind: "markdown", source: "Hello", + documents: { data: { value: 1 } }, + }, ctx); + const view = await app.fetch(new Request(`${BASE}/artifacts/_api/view/themed`, { + headers: { Authorization: `Bearer ${TOKEN}` }, + })); + const served = await view.json() as { document: string }; + expect(documents[0]).toBe(served.document); + await module.connector.callTool("validate_artifact", { kind: "markdown", source: "Hello" }, ctx); + const operations = new ArtifactOperations({ store, limits: resolveLimits(), allowlist: resolveAllowlist() }); + const configured = await operations.setRefresh({ + id: "themed", document: "data", program: "async () => ({ value: 2 })", schedule: "manual", baseVersion: 0, + by: { kind: "test" }, owner: { identity: { actor: { kind: "test" }, interactive: false } }, + }); + expect(configured.ok).toBe(true); + expect(await module.refresh("themed", { kind: "test" })).toMatchObject({ status: "succeeded" }); + const refreshed = await app.fetch(new Request(`${BASE}/artifacts/_api/view/themed`, { + headers: { Authorization: `Bearer ${TOKEN}` }, + })); + expect(documents[2]).toBe((await refreshed.json() as { document: string }).document); + expect(documents).toHaveLength(3); + for (const document of documents) { + expect(document).toContain('data-scheme="dark"'); + expect(document).toContain("#0a7d55"); + } + } finally { + await app.close(); + } + }); +}); + describe("artifact page shells", () => { it("serves the library, viewer, and snapshot shells open, data-free, and framed off", async () => { const { get } = await deploy(); diff --git a/test/browser/artifacts.spec.ts b/test/browser/artifacts.spec.ts index 01204b0e..ad56c25b 100644 --- a/test/browser/artifacts.spec.ts +++ b/test/browser/artifacts.spec.ts @@ -173,6 +173,50 @@ test("a foreign page cannot embed the frame", async ({ page }) => { } }); +test("an older artifact filter response cannot replace the current library", async ({ page }) => { + pageSource = '
page
'; + await start(); + await page.addInitScript((token) => localStorage.setItem("connecta:token", token), TOKEN); + await page.goto(`${origin}/artifacts`); + await expect(page.getByRole("link", { name: "Probe", exact: true })).toBeVisible(); + let release!: () => void; + const gate = new Promise(resolve => { release = resolve; }); + let started!: () => void; + const entered = new Promise(resolve => { started = resolve; }); + await page.route("**/artifacts/_api/list?archived=1", async route => { + started(); + await gate; + await route.fulfill({ json: { artifacts: [{ + id: "obsolete", title: "Obsolete filter", kind: "html", viewVersion: 1, + updatedAt: "2026-10-01T00:00:00Z", updatedBy: { label: "viewer" }, archived: true, + }], nextCursor: "obsolete" } }); + }); + await page.getByRole("checkbox", { name: "Show archived" }).check(); + await entered; + await page.getByRole("checkbox", { name: "Show archived" }).uncheck(); + await expect(page.getByRole("link", { name: "Probe", exact: true })).toBeVisible(); + const completed = page.waitForResponse(response => response.url().endsWith("?archived=1")); + release(); + await completed; + await expect(page.getByRole("link", { name: "Probe", exact: true })).toBeVisible(); + await expect(page.getByRole("link", { name: "Obsolete filter" })).toHaveCount(0); + await expect(page.getByRole("button", { name: "Load more" })).toHaveCount(0); +}); + +test("signing in again opens an artifact in a fresh confined shell", async ({ page }) => { + pageSource = '
private page
'; + await start(); + await page.addInitScript((token) => localStorage.setItem("connecta:token", token), TOKEN); + await page.goto(`${origin}/artifacts/probe`); + await expect(page.frameLocator("#artifactFrame").locator("#artifact-root")).toHaveText("private page"); + await page.getByRole("button", { name: "Change token" }).click(); + await page.getByRole("textbox", { name: "Bearer token" }).fill(TOKEN); + await page.getByRole("button", { name: "Open operator pages" }).click(); + await expect(page.frameLocator("#artifactFrame").locator("#artifact-root")).toHaveText("private page"); + expect(requests.filter(path => path === "/artifacts/probe")).toHaveLength(2); + expect(requests.filter(path => path === "/artifacts/_frame")).toHaveLength(2); +}); + test("library and browser history open a fresh frame for each page", async ({ page }) => { pageSource = '
first page
'; await start(); diff --git a/test/browser/operator-ui.spec.ts b/test/browser/operator-ui.spec.ts index dbef9dc8..3404edf9 100644 --- a/test/browser/operator-ui.spec.ts +++ b/test/browser/operator-ui.spec.ts @@ -1322,6 +1322,8 @@ test("keeps loaded artifacts when loading more fails", async ({ page }) => { test("creates, shows once, renames and revokes client tokens through real routes", async ({ page }) => { const storage = memoryStorage(); + const manager = new AccessTokenManager(storage); + await manager.create("Existing client", "older-owner"); const connecta = createTestConnecta({ connectors: [], auth: { ...fakeClerkAuth(), activityActorNamespace: "clerk:test" }, @@ -1331,21 +1333,40 @@ test("creates, shows once, renames and revokes client tokens through real routes const paths = new Set(["/ui/access-tokens"]); realRoutes = { connecta, paths, answered: [] }; tokenManagement = true; + let releaseList!: () => void; + const listGate = new Promise(resolve => { releaseList = resolve; }); + let listStarted!: () => void; + const listEntered = new Promise(resolve => { listStarted = resolve; }); + let held = false; + await page.route("**/ui/access-tokens", async route => { + if (route.request().method() !== "GET" || held) return route.continue(); + held = true; + const response = await route.fetch(); + listStarted(); + await listGate; + await route.fulfill({ response }); + }); try { await openAuthenticated(page); await page.getByRole("link", { name: "Access tokens", exact: true }).click(); + await listEntered; await page.getByLabel("Client name").fill("Desktop client"); await page.getByRole("button", { name: "Create token", exact: true }).click(); await expect(page.locator("#createdToken")).toContainText("cta_"); + const listed = page.waitForResponse(response => response.url().endsWith("/ui/access-tokens") && response.request().method() === "GET"); + releaseList(); + await listed; + await expect(page.getByRole("heading", { name: "Desktop client", exact: true })).toBeVisible(); + await expect(page.getByRole("heading", { name: "Existing client", exact: true })).toBeVisible(); const secret = (await page.locator("#createdToken").textContent())!; - const manager = new AccessTokenManager(storage); - const [record] = await manager.list(); + const record = (await manager.list()).find(token => token.name === "Desktop client"); + const card = page.locator(`.token-card[aria-labelledby="access-token-${record!.id}"]`); paths.add(`/ui/access-tokens/${record!.id}`); const authenticate = () => manager.auth.authorize(new Request(REAL_BASE + "/mcp", { headers: { Authorization: `Bearer ${secret}` } }), REAL_BASE); expect((await authenticate()).ok).toBe(true); await page.getByRole("button", { name: "I stored it" }).click(); await expect(page.locator("#createdToken")).toHaveCount(0); - await page.getByRole("button", { name: "Rename", exact: true }).click(); + await card.getByRole("button", { name: "Rename", exact: true }).click(); await page.getByLabel("Token name", { exact: true }).fill("Renamed desktop"); await page.getByRole("button", { name: "Save name", exact: true }).click(); await expect(page.getByRole("heading", { name: "Renamed desktop", exact: true })).toBeVisible(); @@ -1353,8 +1374,8 @@ test("creates, shows once, renames and revokes client tokens through real routes await expect(page.getByRole("heading", { name: "Renamed desktop", exact: true })).toBeVisible(); expect(await page.content()).not.toContain(secret); page.once("dialog", dialog => dialog.accept()); - await page.getByRole("button", { name: "Revoke", exact: true }).click(); - await expect(page.locator(".token-card")).toHaveClass(/revoked/); + await card.getByRole("button", { name: "Revoke", exact: true }).click(); + await expect(card).toHaveClass(/revoked/); expect((await authenticate()).ok).toBe(false); } finally { await connecta.close(); } }); diff --git a/test/doctor-cli.test.ts b/test/doctor-cli.test.ts index 152e747e..6790635c 100644 --- a/test/doctor-cli.test.ts +++ b/test/doctor-cli.test.ts @@ -1,3 +1,4 @@ +import { createServer } from "node:http"; import { execFile } from "node:child_process"; import { once } from "node:events"; import { dirname, resolve } from "node:path"; @@ -184,3 +185,42 @@ describe("connecta doctor's executor line", () => { }); }); }); + +describe("connecta doctor's credential destinations", () => { + it.each(["/health", "/mcp"])("refuses redirects from %s without forwarding credentials", async (route) => { + const received: Array> = []; + const target = createServer((request, response) => { + received.push(request.headers); + response.setHeader("Content-Type", "application/json"); + response.end(JSON.stringify({ status: "ok" })); + }); + target.listen(0, "127.0.0.1"); + await once(target, "listening"); + teardown.push(() => new Promise((done) => target.close(() => done()))); + const targetAddress = target.address(); + if (!targetAddress || typeof targetAddress === "string") throw new Error("Expected TCP address"); + const source = createServer((request, response) => { + if (request.url !== route) { + response.setHeader("Content-Type", "application/json"); + response.end(JSON.stringify({ status: "ok" })); + return; + } + response.writeHead(302, { Location: `http://127.0.0.1:${targetAddress.port}/capture` }); + response.end(); + }); + source.listen(0, "127.0.0.1"); + await once(source, "listening"); + teardown.push(() => new Promise((done) => source.close(() => done()))); + const sourceAddress = source.address(); + if (!sourceAddress || typeof sourceAddress === "string") throw new Error("Expected TCP address"); + await expect(run(process.execPath, [CLI, "doctor", "--url", `http://127.0.0.1:${sourceAddress.port}`], { + env: { + ...process.env, + CONNECTA_TOKEN: "synthetic-bearer", + CF_ACCESS_CLIENT_ID: "synthetic-id", + CF_ACCESS_CLIENT_SECRET: "synthetic-secret", + }, + })).rejects.toMatchObject({ stderr: expect.stringContaining("redirect") }); + expect(received).toEqual([]); + }); +}); diff --git a/test/downstream-oauth.test.ts b/test/downstream-oauth.test.ts index 23e4ead7..5258aad4 100644 --- a/test/downstream-oauth.test.ts +++ b/test/downstream-oauth.test.ts @@ -2384,11 +2384,15 @@ describe("OAuthRefreshCoordinator", () => { it("persists an aborted owner's accepted rotation and hands it to a contender", async () => { const storage = memoryStorage(); const coordinator = new OAuthRefreshCoordinator(); + const trackedOwner = trackedAbortSignal(); const owner = new KvOAuthProvider( "svc", storage, REDIRECT, coordinator, + false, + undefined, + trackedOwner.signal, ); const contender = new KvOAuthProvider( "svc", @@ -2415,7 +2419,6 @@ describe("OAuthRefreshCoordinator", () => { refresh_token: "refresh-new", }); }; - const trackedOwner = trackedAbortSignal(); // The owner has its valid response; the SDK has not saved it yet. await coordinator.coordinatedFetch( owner, @@ -2475,6 +2478,116 @@ describe("OAuthRefreshCoordinator", () => { expect(redeemed).toEqual(["refresh-old", "refresh-new"]); }); + it.each([false, true])("commits an accepted response arriving after cancellation only in its active epoch, disconnected=%s", async disconnected => { + const storage = memoryStorage(); + const coordinator = new OAuthRefreshCoordinator(); + const controller = new AbortController(); + const owner = new KvOAuthProvider("svc", storage, REDIRECT, coordinator, false, undefined, controller.signal); + owner.captureGeneration("legacy"); + await owner.saveTokens({ access_token: "old", token_type: "Bearer", refresh_token: "refresh-old" }); + const answering = deferred(); + const requested = deferred(); + const baseFetch: FetchLike = async () => { requested.resolve(); return answering.promise; }; + const refreshed = coordinator.coordinatedFetch(owner, baseFetch, controller.signal)( + "https://auth.example/token", refreshInit("refresh-old"), + ); + const failed = expect(refreshed).rejects.toThrow("owner cancelled"); + await requested.promise; + controller.abort(new Error("owner cancelled")); + await failed; + const observer = new KvOAuthProvider("svc", storage, REDIRECT, coordinator); + expect(await observer.tokens()).toMatchObject({ refresh_token: "refresh-old" }); + if (disconnected) await observer.resetAuthorization(true); + answering.resolve(Response.json({ access_token: "new", token_type: "Bearer", refresh_token: "refresh-new" })); + if (disconnected) { + // Await the late commit attempt, then check both the live epoch and residue. + const committing = vi.spyOn(owner, "saveAcceptedRefreshTokens"); + await vi.waitFor(() => expect(committing).toHaveBeenCalledTimes(1)); + await committing.mock.results[0]!.value; + expect(await observer.tokens()).toBeUndefined(); + expect(await storage.get("oauth:tokens")).toBeNull(); + expect(await observer.operatorDisconnected()).toBe(true); + } else { + await vi.waitFor(async () => expect(await observer.tokens()).toMatchObject({ refresh_token: "refresh-new" })); + } + }); + + it("removes a cancelled owner's accepted rotation when disconnect fences its pending storage write", async () => { + const backing = memoryStorage(); + const writing = deferred(); + const releaseWrite = deferred(); + let hold = false; + const storage: KVStorage = { ...backing, set: async (key, value) => { + if (hold && key === "oauth:tokens") { + writing.resolve(); + await releaseWrite.promise; + } + await backing.set(key, value); + } }; + const coordinator = new OAuthRefreshCoordinator(); + const controller = new AbortController(); + const owner = new KvOAuthProvider("svc", storage, REDIRECT, coordinator, false, undefined, controller.signal); + owner.captureGeneration("legacy"); + await owner.saveTokens({ access_token: "old", token_type: "Bearer", refresh_token: "refresh-old" }); + await coordinator.coordinatedFetch(owner, async () => Response.json({ + access_token: "new", token_type: "Bearer", refresh_token: "refresh-new", + }), controller.signal)("https://auth.example/token", refreshInit("refresh-old")); + hold = true; + const committing = vi.spyOn(owner, "saveAcceptedRefreshTokens"); + controller.abort(new Error("owner cancelled")); + await writing.promise; + const disconnected = new KvOAuthProvider("svc", storage, REDIRECT, coordinator); + await disconnected.resetAuthorization(true); + releaseWrite.resolve(); + await committing.mock.results[0]!.value; + expect(await disconnected.tokens()).toBeUndefined(); + expect(await disconnected.operatorDisconnected()).toBe(true); + expect(await backing.get("oauth:tokens")).toBeNull(); + }); + + it("joins overlapping SDK and cancellation saves before permitting the next rotation", async () => { + const backing = memoryStorage(); + const firstWrite = deferred(); + const releaseFirst = deferred(); + const releaseDuplicate = deferred(); + let held = false; + let writes = 0; + const storage: KVStorage = { ...backing, set: async (key, value) => { + if (held && key === "oauth:tokens" && JSON.parse(value).refresh_token === "refresh-b") { + writes++; + if (writes === 1) { firstWrite.resolve(); await releaseFirst.promise; } + else await releaseDuplicate.promise; + } + await backing.set(key, value); + } }; + const coordinator = new OAuthRefreshCoordinator(); + const controller = new AbortController(); + const owner = new KvOAuthProvider("svc", storage, REDIRECT, coordinator, false, undefined, controller.signal); + owner.captureGeneration("legacy"); + await owner.saveTokens({ access_token: "access-a", token_type: "Bearer", refresh_token: "refresh-a" }); + const rotated = { access_token: "access-b", token_type: "Bearer", refresh_token: "refresh-b" }; + await coordinator.coordinatedFetch(owner, async () => Response.json(rotated), controller.signal)( + "https://auth.example/token", refreshInit("refresh-a"), + ); + held = true; + const recovery = vi.spyOn(owner, "saveAcceptedRefreshTokens"); + controller.abort(new Error("owner cancelled after redemption")); + await firstWrite.promise; + const sdkSave = owner.saveTokens(rotated); + releaseFirst.resolve(); + await recovery.mock.results[0]!.value; + const next = new KvOAuthProvider("svc", storage, REDIRECT, coordinator); + next.captureGeneration("legacy"); + const answer = await coordinator.coordinatedFetch(next, async () => Response.json({ + access_token: "access-c", token_type: "Bearer", refresh_token: "refresh-c", + }))("https://auth.example/token", refreshInit("refresh-b")); + await next.saveTokens(await answer.json() as OAuthTokens); + releaseDuplicate.resolve(); + await sdkSave; + expect(await next.tokens()).toMatchObject({ access_token: "access-c", refresh_token: "refresh-c" }); + expect(writes).toBe(1); + }); + it("keeps a refused grant's flight standing when its owner aborts during the discard", async () => { const storage = memoryStorage(); const coordinator = new OAuthRefreshCoordinator(); diff --git a/test/file-storage.test.ts b/test/file-storage.test.ts index 360044c8..8d1b022c 100644 --- a/test/file-storage.test.ts +++ b/test/file-storage.test.ts @@ -306,10 +306,54 @@ describe("fileStorage", () => { await store.set("k", "v"); vi.spyOn(fs, "renameSync").mockImplementation(() => { throw new Error("rename failed"); }); await expect(store.set("k", "next")).rejects.toThrow("rename failed"); + expect(await store.get("k")).toBe("v"); expect(readdirSync(join(path, ".."))).toEqual(["state.json", "state.json.lock"]); expect(JSON.parse(readFileSync(path, "utf8"))).toEqual({ k: { value: "v" } }); }); + it("rolls back failed sets and deletes before a later write persists", async () => { + const path = tempStatePath(); + const store = openStore(path); + await store.set("k", "v"); + vi.spyOn(fs, "renameSync").mockImplementation(() => { throw new Error("rename failed"); }); + await expect(store.set("fresh", "uncommitted")).rejects.toThrow("rename failed"); + await expect(store.delete("k")).rejects.toThrow("rename failed"); + expect(await store.get("k")).toBe("v"); + expect(await store.get("fresh")).toBeNull(); + vi.restoreAllMocks(); + await store.set("later", "committed"); + expect(JSON.parse(readFileSync(path, "utf8"))).toEqual({ + k: { value: "v" }, later: { value: "committed" }, + }); + }); + + it("persists prototype-named keys across reopen", async () => { + const path = tempStatePath(); + const store = openStore(path); + for (const key of ["__proto__", "constructor", "toString"]) { + await store.set(key, `value:${key}`); + } + store.close(); + const reopened = requireCas(openStore(path)); + for (const key of ["__proto__", "constructor", "toString"]) { + expect(await reopened.get(key)).toBe(`value:${key}`); + expect(await reopened.compareAndSet(key, `value:${key}`, `updated:${key}`)).toBe(true); + } + expect(await reopened.list!("")).toEqual(["__proto__", "constructor", "toString"]); + reopened.close(); + const third = openStore(path); + expect(await third.get("__proto__")).toBe("updated:__proto__"); + }); + + it("expires entries exactly at their TTL boundary", async () => { + vi.spyOn(Date, "now").mockReturnValue(1_000); + const store = requireCas(openStore(tempStatePath())); + await store.set("lease", "old", { ttlSeconds: 1 }); + vi.spyOn(Date, "now").mockReturnValue(2_000); + expect(await store.get("lease")).toBeNull(); + expect(await store.compareAndSet("lease", null, "new")).toBe(true); + }); + it("refuses a separate process and releases locks on process exit", () => { const path = tempStatePath(); const store = openStore(path); @@ -394,6 +438,23 @@ describe("fileStorage", () => { expect(await openStore(tempStatePath()).get("k")).toBeNull(); }); + it.each(["null", "[]", '"snapshot"', "42"])( + "quarantines a JSON snapshot with an invalid root: %s", + async (raw) => { + const path = tempStatePath(); + writeFileSync(path, raw); + const error = vi.spyOn(console, "error").mockImplementation(() => {}); + const store = openStore(path); + expect(error).toHaveBeenCalledOnce(); + expect(await store.get("k")).toBeNull(); + await store.set("k", "v"); + const dir = join(path, ".."); + const quarantine = required(readdirSync(dir).find((file) => file.includes(".corrupt-"))); + expect(readFileSync(join(dir, quarantine), "utf8")).toBe(raw); + expect(JSON.parse(readFileSync(path, "utf8"))).toEqual({ k: { value: "v" } }); + }, + ); + it.skipIf(process.platform === "win32")( "writes the state file owner-only (0600)", async () => { diff --git a/test/meta-tool-schemas.test.ts b/test/meta-tool-schemas.test.ts index 34754bb2..da36c273 100644 --- a/test/meta-tool-schemas.test.ts +++ b/test/meta-tool-schemas.test.ts @@ -1,12 +1,10 @@ // Golden: the input schema of every meta-tool exactly as `tools/list` renders // it today, from the zod definitions in meta-tools.ts and execute.ts. // -// This is the wire contract a client's model reads, and the Effect conversion -// (P1-S16b) re-renders all eight from Effect Schema. A difference there is not -// automatically wrong — but it has to be a decision, justified where it lands, -// rather than a rendering accident. So this file changes only alongside an -// intended change to a tool's input, never to make a refactor pass. Key order -// is not compared; a JSON Schema's meaning does not depend on it. +// This is the wire contract a client's model reads. Any schema-library change +// must preserve it unless the tool's input intentionally changes. This file +// changes only alongside that intended change, never to make a refactor pass. +// Key order is not compared; a JSON Schema's meaning does not depend on it. import { describe, expect, it } from "vitest"; import type { Executor } from "../src/types.js"; diff --git a/test/notion-provider.test.ts b/test/notion-provider.test.ts index e570c66b..ed5e5926 100644 --- a/test/notion-provider.test.ts +++ b/test/notion-provider.test.ts @@ -23,8 +23,12 @@ import { NOTION_MCP_VETTED_CATALOG, notion, } from "../src/providers/notion.js"; +import { CatalogService } from "../src/catalog-service.js"; +import { InvocationService } from "../src/invocation.js"; +import { runEdge } from "../src/runtime/run.js"; +import { writeStateOf } from "../src/resumable.js"; import { isExplicitlyReadOnly } from "../src/tool-safety.js"; -import { silentLogger } from "./helpers.js"; +import { makeRegistry, silentLogger } from "./helpers.js"; import type { Connector, ConnectorContext } from "../src/types.js"; // The whole surface is hand-written, so there is no downstream catalog to @@ -1210,6 +1214,40 @@ describe("notion() error mapping", () => { test.each(cases)("%s", async (_name, run) => run()); }); +describe("notion() successful response integrity", () => { + it.each(["synthetic gateway", "", "null", "[]", '"text"'])("rejects unusable successful JSON without reporting an empty page: %s", async (body) => { + globalThis.fetch = vi.fn(async () => new Response(body)) as unknown as typeof fetch; + await expect(call(build(), "get_page_content", { block_id: "synthetic" })).rejects.toMatchObject({ + code: "connector_call_failed", retryable: false, + }); + expect(globalThis.fetch).toHaveBeenCalledTimes(1); + }); + + it("keeps a sent write's unreadable response unknown and does not expose body details", async () => { + globalThis.fetch = vi.fn(async () => new Response(new ReadableStream({ + start(controller) { controller.error(new Error("synthetic body detail")); }, + }))) as unknown as typeof fetch; + const provider = build(); + const { credential: _credential, ...local } = provider; + const registry = makeRegistry([{ + ...local, + callTool: (name, args) => provider.callTool(name, args, context()), + }]); + const invocation = new InvocationService(registry, new CatalogService(registry, "https://connecta.example")); + const outcome = await runEdge(invocation.pipeline( + "workspace.append_blocks", { block_id: "synthetic", text: ["hello"] }, + { source: "call_destructive_tool", allowDestructive: true }, + )); + expect(outcome.ok).toBe(false); + if (outcome.ok) throw new Error("Expected failed write response"); + expect(outcome.error).toMatchObject({ code: "connector_call_failed", retryable: false }); + expect(outcome.error.message).not.toContain("synthetic body detail"); + expect(outcome.dispatched).toBe(true); + expect(writeStateOf(outcome)).toBe("unknown"); + expect(globalThis.fetch).toHaveBeenCalledTimes(1); + }); +}); + describe("notion() writes", () => { it("creates a data source row with a schema-named title property", async () => { queue({ body: PAGE_FIXTURE }); diff --git a/test/operator-store.test.ts b/test/operator-store.test.ts index 193ab2e4..5be9d815 100644 --- a/test/operator-store.test.ts +++ b/test/operator-store.test.ts @@ -63,6 +63,7 @@ async function loadStore( kind: "clerk", publishableKey: "pk_test_fake", }, + page = "connections", ) { const fetchMock = vi.fn(); const windowListeners = new Map void>(); @@ -80,7 +81,7 @@ async function loadStore( signOut: vi.fn(async () => {}), }; const window = { - location: { href: `${BASE}/`, assign: vi.fn() }, + location: { href: `${BASE}/`, assign: vi.fn(), reload: vi.fn() }, Clerk: clerk, addEventListener: (name: string, listener: () => void) => { windowListeners.set(name, listener); @@ -90,6 +91,7 @@ async function loadStore( for (const [name, value] of Object.entries(PAGE_CONSTANTS)) { vi.stubGlobal(name, value); } + vi.stubGlobal("INITIAL_PAGE", page); vi.stubGlobal("AUTH", browserAuth); vi.stubGlobal("window", window); vi.stubGlobal("localStorage", { @@ -105,6 +107,7 @@ async function loadStore( clerk, fetchMock, windowListeners, + window, /** Hand Clerk's listener a session change, the way Clerk itself would. */ changeSession(next: FakeSession | null) { clerk.session = next as FakeSession; @@ -437,3 +440,98 @@ describe("managed token secret lifetime", () => { expect(JSON.stringify(store.getState())).not.toContain("cta_disposable-secret"); }); }); + + +describe("operator collection ordering", () => { + it("discards an artifact list superseded by a filter change", async () => { + const { store, fetchMock } = await loadStore({ id: "session", getToken: async () => "token" }); + fetchMock.mockResolvedValueOnce(Response.json({ artifacts: [] })); + await store.loadArtifacts(true); + const old = deferred(); + fetchMock.mockReturnValueOnce(old.promise); + const first = store.loadArtifacts(true); + await vi.waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(2)); + fetchMock.mockResolvedValueOnce(Response.json({ artifacts: [{ id: "archived" }], nextCursor: "new" })); + store.setArtifactArchived(true); + await vi.waitFor(() => expect(store.getState().artifactRows).toEqual([{ id: "archived" }])); + old.resolve(Response.json({ artifacts: [{ id: "old" }], nextCursor: "old" })); + await first; + expect(store.getState().artifactArchived).toBe(true); + expect(store.getState().artifactRows).toEqual([{ id: "archived" }]); + expect(store.getState().artifactCursor).toBe("new"); + }); + + it("loads existing tokens and keeps issuance when an earlier list finishes later", async () => { + const { store, fetchMock } = await loadStore({ id: "session", getToken: async () => "token" }); + const old = deferred(); + fetchMock.mockReturnValueOnce(old.promise); + const listing = store.loadAccessTokens(); + await vi.waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(1)); + const issued = { id: "issued", name: "client" }; + fetchMock.mockResolvedValueOnce(Response.json({ token: "secret", accessToken: issued })); + expect(await store.createAccessToken("client")).toBe(true); + const existing = { id: "existing", name: "older client" }; + old.resolve(Response.json({ accessTokens: [existing] })); + await listing; + expect(store.getState().tokens).toEqual([issued, existing]); + expect(store.getState().tokenPhase).toBe("ready"); + }); + + it.each(["rename", "revoke"])("keeps a local %s when a pending list captured the new token earlier", async action => { + const { store, fetchMock } = await loadStore({ id: "session", getToken: async () => "token" }); + const old = deferred(); + fetchMock.mockReturnValueOnce(old.promise); + const listing = store.loadAccessTokens(); + await vi.waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(1)); + const issued = { id: "issued", name: "client" }; + fetchMock.mockResolvedValueOnce(Response.json({ token: "secret", accessToken: issued })); + expect(await store.createAccessToken("client")).toBe(true); + const existing = { id: "existing", name: "older client" }; + const snapshot = Response.json({ accessTokens: [issued, existing] }); + const updated = action === "rename" + ? { ...issued, name: "renamed client" } + : { ...issued, revokedAt: "2026-10-01T00:00:00Z" }; + fetchMock.mockResolvedValueOnce(Response.json({ accessToken: updated })); + if (action === "rename") await store.saveAccessTokenName(issued.id, updated.name); + else await store.revokeAccessToken(issued.id); + old.resolve(snapshot); + await listing; + expect(store.getState().tokens).toEqual([updated, existing]); + }); + + it("keeps an existing token's local rename while a creation exposes its card during a pending read", async () => { + const { store, fetchMock } = await loadStore({ id: "session", getToken: async () => "token" }); + const existing = { id: "existing", name: "older client" }; + fetchMock.mockResolvedValueOnce(Response.json({ accessTokens: [existing] })); + await store.loadAccessTokens(); + const old = deferred(); + fetchMock.mockReturnValueOnce(old.promise); + const listing = store.loadAccessTokens(); + await vi.waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(2)); + const issued = { id: "issued", name: "new client" }; + fetchMock.mockResolvedValueOnce(Response.json({ token: "secret", accessToken: issued })); + await store.createAccessToken("new client"); + const updated = { ...existing, name: "renamed client" }; + fetchMock.mockResolvedValueOnce(Response.json({ accessToken: updated })); + await store.saveAccessTokenName(existing.id, updated.name); + const other = { id: "other", name: "another client" }; + old.resolve(Response.json({ accessTokens: [issued, existing, other] })); + await listing; + expect(store.getState().tokens).toEqual([issued, updated, other]); + }); + + it("reloads a confined artifact shell when Clerk changes identity", async () => { + const { store, fetchMock, changeSession, window } = await loadStore( + { id: "a", getToken: async () => "token-a" }, undefined, "artifact", + ); + window.location.href = `${BASE}/artifacts/probe`; + Object.assign(window.location, { pathname: "/artifacts/probe", search: "" }); + fetchMock.mockResolvedValueOnce(Response.json({ document: "page" })); + await store.boot(); + store.markArtifactFrameConfined(); + changeSession({ id: "b", getToken: async () => "token-b" }); + expect(window.location.reload).toHaveBeenCalledTimes(1); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(store.getState().artifactView).toBeNull(); + }); +}); diff --git a/test/package-surface.test.ts b/test/package-surface.test.ts index 7251aa44..ee9283c5 100644 --- a/test/package-surface.test.ts +++ b/test/package-surface.test.ts @@ -399,13 +399,13 @@ describe("Effect behind the published surface", () => { }, 60_000); }); - it("depends on effect at one exact version", () => { - // Exact, never a range: `effect/unstable/*` breaks in minor releases, and + it("depends on stable Effect v4 at one exact version", () => { + // Exact, never a range: unstable Effect APIs can break in minor releases, and // a deployment that also uses Alchemy must resolve one Effect, not two. // An upgrade is its own pull request. const pinned = packageJson.dependencies?.effect; expect(pinned, "effect is not a runtime dependency").toBeTruthy(); - expect(pinned).toMatch(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/); + expect(pinned).toMatch(/^4\.\d+\.\d+$/); expect(packageJson.peerDependencies).not.toHaveProperty("effect"); expect(packageJson.devDependencies).not.toHaveProperty("effect"); const lock = JSON.parse( @@ -419,8 +419,8 @@ describe("Effect behind the published surface", () => { }); it("declares exactly the core runtime dependencies", () => { - // zod leaves for devDependencies once the meta-tool input schemas move - // to Effect Schema (P1-S16b). + // Meta-tool inputs stay Zod: the measured Effect Schema conversion cost + // more bytes without clarifying validation. See architecture.md. expect(Object.keys(packageJson.dependencies ?? {}).sort()).toEqual([ "@cfworker/json-schema", "@modelcontextprotocol/client", diff --git a/test/purity.test.ts b/test/purity.test.ts index 2ca7ba9b..e8908c3b 100644 --- a/test/purity.test.ts +++ b/test/purity.test.ts @@ -173,8 +173,8 @@ describe("src/index.ts import purity (Workers-clean entry)", () => { const ROOT = resolve(SRC, ".."); const RUNNER = join(SRC, "runtime", "run.ts"); // The root entry may import Effect's stable core and nothing else: the -// `effect/unstable/*` modules are allowed behind subpaths, where their minor- -// release churn and their bytes are opt-in. +// area modules such as `effect/http-api` are allowed behind subpaths, where +// their bytes and any unstable API changes are opt-in. const ROOT_EFFECT_ALLOWED = new Set(["effect"]); // Test clocks, test layers, and platform runtimes belong to a test run or a // specific host, never to a runtime graph that ships to both Node and Workers. diff --git a/test/registry.test.ts b/test/registry.test.ts index 3655d49c..9f0efa43 100644 --- a/test/registry.test.ts +++ b/test/registry.test.ts @@ -9,6 +9,7 @@ import { CatalogService } from "../src/catalog-service.js"; import { api } from "../src/connectors/api.js"; import { Registry } from "../src/registry.js"; import { memoryStorage } from "../src/storage/memory.js"; +import { withDeadline } from "../src/timeout.js"; import type { Connector, KVStorage, @@ -947,6 +948,56 @@ describe("tool cache TTL", () => { } }); + it("keeps a queued catalog invalidation alive and ordered until its predecessor finishes", async () => { + vi.useFakeTimers(); + try { + const backing = memoryStorage(); + let release!: () => void; + let reached!: () => void; + const gate = new Promise((resolve) => { release = resolve; }); + const started = new Promise((resolve) => { reached = resolve; }); + let blocked = false; + const storage: KVStorage = { + get: (key) => backing.get(key), + delete: (key) => backing.delete(key), + async set(key, value, options) { + if (!blocked && key.startsWith("catalog:queued:chunk:")) { + blocked = true; + reached(); + await gate; + } + await backing.set(key, value, options); + }, + }; + const registry = new Registry([connectorWith({ + id: "queued", kind: "mcp", tools: [{ name: "read" }], + })], { storage, logger: silentLogger }); + const owner = registry.getTools("queued", BASE); + await started; + const invalidation = registry.invalidateStored("queued"); + await vi.advanceTimersByTimeAsync(0); + // On workerd a waiter with neither I/O nor a timer is cancelled as hung. + expect(vi.getTimerCount()).toBe(1); + const reason = new Error("request stopped waiting"); + const caller = withDeadline(() => invalidation, { + timeoutMs: 10, timeoutError: reason, + }); + const refused = expect(caller).rejects.toBe(reason); + await vi.advanceTimersByTimeAsync(10); + await refused; + // Cancellation of the requester cannot release a mutation's turn early. + expect(vi.getTimerCount()).toBe(1); + await vi.advanceTimersByTimeAsync(2_000); + expect(await backing.get("catalog:queued")).toBeNull(); + release(); + await Promise.all([owner, invalidation]); + expect(await backing.get("catalog:queued")).toBeNull(); + expect(vi.getTimerCount()).toBe(0); + } finally { + vi.useRealTimers(); + } + }); + it("keeps discovery and invalidation working when catalog storage fails", async () => { const warnings: string[] = []; let deletes = 0; diff --git a/test/remote-mcp.test.ts b/test/remote-mcp.test.ts index 98894632..1dd5da45 100644 --- a/test/remote-mcp.test.ts +++ b/test/remote-mcp.test.ts @@ -1410,3 +1410,48 @@ describe("remoteMcp() connection lifecycle", () => { } }); }); + + +describe("OAuth callback transport ownership", () => { + it.each(["exchange", "clearPending"] as const)("closes an exchange-only transport after %s fails", async failure => { + const backing = memoryStorage(); + const context = { ...ctx(), storage: { + ...backing, + delete: async (key: string) => { + if (failure === "clearPending") throw new Error("pending cleanup failed"); + await backing.delete(key); + }, + } }; + const close = vi.fn(async () => {}); + const transport = { + start: async () => {}, send: async () => {}, close, + finishAuth: async () => { + if (failure === "exchange") throw new Error("exchange failed"); + }, + }; + const connector = remoteMcp("down", { + url: "https://downstream.test/mcp", auth: { type: "oauth" }, + _transportFactory: () => transport, + }); + await expect(connector.finishAuth!("code", context)).rejects.toThrow( + failure === "exchange" ? "exchange failed" : "pending cleanup failed", + ); + await connector.closeScope!(context); + await vi.waitFor(() => expect(close).toHaveBeenCalledTimes(1)); + }); + + it("does not hold a failed callback on a hanging transport close", async () => { + const closing = deferred(); + const close = vi.fn(() => closing.promise); + const connector = remoteMcp("down", { + url: "https://downstream.test/mcp", auth: { type: "oauth" }, + _transportFactory: () => ({ + start: async () => {}, send: async () => {}, close, + finishAuth: async () => { throw new Error("exchange failed"); }, + }), + }); + await expect(connector.finishAuth!("code", ctx())).rejects.toThrow("exchange failed"); + await vi.waitFor(() => expect(close).toHaveBeenCalledTimes(1)); + closing.resolve(); + }); +}); diff --git a/test/resumable.test.ts b/test/resumable.test.ts index 98bf25e8..9f21c020 100644 --- a/test/resumable.test.ts +++ b/test/resumable.test.ts @@ -27,6 +27,7 @@ import { } from "../src/resumable.js"; import { canonicalJson, + MAX_JOURNAL_BYTES, classifyWriteOutcome, parseToken, RunJournal, @@ -863,6 +864,34 @@ describe("resumable writes: expiry, divergence, and budgets", () => { expect(w.writes()).toHaveLength(1); }); + it("counts historical sends when a replay skips a recorded write", async () => { + const w = world({ settings: { maxWrites: 2 } }); + let plays = 0; + const first = paused(await w.execute(w.program(async (connecta) => { + plays++; + for (let id = plays >= 3 ? 1 : 0; id < 3; id++) { + await connecta.call!("tracker.close_issue", { id }); + } + return "done"; + }))); + const second = paused(await w.resume(approve(first))); + const result = await w.resume(approve(second, "tool")); + expect(w.writes()).toHaveLength(2); + expect(value(result).error.writes).toEqual({ succeeded: 2, failed: 0, unknown: 0 }); + }); + + it("does not count replayed writes twice across deterministic chained pauses", async () => { + const w = world({ settings: { maxWrites: 2 } }); + const first = paused(await w.execute(w.program(async (connecta) => { + await connecta.call!("tracker.close_issue", { id: 0 }); + await connecta.call!("tracker.close_issue", { id: 1 }); + return "done"; + }))); + const second = paused(await w.resume(approve(first))); + expect(value(await w.resume(approve(second))).result).toBe("done"); + expect(w.writes()).toHaveLength(2); + }); + it("reports resumable writes unavailable when they are off", async () => { const w = world(); const result = await runEdge(resumeExecution( @@ -1239,6 +1268,64 @@ describe("resumable writes: review regressions", () => { }); describe("resumable writes: messages that match what happened", () => { + it.each([false, true])("bounds journaled write answers after a near-cap pause, concurrent=%s", async (concurrent) => { + const w = world({ + read: () => "x".repeat(250_000), + write: () => "x".repeat(250_000), + }); + const first = paused(await w.execute(w.program(async (connecta) => { + for (let id = 0; id < 16; id++) await connecta.call!("reader.get", { id }); + await connecta.call!("tracker.close_issue", { id: 0 }); + if (concurrent) { + await Promise.all([1, 2].map((id) => connecta.call!("tracker.close_issue", { id }))); + } else { + await connecta.call!("tracker.close_issue", { id: 1 }); + } + return "done"; + }))); + const result = await w.resume(approve(first, "tool")); + expect(value(result).error).toMatchObject({ + code: "journal_too_large", + writes: { succeeded: 1, failed: 0, unknown: 0 }, + }); + expect(w.writes()).toHaveLength(1); + const { header: stored } = await header(w.storage, first.token); + expect(stored.bytes).toBeLessThanOrEqual(MAX_JOURNAL_BYTES); + expect(stored.state).toBe("failed"); + expect(stored.writes[0]?.state).toBe("ok"); + expect(value(await w.resume(approve(first, "tool"))).error).toEqual(value(result).error); + expect(w.writes()).toHaveLength(1); + }); + + it.each([false, true])("counts concurrent writes already dispatched when journal capacity runs out, unknown=%s", async (unknown) => { + let release: (() => void) | undefined; + let started = 0; + const bothStarted = new Promise((resolve) => { release = resolve; }); + const concurrent = world({ + read: () => "x".repeat(250_000), + write: async () => { + const index = ++started; + if (index === 2) release?.(); + await bothStarted; + if (unknown && index === 2) throw new ConnectorCallError("timeout", "write timed out"); + return "x".repeat(250_000); + }, + }); + const first = paused(await concurrent.execute(concurrent.program(async (connecta) => { + for (let id = 0; id < 16; id++) await connecta.call!("reader.get", { id }); + await Promise.all([0, 1].map((id) => connecta.call!("tracker.close_issue", { id }))); + return "done"; + }))); + const result = await concurrent.resume(approve(first, "tool")); + expect(value(result).error).toMatchObject({ + code: unknown ? "write_outcome_unknown" : "journal_too_large", + writes: { succeeded: unknown ? 1 : 2, failed: 0, unknown: unknown ? 1 : 0 }, + }); + const { header: stored } = await header(concurrent.storage, first.token); + expect(stored.bytes).toBeLessThanOrEqual(MAX_JOURNAL_BYTES); + expect(stored.writes.map((write) => write.state)).toEqual(["ok", unknown ? "unknown" : "ok"]); + }); + it("does not say nothing was sent when a chained pause is too large to hold", async () => { const w = world(); const first = paused(await w.execute(w.program(async (connecta) => {