Repository navigation
An app's account can belong to the team - #757
Conversation
Tests for the account holder, the audience check, the account-mode switch, the shared-use inbox and the app screens. They fail until the code lands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… Personal or Shared mode Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…anded-over work Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ccount row carry its own heading Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… account-holding app a mode Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… from a signed run Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t it holds Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…request Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ounts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y its published display name Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ccount's stored id Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l on them Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t through each Bot Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nistrators the requests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g for owners Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ming who acted Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s for its shared apps Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd account holders Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, never a duplicate A second mcp_servers row at the same composio:// address could mark a Shared app Personal and let calls through it skip the audience gate, or let a non-admin connect a personal account to it. Every account decision, the gate, the audit's reachedAs and the mode write now resolve the app's answering row, and the gate refuses if the mode changes before the call goes out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Bot granted through a duplicate or non-canonical row of an app now gets the approval the audience gate actually reads, appears once in what it holds, and is found when the app's mode changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wering row Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… is ended Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a duplicate row Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fault Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e vendor Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hared beside the empty state Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…orget its rule on removal Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…o the running app Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… 37th positional argument Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e lint and format run Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two conflicts, both resolved by keeping each side: - `CHANGELOG.md`: 0.1.1 and 0.1.2 were cut while this branch was open, so the Unreleased section this branch writes now sits above them rather than in their place. The migration note's rollback target moves from 0.1.0 to 0.1.2, which is the release somebody would actually roll back to. - `server/src/app.ts`: an import-order collision only. Main's `createCoworkerRoutingService` import and this branch's shared-account imports landed in the same place; both are kept. Main's AG-UI 1.0 upgrade changes `@ag-ui/core` out from under this branch, so the merged tree needs `bun install` before it typechecks.
One conflict, in `CHANGELOG.md`, resolved by keeping both: #756's note about a new deployment starting with two coworkers sits beside this branch's Shared apps entry, under the same Unreleased heading. The upgrade preamble stays at the top of the section, where that section's notes belong.
davidmckayv
left a comment
There was a problem hiding this comment.
Reviewed against main at aff4981e (this branch already contains it; merged clean). The design holds: every call that uses a shared account goes through the audience gate on the answering row and is refused when anything is missing, steering from email, Slack or webhooks is held to its own approval, unoriginated handoffs are refused, only administrators connect or switch shared accounts, and connectionTokenFor refuses when the resolved account differs from the one decided. Migration 0052 chains to 0051, drizzle-kit check is clean, and the full suite on main plus this branch passes 6,078 against a fresh PostgreSQL with the same 13 local-environment failures as main.
One fix before merging:
Switching to Shared can leave the ask-before-write rule missing for good (server/src/plugins/account-mode.ts:147-160, early return at :119). switchMode sets the mode (setAccountModeColumns), then each Bot's approval (setApproval), then the write rule (createTeamRule), with no transaction. If either of the last two throws, the app is already shared, and a retry takes the accountMode === input.mode early return, reports changed: true, and never creates them. Missing approvals fail closed, but a missing write rule fails open: writes through the team account run without asking. Do the three writes in one transaction, or have the early return re-assert the approvals and the rule.
Hardening, not blocking:
A run with no initiator reads as a person (server/src/plugins/shared-use.ts, steeringOf; also mintRunAssertion defaulting to PERSON_INITIATOR in server/src/agents/callback-token.ts:138). Every background path today sets one (responsibilities, routines, memory, group turns, hops), so nothing gets through now, but a future path that forgets would pass the outside-input check. Refusing a missing initiator for shared accounts would close that.
…t does not say what started it switchMode set the mode, then each Bot's approval, then the ask-before-write rule, with no transaction, and a retry on an app already in that mode returned changed without doing anything. A failure after the mode left a Shared app writing through the team account without asking, permanently. The approvals and the rule are now made before the mode says shared, only where missing, and the already-in-that-mode path makes them exist again (or, for Personal, removes what a half-finished switch back left). The rule is never duplicated and an approval an administrator changed is not written over. A run that carried no initiator was read as a person, so a background path added later that forgot to pass one would get past the outside-input check on a shared account. The shared gate now refuses it, without filing a request no approval could answer, and records it as shared_steering. The direct tool-call route says it is a person.
An app's account can belong to the team
An administrator can make a Composio app Shared: one account, connected once, that every Bot granted the app acts as. Until now every brokered app was personal — each person connected their own mailbox or tracker, and a Bot acting for the team had no account of its own to use.
What this adds
brokered_connectionstable that records an account's holder — a person or the deployment — rather than assuming a person.Before upgrading
0052_shared_brokered_accountscopies every Composio connection intobrokered_connectionsand leavescomposio_connectionsin place, unwritten. Rolling back to 0.1.2 works, but accounts connected after the upgrade are invisible to it.mainis merged in as ofae688f2.Verification
bun run formatbun run lintbun run typecheckThe suite was run in batches rather than as a single
bun test, because a whole-suite run exhausts PostgreSQL's default 100-connection limit locally — the suite now has 91 test files that each open a 2-connection pool, up from 62 onmain. Every file passes, and CI'stestsjob passes as a single run.Tested against a real Composio account
Driven through the browser against the live Composio API, not only in tests:
openbot-deployment:<id>:<random>.holder=deploymentwith a null user id — there is no personal row for the app at all.audience: "owner"; granting to a public Bot recordedaudience: "team", each computed from the Bot's own exposure in the same request.owner→team), which is the administrator branch; an owner without admin rights would have filed a request instead."reachedAs": "deployment".bun run test:live-composio) passes for the deployment identity.One pre-existing live test fails, unrelated to this branch:
a listing carries a version and a behaviour label for every action. Composio has begun returning actions taggedcreateHint/updateHintinstead ofreadOnlyHint/destructiveHint— 25 of 63 Gmail actions, includingGMAIL_SEND_EMAIL. The classifier's fail-closed branch treats them as writes, so nothing is misclassified as a read, but that branch is now load-bearing and the test's assumption that it never fires no longer holds. Worth a follow-up to teacheffectOfthe new tags.🤖 Generated with Claude Code