Repository navigation
refactor(providers): declare provider capabilities and route the shell by them - #710
charleslpan wants to merge 6 commits into
Conversation
…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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ 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); | ||
| }, |
There was a problem hiding this comment.
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)
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); |
There was a problem hiding this comment.
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)
Reviewed by Cursor Bugbot for commit 7125142. Configure here.
There was a problem hiding this comment.
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
- Guide text is trusted product copy, assembled in
luke-guide.tsfrom build-fixed capability tables. It informs the model what Luke may offer; it does not authorize execution. - Runtime gates still bind every listed act in
session-acts.ts:authorizeActEnveloperequiresenvelope.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
- 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. - 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.
Sent by Cursor Security Agent: Security Reviewer
There was a problem hiding this comment.
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
- Roster before host acts — unchanged. Message still requires
sessionRegistry.get+canReceiveMessage; control requires roster membership + advertised control id; add-agent requiresspawnableAgents; rename requiresrenameTarget. Host lookup runs only after those gates. - Organization binding —
SupersetWorkspaceHost.claimstill callsactableContext(..., this.#organization)with the sameactableInOrganizationpredicate (activeOrganizationId !== undefined && context.organizationId === activeOrganizationId). Same shape as oldsupersetContext→observedSupersetWorkspaces.actableContext(..., observedSupersetOrganization). ownsControlvsisSupersetControlId—ownsControlisisSupersetControlId.hostForControlstill requires both control ownership and a successful claim, so provider-native controls on a managed row still reach the adapter.- Non-Superset hosts — Conductor and Claude registrations omit
claim/ownsControl(asserted inworkspace-hosts.test.ts). Only Superset implements claim in this PR. - Read failure — fail-closed for CLI writes, not fail-open. Failed
read()clears#organizationtoundefined, soclaimreturnsundefinedand acts fall through to the provider adapter — same as oldobservedSupersetOrganization = undefinedon read failure. Covered by host test “failed read … no claims”. - 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 branchespackages/superset/src/host.ts—claim,ownsControl,readfailure → empty org/snapshotpackages/providers/src/shared/workspace-hosts.ts— optionalclaim/ownsControl; only Superset suppliedpackages/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.
Sent by Cursor Security Agent: Security Reviewer
There was a problem hiding this comment.
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.
Sent by Cursor Security Agent: Security Reviewer




Summary
One branch, six commits, one stage per commit,
./scripts/check.shgreen at every commit and on the rebased whole.PROVIDER_CAPABILITIESinpackages/providers/src/capabilities.tsstates 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 localdeliverMessageseam, public methods for the rest, union over composite members, throws on an unknown base) and the test requires equality in both directions.CONNECTION_KINDopens@sidecar/credentials/connections. Hand-written lists in rootAGENTS.mdandPRIVACY.mdare anchored by phrase.WORKSPACE_PROVIDER_ID_LIST,workspaceProviderRegistrationswith a declared observation mode (host-enriched/decorated/none) and per-pass refresh. The desktop'sadapterFor, project offer, and observation loop iterate the table; the hand-wired Superset and local Conductor branches are gone.WorkspaceHostRegistration.claim/ownsControl;SupersetWorkspaceHostowns 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.CONNECTIONS/CONNECTION_LIST,InteractiveSignIn/ConsentConnectcontracts,ConnectionRegistrationassembled once inapps/desktop/src/main/connections.ts, genericconnectProvider/beginProviderSignIn/ … /disconnectProviderbridge entries replacing the Superset-named and Linear-named IPC,RuntimeStatus.cliConnectionsandconsentSignInAvailablereplacing three provider-named fields,issueTrackerRegistrationsin@sidecar/trackers. Renderer slot and controls keyed by connection id.WORKSPACE_AGENT_CHOICE,WORKSPACE_AGENT_SETTING_ID(literal allowlist members), templated guide rows with labels pinned by test, Connections page iterated fromCONNECTION_LISTby kind with shared workspace sub-rows, settings search built from the same tables.@sidecar/providers/vocabularydoor,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.mdand rootCLAUDE.mdare unchanged.Deviations from the plan, called out
read-transcript-sinceact andreadTranscriptSinceseam; neither exists in the tree, so the act set has eight members, not nine.organization→scope; the snapshot'sorganizationsfield is also renamedscopesso the generic contract carries no Superset noun. Main and renderer land together.cli-missingor not yet known.SESSION_APPLICATION_NAME.SETTINGS_SEARCH_ROW.CODEX_CLOUDis dropped; the Codex row anchors by its id like every other row.stopSessionObservationis existing behavior, preserved and noted as a follow-up.Verification
./scripts/check.shat each of the six commits: exit 0../scripts/check.shon the rebased branch (base2767418): 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../scripts/verify.shand./scripts/run.shcannot 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 logoutbetween passes; Linear connect and disconnect.🤖 Generated with Claude Code
Open in Alchemize
Automated visual evidence
Download the deterministic macOS evidence · workflow run
7125142e1cfc3735e8ea50ffce8765cd26114a4esmoke