Skip to content

Connect many apps through one Composio key; footer button becomes Connect apps - #323

Merged
Birfy merged 6 commits into
mainfrom
feat/311-connect-apps-gateway
Oct 18, 2026
Merged

Birfy merged 6 commits into
mainfrom
feat/311-connect-apps-gateway

Conversation

@Birfy

@Birfy Birfy commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #311

Also folds in #322 (Danyang confirmed): the bottom-left sidebar footer button now reads Connect apps (连接应用) and opens Settings > Connectors instead of the shell's plugin panel. The plugin panel stays reachable from a new Plugins (插件) row in the account menu, so classic mode is not needed.

Settings > Connectors gets a Composio row: paste one Composio API key, pick the apps (Gmail, Google Calendar, Slack, Notion, Linear and more, or type any Composio app name), and every Bot gets the tools as mcp__composio__*. Composio shows a sign-in link in the chat the first time a Bot needs an app. Choose apps changes the list later without the key.

How: the host asks Composio's tool router for a session limited to the picked apps (workbench off) and mounts its MCP address with DSH's own MCP client, next to GitHub. The connector code is now a small table of definitions shared by GitHub and Composio.

Secrets: COMPOSIO_API_KEY is kept with scope [] (no Bot shell), sent only as the x-api-key header to Composio's own host (https, MCP address must be on the API host, no redirects), proved by creating the session before it is saved, never written to state.json, redacted from errors. connector-apps is shared-blocked like the other connector writes. composio is a reserved capability name; composio: false hides the row.

Ran at tip: ARCH_DOCS_BASE=origin/main TZ=UTC npm run verify (green, core 852 tests incl. new Composio tests for the request, key handling, mount, apps change, refusals, https and same-host checks). Not run: clicking the footer button and the Plugins row in a running app (source-level tests only), a real Composio key and account, smoke test, a visual check in the browser (only a server-side render of the row).

