Skip to content

refactor(providers): declare provider capabilities and route the shell by them - #710

Open
charleslpan wants to merge 6 commits into
mainfrom
refactor/provider-capability-contracts
Open

charleslpan wants to merge 6 commits into
mainfrom
refactor/provider-capability-contracts

Conversation

@charleslpan

@charleslpan charleslpan commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

One branch, six commits, one stage per commit, ./scripts/check.sh green at every commit and on the rebased whole.

  1. Capability declaration + seam conformance — PROVIDER_CAPABILITIES in packages/providers/src/capabilities.ts states each observed provider's location, hook, credential kind, and acts. implementedActs (@sidecar/providers/testing) reads the seams each adapter actually overrides (cloud route seams, the local deliverMessage seam, public methods for the rest, union over composite members, throws on an unknown base) and the test requires equality in both directions. CONNECTION_KIND opens @sidecar/credentials/connections. Hand-written lists in root AGENTS.md and PRIVACY.md are anchored by phrase.
  2. One workspace registration table — WORKSPACE_PROVIDER_ID_LIST, workspaceProviderRegistrations with a declared observation mode (host-enriched / decorated / none) and per-pass refresh. The desktop's adapterFor, project offer, and observation loop iterate the table; the hand-wired Superset and local Conductor branches are gone.
  3. Host claim contract — WorkspaceHostRegistration.claim / ownsControl; SupersetWorkspaceHost owns the Superset pass and delivers the four CLI acts for claimed rows. The performer's four Superset bypass blocks become host lookups; every capability re-check, count, and the no-refresh-after-host-act behavior are unchanged and tested.
  4. One connection model — CONNECTIONS / CONNECTION_LIST, InteractiveSignIn / ConsentConnect contracts, ConnectionRegistration assembled once in apps/desktop/src/main/connections.ts, generic connectProvider / beginProviderSignIn / … / disconnectProvider bridge entries replacing the Superset-named and Linear-named IPC, RuntimeStatus.cliConnections and consentSignInAvailable replacing three provider-named fields, issueTrackerRegistrations in @sidecar/trackers. Renderer slot and controls keyed by connection id.
  5. Declared agent choice and connection rows — WORKSPACE_AGENT_CHOICE, WORKSPACE_AGENT_SETTING_ID (literal allowlist members), templated guide rows with labels pinned by test, Connections page iterated from CONNECTION_LIST by kind with shared workspace sub-rows, settings search built from the same tables.
  6. Guide composition — @sidecar/providers/vocabulary door, WORKSPACE_PROVIDER_CAPABILITIES (Superset checked as adapter seams + stated host acts), Luke's guide composes every provider list it speaks; coverage tests become structural.

Nothing widens: declared acts are the existing overrides, host acts are the four CLI calls already made, sign-in and disconnect run the same CLI commands, the analytics allowlist is untouched. PRIVACY.md and root CLAUDE.md are unchanged.

Deviations from the plan, called out

  • The plan named a read-transcript-since act and readTranscriptSince seam; neither exists in the tree, so the act set has eight members, not nine.
  • The one wire value change is the sign-in stage organization → scope; the snapshot's organizations field is also renamed scopes so the generic contract carries no Superset noun. Main and renderer land together.
  • Superset's installed/connected state moves from a one-time bootstrap stat to the settings snapshot, read fresh from the CLI at each snapshot; the row hides while the connection is cli-missing or not yet known.
  • The composed creation list reads "Codex, Conductor, Superset, and Conductor (local)" (registry order); the apps-beside-a-session sentence now also names Claude, from SESSION_APPLICATION_NAME.
  • The Superset search result now wears its provider mark like every other connection row (it wore the plug icon before). Labels are unchanged and pinned.
  • A stored default workspace provider with no offered projects now draws its display name rather than its raw id.
  • SETTINGS_SEARCH_ROW.CODEX_CLOUD is dropped; the Codex row anchors by its id like every other row.
  • The plan's line numbers were stale against the tree; every site was located and verified before editing.
  • Superset workspace rows surviving stopSessionObservation is existing behavior, preserved and noted as a follow-up.

