dsh-keys-manager 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,33 +1,97 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to dsh-keys-manager are documented here. Generated from
4
- git log by the release-prep workflow.
3
+ All notable changes to dsh-keys-manager are documented here. Assembled from `.changes/unreleased/` fragments by the release-prep workflow.
4
+
5
+ ## [0.2.3] - 2026-10-22
6
+
7
+ ### Added
8
+
9
+ - The provider list gains five single-select quick filters — All, Built-in, Plugin, OAuth-capable and Transport — that narrow the managed and available lists and compose with the existing search.
10
+ - OAuth-capable uses the same verdict as a row's Add account button, so it lists exactly the routes that can start a sign-in right now.
11
+ - The chosen filter also governs the transport accounts section, which appears under All and Transport; with Transport selected and no accounts it stays visible with a `0` count and the existing empty copy.
12
+ - The quick-filter chips show a visible focus ring, the row is announced as a labelled group, the unattributed count keeps its number in its accessible name, and an empty transport list says so instead of reusing the search-empty copy.
13
+
14
+ ### Fixed
15
+
16
+ - A transport-plugin route (Cursor) now renders as a plugin Transport provider card and can no longer be adopted, key-bound or tested as a pi-ai provider; its account controls moved into that card.
17
+ - A transport account whose route id collides with an ordinary provider's id keeps its row (and its only sign-in/remove control) instead of being treated as card-owned by a card that never rendered it.
18
+ - Activating an account no longer holds the global mutation queue while it reads the OAuth credential, so a slow credential store cannot delay unrelated key or settings changes; the activation's errors, returned state and cooldown reset are unchanged.
19
+ - An activation now writes its credential projection only while the route still serves the value it read, and never deletes a grant another writer replaced. A rejected state flush puts the projection back — removing it only when the activation created it — so a refused activation leaves nothing behind.
20
+ - A refused activation restores the route's owner classification along with the projection it puts back, so requests keep using the restored credential and failures keep attributing to it instead of falling back to the key pool; the activated account keeps its claim whenever its projection is still serving.
21
+ - Provider rows show an `Unattributed` count for stream attempts a route served with no account owner — whether its ownership read had not landed yet or had failed — and the count clears once a later read attributes the route. A failover retry is a new attempt, so one request can be counted more than once.
22
+ - A request waiting on ownership hydration no longer keeps the process alive for the rest of the bounded wait.
23
+
24
+ ### Changed
25
+
26
+ - Release notes are now assembled from authored `.changes/unreleased/` fragments instead of `git log` subjects, and a release with no changelog bullets is refused.
27
+ - Backfill `CHANGELOG.md` for 0.1.0–0.2.2 with authored notes; earlier sections listed merge subjects.
5
28
 
6
29
  ## [0.2.2] - 2026-10-21
7
30
 
8
- - 838fb85 Merge pull request #31 from btspoony/feat/unified-release-version
9
- - ed5baaa Merge pull request #26 from btspoony/fix/oauth-binding-and-key-test
10
- - 2981bd5 Merge pull request #29 from btspoony/fix/retry-step-regressions
11
- - 25d7fc8 Merge pull request #28 from btspoony/feat/omp-transport-cursor
12
- - 03352c5 Merge pull request #27 from btspoony/docs/readme-extra-providers
13
- - 1d498e8 Merge pull request #23 from btspoony/fix/issue-18-harvest-abort-compensation
14
- - be938f0 Merge pull request #25 from btspoony/feat/issue-11-coreweave-header-carrier
15
- - 175e827 Merge pull request #24 from btspoony/docs/readme-desktop-web-install
31
+ ### Added
32
+
33
+ - Extra providers can declare environment-sourced request headers; values are read from your environment when the provider is added and refused loudly when missing, blank or credential-bearing.
34
+ - `coreweave` joins the catalog through that carrier with `COREWEAVE_API_KEY` and `COREWEAVE_PROJECT` for its `OpenAI-Project` header, and providers with declared headers use their seed catalog instead of model discovery.
35
+ - Changing the environment later surfaces as a state warning, and removing the provider drops only the header value it set.
36
+ - New published component `dsh-llm-omp-transports` serves Cursor through DSH's LLM seam: text and reasoning deltas, usage before a single finish, finish kinds, error classification and abort.
37
+ - Tool calls are projected from DSH into the transport and unresolved calls hand off to DSH's tool loop; transport-resolved native frames are not re-dispatched.
38
+ - Cursor sign-in uses a PKCE flow with poll and refresh, and a generated 123-row model table comes from a pinned catalog.
39
+ - The install now mounts two components (the plugin detail page lists both), and the keys manager lists, activates, removes and re-authenticates the resulting Cursor accounts beside the pi-ai pool.
40
+ - Key and account rows gain a Test action that runs one real request through the LLM seam and reports whether that credential serves the route, without touching health, cursor or binding state.
41
+
42
+ ### Changed
43
+
44
+ - Release prep drives both published packages from one version input: the resolved version is written to both manifests with both tags, and a transport manifest that disagrees with it is refused.
45
+
46
+ ### Fixed
47
+
48
+ - An OAuth-served route no longer fails with `MISSING_CREDENTIAL`: a broker reference is written only while the key pool actually serves the route, so pi-ai can reach its own grant, and a broken binding left by an earlier version is healed at startup.
49
+ - A login harvest that aborts because the attempt already settled now demotes the pi-ai projection it had written, so the route stops serving a superseded grant instead of staying bound to it.
50
+
51
+ ### Docs
52
+
53
+ - README documents the Desktop/Web install path.
54
+ - README lists the extra providers in a collapsible table.
16
55
 
17
56
  ## [0.2.1] - 2026-10-20
18
57
 
19
- - 83c11e1 Merge pull request #20 from btspoony/fix/authorization-inject
58
+ ### Fixed
59
+
60
+ - The plugin declares `authorization` in its inject list, so loading no longer fails with `cannot get property "authorization" without inject`.
20
61
 
21
62
  ## [0.2.0] - 2026-10-20
22
63
 
23
- - 74defd3 Merge pull request #17 from btspoony/feat/issue-2-phase2-oauth-pool
24
- - 7e7e356 Merge pull request #14 from btspoony/docs/post-merge-followups
64
+ ### Added
65
+
66
+ - OAuth accounts are pool members beside keys for Anthropic, GitHub Copilot, Kimi Coding, OpenAI Codex, OpenRouter, Radius and xAI: each provider card offers "Add account" next to "Add key".
67
+ - Account rows show the signed-in identity, status (active, cooling, paused, missing), last-used time and counters, with activate, re-authenticate and remove actions.
68
+ - Sign-in runs through the DSH authorization seam, bridging device-code and paste prompts into the plugin dialog.
69
+ - Accounts inherit sticky, round-robin, failover and cooldown behaviour; a failing account cools down and the provider falls back to its key pool, and re-activating the account restores it.
70
+ - Refresh rotations upstream are synced back into the owning account, and grant tokens stay in the DSH credential store: the manager keeps references and identity metadata only.
71
+
72
+ ### Docs
73
+
74
+ - The install documentation states that a registry install ships a prebuilt tarball and runs no build on your machine, while the clone/link path builds during `pnpm i`.
75
+ - The per-language documentation links moved into each README's Documentation section; no link changed target.
25
76
 
26
77
  ## [0.1.0] - 2026-10-19
27
78
 
