Skip to content

feat: make plugin-only Serve mode nodes discoverable/reachable by other mesh peers - #1192

Merged
michaelneale merged 12 commits into
Mesh-LLM:mainfrom
MahdiHedhli:pr/mesh-wide-plugin-serving
Aug 12, 2026
Merged

feat: make plugin-only Serve mode nodes discoverable/reachable by other mesh peers#1192
michaelneale merged 12 commits into
Mesh-LLM:mainfrom
MahdiHedhli:pr/mesh-wide-plugin-serving

Conversation

@MahdiHedhli

@MahdiHedhli MahdiHedhli commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Builds on #1191 (please review/merge that first — this branch is based on top of it).

Edit: this PR was substantially reworked after rebasing onto current main — see the update comment below.

A node whose only inference capacity comes from a plugin (e.g. one relaying to an external OpenAI-compatible server — no local model ever loads) is invisible to every other mesh peer, for two independent reasons, even though its own local ingress and gossip advertisement of its plugin's models already work correctly (both already generalized by this crate's "daemon model lifecycle reconciliation" work, ahead of this PR).

The gap

  1. Host-role eligibility. Other peers only consider a node as a route candidate for model X if hosts_for_model() finds it, which requires NodeRole::Host. That role is set in exactly one place in the whole crate — as a side effect of a local model finishing load. A plugin-only node's role never leaves Worker, so it's filtered out of every peer's routing consideration regardless of what its gossip payload advertises.
  2. Inbound tunnel routing. tunnel::Manager's configured http_port — where an inbound QUIC-tunneled request from a peer gets relayed — is, like the role above, only ever set as a side effect of a local model finishing load. If it's still 0, an inbound tunnel is silently dropped.

Fix