Verification

  • ./scripts/check.sh at each of the six commits: exit 0.
  • ./scripts/check.sh on the rebased branch (base 2767418): exit 0 — biome and oxlint clean (pre-existing CSS specificity warnings only), every package typechecks, tests: 2211 passing, 0 failing, desktop main and renderer build.
  • Not performed: ./scripts/verify.sh and ./scripts/run.sh cannot run in this Linux cloud sandbox. No macOS package, no visual evidence, and no physical-notch check were produced. Stages 4 and 5 change renderer files, so the completion invariant still owes: inspect the Connections page (Codex prose row, Conductor with nested local, Superset with kind row, Linear) and the sign-in slot in the visual evidence, and on a Mac with Superset signed in exercise message, delete, rename, and add-agent on a hosted row; Superset connect, choose organization, cancel, disconnect; codex logout between passes; Linear connect and disconnect.

🤖 Generated with Claude Code

Open in Alchemize

Automated visual evidence

Download the deterministic macOS evidence · workflow run

  • Commit: 7125142e1cfc3735e8ea50ffce8765cd26114a4e
  • Scenario: smoke
  • Physical-notch check: not performed by CI

charleslpan and others added 6 commits September 4, 2026 23:58
…inst the adapter seams

Adds PROVIDER_CAPABILITIES, one row per provider naming its location,
observation hook, credential kind, and acts, with a conformance test that
reads each adapter's overridden seams and compares. The connection kind
vocabulary opens a renderer-safe door on @sidecar/credentials. Hand-written
provider lists in Luke's guide, the root agent guide, and PRIVACY.md are
covered by tests anchored on fixed phrases.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…sktop iterates

Adds WORKSPACE_PROVIDER_ID_LIST and workspaceProviderRegistrations, which
folds Superset's workspace adapter and local Conductor's beside the observed
providers with a declared observation mode and per-pass refresh. The desktop
resolves an adapter by one lookup, offers projects from one list, and runs
one observation loop over the table instead of hand-wired extra branches.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ontract

WorkspaceHostRegistration gains claim and ownsControl, and SupersetWorkspaceHost
in @sidecar/superset owns the Superset pass: the host-state read, the four CLI
acts a managed row advertises, and the chatless workspace rows. The act
performer asks the claiming host instead of a hand-wired Superset bypass, with
every capability re-check, count, and no-refresh behavior unchanged. The
providers guide describes the declaration, the registration tables, and the
claim contract; Luke's guide names Superset beside Conductor for adding agents.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…neric rows

Adds the connection vocabulary (CONNECTIONS, CONNECTION_LIST, kinds, sections,
CLI-login declarations) to @sidecar/credentials, the interactive and consent
sign-in contracts, and a ConnectionRegistration the main process assembles
once in connections.ts. Generic bridge entries replace the Superset-named
sign-in IPC and the Linear tracker IPC; RuntimeStatus carries cliConnections
and consentSignInAvailable in place of three provider-named fields; the issue
trackers come from issueTrackerRegistrations. The renderer's sign-in slot and
panel controls are keyed by connection id and read every word from the
declaration. The one wire value change is the sign-in stage organization,
now scope, and the snapshot's organizations field is now scopes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ations

WORKSPACE_AGENT_CHOICE declares whether each workspace provider's new agents
run a tabled model, an observed kind, or nothing, and WORKSPACE_AGENT_SETTING_ID
maps the choosing providers to their literal setting ids. The settings schema,
the spoken agent selection, the remember-defaults side effect, and the store
types branch on the declaration. The Connections page iterates the connection
list by kind (key, CLI login as prose or button, local), hangs the same
workspace sub-rows under each connected row, and the settings search builds
its rows and drawn-state table from the same declarations. Labels are pinned
by tests; a stored default provider with no offered projects now draws its
display name rather than its raw id.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…y declaration