28
- - b1380c9 Merge pull request #12 from btspoony/feat/issue-2-phase1-providers
29
- - 14e57ae Merge pull request #8 from btspoony/fix/post-merge-review
30
- - 8300083 Merge pull request #10 from btspoony/chore/initial-version-0.0.1
31
- - 8076b79 docs: record generated-CHANGELOG rule in AGENTS.md (#9)
32
- - 39d409b Merge pull request #1 from btspoony/feat/keys-manager-plugin
33
- - bbfcccb Initial commit
79
+ ### Added
80
+
81
+ - Twelve extra API-key providers from oh-my-pi are available as ordinary `llm-pi-ai` routes: siliconflow, siliconflow-cn, litellm, charm-hyper, zhipu-coding-plan, alibaba-token-plan, abliteration, cline-pass, commandcode, firepass, yolo-auto, and ollama-cloud.
82
+ - An extra provider is added with its API key, shows a seed model list until runtime discovery overrides it, and is released back to the plain route when removed.
83
+ - Several API keys per provider, each with an alias and a health state, instead of one shared key.
84
+ - Each session is pinned to one key, with round-robin selection for new sessions.
85
+ - A failed request retries on the same key within a bounded budget, then fails over to another key.
86
+ - A failing key cools down exponentially, capped at seven days, and returns to the rotation on its own.
87
+ - The plugin detail page gains a search-driven manager, and the existing model cards show a "managed by Keys Manager" badge.
88
+
89
+ ### Fixed
90
+
91
+ - Retry pins expire after 60 seconds, and starting a new step no longer deletes another in-flight step's pin.
92
+ - Request-step records are keyed to the provider the request finally uses, and unmanaged providers are skipped before anything is recorded.
93
+
94
+ ### Docs
95
+
96
+ - README rebuilt around a quick start, the two supported install paths (`dsh plugin --profile web add` and clone plus `dsh plugin --profile web link .`), the provider table and the managed-state notes, with a full Chinese translation.
97
+ - Development, release and known-limit pages moved to `docs/` in both languages, and the known limits for concurrent steps, ambient request identity and purpose-scoped streams are stated instead of implied.
@@ -1,2 +1,2 @@
1
1
  /** Stylesheet owned by the Keys Manager client surface. */
2
- export declare const CSS = "\n.km-page{max-width:920px;margin:0 auto;padding:0 0 8px;color:var(--dsw-alias-label-primary);font-size:13.5px;line-height:1.5}\n.km-embedded-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin:0 0 16px}\n.km-summary{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin:0 0 16px;flex:1;min-width:0}\n.km-embedded-head .km-summary{margin:0}\n.km-embedded-actions{display:flex;gap:8px;flex-shrink:0;padding-top:2px}\n.km-summary-line{color:var(--dsw-alias-label-secondary);font-size:12.5px;line-height:1.5}\n.km-stat{display:flex;flex-direction:column;gap:2px;padding:11px 14px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1)}\n.km-stat strong{font-size:19px;line-height:1.15;font-weight:650;font-variant-numeric:tabular-nums}\n.km-stat span{color:var(--dsw-alias-label-secondary);font-size:12px}\n.km-search{position:relative;display:flex;align-items:center;margin:0 0 20px}\n.km-search-icon{position:absolute;left:12px;color:var(--dsw-alias-label-secondary);pointer-events:none}\n.km-search-input{width:100%;box-sizing:border-box;padding:10px 38px 10px 36px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;outline:none}\n.km-search-input:focus{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-state-business-primary) 20%,transparent)}\n.km-search-clear{position:absolute;right:8px;display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}\n.km-search-clear:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}\n.km-section{margin:0 0 22px}\n.km-section-head{display:flex;align-items:baseline;gap:8px;margin:0 0 9px}\n.km-section-title{font-size:14px;font-weight:650;margin:0;letter-spacing:.01em}\n.km-section-count{font-size:12px;color:var(--dsw-alias-label-secondary)}\n.km-provider-list{display:flex;flex-direction:column;gap:8px}\n.km-show-more{display:flex;justify-content:center;padding:8px 0 0}\n.km-card{border:1px solid var(--dsw-alias-border-l1);border-radius:12px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}\n.km-provider-managed{border-color:color-mix(in srgb,var(--dsw-alias-brand-primary) 26%,var(--dsw-alias-border-l1))}\n.km-provider-open{overflow:visible}\n.km-provider-head{display:flex;align-items:center;gap:12px;width:100%;padding:12px 14px;border:0;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}\n.km-provider-head:hover{background:color-mix(in srgb,var(--dsw-alias-label-primary) 3%,transparent)}\n.km-avatar{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;flex:0 0 32px;border-radius:9px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);font-weight:650;font-size:13px;color:var(--dsw-alias-label-secondary)}\n.km-provider-main{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1}\n.km-provider-title-line{display:flex;align-items:center;gap:6px;flex-wrap:wrap;min-width:0}\n.km-provider-name{font-weight:650;font-size:13.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:360px}\n.km-provider-meta{display:flex;align-items:center;gap:7px;flex-wrap:wrap;color:var(--dsw-alias-label-secondary);font-size:11.5px;min-width:0}\n.km-provider-side{display:flex;align-items:center;gap:9px;flex-shrink:0}\n.km-count{font-size:11.5px;color:var(--dsw-alias-label-secondary);white-space:nowrap}\n.km-chevron{color:var(--dsw-alias-label-secondary);transition:transform .16s ease}\n.km-chevron-open{transform:rotate(180deg)}\n.km-provider-body{border-top:1px solid var(--dsw-alias-border-l1);padding:13px 14px 14px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 40%,transparent)}\n.km-adopt{display:flex;flex-direction:column;gap:12px}\n.km-adopt-copy{display:flex;flex-direction:column;gap:5px;color:var(--dsw-alias-label-secondary);font-size:12.5px}\n.km-adopt-title{color:var(--dsw-alias-label-primary);font-size:12.5px}\n.km-adopt-actions{display:flex;gap:8px;align-items:center}\n.km-meta-line{display:flex;gap:6px;align-items:baseline;flex-wrap:wrap;font-size:11.5px}\n.km-managed-body{display:flex;flex-direction:column;gap:10px}\n.km-managed-toolbar{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;flex-wrap:wrap}\n.km-managed-summary{display:flex;flex-direction:column;gap:2px;min-width:0;font-size:11.5px}\n.km-managed-actions{display:flex;gap:6px;align-items:center;flex-shrink:0}\n.km-keys{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l1);border-radius:9px;background:var(--dsw-alias-bg-layer-1);overflow:visible}\n.km-key-row{position:relative;display:grid;grid-template-columns:minmax(0,1.35fr) minmax(150px,.8fr) minmax(150px,.9fr) 32px;gap:10px;align-items:center;padding:10px 11px;border-top:1px solid var(--dsw-alias-border-l1)}\n.km-key-row:first-child{border-top:0}\n.km-account-row{position:relative;display:grid;grid-template-columns:minmax(0,1.35fr) minmax(150px,.8fr) minmax(150px,.9fr) 32px;gap:10px;align-items:center;padding:10px 11px 10px 14px;border-top:1px solid var(--dsw-alias-border-l1)}\n.km-account-row:first-child{border-top:0}\n.km-account-row::before{content:'';position:absolute;left:0;top:12px;bottom:12px;width:2px;border-radius:1px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 55%,transparent)}\n.km-account-glyph{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;flex:0 0 22px;border-radius:50%;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 14%,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-state-business-primary)}\n.km-key-identity{min-width:0;display:flex;flex-direction:column;gap:2px}\n.km-key-alias-line{display:flex;align-items:center;gap:6px;min-width:0}\n.km-key-alias{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px}\n.km-key-sub{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--dsw-alias-label-secondary);flex-wrap:wrap}\n.km-key-status{display:flex;flex-direction:column;gap:2px;align-items:flex-start}\n.km-key-last{display:flex;flex-direction:column;gap:2px;min-width:0}\n.km-key-time{font-size:11.5px;color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}\n.km-key-time.km-warn{color:var(--dsw-alias-state-warn-primary)}\n.km-key-actions{position:relative;display:flex;justify-content:flex-end}\n.km-status{display:inline-flex;align-items:center;gap:5px;padding:2px 7px;border-radius:999px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);font-size:11px;white-space:nowrap;color:var(--dsw-alias-label-secondary)}\n.km-status-success{color:var(--dsw-alias-state-success-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-success-primary) 26%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 8%,transparent)}\n.km-status-warn{color:var(--dsw-alias-state-warn-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 30%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 8%,transparent)}\n.km-status-error{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 30%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 8%,transparent)}\n.km-status-idle{color:var(--dsw-alias-state-idle-primary)}\n.km-dot{display:inline-block;width:6px;height:6px;border-radius:50%;flex:0 0 6px;background:var(--dsw-alias-state-idle-primary)}\n.km-dot-success{background:var(--dsw-alias-state-success-primary)}\n.km-dot-warn{background:var(--dsw-alias-state-warn-primary)}\n.km-dot-error{background:var(--dsw-alias-state-error-primary)}\n.km-dot-idle{background:var(--dsw-alias-state-idle-primary)}\n.km-dot-sep{color:var(--dsw-alias-label-secondary)}\n.km-tag{display:inline-flex;align-items:center;padding:1px 6px;border-radius:5px;font-size:10.5px;line-height:1.45;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);white-space:nowrap}\n.km-tag-accent{color:var(--dsw-alias-state-business-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-business-primary) 34%,var(--dsw-alias-border-l1));background:var(--dsw-alias-state-business-tertiary)}\n.km-tag-warn{color:var(--dsw-alias-state-warn-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 30%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 8%,transparent)}\n.km-icon-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:1px solid transparent;border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;flex:0 0 auto}\n.km-icon-btn:hover{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary)}\n.km-icon-btn:disabled{opacity:.5;cursor:default}\n.km-menu{position:absolute;top:32px;right:0;z-index:20;min-width:138px;padding:4px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-overlay);box-shadow:0 7px 22px color-mix(in srgb,var(--dsw-alias-label-primary) 14%,transparent)}\n.km-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 9px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12.5px;text-align:left;cursor:pointer}\n.km-menu-item:hover{background:var(--dsw-alias-bg-layer-2)}\n.km-menu-item:disabled{opacity:.5;cursor:default}\n.km-menu-danger{color:var(--dsw-alias-state-error-primary)}\n.km-menu-sep{height:1px;margin:4px 2px;background:var(--dsw-alias-border-l1)}\n.km-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:7px 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:12.5px;font-weight:550;line-height:1.2;cursor:pointer;white-space:nowrap;transition:opacity .15s ease,background .15s ease}\n.km-btn:hover{background:var(--dsw-alias-bg-layer-2)}\n.km-btn:disabled{opacity:.5;cursor:default}\n.km-btn-primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-base);border-color:transparent}\n.km-btn-primary:hover{opacity:.9;background:var(--dsw-alias-label-primary)}\n.km-btn-ghost{background:transparent;border-color:transparent;color:var(--dsw-alias-label-secondary)}\n.km-btn-ghost:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}\n.km-btn-danger{background:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-bg-base);border-color:transparent}\n.km-btn-danger:hover{opacity:.9;background:var(--dsw-alias-state-error-primary)}\n.km-btn-small{padding:5px 8px;font-size:12px;border-radius:7px}\n.km-input{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;outline:none}\n.km-input:focus{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-state-business-primary) 18%,transparent)}\n.km-input:disabled{opacity:.6}\nselect.km-input{appearance:auto}\n.km-form{display:flex;flex-direction:column;gap:10px;padding:12px;border:1px solid var(--dsw-alias-border-l1);border-radius:9px;background:var(--dsw-alias-bg-layer-1)}\n.km-form-lead{display:flex;flex-direction:column;gap:2px}\n.km-form-title{font-weight:650;font-size:12.5px}\n.km-form-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.4fr);gap:10px}\n.km-field{display:flex;flex-direction:column;gap:5px;min-width:0}\n.km-field-wide{min-width:0}\n.km-field-label{font-size:11.5px;font-weight:550;color:var(--dsw-alias-label-secondary)}\n.km-field-hint{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.45}\n.km-secret{position:relative;display:flex;align-items:center}\n.km-secret-input{padding-right:38px}\n.km-secret-toggle{position:absolute;right:4px;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}\n.km-secret-toggle:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}\n.km-form-actions{display:flex;justify-content:flex-end;gap:8px}\n.km-rename{display:flex;align-items:center;gap:4px;min-width:0;flex:1 1 auto;max-width:100%}\n.km-rename-input{flex:1 1 auto;width:auto;min-width:60px;padding:4px 7px;font-size:12.5px}\n.km-inline-error{padding:7px 9px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary) 35%,var(--dsw-alias-border-l1));border-radius:7px;background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 8%,transparent);color:var(--dsw-alias-state-error-primary);font-size:12px;word-break:break-word}\n.km-key-error{grid-column:1/-1;margin-top:-2px}\n.km-notice{display:flex;align-items:flex-start;gap:8px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-size:12px}\n.km-notice-icon{flex:0 0 auto;margin-top:1px}\n.km-notice-warn{border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 32%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 7%,transparent)}\n.km-notice-error{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 32%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 7%,transparent)}\n.km-notice-body{display:flex;flex-direction:column;gap:2px;min-width:0}\n.km-notice-title{color:var(--dsw-alias-label-primary);font-weight:600;font-size:12px}\n.km-notice-text{line-height:1.5}\n.km-notice-action{flex:0 0 auto}\n.km-empty-keys{padding:12px;border:1px dashed var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-secondary);font-size:12px}\n.km-empty{display:flex;align-items:center;gap:8px;padding:14px;border:1px dashed var(--dsw-alias-border-l2);border-radius:10px;color:var(--dsw-alias-label-secondary);font-size:12.5px}\n.km-provider-foot{display:flex;justify-content:space-between;gap:10px;align-items:center;margin-top:10px;padding-top:9px;border-top:1px dashed var(--dsw-alias-border-l1);font-size:11px;min-height:15px}\n.km-settings{padding:14px}\n.km-settings-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:12px}\n.km-settings-title{font-size:14px;font-weight:650;margin:0 0 3px}\n.km-settings-intro{margin:0;color:var(--dsw-alias-label-secondary);font-size:12px}\n.km-settings-icon{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border-radius:9px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-brand-primary);border:1px solid var(--dsw-alias-border-l1)}\n.km-settings-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(0,1fr);gap:12px;align-items:start}\n.km-field-note{grid-column:1/-1}\n.km-policy-note{padding:9px 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-2)}\n.km-policy-title{display:flex;align-items:center;gap:6px;font-size:11.5px;font-weight:600;color:var(--dsw-alias-label-primary);margin-bottom:2px}\n.km-policy-note p{margin:0;font-size:11.5px;line-height:1.5}\n.km-settings-actions{display:flex;align-items:center;justify-content:flex-end;gap:10px;margin-top:12px}\n.km-saved{display:inline-flex;align-items:center;gap:4px;color:var(--dsw-alias-state-success-primary);font-size:11.5px}\n.km-banner{display:flex;align-items:flex-start;gap:8px;padding:9px 10px;border-radius:8px;margin:0 0 12px;font-size:12px;line-height:1.5;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1)}\n.km-banner-error{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 32%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 7%,transparent)}\n.km-banner-warn{border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 32%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 7%,transparent)}\n.km-banner-text{flex:1;min-width:0;word-break:break-word}\n.km-banner-close{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border:0;border-radius:5px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}\n.km-banner-close:hover{background:var(--dsw-alias-bg-layer-2)}\n.km-loading{display:flex;align-items:center;gap:8px;padding:20px 0;color:var(--dsw-alias-label-secondary);font-size:12.5px}\n.km-load-failure{display:flex;flex-direction:column;gap:10px;align-items:flex-start;padding:18px;border:1px dashed var(--dsw-alias-border-l2);border-radius:10px;color:var(--dsw-alias-label-secondary);font-size:12.5px}\n.km-empty-title{font-weight:600;color:var(--dsw-alias-label-primary)}\n.km-footer{display:flex;align-items:flex-start;gap:6px;padding-top:4px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1.5}\n.km-muted{color:var(--dsw-alias-label-secondary)}\n.km-warn{color:var(--dsw-alias-state-warn-primary)}\n.km-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}\n.km-truncate{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.km-spin{animation:km-spin 1s linear infinite}\n.km-models-note{display:flex;align-items:center;gap:7px;flex-wrap:wrap;margin:8px 0 0;padding:7px 9px;border:1px solid color-mix(in srgb,var(--dsw-alias-brand-primary) 24%,var(--dsw-alias-border-l1));border-radius:7px;background:color-mix(in srgb,var(--dsw-alias-brand-primary) 6%,transparent);font-size:11.5px;color:var(--dsw-alias-label-primary)}\n.km-models-note-warn{border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 30%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 7%,transparent)}\n.km-models-note-text{font-weight:600}\n.km-models-note-hint{color:var(--dsw-alias-label-secondary);font-weight:400}\n.km-modal-backdrop{position:fixed;inset:0;z-index:80;display:flex;align-items:center;justify-content:center;padding:24px;background:color-mix(in srgb,var(--dsw-alias-label-primary) 26%,transparent)}\n.km-modal{width:min(440px,100%);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-overlay);padding:16px;box-shadow:0 18px 48px color-mix(in srgb,var(--dsw-alias-label-primary) 20%,transparent)}\n.km-modal-title{font-weight:650;font-size:14px;margin-bottom:7px}\n.km-modal-body{color:var(--dsw-alias-label-secondary);font-size:12.5px}\n.km-modal-copy{margin:0;line-height:1.55}\n.km-login-flow{display:flex;flex-direction:column;gap:10px}\n.km-login-pending{display:flex;align-items:center;gap:8px;color:var(--dsw-alias-label-secondary);font-size:12.5px}\n.km-login-notices{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5;word-break:break-word}\n.km-login-link{color:var(--dsw-alias-state-business-primary);text-decoration:underline;text-underline-offset:2px}\n.km-login-link:hover{color:var(--dsw-alias-label-primary)}\n.km-login-prompt{display:flex;flex-direction:column;gap:10px;padding-top:2px;border-top:1px dashed var(--dsw-alias-border-l1)}\n.km-login-prompt-title{font-weight:600;font-size:12.5px;color:var(--dsw-alias-label-primary)}\n.km-modal-footer{display:flex;justify-content:flex-end;gap:8px;margin-top:16px}\n@keyframes km-spin{to{transform:rotate(360deg)}}\n@media(max-width:720px){\n .km-summary{grid-template-columns:repeat(2,minmax(0,1fr))}\n .km-key-row,.km-account-row{grid-template-columns:minmax(0,1fr) 32px;gap:6px}\n .km-key-status,.km-key-last{grid-column:1;padding-left:0}\n .km-key-actions{grid-column:2;grid-row:1}\n .km-settings-grid{grid-template-columns:1fr}\n .km-form-grid{grid-template-columns:1fr}\n .km-provider-side .km-status{display:none}\n .km-embedded-head{flex-direction:column;gap:10px}\n .km-embedded-actions{padding-top:0}\n}\n";
2
+ export declare const CSS = "\n.km-page{max-width:920px;margin:0 auto;padding:0 0 8px;color:var(--dsw-alias-label-primary);font-size:13.5px;line-height:1.5}\n.km-embedded-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin:0 0 16px}\n.km-summary{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin:0 0 16px;flex:1;min-width:0}\n.km-embedded-head .km-summary{margin:0}\n.km-embedded-actions{display:flex;gap:8px;flex-shrink:0;padding-top:2px}\n.km-summary-line{color:var(--dsw-alias-label-secondary);font-size:12.5px;line-height:1.5}\n.km-stat{display:flex;flex-direction:column;gap:2px;padding:11px 14px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1)}\n.km-stat strong{font-size:19px;line-height:1.15;font-weight:650;font-variant-numeric:tabular-nums}\n.km-stat span{color:var(--dsw-alias-label-secondary);font-size:12px}\n.km-filters{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 12px}\n.km-chip{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;border:1px solid var(--dsw-alias-border-l1);border-radius:999px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font:inherit;font-size:12.5px;cursor:pointer}\n.km-chip:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}\n.km-chip:focus-visible{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-state-business-primary) 20%,transparent)}\n.km-chip-on{border-color:color-mix(in srgb,var(--dsw-alias-state-business-primary) 55%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 12%,var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-label-primary)}\n.km-chip-count{font-size:11.5px;color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}\n.km-chip-on .km-chip-count{color:inherit}\n.km-search{position:relative;display:flex;align-items:center;margin:0 0 20px}\n.km-search-icon{position:absolute;left:12px;color:var(--dsw-alias-label-secondary);pointer-events:none}\n.km-search-input{width:100%;box-sizing:border-box;padding:10px 38px 10px 36px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;outline:none}\n.km-search-input:focus{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-state-business-primary) 20%,transparent)}\n.km-search-clear{position:absolute;right:8px;display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}\n.km-search-clear:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}\n.km-section{margin:0 0 22px}\n.km-section-head{display:flex;align-items:baseline;gap:8px;margin:0 0 9px}\n.km-section-title{font-size:14px;font-weight:650;margin:0;letter-spacing:.01em}\n.km-section-count{font-size:12px;color:var(--dsw-alias-label-secondary)}\n.km-provider-list{display:flex;flex-direction:column;gap:8px}\n.km-show-more{display:flex;justify-content:center;padding:8px 0 0}\n.km-card{border:1px solid var(--dsw-alias-border-l1);border-radius:12px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}\n.km-provider-managed{border-color:color-mix(in srgb,var(--dsw-alias-brand-primary) 26%,var(--dsw-alias-border-l1))}\n.km-provider-open{overflow:visible}\n.km-provider-head{display:flex;align-items:center;gap:12px;width:100%;padding:12px 14px;border:0;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}\n.km-provider-head:hover{background:color-mix(in srgb,var(--dsw-alias-label-primary) 3%,transparent)}\n.km-avatar{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;flex:0 0 32px;border-radius:9px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);font-weight:650;font-size:13px;color:var(--dsw-alias-label-secondary)}\n.km-provider-main{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1}\n.km-provider-title-line{display:flex;align-items:center;gap:6px;flex-wrap:wrap;min-width:0}\n.km-provider-name{font-weight:650;font-size:13.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:360px}\n.km-provider-meta{display:flex;align-items:center;gap:7px;flex-wrap:wrap;color:var(--dsw-alias-label-secondary);font-size:11.5px;min-width:0}\n.km-provider-side{display:flex;align-items:center;gap:9px;flex-shrink:0}\n.km-count{font-size:11.5px;color:var(--dsw-alias-label-secondary);white-space:nowrap}\n.km-chevron{color:var(--dsw-alias-label-secondary);transition:transform .16s ease}\n.km-chevron-open{transform:rotate(180deg)}\n.km-provider-body{border-top:1px solid var(--dsw-alias-border-l1);padding:13px 14px 14px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 40%,transparent)}\n.km-adopt{display:flex;flex-direction:column;gap:12px}\n.km-adopt-copy{display:flex;flex-direction:column;gap:5px;color:var(--dsw-alias-label-secondary);font-size:12.5px}\n.km-adopt-title{color:var(--dsw-alias-label-primary);font-size:12.5px}\n.km-adopt-actions{display:flex;gap:8px;align-items:center}\n.km-meta-line{display:flex;gap:6px;align-items:baseline;flex-wrap:wrap;font-size:11.5px}\n.km-managed-body{display:flex;flex-direction:column;gap:10px}\n.km-managed-toolbar{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;flex-wrap:wrap}\n.km-managed-summary{display:flex;flex-direction:column;gap:2px;min-width:0;font-size:11.5px}\n.km-managed-actions{display:flex;gap:6px;align-items:center;flex-shrink:0}\n.km-keys{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l1);border-radius:9px;background:var(--dsw-alias-bg-layer-1);overflow:visible}\n.km-key-row{position:relative;display:grid;grid-template-columns:minmax(0,1.35fr) minmax(150px,.8fr) minmax(150px,.9fr) 32px;gap:10px;align-items:center;padding:10px 11px;border-top:1px solid var(--dsw-alias-border-l1)}\n.km-key-row:first-child{border-top:0}\n.km-account-row{position:relative;display:grid;grid-template-columns:minmax(0,1.35fr) minmax(150px,.8fr) minmax(150px,.9fr) 32px;gap:10px;align-items:center;padding:10px 11px 10px 14px;border-top:1px solid var(--dsw-alias-border-l1)}\n.km-account-row:first-child{border-top:0}\n.km-account-row::before{content:'';position:absolute;left:0;top:12px;bottom:12px;width:2px;border-radius:1px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 55%,transparent)}\n.km-account-glyph{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;flex:0 0 22px;border-radius:50%;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 14%,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-state-business-primary)}\n.km-key-identity{min-width:0;display:flex;flex-direction:column;gap:2px}\n.km-key-alias-line{display:flex;align-items:center;gap:6px;min-width:0}\n.km-key-alias{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px}\n.km-key-sub{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--dsw-alias-label-secondary);flex-wrap:wrap}\n.km-key-status{display:flex;flex-direction:column;gap:2px;align-items:flex-start}\n.km-key-last{display:flex;flex-direction:column;gap:2px;min-width:0}\n.km-key-time{font-size:11.5px;color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}\n.km-key-time.km-warn{color:var(--dsw-alias-state-warn-primary)}\n.km-key-actions{position:relative;display:flex;justify-content:flex-end}\n.km-status{display:inline-flex;align-items:center;gap:5px;padding:2px 7px;border-radius:999px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);font-size:11px;white-space:nowrap;color:var(--dsw-alias-label-secondary)}\n.km-status-success{color:var(--dsw-alias-state-success-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-success-primary) 26%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 8%,transparent)}\n.km-status-warn{color:var(--dsw-alias-state-warn-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 30%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 8%,transparent)}\n.km-status-error{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 30%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 8%,transparent)}\n.km-status-idle{color:var(--dsw-alias-state-idle-primary)}\n.km-dot{display:inline-block;width:6px;height:6px;border-radius:50%;flex:0 0 6px;background:var(--dsw-alias-state-idle-primary)}\n.km-dot-success{background:var(--dsw-alias-state-success-primary)}\n.km-dot-warn{background:var(--dsw-alias-state-warn-primary)}\n.km-dot-error{background:var(--dsw-alias-state-error-primary)}\n.km-dot-idle{background:var(--dsw-alias-state-idle-primary)}\n.km-dot-sep{color:var(--dsw-alias-label-secondary)}\n.km-tag{display:inline-flex;align-items:center;padding:1px 6px;border-radius:5px;font-size:10.5px;line-height:1.45;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);white-space:nowrap}\n.km-tag-accent{color:var(--dsw-alias-state-business-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-business-primary) 34%,var(--dsw-alias-border-l1));background:var(--dsw-alias-state-business-tertiary)}\n.km-tag-warn{color:var(--dsw-alias-state-warn-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 30%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 8%,transparent)}\n.km-icon-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:1px solid transparent;border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;flex:0 0 auto}\n.km-icon-btn:hover{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary)}\n.km-icon-btn:disabled{opacity:.5;cursor:default}\n.km-menu{position:absolute;top:32px;right:0;z-index:20;min-width:138px;padding:4px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-overlay);box-shadow:0 7px 22px color-mix(in srgb,var(--dsw-alias-label-primary) 14%,transparent)}\n.km-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 9px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12.5px;text-align:left;cursor:pointer}\n.km-menu-item:hover{background:var(--dsw-alias-bg-layer-2)}\n.km-menu-item:disabled{opacity:.5;cursor:default}\n.km-menu-danger{color:var(--dsw-alias-state-error-primary)}\n.km-menu-sep{height:1px;margin:4px 2px;background:var(--dsw-alias-border-l1)}\n.km-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:7px 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:12.5px;font-weight:550;line-height:1.2;cursor:pointer;white-space:nowrap;transition:opacity .15s ease,background .15s ease}\n.km-btn:hover{background:var(--dsw-alias-bg-layer-2)}\n.km-btn:disabled{opacity:.5;cursor:default}\n.km-btn-primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-base);border-color:transparent}\n.km-btn-primary:hover{opacity:.9;background:var(--dsw-alias-label-primary)}\n.km-btn-ghost{background:transparent;border-color:transparent;color:var(--dsw-alias-label-secondary)}\n.km-btn-ghost:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}\n.km-btn-danger{background:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-bg-base);border-color:transparent}\n.km-btn-danger:hover{opacity:.9;background:var(--dsw-alias-state-error-primary)}\n.km-btn-small{padding:5px 8px;font-size:12px;border-radius:7px}\n.km-input{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;outline:none}\n.km-input:focus{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-state-business-primary) 18%,transparent)}\n.km-input:disabled{opacity:.6}\nselect.km-input{appearance:auto}\n.km-form{display:flex;flex-direction:column;gap:10px;padding:12px;border:1px solid var(--dsw-alias-border-l1);border-radius:9px;background:var(--dsw-alias-bg-layer-1)}\n.km-form-lead{display:flex;flex-direction:column;gap:2px}\n.km-form-title{font-weight:650;font-size:12.5px}\n.km-form-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.4fr);gap:10px}\n.km-field{display:flex;flex-direction:column;gap:5px;min-width:0}\n.km-field-wide{min-width:0}\n.km-field-label{font-size:11.5px;font-weight:550;color:var(--dsw-alias-label-secondary)}\n.km-field-hint{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.45}\n.km-secret{position:relative;display:flex;align-items:center}\n.km-secret-input{padding-right:38px}\n.km-secret-toggle{position:absolute;right:4px;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}\n.km-secret-toggle:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}\n.km-form-actions{display:flex;justify-content:flex-end;gap:8px}\n.km-rename{display:flex;align-items:center;gap:4px;min-width:0;flex:1 1 auto;max-width:100%}\n.km-rename-input{flex:1 1 auto;width:auto;min-width:60px;padding:4px 7px;font-size:12.5px}\n.km-inline-error{padding:7px 9px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary) 35%,var(--dsw-alias-border-l1));border-radius:7px;background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 8%,transparent);color:var(--dsw-alias-state-error-primary);font-size:12px;word-break:break-word}\n.km-key-error{grid-column:1/-1;margin-top:-2px}\n.km-notice{display:flex;align-items:flex-start;gap:8px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-size:12px}\n.km-notice-icon{flex:0 0 auto;margin-top:1px}\n.km-notice-warn{border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 32%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 7%,transparent)}\n.km-notice-error{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 32%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 7%,transparent)}\n.km-notice-body{display:flex;flex-direction:column;gap:2px;min-width:0}\n.km-notice-title{color:var(--dsw-alias-label-primary);font-weight:600;font-size:12px}\n.km-notice-text{line-height:1.5}\n.km-notice-action{flex:0 0 auto}\n.km-empty-keys{padding:12px;border:1px dashed var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-secondary);font-size:12px}\n.km-empty{display:flex;align-items:center;gap:8px;padding:14px;border:1px dashed var(--dsw-alias-border-l2);border-radius:10px;color:var(--dsw-alias-label-secondary);font-size:12.5px}\n.km-provider-foot{display:flex;justify-content:space-between;gap:10px;align-items:center;margin-top:10px;padding-top:9px;border-top:1px dashed var(--dsw-alias-border-l1);font-size:11px;min-height:15px}\n.km-settings{padding:14px}\n.km-settings-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:12px}\n.km-settings-title{font-size:14px;font-weight:650;margin:0 0 3px}\n.km-settings-intro{margin:0;color:var(--dsw-alias-label-secondary);font-size:12px}\n.km-settings-icon{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border-radius:9px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-brand-primary);border:1px solid var(--dsw-alias-border-l1)}\n.km-settings-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(0,1fr);gap:12px;align-items:start}\n.km-field-note{grid-column:1/-1}\n.km-policy-note{padding:9px 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-2)}\n.km-policy-title{display:flex;align-items:center;gap:6px;font-size:11.5px;font-weight:600;color:var(--dsw-alias-label-primary);margin-bottom:2px}\n.km-policy-note p{margin:0;font-size:11.5px;line-height:1.5}\n.km-settings-actions{display:flex;align-items:center;justify-content:flex-end;gap:10px;margin-top:12px}\n.km-saved{display:inline-flex;align-items:center;gap:4px;color:var(--dsw-alias-state-success-primary);font-size:11.5px}\n.km-banner{display:flex;align-items:flex-start;gap:8px;padding:9px 10px;border-radius:8px;margin:0 0 12px;font-size:12px;line-height:1.5;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1)}\n.km-banner-error{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 32%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 7%,transparent)}\n.km-banner-warn{border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 32%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 7%,transparent)}\n.km-banner-text{flex:1;min-width:0;word-break:break-word}\n.km-banner-close{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border:0;border-radius:5px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}\n.km-banner-close:hover{background:var(--dsw-alias-bg-layer-2)}\n.km-loading{display:flex;align-items:center;gap:8px;padding:20px 0;color:var(--dsw-alias-label-secondary);font-size:12.5px}\n.km-load-failure{display:flex;flex-direction:column;gap:10px;align-items:flex-start;padding:18px;border:1px dashed var(--dsw-alias-border-l2);border-radius:10px;color:var(--dsw-alias-label-secondary);font-size:12.5px}\n.km-empty-title{font-weight:600;color:var(--dsw-alias-label-primary)}\n.km-footer{display:flex;align-items:flex-start;gap:6px;padding-top:4px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1.5}\n.km-muted{color:var(--dsw-alias-label-secondary)}\n.km-warn{color:var(--dsw-alias-state-warn-primary)}\n.km-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}\n.km-truncate{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.km-spin{animation:km-spin 1s linear infinite}\n.km-models-note{display:flex;align-items:center;gap:7px;flex-wrap:wrap;margin:8px 0 0;padding:7px 9px;border:1px solid color-mix(in srgb,var(--dsw-alias-brand-primary) 24%,var(--dsw-alias-border-l1));border-radius:7px;background:color-mix(in srgb,var(--dsw-alias-brand-primary) 6%,transparent);font-size:11.5px;color:var(--dsw-alias-label-primary)}\n.km-models-note-warn{border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 30%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 7%,transparent)}\n.km-models-note-text{font-weight:600}\n.km-models-note-hint{color:var(--dsw-alias-label-secondary);font-weight:400}\n.km-modal-backdrop{position:fixed;inset:0;z-index:80;display:flex;align-items:center;justify-content:center;padding:24px;background:color-mix(in srgb,var(--dsw-alias-label-primary) 26%,transparent)}\n.km-modal{width:min(440px,100%);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-overlay);padding:16px;box-shadow:0 18px 48px color-mix(in srgb,var(--dsw-alias-label-primary) 20%,transparent)}\n.km-modal-title{font-weight:650;font-size:14px;margin-bottom:7px}\n.km-modal-body{color:var(--dsw-alias-label-secondary);font-size:12.5px}\n.km-modal-copy{margin:0;line-height:1.55}\n.km-login-flow{display:flex;flex-direction:column;gap:10px}\n.km-login-pending{display:flex;align-items:center;gap:8px;color:var(--dsw-alias-label-secondary);font-size:12.5px}\n.km-login-notices{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5;word-break:break-word}\n.km-login-link{color:var(--dsw-alias-state-business-primary);text-decoration:underline;text-underline-offset:2px}\n.km-login-link:hover{color:var(--dsw-alias-label-primary)}\n.km-login-prompt{display:flex;flex-direction:column;gap:10px;padding-top:2px;border-top:1px dashed var(--dsw-alias-border-l1)}\n.km-login-prompt-title{font-weight:600;font-size:12.5px;color:var(--dsw-alias-label-primary)}\n.km-modal-footer{display:flex;justify-content:flex-end;gap:8px;margin-top:16px}\n@keyframes km-spin{to{transform:rotate(360deg)}}\n@media(max-width:720px){\n .km-summary{grid-template-columns:repeat(2,minmax(0,1fr))}\n .km-key-row,.km-account-row{grid-template-columns:minmax(0,1fr) 32px;gap:6px}\n .km-key-status,.km-key-last{grid-column:1;padding-left:0}\n .km-key-actions{grid-column:2;grid-row:1}\n .km-settings-grid{grid-template-columns:1fr}\n .km-form-grid{grid-template-columns:1fr}\n .km-provider-side .km-status{display:none}\n .km-embedded-head{flex-direction:column;gap:10px}\n .km-embedded-actions{padding-top:0}\n}\n";
@@ -1,6 +1,20 @@
1
1
  /** Keys Manager browser components, rendered from the plugin-detail card and
2
2
  * the Models provider-card extension slot. */
3
3
  import React from 'react';
4
+ import type { ProviderView } from './types';
5
+ /**
6
+ * True when this route can offer OAuth sign-in right now.
7
+ *
8
+ * C6-2: the catalog's `nativeAuth.oauth` flag says the provider supports
9
+ * OAuth upstream; the host's `accountSupported` verdict says this Harness
10
+ * build has a flow it can actually begin. The host verdict is authoritative
11
+ * whenever present, so a nativeAuth.oauth route without a pi-ai flow hides
12
+ * Add account instead of opening a sign-in that would fail. Older host
13
+ * snapshots that predate the field keep the catalog-only behaviour.
14
+ */
15
+ export declare function oauthCapable(provider: ProviderView): boolean;
16
+ /** Where the row tag says a route came from, including legacy snapshots. */
17
+ export declare function originOf(provider: ProviderView): 'dsh' | 'plugin' | 'custom';
4
18
  declare function KeysManagerBody(): React.ReactElement;
5
19
  interface KeysManagerCardProps {
6
20
  view?: 'summary' | 'page';
package/lib/client.js CHANGED
@@ -44,9 +44,20 @@ var zh = {
44
44
  title: "\u5BC6\u94A5\u7BA1\u7406",
45
45
  subtitle: "\u4E3A\u540C\u4E00\u4E2A Provider \u914D\u7F6E\u591A\u4E2A API Key\u3002\u5355\u4F1A\u8BDD\u56FA\u5B9A\u4F7F\u7528\u4E00\u4E2A Key\uFF0C\u65B0\u4F1A\u8BDD\u8F6E\u6362\uFF1B\u5931\u8D25\u540E\u81EA\u52A8\u51B7\u5374\u5E76\u6062\u590D\u3002",
46
46
  search: "\u641C\u7D22 Provider\uFF08\u540D\u79F0\u3001ID \u6216\u73AF\u5883\u53D8\u91CF\uFF09",
47
+ filterAll: "\u5168\u90E8",
48
+ filterBuiltin: "\u5185\u7F6E",
49
+ filterPlugin: "\u63D2\u4EF6",
50
+ filterOauth: "\u652F\u6301 OAuth",
51
+ filterTransport: "\u4F20\u8F93",
52
+ filterGroup: "\u7B5B\u9009",
53
+ // Unattributed requests (I-000120, D8): the compass's fixed copy, shared
54
+ // with the filter plan's dictionary task — one pair, never a second.
55
+ unattributed: "{n} \u65E0\u5F52\u5C5E",
56
+ unattributedHint: "\u8BE5\u8DEF\u7531\u5728\u5F52\u5C5E\u8BFB\u53D6\u5C1A\u672A\u843D\u5730\uFF08\u6216\u5DF2\u5931\u8D25\uFF09\u671F\u95F4\u5E94\u7B54\u7684\u6D41\u5C1D\u8BD5\u6570\uFF1B\u5931\u8D25\u8F6C\u79FB\u91CD\u8BD5\u4F1A\u5404\u8BA1\u4E00\u6B21\uFF0C\u6545\u5355\u4E2A\u8BF7\u6C42\u53EF\u80FD\u88AB\u8BA1\u591A\u6B21\u3002\u8BFB\u53D6\u5B8C\u6210\u5F52\u5C5E\u540E\u6E05\u96F6\u3002",
47
57
  managed: "\u5DF2\u63A5\u7BA1",
48
58
  available: "\u53EF\u6DFB\u52A0 Providers",
49
59
  managedEmpty: "\u8FD8\u6CA1\u6709\u63A5\u7BA1\u7684 Provider\u3002\u4ECE\u4E0B\u65B9\u9009\u62E9\u4E00\u4E2A\u5F00\u59CB\u3002",
60
+ managedFilteredEmpty: "\u5F53\u524D\u7B5B\u9009\u4E0B\u6CA1\u6709\u5DF2\u63A5\u7BA1\u7684 Provider\u3002",
50
61
  availableEmpty: "\u6CA1\u6709\u5339\u914D\u7684 Provider\u3002",
51
62
  keys: "\u5BC6\u94A5",
52
63
  healthy: "\u53EF\u7528",
@@ -194,7 +205,8 @@ var zh = {
194
205
  deleteAccountBodyNoKeys: "\u8FD9\u4F1A\u4ECE\u51ED\u8BC1\u5E93\u4E2D\u5220\u9664\u8BE5\u8D26\u6237\u4FDD\u5B58\u7684 OAuth \u6388\u6743\u3002\u8BE5\u8DEF\u7531\u6CA1\u6709 API-key \u6C60\uFF0C\u5220\u9664\u540E\u5C06\u505C\u6B62\u670D\u52A1\uFF0C\u76F4\u5230\u91CD\u65B0\u767B\u5F55\u8D26\u6237\u3002",
195
206
  // Transport accounts (llm-omp-transports/<route>): managed beside the
196
207
  // pi-ai pool, so the copy never promises a key-pool fallback.
197
- transportAccounts: "\u4F20\u8F93\u8D26\u6237",
208
+ transportEmpty: "\u8FD8\u6CA1\u6709\u4F20\u8F93\u8D26\u6237\u3002",
209
+ transportProviderHint: "\u8BE5\u8DEF\u7531\u7531\u4F20\u8F93\u63D2\u4EF6\u63D0\u4F9B\uFF0C\u51ED\u8BC1\u5C31\u5728\u672C\u5361\u7247\u4E2D\u7BA1\u7406\u3002",
198
210
  transportTag: "\u4F20\u8F93",
199
211
  transportSignIn: "\u767B\u5F55",
200
212
  transportExpires: "\u6709\u6548\u671F\u81F3 {time}",
@@ -210,9 +222,18 @@ var en = {
210
222
  title: "Keys Manager",
211
223
  subtitle: "Give one provider several API keys. A session keeps one key; new sessions rotate; failures cool down and recover automatically.",
212
224
  search: "Search providers by name, id, or environment variable",
225
+ filterAll: "All",
226
+ filterBuiltin: "Built-in",
227
+ filterPlugin: "Plugin",
228
+ filterOauth: "OAuth-capable",
229
+ filterTransport: "Transport",
230
+ filterGroup: "Filters",
231
+ unattributed: "{n} unattributed",
232
+ unattributedHint: "Stream attempts this route served with no account owner because its ownership read had not landed (or had failed). A failover retry is a new attempt, so one request can be counted more than once. The count clears when a read attributes the route.",
213
233
  managed: "Managed",
214
234
  available: "Available providers",
215
235
  managedEmpty: "No managed providers yet. Adopt one below to start.",
236
+ managedFilteredEmpty: "No managed providers match the current filter.",
216
237
  availableEmpty: "No matching providers.",
217
238
  keys: "keys",
218
239
  healthy: "healthy",
@@ -359,7 +380,8 @@ var en = {
359
380
  deleteAccountBody: "This removes the account\u2019s stored OAuth grant from the credential store. If it is the active account, the provider route stops serving it and falls back to its API-key pool; the provider stays managed.",
360
381
  deleteAccountBodyNoKeys: "This removes the account\u2019s stored OAuth grant from the credential store. This route has no API-key pool, so it stops serving requests until an account is signed in again.",
361
382
  // Transport accounts — same values as locale/en.json.
362
- transportAccounts: "Transport accounts",
383
+ transportEmpty: "No transport accounts yet.",
384
+ transportProviderHint: "Served by a transport plugin: its credential is managed inside this card.",
363
385
  transportTag: "Transport",
364
386
  transportSignIn: "Sign in",
365
387
  transportExpires: "Expires {time}",
@@ -386,6 +408,13 @@ var CSS = `
386
408
  .km-stat{display:flex;flex-direction:column;gap:2px;padding:11px 14px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1)}
387
409
  .km-stat strong{font-size:19px;line-height:1.15;font-weight:650;font-variant-numeric:tabular-nums}
388
410
  .km-stat span{color:var(--dsw-alias-label-secondary);font-size:12px}
411
+ .km-filters{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 12px}
412
+ .km-chip{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;border:1px solid var(--dsw-alias-border-l1);border-radius:999px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font:inherit;font-size:12.5px;cursor:pointer}
413
+ .km-chip:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
414
+ .km-chip:focus-visible{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-state-business-primary) 20%,transparent)}
415
+ .km-chip-on{border-color:color-mix(in srgb,var(--dsw-alias-state-business-primary) 55%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 12%,var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-label-primary)}
416
+ .km-chip-count{font-size:11.5px;color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}
417
+ .km-chip-on .km-chip-count{color:inherit}
389
418
  .km-search{position:relative;display:flex;align-items:center;margin:0 0 20px}
390
419
  .km-search-icon{position:absolute;left:12px;color:var(--dsw-alias-label-secondary);pointer-events:none}
391
420
  .km-search-input{width:100%;box-sizing:border-box;padding:10px 38px 10px 36px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;outline:none}
@@ -873,6 +902,37 @@ function providerInitial(provider) {
873
902
  function oauthCapable(provider) {
874
903
  return provider.nativeAuth?.oauth === true && provider.accountSupported !== false;
875
904
  }
905
+ function originOf(provider) {
906
+ return provider.origin ?? (provider.extra ? "plugin" : "dsh");
907
+ }
908
+ var FILTER_CHIPS = [
909
+ { value: "all", label: "filterAll" },
910
+ { value: "builtin", label: "filterBuiltin" },
911
+ { value: "plugin", label: "filterPlugin" },
912
+ { value: "oauth", label: "filterOauth" },
913
+ { value: "transport", label: "filterTransport" }
914
+ ];
915
+ function matchesFilter(provider, filter) {
916
+ switch (filter) {
917
+ case "transport":
918
+ return provider.transport === true;
919
+ case "builtin":
920
+ return provider.transport !== true && originOf(provider) === "dsh";
921
+ case "plugin":
922
+ return provider.transport !== true && originOf(provider) === "plugin";
923
+ case "oauth":
924
+ return provider.transport !== true && oauthCapable(provider);
925
+ default:
926
+ return true;
927
+ }
928
+ }
929
+ function orphanTransportAccounts(providers, transportAccounts) {
930
+ return transportAccounts.filter((account) => !providers.some((provider) => provider.transport === true && provider.provider === account.id));
931
+ }
932
+ function managedEntryCount(providers, transportAccounts) {
933
+ const cards = providers.filter((provider) => provider.managed || provider.transport === true).length;
934
+ return cards + orphanTransportAccounts(providers, transportAccounts).length;
935
+ }
876
936
  function accountIdentityOf(key, provider) {
877
937
  const oauth = key.oauth;
878
938
  if (oauth !== null && oauth !== void 0) {
@@ -1648,7 +1708,7 @@ function TransportAccountRow({ account }) {
1648
1708
  }) : null
1649
1709
  );
1650
1710
  }
1651
- function ProviderCard({ provider, expanded, onToggle, now }) {
1711
+ function ProviderCard({ provider, expanded, onToggle, now, transportAccount }) {
1652
1712
  const runtime = useRuntime();
1653
1713
  const t = useT();
1654
1714
  const [adding, setAdding] = import_react2.default.useState(false);
@@ -1663,11 +1723,15 @@ function ProviderCard({ provider, expanded, onToggle, now }) {
1663
1723
  const keyCount = provider.keyCount;
1664
1724
  const warning = provider.warning;
1665
1725
  const isExtra = provider.extra;
1666
- const origin = provider.origin ?? (provider.extra ? "plugin" : "dsh");
1667
- const originLabel = origin === "plugin" ? t("pluginBuiltin") : origin === "dsh" ? t("dshBuiltin") : t("customProvider");
1726
+ const transport = provider.transport === true;
1727
+ const origin = originOf(provider);
1728
+ const originLabel = transport || origin === "plugin" ? t("pluginBuiltin") : origin === "dsh" ? t("dshBuiltin") : t("customProvider");
1668
1729
  const providerMeta = [
1669
1730
  provider.provider,
1670
- provider.active ? t("active") : provider.configured ? t("configured") : t("notConfigured"),
1731
+ // `active`/`configured` are read off the route's profile or the host's
1732
+ // provider list, neither of which describes a transport route: its state
1733
+ // is the account row it carries (signed out when the host lists none).
1734
+ transport ? t(transportStatusKey(transportAccount?.status ?? "signed-out")) : provider.active ? t("active") : provider.configured ? t("configured") : t("notConfigured"),
1671
1735
  provider.api ?? null
1672
1736
  ].filter((value) => Boolean(value));
1673
1737
  async function adopt() {
@@ -1737,8 +1801,9 @@ function ProviderCard({ provider, expanded, onToggle, now }) {
1737
1801
  { className: "km-provider-title-line" },
1738
1802
  h("span", { className: "km-provider-name" }, provider.displayName || provider.provider),
1739
1803
  h("span", { className: "km-tag" }, originLabel),
1804
+ transport ? h("span", { className: "km-tag km-tag-accent" }, t("transportTag")) : null,
1740
1805
  managed ? h("span", { className: "km-tag km-tag-accent" }, t("managedBadge")) : null,
1741
- !managed && !provider.supported && !accountAdoptable ? h("span", { className: "km-tag km-tag-warn" }, t("unsupported")) : null
1806
+ !transport && !managed && !provider.supported && !accountAdoptable ? h("span", { className: "km-tag km-tag-warn" }, t("unsupported")) : null
1742
1807
  ),
1743
1808
  h(
1744
1809
  "span",
@@ -1752,7 +1817,14 @@ function ProviderCard({ provider, expanded, onToggle, now }) {
1752
1817
  h(
1753
1818
  "span",
1754
1819
  { className: "km-provider-side" },
1755
- managed ? h("span", { className: "km-count" }, countText(t, keyCount, "providerKeyOne", "providerKeys")) : h("span", { className: "km-count km-muted" }, adoptable ? t("unmanaged") : t("unsupported")),
1820
+ // I-000120 / D8: requests this route served without an attributed
1821
+ // account. Rendered only above zero, and the visible label carries the
1822
+ // count so it is the accessible name; the hint stays a description.
1823
+ (provider.unattributedRequests ?? 0) > 0 ? h("span", {
1824
+ className: "km-tag km-tag-warn km-unattributed",
1825
+ title: t("unattributedHint")
1826
+ }, t("unattributed", { n: provider.unattributedRequests ?? 0 })) : null,
1827
+ transport ? null : managed ? h("span", { className: "km-count" }, countText(t, keyCount, "providerKeyOne", "providerKeys")) : h("span", { className: "km-count km-muted" }, adoptable ? t("unmanaged") : t("unsupported")),
1756
1828
  managed ? h(StatusPill, { status: provider.status, t }) : null,
1757
1829
  h(Icon, { name: "chevron", size: 16, className: cx("km-chevron", expanded && "km-chevron-open") })
1758
1830
  )
@@ -1762,7 +1834,9 @@ function ProviderCard({ provider, expanded, onToggle, now }) {
1762
1834
  { className: "km-provider-body" },
1763
1835
  error !== null ? h("div", { className: "km-inline-error", role: "alert" }, error) : null,
1764
1836
  warning !== void 0 || broken ? h(Notice, { tone: "warn", title: t("warning") }, broken ? t("bindingBroken") : warning) : null,
1765
- !managed ? h(
1837
+ // A transport route has no adopt step and no key pool: its whole
1838
+ // credential surface is the one account row its own plugin serves.
1839
+ transport ? transportAccount !== void 0 ? h(TransportAccountRow, { account: transportAccount }) : h("div", { className: "km-muted" }, t("transportProviderHint")) : !managed ? h(
1766
1840
  "div",
1767
1841
  { className: "km-adopt" },
1768
1842
  h(
@@ -2178,6 +2252,7 @@ function KeysManagerBody() {
2178
2252
  const state = snapshot.state;
2179
2253
  const now = snapshot.now;
2180
2254
  const [query, setQuery] = import_react2.default.useState("");
2255
+ const [filter, setFilter] = import_react2.default.useState("all");
2181
2256
  const [showAllAvailable, setShowAllAvailable] = import_react2.default.useState(false);
2182
2257
  const [expanded, setExpanded] = import_react2.default.useState(() => /* @__PURE__ */ new Set());
2183
2258
  const loadedRef = import_react2.default.useRef(false);
@@ -2201,9 +2276,23 @@ function KeysManagerBody() {
2201
2276
  ].filter((value) => Boolean(value)).join(" ").toLowerCase();
2202
2277
  return haystack.includes(normalizedQuery);
2203
2278
  };
2204
- const managed = providers.filter((provider) => provider.managed && matches(provider));
2205
- const available = providers.filter((provider) => !provider.managed && matches(provider));
2279
+ const managed = providers.filter((provider) => (provider.managed || provider.transport === true) && matchesFilter(provider, filter) && matches(provider));
2280
+ const available = providers.filter((provider) => !provider.managed && provider.transport !== true && matchesFilter(provider, filter) && matches(provider));
2206
2281
  const visibleAvailable = showAllAvailable || normalizedQuery.length > 0 ? available : available.slice(0, 12);
2282
+ const matchesAccount = (account) => {
2283
+ if (normalizedQuery.length === 0) return true;
2284
+ const haystack = [account.id, account.scope, account.displayName].join(" ").toLowerCase();
2285
+ return haystack.includes(normalizedQuery);
2286
+ };
2287
+ const orphanAccounts = orphanTransportAccounts(providers, transportAccounts).filter(matchesAccount);
2288
+ const visibleOrphans = filter === "all" || filter === "transport" ? orphanAccounts : [];
2289
+ const filterCount = (value) => {
2290
+ const searchable = providers.filter(matches);
2291
+ if (value === "all") return searchable.length + orphanAccounts.length;
2292
+ if (value === "transport") return searchable.filter((provider) => provider.transport === true).length + orphanAccounts.length;
2293
+ return searchable.filter((provider) => matchesFilter(provider, value)).length;
2294
+ };
2295
+ const managedHiddenByChip = filter !== "all" && providers.some((provider) => provider.managed || provider.transport === true);
2207
2296
  function toggle(provider) {
2208
2297
  setExpanded((current) => {
2209
2298
  const next = new Set(current);
@@ -2242,7 +2331,7 @@ function KeysManagerBody() {
2242
2331
  h(
2243
2332
  "div",
2244
2333
  { className: "km-summary" },
2245
- h("div", { className: "km-stat" }, h("strong", null, loading ? "\u2014" : state?.managedCount ?? 0), h("span", null, t("managed"))),
2334
+ h("div", { className: "km-stat" }, h("strong", null, loading ? "\u2014" : managedEntryCount(providers, transportAccounts)), h("span", null, t("managed"))),
2246
2335
  h("div", { className: "km-stat" }, h("strong", null, loading ? "\u2014" : state?.keyCount ?? 0), h("span", null, t("keys"))),
2247
2336
  h("div", { className: "km-stat" }, h("strong", null, loading ? "\u2014" : state?.healthyKeyCount ?? 0), h("span", null, t("healthy"))),
2248
2337
  h("div", { className: "km-stat" }, h("strong", null, loading ? "\u2014" : state?.coolingKeyCount ?? 0), h("span", null, t("cooling")))
@@ -2259,6 +2348,26 @@ function KeysManagerBody() {
2259
2348
  }, h(Icon, { name: "refresh", size: 14 }), t("refresh"))
2260
2349
  )
2261
2350
  ),
2351
+ // Quick filter: single select, view state only, composed with the text
2352
+ // search below. Counts are derived per predicate from the snapshot.
2353
+ !loading && !failed ? h("div", {
2354
+ className: "km-filters",
2355
+ role: "group",
2356
+ "aria-label": t("filterGroup")
2357
+ }, FILTER_CHIPS.map(
2358
+ (chip) => h(
2359
+ "button",
2360
+ {
2361
+ key: chip.value,
2362
+ type: "button",
2363
+ className: cx("km-chip", `km-chip-${chip.value}`, filter === chip.value && "km-chip-on"),
2364
+ "aria-pressed": filter === chip.value,
2365
+ onClick: () => setFilter(chip.value)
2366
+ },
2367
+ h("span", { className: "km-chip-label" }, t(chip.label)),
2368
+ h("span", { className: "km-chip-count" }, filterCount(chip.value))
2369
+ )
2370
+ )) : null,
2262
2371
  h(
2263
2372
  "div",
2264
2373
  { className: "km-search" },
@@ -2288,15 +2397,29 @@ function KeysManagerBody() {
2288
2397
  "div",
2289
2398
  { className: "km-section-head" },
2290
2399
  h("h2", { className: "km-section-title" }, t("managed")),
2291
- h("span", { className: "km-section-count" }, managed.length)
2400
+ // What is below it, like the available section: the tile above keeps
2401
+ // the global total.
2402
+ h("span", { className: "km-section-count" }, managed.length + visibleOrphans.length)
2292
2403
  ),
2293
- managed.length === 0 ? h("div", { className: "km-empty" }, h(Icon, { name: "key", size: 20 }), h("span", null, t("managedEmpty"))) : h("div", { className: "km-provider-list" }, managed.map((provider) => h(ProviderCard, {
2404
+ // The placeholder only when the section renders neither a card nor a row
2405
+ // the active chip selects.
2406
+ managed.length === 0 && visibleOrphans.length === 0 ? h("div", { className: "km-empty" }, h(Icon, { name: "key", size: 20 }), h(
2407
+ "span",
2408
+ null,
2409
+ normalizedQuery.length > 0 ? t("noSearch") : filter === "transport" ? t("transportEmpty") : managedHiddenByChip ? t("managedFilteredEmpty") : t("managedEmpty")
2410
+ )) : null,
2411
+ managed.length > 0 ? h("div", { className: "km-provider-list" }, managed.map((provider) => h(ProviderCard, {
2294
2412
  key: provider.provider,
2295
2413
  provider,
2296
2414
  now,
2415
+ transportAccount: provider.transport === true ? transportAccounts.find((account) => account.id === provider.provider) : void 0,
2297
2416
  expanded: expanded.has(provider.provider),
2298
2417
  onToggle: () => toggle(provider.provider)
2299
- })))
2418
+ }))) : null,
2419
+ visibleOrphans.length > 0 ? h("div", { className: "km-provider-list" }, visibleOrphans.map((account) => h(TransportAccountRow, {
2420
+ key: account.scope,
2421
+ account
2422
+ }))) : null
2300
2423
  ) : null,
2301
2424
  !loading && !failed ? h(
2302
2425
  "section",
@@ -2324,20 +2447,6 @@ function KeysManagerBody() {
2324
2447
  h(Button, { variant: "ghost", size: "small", onClick: () => setShowAllAvailable(false) }, t("showLess"))
2325
2448
  ) : null
2326
2449
  ) : null,
2327
- !loading && !failed && transportAccounts.length > 0 ? h(
2328
- "section",
2329
- { className: "km-section" },
2330
- h(
2331
- "div",
2332
- { className: "km-section-head" },
2333
- h("h2", { className: "km-section-title" }, t("transportAccounts")),
2334
- h("span", { className: "km-section-count" }, transportAccounts.length)
2335
- ),
2336
- h("div", { className: "km-provider-list" }, transportAccounts.map((account) => h(TransportAccountRow, {
2337
- key: account.scope,
2338
- account
2339
- })))
2340
- ) : null,
2341
2450
  !loading && !failed ? h("section", { className: "km-section" }, h(SettingsCard)) : null,
2342
2451
  h(
2343
2452
  "footer",
@@ -2353,8 +2462,9 @@ function KeysManagerCard({ view }) {
2353
2462
  if (view === "summary") {
2354
2463
  const state = snapshot.state;
2355
2464
  if (state === null) return h("span", { className: "km-summary-line" }, snapshot.status === "error" ? t("loadError") : t("loading"));
2356
- if (state.managedCount === 0) return h("span", { className: "km-summary-line" }, t("summaryEmpty"));
2357
- return h("span", { className: "km-summary-line" }, t("summaryText", { providers: state.managedCount, keys: state.keyCount }));
2465
+ const managed = managedEntryCount(state.providers, state.transportAccounts ?? []);
2466
+ if (managed === 0) return h("span", { className: "km-summary-line" }, t("summaryEmpty"));
2467
+ return h("span", { className: "km-summary-line" }, t("summaryText", { providers: managed, keys: state.keyCount }));
2358
2468
  }
2359
2469
  return h(KeysManagerBody);
2360
2470
  }
package/lib/domain.d.ts CHANGED
@@ -170,6 +170,8 @@ export interface ProviderView {
170
170
  * is account-adoptable while carrying no key-pool credential.
171
171
  */
172
172
  accountSupported?: boolean;
173
+ /** True when another plugin serves this route on its own credential (not the pi-ai key pool). */
174
+ transport?: boolean;
173
175
  reason?: string;
174
176
  settingsNs: string;
175
177
  settingsPath: string[];
@@ -192,6 +194,12 @@ export interface ProviderView {
192
194
  status: ProviderStatus;
193
195
  keys: KeyView[];
194
196
  warning?: string;
197
+ /**
198
+ * Requests this route served before its ownership read decided which
199
+ * account serves it (`I-000120`, D8). Process-local and additive: an older
200
+ * snapshot without the field still renders, and zero keeps it absent.
201
+ */
202
+ unattributedRequests?: number;
195
203
  }
196
204
  /** Health state a transport account row renders. */
197
205
  export type TransportAccountStatus = 'active' | 'expired' | 'needs-reauth' | 'signed-out';