Not in this PR: a per-app approval gate for write actions (tool approval stays DSH's normal one), per-Bot app grants, Composio in-app sign-in buttons.

Settings > Connectors gets a Composio row. The user pastes one API key and
picks apps; the host asks Composio's tool router for a session limited to
those apps and mounts its MCP address with DSH's MCP client, so Bots get
mcp__composio__* tools. Composio asks for each app's sign-in in the chat.

The key is a secret kept with no Bot shell scope, sent only as the
x-api-key header to Composio's own host (https, same-host MCP address, no
redirects), never written to state.json and redacted from errors. The
connector code is now a table of definitions shared with GitHub.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-18T09:52:43.097554Z b974d27 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b974d27b80

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread core/src/host/connectors.js Outdated
return { userId, apps }
},
async mountConfig(token, options, saved) {
const { url } = await composioSession(token, { api: options.api, userId: saved.userId, apps: saved.apps })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reuse or delete remote Composio sessions

Whenever the host starts, the user retries, or the app list changes, this creates a fresh remote session, but unmount only disposes the local MCP fiber and neither stores nor deletes the returned session_id; the preceding connection check also creates another discarded session. Because Composio sessions persist and do not expire, routine use accumulates permanent orphan sessions and can eventually exhaust or clutter the account. Return and persist the session ID so it can be reused, or delete superseded validation/mount sessions.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in b346db6 (code in 58b221f; the rest is a merge of main, with the generated patches regenerated).

  • The Composio session is now made once per connect or Choose-apps change and kept in state.connectors.composio.session ({ id, url, apps }, never the key). The key check and the mount share it, so connecting makes one session, not two.
  • Mount and Try again reuse it only while its address is still https on the API host, the apps match, and GET /tool_router/session/{id} still finds it. Otherwise a new one is made and kept.
  • A superseded session is deleted with DELETE /tool_router/session/{id}: the old one after Choose apps or a new key (using the old key), the new one if saving it failed, and the kept one on Disconnect. The delete is best effort. A failure or outage never fails the action, and a 404 counts as already gone. The id is validated and URL-encoded, and the key goes only in the x-api-key header with redirect: 'error'.
  • Also fixed a race I found: a secret change during connect started a mount before the connection was saved, which could create a stray session.
  • Tests: core/test/connectors.test.js covers one session per connect, reuse on Try again, recreate when Composio no longer has it, delete on Choose apps (and no delete when the new list is refused), delete on Disconnect (also when the delete fails), and a new key. The state file and errors never contain the key.

Birfy added 3 commits October 18, 2026 17:56
…ccount menu

The footer button now opens Connectors, so the shell's plugin panel needs
another way in that does not require classic Agent mode.
@Birfy Birfy changed the title Connect many apps through one Composio key in Connectors Connect many apps through one Composio key; footer button becomes Connect apps Oct 18, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 18, 2026
@Birfy

Birfy commented Oct 18, 2026

Copy link
Copy Markdown
Collaborator Author

Maintenance review at tip 321f6e4 (read-only; another agent owns this branch). Footer requirement is met: the bottom-left button is now Connect apps and calls actions.openConnectors(), and the plugin panel stays reachable from a new Plugins row in the account menu (selectPanel('plugins')); #322 is closed as folded in here. Design looks consistent: the Composio key is a host secret (scope [], header-only to Composio's host, never in state.json), connector-apps is shared-blocked, English UI + zh. CI green, mergeable. Open before merge: (1) Codex P2 at core/src/host/connectors.js:131 is unresolved — every start/retry/app-list change and the pre-save check create a new Composio tool-router session that is never stored or deleted, so sessions pile up; reuse the stored session_id or delete the old one on unmount/replace. (2) Risk tier T3 (authz.js, channel.js, external URL), so it needs Danyang's one-click approval at head before the merge queue.

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Oct 18, 2026
Birfy added 2 commits October 18, 2026 18:14
…delete it when superseded

The key check and every mount each made a session that was never kept or deleted. The session id and address are now kept in state (never the key), reused while Composio still has them, and deleted (best effort) on Disconnect, Choose apps and a new key.
@Birfy
Birfy added this pull request to the merge queue Oct 18, 2026
Merged via the queue into main with commit be97ef3 Oct 18, 2026
8 checks passed
@Birfy
Birfy deleted the feat/311-connect-apps-gateway branch October 18, 2026 10:45

@Birfy Birfy left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Post-merge review of head b346db6 (merged as be97ef3 at 18:45 Shanghai time; I read the diff and the merged core/src/host/connectors.js). Posted from the author's account, so this is not an independent PASS.

Verdict: no blocking findings. The Codex P2 (orphan Composio sessions) is fixed in this head: one session per connect or Choose-apps change, kept in state.connectors.composio.session, reused only when it is https on the API host, for the same apps, and still alive; superseded sessions are deleted (old key first); a session made while the connection was disconnected or re-keyed meanwhile is deleted again. Footer button reads Connect apps → openConnectors(), and the account menu has Plugins → selectPanel('plugins'); zh strings exist for all new copy.

Checked: key kept with scope [] (no Bot shell), sent only as the x-api-key header to the API host and to an MCP address of the same origin (https, redirect: 'error'); errors go through redactSecrets; connector-* endpoints are shared-blocked in authz.js; composio reserved so a capability cannot shadow it; composio: false hides the row; the key is not in state.json.

Follow-ups (not blockers, for Danyang to decide):

  1. state.json now holds the Composio session id and MCP URL (not the key). The PR text says the key is never stored, which is true, but the URL is a session address; keep it out of any Bot-readable file if Composio ever treats it as a bearer.
  2. Every Bot gets mcp__composio__* (and GitHub's tools the same way), including a Bot shared with a friend under a per-Bot grant: the friend then reaches the owner's connected apps through that Bot, limited only by DSH's normal tool approval. The PR lists per-Bot app grants as out of scope; it deserves its own issue because it touches the default-deny grant rule.
  3. If Composio is down at start, composioSessionAlive throws and the connector shows an error (Try again) rather than reusing the stored session; acceptable, just noting it.
  4. Not exercised by tests: clicking the footer button / Plugins row and a real Composio account (source-level tests only, as the PR said).

Birfy added a commit to Birfy/FlyBot that referenced this pull request Oct 18, 2026
…r get them (ThinkFlowLab#346)

Fixes ThinkFlowLab#335

## What changed
Connector tools (`mcp__github__*`, `mcp__composio__*`) were mounted for
every Bot and Session, including a Bot shared with a friend. Now:

- **Per-Bot switch, default off.** `bot.connectors = { github?,
composio? }`; absent means off. Switches: Settings → Connectors →
**Choose Bots** (a switch per local Bot; the row says "On for N of M
Bots") and a **Connectors** card in each Bot's settings. zh strings
added.
- **Shared Bots never get them**, whatever the switch says.
`sharedWith()` (reach.js) is the one definition: a grant, or a current
room with a friend's Bot, another person, another owner's Bot or a room
code. It is read on every call. A grant also clears the Bot's switches,
and turning one on for a shared Bot is refused naming who it is shared
with.
- **Enforced in the tool path, not the UI**: a `ctx.tools.guard` refuses
every connector call from a Bot Session (own chat, group Session,
earlier part; sub-agents follow their parent), and `restrict({deny})`
removes the tools from that Agent's list (on create, on `resolveAgent`,
on a switch/grant change, on `tools/change` when a connector mounts
later). The DSH Agent and plain DSH Sessions (owner-only) are untouched.
- **Upgrade behaviour: every existing Bot starts off**, including ones
that used the tools (connectors are new since ThinkFlowLab#323). New/duplicated Bots
start off; **Disconnect** turns the connector off for every Bot.
`bot-connectors` is `SHARED_BLOCKED`.
- **Composio session address is no longer in `state.json`.** Only `{ id,
apps }` is kept. Composio's reference lists `mcp.url` as required in the
create and `GET /tool_router/session/{id}` answers (`x-api-key`), so the
mount asks for the address by id + key, accepts only https on the API
host, and keeps it in memory. Composio's docs show the address always
paired with the key header and call the exported url+headers config a
secret, but never say the address alone cannot authenticate, so it is
not stored. An older record's `url` is rewritten away at start; the
in-memory address is redacted from connector errors.

Docs: core-host (rule, upgrade, session address), core-client, team,
README en/zh. Changelog fragment:
`changelog.d/335-connector-bots.security.md`.

## Verification at the tip
Tip SHA: 8242518
Ran: `ARCH_DOCS_BASE=origin/main TZ=UTC npm run verify` (Node 24) → exit
0: core 889/889, social 230/230, tree 56/56, timeline 80/80,
`.github/scripts` 86/86, interop 37/37, live-e2e 16/16; arch-docs OK;
`ds-bot-patches check` OK; `build-client --check` OK. New:
`connector-access.test.js` (10: default off, toggled-on local Bot gets
only its connector, shared Bot via grant/room gets none even with the
flag forced on, sub-agents, late-mounting connector, disconnect,
duplicate), `connector-bots.test.js` (6), connector session tests (+4, 6
updated), notices test (+1), authz and install order rows. The new test
files fail on current `main` (the module does not exist there).
Not run: the `dev/bench.sh` smoke test (needs a DSH checkout at
`../deepseek-harness`, not on this box); live Composio/GitHub calls (no
key; the session-address flow is tested against a stand-in for the
documented API); a visual check of the new rows in a browser.
Reviewer notes: T3 expected (`reach.js`, `authz.js`, host tool path).
`core/` and `patches/ds-bot/` are generated together; new `core` files
are stored whole in `patches/ds-bot/files/`.
HugoXK added a commit to HugoXK/FlyBot that referenced this pull request Oct 26, 2026
…ess (ThinkFlowLab#1081)

The case read `state.json` once right after the mount. Measured on `main`, the mount
and the save that drops ThinkFlowLab#323's legacy `session.url` are two start-up paths and the
mount comes first: under a 400 ms delay on every `state.json` write, the read was
dirty 20/20 times and the file settled 401-422 ms later, with no store refusal
(`not saving the Bot team`, `has closed`, `open in another dsh`: 0/20). The save is
not dropped; the read is early. On the same host without the delay the case is
clean 20/20, which is why the CI runner was the one that lost the race.

The case now waits for the observable state — the file no longer carrying the
address — with a 3 s deadline, no fixed sleep. The three assertions are unchanged.
On timeout the message carries what the file still says and the store's own
read-only lines, so a genuinely dropped save stays distinguishable from an early
read (the product-side question the issue raised).

Every `state.json` write in the case is delayed 400 ms on purpose, so the window is
deterministic instead of luck: with the wait removed the case reds 20/20, with it
20/20 green.

Docs-not-needed: a test-only change; no product or architecture behaviour moves.
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.

Let Bots connect to external apps (Gmail, Slack, GitHub, Calendar, ...) through a connector layer such as Composio / MCP

1 participant