Opens the renderer door @sidecar/providers/vocabulary and extends the
declaration to every workspace provider (WORKSPACE_PROVIDER_CAPABILITIES),
with Superset's row checked against its adapter's seams plus the four acts
its host states. Luke's guide composes its transcript, creation, add-agent,
rename, keyless-local, CLI-login, and app-mark lists from the declarations
and the session application names, and its coverage test becomes structural.
The providers and renderer guides describe the declaration tables, the
registration files, the host claim contract, and the new doors.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 5, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
luke-web Ready Ready Preview Sep 5, 2026 12:02am UTC

Request Review

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

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

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7125142. Configure here.

if (!row.interactiveSignIn) return;
row.interactiveSignIn.cancel();
row.countSignInEdge?.(SIGN_IN_EDGE.CANCEL);
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Linear cancel and reopen never run

High Severity

cancelProviderSignIn and reopenProviderSignIn only invoke interactiveSignIn. Linear is wired to those same bridge entries through consentSignIn, so Cancel and Reopen never reach the OAuth flow. The slot closes while the main-process listener keeps running, and a later redirect can still store the grant after the user cancelled.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7125142. Configure here.

const connections = JSON.stringify(snapshot.status.cliConnections);
if (connections === announcedCliConnections) return;
announcedCliConnections = connections;
panels.broadcast(channels.onSettingsChanged, snapshot);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Overlapping CLI snapshots can stale

Medium Severity

broadcastCliConnections awaits a snapshot that now shells out to the Superset CLI, then publishes whichever call finishes last. Sign-in onChange and the observation loop fire overlapping broadcasts with no generation guard, so an older signed-out read can overwrite a newer connected snapshot.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7125142. Configure here.

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

agent_tooling_trust_boundary — not exploitable

Verdict: exploitable: false · severity: none

What changed

PR 710 adds PROVIDER_CAPABILITIES / WORKSPACE_PROVIDER_CAPABILITIES and rewrites Luke guide fact strings so provider name lists (message / control / create workspace / add agent / rename / read transcript) are composed via providersWith(PROVIDER_ACT.*) instead of hand-written lists. That is trusted product copy rendered into app-guide context text (appGuideContextText), not tool schemas, not hidden prompt instructions, and not an auto-approval path.