Both gaps have the same shape: a value that's already generalized everywhere else in run_auto()'s startup path (which now unconditionally binds the real API proxy and constructs the tunnel manager before any model resolution happens) except at these two specific call sites, which still only fire on local-model-load completion.

  • Set tunnel_mgr.set_http_port(api_port) unconditionally, immediately after the tunnel manager is constructed. The existing call sites in startup_handles.rs become redundant-but-harmless (same node, same api_port, for the process's lifetime).
  • Add spawn_plugin_host_role_watcher, a small background task (skipped for --client nodes) that polls the plugin manager's already-debounced health state on a short interval and promotes to Host { http_port: api_port } when it reports at least one model, demoting back to Worker when it stops. Only ever touches a Host role it set itself.

Both are two small, unconditional additions at a single call site in run_auto() — no new machinery, and (per the update below) no longer touching the passive-listener code path at all.

Full analysis in docs/plugins/plugin-hosted-mesh-serving-design.md, added by this PR.

Verification status — please read before merging

Compiles cleanly, clippy clean, full mesh-llm-host-runtime unit test suite passes (1910/1911; the one failure is a pre-existing, confirmed-flaky timing test unrelated to this change — see #1191 for detail). The actual mesh-wide behavior this PR targets — another peer discovering and successfully routing a request to a plugin-only node — has not yet been exercised by a live multi-peer test. I don't have a convenient multi-node test rig set up for this yet. Flagging this explicitly. Happy to do that verification before merge if a maintainer can point me at (or review with) a suitable multi-node setup.

Test plan

  • Compiles cleanly, clippy clean
  • Full unit test suite passes (see above)
  • Live multi-peer verification — not yet done, see above

Summary by CodeRabbit

  • New Features

    • Plugin-hosted inference models can now be served across the mesh by eligible worker nodes.
    • Node roles automatically adjust as plugin model availability changes.
    • Local model availability is preserved when plugin models become unavailable.
    • Inbound plugin traffic uses a health-check-based timeout for improved reliability.
  • Documentation

    • Added design documentation covering mesh-wide plugin model serving, current behavior, and deferred enhancements.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The runtime now tracks local-model and plugin-inference host-role claims. Non-client nodes monitor plugin inference models, configure tunnel access with the API port, and adjust host roles. Plugin envelope timeouts derive from the health-check interval.

Changes

Plugin-hosted mesh serving

Layer / File(s) Summary
Plugin envelope timeout
crates/mesh-llm-host-runtime/src/plugin/transport.rs
The prefix timeout now uses four times the health-check interval plus 30 seconds. Test parsing accepts configurable timeout values.
Host-role claim lifecycle
crates/mesh-llm-host-runtime/src/mesh/node.rs, crates/mesh-llm-host-runtime/src/mesh/mod.rs, crates/mesh-llm-host-runtime/src/mesh/tests/connections.rs
Node tracks local-model and plugin-inference claims. A node becomes Host when a claim is acquired and returns to Worker after the final claim is released.
Runtime plugin host-role wiring
crates/mesh-llm-host-runtime/src/runtime/*, docs/plugins/plugin-hosted-mesh-serving-design.md
Non-client startup sets the tunnel HTTP port and starts a plugin health watcher. Local-model publication and shutdown use host-role claims. The design record documents the routing gaps, fixes, deferred work, and reverted history.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant run_auto
  participant TunnelManager
  participant plugin_host_role
  participant Node
  run_auto->>TunnelManager: set API HTTP port
  run_auto->>plugin_host_role: start watcher
  plugin_host_role->>Node: poll plugin inference models
  Node-->>plugin_host_role: return model availability
  plugin_host_role->>Node: claim or release PluginInference role
Loading

Possibly related PRs

Suggested labels: experimental

Suggested reviewers: michaelneale, ndizazzo, i386

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: making plugin-only Serve mode nodes discoverable and reachable by mesh peers.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/mesh-llm-host-runtime/src/plugin/transport.rs`:
- Around line 27-29: The stalled-prefix unit test currently uses
PLUGIN_ENVELOPE_PREFIX_READ_TIMEOUT and therefore waits for the production
duration. Keep that constant derived from HEALTH_CHECK_INTERVAL_SECS for
production, but update the test-only read path to inject a short timeout or use
a paused Tokio clock so the test completes quickly while preserving the timeout
assertion.

In `@crates/mesh-llm-host-runtime/src/runtime/serving_surface.rs`:
- Around line 1506-1528: Update run_passive_listener_loop and
spawn_plugin_host_role_watcher so the watcher task handle is retained and
cancelled before every passive-loop exit, including local-model startup. Track
plugin-host and local-model host reasons independently, and only demote the node
when neither reason remains, preventing the detached watcher from changing
NodeRole after local startup takes ownership.

In `@docs/plugins/plugin-hosted-mesh-serving-design.md`:
- Around line 3-6: Update the design status section to mark the proposal as
implemented, reflecting the delivered local plugin routing, host-role promotion,
and passive tunnel-port wiring. Clearly list only the remaining follow-up items
as deferred work, and remove the outdated “not implemented” wording.

In `@sdk/kotlin/src/main/resources/mesh-llm/console/assets/cn-DzJZ_V3X.js`:
- Line 1: Replace the bundled configuration defaults in the exported asset’s
configuration object, specifically appVersion and storageNamespace, with release
build values before packaging. Prefer regenerating the Kotlin console assets
through the release build so these values are generated consistently rather than
manually editing the minified bundle.

In
`@sdk/kotlin/src/main/resources/mesh-llm/console/assets/PluginWebUiRoutePage-BR5NTyZb.js`:
- Line 1: Update the PluginWebUiRoutePage mount flow to handle the visible
configuration query state explicitly before attempting plugin mounting: show a
loading fallback while the configuration is pending and an error fallback when
it fails, rather than leaving local mount state idle with an empty host.
Preserve the existing successful configuration path, and rebuild the generated
bundle after changing the source.

In
`@sdk/kotlin/src/main/resources/mesh-llm/console/assets/reserve-policy-CNh2fVCc.js`:
- Line 1: Update the provider reorder implementation in N (the minified
ProviderOrderList) to support keyboard-accessible reordering, adding focusable
Move up and Move down controls or equivalent keyboard drag behavior that updates
providerOrder through onReorder while disabling unavailable moves at the list
boundaries. Implement the source change in ReservePolicySettingsForm.tsx and
rebuild the Kotlin console bundle so the generated asset includes the accessible
controls.

In `@sdk/kotlin/src/main/resources/mesh-llm/console/index.html`:
- Around line 109-114: Remove the Google Fonts preconnect and stylesheet link
elements from the console HTML, and replace the Inter Tight and JetBrains Mono
dependencies with bundled font assets or local system font stacks in the
relevant styles. Ensure the console renders without any mandatory third-party
font requests, including in offline or air-gapped deployments.

In `@sdk/node/console/assets/models-adapter-BtD8xj0i.js`:
- Line 1: Update the status mapper n() to preserve the supported model statuses,
including ready and warming, instead of converting them to offline. Ensure the
mapped status used by r() remains accurate so catalog display, filtering, and
ordering reflect the source model status.

In `@sdk/node/console/assets/PluginWebUiRoutePage-BR5NTyZb.js`:
- Line 1: Update PluginWebUiRoutePage’s plugin-config flow around the a hook
result so configuration errors are handled explicitly instead of allowing
mounting with missing a.data. Use the hook’s loading and error states to block
mounting while unavailable, and render the existing T fallback UI with an
actionable configuration error or retry path when the request fails.

In `@sdk/node/console/assets/reserve-policy-CNh2fVCc.js`:
- Line 1: Update the provider list component N to support keyboard-accessible
reordering in addition to drag-and-drop. Add per-row move-up and move-down
controls or an equivalent keyboard interaction that updates the existing
onReorder callback, disables unavailable boundary actions, and announces the
resulting provider position to assistive technology.

In `@sdk/node/console/index.html`:
- Around line 115-121: Add a link element for the tracked manifest.json in the
index.html document head, using rel="manifest" and the correct manifest path so
browsers apply the app’s start_url, standalone mode, and icons.

In
`@sdk/swift/Sources/MeshLLM/Resources/Console/assets/DashboardPage-HnRRNG4K.js`:
- Line 1: Update the unavailable-overlay condition in W so A only becomes true
when live fetching is paused and no live data has been recovered; incorporate C
(or clear the paused state when C becomes true) to ensure recovered b data
renders the dashboard instead of the error overlay.

In
`@sdk/swift/Sources/MeshLLM/Resources/Console/assets/PluginWebUiRoutePage-BR5NTyZb.js`:
- Line 1: Handle query lifecycle states explicitly in both affected sites: in
sdk/swift/Sources/MeshLLM/Resources/Console/assets/PluginWebUiRoutePage-BR5NTyZb.js:1-1,
update component E to render visible-config loading and error states before the
plugin mount gate, rather than treating missing a.data as idle; in
sdk/swift/Sources/MeshLLM/Resources/Console/assets/ReservesPage-l3BXmSXD.js:1-1,
render live reserve-query loading and error states before showing the
empty-provider state. Preserve the existing success rendering and actionable
error presentation.

In
`@sdk/swift/Sources/MeshLLM/Resources/Console/assets/reserve-fixtures-LwFOyKoB.js`:
- Line 1: Update Ne so the provider grouping Map is keyed by the original
provider name rather than its sanitized value, preserving distinct providers
such as A/B and A B. Derive each group’s sanitized ID separately and make it
collision-safe when assigning the group object’s id, without merging nodes or
totals from different provider names.

In `@sdk/swift/Sources/MeshLLM/Resources/Console/assets/ReservesPage-l3BXmSXD.js`:
- Line 1: Update the Reserves page call to the InfoBanner component, identified
by the imported alias a in ReservesPageContent, so it supplies a unique titleId.
Ensure the same ID is used by InfoBanner for aria-labelledby and the heading id,
preserving an accessible name for the rendered Reserves section.

In `@sdk/swift/Sources/MeshLLM/Resources/Console/index.html`:
- Around line 109-114: Remove the Google Fonts dependency from the console HTML
by deleting the fonts.googleapis.com and fonts.gstatic.com preconnect links and
the remote stylesheet in the resource entrypoint. Keep the console
self-contained for offline use; if the Inter Tight or JetBrains Mono typography
is required, replace it with locally bundled/static assets rather than external
URLs.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1babd25b-b139-425f-807c-82ac121c7cc0

📥 Commits

Reviewing files that changed from the base of the PR and between 126109b and b9ac454.

⛔ Files ignored due to path filters (200)
  • Cargo.lock is excluded by !**/*.lock
  • crates/mesh-llm-ui/package-lock.json is excluded by !**/package-lock.json
  • sdk/kotlin/src/main/resources/mesh-llm/console/apple-touch-icon.png is excluded by !**/*.png
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 is excluded by !**/*.woff2
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_AMS-Regular-DMm9YOAa.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_AMS-Regular-DRggAlZN.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 is excluded by !**/*.woff2
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 is excluded by !**/*.woff2
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 is excluded by !**/*.woff2
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Fraktur-Regular-CB_wures.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 is excluded by !**/*.woff2
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Main-Bold-Cx986IdX.woff2 is excluded by !**/*.woff2
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Main-Bold-Jm3AIy58.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Main-Bold-waoOVXN0.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 is excluded by !**/*.woff2
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Main-Italic-3WenGoN9.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Main-Italic-BMLOBm91.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 is excluded by !**/*.woff2
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Main-Regular-B22Nviop.woff2 is excluded by !**/*.woff2
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Main-Regular-Dr94JaBh.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Main-Regular-ypZvNtVU.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 is excluded by !**/*.woff2
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Math-Italic-DA0__PXp.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Math-Italic-flOr_0UB.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Math-Italic-t53AETM-.woff2 is excluded by !**/*.woff2
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 is excluded by !**/*.woff2
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 is excluded by !**/*.woff2
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 is excluded by !**/*.woff2
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Script-Regular-C5JkGWo-.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 is excluded by !**/*.woff2
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Script-Regular-D5yQViql.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Size1-Regular-C195tn64.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 is excluded by !**/*.woff2
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 is excluded by !**/*.woff2
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Size2-Regular-oD1tc_U0.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Size3-Regular-CTq5MqoE.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Size4-Regular-BF-4gkZK.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Size4-Regular-DWFBv043.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 is excluded by !**/*.woff2
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff is excluded by !**/*.woff
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 is excluded by !**/*.woff2
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf is excluded by !**/*.ttf
  • sdk/kotlin/src/main/resources/mesh-llm/console/favicon.ico is excluded by !**/*.ico
  • sdk/kotlin/src/main/resources/mesh-llm/console/icon-192.png is excluded by !**/*.png
  • sdk/kotlin/src/main/resources/mesh-llm/console/icon-512.png is excluded by !**/*.png
  • sdk/kotlin/src/main/resources/mesh-llm/console/meshllm-apple-touch-icon.png is excluded by !**/*.png
  • sdk/kotlin/src/main/resources/mesh-llm/console/meshllm-favicon-32.png is excluded by !**/*.png
  • sdk/kotlin/src/main/resources/mesh-llm/console/meshllm-favicon.ico is excluded by !**/*.ico
  • sdk/node/console/apple-touch-icon.png is excluded by !**/*.png
  • sdk/node/console/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 is excluded by !**/*.woff2
  • sdk/node/console/assets/KaTeX_AMS-Regular-DMm9YOAa.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_AMS-Regular-DRggAlZN.ttf is excluded by !**/*.ttf
  • sdk/node/console/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf is excluded by !**/*.ttf
  • sdk/node/console/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 is excluded by !**/*.woff2
  • sdk/node/console/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 is excluded by !**/*.woff2
  • sdk/node/console/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf is excluded by !**/*.ttf
  • sdk/node/console/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf is excluded by !**/*.ttf
  • sdk/node/console/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 is excluded by !**/*.woff2
  • sdk/node/console/assets/KaTeX_Fraktur-Regular-CB_wures.ttf is excluded by !**/*.ttf
  • sdk/node/console/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 is excluded by !**/*.woff2
  • sdk/node/console/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_Main-Bold-Cx986IdX.woff2 is excluded by !**/*.woff2
  • sdk/node/console/assets/KaTeX_Main-Bold-Jm3AIy58.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_Main-Bold-waoOVXN0.ttf is excluded by !**/*.ttf
  • sdk/node/console/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 is excluded by !**/*.woff2
  • sdk/node/console/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf is excluded by !**/*.ttf
  • sdk/node/console/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_Main-Italic-3WenGoN9.ttf is excluded by !**/*.ttf
  • sdk/node/console/assets/KaTeX_Main-Italic-BMLOBm91.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 is excluded by !**/*.woff2
  • sdk/node/console/assets/KaTeX_Main-Regular-B22Nviop.woff2 is excluded by !**/*.woff2
  • sdk/node/console/assets/KaTeX_Main-Regular-Dr94JaBh.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_Main-Regular-ypZvNtVU.ttf is excluded by !**/*.ttf
  • sdk/node/console/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf is excluded by !**/*.ttf
  • sdk/node/console/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 is excluded by !**/*.woff2
  • sdk/node/console/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_Math-Italic-DA0__PXp.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_Math-Italic-flOr_0UB.ttf is excluded by !**/*.ttf
  • sdk/node/console/assets/KaTeX_Math-Italic-t53AETM-.woff2 is excluded by !**/*.woff2
  • sdk/node/console/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf is excluded by !**/*.ttf
  • sdk/node/console/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 is excluded by !**/*.woff2
  • sdk/node/console/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 is excluded by !**/*.woff2
  • sdk/node/console/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf is excluded by !**/*.ttf
  • sdk/node/console/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf is excluded by !**/*.ttf
  • sdk/node/console/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 is excluded by !**/*.woff2
  • sdk/node/console/assets/KaTeX_Script-Regular-C5JkGWo-.ttf is excluded by !**/*.ttf
  • sdk/node/console/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 is excluded by !**/*.woff2
  • sdk/node/console/assets/KaTeX_Script-Regular-D5yQViql.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_Size1-Regular-C195tn64.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf is excluded by !**/*.ttf
  • sdk/node/console/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 is excluded by !**/*.woff2
  • sdk/node/console/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf is excluded by !**/*.ttf
  • sdk/node/console/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 is excluded by !**/*.woff2
  • sdk/node/console/assets/KaTeX_Size2-Regular-oD1tc_U0.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_Size3-Regular-CTq5MqoE.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf is excluded by !**/*.ttf
  • sdk/node/console/assets/KaTeX_Size4-Regular-BF-4gkZK.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_Size4-Regular-DWFBv043.ttf is excluded by !**/*.ttf
  • sdk/node/console/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 is excluded by !**/*.woff2
  • sdk/node/console/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff is excluded by !**/*.woff
  • sdk/node/console/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 is excluded by !**/*.woff2
  • sdk/node/console/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf is excluded by !**/*.ttf
  • sdk/node/console/favicon.ico is excluded by !**/*.ico
  • sdk/node/console/icon-192.png is excluded by !**/*.png
  • sdk/node/console/icon-512.png is excluded by !**/*.png
  • sdk/node/console/meshllm-apple-touch-icon.png is excluded by !**/*.png
  • sdk/node/console/meshllm-favicon-32.png is excluded by !**/*.png
  • sdk/node/console/meshllm-favicon.ico is excluded by !**/*.ico
  • sdk/swift/Sources/MeshLLM/Resources/Console/apple-touch-icon.png is excluded by !**/*.png
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 is excluded by !**/*.woff2
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_AMS-Regular-DMm9YOAa.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_AMS-Regular-DRggAlZN.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 is excluded by !**/*.woff2
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 is excluded by !**/*.woff2
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 is excluded by !**/*.woff2
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Fraktur-Regular-CB_wures.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 is excluded by !**/*.woff2
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Main-Bold-Cx986IdX.woff2 is excluded by !**/*.woff2
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Main-Bold-Jm3AIy58.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Main-Bold-waoOVXN0.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 is excluded by !**/*.woff2
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Main-Italic-3WenGoN9.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Main-Italic-BMLOBm91.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 is excluded by !**/*.woff2
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Main-Regular-B22Nviop.woff2 is excluded by !**/*.woff2
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Main-Regular-Dr94JaBh.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Main-Regular-ypZvNtVU.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 is excluded by !**/*.woff2
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Math-Italic-DA0__PXp.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Math-Italic-flOr_0UB.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Math-Italic-t53AETM-.woff2 is excluded by !**/*.woff2
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 is excluded by !**/*.woff2
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 is excluded by !**/*.woff2
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 is excluded by !**/*.woff2
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Script-Regular-C5JkGWo-.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 is excluded by !**/*.woff2
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Script-Regular-D5yQViql.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Size1-Regular-C195tn64.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 is excluded by !**/*.woff2
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 is excluded by !**/*.woff2
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Size2-Regular-oD1tc_U0.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Size3-Regular-CTq5MqoE.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Size4-Regular-BF-4gkZK.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Size4-Regular-DWFBv043.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 is excluded by !**/*.woff2
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff is excluded by !**/*.woff
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 is excluded by !**/*.woff2
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf is excluded by !**/*.ttf
  • sdk/swift/Sources/MeshLLM/Resources/Console/favicon.ico is excluded by !**/*.ico
  • sdk/swift/Sources/MeshLLM/Resources/Console/icon-192.png is excluded by !**/*.png
  • sdk/swift/Sources/MeshLLM/Resources/Console/icon-512.png is excluded by !**/*.png
  • sdk/swift/Sources/MeshLLM/Resources/Console/meshllm-apple-touch-icon.png is excluded by !**/*.png
  • sdk/swift/Sources/MeshLLM/Resources/Console/meshllm-favicon-32.png is excluded by !**/*.png
  • sdk/swift/Sources/MeshLLM/Resources/Console/meshllm-favicon.ico is excluded by !**/*.ico
📒 Files selected for processing (185)
  • Cargo.toml
  • Package.swift
  • crates/mesh-client/Cargo.toml
  • crates/mesh-llm-api-client/Cargo.toml
  • crates/mesh-llm-api-server/Cargo.toml
  • crates/mesh-llm-cli/Cargo.toml
  • crates/mesh-llm-commands/Cargo.toml
  • crates/mesh-llm-config/Cargo.toml
  • crates/mesh-llm-config/src/model/built_in_schema.rs
  • crates/mesh-llm-config/src/model/built_in_schema/presentation.rs
  • crates/mesh-llm-console-server/Cargo.toml
  • crates/mesh-llm-embedded-runtime/Cargo.toml
  • crates/mesh-llm-hardware-profile/Cargo.toml
  • crates/mesh-llm-host-runtime/Cargo.toml
  • crates/mesh-llm-host-runtime/src/network/openai/transport.rs
  • crates/mesh-llm-host-runtime/src/plugin/transport.rs
  • crates/mesh-llm-host-runtime/src/runtime/auto_join.rs
  • crates/mesh-llm-host-runtime/src/runtime/run_auto.rs
  • crates/mesh-llm-host-runtime/src/runtime/serving_surface.rs
  • crates/mesh-llm-host-runtime/src/runtime/startup_models.rs
  • crates/mesh-llm-host-runtime/src/runtime/tests/startup_models.rs
  • crates/mesh-llm-native-runtime/README.md
  • crates/mesh-llm-node/Cargo.toml
  • crates/mesh-llm-nodejs/Cargo.toml
  • crates/mesh-llm-runtime-install/Cargo.toml
  • crates/mesh-llm-sdk/Cargo.toml
  • crates/mesh-llm-sdk/README.md
  • crates/mesh-llm-system/Cargo.toml
  • crates/mesh-llm-tui/Cargo.toml
  • crates/mesh-llm-ui/package.json
  • crates/mesh-llm/Cargo.toml
  • crates/mesh-mixture-of-agents/Cargo.toml
  • crates/model-artifact/Cargo.toml
  • crates/model-hf/Cargo.toml
  • crates/model-package/Cargo.toml
  • crates/model-resolver/Cargo.toml
  • crates/openai-frontend/Cargo.toml
  • crates/skippy-cache/Cargo.toml
  • crates/skippy-runtime/Cargo.toml
  • crates/skippy-server/Cargo.toml
  • docs/SDK.md
  • docs/design/NATIVE_RUNTIMES.md
  • docs/plugins/plugin-hosted-mesh-serving-design.md
  • docs/sdk/node.md
  • docs/sdk/rust.md
  • docs/sdk/swift.md
  • sdk/kotlin/README.md
  • sdk/kotlin/build.gradle.kts
  • sdk/kotlin/example/example-jvm/build.gradle.kts
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/ChatPage-CyOMNyde.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/ConfigurationRoutePage-DX01AAwo.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/ConnectBlock-Q9VL_lJC.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/DashboardPage-HnRRNG4K.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/DeveloperPlaygroundPage-C2WL5aIS.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/InfoBanner-lIYx64rk.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/MeshViz-DGmQQ8Xw.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/MeshVizPerfHarness-QW_kYiId.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/MeshVizPerfPage-BC-vHnJt.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/PluginWebUiRoutePage-BR5NTyZb.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/ReservesPage-l3BXmSXD.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/SharedModal-BDDbY5Up.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/StatusBadge-Bd_11gv5.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/TabPanel-BmUnDUyF.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/TomlView-B-kmE8MM.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/TransparencyPane-CrlPiSUD.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/chevron-right-2cTjc06l.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/cn-DzJZ_V3X.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/config-math-hO60GYXS.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/dist-CmtQcoKK.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/dist-DOBoyB6A.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/eye-Bx-4i7_p.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/format-model-size-DleXDH2g.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/host-surface-Dpepihlo.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/index-DOcqlpTd.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/index-DPNsOWFD.css
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/models-adapter-BtD8xj0i.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/pdf-D97rLqeW.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/pdf.worker.min-DEtVeC4l.mjs
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/position-BWRVSCrm.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/reserve-fixtures-LwFOyKoB.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/reserve-policy-CNh2fVCc.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/shield-alert-CUQLlWwc.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/stagger-Cr3UjKUn.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/tooltip-B7o_GgNn.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/transformers.web-DdRLY0AV.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/use-models-query-B93o-dqC.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/useRouter-Ds6vf8iZ.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/utils-Bn-caNnQ.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/vram-_ZXANuL0.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/assets/workerBundle-Q2hh7yy5.js
  • sdk/kotlin/src/main/resources/mesh-llm/console/index.html
  • sdk/kotlin/src/main/resources/mesh-llm/console/manifest.json
  • sdk/kotlin/src/main/resources/mesh-llm/console/manifest.txt
  • sdk/node/console/assets/ChatPage-CyOMNyde.js
  • sdk/node/console/assets/ConfigurationRoutePage-DX01AAwo.js
  • sdk/node/console/assets/ConnectBlock-Q9VL_lJC.js
  • sdk/node/console/assets/DashboardPage-HnRRNG4K.js
  • sdk/node/console/assets/DeveloperPlaygroundPage-C2WL5aIS.js
  • sdk/node/console/assets/InfoBanner-lIYx64rk.js
  • sdk/node/console/assets/MeshViz-DGmQQ8Xw.js
  • sdk/node/console/assets/MeshVizPerfHarness-QW_kYiId.js
  • sdk/node/console/assets/MeshVizPerfPage-BC-vHnJt.js
  • sdk/node/console/assets/PluginWebUiRoutePage-BR5NTyZb.js
  • sdk/node/console/assets/ReservesPage-l3BXmSXD.js
  • sdk/node/console/assets/SharedModal-BDDbY5Up.js
  • sdk/node/console/assets/StatusBadge-Bd_11gv5.js
  • sdk/node/console/assets/TabPanel-BmUnDUyF.js
  • sdk/node/console/assets/TomlView-B-kmE8MM.js
  • sdk/node/console/assets/TransparencyPane-CrlPiSUD.js
  • sdk/node/console/assets/chevron-right-2cTjc06l.js
  • sdk/node/console/assets/cn-DzJZ_V3X.js
  • sdk/node/console/assets/config-math-hO60GYXS.js
  • sdk/node/console/assets/dist-CmtQcoKK.js
  • sdk/node/console/assets/dist-DOBoyB6A.js
  • sdk/node/console/assets/eye-Bx-4i7_p.js
  • sdk/node/console/assets/format-model-size-DleXDH2g.js
  • sdk/node/console/assets/host-surface-Dpepihlo.js
  • sdk/node/console/assets/index-DOcqlpTd.js
  • sdk/node/console/assets/index-DPNsOWFD.css
  • sdk/node/console/assets/models-adapter-BtD8xj0i.js
  • sdk/node/console/assets/pdf-D97rLqeW.js
  • sdk/node/console/assets/pdf.worker.min-DEtVeC4l.mjs
  • sdk/node/console/assets/position-BWRVSCrm.js
  • sdk/node/console/assets/reserve-fixtures-LwFOyKoB.js
  • sdk/node/console/assets/reserve-policy-CNh2fVCc.js
  • sdk/node/console/assets/shield-alert-CUQLlWwc.js
  • sdk/node/console/assets/stagger-Cr3UjKUn.js
  • sdk/node/console/assets/tooltip-B7o_GgNn.js
  • sdk/node/console/assets/transformers.web-DdRLY0AV.js
  • sdk/node/console/assets/use-models-query-B93o-dqC.js
  • sdk/node/console/assets/useRouter-Ds6vf8iZ.js
  • sdk/node/console/assets/utils-Bn-caNnQ.js
  • sdk/node/console/assets/vram-_ZXANuL0.js
  • sdk/node/console/assets/workerBundle-Q2hh7yy5.js
  • sdk/node/console/index.html
  • sdk/node/console/manifest.json
  • sdk/node/console/manifest.txt
  • sdk/node/package.json
  • sdk/swift/README.md
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/ChatPage-CyOMNyde.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/ConfigurationRoutePage-DX01AAwo.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/ConnectBlock-Q9VL_lJC.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/DashboardPage-HnRRNG4K.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/DeveloperPlaygroundPage-C2WL5aIS.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/InfoBanner-lIYx64rk.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/MeshViz-DGmQQ8Xw.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/MeshVizPerfHarness-QW_kYiId.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/MeshVizPerfPage-BC-vHnJt.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/PluginWebUiRoutePage-BR5NTyZb.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/ReservesPage-l3BXmSXD.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/SharedModal-BDDbY5Up.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/StatusBadge-Bd_11gv5.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/TabPanel-BmUnDUyF.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/TomlView-B-kmE8MM.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/TransparencyPane-CrlPiSUD.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/chevron-right-2cTjc06l.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/cn-DzJZ_V3X.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/config-math-hO60GYXS.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/dist-CmtQcoKK.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/dist-DOBoyB6A.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/eye-Bx-4i7_p.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/format-model-size-DleXDH2g.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/host-surface-Dpepihlo.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/index-DOcqlpTd.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/index-DPNsOWFD.css
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/models-adapter-BtD8xj0i.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/pdf-D97rLqeW.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/pdf.worker.min-DEtVeC4l.mjs
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/position-BWRVSCrm.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/reserve-fixtures-LwFOyKoB.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/reserve-policy-CNh2fVCc.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/shield-alert-CUQLlWwc.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/stagger-Cr3UjKUn.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/tooltip-B7o_GgNn.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/transformers.web-DdRLY0AV.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/use-models-query-B93o-dqC.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/useRouter-Ds6vf8iZ.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/utils-Bn-caNnQ.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/vram-_ZXANuL0.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/workerBundle-Q2hh7yy5.js
  • sdk/swift/Sources/MeshLLM/Resources/Console/index.html
  • sdk/swift/Sources/MeshLLM/Resources/Console/manifest.json
  • sdk/swift/Sources/MeshLLM/Resources/Console/manifest.txt
  • sdk/swift/scripts/generate-swift-bindings.sh
  • website/src/docs/pages/CLI.md

Comment thread crates/mesh-llm-host-runtime/src/plugin/transport.rs Outdated
Comment on lines +1506 to +1528
fn spawn_plugin_host_role_watcher(node: mesh::Node, plugin_manager: plugin::PluginManager, http_port: u16) {
tokio::spawn(async move {
let mut plugin_promoted_role = false;
loop {
tokio::time::sleep(PLUGIN_HOST_ROLE_WATCH_INTERVAL).await;
let has_plugin_models = plugin_manager
.inference_models()
.await
.map(|models| !models.is_empty())
.unwrap_or(false);
if has_plugin_models && !plugin_promoted_role {
if node.role().await == mesh::NodeRole::Worker {
node.set_role(mesh::NodeRole::Host { http_port }).await;
plugin_promoted_role = true;
}
} else if !has_plugin_models && plugin_promoted_role {
if node.role().await == (mesh::NodeRole::Host { http_port }) {
node.set_role(mesh::NodeRole::Worker).await;
}
plugin_promoted_role = false;
}
}
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Stop the plugin watcher when passive mode ends.

run_passive_listener_loop can return a model for local startup, but this detached task continues after that transition. If plugin models later disappear, the task can still change NodeRole even though local-model startup now owns host eligibility.

Retain the task handle and cancel it before every passive-loop exit. Track plugin and local-model host reasons separately before demoting the node.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/mesh-llm-host-runtime/src/runtime/serving_surface.rs` around lines
1506 - 1528, Update run_passive_listener_loop and spawn_plugin_host_role_watcher
so the watcher task handle is retained and cancelled before every passive-loop
exit, including local-model startup. Track plugin-host and local-model host
reasons independently, and only demote the node when neither reason remains,
preventing the detached watcher from changing NodeRole after local startup takes
ownership.

Comment thread docs/plugins/plugin-hosted-mesh-serving-design.md Outdated
@@ -0,0 +1 @@
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),s=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},c=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},l=(n,r,a)=>(a=n==null?{}:e(i(n)),c(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n)),u=(e=>typeof require<`u`?require:typeof Proxy<`u`?new Proxy(e,{get:(e,t)=>(typeof require<`u`?require:e)[t]}):e)(function(e){if(typeof require<`u`)return require.apply(this,arguments);throw Error('Calling `require` for "'+e+"\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.")}),d=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function C(){}var w={H:null,A:null,T:null,S:null},T=Object.prototype.hasOwnProperty;function E(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function ee(e,t){return E(e.type,t,e.props)}function D(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function te(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var O=/\/+/g;function k(e,t){return typeof e==`object`&&e&&e.key!=null?te(``+e.key):t.toString(36)}function A(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(C,C):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function j(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,j(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+k(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(O,`$&/`)+`/`),j(o,r,i,``,function(e){return e})):o!=null&&(D(o)&&(o=ee(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(O,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u<e.length;u++)a=e[u],s=l+k(a,u),c+=j(a,r,i,s,o);else if(u=m(e),typeof u==`function`)for(e=u.call(e),u=0;!(a=e.next()).done;)a=a.value,s=l+k(a,u++),c+=j(a,r,i,s,o);else if(s===`object`){if(typeof e.then==`function`)return j(A(e),r,i,a,o);throw r=String(e),Error(`Objects are not valid as a React child (found: `+(r===`[object Object]`?`object with keys {`+Object.keys(e).join(`, `)+`}`:r)+`). If you meant to render a collection of children, use an array instead.`)}return c}function M(e,t,n){if(e==null)return e;var r=[],i=0;return j(e,r,``,``,function(e){return t.call(n,e,i++)}),r}function N(e){if(e._status===-1){var t=e._result;t=t(),t.then(function(t){(e._status===0||e._status===-1)&&(e._status=1,e._result=t)},function(t){(e._status===0||e._status===-1)&&(e._status=2,e._result=t)}),e._status===-1&&(e._status=0,e._result=t)}if(e._status===1)return e._result.default;throw e._result}var P=typeof reportError==`function`?reportError:function(e){if(typeof window==`object`&&typeof window.ErrorEvent==`function`){var t=new window.ErrorEvent(`error`,{bubbles:!0,cancelable:!0,message:typeof e==`object`&&e&&typeof e.message==`string`?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if(typeof process==`object`&&typeof process.emit==`function`){process.emit(`uncaughtException`,e);return}console.error(e)},F={map:M,forEach:function(e,t,n){M(e,function(){t.apply(this,arguments)},n)},count:function(e){var t=0;return M(e,function(){t++}),t},toArray:function(e){return M(e,function(e){return e})||[]},only:function(e){if(!D(e))throw Error(`React.Children.only expected to receive a single React element child.`);return e}};e.Activity=f,e.Children=F,e.Component=v,e.Fragment=r,e.Profiler=a,e.PureComponent=b,e.StrictMode=i,e.Suspense=l,e.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=w,e.__COMPILER_RUNTIME={__proto__:null,c:function(e){return w.H.useMemoCache(e)}},e.cache=function(e){return function(){return e.apply(null,arguments)}},e.cacheSignal=function(){return null},e.cloneElement=function(e,t,n){if(e==null)throw Error(`The argument must be a React element, but you passed `+e+`.`);var r=g({},e.props),i=e.key;if(t!=null)for(a in t.key!==void 0&&(i=``+t.key),t)!T.call(t,a)||a===`key`||a===`__self`||a===`__source`||a===`ref`&&t.ref===void 0||(r[a]=t[a]);var a=arguments.length-2;if(a===1)r.children=n;else if(1<a){for(var o=Array(a),s=0;s<a;s++)o[s]=arguments[s+2];r.children=o}return E(e.type,i,r)},e.createContext=function(e){return e={$$typeof:s,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null},e.Provider=e,e.Consumer={$$typeof:o,_context:e},e},e.createElement=function(e,t,n){var r,i={},a=null;if(t!=null)for(r in t.key!==void 0&&(a=``+t.key),t)T.call(t,r)&&r!==`key`&&r!==`__self`&&r!==`__source`&&(i[r]=t[r]);var o=arguments.length-2;if(o===1)i.children=n;else if(1<o){for(var s=Array(o),c=0;c<o;c++)s[c]=arguments[c+2];i.children=s}if(e&&e.defaultProps)for(r in o=e.defaultProps,o)i[r]===void 0&&(i[r]=o[r]);return E(e,a,i)},e.createRef=function(){return{current:null}},e.forwardRef=function(e){return{$$typeof:c,render:e}},e.isValidElement=D,e.lazy=function(e){return{$$typeof:d,_payload:{_status:-1,_result:e},_init:N}},e.memo=function(e,t){return{$$typeof:u,type:e,compare:t===void 0?null:t}},e.startTransition=function(e){var t=w.T,n={};w.T=n;try{var r=e(),i=w.S;i!==null&&i(n,r),typeof r==`object`&&r&&typeof r.then==`function`&&r.then(C,P)}catch(e){P(e)}finally{t!==null&&n.types!==null&&(t.types=n.types),w.T=t}},e.unstable_useCacheRefresh=function(){return w.H.useCacheRefresh()},e.use=function(e){return w.H.use(e)},e.useActionState=function(e,t,n){return w.H.useActionState(e,t,n)},e.useCallback=function(e,t){return w.H.useCallback(e,t)},e.useContext=function(e){return w.H.useContext(e)},e.useDebugValue=function(){},e.useDeferredValue=function(e,t){return w.H.useDeferredValue(e,t)},e.useEffect=function(e,t){return w.H.useEffect(e,t)},e.useEffectEvent=function(e){return w.H.useEffectEvent(e)},e.useId=function(){return w.H.useId()},e.useImperativeHandle=function(e,t,n){return w.H.useImperativeHandle(e,t,n)},e.useInsertionEffect=function(e,t){return w.H.useInsertionEffect(e,t)},e.useLayoutEffect=function(e,t){return w.H.useLayoutEffect(e,t)},e.useMemo=function(e,t){return w.H.useMemo(e,t)},e.useOptimistic=function(e,t){return w.H.useOptimistic(e,t)},e.useReducer=function(e,t,n){return w.H.useReducer(e,t,n)},e.useRef=function(e){return w.H.useRef(e)},e.useState=function(e){return w.H.useState(e)},e.useSyncExternalStore=function(e,t,n){return w.H.useSyncExternalStore(e,t,n)},e.useTransition=function(){return w.H.useTransition()},e.version=`19.2.7`})),f=o(((e,t)=>{t.exports=d()})),p=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),m=o(((e,t)=>{t.exports=p()}));function h(e){let t=e?.trim();if(!t||t===`.`||t===`./`)return`/`;let n=(t.startsWith(`/`)?t:`/${t}`).replace(/\/+$/,``);return n===``?`/`:n}function g(e,t=v.routerBasePath){let n=h(t),r=e.startsWith(`/`)?e:`/${e}`;return n===`/`?r:r===`/`?`${n}/`:`${n}${r}`}function _(e,t=v.routerBasePath){let n=h(t);if(n===`/`||!e.startsWith(n))return e;let r=e.slice(n.length);return r?r.startsWith(`/`)?r:`/${r}`:`/`}var v={appVersion:`dev`,apiUrl:`http://127.0.0.1:9337`,managementApiUrl:``,routerBasePath:h(`/`),storageNamespace:`mesh-llm-ui-preview`,isDevelopment:!1};function y(){return v.isDevelopment}var b=l(f(),1),x=(...e)=>e.filter((e,t,n)=>!!e&&e.trim()!==``&&n.indexOf(e)===t).join(` `).trim(),S=e=>e.replace(/([a-z0-9])([A-Z])/g,`$1-$2`).toLowerCase(),C=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(e,t,n)=>n?n.toUpperCase():t.toLowerCase()),w=e=>{let t=C(e);return t.charAt(0).toUpperCase()+t.slice(1)},T={xmlns:`http://www.w3.org/2000/svg`,width:24,height:24,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:2,strokeLinecap:`round`,strokeLinejoin:`round`},E=e=>{for(let t in e)if(t.startsWith(`aria-`)||t===`role`||t===`title`)return!0;return!1},ee=(0,b.createContext)({}),D=()=>(0,b.useContext)(ee),te=(0,b.forwardRef)(({color:e,size:t,strokeWidth:n,absoluteStrokeWidth:r,className:i=``,children:a,iconNode:o,...s},c)=>{let{size:l=24,strokeWidth:u=2,absoluteStrokeWidth:d=!1,color:f=`currentColor`,className:p=``}=D()??{},m=r??d?Number(n??u)*24/Number(t??l):n??u;return(0,b.createElement)(`svg`,{ref:c,...T,width:t??l??T.width,height:t??l??T.height,stroke:e??f,strokeWidth:m,className:x(`lucide`,p,i),...!a&&!E(s)&&{"aria-hidden":`true`},...s},[...o.map(([e,t])=>(0,b.createElement)(e,t)),...Array.isArray(a)?a:[a]])}),O=(e,t)=>{let n=(0,b.forwardRef)(({className:n,...r},i)=>(0,b.createElement)(te,{ref:i,iconNode:t,className:x(`lucide-${S(w(e))}`,`lucide-${e}`,n),...r}));return n.displayName=w(e),n};function k(e){var t,n,r=``;if(typeof e==`string`||typeof e==`number`)r+=e;else if(typeof e==`object`)if(Array.isArray(e)){var i=e.length;for(t=0;t<i;t++)e[t]&&(n=k(e[t]))&&(r&&(r+=` `),r+=n)}else for(n in e)e[n]&&(r&&(r+=` `),r+=n);return r}function A(){for(var e,t,n=0,r=``,i=arguments.length;n<i;n++)(e=arguments[n])&&(t=k(e))&&(r&&(r+=` `),r+=t);return r}var j=(e,t)=>{let n=Array(e.length+t.length);for(let t=0;t<e.length;t++)n[t]=e[t];for(let r=0;r<t.length;r++)n[e.length+r]=t[r];return n},M=(e,t)=>({classGroupId:e,validator:t}),N=(e=new Map,t=null,n)=>({nextPart:e,validators:t,classGroupId:n}),P=`-`,F=[],ne=`arbitrary..`,re=e=>{let t=R(e),{conflictingClassGroups:n,conflictingClassGroupModifiers:r}=e;return{getClassGroupId:e=>{if(e.startsWith(`[`)&&e.endsWith(`]`))return L(e);let n=e.split(P);return I(n,+(n[0]===``&&n.length>1),t)},getConflictingClassGroupIds:(e,t)=>{if(t){let t=r[e],i=n[e];return t?i?j(i,t):t:i||F}return n[e]||F}}},I=(e,t,n)=>{if(e.length-t===0)return n.classGroupId;let r=e[t],i=n.nextPart.get(r);if(i){let n=I(e,t+1,i);if(n)return n}let a=n.validators;if(a===null)return;let o=t===0?e.join(P):e.slice(t).join(P),s=a.length;for(let e=0;e<s;e++){let t=a[e];if(t.validator(o))return t.classGroupId}},L=e=>e.slice(1,-1).indexOf(`:`)===-1?void 0:(()=>{let t=e.slice(1,-1),n=t.indexOf(`:`),r=t.slice(0,n);return r?ne+r:void 0})(),R=e=>{let{theme:t,classGroups:n}=e;return z(n,t)},z=(e,t)=>{let n=N();for(let r in e){let i=e[r];B(i,n,r,t)}return n},B=(e,t,n,r)=>{let i=e.length;for(let a=0;a<i;a++){let i=e[a];V(i,t,n,r)}},V=(e,t,n,r)=>{if(typeof e==`string`){ie(e,t,n);return}if(typeof e==`function`){H(e,t,n,r);return}U(e,t,n,r)},ie=(e,t,n)=>{let r=e===``?t:ae(t,e);r.classGroupId=n},H=(e,t,n,r)=>{if(oe(e)){B(e(r),t,n,r);return}t.validators===null&&(t.validators=[]),t.validators.push(M(n,e))},U=(e,t,n,r)=>{let i=Object.entries(e),a=i.length;for(let e=0;e<a;e++){let[a,o]=i[e];B(o,ae(t,a),n,r)}},ae=(e,t)=>{let n=e,r=t.split(P),i=r.length;for(let e=0;e<i;e++){let t=r[e],i=n.nextPart.get(t);i||(i=N(),n.nextPart.set(t,i)),n=i}return n},oe=e=>`isThemeGetter`in e&&e.isThemeGetter===!0,se=e=>{if(e<1)return{get:()=>void 0,set:()=>{}};let t=0,n=Object.create(null),r=Object.create(null),i=(i,a)=>{n[i]=a,t++,t>e&&(t=0,r=n,n=Object.create(null))};return{get(e){let t=n[e];if(t!==void 0)return t;if((t=r[e])!==void 0)return i(e,t),t},set(e,t){e in n?n[e]=t:i(e,t)}}},ce=`!`,le=`:`,ue=[],de=(e,t,n,r,i)=>({modifiers:e,hasImportantModifier:t,baseClassName:n,maybePostfixModifierPosition:r,isExternal:i}),fe=e=>{let{prefix:t,experimentalParseClassName:n}=e,r=e=>{let t=[],n=0,r=0,i=0,a,o=e.length;for(let s=0;s<o;s++){let o=e[s];if(n===0&&r===0){if(o===le){t.push(e.slice(i,s)),i=s+1;continue}if(o===`/`){a=s;continue}}o===`[`?n++:o===`]`?n--:o===`(`?r++:o===`)`&&r--}let s=t.length===0?e:e.slice(i),c=s,l=!1;s.endsWith(ce)?(c=s.slice(0,-1),l=!0):s.startsWith(ce)&&(c=s.slice(1),l=!0);let u=a&&a>i?a-i:void 0;return de(t,l,c,u)};if(t){let e=t+le,n=r;r=t=>t.startsWith(e)?n(t.slice(e.length)):de(ue,!1,t,void 0,!0)}if(n){let e=r;r=t=>n({className:t,parseClassName:e})}return r},pe=e=>{let t=new Map;return e.orderSensitiveModifiers.forEach((e,n)=>{t.set(e,1e6+n)}),e=>{let n=[],r=[];for(let i=0;i<e.length;i++){let a=e[i],o=a[0]===`[`,s=t.has(a);o||s?(r.length>0&&(r.sort(),n.push(...r),r=[]),n.push(a)):r.push(a)}return r.length>0&&(r.sort(),n.push(...r)),n}},me=e=>({cache:se(e.cacheSize),parseClassName:fe(e),sortModifiers:pe(e),postfixLookupClassGroupIds:he(e),...re(e)}),he=e=>{let t=Object.create(null),n=e.postfixLookupClassGroups;if(n)for(let e=0;e<n.length;e++)t[n[e]]=!0;return t},ge=/\s+/,_e=(e,t)=>{let{parseClassName:n,getClassGroupId:r,getConflictingClassGroupIds:i,sortModifiers:a,postfixLookupClassGroupIds:o}=t,s=[],c=e.trim().split(ge),l=``;for(let e=c.length-1;e>=0;--e){let t=c[e],{isExternal:u,modifiers:d,hasImportantModifier:f,baseClassName:p,maybePostfixModifierPosition:m}=n(t);if(u){l=t+(l.length>0?` `+l:l);continue}let h=!!m,g;if(h){g=r(p.substring(0,m));let e=g&&o[g]?r(p):void 0;e&&e!==g&&(g=e,h=!1)}else g=r(p);if(!g){if(!h){l=t+(l.length>0?` `+l:l);continue}if(g=r(p),!g){l=t+(l.length>0?` `+l:l);continue}h=!1}let _=d.length===0?``:d.length===1?d[0]:a(d).join(`:`),v=f?_+ce:_,y=v+g;if(s.indexOf(y)>-1)continue;s.push(y);let b=i(g,h);for(let e=0;e<b.length;++e){let t=b[e];s.push(v+t)}l=t+(l.length>0?` `+l:l)}return l},ve=(...e)=>{let t=0,n,r,i=``;for(;t<e.length;)(n=e[t++])&&(r=ye(n))&&(i&&(i+=` `),i+=r);return i},ye=e=>{if(typeof e==`string`)return e;let t,n=``;for(let r=0;r<e.length;r++)e[r]&&(t=ye(e[r]))&&(n&&(n+=` `),n+=t);return n},be=(e,...t)=>{let n,r,i,a,o=o=>(n=me(t.reduce((e,t)=>t(e),e())),r=n.cache.get,i=n.cache.set,a=s,s(o)),s=e=>{let t=r(e);if(t)return t;let a=_e(e,n);return i(e,a),a};return a=o,(...e)=>a(ve(...e))},xe=[],W=e=>{let t=t=>t[e]||xe;return t.isThemeGetter=!0,t},Se=/^\[(?:(\w[\w-]*):)?(.+)\]$/i,Ce=/^\((?:(\w[\w-]*):)?(.+)\)$/i,we=/^\d+(?:\.\d+)?\/\d+(?:\.\d+)?$/,Te=/^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/,Ee=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/,De=/^(rgba?|hsla?|hwb|(ok)?(lab|lch)|color-mix)\(.+\)$/,Oe=/^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/,ke=/^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/,G=e=>we.test(e),K=e=>!!e&&!Number.isNaN(Number(e)),q=e=>!!e&&Number.isInteger(Number(e)),Ae=e=>e.endsWith(`%`)&&K(e.slice(0,-1)),J=e=>Te.test(e),je=()=>!0,Me=e=>Ee.test(e)&&!De.test(e),Ne=()=>!1,Pe=e=>Oe.test(e),Fe=e=>ke.test(e),Ie=e=>!Y(e)&&!Z(e),Le=e=>e.startsWith(`@container`)&&(e[10]===`/`&&e[11]!==void 0||e[11]===`s`&&e[16]!==void 0&&e.startsWith(`-size/`,10)||e[11]===`n`&&e[18]!==void 0&&e.startsWith(`-normal/`,10)),Re=e=>Q(e,et,Ne),Y=e=>Se.test(e),X=e=>Q(e,tt,Me),ze=e=>Q(e,nt,K),Be=e=>Q(e,it,je),Ve=e=>Q(e,rt,Ne),He=e=>Q(e,Qe,Ne),Ue=e=>Q(e,$e,Fe),We=e=>Q(e,at,Pe),Z=e=>Ce.test(e),Ge=e=>$(e,tt),Ke=e=>$(e,rt),qe=e=>$(e,Qe),Je=e=>$(e,et),Ye=e=>$(e,$e),Xe=e=>$(e,at,!0),Ze=e=>$(e,it,!0),Q=(e,t,n)=>{let r=Se.exec(e);return r?r[1]?t(r[1]):n(r[2]):!1},$=(e,t,n=!1)=>{let r=Ce.exec(e);return r?r[1]?t(r[1]):n:!1},Qe=e=>e===`position`||e===`percentage`,$e=e=>e===`image`||e===`url`,et=e=>e===`length`||e===`size`||e===`bg-size`,tt=e=>e===`length`,nt=e=>e===`number`,rt=e=>e===`family-name`,it=e=>e===`number`||e===`weight`,at=e=>e===`shadow`,ot=be(()=>{let e=W(`color`),t=W(`font`),n=W(`text`),r=W(`font-weight`),i=W(`tracking`),a=W(`leading`),o=W(`breakpoint`),s=W(`container`),c=W(`spacing`),l=W(`radius`),u=W(`shadow`),d=W(`inset-shadow`),f=W(`text-shadow`),p=W(`drop-shadow`),m=W(`blur`),h=W(`perspective`),g=W(`aspect`),_=W(`ease`),v=W(`animate`),y=()=>[`auto`,`avoid`,`all`,`avoid-page`,`page`,`left`,`right`,`column`],b=()=>[`center`,`top`,`bottom`,`left`,`right`,`top-left`,`left-top`,`top-right`,`right-top`,`bottom-right`,`right-bottom`,`bottom-left`,`left-bottom`],x=()=>[...b(),Z,Y],S=()=>[`auto`,`hidden`,`clip`,`visible`,`scroll`],C=()=>[`auto`,`contain`,`none`],w=()=>[Z,Y,c],T=()=>[G,`full`,`auto`,...w()],E=()=>[q,`none`,`subgrid`,Z,Y],ee=()=>[`auto`,{span:[`full`,q,Z,Y]},q,Z,Y],D=()=>[q,`auto`,Z,Y],te=()=>[`auto`,`min`,`max`,`fr`,Z,Y],O=()=>[`start`,`end`,`center`,`between`,`around`,`evenly`,`stretch`,`baseline`,`center-safe`,`end-safe`],k=()=>[`start`,`end`,`center`,`stretch`,`center-safe`,`end-safe`],A=()=>[`auto`,...w()],j=()=>[G,`auto`,`full`,`dvw`,`dvh`,`lvw`,`lvh`,`svw`,`svh`,`min`,`max`,`fit`,...w()],M=()=>[G,`screen`,`full`,`dvw`,`lvw`,`svw`,`min`,`max`,`fit`,...w()],N=()=>[G,`screen`,`full`,`lh`,`dvh`,`lvh`,`svh`,`min`,`max`,`fit`,...w()],P=()=>[e,Z,Y],F=()=>[...b(),qe,He,{position:[Z,Y]}],ne=()=>[`no-repeat`,{repeat:[``,`x`,`y`,`space`,`round`]}],re=()=>[`auto`,`cover`,`contain`,Je,Re,{size:[Z,Y]}],I=()=>[Ae,Ge,X],L=()=>[``,`none`,`full`,l,Z,Y],R=()=>[``,K,Ge,X],z=()=>[`solid`,`dashed`,`dotted`,`double`],B=()=>[`normal`,`multiply`,`screen`,`overlay`,`darken`,`lighten`,`color-dodge`,`color-burn`,`hard-light`,`soft-light`,`difference`,`exclusion`,`hue`,`saturation`,`color`,`luminosity`],V=()=>[K,Ae,qe,He],ie=()=>[``,`none`,m,Z,Y],H=()=>[`none`,K,Z,Y],U=()=>[`none`,K,Z,Y],ae=()=>[K,Z,Y],oe=()=>[G,`full`,...w()];return{cacheSize:500,theme:{animate:[`spin`,`ping`,`pulse`,`bounce`],aspect:[`video`],blur:[J],breakpoint:[J],color:[je],container:[J],"drop-shadow":[J],ease:[`in`,`out`,`in-out`],font:[Ie],"font-weight":[`thin`,`extralight`,`light`,`normal`,`medium`,`semibold`,`bold`,`extrabold`,`black`],"inset-shadow":[J],leading:[`none`,`tight`,`snug`,`normal`,`relaxed`,`loose`],perspective:[`dramatic`,`near`,`normal`,`midrange`,`distant`,`none`],radius:[J],shadow:[J],spacing:[`px`,K],text:[J],"text-shadow":[J],tracking:[`tighter`,`tight`,`normal`,`wide`,`wider`,`widest`]},classGroups:{aspect:[{aspect:[`auto`,`square`,G,Y,Z,g]}],container:[`container`],"container-type":[{"@container":[``,`normal`,`size`,Z,Y]}],"container-named":[Le],columns:[{columns:[K,Y,Z,s]}],"break-after":[{"break-after":y()}],"break-before":[{"break-before":y()}],"break-inside":[{"break-inside":[`auto`,`avoid`,`avoid-page`,`avoid-column`]}],"box-decoration":[{"box-decoration":[`slice`,`clone`]}],box:[{box:[`border`,`content`]}],display:[`block`,`inline-block`,`inline`,`flex`,`inline-flex`,`table`,`inline-table`,`table-caption`,`table-cell`,`table-column`,`table-column-group`,`table-footer-group`,`table-header-group`,`table-row-group`,`table-row`,`flow-root`,`grid`,`inline-grid`,`contents`,`list-item`,`hidden`],sr:[`sr-only`,`not-sr-only`],float:[{float:[`right`,`left`,`none`,`start`,`end`]}],clear:[{clear:[`left`,`right`,`both`,`none`,`start`,`end`]}],isolation:[`isolate`,`isolation-auto`],"object-fit":[{object:[`contain`,`cover`,`fill`,`none`,`scale-down`]}],"object-position":[{object:x()}],overflow:[{overflow:S()}],"overflow-x":[{"overflow-x":S()}],"overflow-y":[{"overflow-y":S()}],overscroll:[{overscroll:C()}],"overscroll-x":[{"overscroll-x":C()}],"overscroll-y":[{"overscroll-y":C()}],position:[`static`,`fixed`,`absolute`,`relative`,`sticky`],inset:[{inset:T()}],"inset-x":[{"inset-x":T()}],"inset-y":[{"inset-y":T()}],start:[{"inset-s":T(),start:T()}],end:[{"inset-e":T(),end:T()}],"inset-bs":[{"inset-bs":T()}],"inset-be":[{"inset-be":T()}],top:[{top:T()}],right:[{right:T()}],bottom:[{bottom:T()}],left:[{left:T()}],visibility:[`visible`,`invisible`,`collapse`],z:[{z:[q,`auto`,Z,Y]}],basis:[{basis:[G,`full`,`auto`,s,...w()]}],"flex-direction":[{flex:[`row`,`row-reverse`,`col`,`col-reverse`]}],"flex-wrap":[{flex:[`nowrap`,`wrap`,`wrap-reverse`]}],flex:[{flex:[K,G,`auto`,`initial`,`none`,Y]}],grow:[{grow:[``,K,Z,Y]}],shrink:[{shrink:[``,K,Z,Y]}],order:[{order:[q,`first`,`last`,`none`,Z,Y]}],"grid-cols":[{"grid-cols":E()}],"col-start-end":[{col:ee()}],"col-start":[{"col-start":D()}],"col-end":[{"col-end":D()}],"grid-rows":[{"grid-rows":E()}],"row-start-end":[{row:ee()}],"row-start":[{"row-start":D()}],"row-end":[{"row-end":D()}],"grid-flow":[{"grid-flow":[`row`,`col`,`dense`,`row-dense`,`col-dense`]}],"auto-cols":[{"auto-cols":te()}],"auto-rows":[{"auto-rows":te()}],gap:[{gap:w()}],"gap-x":[{"gap-x":w()}],"gap-y":[{"gap-y":w()}],"justify-content":[{justify:[...O(),`normal`]}],"justify-items":[{"justify-items":[...k(),`normal`]}],"justify-self":[{"justify-self":[`auto`,...k()]}],"align-content":[{content:[`normal`,...O()]}],"align-items":[{items:[...k(),{baseline:[``,`last`]}]}],"align-self":[{self:[`auto`,...k(),{baseline:[``,`last`]}]}],"place-content":[{"place-content":O()}],"place-items":[{"place-items":[...k(),`baseline`]}],"place-self":[{"place-self":[`auto`,...k()]}],p:[{p:w()}],px:[{px:w()}],py:[{py:w()}],ps:[{ps:w()}],pe:[{pe:w()}],pbs:[{pbs:w()}],pbe:[{pbe:w()}],pt:[{pt:w()}],pr:[{pr:w()}],pb:[{pb:w()}],pl:[{pl:w()}],m:[{m:A()}],mx:[{mx:A()}],my:[{my:A()}],ms:[{ms:A()}],me:[{me:A()}],mbs:[{mbs:A()}],mbe:[{mbe:A()}],mt:[{mt:A()}],mr:[{mr:A()}],mb:[{mb:A()}],ml:[{ml:A()}],"space-x":[{"space-x":w()}],"space-x-reverse":[`space-x-reverse`],"space-y":[{"space-y":w()}],"space-y-reverse":[`space-y-reverse`],size:[{size:j()}],"inline-size":[{inline:[`auto`,...M()]}],"min-inline-size":[{"min-inline":[`auto`,...M()]}],"max-inline-size":[{"max-inline":[`none`,...M()]}],"block-size":[{block:[`auto`,...N()]}],"min-block-size":[{"min-block":[`auto`,...N()]}],"max-block-size":[{"max-block":[`none`,...N()]}],w:[{w:[s,`screen`,...j()]}],"min-w":[{"min-w":[s,`screen`,`none`,...j()]}],"max-w":[{"max-w":[s,`screen`,`none`,`prose`,{screen:[o]},...j()]}],h:[{h:[`screen`,`lh`,...j()]}],"min-h":[{"min-h":[`screen`,`lh`,`none`,...j()]}],"max-h":[{"max-h":[`screen`,`lh`,...j()]}],"font-size":[{text:[`base`,n,Ge,X]}],"font-smoothing":[`antialiased`,`subpixel-antialiased`],"font-style":[`italic`,`not-italic`],"font-weight":[{font:[r,Ze,Be]}],"font-stretch":[{"font-stretch":[`ultra-condensed`,`extra-condensed`,`condensed`,`semi-condensed`,`normal`,`semi-expanded`,`expanded`,`extra-expanded`,`ultra-expanded`,Ae,Y]}],"font-family":[{font:[Ke,Ve,t]}],"font-features":[{"font-features":[Y]}],"fvn-normal":[`normal-nums`],"fvn-ordinal":[`ordinal`],"fvn-slashed-zero":[`slashed-zero`],"fvn-figure":[`lining-nums`,`oldstyle-nums`],"fvn-spacing":[`proportional-nums`,`tabular-nums`],"fvn-fraction":[`diagonal-fractions`,`stacked-fractions`],tracking:[{tracking:[i,Z,Y]}],"line-clamp":[{"line-clamp":[K,`none`,Z,ze]}],leading:[{leading:[a,...w()]}],"list-image":[{"list-image":[`none`,Z,Y]}],"list-style-position":[{list:[`inside`,`outside`]}],"list-style-type":[{list:[`disc`,`decimal`,`none`,Z,Y]}],"text-alignment":[{text:[`left`,`center`,`right`,`justify`,`start`,`end`]}],"placeholder-color":[{placeholder:P()}],"text-color":[{text:P()}],"text-decoration":[`underline`,`overline`,`line-through`,`no-underline`],"text-decoration-style":[{decoration:[...z(),`wavy`]}],"text-decoration-thickness":[{decoration:[K,`from-font`,`auto`,Z,X]}],"text-decoration-color":[{decoration:P()}],"underline-offset":[{"underline-offset":[K,`auto`,Z,Y]}],"text-transform":[`uppercase`,`lowercase`,`capitalize`,`normal-case`],"text-overflow":[`truncate`,`text-ellipsis`,`text-clip`],"text-wrap":[{text:[`wrap`,`nowrap`,`balance`,`pretty`]}],indent:[{indent:w()}],"tab-size":[{tab:[q,Z,Y]}],"vertical-align":[{align:[`baseline`,`top`,`middle`,`bottom`,`text-top`,`text-bottom`,`sub`,`super`,Z,Y]}],whitespace:[{whitespace:[`normal`,`nowrap`,`pre`,`pre-line`,`pre-wrap`,`break-spaces`]}],break:[{break:[`normal`,`words`,`all`,`keep`]}],wrap:[{wrap:[`break-word`,`anywhere`,`normal`]}],hyphens:[{hyphens:[`none`,`manual`,`auto`]}],content:[{content:[`none`,Z,Y]}],"bg-attachment":[{bg:[`fixed`,`local`,`scroll`]}],"bg-clip":[{"bg-clip":[`border`,`padding`,`content`,`text`]}],"bg-origin":[{"bg-origin":[`border`,`padding`,`content`]}],"bg-position":[{bg:F()}],"bg-repeat":[{bg:ne()}],"bg-size":[{bg:re()}],"bg-image":[{bg:[`none`,{linear:[{to:[`t`,`tr`,`r`,`br`,`b`,`bl`,`l`,`tl`]},q,Z,Y],radial:[``,Z,Y],conic:[q,Z,Y]},Ye,Ue]}],"bg-color":[{bg:P()}],"gradient-from-pos":[{from:I()}],"gradient-via-pos":[{via:I()}],"gradient-to-pos":[{to:I()}],"gradient-from":[{from:P()}],"gradient-via":[{via:P()}],"gradient-to":[{to:P()}],rounded:[{rounded:L()}],"rounded-s":[{"rounded-s":L()}],"rounded-e":[{"rounded-e":L()}],"rounded-t":[{"rounded-t":L()}],"rounded-r":[{"rounded-r":L()}],"rounded-b":[{"rounded-b":L()}],"rounded-l":[{"rounded-l":L()}],"rounded-ss":[{"rounded-ss":L()}],"rounded-se":[{"rounded-se":L()}],"rounded-ee":[{"rounded-ee":L()}],"rounded-es":[{"rounded-es":L()}],"rounded-tl":[{"rounded-tl":L()}],"rounded-tr":[{"rounded-tr":L()}],"rounded-br":[{"rounded-br":L()}],"rounded-bl":[{"rounded-bl":L()}],"border-w":[{border:R()}],"border-w-x":[{"border-x":R()}],"border-w-y":[{"border-y":R()}],"border-w-s":[{"border-s":R()}],"border-w-e":[{"border-e":R()}],"border-w-bs":[{"border-bs":R()}],"border-w-be":[{"border-be":R()}],"border-w-t":[{"border-t":R()}],"border-w-r":[{"border-r":R()}],"border-w-b":[{"border-b":R()}],"border-w-l":[{"border-l":R()}],"divide-x":[{"divide-x":R()}],"divide-x-reverse":[`divide-x-reverse`],"divide-y":[{"divide-y":R()}],"divide-y-reverse":[`divide-y-reverse`],"border-style":[{border:[...z(),`hidden`,`none`]}],"divide-style":[{divide:[...z(),`hidden`,`none`]}],"border-color":[{border:P()}],"border-color-x":[{"border-x":P()}],"border-color-y":[{"border-y":P()}],"border-color-s":[{"border-s":P()}],"border-color-e":[{"border-e":P()}],"border-color-bs":[{"border-bs":P()}],"border-color-be":[{"border-be":P()}],"border-color-t":[{"border-t":P()}],"border-color-r":[{"border-r":P()}],"border-color-b":[{"border-b":P()}],"border-color-l":[{"border-l":P()}],"divide-color":[{divide:P()}],"outline-style":[{outline:[...z(),`none`,`hidden`]}],"outline-offset":[{"outline-offset":[K,Z,Y]}],"outline-w":[{outline:[``,K,Ge,X]}],"outline-color":[{outline:P()}],shadow:[{shadow:[``,`none`,u,Xe,We]}],"shadow-color":[{shadow:P()}],"inset-shadow":[{"inset-shadow":[`none`,d,Xe,We]}],"inset-shadow-color":[{"inset-shadow":P()}],"ring-w":[{ring:R()}],"ring-w-inset":[`ring-inset`],"ring-color":[{ring:P()}],"ring-offset-w":[{"ring-offset":[K,X]}],"ring-offset-color":[{"ring-offset":P()}],"inset-ring-w":[{"inset-ring":R()}],"inset-ring-color":[{"inset-ring":P()}],"text-shadow":[{"text-shadow":[`none`,f,Xe,We]}],"text-shadow-color":[{"text-shadow":P()}],opacity:[{opacity:[K,Z,Y]}],"mix-blend":[{"mix-blend":[...B(),`plus-darker`,`plus-lighter`]}],"bg-blend":[{"bg-blend":B()}],"mask-clip":[{"mask-clip":[`border`,`padding`,`content`,`fill`,`stroke`,`view`]},`mask-no-clip`],"mask-composite":[{mask:[`add`,`subtract`,`intersect`,`exclude`]}],"mask-image-linear-pos":[{"mask-linear":[K]}],"mask-image-linear-from-pos":[{"mask-linear-from":V()}],"mask-image-linear-to-pos":[{"mask-linear-to":V()}],"mask-image-linear-from-color":[{"mask-linear-from":P()}],"mask-image-linear-to-color":[{"mask-linear-to":P()}],"mask-image-t-from-pos":[{"mask-t-from":V()}],"mask-image-t-to-pos":[{"mask-t-to":V()}],"mask-image-t-from-color":[{"mask-t-from":P()}],"mask-image-t-to-color":[{"mask-t-to":P()}],"mask-image-r-from-pos":[{"mask-r-from":V()}],"mask-image-r-to-pos":[{"mask-r-to":V()}],"mask-image-r-from-color":[{"mask-r-from":P()}],"mask-image-r-to-color":[{"mask-r-to":P()}],"mask-image-b-from-pos":[{"mask-b-from":V()}],"mask-image-b-to-pos":[{"mask-b-to":V()}],"mask-image-b-from-color":[{"mask-b-from":P()}],"mask-image-b-to-color":[{"mask-b-to":P()}],"mask-image-l-from-pos":[{"mask-l-from":V()}],"mask-image-l-to-pos":[{"mask-l-to":V()}],"mask-image-l-from-color":[{"mask-l-from":P()}],"mask-image-l-to-color":[{"mask-l-to":P()}],"mask-image-x-from-pos":[{"mask-x-from":V()}],"mask-image-x-to-pos":[{"mask-x-to":V()}],"mask-image-x-from-color":[{"mask-x-from":P()}],"mask-image-x-to-color":[{"mask-x-to":P()}],"mask-image-y-from-pos":[{"mask-y-from":V()}],"mask-image-y-to-pos":[{"mask-y-to":V()}],"mask-image-y-from-color":[{"mask-y-from":P()}],"mask-image-y-to-color":[{"mask-y-to":P()}],"mask-image-radial":[{"mask-radial":[Z,Y]}],"mask-image-radial-from-pos":[{"mask-radial-from":V()}],"mask-image-radial-to-pos":[{"mask-radial-to":V()}],"mask-image-radial-from-color":[{"mask-radial-from":P()}],"mask-image-radial-to-color":[{"mask-radial-to":P()}],"mask-image-radial-shape":[{"mask-radial":[`circle`,`ellipse`]}],"mask-image-radial-size":[{"mask-radial":[{closest:[`side`,`corner`],farthest:[`side`,`corner`]}]}],"mask-image-radial-pos":[{"mask-radial-at":b()}],"mask-image-conic-pos":[{"mask-conic":[K]}],"mask-image-conic-from-pos":[{"mask-conic-from":V()}],"mask-image-conic-to-pos":[{"mask-conic-to":V()}],"mask-image-conic-from-color":[{"mask-conic-from":P()}],"mask-image-conic-to-color":[{"mask-conic-to":P()}],"mask-mode":[{mask:[`alpha`,`luminance`,`match`]}],"mask-origin":[{"mask-origin":[`border`,`padding`,`content`,`fill`,`stroke`,`view`]}],"mask-position":[{mask:F()}],"mask-repeat":[{mask:ne()}],"mask-size":[{mask:re()}],"mask-type":[{"mask-type":[`alpha`,`luminance`]}],"mask-image":[{mask:[`none`,Z,Y]}],filter:[{filter:[``,`none`,Z,Y]}],blur:[{blur:ie()}],brightness:[{brightness:[K,Z,Y]}],contrast:[{contrast:[K,Z,Y]}],"drop-shadow":[{"drop-shadow":[``,`none`,p,Xe,We]}],"drop-shadow-color":[{"drop-shadow":P()}],grayscale:[{grayscale:[``,K,Z,Y]}],"hue-rotate":[{"hue-rotate":[K,Z,Y]}],invert:[{invert:[``,K,Z,Y]}],saturate:[{saturate:[K,Z,Y]}],sepia:[{sepia:[``,K,Z,Y]}],"backdrop-filter":[{"backdrop-filter":[``,`none`,Z,Y]}],"backdrop-blur":[{"backdrop-blur":ie()}],"backdrop-brightness":[{"backdrop-brightness":[K,Z,Y]}],"backdrop-contrast":[{"backdrop-contrast":[K,Z,Y]}],"backdrop-grayscale":[{"backdrop-grayscale":[``,K,Z,Y]}],"backdrop-hue-rotate":[{"backdrop-hue-rotate":[K,Z,Y]}],"backdrop-invert":[{"backdrop-invert":[``,K,Z,Y]}],"backdrop-opacity":[{"backdrop-opacity":[K,Z,Y]}],"backdrop-saturate":[{"backdrop-saturate":[K,Z,Y]}],"backdrop-sepia":[{"backdrop-sepia":[``,K,Z,Y]}],"border-collapse":[{border:[`collapse`,`separate`]}],"border-spacing":[{"border-spacing":w()}],"border-spacing-x":[{"border-spacing-x":w()}],"border-spacing-y":[{"border-spacing-y":w()}],"table-layout":[{table:[`auto`,`fixed`]}],caption:[{caption:[`top`,`bottom`]}],transition:[{transition:[``,`all`,`colors`,`opacity`,`shadow`,`transform`,`none`,Z,Y]}],"transition-behavior":[{transition:[`normal`,`discrete`]}],duration:[{duration:[K,`initial`,Z,Y]}],ease:[{ease:[`linear`,`initial`,_,Z,Y]}],delay:[{delay:[K,Z,Y]}],animate:[{animate:[`none`,v,Z,Y]}],backface:[{backface:[`hidden`,`visible`]}],perspective:[{perspective:[h,Z,Y]}],"perspective-origin":[{"perspective-origin":x()}],rotate:[{rotate:H()}],"rotate-x":[{"rotate-x":H()}],"rotate-y":[{"rotate-y":H()}],"rotate-z":[{"rotate-z":H()}],scale:[{scale:U()}],"scale-x":[{"scale-x":U()}],"scale-y":[{"scale-y":U()}],"scale-z":[{"scale-z":U()}],"scale-3d":[`scale-3d`],skew:[{skew:ae()}],"skew-x":[{"skew-x":ae()}],"skew-y":[{"skew-y":ae()}],transform:[{transform:[Z,Y,``,`none`,`gpu`,`cpu`]}],"transform-origin":[{origin:x()}],"transform-style":[{transform:[`3d`,`flat`]}],translate:[{translate:oe()}],"translate-x":[{"translate-x":oe()}],"translate-y":[{"translate-y":oe()}],"translate-z":[{"translate-z":oe()}],"translate-none":[`translate-none`],zoom:[{zoom:[q,Z,Y]}],accent:[{accent:P()}],appearance:[{appearance:[`none`,`auto`]}],"caret-color":[{caret:P()}],"color-scheme":[{scheme:[`normal`,`dark`,`light`,`light-dark`,`only-dark`,`only-light`]}],cursor:[{cursor:[`auto`,`default`,`pointer`,`wait`,`text`,`move`,`help`,`not-allowed`,`none`,`context-menu`,`progress`,`cell`,`crosshair`,`vertical-text`,`alias`,`copy`,`no-drop`,`grab`,`grabbing`,`all-scroll`,`col-resize`,`row-resize`,`n-resize`,`e-resize`,`s-resize`,`w-resize`,`ne-resize`,`nw-resize`,`se-resize`,`sw-resize`,`ew-resize`,`ns-resize`,`nesw-resize`,`nwse-resize`,`zoom-in`,`zoom-out`,Z,Y]}],"field-sizing":[{"field-sizing":[`fixed`,`content`]}],"pointer-events":[{"pointer-events":[`auto`,`none`]}],resize:[{resize:[`none`,``,`y`,`x`]}],"scroll-behavior":[{scroll:[`auto`,`smooth`]}],"scrollbar-thumb-color":[{"scrollbar-thumb":P()}],"scrollbar-track-color":[{"scrollbar-track":P()}],"scrollbar-gutter":[{"scrollbar-gutter":[`auto`,`stable`,`both`]}],"scrollbar-w":[{scrollbar:[`auto`,`thin`,`none`]}],"scroll-m":[{"scroll-m":w()}],"scroll-mx":[{"scroll-mx":w()}],"scroll-my":[{"scroll-my":w()}],"scroll-ms":[{"scroll-ms":w()}],"scroll-me":[{"scroll-me":w()}],"scroll-mbs":[{"scroll-mbs":w()}],"scroll-mbe":[{"scroll-mbe":w()}],"scroll-mt":[{"scroll-mt":w()}],"scroll-mr":[{"scroll-mr":w()}],"scroll-mb":[{"scroll-mb":w()}],"scroll-ml":[{"scroll-ml":w()}],"scroll-p":[{"scroll-p":w()}],"scroll-px":[{"scroll-px":w()}],"scroll-py":[{"scroll-py":w()}],"scroll-ps":[{"scroll-ps":w()}],"scroll-pe":[{"scroll-pe":w()}],"scroll-pbs":[{"scroll-pbs":w()}],"scroll-pbe":[{"scroll-pbe":w()}],"scroll-pt":[{"scroll-pt":w()}],"scroll-pr":[{"scroll-pr":w()}],"scroll-pb":[{"scroll-pb":w()}],"scroll-pl":[{"scroll-pl":w()}],"snap-align":[{snap:[`start`,`end`,`center`,`align-none`]}],"snap-stop":[{snap:[`normal`,`always`]}],"snap-type":[{snap:[`none`,`x`,`y`,`both`]}],"snap-strictness":[{snap:[`mandatory`,`proximity`]}],touch:[{touch:[`auto`,`none`,`manipulation`]}],"touch-x":[{"touch-pan":[`x`,`left`,`right`]}],"touch-y":[{"touch-pan":[`y`,`up`,`down`]}],"touch-pz":[`touch-pinch-zoom`],select:[{select:[`none`,`text`,`all`,`auto`]}],"will-change":[{"will-change":[`auto`,`scroll`,`contents`,`transform`,Z,Y]}],fill:[{fill:[`none`,...P()]}],"stroke-w":[{stroke:[K,Ge,X,ze]}],stroke:[{stroke:[`none`,...P()]}],"forced-color-adjust":[{"forced-color-adjust":[`auto`,`none`]}]},conflictingClassGroups:{"container-named":[`container-type`],overflow:[`overflow-x`,`overflow-y`],overscroll:[`overscroll-x`,`overscroll-y`],inset:[`inset-x`,`inset-y`,`inset-bs`,`inset-be`,`start`,`end`,`top`,`right`,`bottom`,`left`],"inset-x":[`right`,`left`],"inset-y":[`top`,`bottom`],flex:[`basis`,`grow`,`shrink`],gap:[`gap-x`,`gap-y`],p:[`px`,`py`,`ps`,`pe`,`pbs`,`pbe`,`pt`,`pr`,`pb`,`pl`],px:[`pr`,`pl`],py:[`pt`,`pb`],m:[`mx`,`my`,`ms`,`me`,`mbs`,`mbe`,`mt`,`mr`,`mb`,`ml`],mx:[`mr`,`ml`],my:[`mt`,`mb`],size:[`w`,`h`],"font-size":[`leading`],"fvn-normal":[`fvn-ordinal`,`fvn-slashed-zero`,`fvn-figure`,`fvn-spacing`,`fvn-fraction`],"fvn-ordinal":[`fvn-normal`],"fvn-slashed-zero":[`fvn-normal`],"fvn-figure":[`fvn-normal`],"fvn-spacing":[`fvn-normal`],"fvn-fraction":[`fvn-normal`],"line-clamp":[`display`,`overflow`],rounded:[`rounded-s`,`rounded-e`,`rounded-t`,`rounded-r`,`rounded-b`,`rounded-l`,`rounded-ss`,`rounded-se`,`rounded-ee`,`rounded-es`,`rounded-tl`,`rounded-tr`,`rounded-br`,`rounded-bl`],"rounded-s":[`rounded-ss`,`rounded-es`],"rounded-e":[`rounded-se`,`rounded-ee`],"rounded-t":[`rounded-tl`,`rounded-tr`],"rounded-r":[`rounded-tr`,`rounded-br`],"rounded-b":[`rounded-br`,`rounded-bl`],"rounded-l":[`rounded-tl`,`rounded-bl`],"border-spacing":[`border-spacing-x`,`border-spacing-y`],"border-w":[`border-w-x`,`border-w-y`,`border-w-s`,`border-w-e`,`border-w-bs`,`border-w-be`,`border-w-t`,`border-w-r`,`border-w-b`,`border-w-l`],"border-w-x":[`border-w-r`,`border-w-l`],"border-w-y":[`border-w-t`,`border-w-b`],"border-color":[`border-color-x`,`border-color-y`,`border-color-s`,`border-color-e`,`border-color-bs`,`border-color-be`,`border-color-t`,`border-color-r`,`border-color-b`,`border-color-l`],"border-color-x":[`border-color-r`,`border-color-l`],"border-color-y":[`border-color-t`,`border-color-b`],translate:[`translate-x`,`translate-y`,`translate-none`],"translate-none":[`translate`,`translate-x`,`translate-y`,`translate-z`],"scroll-m":[`scroll-mx`,`scroll-my`,`scroll-ms`,`scroll-me`,`scroll-mbs`,`scroll-mbe`,`scroll-mt`,`scroll-mr`,`scroll-mb`,`scroll-ml`],"scroll-mx":[`scroll-mr`,`scroll-ml`],"scroll-my":[`scroll-mt`,`scroll-mb`],"scroll-p":[`scroll-px`,`scroll-py`,`scroll-ps`,`scroll-pe`,`scroll-pbs`,`scroll-pbe`,`scroll-pt`,`scroll-pr`,`scroll-pb`,`scroll-pl`],"scroll-px":[`scroll-pr`,`scroll-pl`],"scroll-py":[`scroll-pt`,`scroll-pb`],touch:[`touch-x`,`touch-y`,`touch-pz`],"touch-x":[`touch`],"touch-y":[`touch`],"touch-pz":[`touch`]},conflictingClassGroupModifiers:{"font-size":[`leading`]},postfixLookupClassGroups:[`container-type`],orderSensitiveModifiers:[`*`,`**`,`after`,`backdrop`,`before`,`details-content`,`file`,`first-letter`,`first-line`,`marker`,`placeholder`,`selection`]}});function st(...e){return ot(A(e))}export{v as a,_ as c,o as d,s as f,O as i,m as l,l as m,ot as n,g as o,u as p,A as r,y as s,st as t,f as u}; No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify release configuration injection for the console bundle.
rg -n -C 3 'appVersion|storageNamespace|mesh-llm-ui-preview|0\.74\.0' crates/mesh-llm-ui sdk/kotlin

Repository: Mesh-LLM/mesh-llm

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tracked relevant files =="
git ls-files sdk/kotlin crates/mesh-llm-ui | sed -n '1,200p'

echo
echo "== search only config-adjacent files for appVersion/storageNamespace/0.74.0/mesh-llm-ui-preview =="
rg -n -C 2 'appVersion|storageNamespace|mesh-llm-ui-preview|0\.74\.0' sdk/kotlin crates/mesh-llm-ui \
  -g '!**/node_modules/**' -g '!**/target/**' -g '!**/dist/**' -g '!**/build/**' \
  | sed -n '1,240p'

echo
echo "== search build/config files for asset handling/replace/inject =="
rg -n -C 3 'assets/cn-DzJZ_V3X\.js|DzJZ_V3X|replace\(|substitute\(|env\(|APP_VERSION|APP_|storageNamespace|mesh-llm-ui-preview|vite|rollup|webpack|rolldown' sdk kotlin crates/mesh-llm-ui \
  -g '!**/node_modules/**' -g '!**/target/**' -g '!**/dist/**' -g '!**/build/**' \
  | sed -n '1,240p'

Repository: Mesh-LLM/mesh-llm

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="sdk/kotlin/src/main/resources/mesh-llm/console/assets/cn-DzJZ_V3X.js"
echo "== bundle size/first 80 chars =="
wc -c "$file"
head -c 2000 "$file" | sed -n '1p'

python3 - <<'PY'
from pathlib import Path
text=Path('sdk/kotlin/src/main/resources/mesh-llm/console/assets/cn-DzJZ_V3X.js').read_text(errors='replace')
needle='appVersion:'
pos=text.find(needle)
print('appVersion_pos=', pos)
if pos!=-1:
    print(text[pos:pos+120])
needle='storageNamespace:'
pos=text.find(needle)
print('storageNamespace_pos=', pos)
if pos!=-1:
    print(text[pos:pos+180])
print('contains_074=', '0.74.0' in text)
PY

Repository: Mesh-LLM/mesh-llm

Length of output: 2633


Replace release-incompatible UI defaults before packaging.

The bundled Kotlin console asset contains appVersion: "dev" and storageNamespace: "mesh-llm-ui-preview". Use the release build to replace these values, or regenerate the Kotlin assets, so the console reports the release version and stores UI state with a release-scoped namespace.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/kotlin/src/main/resources/mesh-llm/console/assets/cn-DzJZ_V3X.js` at line
1, Replace the bundled configuration defaults in the exported asset’s
configuration object, specifically appVersion and storageNamespace, with release
build values before packaging. Prefer regenerating the Kotlin console assets
through the release build so these values are generated consistently rather than
manually editing the minified bundle.

@@ -0,0 +1 @@
import{i as e,l as t,m as n,u as r}from"./cn-DzJZ_V3X.js";import{t as i}from"./useRouter-Ds6vf8iZ.js";import{t as a}from"./shield-alert-CUQLlWwc.js";import{S as o,b as s,v as c,x as l,yt as u}from"./index-DOcqlpTd.js";import{i as d,n as f,r as p,t as m}from"./host-surface-Dpepihlo.js";import{t as h}from"./InfoBanner-lIYx64rk.js";var g=e(`boxes`,[[`path`,{d:`M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z`,key:`lc1i9w`}],[`path`,{d:`m7 16.5-4.74-2.85`,key:`1o9zyk`}],[`path`,{d:`m7 16.5 5-3`,key:`va8pkn`}],[`path`,{d:`M7 16.5v5.17`,key:`jnp8gn`}],[`path`,{d:`M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z`,key:`8zsnat`}],[`path`,{d:`m17 16.5-5-3`,key:`8arw3v`}],[`path`,{d:`m17 16.5 4.74-2.85`,key:`8rfmw`}],[`path`,{d:`M17 16.5v5.17`,key:`k6z78m`}],[`path`,{d:`M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z`,key:`1xygjf`}],[`path`,{d:`M12 8 7.26 5.15`,key:`1vbdud`}],[`path`,{d:`m12 8 4.74-2.85`,key:`3rx089`}],[`path`,{d:`M12 13.5V8`,key:`1io7kd`}]]),_=n(r(),1),v=t();function y(e){switch(e.state){case`none`:return{kind:`fallback`,title:`Plugin web UI is not declared`,detail:`This plugin does not publish host-projected console pages.`,tone:`muted`};case`disabled`:return{kind:`fallback`,title:`Plugin web UI is disabled`,detail:e.unavailable_reason??`The plugin remains available, but its console projection is turned off.`,tone:`warn`};case`invalid`:return{kind:`fallback`,title:`Plugin web UI bundle is invalid`,detail:e.unavailable_reason??`The host rejected the declared bundle metadata or assets.`,tone:`bad`};case`plugin_not_running`:return{kind:`fallback`,title:`Plugin is not running`,detail:e.unavailable_reason??`The UI declaration is known, but the plugin process is unavailable.`,tone:`warn`};case`ready`:return{kind:`fallback`,title:`Plugin page is not declared`,detail:`The requested page id is not part of the ready plugin web UI declaration.`,tone:`muted`};default:return b(e.state)}}function b(e){throw TypeError(`Unhandled plugin web UI state: ${String(e)}`)}function x(e,t){if(!e)return null;if(e.state!==`ready`||!e.declared||!e.enabled||!e.available)return y(e);if(!e.asset_base_url)return{kind:`fallback`,title:`Plugin web UI asset route is unavailable`,detail:`The host marked this plugin ready without a bundle asset base URL.`,tone:`bad`};let n=e.pages?.find(e=>e.id===t);return n?{kind:`ready`,page:n}:y(e)}function S(e,t,n){let r=new URL(c(e,t,n),window.location.origin);if(r.origin!==window.location.origin)throw TypeError(`Plugin web UI asset URL must be same-origin`);return r.href}function C(e){let t=!0;return()=>{t&&(t=!1,e.unmount())}}function w(e){return e instanceof Error?e.message:`Plugin bundle import failed`}function T({detail:e,title:t,tone:n}){return(0,v.jsx)(h,{className:`mt-0`,description:e,leadingIcon:(0,v.jsx)(n===`bad`?a:g,{className:`size-4`,"aria-hidden":`true`}),leadingIconClassName:n===`bad`?`text-bad`:n===`warn`?`text-warn`:`text-fg-faint`,title:t,titleId:`plugin-web-ui-fallback-title`,titleLevel:`h1`})}function E(){let{pluginName:e,pageId:t}=u({from:`/plugins/$pluginName/$pageId`}),n=i(),r=o(e),a=l(e,{enabled:r.data?.state===`ready`}),c=s(e),h=(0,_.useRef)(c.mutateAsync),g=(0,_.useRef)(null),[y,b]=(0,_.useState)({kind:`idle`}),[E,D]=(0,_.useState)(null),O=(0,_.useMemo)(()=>x(r.data,t),[r.data,t]);return(0,_.useEffect)(()=>{h.current=c.mutateAsync},[c.mutateAsync]),(0,_.useEffect)(()=>{if(O?.kind!==`ready`||!r.data||!a.data){let e=window.setTimeout(()=>b({kind:`idle`}),0);return()=>window.clearTimeout(e)}let t=!1,i,o=window.setTimeout(()=>{t||b({kind:`loading`})},0);return(async()=>{let o=await d(S(e,r.data,O.page.entry_script)),s=m({pluginName:e,page:O.page,webUi:r.data,visibleConfig:a.data,navigateTo:e=>void n.navigate({to:e}),openPluginPage:t=>void n.navigate({to:`/plugins/$pluginName/$pageId`,params:{pluginName:e,pageId:t}}),requestConfigMutation:e=>h.current(e),showToast:e=>D(e.description?`${e.title}: ${e.description}`:e.title)}),c=await o.registerMeshPluginUi(s);p(c);let l=c.pages[O.page.id],u=g.current;if(!l||!u){b({kind:`error`,message:`Plugin bundle did not register the requested page.`});return}let _=await l({element:u,host:s,page:O.page});if(f(_),i=C(_),t){i();return}b({kind:`mounted`})})().catch(e=>{t||b({kind:`error`,message:w(e)})}),()=>{t=!0,window.clearTimeout(o),i?.()}},[O,r.data,e,n,a.data]),r.isPending?(0,v.jsx)(T,{kind:`fallback`,title:`Loading plugin web UI`,detail:`Fetching plugin UI metadata.`,tone:`muted`}):r.isError?(0,v.jsx)(T,{kind:`fallback`,title:`Plugin web UI metadata unavailable`,detail:`The host could not fetch this plugin web UI declaration.`,tone:`bad`}):O?O.kind===`fallback`?(0,v.jsx)(T,{...O}):(0,v.jsxs)(`section`,{className:`panel-shell flex min-h-[24rem] flex-col rounded-[var(--radius-lg)] border border-border bg-panel`,children:[(0,v.jsxs)(`header`,{className:`border-b border-border-soft px-5 py-4`,children:[(0,v.jsx)(`div`,{className:`type-label text-fg-faint`,children:`Plugin page`}),(0,v.jsx)(`h1`,{className:`type-headline mt-1 text-foreground`,children:O.page.label}),(0,v.jsxs)(`p`,{className:`type-caption mt-1 text-fg-dim`,children:[`Mounted from `,(0,v.jsx)(`span`,{className:`font-mono text-foreground`,children:e}),` page`,` `,(0,v.jsx)(`span`,{className:`font-mono text-foreground`,children:O.page.id}),`.`]})]}),y.kind===`loading`?(0,v.jsx)(`div`,{className:`border-b border-border-soft px-5 py-2 text-[length:var(--density-type-caption)] text-fg-faint`,children:`Loading plugin bundle...`}):null,y.kind===`error`?(0,v.jsx)(`div`,{className:`border-b border-border-soft px-5 py-2 text-[length:var(--density-type-caption)] text-bad`,role:`alert`,children:y.message}):null,E?(0,v.jsx)(`div`,{className:`border-b border-border-soft px-5 py-2 text-[length:var(--density-type-caption)] text-foreground`,role:`status`,children:E}):null,(0,v.jsx)(`section`,{ref:g,className:`min-h-0 flex-1 p-5`,"aria-label":`${O.page.label} plugin host`}),y.kind===`mounted`?(0,v.jsx)(`div`,{className:`sr-only`,"aria-live":`polite`,children:`Plugin page mounted`}):null]}):null}export{E as PluginWebUiRoutePage}; No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle visible-configuration failures before mounting the plugin.

Line [1] exits the mount effect when the visible configuration query has no data. The route still renders the page header and an empty host because its local state remains idle. A terminal configuration error prevents the plugin page from mounting and gives the user no feedback. Add explicit pending and error handling for the visible configuration query, then rebuild this asset.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@sdk/kotlin/src/main/resources/mesh-llm/console/assets/PluginWebUiRoutePage-BR5NTyZb.js`
at line 1, Update the PluginWebUiRoutePage mount flow to handle the visible
configuration query state explicitly before attempting plugin mounting: show a
loading fallback while the configuration is pending and an error fallback when
it fails, rather than leaving local mount state idle with an empty host.
Preserve the existing successful configuration path, and rebuild the generated
bundle after changing the source.

@@ -0,0 +1 @@
import{l as e,m as t,u as n}from"./cn-DzJZ_V3X.js";import{c as r,d as i,f as a,h as o,l as s,m as ee,n as te,o as c,p as ne,s as l,t as re,u as ie}from"./ConnectBlock-Q9VL_lJC.js";import{D as u}from"./tooltip-B7o_GgNn.js";import{i as ae}from"./format-model-size-DleXDH2g.js";import{t as oe}from"./MeshViz-DGmQQ8Xw.js";import{n as d}from"./vram-_ZXANuL0.js";import{M as se,at as f,ht as ce,it as p,w as le}from"./index-DOcqlpTd.js";import{t as ue}from"./use-models-query-B93o-dqC.js";import{t as de}from"./models-adapter-BtD8xj0i.js";var m=t(n(),1),h={title:`Welcome to the public mesh`,description:`Run open models with shared community capacity. Start a local node to contribute compute or use the OpenAI-compatible endpoint.`,actions:u.hero.actions},g={...u.connect,runCommand:`mesh-llm --auto`,description:`join the public mesh`};function _(e){return f(e)?h:u.hero}function v(e){return f(e)?g:u.connect}function y(e){return e===`client`||e===`standby`||e===`loading`||e===`serving`}function b(e){return e===`loading`?`degraded`:y(e)?`online`:`offline`}function x(e){return typeof e==`string`?e:e.name}function S(e,t){return e.node_id??e.id??e.hostname??`peer-${t}`}function C(e){return e.node_state??e.state??e.role?.toLowerCase()}function w(e){let t=C(e);if(y(t))return t}function T(e){let t=e.role?.toLowerCase(),n=w(e);return t===`host`?`host`:t===`worker`?`worker`:t===`client`||n===`client`?`client`:`peer`}function E(e){let t=new Set,n=[];for(let r of e){let e=r?.trim();!e||t.has(e)||(t.add(e),n.push(e))}return n}function D(e){let t=[e.serving_models,e.hosted_models,e.models];for(let e of t){let t=E(e??[]);if(t.length>0)return t}return[]}function O(e){return typeof e!=`number`||!Number.isFinite(e)?0:Math.min(Math.max(Math.round(e),0),100)}function k(e){return typeof e==`string`?e:e?.display_name??e?.name??e?.status}function A(e){return typeof e==`number`&&Number.isFinite(e)?e:0}function j(e){if(!e?.length)return null;let t=e.reduce((e,t)=>e+(d(t)??0),0);return t>0?t:null}function M(e){return A(j(e.gpus)??e.my_vram_gb??e.vram_gb??void 0)}function N(e){let t=A(j(e.gpus)??e.my_vram_gb??void 0);return e.peers.reduce((e,t)=>e+M(t),t)}function P(e){return A(e.inflight_requests)}function F(e,t){let n=S(e,t),r=w(e);return{id:n,hostname:e.hostname??n,region:e.region??``,status:b(C(e)),hostedModels:D(e),sharePct:O(e.share_pct),latencyMs:e.latency_ms??e.rtt_ms??null,latencySource:e.latency_source??null,latencyAgeMs:e.latency_age_ms??null,latencyObserverId:e.latency_observer_id??null,loadPct:e.load_pct??0,shortId:n.slice(0,8),version:e.version,vramGB:M(e),role:T(e),nodeState:r,toksPerSec:e.tok_per_sec,hardwareLabel:e.hardware_label,owner:k(e.owner),firstJoinedMeshTs:e.first_joined_mesh_ts}}function I(e){return(e.runtime?.stages??[]).some(t=>t.node_id===e.node_id)}function L(e){let t=I(e),n=E([...e.serving_models.map(x),e.node_state===`serving`?e.model_name:void 0]),r=t&&e.node_state===`standby`?`serving`:e.node_state;return{id:e.node_id,hostname:e.hostname??e.my_hostname??`localhost`,region:e.region??``,status:b(r),hostedModels:n,sharePct:0,latencyMs:0,latencySource:null,latencyAgeMs:null,latencyObserverId:null,loadPct:e.load_pct??0,shortId:e.node_id.slice(0,8),role:`you`,nodeState:r,version:e.version,vramGB:j(e.gpus)??e.my_vram_gb,toksPerSec:e.tok_per_sec,firstJoinedMeshTs:e.first_joined_mesh_ts}}function R(e){if(e.some(e=>e.sharePct>0))return e;let t=e.filter(e=>e.nodeState===`serving`||e.hostedModels.length>0).reduce((e,t)=>e+(t.vramGB??0),0);return t<=0?e:e.map(e=>({...e,sharePct:e.nodeState===`serving`||e.hostedModels.length>0?O((e.vramGB??0)/t*100):e.sharePct}))}function z(e){let t=e.filter(e=>e.status===`online`).length,n=e.reduce((e,t)=>e+(t.vramGB??0),0);return{total:e.length,online:t,capacity:`${n.toFixed(0)} GB`}}function B(e){let t=E([...e.serving_models.map(x),e.node_state===`serving`?e.model_name:void 0]),n=E(e.peers.flatMap(D)),r=E([...t,...n]),i=N(e),a=e.peers.length,o=P(e),s=k(e.owner)??`Unsigned`;return[{id:`node-id`,label:`Node ID`,value:e.node_id||`n/a`,variant:`identity`,mono:!0,badge:{label:e.node_state,tone:e.node_state===`serving`?`good`:e.node_state===`loading`?`warn`:`muted`}},{id:`owner`,label:`Owner`,value:s,variant:`identity`,mono:!0,badge:{label:s.toLowerCase()===`unsigned`?`not cryptographically bound`:`verified identity`,tone:`muted`}},{id:`nodes`,label:`Nodes`,value:a+1,meta:`1 you · ${a} peers`},{id:`active-models`,label:`Active models`,value:r.length,meta:`${t.length} loaded locally · ${n.length} remote`},{id:`mesh-vram`,label:`Mesh VRAM`,value:i.toFixed(1),unit:`GB`},{id:`inflight`,label:`Inflight`,value:o}]}function V(e){return[{id:e.node_id,label:e.hostname??`localhost`,x:50,y:50,status:b(e.node_state),role:`self`,meshState:e.node_state,servingModels:e.serving_models.map(x),hostname:e.hostname,vramGB:e.my_vram_gb,firstJoinedMeshTs:e.first_joined_mesh_ts}]}function fe(e,t=[]){let n=R([L(e),...e.peers.map(F)]);return{...u,hero:_(e),peers:n,peerSummary:z(n),statusMetrics:B(e),meshNodeSeeds:V(e),meshId:e.mesh_id??``,models:t,connect:v(e),wakeableNodes:e.wakeable_nodes}}var H=e(),pe=1e4;function me(e,t){if(t)return e.find(e=>e.id===t.peerId||e.id===t.id)}function he(e,t){return t?e.isError?`unavailable`:e.data?`live`:`checking`:`configured`}function U(e,t){let n=t?.meshId===e.meshId?t.nodes:[];return{source:e,meshId:e.meshId,nodes:l(n,e.peers,e.meshId,e.meshNodeSeeds)}}function W({data:e=u,liveFetchPaused:t,setLiveFetchPaused:n}){let{mode:l,setMode:d}=ce(),f=l===`live`,h=f&&!t,g=le({enabled:h}),_=ue({enabled:h}),v=he(g,f),y=(0,m.useMemo)(()=>_.data?de(_.data.mesh_models):void 0,[_.data]),b=(0,m.useMemo)(()=>g.data?fe(g.data,y??[]):void 0,[y,g.data]),x=f?p(g.data):p(),S=(f?b:e)??e,C=!!b,w=f&&!C?void 0:S.meshId??`dashboard-mesh`,[T,E]=(0,m.useState)(),[D,O]=(0,m.useState)(()=>U(S,void 0)),k=w!==void 0&&T===w;(0,m.useEffect)(()=>{if(!f){n(!1);return}if(t||C)return;let e=window.setTimeout(()=>{n(!0)},pe);return()=>window.clearTimeout(e)},[C,t,f,n]);let A=f&&t,j=f&&!b&&!A,M=D;M.source!==S&&(M=U(S,M),O(M));let N=M.nodes,P=N.find(e=>e.role===`self`)?.id??`self`,F=(0,m.useMemo)(()=>new Map(S.peers.map(e=>[e.id,e])),[S.peers]),I=(0,m.useCallback)(e=>e.peerId?F.get(e.peerId):F.get(e.id),[F]),[L,R]=(0,m.useState)(void 0),[z,B]=(0,m.useState)(),[V,W]=(0,m.useState)(),[G,K]=(0,m.useState)([]),[q,J]=(0,m.useState)(null),{copyState:ge,copyText:Y}=se(),X=L&&S.models.some(e=>e.name===L.name)?L:void 0,Z=z&&S.peers.some(e=>e.id===z.peerId||e.id===z.id)?z:void 0,Q=me(S.peers,Z),_e=(0,m.useMemo)(()=>{if(!V)return;let e=F.get(V);if(e)return c(e,N).id},[V,N,F]),ve=(0,m.useMemo)(()=>{if(G.length===0||G.length===S.peers.length)return;let e=new Set(G);return new Set(N.filter(t=>!e.has(t.peerId??t.id)).map(e=>e.id))},[S.peers.length,G,N]),ye=e=>{R(e),J(`model`)},$=e=>{B(e),J(`node`)},be=e=>$(c(e,N)),xe=()=>J(null),Se=()=>{J(null),R(void 0)},Ce=(0,m.useCallback)(()=>{Y(S.connect.runCommand)},[Y,S.connect.runCommand]),we=(0,m.useCallback)(()=>{n(!1),Promise.all([g.refetch(),_.refetch()])},[_,n,g]),Te=(0,m.useCallback)(()=>d(`harness`),[d]);return A?(0,H.jsx)(ae,{debugTitle:`Could not reach the local mesh backend`,title:`Live mesh data is unavailable`,debugDescription:`The dashboard could not fetch the initial status and model catalog from the configured API target. Start the backend, verify the endpoint, or switch Data source back to Harness in Tweaks while debugging.`,productionDescription:`The dashboard is waiting for live mesh status and model data. Keep the page open while the service recovers.`,onRetry:we,onSwitchToTestData:Te,children:(0,H.jsx)(r,{})}):j?(0,H.jsx)(r,{}):(0,H.jsx)(ie,{hero:(0,H.jsx)(a,{title:S.hero.title,description:S.hero.description,actions:S.hero.actions}),status:(0,H.jsx)(te,{metrics:S.statusMetrics}),topology:(0,H.jsxs)(`div`,{className:`relative h-full min-h-0`,children:[(0,H.jsx)(oe,{nodes:N,selfId:P,meshId:S.meshId??`dashboard-mesh`,selectedNodeId:Z?.id,hoveredNodeId:_e,dimmedNodeIds:ve,onPick:$,onReady:()=>E(w),getNodePeer:I},w),!k&&(0,H.jsx)(`div`,{className:`pointer-events-none absolute inset-0`,children:(0,H.jsx)(s,{})})]}),catalog:(0,H.jsx)(ne,{models:S.models,selectedModelName:X?.name,onSelect:ye}),peers:(0,H.jsx)(i,{peers:S.peers,models:S.models,summary:S.peerSummary,selectedPeerId:Q?.id,onSelect:be,onHoverPeerIdChange:W,onFilteredPeerIdsChange:K}),connect:(0,H.jsx)(re,{installHref:S.connect.installHref,apiUrl:x,apiStatus:S.connect.apiStatus,apiTargetLiveness:v,runCommand:S.connect.runCommand,description:S.connect.description,copyState:ge,onCopy:Ce}),drawers:(0,H.jsxs)(H.Fragment,{children:[(0,H.jsx)(o,{open:q===`model`,model:X,peers:S.peers,onClose:Se}),(0,H.jsx)(ee,{open:q===`node`,node:Z,peer:Q,models:S.models,onClose:xe})]})})}function G(e={}){let[t,n]=(0,m.useState)(!1);return(0,H.jsx)(H.Fragment,{children:(0,H.jsx)(W,{...e,liveFetchPaused:t,setLiveFetchPaused:n})})}function K(e={}){return(0,H.jsx)(G,{...e})}export{K as DashboardPage,G as DashboardPageSurface};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not mask recovered live data after the timeout.

A is set to f&&t, so the unavailable overlay wins whenever liveFetchPaused is true. C already indicates that b contains live data, but it is not part of A. If an in-flight request completes after the timeout, the dashboard has valid data but still renders the error overlay.

Gate the overlay with !C, or clear liveFetchPaused when C becomes true.

Suggested source-level fix
-let A=f&&t
+let A=f&&t&&!C
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import{l as e,m as t,u as n}from"./cn-DzJZ_V3X.js";import{c as r,d as i,f as a,h as o,l as s,m as ee,n as te,o as c,p as ne,s as l,t as re,u as ie}from"./ConnectBlock-Q9VL_lJC.js";import{D as u}from"./tooltip-B7o_GgNn.js";import{i as ae}from"./format-model-size-DleXDH2g.js";import{t as oe}from"./MeshViz-DGmQQ8Xw.js";import{n as d}from"./vram-_ZXANuL0.js";import{M as se,at as f,ht as ce,it as p,w as le}from"./index-DOcqlpTd.js";import{t as ue}from"./use-models-query-B93o-dqC.js";import{t as de}from"./models-adapter-BtD8xj0i.js";var m=t(n(),1),h={title:`Welcome to the public mesh`,description:`Run open models with shared community capacity. Start a local node to contribute compute or use the OpenAI-compatible endpoint.`,actions:u.hero.actions},g={...u.connect,runCommand:`mesh-llm --auto`,description:`join the public mesh`};function _(e){return f(e)?h:u.hero}function v(e){return f(e)?g:u.connect}function y(e){return e===`client`||e===`standby`||e===`loading`||e===`serving`}function b(e){return e===`loading`?`degraded`:y(e)?`online`:`offline`}function x(e){return typeof e==`string`?e:e.name}function S(e,t){return e.node_id??e.id??e.hostname??`peer-${t}`}function C(e){return e.node_state??e.state??e.role?.toLowerCase()}function w(e){let t=C(e);if(y(t))return t}function T(e){let t=e.role?.toLowerCase(),n=w(e);return t===`host`?`host`:t===`worker`?`worker`:t===`client`||n===`client`?`client`:`peer`}function E(e){let t=new Set,n=[];for(let r of e){let e=r?.trim();!e||t.has(e)||(t.add(e),n.push(e))}return n}function D(e){let t=[e.serving_models,e.hosted_models,e.models];for(let e of t){let t=E(e??[]);if(t.length>0)return t}return[]}function O(e){return typeof e!=`number`||!Number.isFinite(e)?0:Math.min(Math.max(Math.round(e),0),100)}function k(e){return typeof e==`string`?e:e?.display_name??e?.name??e?.status}function A(e){return typeof e==`number`&&Number.isFinite(e)?e:0}function j(e){if(!e?.length)return null;let t=e.reduce((e,t)=>e+(d(t)??0),0);return t>0?t:null}function M(e){return A(j(e.gpus)??e.my_vram_gb??e.vram_gb??void 0)}function N(e){let t=A(j(e.gpus)??e.my_vram_gb??void 0);return e.peers.reduce((e,t)=>e+M(t),t)}function P(e){return A(e.inflight_requests)}function F(e,t){let n=S(e,t),r=w(e);return{id:n,hostname:e.hostname??n,region:e.region??``,status:b(C(e)),hostedModels:D(e),sharePct:O(e.share_pct),latencyMs:e.latency_ms??e.rtt_ms??null,latencySource:e.latency_source??null,latencyAgeMs:e.latency_age_ms??null,latencyObserverId:e.latency_observer_id??null,loadPct:e.load_pct??0,shortId:n.slice(0,8),version:e.version,vramGB:M(e),role:T(e),nodeState:r,toksPerSec:e.tok_per_sec,hardwareLabel:e.hardware_label,owner:k(e.owner),firstJoinedMeshTs:e.first_joined_mesh_ts}}function I(e){return(e.runtime?.stages??[]).some(t=>t.node_id===e.node_id)}function L(e){let t=I(e),n=E([...e.serving_models.map(x),e.node_state===`serving`?e.model_name:void 0]),r=t&&e.node_state===`standby`?`serving`:e.node_state;return{id:e.node_id,hostname:e.hostname??e.my_hostname??`localhost`,region:e.region??``,status:b(r),hostedModels:n,sharePct:0,latencyMs:0,latencySource:null,latencyAgeMs:null,latencyObserverId:null,loadPct:e.load_pct??0,shortId:e.node_id.slice(0,8),role:`you`,nodeState:r,version:e.version,vramGB:j(e.gpus)??e.my_vram_gb,toksPerSec:e.tok_per_sec,firstJoinedMeshTs:e.first_joined_mesh_ts}}function R(e){if(e.some(e=>e.sharePct>0))return e;let t=e.filter(e=>e.nodeState===`serving`||e.hostedModels.length>0).reduce((e,t)=>e+(t.vramGB??0),0);return t<=0?e:e.map(e=>({...e,sharePct:e.nodeState===`serving`||e.hostedModels.length>0?O((e.vramGB??0)/t*100):e.sharePct}))}function z(e){let t=e.filter(e=>e.status===`online`).length,n=e.reduce((e,t)=>e+(t.vramGB??0),0);return{total:e.length,online:t,capacity:`${n.toFixed(0)} GB`}}function B(e){let t=E([...e.serving_models.map(x),e.node_state===`serving`?e.model_name:void 0]),n=E(e.peers.flatMap(D)),r=E([...t,...n]),i=N(e),a=e.peers.length,o=P(e),s=k(e.owner)??`Unsigned`;return[{id:`node-id`,label:`Node ID`,value:e.node_id||`n/a`,variant:`identity`,mono:!0,badge:{label:e.node_state,tone:e.node_state===`serving`?`good`:e.node_state===`loading`?`warn`:`muted`}},{id:`owner`,label:`Owner`,value:s,variant:`identity`,mono:!0,badge:{label:s.toLowerCase()===`unsigned`?`not cryptographically bound`:`verified identity`,tone:`muted`}},{id:`nodes`,label:`Nodes`,value:a+1,meta:`1 you · ${a} peers`},{id:`active-models`,label:`Active models`,value:r.length,meta:`${t.length} loaded locally · ${n.length} remote`},{id:`mesh-vram`,label:`Mesh VRAM`,value:i.toFixed(1),unit:`GB`},{id:`inflight`,label:`Inflight`,value:o}]}function V(e){return[{id:e.node_id,label:e.hostname??`localhost`,x:50,y:50,status:b(e.node_state),role:`self`,meshState:e.node_state,servingModels:e.serving_models.map(x),hostname:e.hostname,vramGB:e.my_vram_gb,firstJoinedMeshTs:e.first_joined_mesh_ts}]}function fe(e,t=[]){let n=R([L(e),...e.peers.map(F)]);return{...u,hero:_(e),peers:n,peerSummary:z(n),statusMetrics:B(e),meshNodeSeeds:V(e),meshId:e.mesh_id??``,models:t,connect:v(e),wakeableNodes:e.wakeable_nodes}}var H=e(),pe=1e4;function me(e,t){if(t)return e.find(e=>e.id===t.peerId||e.id===t.id)}function he(e,t){return t?e.isError?`unavailable`:e.data?`live`:`checking`:`configured`}function U(e,t){let n=t?.meshId===e.meshId?t.nodes:[];return{source:e,meshId:e.meshId,nodes:l(n,e.peers,e.meshId,e.meshNodeSeeds)}}function W({data:e=u,liveFetchPaused:t,setLiveFetchPaused:n}){let{mode:l,setMode:d}=ce(),f=l===`live`,h=f&&!t,g=le({enabled:h}),_=ue({enabled:h}),v=he(g,f),y=(0,m.useMemo)(()=>_.data?de(_.data.mesh_models):void 0,[_.data]),b=(0,m.useMemo)(()=>g.data?fe(g.data,y??[]):void 0,[y,g.data]),x=f?p(g.data):p(),S=(f?b:e)??e,C=!!b,w=f&&!C?void 0:S.meshId??`dashboard-mesh`,[T,E]=(0,m.useState)(),[D,O]=(0,m.useState)(()=>U(S,void 0)),k=w!==void 0&&T===w;(0,m.useEffect)(()=>{if(!f){n(!1);return}if(t||C)return;let e=window.setTimeout(()=>{n(!0)},pe);return()=>window.clearTimeout(e)},[C,t,f,n]);let A=f&&t,j=f&&!b&&!A,M=D;M.source!==S&&(M=U(S,M),O(M));let N=M.nodes,P=N.find(e=>e.role===`self`)?.id??`self`,F=(0,m.useMemo)(()=>new Map(S.peers.map(e=>[e.id,e])),[S.peers]),I=(0,m.useCallback)(e=>e.peerId?F.get(e.peerId):F.get(e.id),[F]),[L,R]=(0,m.useState)(void 0),[z,B]=(0,m.useState)(),[V,W]=(0,m.useState)(),[G,K]=(0,m.useState)([]),[q,J]=(0,m.useState)(null),{copyState:ge,copyText:Y}=se(),X=L&&S.models.some(e=>e.name===L.name)?L:void 0,Z=z&&S.peers.some(e=>e.id===z.peerId||e.id===z.id)?z:void 0,Q=me(S.peers,Z),_e=(0,m.useMemo)(()=>{if(!V)return;let e=F.get(V);if(e)return c(e,N).id},[V,N,F]),ve=(0,m.useMemo)(()=>{if(G.length===0||G.length===S.peers.length)return;let e=new Set(G);return new Set(N.filter(t=>!e.has(t.peerId??t.id)).map(e=>e.id))},[S.peers.length,G,N]),ye=e=>{R(e),J(`model`)},$=e=>{B(e),J(`node`)},be=e=>$(c(e,N)),xe=()=>J(null),Se=()=>{J(null),R(void 0)},Ce=(0,m.useCallback)(()=>{Y(S.connect.runCommand)},[Y,S.connect.runCommand]),we=(0,m.useCallback)(()=>{n(!1),Promise.all([g.refetch(),_.refetch()])},[_,n,g]),Te=(0,m.useCallback)(()=>d(`harness`),[d]);return A?(0,H.jsx)(ae,{debugTitle:`Could not reach the local mesh backend`,title:`Live mesh data is unavailable`,debugDescription:`The dashboard could not fetch the initial status and model catalog from the configured API target. Start the backend, verify the endpoint, or switch Data source back to Harness in Tweaks while debugging.`,productionDescription:`The dashboard is waiting for live mesh status and model data. Keep the page open while the service recovers.`,onRetry:we,onSwitchToTestData:Te,children:(0,H.jsx)(r,{})}):j?(0,H.jsx)(r,{}):(0,H.jsx)(ie,{hero:(0,H.jsx)(a,{title:S.hero.title,description:S.hero.description,actions:S.hero.actions}),status:(0,H.jsx)(te,{metrics:S.statusMetrics}),topology:(0,H.jsxs)(`div`,{className:`relative h-full min-h-0`,children:[(0,H.jsx)(oe,{nodes:N,selfId:P,meshId:S.meshId??`dashboard-mesh`,selectedNodeId:Z?.id,hoveredNodeId:_e,dimmedNodeIds:ve,onPick:$,onReady:()=>E(w),getNodePeer:I},w),!k&&(0,H.jsx)(`div`,{className:`pointer-events-none absolute inset-0`,children:(0,H.jsx)(s,{})})]}),catalog:(0,H.jsx)(ne,{models:S.models,selectedModelName:X?.name,onSelect:ye}),peers:(0,H.jsx)(i,{peers:S.peers,models:S.models,summary:S.peerSummary,selectedPeerId:Q?.id,onSelect:be,onHoverPeerIdChange:W,onFilteredPeerIdsChange:K}),connect:(0,H.jsx)(re,{installHref:S.connect.installHref,apiUrl:x,apiStatus:S.connect.apiStatus,apiTargetLiveness:v,runCommand:S.connect.runCommand,description:S.connect.description,copyState:ge,onCopy:Ce}),drawers:(0,H.jsxs)(H.Fragment,{children:[(0,H.jsx)(o,{open:q===`model`,model:X,peers:S.peers,onClose:Se}),(0,H.jsx)(ee,{open:q===`node`,node:Z,peer:Q,models:S.models,onClose:xe})]})})}function G(e={}){let[t,n]=(0,m.useState)(!1);return(0,H.jsx)(H.Fragment,{children:(0,H.jsx)(W,{...e,liveFetchPaused:t,setLiveFetchPaused:n})})}function K(e={}){return(0,H.jsx)(G,{...e})}export{K as DashboardPage,G as DashboardPageSurface};
import{l as e,m as t,u as n}from"./cn-DzJZ_V3X.js";import{c as r,d as i,f as a,h as o,l as s,m as ee,n as te,o as c,p as ne,s as l,t as re,u as ie}from"./ConnectBlock-Q9VL_lJC.js";import{D as u}from"./tooltip-B7o_GgNn.js";import{i as ae}from"./format-model-size-DleXDH2g.js";import{t as oe}from"./MeshViz-DGmQQ8Xw.js";import{n as d}from"./vram-_ZXANuL0.js";import{M as se,at as f,ht as ce,it as p,w as le}from"./index-DOcqlpTd.js";import{t as ue}from"./use-models-query-B93o-dqC.js";import{t as de}from"./models-adapter-BtD8xj0i.js";var m=t(n(),1),h={title:`Welcome to the public mesh`,description:`Run open models with shared community capacity. Start a local node to contribute compute or use the OpenAI-compatible endpoint.`,actions:u.hero.actions},g={...u.connect,runCommand:`mesh-llm --auto`,description:`join the public mesh`};function _(e){return f(e)?h:u.hero}function v(e){return f(e)?g:u.connect}function y(e){return e===`client`||e===`standby`||e===`loading`||e===`serving`}function b(e){return e===`loading`?`degraded`:y(e)?`online`:`offline`}function x(e){return typeof e==`string`?e:e.name}function S(e,t){return e.node_id??e.id??e.hostname??`peer-${t}`}function C(e){return e.node_state??e.state??e.role?.toLowerCase()}function w(e){let t=C(e);if(y(t))return t}function T(e){let t=e.role?.toLowerCase(),n=w(e);return t===`host`?`host`:t===`worker`?`worker`:t===`client`||n===`client`?`client`:`peer`}function E(e){let t=new Set,n=[];for(let r of e){let e=r?.trim();!e||t.has(e)||(t.add(e),n.push(e))}return n}function D(e){let t=[e.serving_models,e.hosted_models,e.models];for(let e of t){let t=E(e??[]);if(t.length>0)return t}return[]}function O(e){return typeof e!=`number`||!Number.isFinite(e)?0:Math.min(Math.max(Math.round(e),0),100)}function k(e){return typeof e==`string`?e:e?.display_name??e?.name??e?.status}function A(e){return typeof e==`number`&&Number.isFinite(e)?e:0}function j(e){if(!e?.length)return null;let t=e.reduce((e,t)=>e+(d(t)??0),0);return t>0?t:null}function M(e){return A(j(e.gpus)??e.my_vram_gb??e.vram_gb??void 0)}function N(e){let t=A(j(e.gpus)??e.my_vram_gb??void 0);return e.peers.reduce((e,t)=>e+M(t),t)}function P(e){return A(e.inflight_requests)}function F(e,t){let n=S(e,t),r=w(e);return{id:n,hostname:e.hostname??n,region:e.region??``,status:b(C(e)),hostedModels:D(e),sharePct:O(e.share_pct),latencyMs:e.latency_ms??e.rtt_ms??null,latencySource:e.latency_source??null,latencyAgeMs:e.latency_age_ms??null,latencyObserverId:e.latency_observer_id??null,loadPct:e.load_pct??0,shortId:n.slice(0,8),version:e.version,vramGB:M(e),role:T(e),nodeState:r,toksPerSec:e.tok_per_sec,hardwareLabel:e.hardware_label,owner:k(e.owner),firstJoinedMeshTs:e.first_joined_mesh_ts}}function I(e){return(e.runtime?.stages??[]).some(t=>t.node_id===e.node_id)}function L(e){let t=I(e),n=E([...e.serving_models.map(x),e.node_state===`serving`?e.model_name:void 0]),r=t&&e.node_state===`standby`?`serving`:e.node_state;return{id:e.node_id,hostname:e.hostname??e.my_hostname??`localhost`,region:e.region??``,status:b(r),hostedModels:n,sharePct:0,latencyMs:0,latencySource:null,latencyAgeMs:null,latencyObserverId:null,loadPct:e.load_pct??0,shortId:e.node_id.slice(0,8),role:`you`,nodeState:r,version:e.version,vramGB:j(e.gpus)??e.my_vram_gb,toksPerSec:e.tok_per_sec,firstJoinedMeshTs:e.first_joined_mesh_ts}}function R(e){if(e.some(e=>e.sharePct>0))return e;let t=e.filter(e=>e.nodeState===`serving`||e.hostedModels.length>0).reduce((e,t)=>e+(t.vramGB??0),0);return t<=0?e:e.map(e=>({...e,sharePct:e.nodeState===`serving`||e.hostedModels.length>0?O((e.vramGB??0)/t*100):e.sharePct}))}function z(e){let t=e.filter(e=>e.status===`online`).length,n=e.reduce((e,t)=>e+(t.vramGB??0),0);return{total:e.length,online:t,capacity:`${n.toFixed(0)} GB`}}function B(e){let t=E([...e.serving_models.map(x),e.node_state===`serving`?e.model_name:void 0]),n=E(e.peers.flatMap(D)),r=E([...t,...n]),i=N(e),a=e.peers.length,o=P(e),s=k(e.owner)??`Unsigned`;return[{id:`node-id`,label:`Node ID`,value:e.node_id||`n/a`,variant:`identity`,mono:!0,badge:{label:e.node_state,tone:e.node_state===`serving`?`good`:e.node_state===`loading`?`warn`:`muted`}},{id:`owner`,label:`Owner`,value:s,variant:`identity`,mono:!0,badge:{label:s.toLowerCase()===`unsigned`?`not cryptographically bound`:`verified identity`,tone:`muted`}},{id:`nodes`,label:`Nodes`,value:a+1,meta:`1 you · ${a} peers`},{id:`active-models`,label:`Active models`,value:r.length,meta:`${t.length} loaded locally · ${n.length} remote`},{id:`mesh-vram`,label:`Mesh VRAM`,value:i.toFixed(1),unit:`GB`},{id:`inflight`,label:`Inflight`,value:o}]}function V(e){return[{id:e.node_id,label:e.hostname??`localhost`,x:50,y:50,status:b(e.node_state),role:`self`,meshState:e.node_state,servingModels:e.serving_models.map(x),hostname:e.hostname,vramGB:e.my_vram_gb,firstJoinedMeshTs:e.first_joined_mesh_ts}]}function fe(e,t=[]){let n=R([L(e),...e.peers.map(F)]);return{...u,hero:_(e),peers:n,peerSummary:z(n),statusMetrics:B(e),meshNodeSeeds:V(e),meshId:e.mesh_id??``,models:t,connect:v(e),wakeableNodes:e.wakeable_nodes}}var H=e(),pe=1e4;function me(e,t){if(t)return e.find(e=>e.id===t.peerId||e.id===t.id)}function he(e,t){return t?e.isError?`unavailable`:e.data?`live`:`checking`:`configured`}function U(e,t){let n=t?.meshId===e.meshId?t.nodes:[];return{source:e,meshId:e.meshId,nodes:l(n,e.peers,e.meshId,e.meshNodeSeeds)}}function W({data:e=u,liveFetchPaused:t,setLiveFetchPaused:n}){let{mode:l,setMode:d}=ce(),f=l===`live`,h=f&&!t,g=le({enabled:h}),_=ue({enabled:h}),v=he(g,f),y=(0,m.useMemo)(()=>_.data?de(_.data.mesh_models):void 0,[_.data]),b=(0,m.useMemo)(()=>g.data?fe(g.data,y??[]):void 0,[y,g.data]),x=f?p(g.data):p(),S=(f?b:e)??e,C=!!b,w=f&&!C?void 0:S.meshId??`dashboard-mesh`,[T,E]=(0,m.useState)(),[D,O]=(0,m.useState)(()=>U(S,void 0)),k=w!==void 0&&T===w;(0,m.useEffect)(()=>{if(!f){n(!1);return}if(t||C)return;let e=window.setTimeout(()=>{n(!0)},pe);return()=>window.clearTimeout(e)},[C,t,f,n]);let A=f&&t&&!C,j=f&&!b&&!A,M=D;M.source!==S&&(M=U(S,M),O(M));let N=M.nodes,P=N.find(e=>e.role===`self`)?.id??`self`,F=(0,m.useMemo)(()=>new Map(S.peers.map(e=>[e.id,e])),[S.peers]),I=(0,m.useCallback)(e=>e.peerId?F.get(e.peerId):F.get(e.id),[F]),[L,R]=(0,m.useState)(void 0),[z,B]=(0,m.useState)(),[V,W]=(0,m.useState)(),[G,K]=(0,m.useState)([]),[q,J]=(0,m.useState)(null),{copyState:ge,copyText:Y}=se(),X=L&&S.models.some(e=>e.name===L.name)?L:void 0,Z=z&&S.peers.some(e=>e.id===z.peerId||e.id===z.id)?z:void 0,Q=me(S.peers,Z),_e=(0,m.useMemo)(()=>{if(!V)return;let e=F.get(V);if(e)return c(e,N).id},[V,N,F]),ve=(0,m.useMemo)(()=>{if(G.length===0||G.length===S.peers.length)return;let e=new Set(G);return new Set(N.filter(t=>!e.has(t.peerId??t.id)).map(e=>e.id))},[S.peers.length,G,N]),ye=e=>{R(e),J(`model`)},$=e=>{B(e),J(`node`)},be=e=>$(c(e,N)),xe=()=>J(null),Se=()=>{J(null),R(void 0)},Ce=(0,m.useCallback)(()=>{Y(S.connect.runCommand)},[Y,S.connect.runCommand]),we=(0,m.useCallback)(()=>{n(!1),Promise.all([g.refetch(),_.refetch()])},[_,n,g]),Te=(0,m.useCallback)(()=>d(`harness`),[d]);return A?(0,H.jsx)(ae,{debugTitle:`Could not reach the local mesh backend`,title:`Live mesh data is unavailable`,debugDescription:`The dashboard could not fetch the initial status and model catalog from the configured API target. Start the backend, verify the endpoint, or switch Data source back to Harness in Tweaks while debugging.`,productionDescription:`The dashboard is waiting for live mesh status and model data. Keep the page open while the service recovers.`,onRetry:we,onSwitchToTestData:Te,children:(0,H.jsx)(r,{})}):j?(0,H.jsx)(r,{}):(0,H.jsx)(ie,{hero:(0,H.jsx)(a,{title:S.hero.title,description:S.hero.description,actions:S.hero.actions}),status:(0,H.jsx)(te,{metrics:S.statusMetrics}),topology:(0,H.jsx)(oe,{nodes:N,selfId:P,meshId:S.meshId??`dashboard-mesh`,selectedNodeId:Z?.id,hoveredNodeId:_e,dimmedNodeIds:ve,onPick:$,onReady:()=>E(w),getNodePeer:I},w),catalog:(0,H.jsx)(ne,{models:S.models,selectedModelName:X?.name,onSelect:ye}),peers:(0,H.jsx)(i,{peers:S.peers,models:S.models,summary:S.peerSummary,selectedPeerId:Q?.id,onSelect:be,onHoverPeerIdChange:W,onFilteredPeerIdsChange:K}),connect:(0,H.jsx)(re,{installHref:S.connect.installHref,apiUrl:x,apiStatus:S.connect.apiStatus,apiTargetLiveness:v,runCommand:S.connect.runCommand,description:S.connect.description,copyState:ge,onCopy:Ce}),drawers:(0,H.jsxs)(H.Fragment,{children:[(0,H.jsx)(o,{open:q===`model`,model:X,peers:S.peers,onClose:Se}),(0,H.jsx)(ee,{open:q===`node`,node:Z,peer:Q,models:S.models,onClose:xe})]})})}function G(e={}){let[t,n]=(0,m.useState)(!1);return(0,H.jsx)(H.Fragment,{children:(0,H.jsx)(W,{...e,liveFetchPaused:t,setLiveFetchPaused:n})})}function K(e={}){return(0,H.jsx)(G,{...e})}export{K as DashboardPage,G as DashboardPageSurface};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/swift/Sources/MeshLLM/Resources/Console/assets/DashboardPage-HnRRNG4K.js`
at line 1, Update the unavailable-overlay condition in W so A only becomes true
when live fetching is paused and no live data has been recovered; incorporate C
(or clear the paused state when C becomes true) to ensure recovered b data
renders the dashboard instead of the error overlay.

@@ -0,0 +1 @@
import{i as e,l as t,m as n,u as r}from"./cn-DzJZ_V3X.js";import{t as i}from"./useRouter-Ds6vf8iZ.js";import{t as a}from"./shield-alert-CUQLlWwc.js";import{S as o,b as s,v as c,x as l,yt as u}from"./index-DOcqlpTd.js";import{i as d,n as f,r as p,t as m}from"./host-surface-Dpepihlo.js";import{t as h}from"./InfoBanner-lIYx64rk.js";var g=e(`boxes`,[[`path`,{d:`M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z`,key:`lc1i9w`}],[`path`,{d:`m7 16.5-4.74-2.85`,key:`1o9zyk`}],[`path`,{d:`m7 16.5 5-3`,key:`va8pkn`}],[`path`,{d:`M7 16.5v5.17`,key:`jnp8gn`}],[`path`,{d:`M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z`,key:`8zsnat`}],[`path`,{d:`m17 16.5-5-3`,key:`8arw3v`}],[`path`,{d:`m17 16.5 4.74-2.85`,key:`8rfmw`}],[`path`,{d:`M17 16.5v5.17`,key:`k6z78m`}],[`path`,{d:`M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z`,key:`1xygjf`}],[`path`,{d:`M12 8 7.26 5.15`,key:`1vbdud`}],[`path`,{d:`m12 8 4.74-2.85`,key:`3rx089`}],[`path`,{d:`M12 13.5V8`,key:`1io7kd`}]]),_=n(r(),1),v=t();function y(e){switch(e.state){case`none`:return{kind:`fallback`,title:`Plugin web UI is not declared`,detail:`This plugin does not publish host-projected console pages.`,tone:`muted`};case`disabled`:return{kind:`fallback`,title:`Plugin web UI is disabled`,detail:e.unavailable_reason??`The plugin remains available, but its console projection is turned off.`,tone:`warn`};case`invalid`:return{kind:`fallback`,title:`Plugin web UI bundle is invalid`,detail:e.unavailable_reason??`The host rejected the declared bundle metadata or assets.`,tone:`bad`};case`plugin_not_running`:return{kind:`fallback`,title:`Plugin is not running`,detail:e.unavailable_reason??`The UI declaration is known, but the plugin process is unavailable.`,tone:`warn`};case`ready`:return{kind:`fallback`,title:`Plugin page is not declared`,detail:`The requested page id is not part of the ready plugin web UI declaration.`,tone:`muted`};default:return b(e.state)}}function b(e){throw TypeError(`Unhandled plugin web UI state: ${String(e)}`)}function x(e,t){if(!e)return null;if(e.state!==`ready`||!e.declared||!e.enabled||!e.available)return y(e);if(!e.asset_base_url)return{kind:`fallback`,title:`Plugin web UI asset route is unavailable`,detail:`The host marked this plugin ready without a bundle asset base URL.`,tone:`bad`};let n=e.pages?.find(e=>e.id===t);return n?{kind:`ready`,page:n}:y(e)}function S(e,t,n){let r=new URL(c(e,t,n),window.location.origin);if(r.origin!==window.location.origin)throw TypeError(`Plugin web UI asset URL must be same-origin`);return r.href}function C(e){let t=!0;return()=>{t&&(t=!1,e.unmount())}}function w(e){return e instanceof Error?e.message:`Plugin bundle import failed`}function T({detail:e,title:t,tone:n}){return(0,v.jsx)(h,{className:`mt-0`,description:e,leadingIcon:(0,v.jsx)(n===`bad`?a:g,{className:`size-4`,"aria-hidden":`true`}),leadingIconClassName:n===`bad`?`text-bad`:n===`warn`?`text-warn`:`text-fg-faint`,title:t,titleId:`plugin-web-ui-fallback-title`,titleLevel:`h1`})}function E(){let{pluginName:e,pageId:t}=u({from:`/plugins/$pluginName/$pageId`}),n=i(),r=o(e),a=l(e,{enabled:r.data?.state===`ready`}),c=s(e),h=(0,_.useRef)(c.mutateAsync),g=(0,_.useRef)(null),[y,b]=(0,_.useState)({kind:`idle`}),[E,D]=(0,_.useState)(null),O=(0,_.useMemo)(()=>x(r.data,t),[r.data,t]);return(0,_.useEffect)(()=>{h.current=c.mutateAsync},[c.mutateAsync]),(0,_.useEffect)(()=>{if(O?.kind!==`ready`||!r.data||!a.data){let e=window.setTimeout(()=>b({kind:`idle`}),0);return()=>window.clearTimeout(e)}let t=!1,i,o=window.setTimeout(()=>{t||b({kind:`loading`})},0);return(async()=>{let o=await d(S(e,r.data,O.page.entry_script)),s=m({pluginName:e,page:O.page,webUi:r.data,visibleConfig:a.data,navigateTo:e=>void n.navigate({to:e}),openPluginPage:t=>void n.navigate({to:`/plugins/$pluginName/$pageId`,params:{pluginName:e,pageId:t}}),requestConfigMutation:e=>h.current(e),showToast:e=>D(e.description?`${e.title}: ${e.description}`:e.title)}),c=await o.registerMeshPluginUi(s);p(c);let l=c.pages[O.page.id],u=g.current;if(!l||!u){b({kind:`error`,message:`Plugin bundle did not register the requested page.`});return}let _=await l({element:u,host:s,page:O.page});if(f(_),i=C(_),t){i();return}b({kind:`mounted`})})().catch(e=>{t||b({kind:`error`,message:w(e)})}),()=>{t=!0,window.clearTimeout(o),i?.()}},[O,r.data,e,n,a.data]),r.isPending?(0,v.jsx)(T,{kind:`fallback`,title:`Loading plugin web UI`,detail:`Fetching plugin UI metadata.`,tone:`muted`}):r.isError?(0,v.jsx)(T,{kind:`fallback`,title:`Plugin web UI metadata unavailable`,detail:`The host could not fetch this plugin web UI declaration.`,tone:`bad`}):O?O.kind===`fallback`?(0,v.jsx)(T,{...O}):(0,v.jsxs)(`section`,{className:`panel-shell flex min-h-[24rem] flex-col rounded-[var(--radius-lg)] border border-border bg-panel`,children:[(0,v.jsxs)(`header`,{className:`border-b border-border-soft px-5 py-4`,children:[(0,v.jsx)(`div`,{className:`type-label text-fg-faint`,children:`Plugin page`}),(0,v.jsx)(`h1`,{className:`type-headline mt-1 text-foreground`,children:O.page.label}),(0,v.jsxs)(`p`,{className:`type-caption mt-1 text-fg-dim`,children:[`Mounted from `,(0,v.jsx)(`span`,{className:`font-mono text-foreground`,children:e}),` page`,` `,(0,v.jsx)(`span`,{className:`font-mono text-foreground`,children:O.page.id}),`.`]})]}),y.kind===`loading`?(0,v.jsx)(`div`,{className:`border-b border-border-soft px-5 py-2 text-[length:var(--density-type-caption)] text-fg-faint`,children:`Loading plugin bundle...`}):null,y.kind===`error`?(0,v.jsx)(`div`,{className:`border-b border-border-soft px-5 py-2 text-[length:var(--density-type-caption)] text-bad`,role:`alert`,children:y.message}):null,E?(0,v.jsx)(`div`,{className:`border-b border-border-soft px-5 py-2 text-[length:var(--density-type-caption)] text-foreground`,role:`status`,children:E}):null,(0,v.jsx)(`section`,{ref:g,className:`min-h-0 flex-1 p-5`,"aria-label":`${O.page.label} plugin host`}),y.kind===`mounted`?(0,v.jsx)(`div`,{className:`sr-only`,"aria-live":`polite`,children:`Plugin page mounted`}):null]}):null}export{E as PluginWebUiRoutePage};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle async query lifecycle states instead of treating missing data as idle or empty.

Both components consume only query .data. A slow or failed request then produces a blank or misleading UI without an actionable error.

  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/PluginWebUiRoutePage-BR5NTyZb.js#L1-L1: Render visible-config loading and error states before the plugin mount gate.
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/ReservesPage-l3BXmSXD.js#L1-L1: Render live reserve-query loading and error states before the empty-provider state.
📍 Affects 2 files
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/PluginWebUiRoutePage-BR5NTyZb.js#L1-L1 (this comment)
  • sdk/swift/Sources/MeshLLM/Resources/Console/assets/ReservesPage-l3BXmSXD.js#L1-L1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@sdk/swift/Sources/MeshLLM/Resources/Console/assets/PluginWebUiRoutePage-BR5NTyZb.js`
at line 1, Handle query lifecycle states explicitly in both affected sites: in
sdk/swift/Sources/MeshLLM/Resources/Console/assets/PluginWebUiRoutePage-BR5NTyZb.js:1-1,
update component E to render visible-config loading and error states before the
plugin mount gate, rather than treating missing a.data as idle; in
sdk/swift/Sources/MeshLLM/Resources/Console/assets/ReservesPage-l3BXmSXD.js:1-1,
render live reserve-query loading and error states before showing the
empty-provider state. Preserve the existing success rendering and actionable
error presentation.

@@ -0,0 +1 @@
import{i as e,l as t,m as n,u as r}from"./cn-DzJZ_V3X.js";import{C as i,D as a}from"./dist-CmtQcoKK.js";import{d as o,t as s}from"./utils-Bn-caNnQ.js";import{c,l}from"./SharedModal-BDDbY5Up.js";import{a as u,i as d,j as f,n as p}from"./tooltip-B7o_GgNn.js";import{t as m}from"./chevron-right-2cTjc06l.js";import{a as h}from"./vram-_ZXANuL0.js";import{a as g,c as _,l as v,n as y,o as b,r as x,s as S,t as C,u as w}from"./reserve-policy-CNh2fVCc.js";import{R as T,r as E}from"./position-BWRVSCrm.js";import{_t as ee,c as D,d as O,f as k,l as A,u as j,ut as M}from"./index-DOcqlpTd.js";import{n as N,t as te}from"./stagger-Cr3UjKUn.js";import{t as ne}from"./InfoBanner-lIYx64rk.js";var re=e(`settings-2`,[[`path`,{d:`M14 17H5`,key:`gfn3mx`}],[`path`,{d:`M19 7h-9`,key:`6i9tg`}],[`circle`,{cx:`17`,cy:`17`,r:`3`,key:`18b49y`}],[`circle`,{cx:`7`,cy:`7`,r:`3`,key:`dfmy0x`}]]),ie=e(`signal`,[[`path`,{d:`M2 20h.01`,key:`4haj6o`}],[`path`,{d:`M7 20v-4`,key:`j294jx`}],[`path`,{d:`M12 20v-8`,key:`i3yub9`}],[`path`,{d:`M17 20V8`,key:`1tkaf5`}],[`path`,{d:`M22 4v16`,key:`sih9yq`}]]),ae=e(`terminal`,[[`path`,{d:`M12 19h8`,key:`baeox8`}],[`path`,{d:`m4 17 6-6-6-6`,key:`1yngyt`}]]),oe=e(`zap`,[[`path`,{d:`M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z`,key:`1xq2db`}]]),P=n(r(),1),F=t();function I({label:e,tone:t,dot:n=!1,icon:r,className:i,tooltip:a}){let o=(0,F.jsxs)(v,{className:s(`h-5 shrink-0 rounded-full px-2 text-[10px] font-medium`,se(t),n||r?`gap-1`:``,i),children:[n?(0,F.jsx)(`span`,{className:`h-1.5 w-1.5 rounded-full bg-current`}):null,r,e]});return a?(0,F.jsxs)(d,{children:[(0,F.jsx)(u,{asChild:!0,children:(0,F.jsx)(`button`,{type:`button`,className:`inline-flex rounded-full bg-transparent p-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring`,children:o})}),(0,F.jsx)(p,{side:`bottom`,align:`center`,sideOffset:8,children:a})]}):o}function se(e){return e===`good`?`border-[color:color-mix(in_oklab,var(--color-good)_34%,var(--color-border))] bg-[color:color-mix(in_oklab,var(--color-good)_7%,transparent)] text-[color:var(--color-good)]`:e===`warm`||e===`warn`?`border-[color:color-mix(in_oklab,var(--color-warn)_34%,var(--color-border))] bg-[color:color-mix(in_oklab,var(--color-warn)_7%,transparent)] text-[color:var(--color-warn)]`:e===`cold`||e===`info`?`border-[color:color-mix(in_oklab,var(--color-accent)_34%,var(--color-border))] bg-[color:color-mix(in_oklab,var(--color-accent)_7%,transparent)] text-[color:var(--color-accent)]`:e===`bad`?`border-[color:color-mix(in_oklab,var(--color-bad)_34%,var(--color-border))] bg-[color:color-mix(in_oklab,var(--color-bad)_7%,transparent)] text-[color:var(--color-bad)]`:`border-border/70 bg-panel text-fg-dim`}function L(e){return e>=1e3?`${z(e/1e3)} TB`:`${z(e)} GB`}function R(e){return e<60?`${e}s`:e<3600?`${Math.floor(e/60)}m ${e%60}s`:e<86400?`${Math.ceil(e/3600)} hr`:`${Math.ceil(e/86400)} d`}function z(e){return Number.isInteger(e)?e.toFixed(0):e.toFixed(1)}var B=[`standby`,`waking`,`joining`,`online`,`failed`,`unreachable`],V={standby:{label:`Standby`,tone:`neutral`,dotClassName:`border-border/70 bg-panel text-fg-faint`},waking:{label:`Waking`,tone:`warn`,dotClassName:`border-[color:color-mix(in_oklab,var(--color-warn)_42%,var(--color-border))] bg-[color:color-mix(in_oklab,var(--color-warn)_16%,transparent)] text-[color:var(--color-warn)]`,pulse:!0},joining:{label:`Joining`,tone:`info`,dotClassName:`border-[color:color-mix(in_oklab,var(--color-accent)_40%,var(--color-border))] bg-[color:color-mix(in_oklab,var(--color-accent)_14%,transparent)] text-[color:var(--color-accent)]`,pulse:!0},online:{label:`Online`,tone:`good`,dotClassName:`border-[color:color-mix(in_oklab,var(--color-good)_40%,var(--color-border))] bg-[color:color-mix(in_oklab,var(--color-good)_14%,transparent)] text-[color:var(--color-good)]`},failed:{label:`Wake failed`,tone:`bad`,dotClassName:`border-[color:color-mix(in_oklab,var(--color-bad)_42%,var(--color-border))] bg-[color:color-mix(in_oklab,var(--color-bad)_14%,transparent)] text-[color:var(--color-bad)]`},unreachable:{label:`Unreachable`,tone:`bad`,dotClassName:`border-[color:color-mix(in_oklab,var(--color-bad)_44%,var(--color-border))] bg-[color:color-mix(in_oklab,var(--color-bad)_10%,transparent)] text-[color:var(--color-bad)]`,hatched:!0}};function H(e){return V[e]}function U(e){return e==null||!Number.isFinite(e)?0:Math.min(100,Math.max(0,e))}function W(e){if(e==null||!Number.isFinite(e))return`pending`;if(e<60)return`${Math.max(0,Math.round(e))}s`;let t=Math.floor(e/60),n=Math.round(e%60);return n>0?`${t}m ${n}s`:`${t}m`}function G({node:e}){let t=H(e.state),n=U(e.progress);return(0,F.jsx)(D,{className:`rounded-[var(--radius)] border-[color:color-mix(in_oklab,var(--reserve-active-color)_22%,var(--color-border))] bg-[color:color-mix(in_oklab,var(--reserve-active-color)_4%,var(--color-panel-strong))] shadow-none`,style:{"--reserve-active-color":`var(${e.state===`waking`?`--color-warn`:`--color-accent`})`},children:(0,F.jsxs)(A,{className:`space-y-2.5 p-[11px]`,children:[(0,F.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,F.jsxs)(`div`,{className:`flex min-w-0 items-start gap-2.5`,children:[(0,F.jsx)(`span`,{className:s(`mt-0.5 inline-flex size-[26px] shrink-0 items-center justify-center rounded-[var(--radius)] border text-[11px] font-semibold`,t.dotClassName),children:e.state===`joining`?(0,F.jsx)(i,{"aria-hidden":`true`,className:`size-3`}):(0,F.jsx)(a,{"aria-hidden":`true`,className:`size-3`})}),(0,F.jsxs)(`div`,{className:`min-w-0`,children:[(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center gap-1.5 text-[13px] font-semibold leading-tight text-foreground [overflow-wrap:anywhere]`,children:[(0,F.jsx)(`span`,{children:e.id}),(0,F.jsx)(I,{label:t.label,tone:t.tone,dot:!0})]}),(0,F.jsx)(`div`,{className:`mt-0.5 text-[11.5px] leading-tight text-fg-dim`,children:e.hw}),e.location?(0,F.jsx)(`div`,{className:`type-caption mt-1 text-fg-faint`,children:e.location}):null]})]}),(0,F.jsxs)(`div`,{className:`shrink-0 text-right`,children:[(0,F.jsx)(`div`,{className:`text-[13px] font-semibold leading-none text-foreground`,children:L(e.vram)}),(0,F.jsx)(`div`,{className:`mt-1 text-[9.5px] font-semibold uppercase leading-none tracking-[0.06em] text-fg-faint`,children:`VRAM`})]})]}),(0,F.jsxs)(`div`,{className:`space-y-1`,children:[(0,F.jsxs)(`div`,{className:`flex justify-between text-[11px] leading-none text-fg-dim`,children:[(0,F.jsxs)(`span`,{children:[`ETA `,W(e.eta)]}),(0,F.jsxs)(`span`,{children:[n,`%`]})]}),(0,F.jsx)(`div`,{className:`h-[3px] overflow-hidden rounded-full bg-muted`,role:`progressbar`,"aria-label":`${e.id} wake progress`,"aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":n,children:(0,F.jsx)(`div`,{className:`h-full rounded-full bg-[color:var(--reserve-active-color)]`,style:{width:`${n}%`}})})]}),e.note?(0,F.jsx)(`div`,{className:`type-caption rounded-[var(--radius)] border border-border/70 bg-panel px-2.5 py-2 text-fg-dim`,children:e.note}):null,(0,F.jsx)(`div`,{className:`flex flex-wrap gap-1`,children:e.models.map(e=>(0,F.jsx)(v,{className:`rounded-[var(--radius)] px-2 py-0.5 text-[11px] leading-none text-foreground`,children:e},e))})]})})}function K({node:e,onRetry:t,onOpenLogs:n,onDismiss:r}){let i=H(e.state),a=e.failedAt?e.failedAt.startsWith(`failed`)?e.failedAt:`failed ${e.failedAt}`:e.lastSeen?e.lastSeen.startsWith(`last seen`)?e.lastSeen:`last seen ${e.lastSeen}`:`No timestamp reported`;return(0,F.jsx)(D,{className:`rounded-[var(--radius)] border-[color:color-mix(in_oklab,var(--color-bad)_26%,var(--color-border))] bg-[color:color-mix(in_oklab,var(--color-bad)_4%,var(--color-panel-strong))] shadow-none`,children:(0,F.jsxs)(A,{className:`space-y-2 p-[10px]`,children:[(0,F.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,F.jsxs)(`div`,{className:`flex min-w-0 items-start gap-2.5`,children:[(0,F.jsx)(`span`,{className:`mt-0.5 inline-flex size-[26px] shrink-0 items-center justify-center rounded-[var(--radius)] border border-[color:color-mix(in_oklab,var(--color-bad)_40%,transparent)] bg-[color:color-mix(in_oklab,var(--color-bad)_20%,var(--color-panel))] text-[color:var(--color-bad)]`,children:(0,F.jsx)(l,{className:`size-[13px]`,"aria-hidden":`true`})}),(0,F.jsxs)(`div`,{className:`min-w-0`,children:[(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center gap-1.5 text-[13px] font-semibold leading-tight text-foreground [overflow-wrap:anywhere]`,children:[(0,F.jsx)(`span`,{children:e.id}),(0,F.jsx)(I,{label:i.label,tone:i.tone,dot:!0})]}),(0,F.jsx)(`div`,{className:`mt-0.5 text-[11.5px] leading-tight text-fg-dim`,children:e.hw}),e.location?(0,F.jsx)(`div`,{className:`type-caption mt-1 text-fg-faint`,children:e.location}):null]})]}),(0,F.jsxs)(`div`,{className:`shrink-0 text-right`,children:[(0,F.jsx)(`div`,{className:`text-[13px] font-semibold leading-none text-foreground`,children:L(e.vram)}),(0,F.jsx)(`div`,{className:`mt-1 text-[9.5px] font-semibold uppercase leading-none tracking-[0.06em] text-fg-faint`,children:`VRAM`})]})]}),(0,F.jsxs)(`div`,{className:`flex items-center gap-2 rounded-[var(--radius)] border border-[color:color-mix(in_oklab,var(--color-bad)_18%,var(--color-border))] bg-panel px-2 py-1 text-[11.5px] leading-snug text-[color:var(--color-bad)]`,children:[(0,F.jsx)(`span`,{className:`size-[5px] shrink-0 rounded-full bg-[color:var(--color-bad)] shadow-[0_0_8px_color-mix(in_oklab,var(--color-bad)_70%,transparent)]`,"aria-hidden":`true`}),(0,F.jsx)(`span`,{className:`min-w-0 flex-1 font-mono text-[10.5px]`,children:e.error??`Reserve node needs attention.`}),(0,F.jsx)(`span`,{className:`type-caption shrink-0 text-fg-faint`,children:a})]}),e.note?(0,F.jsx)(`div`,{className:`type-caption text-fg-dim`,children:e.note}):null,(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center gap-1.5`,children:[(0,F.jsxs)(k,{className:`ui-control h-[24px] gap-1.5 rounded-[var(--radius)] border px-2.5 text-[11px]`,disabled:!t||e.retryable===!1,onClick:()=>t?.(e),size:`sm`,variant:`outline`,children:[(0,F.jsx)(T,{className:`size-[11px]`,"aria-hidden":`true`}),`Retry`]}),(0,F.jsxs)(k,{className:`ui-control h-[24px] gap-1.5 rounded-[var(--radius)] border px-2.5 text-[11px]`,disabled:!n,onClick:()=>n?.(e),size:`sm`,variant:`outline`,children:[(0,F.jsx)(ae,{className:`size-[11px]`,"aria-hidden":`true`}),`Logs`]}),(0,F.jsx)(k,{"aria-label":`Dismiss ${e.id}`,className:`ml-auto h-[24px] w-[24px] rounded-[var(--radius)] border-0 bg-transparent px-0 text-fg-faint hover:bg-panel hover:text-foreground`,disabled:!r,onClick:()=>r?.(e),size:`sm`,variant:`ghost`,children:(0,F.jsx)(f,{className:`size-[11px]`,"aria-hidden":`true`})})]})]})})}function ce({node:e}){let t=(0,P.useRef)(null),n=(0,P.useRef)(null),r=(0,P.useRef)(null),i=H(e.state),a=[e.id,e.hw,L(e.vram),i.label,e.error,e.lastSeen].filter(Boolean).join(` · `);return(0,P.useEffect)(()=>{r.current?.revert(),r.current=null;let e=t.current,a=n.current;if(!(!i.pulse||!e||!a)&&!(typeof window<`u`&&typeof window.matchMedia==`function`&&window.matchMedia(`(prefers-reduced-motion: reduce)`).matches))return r.current=N({root:t}).add(()=>{E(e,{scale:[1,1.025,1],duration:1600,loop:!0,ease:`inOutSine`}),E(a,{opacity:[.28,.58,.28],scale:[.86,1.16,.86],duration:1600,loop:!0,ease:`inOutSine`})}),()=>{r.current?.revert(),r.current=null}},[i.pulse]),(0,F.jsxs)(`button`,{"aria-label":a,className:`relative inline-flex size-[14px] cursor-pointer overflow-visible rounded-[3px] border-0 bg-transparent p-0 transition-transform duration-150 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent`,ref:t,title:a,type:`button`,children:[i.pulse?(0,F.jsx)(`span`,{"aria-hidden":`true`,className:s(`pointer-events-none absolute inset-[-5px] rounded-full opacity-30 blur-[1.5px]`,le(e.state)),ref:n}):null,(0,F.jsx)(`span`,{"aria-hidden":`true`,className:s(`relative z-[1] size-full rounded-[3px] border`,ue(e.state))})]})}function le(e){switch(e){case`waking`:return`bg-[radial-gradient(circle,color-mix(in_oklab,var(--color-warn)_38%,transparent)_0%,color-mix(in_oklab,var(--color-warn)_18%,transparent)_44%,transparent_74%)]`;case`joining`:return`bg-[radial-gradient(circle,color-mix(in_oklab,var(--color-accent)_42%,transparent)_0%,color-mix(in_oklab,var(--color-accent)_20%,transparent)_44%,transparent_74%)]`;case`standby`:case`online`:case`failed`:case`unreachable`:return null}}function ue(e){switch(e){case`standby`:return`border-[color:color-mix(in_oklab,var(--color-fg-faint)_30%,transparent)] bg-[color:color-mix(in_oklab,var(--color-fg-faint)_14%,var(--color-panel-strong))]`;case`waking`:return`border-[color:color-mix(in_oklab,var(--color-warn)_70%,transparent)] bg-[color:color-mix(in_oklab,var(--color-warn)_60%,var(--color-panel-strong))]`;case`joining`:return`border-[color:color-mix(in_oklab,var(--color-accent)_70%,transparent)] bg-[color:color-mix(in_oklab,var(--color-accent)_75%,var(--color-panel-strong))]`;case`online`:return`border-[color:color-mix(in_oklab,var(--color-good)_70%,transparent)] bg-[color:color-mix(in_oklab,var(--color-good)_55%,var(--color-panel-strong))]`;case`failed`:return`border-[color:color-mix(in_oklab,var(--color-bad)_70%,transparent)] bg-[color:color-mix(in_oklab,var(--color-bad)_65%,var(--color-panel-strong))]`;case`unreachable`:return`border-[color:color-mix(in_oklab,var(--color-bad)_70%,transparent)] bg-[repeating-linear-gradient(135deg,color-mix(in_oklab,var(--color-bad)_55%,var(--color-panel-strong))_0_2px,color-mix(in_oklab,var(--color-bad)_18%,var(--color-panel-strong))_2px_4px)]`}}function de({nodes:e}){return e.length===0?null:(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center gap-1.5`,children:[(0,F.jsxs)(`span`,{className:`mr-1 inline-flex items-center gap-1.5 py-[3px] text-[10px] font-semibold uppercase leading-none tracking-[0.06em] text-[color:var(--color-good)]`,children:[(0,F.jsx)(`span`,{className:`size-[5px] rounded-full bg-[color:var(--color-good)] shadow-[0_0_6px_var(--color-good)]`,"aria-hidden":`true`}),e.length,` online`]}),e.map(e=>(0,F.jsxs)(`span`,{className:`inline-flex shrink-0 items-center gap-1.5 rounded-full border border-[color:color-mix(in_oklab,var(--color-good)_25%,var(--color-border))] bg-[color:color-mix(in_oklab,var(--color-good)_6%,var(--color-panel))] px-2 py-[3px] font-mono text-[10.5px] leading-none text-fg-dim`,children:[(0,F.jsx)(`span`,{className:`size-[5px] rounded-full bg-[color:var(--color-good)] shadow-[0_0_6px_var(--color-good)]`,"aria-hidden":`true`}),e.id,(0,F.jsx)(`span`,{className:`text-fg-faint`,children:`·`}),(0,F.jsx)(`span`,{children:L(e.vram)}),e.since?(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(`span`,{className:`text-fg-faint`,children:`·`}),(0,F.jsxs)(`span`,{className:`text-fg-faint`,children:[`up `,e.since]})]}):null]},e.id))]})}function q({label:e,value:t,title:n}){return(0,F.jsxs)(`div`,{className:`flex flex-col items-center px-0 text-center leading-[1.1]`,title:n,children:[(0,F.jsx)(`div`,{className:`whitespace-nowrap text-[13px] font-medium leading-none tabular-nums text-foreground`,children:t}),(0,F.jsx)(`div`,{className:`mt-[2px] text-[9.5px] font-medium uppercase leading-none tracking-[0.05em] text-fg-faint`,children:e})]})}var fe=[`failed`,`unreachable`,`joining`,`waking`,`online`,`standby`],pe={failed:`failed`,unreachable:`unreachable`,joining:`joining`,waking:`waking`,online:`online`,standby:`standby`};function me({provider:e,totals:t,expanded:n,onToggle:r,onWakeProvider:i}){let c=t.counts.standby,l=n?o:m,u=t.totalNodes>0&&t.counts.online===t.totalNodes,d=fe.map(e=>{let n=t.counts[e];if(n===0)return null;let r=H(e);return(0,F.jsx)(I,{className:`h-[19px] px-1.5 text-[10.5px] font-medium`,dot:!0,label:`${n} ${pe[e]}`,tone:r.tone},e)});return(0,F.jsx)(`div`,{className:`px-[14px] py-[12px]`,children:(0,F.jsxs)(`div`,{className:`grid w-full grid-cols-[minmax(0,1fr)_auto_auto] items-center gap-x-3 gap-y-2 sm:grid-cols-[minmax(0,1fr)_auto_auto_auto] lg:grid-cols-[minmax(0,1fr)_auto_auto_112px_auto_auto]`,children:[(0,F.jsxs)(`button`,{"aria-expanded":n,className:`flex min-w-0 items-center gap-3 text-left`,"data-testid":`reserve-provider-toggle`,onClick:r,type:`button`,children:[(0,F.jsx)(`span`,{className:s(`inline-flex size-[28px] shrink-0 items-center justify-center rounded-[var(--radius)] border text-[11px] font-semibold`,u?`border-[color:color-mix(in_oklab,var(--color-good)_42%,var(--color-border))] bg-[color:color-mix(in_oklab,var(--color-good)_15%,transparent)] text-[color:var(--color-good)]`:`border-[color:color-mix(in_oklab,var(--color-accent)_34%,var(--color-border))] bg-[color:color-mix(in_oklab,var(--color-accent)_10%,transparent)] text-[color:var(--color-accent)]`),children:(0,F.jsx)(J,{icon:e.icon,fallback:e.name.slice(0,1),className:`size-3.5`,"aria-hidden":`true`})}),(0,F.jsxs)(`span`,{className:`min-w-0 text-left`,children:[(0,F.jsxs)(`span`,{className:`flex items-center gap-2 text-[13px] font-semibold leading-[1.05] text-foreground`,children:[(0,F.jsx)(`span`,{className:`truncate`,children:e.name}),(0,F.jsx)(v,{className:`h-[19px] rounded-full px-[7px] py-0 text-[10.5px] font-medium leading-none text-fg-faint`,children:e.kind})]}),(0,F.jsxs)(`span`,{className:`mt-[6px] block truncate text-[11px] leading-[1.2] tracking-[0.015em] text-fg-faint`,children:[e.region,` · `,e.billing]})]})]}),(0,F.jsxs)(`div`,{className:`col-start-2 row-start-1 flex flex-col items-end gap-0.5 font-mono text-[10.5px] leading-none text-fg-dim sm:hidden`,children:[(0,F.jsxs)(`span`,{className:`whitespace-nowrap text-foreground`,children:[t.totalNodes,` nodes`]}),(0,F.jsx)(`span`,{className:`whitespace-nowrap`,children:L(t.totalVram)})]}),(0,F.jsx)(`div`,{className:`col-start-1 row-start-2 flex min-w-0 flex-wrap items-center justify-start gap-1 overflow-hidden sm:col-span-4 lg:hidden`,children:(0,F.jsx)(`div`,{className:`flex min-w-0 flex-wrap items-center justify-start gap-1 overflow-hidden`,children:d})}),(0,F.jsx)(`div`,{className:`hidden min-w-0 flex-nowrap items-center justify-end gap-1 overflow-hidden pr-2 lg:col-start-2 lg:row-start-1 lg:flex`,children:d}),(0,F.jsx)(`span`,{"aria-hidden":`true`,className:`hidden h-[90%] w-px self-center justify-self-center bg-border lg:col-start-3 lg:row-start-1 lg:block`}),(0,F.jsxs)(`div`,{className:`hidden min-w-0 items-center justify-start gap-3 px-2 sm:col-start-2 sm:row-start-1 sm:flex lg:col-start-4`,children:[(0,F.jsx)(q,{label:`Nodes`,value:t.totalNodes}),(0,F.jsx)(q,{label:`VRAM`,value:L(t.totalVram)})]}),(0,F.jsxs)(k,{className:`ui-control-primary col-span-2 col-start-2 row-start-2 ml-auto h-[24px] w-[102px] shrink-0 justify-center rounded-[var(--radius)] px-[10px] text-[11.5px] whitespace-nowrap sm:col-span-1 sm:col-start-3 sm:row-start-1 sm:ml-0 lg:col-start-5 lg:w-[96px] xl:w-[102px]`,disabled:c===0,onClick:i,size:`sm`,title:c===0?`No standby nodes are available to wake.`:void 0,type:`button`,variant:`default`,children:[(0,F.jsx)(a,{className:`size-[11px]`,"aria-hidden":`true`}),c>0?`Wake ${c}`:`Wake provider`]}),(0,F.jsx)(`button`,{"aria-expanded":n,"aria-label":n?`Collapse row`:`Expand row`,className:`col-start-3 row-start-1 inline-flex size-6 shrink-0 items-center justify-center justify-self-end rounded-[var(--radius)] text-fg-faint transition hover:bg-panel hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring sm:col-start-4 lg:col-start-6`,"data-testid":`reserve-provider-chevron-toggle`,onClick:r,type:`button`,children:(0,F.jsx)(l,{className:`size-4`,"aria-hidden":`true`})})]})})}function J({fallback:e,icon:t,...n}){return t===`cloud`?(0,F.jsx)(h,{...n}):t===`server`?(0,F.jsx)(c,{...n}):t===`lan`?(0,F.jsx)(i,{...n}):(0,F.jsx)(F.Fragment,{children:e})}function he(e){return Y(e.nodes)}function ge(e){let t=Y(e.flatMap(e=>e.nodes));return{...t,onlineNodes:t.counts.online,reserveNodes:t.totalNodes-t.counts.online}}function Y(e){let t=_e(),n=ve(),r=0,i;for(let a of e)t[a.state]+=1,n[a.state]+=a.vram,r+=a.vram,a.eta!=null&&(i=i==null?a.eta:Math.max(i,a.eta));return{counts:t,vramByState:n,totalNodes:e.length,totalVram:r,activeNodes:t.waking+t.joining,errorNodes:t.failed+t.unreachable,longestEta:i}}function _e(){return B.reduce((e,t)=>({...e,[t]:0}),{})}function ve(){return B.reduce((e,t)=>({...e,[t]:0}),{})}function ye({provider:e,onDismissNode:t,onOpenLogs:n,onRetryAll:r,onRetryNode:i,onWakeProvider:a}){let[o,c]=(0,P.useState)(!1),u=(0,P.useMemo)(()=>he(e),[e]),d=e.nodes.filter(e=>e.state===`waking`||e.state===`joining`),f=e.nodes.filter(e=>e.state===`failed`||e.state===`unreachable`),p=e.nodes.filter(e=>e.state===`online`);return(0,F.jsxs)(`section`,{className:s(`overflow-hidden rounded-[var(--radius)] border shadow-none`,u.totalNodes>0&&u.counts.online===u.totalNodes?`border-[color:color-mix(in_oklab,var(--color-good)_24%,var(--color-border))] bg-[color:color-mix(in_oklab,var(--color-good)_3%,var(--color-panel-strong))]`:f.length>0?`border-[color:color-mix(in_oklab,var(--color-bad)_22%,var(--color-border))] bg-[color:color-mix(in_oklab,var(--color-bad)_2%,var(--color-panel-strong))]`:`border-border/70 bg-panel-strong`),"data-reserve-entrance":!0,"data-testid":`reserve-provider-group`,children:[(0,F.jsx)(me,{expanded:o,onToggle:()=>c(e=>!e),provider:e,totals:u,onWakeProvider:()=>a?.(e)}),o?(0,F.jsxs)(`div`,{className:`border-t border-border/60 bg-[color:color-mix(in_oklab,var(--color-panel)_70%,var(--color-panel-strong))]`,children:[d.length>0?(0,F.jsx)(`div`,{className:s(`grid gap-2 border-b border-border/60 bg-[color:color-mix(in_oklab,var(--color-panel)_50%,var(--color-panel-strong))] px-[14px] py-[10px]`,d.length>1?`md:grid-cols-2`:``),children:d.map(e=>(0,F.jsx)(G,{node:e},e.id))}):null,f.length>0?(0,F.jsxs)(`div`,{className:`border-b border-[color:color-mix(in_oklab,var(--color-bad)_15%,var(--color-border))] bg-[color:color-mix(in_oklab,var(--color-bad)_4%,var(--color-panel-strong))] px-[14px] py-[10px]`,children:[(0,F.jsxs)(`div`,{className:`mb-2 flex items-center gap-2`,children:[(0,F.jsx)(l,{className:`size-[11px] shrink-0 text-[color:var(--color-bad)]`,"aria-hidden":`true`}),(0,F.jsxs)(`div`,{className:`type-label text-[color:var(--color-bad)]`,children:[f.length,` nodes need attention`]}),(0,F.jsx)(`span`,{className:`min-w-0 flex-1`}),(0,F.jsxs)(k,{className:`ui-control h-[22px] gap-1.5 rounded-[var(--radius)] border px-2.5 text-[11px]`,onClick:()=>r?.(e),size:`sm`,variant:`outline`,children:[(0,F.jsx)(T,{className:`size-[10px]`,"aria-hidden":`true`}),`Retry all`]})]}),(0,F.jsx)(`div`,{className:`grid gap-2 md:grid-cols-2`,children:f.map(r=>(0,F.jsx)(K,{node:r,onDismiss:n=>t?.(e,n),onOpenLogs:t=>n?.(e,t),onRetry:t=>i?.(e,t)},r.id))})]}):null,p.length>0?(0,F.jsx)(`div`,{className:`border-b border-border/60 bg-[color:color-mix(in_oklab,var(--color-panel)_50%,var(--color-panel-strong))] px-[14px] py-[10px]`,children:(0,F.jsx)(de,{nodes:p})}):null,(0,F.jsx)(`div`,{className:`px-[14px] py-3`,children:(0,F.jsx)(`div`,{className:`flex flex-wrap items-center gap-[4px]`,children:e.nodes.map(e=>(0,F.jsx)(ce,{node:e},e.id))})})]}):null]})}function be(){return(0,F.jsx)(`div`,{"aria-label":`Reserve node state legend`,className:`flex flex-wrap items-center justify-end gap-x-3 gap-y-1`,role:`img`,children:B.map(e=>{let t=H(e);return(0,F.jsxs)(`span`,{className:s(`inline-flex items-center gap-1.5 text-[11px] leading-none text-fg-dim`,t.hatched&&`text-fg-dim`),children:[(0,F.jsx)(`span`,{className:s(`size-[9px] rounded-[2px] border`,xe(e,t.hatched)),"aria-hidden":`true`}),t.label]},e)})})}function xe(e,t){return t?`border-[color:color-mix(in_oklab,var(--color-bad)_70%,transparent)] bg-[repeating-linear-gradient(135deg,color-mix(in_oklab,var(--color-bad)_55%,var(--color-panel-strong))_0_2px,color-mix(in_oklab,var(--color-bad)_18%,var(--color-panel-strong))_2px_4px)] text-[color:var(--color-bad)]`:e===`standby`?`border-[color:color-mix(in_oklab,var(--color-fg-faint)_30%,transparent)] bg-[color:color-mix(in_oklab,var(--color-fg-faint)_14%,var(--color-panel-strong))] text-fg-faint`:e===`waking`?`border-[color:color-mix(in_oklab,var(--color-warn)_70%,transparent)] bg-[color:color-mix(in_oklab,var(--color-warn)_60%,var(--color-panel-strong))] text-[color:var(--color-warn)] shadow-[0_0_0_2px_color-mix(in_oklab,var(--color-warn)_25%,transparent)]`:e===`joining`?`border-[color:color-mix(in_oklab,var(--color-accent)_70%,transparent)] bg-[color:color-mix(in_oklab,var(--color-accent)_75%,var(--color-panel-strong))] text-[color:var(--color-accent)] shadow-[0_0_0_2px_color-mix(in_oklab,var(--color-accent)_25%,transparent)]`:e===`online`?`border-[color:color-mix(in_oklab,var(--color-good)_70%,transparent)] bg-[color:color-mix(in_oklab,var(--color-good)_55%,var(--color-panel-strong))] text-[color:var(--color-good)]`:`border-[color:color-mix(in_oklab,var(--color-bad)_70%,transparent)] bg-[color:color-mix(in_oklab,var(--color-bad)_65%,var(--color-panel-strong))] text-[color:var(--color-bad)]`}var Se=[`online`,`joining`,`waking`,`failed`,`unreachable`,`standby`];function Ce({allocation:e,className:t}){let n=B.reduce((t,n)=>t+e[n],0);return n===0?(0,F.jsx)(`div`,{"aria-label":`No reserve capacity by state`,className:s(`h-[6px] rounded-full bg-panel-strong`,t),role:`img`}):(0,F.jsx)(`div`,{"aria-label":`Reserve capacity by node state`,className:s(`flex h-[6px] overflow-hidden rounded-full border border-border/60 bg-panel-strong`,t),role:`img`,children:Se.map(t=>{let r=e[t];if(r===0)return null;let i=H(t);return(0,F.jsx)(`div`,{className:s(`h-full border-r border-background last:border-r-0`,we(t,i.hatched)),style:{width:`${r/n*100}%`},title:`${i.label}: ${L(r)}`},t)})})}function we(e,t){return t?`bg-[repeating-linear-gradient(135deg,color-mix(in_oklab,var(--color-bad)_55%,var(--color-panel-strong))_0_2px,color-mix(in_oklab,var(--color-bad)_30%,var(--color-panel-strong))_2px_4px)]`:e===`online`?`bg-[color:color-mix(in_oklab,var(--color-good)_55%,var(--color-panel-strong))]`:e===`joining`?`bg-[color:color-mix(in_oklab,var(--color-accent)_75%,var(--color-panel-strong))]`:e===`waking`?`bg-[color:color-mix(in_oklab,var(--color-warn)_60%,var(--color-panel-strong))]`:e===`failed`?`bg-[color:color-mix(in_oklab,var(--color-bad)_65%,var(--color-panel-strong))]`:`bg-[color:color-mix(in_oklab,var(--color-fg-faint)_35%,var(--color-panel-strong))]`}function X({label:e,value:t,subLabel:n,children:r,mono:i,labelFirst:a,valueClassName:o}){let c=(0,F.jsx)(`div`,{className:`text-[10.5px] font-semibold uppercase leading-none tracking-[0.08em] text-fg-faint`,children:e}),l=(0,F.jsx)(`div`,{className:s(`text-[22px] font-medium leading-[1.1] tracking-[-0.02em] text-foreground`,i?`font-mono`:``,o),children:t});return(0,F.jsxs)(`div`,{className:`flex min-w-0 flex-col justify-center whitespace-nowrap border-b border-r border-border/60 px-[20px] py-3 lg:border-b-0 lg:px-[20px]`,children:[a?c:l,(0,F.jsx)(`div`,{className:a?`mt-1.5`:`mt-[3px]`,children:a?l:c}),n?(0,F.jsx)(`div`,{className:`mt-1 text-[10.5px] leading-snug text-fg-dim`,children:n}):null,r?(0,F.jsx)(`div`,{className:`mt-2`,children:r}):null]})}function Te({totals:e,liveMeshVramGB:t,autoWakeEnabled:n=!0,policyLabel:r}){let i=e.longestEta==null?`-`:R(e.longestEta),a=t==null?`Live mesh VRAM pending`:`vs ${Math.round(t)} GB on the live mesh`;return(0,F.jsx)(`div`,{className:`border-b border-border/60 bg-[color-mix(in_oklab,var(--color-panel-strong)_50%,var(--color-panel))]`,children:(0,F.jsxs)(`div`,{className:`grid grid-cols-[repeat(auto-fit,minmax(148px,1fr))] gap-0 lg:grid-cols-[auto_auto_auto_auto_minmax(220px,1fr)]`,children:[(0,F.jsx)(X,{label:`Managed nodes`,value:e.totalNodes,subLabel:`${e.onlineNodes} online · ${e.reserveNodes} reserve`,mono:!0}),(0,F.jsx)(X,{label:`Combined VRAM`,value:L(e.totalVram),subLabel:a,mono:!0}),(0,F.jsx)(X,{label:`Until ready`,value:i,subLabel:`longest in-flight wake`,mono:!0}),(0,F.jsxs)(`div`,{className:`hidden min-w-0 flex-col justify-center whitespace-nowrap border-b border-r border-border/60 px-[20px] py-3 sm:flex sm:px-[20px] lg:border-b-0 lg:px-[20px]`,children:[(0,F.jsx)(`div`,{className:`text-[10.5px] font-semibold uppercase leading-none tracking-[0.08em] text-fg-faint`,children:`Status`}),(0,F.jsxs)(`div`,{className:`mt-1 flex flex-col gap-1 text-[11px] font-normal leading-none tracking-normal text-fg-faint`,children:[(0,F.jsxs)(`span`,{className:`inline-flex items-center gap-1.5`,children:[(0,F.jsx)(`span`,{className:`size-1.5 rounded-full bg-[color:var(--color-accent)]`,"aria-hidden":`true`}),(0,F.jsx)(`span`,{className:`font-mono text-[13px] font-medium text-foreground`,children:e.counts.joining}),`joining`]}),(0,F.jsxs)(`span`,{className:`inline-flex items-center gap-1.5`,children:[(0,F.jsx)(`span`,{className:`size-1.5 rounded-full bg-[color:var(--color-warn)]`,"aria-hidden":`true`}),(0,F.jsx)(`span`,{className:`font-mono text-[13px] font-medium text-foreground`,children:e.counts.waking}),`waking`]}),(0,F.jsxs)(`span`,{className:`inline-flex items-center gap-1.5 text-[color:var(--color-bad)]`,children:[(0,F.jsx)(`span`,{className:`size-1.5 rounded-full bg-current`,"aria-hidden":`true`}),(0,F.jsx)(`span`,{className:`font-mono text-[13px] font-medium`,children:e.errorNodes}),(0,F.jsx)(`span`,{className:`text-[color:var(--color-bad)]`,children:`errors`})]})]})]}),(0,F.jsxs)(`div`,{className:`col-span-full flex min-w-0 flex-col justify-center gap-1.5 px-[20px] py-3 sm:px-[20px] lg:col-span-1 lg:min-w-[220px] lg:px-[20px]`,children:[(0,F.jsxs)(`div`,{className:`flex items-center justify-between gap-4 text-[10px] font-semibold uppercase leading-none tracking-[0.08em] text-fg-faint`,children:[(0,F.jsx)(`span`,{children:`Capacity by state`}),(0,F.jsx)(`span`,{className:`font-mono font-medium normal-case tracking-normal text-fg-dim`,children:L(e.totalVram)})]}),(0,F.jsx)(Ce,{allocation:e.vramByState}),(0,F.jsxs)(`div`,{className:`flex items-center justify-between gap-4 text-[10.5px] leading-none text-fg-faint`,children:[(0,F.jsxs)(`span`,{className:`inline-flex items-center gap-1.5 font-mono`,children:[`auto-wake`,n?(0,F.jsxs)(`span`,{className:`inline-flex items-center gap-1 rounded-full border border-[color:color-mix(in_oklab,var(--color-good)_38%,transparent)] bg-[color:color-mix(in_oklab,var(--color-good)_18%,transparent)] px-1.5 py-0.5 text-[10px] font-medium leading-none text-[color:var(--color-good)]`,children:[(0,F.jsx)(`span`,{className:`size-1 rounded-full bg-current`,"aria-hidden":`true`}),`on`]}):(0,F.jsxs)(`span`,{className:`inline-flex items-center gap-1 rounded-full border border-[color:color-mix(in_oklab,var(--color-fg-faint)_30%,transparent)] bg-[color:color-mix(in_oklab,var(--color-fg-faint)_10%,transparent)] px-1.5 py-0.5 text-[10px] font-medium leading-none text-fg-faint`,children:[(0,F.jsx)(`span`,{className:`size-1 rounded-full bg-current`,"aria-hidden":`true`}),`paused`]})]}),r?(0,F.jsx)(`span`,{className:`font-mono`,children:r}):null]})]})]})})}function Ee({providers:e,liveMeshVramGB:t,wakePolicySettings:n,onDismissNode:r,onOpenLogs:i,onRetryAll:o,onRetryNode:s,onWakeProvider:c}){let l=ge(e);return(0,F.jsxs)(D,{className:`overflow-hidden rounded-[10px] bg-panel shadow-none`,"data-reserve-entrance":!0,"data-testid":`reserves-section`,children:[(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center justify-between gap-3 border-b border-border/60 bg-[color-mix(in_oklab,var(--color-panel-strong)_50%,var(--color-panel))] px-[14px] py-[10px]`,children:[(0,F.jsx)(`h3`,{className:`text-[12px] font-semibold leading-none tracking-[0.0125em] text-foreground`,children:`Reserve fleet`}),(0,F.jsx)(be,{})]}),(0,F.jsx)(Te,{autoWakeEnabled:n?.autoWakeEnabled,liveMeshVramGB:t,policyLabel:n?`policy: ${n.providerOrder.join(` → `).toLowerCase()}`:void 0,totals:l}),(0,F.jsx)(`div`,{className:`space-y-3 px-[14px] py-[14px]`,children:(0,F.jsx)(`div`,{className:`space-y-2`,children:e.length>0?e.map(e=>(0,F.jsx)(ye,{provider:e,onDismissNode:r,onOpenLogs:i,onRetryAll:o,onRetryNode:s,onWakeProvider:c},e.id)):(0,F.jsxs)(`div`,{className:`flex min-h-[154px] flex-col items-center justify-center rounded-[var(--radius)] border border-dashed border-[color:color-mix(in_oklab,var(--color-accent)_22%,var(--color-border))] bg-[radial-gradient(circle_at_50%_0%,color-mix(in_oklab,var(--color-accent)_10%,transparent),transparent_42%),color-mix(in_oklab,var(--color-panel-strong)_70%,var(--color-panel))] px-5 py-7 text-center`,children:[(0,F.jsx)(`div`,{className:`inline-flex size-10 items-center justify-center rounded-[var(--radius)] border border-[color:color-mix(in_oklab,var(--color-accent)_34%,var(--color-border))] bg-[color:color-mix(in_oklab,var(--color-accent)_12%,transparent)] text-[color:var(--color-accent)]`,children:(0,F.jsx)(a,{className:`size-4`,"aria-hidden":`true`})}),(0,F.jsx)(`div`,{className:`mt-3 text-[13px] font-semibold leading-tight text-foreground`,children:`No reserve providers are configured yet.`}),(0,F.jsx)(`p`,{className:`mt-1 max-w-[420px] text-[11.5px] leading-[1.45] text-fg-dim`,children:`Add cloud VMs, colocated hosts, or office workstations here so they can wake when mesh demand rises.`})]})})}),(0,F.jsxs)(`div`,{className:`type-caption flex flex-col gap-2 border-t border-border/60 bg-[color-mix(in_oklab,var(--color-panel-strong)_40%,var(--color-panel))] px-[14px] py-[10px] text-fg-dim sm:flex-row sm:items-center sm:justify-between`,children:[(0,F.jsxs)(`span`,{children:[`Once a node finishes joining it leaves this list and appears in`,` `,(0,F.jsx)(`span`,{className:`text-foreground`,children:`Connected peers`}),` on the Network tab.`]}),(0,F.jsx)(`a`,{className:`ui-link shrink-0 whitespace-nowrap sm:ml-auto`,href:`#reserve-policy`,children:`Configure auto-wake →`})]})]})}function De({tile:e}){return(0,F.jsx)(D,{className:`rounded-[var(--radius)] border-border/70 bg-panel-strong shadow-none`,children:(0,F.jsxs)(A,{className:`relative px-[14px] py-3`,children:[(0,F.jsxs)(`div`,{className:`flex items-center gap-2 pr-24 text-[10.5px] font-medium uppercase leading-none tracking-[0.055em] text-fg-faint`,children:[(0,F.jsx)(Oe,{"aria-hidden":`true`,className:`size-[11px] shrink-0`,title:e.title}),(0,F.jsx)(`span`,{children:e.title})]}),e.status?(0,F.jsx)(I,{className:`absolute right-[14px] top-3 h-[18px] px-1.5 text-[10px] font-medium`,dot:!0,label:e.status,tone:e.status===`Enabled`?`good`:`neutral`}):(0,F.jsx)(`div`,{className:s(`mt-2 text-[12px] font-medium leading-tight text-foreground`,`font-mono`),children:e.value}),(0,F.jsx)(`p`,{className:s(`text-[11.5px] leading-snug text-fg-faint`,e.status?`mt-3`:`mt-1.5`),children:e.explanation})]})})}function Oe({title:e,...t}){return e===`Auto-wake`?(0,F.jsx)(ie,{...t}):e===`Sleep idle reserves`?(0,F.jsx)(M,{...t}):(0,F.jsx)(w,{...t})}function ke({className:e,defaultSettings:t=C,mode:n=`reserves`,onOpenConfigurationTab:r,onSettingsChange:i,providers:a,settings:o}){let[c,l]=(0,P.useState)(t),[u,d]=(0,P.useState)(t),[f,p]=(0,P.useState)(null),m=o??c,h=(0,P.useMemo)(()=>y(m),[m]),b=m.autoWakeEnabled?`Auto-wake on`:`Auto-wake paused`,x=n===`configuration`;function S(e){d(m),p(e)}function w(){o||l(u),i?.(u)}return(0,F.jsxs)(F.Fragment,{children:[(0,F.jsxs)(D,{className:s(`overflow-hidden rounded-[10px] bg-panel shadow-none`,e),"data-testid":`reserve-policy-panel`,children:[(0,F.jsx)(j,{className:s(`border-b border-border/70 px-[14px] py-[10px]`,x?`space-y-2`:`space-y-0`),children:(0,F.jsxs)(`div`,{className:`flex flex-wrap items-start justify-between gap-3`,children:[(0,F.jsxs)(`div`,{children:[(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,F.jsx)(O,{className:`text-[12px] font-semibold leading-none tracking-[0.0125em] text-foreground`,children:`Reserve policy`}),x?(0,F.jsx)(I,{dot:!0,label:b,tone:m.autoWakeEnabled?`good`:`neutral`}):null]}),x?(0,F.jsx)(`p`,{className:`type-caption mt-1 max-w-[68ch] text-fg-dim`,children:`Preview reserve wake thresholds, provider order, and idle sleep rules before the backend configuration fields are wired in.`}):null]}),x?(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,F.jsxs)(k,{className:`ui-control h-8 rounded-[var(--radius)] border px-3 text-[length:var(--density-type-control)]`,onClick:r??(()=>S(`policy`)),size:`sm`,type:`button`,variant:`outline`,children:[(0,F.jsx)(re,{"aria-hidden":`true`,className:`mr-1 size-3.5`}),`Edit policy`]}),(0,F.jsxs)(k,{className:`ui-control-primary h-8 rounded-[var(--radius)] px-3 text-[length:var(--density-type-control)]`,onClick:()=>S(`autowake`),size:`sm`,type:`button`,variant:`default`,children:[(0,F.jsx)(oe,{"aria-hidden":`true`,className:`mr-1 size-3.5`}),`Configure auto-wake`]})]}):(0,F.jsx)(`button`,{className:`ui-link text-[11.5px] leading-none`,onClick:r??(()=>S(`policy`)),type:`button`,children:`Edit policy →`})]})}),(0,F.jsxs)(A,{className:`p-[14px]`,children:[(0,F.jsx)(`div`,{className:x?`grid gap-[14px] lg:grid-cols-3`:`grid gap-[14px] md:grid-cols-3`,children:h.map(e=>(0,F.jsx)(De,{tile:e},e.title))}),x?(0,F.jsxs)(`div`,{className:`mt-3 flex flex-wrap items-center justify-between gap-2 border-t border-border/70 pt-3`,children:[(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,F.jsx)(v,{className:`rounded-full px-2 py-0.5 text-[10px] uppercase tracking-[0.08em] text-fg-dim`,children:`Preview only`}),(0,F.jsx)(`span`,{className:`type-caption text-fg-dim`,children:`These controls shape the high-fidelity UI now, and backend persistence can attach later without changing the interaction model.`})]}),r?(0,F.jsx)(`button`,{className:`ui-link text-[length:var(--density-type-caption-lg)]`,onClick:r,type:`button`,children:`Open Reserves tab`}):null]}):null]})]}),(0,F.jsx)(_,{confirmLabel:f===`autowake`?`Apply auto-wake`:`Save policy`,description:f===`autowake`?`Tune the reserve wake threshold and warm-up window. This preview updates the visible cards only and does not send backend requests.`:`Adjust how reserve providers are prioritized and when they go back to sleep. This preview edits local UI state only.`,onConfirm:w,onOpenChange:e=>{e||p(null)},open:f!==null,title:f===`autowake`?`Configure auto-wake`:`Edit reserve policy`,children:(0,F.jsx)(g,{onSettingsChange:d,providers:f===`policy`?a:void 0,settings:u})})]})}function Z(e){return e.map(e=>({...e,tags:e.tags?[...e.tags]:void 0,nodes:e.nodes.map(e=>({...e,models:[...e.models]}))}))}function Ae(e){return e.toLowerCase().trim().replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``)}function Q(e,t,n){return e.map(e=>e.id===t?{...e,nodes:n(e.nodes)}:e)}function $(e){return{...e,state:`waking`,progress:e.progress??12,eta:e.eta??240,error:void 0,failedAt:void 0,lastSeen:void 0,note:`Preview wake requested. No backend request was sent.`}}function je({configurationHref:e,liveMeshVramGB:t,providers:n}){let r=ee(),i=(0,P.useRef)(null),[o,s]=(0,P.useState)(n),[c,l]=(0,P.useState)(()=>Z(n)),[u,d]=(0,P.useState)(C),[f,p]=(0,P.useState)(null),[m,h]=(0,P.useState)(!1),[g,y]=(0,P.useState)(x);o!==n&&(s(n),l(Z(n))),(0,P.useLayoutEffect)(()=>{if(!i.current||typeof window<`u`&&typeof window.matchMedia==`function`&&window.matchMedia(`(prefers-reduced-motion: reduce)`).matches)return;let e=N({root:i}).add(()=>{E(`[data-reserve-entrance]`,{opacity:[0,1],y:[10,0],duration:260,delay:te(45),ease:`out(4)`})});return()=>e.revert()},[]);let w=(0,P.useMemo)(()=>f?c.find(e=>e.id===f.providerId)??null:null,[f,c]),T=(0,P.useMemo)(()=>f&&`nodeId`in f?w?.nodes.find(e=>e.id===f.nodeId)??null:null,[f,w]);function D(){y(x)}function O(){let e=S(g.providerId);if(e.availability!==`supported`)return;let t=g.name.trim()||e.defaultName,n=g.region.trim()||e.defaultRegion,r=Ae(t)||`preview-provider`;l(i=>[{id:`${r}-${i.length+1}`,name:t,kind:e.kind,icon:e.icon,region:n,tags:[n],billing:e.billing,summary:e.summary,nodes:[{id:`${r}-01`,hw:`RTX 4090`,vram:24,location:`${n} · preview reserve`,note:`This node exists only inside the UI preview and does not wake a real machine.`,models:[`Qwen3-14B`],state:`standby`,since:`just added`}]},...i]),D()}function k(){f&&l(e=>{switch(f.kind){case`wake-provider`:return Q(e,f.providerId,e=>{let t=e.findIndex(e=>e.state===`standby`);return t===-1?e:e.map((e,n)=>n===t?$(e):e)});case`retry-all`:return Q(e,f.providerId,e=>e.map(e=>e.state===`failed`||e.state===`unreachable`?$(e):e));case`retry-node`:return Q(e,f.providerId,e=>e.map(e=>e.id===f.nodeId?$(e):e));case`dismiss`:return Q(e,f.providerId,e=>e.filter(e=>e.id!==f.nodeId));case`logs`:return e}})}let A=(0,P.useCallback)(()=>{e&&r({to:`/configuration/$configurationTab`,params:{configurationTab:`wake-policy`}})},[e,r]);function j(){return!f||!w?null:f.kind===`wake-provider`?{title:`Wake ${w.name}`,description:`Queue the next standby node from ${w.name}. This preview updates the panel locally and skips backend provisioning calls.`,confirmLabel:`Queue wake`,confirmTone:`default`}:f.kind===`retry-all`?{title:`Retry all wake failures for ${w.name}`,description:`Re-queue every failed or unreachable node in this provider group. This preview only flips the visible state chips and progress rows.`,confirmLabel:`Retry all`,confirmTone:`default`}:T?f.kind===`retry-node`?{title:`Retry ${T.id}`,description:`Re-run the wake attempt for this node without contacting a provider API. The mockup moves the row back into active wake state.`,confirmLabel:`Retry node`,confirmTone:`default`}:f.kind===`logs`?{title:`Logs for ${T.id}`,description:`Inspect the last visible wake failure details. This preview keeps log text inline until backend log streaming exists.`,confirmLabel:`Close`,confirmTone:`default`}:{title:`Dismiss ${T.id}`,description:`Hide this failed reserve row from the current UI view. This is a preview-only dismissal and does not change backend state.`,confirmLabel:`Dismiss node`,confirmTone:`destructive`}:null}let M=j();return(0,F.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-[14px]`,ref:i,children:[(0,F.jsx)(`div`,{"data-reserve-entrance":!0,"data-testid":`reserves-hero`,children:(0,F.jsx)(ne,{action:(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center gap-3 text-[12px] leading-none text-fg-dim sm:text-[12.5px]`,children:[(0,F.jsx)(`a`,{className:`ui-link font-medium`,href:`#reserve-policy`,children:`Reserve policy →`}),(0,F.jsx)(`span`,{"aria-hidden":`true`,className:`text-fg-faint`,children:`·`}),(0,F.jsx)(`button`,{className:`ui-link-muted font-normal text-fg-dim`,onClick:()=>h(!0),type:`button`,children:`Add provider →`})]}),actionClassName:`basis-full justify-start pl-[46px] pt-1 sm:basis-auto sm:justify-end sm:pl-0 sm:pt-0`,className:`min-h-[68px] flex-wrap items-start gap-3 rounded-[var(--radius)] px-[18px] py-[14px] sm:flex-nowrap sm:items-center`,description:`Off-mesh nodes you can wake on demand. Cloud VMs, colocated hosts, and office workstations join the mesh when demand rises, then step back when queues clear.`,descriptionClassName:`mt-0.5 text-[12px] leading-[1.45]`,leadingIcon:(0,F.jsx)(a,{className:`size-4`,"aria-hidden":`true`}),title:`Reserves`,titleClassName:`text-[13.5px] font-semibold leading-tight tracking-normal`,titleLevel:`h1`})}),(0,F.jsx)(`div`,{"data-reserve-entrance":!0,id:`reserve-fleet`,children:(0,F.jsx)(Ee,{liveMeshVramGB:t,onDismissNode:(e,t)=>p({kind:`dismiss`,providerId:e.id,nodeId:t.id}),onOpenLogs:(e,t)=>p({kind:`logs`,providerId:e.id,nodeId:t.id}),onRetryAll:e=>p({kind:`retry-all`,providerId:e.id}),onRetryNode:(e,t)=>p({kind:`retry-node`,providerId:e.id,nodeId:t.id}),onWakeProvider:e=>p({kind:`wake-provider`,providerId:e.id}),providers:c,wakePolicySettings:u})}),(0,F.jsx)(`div`,{"data-reserve-entrance":!0,id:`reserve-policy`,children:(0,F.jsx)(ke,{mode:`reserves`,onOpenConfigurationTab:e?A:void 0,onSettingsChange:d,providers:c.map(e=>e.name),settings:u})}),(0,F.jsx)(b,{confirmLabel:`Add preview provider`,description:`Stage a reserve provider row in the local preview. No infrastructure is provisioned.`,onDraftChange:y,onConfirm:O,onOpenChange:e=>{h(e),e||D()},open:m,providerDraft:g}),M?(0,F.jsx)(_,{confirmLabel:M.confirmLabel,confirmTone:M.confirmTone,description:M.description,onConfirm:k,onOpenChange:e=>{e||p(null)},open:f!==null,showCancel:f?.kind!==`logs`,title:M.title,children:w?(0,F.jsxs)(`div`,{className:`space-y-3 rounded-[var(--radius)] border border-border bg-background px-3.5 py-3`,children:[(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,F.jsx)(v,{className:`rounded-full px-2 py-0.5 text-[10px] uppercase tracking-[0.08em] text-fg-dim`,children:w.name}),T?(0,F.jsx)(v,{className:`rounded-full px-2 py-0.5 text-[10px] uppercase tracking-[0.08em] text-fg-dim`,children:T.id}):null]}),T?(0,F.jsxs)(`div`,{className:`space-y-1`,children:[(0,F.jsxs)(`div`,{className:`text-[length:var(--density-type-control-lg)] font-medium text-foreground`,children:[T.hw,` · `,T.vram,` GB`]}),T.location?(0,F.jsx)(`div`,{className:`type-caption text-fg-faint`,children:T.location}):null,T.error?(0,F.jsx)(`div`,{className:`type-caption text-fg-dim`,children:T.error}):null]}):(0,F.jsx)(`div`,{className:`type-caption text-fg-dim`,children:`The action will target the next standby reserve in this provider group.`}),f?.kind===`logs`&&T?(0,F.jsxs)(`div`,{className:`rounded-[var(--radius)] border border-border bg-panel px-3 py-2 font-mono text-[length:var(--density-type-caption)] text-fg-dim`,children:[`[`,T.id,`] wake attempt timed out while waiting for provider health checks. retryable=`,String(T.retryable!==!1)]}):null]}):null}):null]})}var Me=[{id:`vast`,name:`Vast.ai`,kind:`Cloud GPU`,icon:`cloud`,region:`us-east · us-west · eu-central`,tags:[`us-east`,`us-west`,`eu-central`],billing:`$0.34–$1.80 / GPU-hr`,summary:`Spot GPU capacity ready to absorb long-context and overnight burst runs.`,nodes:[{id:`vast-a100-1`,hw:`A100 80GB`,vram:80,models:[`Qwen2.5-72B-Instruct`],state:`waking`,eta:155,progress:35},{id:`vast-a100-2`,hw:`A100 80GB`,vram:80,models:[`Qwen2.5-72B-Instruct`],state:`standby`},{id:`vast-h100-1`,hw:`H100 80GB`,vram:80,models:[`DeepSeek-R1`,`Llama-3.3-70B`],state:`joining`,eta:18,progress:82},{id:`vast-3090-1`,hw:`RTX 3090`,vram:24,models:[`Qwen3.6-27B-UD`],state:`standby`},{id:`vast-3090-2`,hw:`RTX 3090`,vram:24,models:[`Qwen3.6-27B-UD`],state:`standby`},{id:`vast-3090-3`,hw:`RTX 3090`,vram:24,models:[`Qwen3.6-27B-UD`],state:`standby`},{id:`vast-4090-1`,hw:`RTX 4090`,vram:24,models:[`gemma-4-26B-A4B-it-UD`],state:`standby`},{id:`vast-4090-2`,hw:`RTX 4090`,vram:24,models:[`gemma-4-26B-A4B-it-UD`],state:`standby`},{id:`vast-a40-1`,hw:`A40 48GB`,vram:48,models:[`Qwen3.6-35B-A3B-UD`],state:`failed`,error:`auth: api key rejected`,failedAt:`22s ago`,retryable:!0},{id:`vast-a40-2`,hw:`A40 48GB`,vram:48,models:[`Qwen3.6-35B-A3B-UD`],state:`standby`},{id:`vast-a40-3`,hw:`A40 48GB`,vram:48,models:[`Qwen3.6-35B-A3B-UD`],state:`unreachable`,error:`provider api timeout`,lastSeen:`4m ago`,retryable:!0},{id:`vast-l4-1`,hw:`L4 24GB`,vram:24,models:[`Qwen3.6-27B-UD`],state:`standby`}]},{id:`runpod`,name:`RunPod`,kind:`Cloud GPU`,icon:`cloud`,region:`us-east · eu-north`,tags:[`us-east`,`eu-north`],billing:`$0.69–$2.49 / GPU-hr`,summary:`Primary elastic pool for inference spikes that outrun local VRAM.`,nodes:[{id:`runpod-h100-1`,hw:`H100 PCIe 80GB`,vram:80,models:[`DeepSeek-R1`],state:`standby`},{id:`runpod-h100-2`,hw:`H100 80GB`,vram:80,models:[`DeepSeek-R1`,`Qwen3-32B`],state:`waking`,eta:55,progress:60},{id:`runpod-a6000-1`,hw:`A6000 48GB`,vram:48,models:[`Qwen3.6-35B-A3B-UD`],state:`standby`},{id:`runpod-a6000-2`,hw:`A6000 48GB`,vram:48,models:[`Qwen3.6-35B-A3B-UD`],state:`standby`},{id:`runpod-a6000-3`,hw:`A6000 48GB`,vram:48,models:[`Qwen3.6-35B-A3B-UD`],state:`standby`},{id:`runpod-l40-1`,hw:`L40 48GB`,vram:48,models:[`gemma-4-26B-A4B-it-UD`],state:`standby`},{id:`runpod-l40-2`,hw:`L40 48GB`,vram:48,models:[`gemma-4-26B-A4B-it-UD`],state:`failed`,error:`quota exceeded · 0 / $50 spend cap`,failedAt:`1m ago`,retryable:!1}]},{id:`do`,name:`DigitalOcean`,kind:`Cloud GPU`,icon:`cloud`,region:`nyc1 · sfo3 · ams3`,tags:[`nyc1`,`sfo3`,`ams3`],billing:`$0.76–$2.49 / GPU-hr`,summary:`Reserved GPU droplets held as last-resort capacity for public demo traffic.`,nodes:[{id:`do-h100-1`,hw:`H100 80GB`,vram:80,models:[`DeepSeek-R1`],state:`online`,since:`6h`},{id:`do-h100-2`,hw:`H100 80GB`,vram:80,models:[`DeepSeek-R1`],state:`online`,since:`6h`},{id:`do-a100-1`,hw:`A100 80GB`,vram:80,models:[`Qwen2.5-72B-Instruct`],state:`online`,since:`2h`},{id:`do-a100-2`,hw:`A100 80GB`,vram:80,models:[`Qwen2.5-72B-Instruct`],state:`standby`},{id:`do-a100-3`,hw:`A100 80GB`,vram:80,models:[`Qwen2.5-72B-Instruct`],state:`standby`},{id:`do-l40-1`,hw:`L40 48GB`,vram:48,models:[`gemma-4-26B-A4B-it-UD`],state:`standby`},{id:`do-l40-2`,hw:`L40 48GB`,vram:48,models:[`gemma-4-26B-A4B-it-UD`],state:`standby`}]},{id:`lambda`,name:`Lambda Labs`,kind:`Cloud GPU`,icon:`cloud`,region:`us-east · us-west · asia-1`,tags:[`us-east`,`us-west`,`asia-1`],billing:`$0.50–$2.49 / GPU-hr · reserved`,summary:`Fallback wake targets for large memory jobs that need fast replacement.`,nodes:[{id:`lambda-h200-1`,hw:`H200 SXM 141GB`,vram:141,models:[`DeepSeek-R1`,`Llama-3.3-70B`],state:`standby`},{id:`lambda-h200-2`,hw:`H200 SXM 141GB`,vram:141,models:[`DeepSeek-R1`,`Llama-3.3-70B`],state:`standby`},{id:`lambda-h100-1`,hw:`H100 SXM 80GB`,vram:80,models:[`DeepSeek-R1`],state:`standby`},{id:`lambda-h100-2`,hw:`H100 SXM 80GB`,vram:80,models:[`DeepSeek-R1`],state:`standby`},{id:`lambda-h100-3`,hw:`H100 SXM 80GB`,vram:80,models:[`DeepSeek-R1`],state:`standby`},{id:`lambda-a100-1`,hw:`A100 80GB`,vram:80,models:[`Qwen2.5-72B-Instruct`],state:`standby`},{id:`lambda-a100-2`,hw:`A100 80GB`,vram:80,models:[`Qwen2.5-72B-Instruct`],state:`standby`},{id:`lambda-a100-3`,hw:`A100 80GB`,vram:80,models:[`Qwen2.5-72B-Instruct`],state:`standby`},{id:`lambda-l40-1`,hw:`L40 48GB`,vram:48,models:[`gemma-4-26B-A4B-it-UD`],state:`standby`},{id:`lambda-l40-2`,hw:`L40 48GB`,vram:48,models:[`gemma-4-26B-A4B-it-UD`],state:`standby`}]},{id:`metal`,name:`Bare metal hosts`,kind:`Co-located · always on`,icon:`server`,region:`rack-01 · rack-02 · syd · home-lab`,tags:[`rack-01`,`rack-02`,`syd`,`home-lab`],billing:`flat · contributing to mesh`,summary:`Racked GPUs that stay closest to production data paths and private models.`,nodes:[{id:`rack-01`,hw:`4×A100 SXM 80GB`,vram:320,models:[`DeepSeek-R1`,`Llama-3.3-70B`,`Qwen2.5-72B-Instruct`],state:`online`,since:`14d`},{id:`rack-02`,hw:`4×A100 SXM 80GB`,vram:320,models:[`DeepSeek-R1`,`Llama-3.3-70B`],state:`online`,since:`14d`},{id:`syd-h100-1`,hw:`8×H100 SXM`,vram:640,models:[`DeepSeek-R1`,`Qwen2.5-72B-Instruct`],state:`online`,since:`3d`},{id:`home-lab-1`,hw:`2×4090`,vram:48,models:[`Qwen3.6-27B-UD`],state:`online`,since:`31d`},{id:`home-lab-2`,hw:`2×3090`,vram:48,models:[`Qwen3.6-27B-UD`],state:`online`,since:`31d`}]},{id:`lan`,name:`Office LAN`,kind:`On-prem · mesh-llm not started`,icon:`lan`,region:`192.168.1.0/24 · discovered via mDNS`,tags:[`192.168.1.0/24`,`mDNS`,`idle workstations`],billing:`no cost · workstation idle`,summary:`Operator-controlled desktops and workstations that wake first for the cheapest burst capacity.`,nodes:[{id:`design-mbp-01`,hw:`M3 Max · 64 GB`,vram:48,models:[`Qwen3.6-27B-UD`,`gemma-4-26B-A4B-it-UD`],state:`standby`},{id:`design-mbp-02`,hw:`M3 Max · 36 GB`,vram:27,models:[`Qwen3.5-4B-UD`],state:`standby`},{id:`design-mbp-03`,hw:`M2 Max · 32 GB`,vram:24,models:[`Qwen3.5-4B-UD`],state:`standby`},{id:`ws-eng-04`,hw:`M4 Pro · 48 GB`,vram:36,models:[`Qwen3.6-27B-UD`],state:`standby`},{id:`ws-eng-05`,hw:`M4 Pro · 48 GB`,vram:36,models:[`Qwen3.6-27B-UD`],state:`standby`},{id:`ws-eng-06`,hw:`M4 Pro · 48 GB`,vram:36,models:[`Qwen3.6-27B-UD`],state:`standby`},{id:`ws-eng-07`,hw:`RTX 4090 · 24 GB`,vram:24,models:[`Qwen3.6-27B-UD`],state:`standby`},{id:`ws-eng-08`,hw:`RTX 4090 · 24 GB`,vram:24,models:[`Qwen3.6-27B-UD`],state:`standby`},{id:`ws-eng-09`,hw:`RTX 3090 · 24 GB`,vram:24,models:[`Qwen3.5-4B-UD`],state:`standby`},{id:`ws-eng-10`,hw:`RTX 3090 · 24 GB`,vram:24,models:[`Qwen3.5-4B-UD`],state:`standby`},{id:`ws-eng-11`,hw:`RTX 3090 · 24 GB`,vram:24,models:[`Qwen3.5-4B-UD`],state:`standby`},{id:`ws-eng-12`,hw:`RTX 3090 · 24 GB`,vram:24,models:[`Qwen3.5-4B-UD`],state:`standby`},{id:`ws-data-01`,hw:`RTX 4080 · 16 GB`,vram:16,models:[`Qwen3.5-4B-UD`],state:`standby`},{id:`ws-data-02`,hw:`RTX 4080 · 16 GB`,vram:16,models:[`Qwen3.5-4B-UD`],state:`standby`},{id:`ws-prod-01`,hw:`RTX 4080 · 16 GB`,vram:16,models:[`Qwen3.5-4B-UD`],state:`standby`},{id:`ws-prod-02`,hw:`RTX 4070 · 12 GB`,vram:12,models:[`Qwen3.5-2B`],state:`standby`},{id:`ws-prod-03`,hw:`RTX 4070 · 12 GB`,vram:12,models:[`Qwen3.5-2B`],state:`standby`},{id:`ws-mkt-01`,hw:`M2 Pro · 32 GB`,vram:24,models:[`Qwen3.5-4B-UD`],state:`standby`},{id:`ws-mkt-02`,hw:`M2 Pro · 32 GB`,vram:24,models:[`Qwen3.5-4B-UD`],state:`standby`},{id:`ws-mkt-03`,hw:`M1 Pro · 16 GB`,vram:12,models:[`Qwen3.5-2B`],state:`standby`},{id:`ws-mkt-04`,hw:`M1 Pro · 16 GB`,vram:12,models:[`Qwen3.5-2B`],state:`unreachable`,error:`offline`,lastSeen:`2h ago`,retryable:!0},{id:`ws-mkt-05`,hw:`M1 Pro · 16 GB`,vram:12,models:[`Qwen3.5-2B`],state:`standby`},{id:`ws-ops-01`,hw:`RTX 3060 · 12 GB`,vram:12,models:[`Qwen3.5-2B`],state:`standby`},{id:`ws-ops-02`,hw:`RTX 3060 · 12 GB`,vram:12,models:[`Qwen3.5-2B`],state:`standby`},{id:`ws-ops-03`,hw:`RTX 3060 · 12 GB`,vram:12,models:[`Qwen3.5-2B`],state:`standby`},{id:`ws-ops-04`,hw:`RTX 3060 · 12 GB`,vram:12,models:[`Qwen3.5-2B`],state:`standby`},{id:`ws-ops-05`,hw:`RTX 3060 · 12 GB`,vram:12,models:[`Qwen3.5-2B`],state:`standby`},{id:`ws-qa-01`,hw:`RTX 3060 · 12 GB`,vram:12,models:[`Qwen3.5-2B`],state:`standby`},{id:`ws-qa-02`,hw:`RTX 3060 · 12 GB`,vram:12,models:[`Qwen3.5-2B`],state:`standby`},{id:`ws-qa-03`,hw:`RTX 3060 · 12 GB`,vram:12,models:[`Qwen3.5-2B`],state:`standby`}]}];y(C);function Ne(e){if(!e)return;let t=new Map;for(let n of e){let e=n.provider??`Unassigned reserves`,r=e.toLowerCase().replace(/[^a-z0-9]+/g,`-`),i=t.get(r)??{id:r,name:e,kind:n.provider?`Provider`:`Reserve pool`,region:`pending`,tags:[`pending`],billing:`not configured`,summary:`Live wakeable node advertisement awaiting richer provider metadata.`,nodes:[]};i.nodes.push({id:n.logical_id,hw:`Advertised reserve node`,vram:n.vram_gb,location:n.provider?`${e} reserve`:`advertised reserve`,note:`Live node metadata is limited to the wakeable-node status payload.`,models:n.models,state:n.state===`waking`?`waking`:`standby`,eta:n.wake_eta_secs}),t.set(r,i)}return[...t.values()]}export{Ne as n,je as r,Me as t};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep provider identity separate from its sanitized ID.

Ne uses the lowercased, punctuation-stripped value as the Map key. Distinct provider names such as A/B and A B both become a-b, so their live nodes and totals are merged into one group. Key the map by the original provider name, then derive a collision-safe ID separately.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@sdk/swift/Sources/MeshLLM/Resources/Console/assets/reserve-fixtures-LwFOyKoB.js`
at line 1, Update Ne so the provider grouping Map is keyed by the original
provider name rather than its sanitized value, preserving distinct providers
such as A/B and A B. Derive each group’s sanitized ID separately and make it
collision-safe when assigning the group object’s id, without merging nodes or
totals from different provider names.

@@ -0,0 +1 @@
import{l as e}from"./cn-DzJZ_V3X.js";import{c as t}from"./utils-Bn-caNnQ.js";import{D as n}from"./tooltip-B7o_GgNn.js";import{n as r}from"./vram-_ZXANuL0.js";import{n as i,r as a,t as o}from"./reserve-fixtures-LwFOyKoB.js";import{ht as s,w as c}from"./index-DOcqlpTd.js";var l=e();function u(e){return Number.isFinite(e)&&e!=null&&e>0?e:0}function d(e){return(e??[]).reduce((e,t)=>e+(r(t)??0),0)}function f(e){return d(e.gpus)||u(e.my_vram_gb)||u(e.vram_gb)}function p(e){if(e)return e.peers.reduce((e,t)=>e+f(t),f(e))}function m({data:e=n}={}){let r=t(`global/newReservesPage`),u=t(`configuration/wakePolicyConfiguration`),{mode:d}=s(),f=d===`live`,m=c({enabled:f&&r}),h=i(m.data?.wakeable_nodes),g=f?h??[]:o,_=e.peers.reduce((e,t)=>e+(t.vramGB??0),0),v=f?p(m.data):_;return r?(0,l.jsx)(a,{configurationHref:u?`/configuration/wake-policy`:void 0,liveMeshVramGB:v,providers:g}):(0,l.jsxs)(`section`,{className:`panel-shell mx-auto max-w-3xl rounded-[var(--radius-lg)] border border-border bg-panel p-6`,children:[(0,l.jsx)(`div`,{className:`type-label text-fg-faint`,children:`Feature flag disabled`}),(0,l.jsx)(`h1`,{className:`type-display mt-1 text-foreground`,children:`Reserves is gated`}),(0,l.jsxs)(`p`,{className:`type-body mt-2 max-w-[68ch] text-fg-dim`,children:[`Enable `,(0,l.jsx)(`span`,{className:`font-mono text-foreground`,children:`global/newReservesPage`}),` in the developer playground to expose this app surface.`]})]})}function h(e={}){return(0,l.jsx)(m,{...e})}export{h as ReservesPage,m as ReservesPageContent};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Provide the InfoBanner heading ID.

This call omits titleId, although sdk/swift/Sources/MeshLLM/Resources/Console/assets/InfoBanner-lIYx64rk.js Line 1 uses that prop for both aria-labelledby and the heading id. The rendered Reserves section therefore has no accessible name. Pass a unique ID or make InfoBanner generate one when it is omitted.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/swift/Sources/MeshLLM/Resources/Console/assets/ReservesPage-l3BXmSXD.js`
at line 1, Update the Reserves page call to the InfoBanner component, identified
by the imported alias a in ReservesPageContent, so it supplies a unique titleId.
Ensure the same ID is used by InfoBanner for aria-labelledby and the heading id,
preserving an accessible name for the rendered Reserves section.

Comment on lines +109 to +114
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter+Tight:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap"
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Look for self-hosted font assets and remote font references across console outputs and website source.
set -euo pipefail

# Locate any vendored font files.
fd -e woff -e woff2 -e ttf -e otf | head -50

# Find every reference to the remote font host.
rg -n 'fonts\.(googleapis|gstatic)\.com' --iglob '*.html' --iglob '*.css' --iglob '*.ts' --iglob '*.tsx'

# Locate the maintained console HTML source that generates this file.
fd -g 'index.html' | rg -v 'Resources|console/|node_modules'

Repository: Mesh-LLM/mesh-llm

Length of output: 155


🏁 Script executed:

#!/bin/bash
set -u

echo "== repo files around target =="
git ls-files | rg 'sdk/swift/Sources/MeshLLM/Resources/Console/index\.html|sdk/swift|Console|console|Resources' | sed -n '1,120p'

echo
echo "== target file excerpt =="
if [ -f sdk/swift/Sources/MeshLLM/Resources/Console/index.html ]; then
  sed -n '90,130p' sdk/swift/Sources/MeshLLM/Resources/Console/index.html | nl -ba -v90
fi

echo
echo "== font asset files tracked =="
git ls-files | rg -i '\.(woff2?|ttf|otf|eot)$|fonts|font' | sed -n '1,200p'

echo
echo "== remote font references tracked =="
git ls-files | rg -i '\.(html|css|scss|sass|less|ts|tsx|js|jsx|mjs|cjs)$' | xargs rg -n "fonts\.(googleapis|gstatic)\.com|Inter Tight|JetBrains Mono" 2>/dev/null | sed -n '1,200p'

echo
echo "== console-related source candidates (top-level names) =="
git ls-files | rg 'index\.html|package\.json|vite|next|svelte|astro|webpack|rollup|glow|mesh|api|swift|Resources' | sed -n '1,240p'

Repository: Mesh-LLM/mesh-llm

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -u

echo "== exact target excerpt =="
sed -n '100,120p' sdk/swift/Sources/MeshLLM/Resources/Console/index.html | nl -ba -v100

echo
echo "== tracked font asset files =="
git ls-files | rg -i '\.(woff2?|ttf|otf|eot)$' | sed -n '1,120p'

echo
echo "== exact remote font host references in tracked source files =="
git ls-files | rg '\.(html|css|scss|sass|less|ts|tsx|js|jsx|mjs|cjs)$' | \
  rg 'Resources|console|Console|source|src|assets|ui|website' | \
  xargs rg -n "fonts\.(googleapis|gstatic)\.com|Inter Tight|JetBrains Mono" 2>/dev/null | sed -n '1,240p'

echo
echo "== tracked console source/index candidates =="
git ls-files | rg '^(sdk/|packages/|apps/|src/|website/|console/|.*console.*index\.html$)' | sed -n '1,260p'

Repository: Mesh-LLM/mesh-llm

Length of output: 50374


Remove the Google Fonts dependency from the SDK console.

sdk/swift/Sources/MeshLLM/Resources/Console/index.html preconnects to fonts.googleapis.com and loads fonts.gstatic.com. Operators can run the Swift SDK console from the local node on restricted or offline networks, so this loads an external third-party resource for an operator-facing UI. If the design requires these fonts, move them behind a local/static policy rather than leaving a remote stylesheet in this resource.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/swift/Sources/MeshLLM/Resources/Console/index.html` around lines 109 -
114, Remove the Google Fonts dependency from the console HTML by deleting the
fonts.googleapis.com and fonts.gstatic.com preconnect links and the remote
stylesheet in the resource entrypoint. Keep the console self-contained for
offline use; if the Inter Tight or JetBrains Mono typography is required,
replace it with locally bundled/static assets rather than external URLs.

An "external relay" style plugin — one whose real inference traffic
bypasses this IPC channel entirely, going through its own HTTP proxy
instead — generates no application-level IPC traffic of its own. The
supervisor's periodic HealthRequest (health::HEALTH_CHECK_INTERVAL_SECS)
is the *only* guaranteed traffic such a plugin's connection ever sees, so
this timeout must comfortably outlast that cadence — the previous fixed
10s value is shorter than the 15s health-check interval, so it will
always eventually disconnect an idle-but-healthy relay plugin as falsely
idle, race-losing against its own supervisor's probe cadence, well before
any genuinely idle timeout should apply.

Derive it from HEALTH_CHECK_INTERVAL_SECS directly (4x plus a fixed
floor, to absorb a slow/jittery tick) rather than a separate hardcoded
constant, so the relationship this timeout depends on stays enforced if
that interval ever changes.

Found and confirmed via live testing of an external-relay plugin against
a real OpenAI-compatible server.

Signed-off-by: MahdiHedhli <16087011+MahdiHedhli@users.noreply.github.com>
…eers

A node whose only inference capacity comes from a plugin (e.g. one
relaying to an external OpenAI-compatible server — no local model ever
loads) is currently invisible to every other mesh peer, for two
independent reasons, even though its own local ingress and gossip
advertisement of its plugin's models already work correctly:

1. `NodeRole::Host { http_port }` — required for any peer's
   `hosts_for_model()` to consider a node as a route candidate at all
   (`mesh/peer_state.rs`) — is set in exactly one place in the whole
   crate, as a side effect of a local model finishing load
   (`startup_handles.rs`). A node with zero local models never becomes
   `Host`, so it's filtered out of every peer's routing consideration
   regardless of what its gossip payload advertises. Fixed with
   `spawn_plugin_host_role_watcher`: promotes to `Host` once the plugin
   manager reports at least one inference model, demotes back to `Worker`
   when it stops. Reads the plugin manager's already-debounced health
   state rather than probing anything itself, so a short poll interval
   doesn't add flapping risk. Only ever touches a `Host` role it set
   itself, never one a local model set.

2. `tunnel::Manager`'s http_port — which an inbound QUIC-tunneled request
   from a peer gets relayed to — is likewise only ever set as a side
   effect of a local model finishing load (`startup_handles.rs`, three
   call sites). Since `run_auto()` already unconditionally binds and
   starts the real API proxy for every node before any model resolution
   happens, the fix is simply to set it right there too, immediately
   after the tunnel manager is constructed — the three existing call
   sites in `startup_handles.rs` become redundant-but-harmless (same
   node, same `api_port`, for the process's lifetime) rather than
   conflicting with this.

Both changes are small and land at a single call site each in
`run_auto()`'s already-unconditional startup path, rather than requiring
any of the passive-listener-specific machinery an earlier version of this
fix (against an older base) needed — that machinery is dead code on
current `main` (see `run_auto_model_path_or_shutdown`'s `dead_code`
`expect`), superseded by this crate's own "daemon model lifecycle
reconciliation" work, which already generalized the node's own local
ingress and tunnel manager construction independent of local model
selection. This fix closes the one piece that generalization didn't
cover: role eligibility for plugin-only nodes.

See docs/plugins/plugin-hosted-mesh-serving-design.md for the full
analysis.

Signed-off-by: MahdiHedhli <16087011+MahdiHedhli@users.noreply.github.com>
Documents the root cause (with file:line references) and fix shape for
the previous commit: a plugin-only node's own local ingress and gossip
advertisement already worked correctly, but two independent, narrower
gaps — host-role eligibility and inbound tunnel routing — meant no other
mesh peer could ever discover or route a request to it. Also documents
what's deliberately deferred (capacity/VRAM host ranking, model
descriptors, tunnel-path streaming verification, live multi-peer
verification) and the history of an earlier, now-reverted version of this
fix that targeted a code path a since-landed upstream refactor made
unreachable.

Signed-off-by: MahdiHedhli <16087011+MahdiHedhli@users.noreply.github.com>
@MahdiHedhli
MahdiHedhli force-pushed the pr/mesh-wide-plugin-serving branch from b9ac454 to 0f97965 Compare August 7, 2026 18:19
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@MahdiHedhli

Copy link
Copy Markdown
Contributor Author

Update: rebasing onto current `main` surfaced that this crate's own recent "daemon model lifecycle reconciliation" work had already independently solved a chunk of what an earlier version of this PR targeted — specifically, `tunnel::Manager` construction and this node's own local ingress are now unconditional in `run_auto()`'s startup path regardless of local model state, whereas when I originally wrote this fix (against an older base) they weren't. That made the earlier version's changes to `network/openai/transport.rs` (routing plugin models through the passive listener) a no-op — that code path is dead in the primary flow now (see the comment on #1191 for detail) — so I dropped it.

What's left and still genuinely needed: `tunnel_mgr.set_http_port(...)` and `NodeRole::Host` promotion are still only ever triggered by a local model finishing load, even after that refactor — nothing generalized those two specifically for a plugin-only node. This PR is now scoped to just that, and is meaningfully smaller and simpler than the original version (no threading through the passive-listener/model-selection context, since that machinery turned out to be dead code already).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/mesh-llm-host-runtime/src/runtime/run_auto.rs`:
- Around line 1203-1207: The plugin model check around
PluginManager::inference_models must distinguish errors from a successful empty
model list. Match on the Result, log failures, and bypass the watcher-driven
role transition and remote-routing updates when sampling fails; only apply the
existing has_plugin_models-based transitions after a successful response.
- Around line 1170-1221: Extract spawn_plugin_host_role_watcher and its
PLUGIN_HOST_ROLE_WATCH_INTERVAL constant from run_auto.rs into a semantically
named runtime module such as plugin_host_role.rs, keeping the watcher behavior
unchanged. Expose the function as needed, register the new module, and update
run_auto.rs to invoke it while retaining only startup orchestration. Move any
tests specific to this watcher into the new module.
- Around line 1208-1217: The plugin watcher must use the ownership-aware role
transition mechanism shared with local-model lifecycle code instead of the
task-local plugin_promoted_role flag and separate role()/set_role() calls;
update the mixed-role handling around the plugin watcher in
crates/mesh-llm-host-runtime/src/runtime/run_auto.rs#L1208-L1217 accordingly. In
docs/plugins/plugin-hosted-mesh-serving-design.md#L81-L93, remove or qualify the
claim that mixed local/plugin nodes are protected until this ownership mechanism
is enforced.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f726a14e-ccda-4998-b8f1-e353f30713ef

📥 Commits

Reviewing files that changed from the base of the PR and between 126109b and 0f97965.

📒 Files selected for processing (3)
  • crates/mesh-llm-host-runtime/src/plugin/transport.rs
  • crates/mesh-llm-host-runtime/src/runtime/run_auto.rs
  • docs/plugins/plugin-hosted-mesh-serving-design.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/mesh-llm-host-runtime/src/plugin/transport.rs

Comment thread crates/mesh-llm-host-runtime/src/runtime/run_auto.rs Outdated
Comment thread crates/mesh-llm-host-runtime/src/runtime/run_auto.rs Outdated
Comment thread crates/mesh-llm-host-runtime/src/runtime/run_auto.rs Outdated
@MahdiHedhli

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Already reviewed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…x a role-ownership race

Two issues from review:

1. `plugin_manager.inference_models()` returning `Err` was collapsed into
   `has_plugin_models = false` via `.unwrap_or(false)`, treating a read
   failure identically to "the plugin genuinely has no models right
   now." A transient error could therefore demote an otherwise-healthy
   `Host` role (or silently block a legitimate promotion). Now logs a
   warning and skips the tick entirely on error, re-evaluating fresh next
   time rather than acting on an indeterminate read.

2. `http_port` is identical whether a local model or this watcher's
   plugin-driven promotion is the reason a node is `Host` — both derive
   it from the same `api_port` — so the role value alone can't
   distinguish who's responsible for it. If a local model started being
   served *after* this watcher's promotion, the watcher's stale
   `plugin_promoted_role` flag could still fire once the plugin's models
   later disappeared, demoting a role the local model may now depend on.
   Added a `node.models_being_served().is_empty()` check before actually
   changing the role on the demotion path — the flag still resets either
   way (the watcher's original promotion claim is stale regardless), but
   the role itself is only touched when no local model is active.

This isn't full ownership tracking — `node.set_role()` has no concept of
"who claimed this and why," and building that is a larger, more invasive
change than this fix's scope. Documented as a deliberately-deferred
follow-up, and softened the design doc's earlier claim that this watcher
"never touches a Host role a local model is responsible for" — that's
now accurate for the one race this patch closes, not an enforced
guarantee for every possible sequencing.

Signed-off-by: MahdiHedhli <16087011+MahdiHedhli@users.noreply.github.com>
@MahdiHedhli

Copy link
Copy Markdown
Contributor Author

Addressed 2 of the 3 findings from the fresh review in dc3568e:

  • Error vs. empty distinction: inference_models() returning Err was collapsed into "no models" via .unwrap_or(false). Now logs and skips the tick on error instead of acting on an indeterminate read.
  • Role-ownership race: this was a real, valid finding, not just style — http_port is identical whether a local model or this watcher's promotion is why a node is Host (both derive it from the same api_port), so the role value alone can't tell the two apart. If a local model starts being served after this watcher promotes the role, the watcher's stale flag could later demote a role the local model now depends on. Added a node.models_being_served().is_empty() check before actually changing the role on the demotion path. Also softened the design doc's claim about this to be accurate (it closes this one race, it isn't full ownership tracking) rather than overstated, and added "real ownership tracking for NodeRole::Host" as an explicit deferred follow-up.

Not addressed, with reason:

  • Extract the watcher into its own modulerun_auto.rs already has many small spawn_run_auto_*-style helpers defined inline (bootstrap proxy, discovery publisher, model intent channel, etc.) rather than in dedicated modules; this watcher matches that existing convention. A broader reorganization of this file is out of scope for this fix.

Recompiled, clippy clean, full unit test suite still passes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/plugins/plugin-hosted-mesh-serving-design.md (2)

111-116: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Describe the new watcher accurately.

The plugin host-role watcher is new background machinery, and it is skipped for --client nodes. Replace “unconditional additions” and “no new machinery” with wording that distinguishes unconditional tunnel-port setup from conditional watcher startup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/plugins/plugin-hosted-mesh-serving-design.md` around lines 111 - 116,
Update the run_auto() startup-path description to distinguish the unconditional
tunnel-port setup from the new background plugin host-role watcher, which starts
only for non-client nodes. Replace claims that both additions are unconditional
and involve no new machinery while preserving the clarification that no
passive-listener/model-selection context is threaded through the path.

5-10: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clarify the live test scope.

The header says serving works “through the mesh,” but the deferred work says peer discovery and successful routing to a plugin-only node have not been exercised. Re-word this as a single local-node proxy test through the tunnel path, or remove the routing-through-mesh wording.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/plugins/plugin-hosted-mesh-serving-design.md` around lines 5 - 10,
Revise the document’s live end-to-end test description to state that it covered
a single local-node proxy through the tunnel path only. Remove or qualify
wording that claims serving or routing through the mesh, while preserving the
distinction that peer discovery and routing to plugin-only nodes remain
untested.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@docs/plugins/plugin-hosted-mesh-serving-design.md`:
- Around line 111-116: Update the run_auto() startup-path description to
distinguish the unconditional tunnel-port setup from the new background plugin
host-role watcher, which starts only for non-client nodes. Replace claims that
both additions are unconditional and involve no new machinery while preserving
the clarification that no passive-listener/model-selection context is threaded
through the path.
- Around line 5-10: Revise the document’s live end-to-end test description to
state that it covered a single local-node proxy through the tunnel path only.
Remove or qualify wording that claims serving or routing through the mesh, while
preserving the distinction that peer discovery and routing to plugin-only nodes
remain untested.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 92718895-3bd4-464d-8dba-6f40f07590d5

📥 Commits

Reviewing files that changed from the base of the PR and between 0f97965 and dc3568e.

📒 Files selected for processing (2)
  • crates/mesh-llm-host-runtime/src/runtime/run_auto.rs
  • docs/plugins/plugin-hosted-mesh-serving-design.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/mesh-llm-host-runtime/src/runtime/run_auto.rs

Two imprecisions caught by review:

- The "Status" intro described the live test as "serving an LM Studio
  model through the mesh," which reads as claiming mesh-wide (multi-peer)
  serving was exercised — contradicted a few paragraphs later by
  "Deliberately deferred"'s explicit "not yet exercised by a live
  multi-peer test." Reworded to be precise: a single node's own local
  proxy, through the tunnel path, not yet through an actual inbound
  request from another peer.
- "Both changes are two small, unconditional additions ... no new
  machinery" undersold the host-role watcher, which is new background
  machinery and is started conditionally (skipped for --client nodes) —
  only the tunnel-port fix is truly "unconditional, no new machinery."
  Reworded to describe each accurately.

Signed-off-by: MahdiHedhli <16087011+MahdiHedhli@users.noreply.github.com>
@MahdiHedhli

Copy link
Copy Markdown
Contributor Author

Addressed both findings from the latest review in e932d02 — both were doc wording precision issues in `plugin-hosted-mesh-serving-design.md`:

  • The "Status" intro said the live test covered "serving an LM Studio model through the mesh," which read as claiming mesh-wide serving was exercised — contradicted a few paragraphs later by "Deliberately deferred"'s explicit "not yet exercised by a live multi-peer test." Reworded to be precise about what was actually tested (a single node's own local proxy through the tunnel path, not routing from another peer).
  • "Both changes are ... unconditional additions ... no new machinery" undersold the host-role watcher, which is new background machinery started conditionally (skipped for `--client` nodes) — only the tunnel-port fix is truly unconditional/no-new-machinery. Reworded to describe each accurately.

`run_auto.rs` itself (the role-ownership race + error-handling fix from the previous round) came back "skipped from review as similar to previous changes" — no new findings there.

@i386 i386 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and fixed the remaining actionable review findings. The production plugin-envelope timeout remains derived from the health cadence while tests use a short injected timeout; the watcher now lives in runtime/plugin_host_role.rs; and host-role claims are reference-counted across plugin and local-model lifecycles so one source cannot demote a host still owned by the other. Full validation: cargo fmt --all -- --check, cargo check -p mesh-llm-host-runtime, and cargo test -p mesh-llm-host-runtime --lib (1,912 passed, 8 ignored). The broad host-runtime Clippy run is still blocked by 28 pre-existing unrelated unfulfilled_lint_expectations errors. Approving; live multi-peer verification remains explicitly deferred in the PR description.

@i386
i386 enabled auto-merge (squash) August 8, 2026 07:25
@i386

i386 commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

thanks again!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/mesh-llm-host-runtime/src/mesh/node.rs`:
- Around line 1172-1197: Update claim_host_role and release_host_role to track
whether NodeRole actually transitions, release the claims and role locks before
invoking Node::regossip, and call it only when promotion or demotion occurs. Add
a connected-peer test covering both Worker-to-Host promotion and Host-to-Worker
demotion announcements.
- Around line 1172-1197: Extract HostRoleClaim, host-role claim storage,
claim_host_role, and release_host_role from the node implementation into a
semantically named host_role_claims mesh module. Move the associated tests and
update Node wiring, imports, and module declarations so the existing host/worker
role transitions and claim counting behavior remain unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 725b894d-0eb4-4ef7-91c5-0612a1c015a0

📥 Commits

Reviewing files that changed from the base of the PR and between e932d02 and 74ab7ad.

📒 Files selected for processing (8)
  • crates/mesh-llm-host-runtime/src/mesh/mod.rs
  • crates/mesh-llm-host-runtime/src/mesh/node.rs
  • crates/mesh-llm-host-runtime/src/mesh/tests/connections.rs
  • crates/mesh-llm-host-runtime/src/plugin/transport.rs
  • crates/mesh-llm-host-runtime/src/runtime/mod.rs
  • crates/mesh-llm-host-runtime/src/runtime/plugin_host_role.rs
  • crates/mesh-llm-host-runtime/src/runtime/run_auto.rs
  • crates/mesh-llm-host-runtime/src/runtime/startup_handles.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/mesh-llm-host-runtime/src/runtime/run_auto.rs
  • crates/mesh-llm-host-runtime/src/plugin/transport.rs

Comment on lines +1172 to +1197
pub async fn claim_host_role(&self, claim: HostRoleClaim, http_port: u16) {
let mut claims = self.host_role_claims.lock().await;
*claims.entry(claim).or_default() += 1;
let mut role = self.role.lock().await;
if matches!(*role, NodeRole::Worker) {
*role = NodeRole::Host { http_port };
}
}

pub async fn release_host_role(&self, claim: HostRoleClaim) {
let mut claims = self.host_role_claims.lock().await;
let Some(count) = claims.get_mut(&claim) else {
return;
};
if *count > 1 {
*count -= 1;
} else {
claims.remove(&claim);
}
if claims.is_empty() {
let mut role = self.role.lock().await;
if matches!(*role, NodeRole::Host { .. }) {
*role = NodeRole::Worker;
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Re-gossip after each role transition.

claim_host_role and release_host_role change NodeRole but do not call Node::regossip. Node::regossip is designated for role changes in crates/mesh-llm-host-runtime/src/mesh/node.rs lines 1597-1599.

Connected peers can retain the previous Worker or Host announcement until an unrelated gossip cycle occurs. This delays plugin-host routing after promotion and can retain stale routing after demotion. Track whether the role changed, release both locks, then call regossip only for an actual transition. Add a connected-peer test for promotion and demotion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/mesh-llm-host-runtime/src/mesh/node.rs` around lines 1172 - 1197,
Update claim_host_role and release_host_role to track whether NodeRole actually
transitions, release the claims and role locks before invoking Node::regossip,
and call it only when promotion or demotion occurs. Add a connected-peer test
covering both Worker-to-Host promotion and Host-to-Worker demotion
announcements.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Extract host-role claim management from node.rs.

node.rs exceeds 1,000 lines. HostRoleClaim, its storage, transition methods, and tests are a separable mesh responsibility. Move them to a semantically named mesh module such as host_role_claims.

As per coding guidelines, “When modifying a Rust source file over 1,000 lines, extract any separable responsibility into a semantically named module.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/mesh-llm-host-runtime/src/mesh/node.rs` around lines 1172 - 1197,
Extract HostRoleClaim, host-role claim storage, claim_host_role, and
release_host_role from the node implementation into a semantically named
host_role_claims mesh module. Move the associated tests and update Node wiring,
imports, and module declarations so the existing host/worker role transitions
and claim counting behavior remain unchanged.

Source: Coding guidelines

@i386
i386 disabled auto-merge August 8, 2026 07:40

@michaelneale michaelneale left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 (micn's agent) Deep review — traced the full claim/release lifecycle through startup_handles.rs, the watcher, and the tunnel path. The reworked shape is right: the two gaps are real (I verified NodeRole::Host was previously set in exactly one place as a local-model-load side effect, and tunnel.rs does silently drop inbound relays while http_port == 0), and the 74ab7ad9 move from a task-local flag to reference-counted claim_host_role/release_host_role on mesh::Node is a genuinely better mechanism than what the PR description still describes — it correctly handles multiple concurrent local-model tasks and the local-model+plugin overlap, and gating set_role behind #[cfg(test)] makes claims the only production mutation path. Nice.

Findings, most important first:

1. Claim leak on the StartupLoopOutcome::Exit path. startup_publish_loaded_runtime claims HostRoleClaim::LocalModel on every loop iteration, and startup_shutdown_local_model_loop releases it — but that teardown only runs for Shutdown and RelaunchSplit. The Exit outcome (reached via startup_handle_fallback_failureStartupLoopControl::Return, i.e. a split model withdrawn after stage-peer loss + failed local fallback) ends the task through startup_remove_runtime_instance_artifacts, which never releases the claim. Pre-PR this didn't matter (nothing ever demoted Host), but under the new claims model the leaked LocalModel claim is permanent: a plugin-only node that once briefly hosted a split model stays advertised as Host even after its plugin models disappear, because release_host_role(PluginInference) can never empty the map. Peers keep routing to a node serving nothing (it fails gracefully via the now-always-set tunnel port, but it's still wrong routing). Suggest releasing the claim in the Exit arm or inside startup_handle_fallback_failure.

2. Design doc is now stale against the code it documents. plugin-hosted-mesh-serving-design.md describes the watcher as using "a task-local flag ... a soft safeguard, not real ownership tracking", describes a models_being_served() demotion check that no longer exists in plugin_host_role.rs, and lists "Real ownership tracking for NodeRole::Host ... an explicit set of claimants ... only clear the role when the last claimant releases it" under Deliberately deferred — which is precisely what 74ab7ad9 implemented. Since the doc ships in-tree as the authoritative analysis, it should be updated in this PR (the PR body's "Fix" section has the same drift). Cheap fix, avoids the next reader distrusting the doc.

3. Behavior change worth calling out explicitly: nodes now demote to Worker on model shutdown. Previously a node stayed Host forever after its first model load. Now clean shutdown/relaunch-split releases the claim and (with no other claimants) demotes. I think this is correct — a node waiting for split peers isn't serving anything — but it's a mesh-visible gossip change on the local-model path, not just the plugin path, and the PR description doesn't mention it. Mixed-version note: older peers just see a role transition in gossip, which they already handle, so no wire concern.

4. Plugin transport timeout: 10s → 90s (HEALTH_CHECK_INTERVAL_SECS * 4 + 30). The reasoning is sound and well-commented (relay-style plugins see no IPC traffic between health probes, so 10s falsely disconnected every idle-healthy relay). Side effect: a genuinely hung plugin connection now takes up to 90s to detect instead of 10s. Acceptable trade, but worth a maintainer's conscious nod since it affects all plugins, not just relays.

5. Minor: the watcher task is spawned detached with no shutdown handle (loops forever; consistent with other run_auto spawns, fine); the 5s poll reading already-debounced health state is a sensible cadence; lock ordering in claim/release is consistent (claims → role) so no deadlock.

On the honest "not yet live multi-peer tested" flag: given micn's lab has a 2-node setup, the confidence test before 0.75.x-anything would be: node A serving a plugin-relayed model only, node B client --auto, verify B's /v1/models shows A's plugin model and a completion routes through A's tunnel. Happy to run that on the lab rig if useful — it's exactly the scenario the fix targets and would close the one open verification box.

Also noting CI shows only CodeRabbit on the latest merge commit — the workflow run for 68d30b22 doesn't appear to have started; worth a re-trigger before merge.

@ndizazzo ndizazzo changed the title Make plugin-only Serve mode nodes discoverable/reachable by other mesh peers feat: make plugin-only Serve mode nodes discoverable/reachable by other mesh peers Aug 9, 2026
@ndizazzo ndizazzo added this to the 0.76.0 milestone Aug 10, 2026

i386 commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

@micspiral need a dario on this

@micspiral

Copy link
Copy Markdown

🤖 Reviewed this locally on the current branch head (8944bc43). Formatting, clippy (-p mesh-llm-host-runtime --all-targets -D warnings), and the full lib suite are all clean for me — 1926 passed / 0 failed, including both new tests, so the "1910/1911 with one flaky" note in the description is now stale. The diagnosis checks out too: accepts_http_inference() really is just matches!(self.role, NodeRole::Host { .. }), gossip already merges plugin models via append_external_inference_models, and tunnel.rs's port == 0 early-return really did drop inbound tunnels. The refcounted claim registry that replaced the original task-local flag is a nicer shape than what the description describes, and the claim/release balance holds across the shutdown, relaunch-split, and fallback-failure exits.

One thing I want to flag before merge, because I don't think the description covers it:

Is unconditional set_http_port on --client nodes deliberate?

spawn_plugin_host_role_watcher / plugin_host_role::spawn is correctly gated behind if !is_client, but tunnel_mgr.set_http_port(api_port) immediately above it is not:

tunnel_mgr.set_http_port(api_port);
if !is_client {
    plugin_host_role::spawn(node.clone(), plugin_manager.clone(), api_port);
}

The effect is that a client node's inbound QUIC HTTP tunnel, which previously terminated at the port == 0 check, now relays into the local API proxy. With no local model that lands in route_missing_local_modelremote_mesh_targetshosts_for_model, which tunnels straight back out to another peer. So a --client node becomes a mesh-internal request relay for any admitted peer that opens a tunnel to it.

It isn't alarming: clients never advertise Host, so nothing selects them through hosts_for_model() or any_host(), requests terminate at a real host rather than looping, and RemoteQuicHttp still goes through the activity-policy admission check. But it is reachable by any peer that deliberately dials a client, which on the public mesh means any member — and it looks incidental rather than designed, since the watcher right below it went to the trouble of gating on exactly this condition.

If it's incidental, the fix is to gate it the same way:

if !is_client {
    tunnel_mgr.set_http_port(api_port);
    plugin_host_role::spawn(node.clone(), plugin_manager.clone(), api_port);
}

If it is deliberate — e.g. you want clients usable as relay hops — that's worth a sentence in the design doc under a "client nodes" heading, since it's a new inbound-traffic capability for a node role that previously had none, and reviewers of the plugin-only path won't otherwise think to look for it.

Smaller, non-blocking notes:

  • The IPC prefix timeout goes 10s → 90s. The reasoning is right (10s < the 15s health cadence was a guaranteed false-idle disconnect), but detection of a genuinely wedged plugin connection is now ~9x slower. Probably fine given the supervisor's own failure threshold covers liveness, but worth a nod from whoever owns plugin/health.rs.
  • read_envelope_from_reader now takes injected 1s timeouts, so the production constant is no longer exercised by any test. Necessary to keep tests fast, just noting the coverage gap.
  • Doc drift: the design doc and the run_auto.rs comment both still say spawn_plugin_host_role_watcher (renamed to plugin_host_role::spawn), and the doc still describes the task-local-flag approach that the refcount registry replaced.
  • A forced task.abort() (await_managed_model_stop(force: true), and the 3s shutdown drain) skips startup_shutdown_local_model_loop and therefore the release, so a stale LocalModel claim can leave the node advertising Host with nothing loaded. Not a regression — the role was previously never cleared at all — but the refcount makes it newly expressible.

On the missing live verification: the new host_role_claim_transitions_regossip_to_connected_peer test covers the gossip-propagation half in-process, which lowers the risk here quite a bit. The genuinely unexercised part is the inbound tunnel relay, which is also exactly the part the client-gating question above touches — so those two are worth settling together.

@michaelneale

Copy link
Copy Markdown
Collaborator

@MahdiHedhli I think this is ok, just that one Q from my bot above about the serving side effect - if you know if that is deliberate or not, and then I think this is good to go

michaelneale and others added 2 commits August 12, 2026 20:09
A --client node has no compute to offer and never advertises Host, so
setting its tunnel http_port only had one effect: an inbound QUIC HTTP
tunnel from any admitted peer would relay into the local API proxy,
find no local model, and route back out to another peer — making the
client a mesh-internal request relay. Nothing selects a client that
way, but any peer that deliberately dials one could use it.

That ingress also carries /mesh/load and /mesh/drop, dispatched ahead
of the inference admission check, so this widened exactly the surface
issue Mesh-LLM#1190 exists to narrow.

Gate set_http_port with the host-role watcher it already sits beside,
so a client stays as reachable as it was before this change — not at
all. Plugin-only nodes are never --client, so the case this PR targets
is unaffected.

Also refresh the design doc: it still named the pre-rename
spawn_plugin_host_role_watcher and described the tunnel port as
unconditional.

Co-authored-by: Mahdi Hedhli <16087011+MahdiHedhli@users.noreply.github.com>
Co-authored-by: Michael Neale <14976+michaelneale@users.noreply.github.com>
Signed-off-by: Michael Neale <14976+michaelneale@users.noreply.github.com>
@michaelneale
michaelneale merged commit 4890efd into Mesh-LLM:main Aug 12, 2026
91 of 93 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants