Skip to content

fix(app): let a plugin server's event stream follow its endpoint - #60087

Merged
Hona merged 2 commits into
anomalyco:devfrom
Hona:plugin-stream-endpoint
Oct 29, 2026
Merged

Hona merged 2 commits into
anomalyco:devfrom
Hona:plugin-stream-endpoint

Conversation

@Hona

@Hona Hona commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

Closes #60005. A plugin's server now loads after a Desktop restart: its event stream follows the endpoint the plugin reports instead of retrying 127.0.0.1:0.

Cause

runtime/plugin/servers.tsx restores a plugin's last inventory on the placeholder endpoint http://127.0.0.1:0, and the app creates the server's SDK before the plugin reports. The transport moved to the real endpoint, but the event stream kept the API it was created with. Its only way out, reconnect, was read once at creation and was undefined for the placeholder.

flowchart LR
  restore["restored server: 127.0.0.1:0"] --> report["plugin reports its endpoint"]
  report --> http["requests: new endpoint"]
  report --> before["stream before: retries port 0 forever"]
  report --> after["stream now: next reconnect resolves the endpoint; a connected stream moves at once"]
Loading

Change

  • Every reconnect resolves the endpoint again. For a plugin's server, the stream asks the plugin's reconnect if it has one at that moment, otherwise it takes the plugin's current endpoint.
  • A connected stream moves at once when the plugin reports a different endpoint, instead of staying on the old one until it drops.
  • No double work. The transport keeps its clients when the endpoint is unchanged, so a managed plugin whose own reconnect already moved the stream is not asked again.
  • The transport keeps its endpoint as plain values. A plugin's endpoint can be a store proxy that changes in place, such as an SSH sign-in that issues a new password. Holding the proxy made the new endpoint look unchanged, so the clients kept the old password. The app's controller already copies credentials for the same reason (runtime.tsx).
  • Sidecar and HTTP servers behave as before.

Tests

  • test-browser/plugin-server-stream.test.ts drives the real createServerSdkContext: the server starts on the placeholder, the plugin reports an endpoint, then another.
    • Without the reconnect change, the first step times out on port 0.
    • Without the move-at-once change, the second step times out.

This replaces #60006 by @Belkata, which found the cause. This PR also moves a connected stream when the endpoint changes and keeps the change inside the SDK context.

Visual

<svg xmlns="http://www.w3.org/2000/svg" width="240" height="72" viewBox="0 0 240 72" fill="none" stroke="currentColor" stroke-linecap="round">
  <rect x="8.5" y="24.5" width="40" height="24" rx="3" opacity=".5"/>
  <line x1="16" y1="36.5" x2="40" y2="36.5" opacity=".5"/>
  <g opacity=".35"><rect x="190.5" y="8.5" width="42" height="20" rx="3"/><line x1="198" y1="18.5" x2="224" y2="18.5"/></g>
  <rect x="190.5" y="44.5" width="42" height="20" rx="3" opacity=".7"/>
  <line x1="198" y1="54.5" x2="224" y2="54.5" opacity=".7"/>
  <path d="M50 36 C110 36 130 18 188 18" stroke-dasharray="4 4" opacity=".4">
    <animate attributeName="opacity" values=".4;.4;0;0;.4" keyTimes="0;.35;.45;.95;1" dur="4s" repeatCount="indefinite"/>
  </path>
  <path d="M50 36 C110 36 130 54 188 54" stroke-dasharray="4 4" opacity="0">
    <animate attributeName="opacity" values="0;0;.8;.8;0" keyTimes="0;.35;.45;.95;1" dur="4s" repeatCount="indefinite"/>
    <animate attributeName="stroke-dashoffset" values="16;0" dur="1s" repeatCount="indefinite"/>
  </path>
  <circle cx="211.5" cy="54.5" r="3" fill="currentColor" opacity="0">
    <animate attributeName="opacity" values="0;0;.8;.8;0" keyTimes="0;.45;.55;.95;1" dur="4s" repeatCount="indefinite"/>
  </circle>
</svg>

Added by the opencode bot (AI-generated).

Related: #57807

A plugin server restored from the plugin's last inventory starts on the
placeholder endpoint 127.0.0.1:0. Its event stream read `reconnect` once at
creation, so after the plugin reported the real endpoint the stream kept
retrying port 0 and the server's sessions never loaded.

The stream now resolves the plugin's current endpoint on every reconnect,
and a connected stream moves as soon as the plugin reports a new endpoint.

Closes anomalyco#60005
Copilot AI balanced review requested due to automatic review settings October 29, 2026 03:11
@Hona
Hona requested a review from Brendonovich as a code owner October 29, 2026 03:11
@Hona
Hona enabled auto-merge (squash) October 29, 2026 03:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@opencodejr opencodejr 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.

The fix works. I ran the #60005 reproduction against the real createServerSdkContext with a fake fetch. On the base, the event stream keeps retrying 127.0.0.1:0/api/event after the plugin reports its endpoint. With this change, only the retry already in flight still goes to port 0; every later request goes to the reported endpoint and stays there. The new browser test fails on the base and passes here, 5 runs out of 5. The other runtime/server tests and tsc -b pass.

The public API is unchanged: everything changed is internal to packages/app. ServerTransport.update now keeps its clients when the endpoint is unchanged.

I found nothing that blocks a merge.

Related: #57807

@Hona
Hona merged commit 5c0376f into anomalyco:dev Oct 29, 2026
22 checks passed

@opencodejr opencodejr 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.

My earlier review had no open points. The new commit copies the endpoint into plain values, so a password changed in place on the plugin's endpoint now rebuilds the clients. It drops nothing, since the endpoint type has only url and password. I re-ran the #60005 reproduction, the new browser test, the runtime/server unit tests and tsc -b on the new head: the stream still follows the reported endpoint and everything passes. The public API is unchanged. Nothing new to raise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

desktop: plugin-provided server stuck loading after restart (event stream retries http://127.0.0.1:0)

2 participants