Why this is not a boundary crossing

  1. Guide text is trusted product copy, assembled in luke-guide.ts from build-fixed capability tables. It informs the model what Luke may offer; it does not authorize execution.
  2. Runtime gates still bind every listed act in session-acts.ts:
    • authorizeActEnvelope requires envelope.armed (developer-opened turn)
    • message → observed session + canReceiveMessage
    • control → advertised control id on latest roster
    • create workspace → connected adapter + listed project (+ spawnable agent / bounds)
    • add agent → roster spawnableAgents
    • rename workspace/session → renameTarget / canRename
    • read transcript → observed local session + adapter that can read
  3. Tool execution still goes through the armed-turn carrier (carryAct / #toolCallOutput) and main-process revalidation before any provider/host call. Host claim routing in this PR changes who delivers an already-gated act, not whether the gate runs.
  4. Per module rules: model-influenced tool attempts without a concrete gate bypass are out of scope; intentional guide inference over declared capabilities with policy gates intact is expected.

Residual note (non-finding)

If a future change ever drove tool registration or skipped armed/roster checks from the capability table alone, re-review that path. This PR does not do that.

Open in Web View Automation 

Sent by Cursor Security Agent: Security Reviewer

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

agent_tooling_trust_boundary — not exploitable

Verdict: exploitable: false · severity: none · false-positive risk: high

Candidate claimed that routing message/control/add-agent/rename through generic workspaceHosts.claim / ownsControl could let a host steal acts, send CLI writes to the wrong session, or fail-open. Checked against the pre-change supersetContext path: this is an equivalent refactor, not a new boundary crossing.

Checks

  1. Roster before host acts — unchanged. Message still requires sessionRegistry.get + canReceiveMessage; control requires roster membership + advertised control id; add-agent requires spawnableAgents; rename requires renameTarget. Host lookup runs only after those gates.
  2. Organization binding — SupersetWorkspaceHost.claim still calls actableContext(..., this.#organization) with the same actableInOrganization predicate (activeOrganizationId !== undefined && context.organizationId === activeOrganizationId). Same shape as old supersetContext → observedSupersetWorkspaces.actableContext(..., observedSupersetOrganization).
  3. ownsControl vs isSupersetControlId — ownsControl is isSupersetControlId. hostForControl still requires both control ownership and a successful claim, so provider-native controls on a managed row still reach the adapter.
  4. Non-Superset hosts — Conductor and Claude registrations omit claim / ownsControl (asserted in workspace-hosts.test.ts). Only Superset implements claim in this PR.
  5. Read failure — fail-closed for CLI writes, not fail-open. Failed read() clears #organization to undefined, so claim returns undefined and acts fall through to the provider adapter — same as old observedSupersetOrganization = undefined on read failure. Covered by host test “failed read … no claims”.
  6. Boundary — same four CLI acts, same org-bound context, same control id allowlist, same roster-first gating. The generic interface widens the type surface for future hosts; this PR does not add another claimer.

Affected lines (review focus, not vulnerable)

  • apps/desktop/src/main/ipc/session-acts.ts — hostActsFor / hostForControl; message/control/add-agent/rename host branches
  • packages/superset/src/host.ts — claim, ownsControl, read failure → empty org/snapshot
  • packages/providers/src/shared/workspace-hosts.ts — optional claim / ownsControl; only Superset supplied
  • packages/superset/src/workspaces.ts — actableContext / actableInOrganization (unchanged binding)

Evidence (brief)

Old: managed = supersetContext(identity) after roster checks; context from actableContext(..., observedSupersetOrganization); controls also required isSupersetControlId.
New: hosted = hostActsFor(identity) / hostForControl(...) after the same roster checks; claim uses the same actableContext; ownsControl ≡ isSupersetControlId; non-Superset hosts cannot claim.

Open in Web View Automation 

Sent by Cursor Security Agent: Security Reviewer

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

agent_tooling_trust_boundary — generic connection IPC

Verdict: not exploitable (false positive / by-design trust model)

Candidate claimed that replacing provider-named IPC with connectProvider / disconnectProvider / beginProviderSignIn (keyed by connection id) lets a renderer or injected caller hit the wrong seam or index connections[] unsafely.

Checks

Check Result
Wire validation Args use oneConnectionId / connectionIdAndString → isConnectionId (Object.hasOwn(CONNECTIONS, …)). Bridge registration rejects untrusted senders and bad args before handlers run.
Unsupported seams Handlers refuse when the row lacks consentSignIn / disconnect / interactiveSignIn (UNSUPPORTED). Covered by connections.test.ts (e.g. connect on Superset, disconnect on local, sign-in on key row).
Prototype pollution Allowlist via Object.hasOwn; inherited names like toString rejected (tested).
settings-rows credential vs connection id setProviderApiKey is gated by isCredentialProviderId. Current credential ids are a subset of connection ids; connections[providerId].onCredentialChanged is the intentional same-row refresh, not cross-connection dispatch.
Cross-connection privilege Trusted renderer (url === rendererUrl()) can already invoke every Settings connection act. Generics do not widen that; wrong seams still fail closed. No less-privileged actor path.

exploitable: false
severity: none
false-positive risk: high — intended generic dispatch + per-row seam refusal, same privilege envelope as the old named channels.

Open in Web View Automation 

Sent by Cursor Security Agent: Security Reviewer

This branch was successfully deployed

1 active deployment
Preview — 7125142e Deployed Sep 5, 2026 by vercel[bot]
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.

1 participant