Skip to content

Connector tools on by default for your own Bots; sharing a Bot doesn't take them away; others need your opt-in - #370

Merged
Birfy merged 6 commits into
mainfrom
fix/connectors-default-on-own-bots
Oct 18, 2026
Merged

Birfy merged 6 commits into
mainfrom
fix/connectors-default-on-own-bots

Conversation

@Birfy

@Birfy Birfy commented Oct 18, 2026

Copy link
Copy Markdown
Collaborator

Fixes #369

Supersedes part of #346 (the "off by default, never on a shared Bot" rule); builds on #335. Owner's decision, recorded in docs/subsystems/core-host.md: connected connectors should be on by default for the owner, a shared Bot must still work for its owner, and the default-off applies to other people, who the owner can opt in per Bot.

What changed

  • Two flags per Bot. bot.connectors = { x?: false } = explicit off for the owner's own use (missing or legacy true = on). bot.connectorsShared = { x?: true } = explicit on for others' use (default off; only counts while the owner's own use is on). bot-connectors takes scope: 'owner' | 'shared'; turning owner use off drops the shared offer, and turning shared on while owner is off is refused. Sharing no longer clears or locks anything (setGrants no longer deletes the flags).
  • Whose turn it is is decided at call time (new core/src/host/turn-origin.js, tracked in turns.js), from the messages the turn took in, never from the Bot's own words. The turn's verdict is the strictest over its messages and sticky for the turn.
    • owner: owner-typed, answered question card, or a same-owner Bot relaying an owner request (deliver stamps source.origin, message_bot/stop_bot pass the caller's verdict);
    • other: friend / remote, another owner's Bot, another member, unstamped relays, end-of-turn replies/retries/deferred requests carrying an other turn;
    • scheduled tasks: owner's only while the Bot is not shared;
    • fail closed: no verdict, Bot gone, unknown stamp, unresolvable parent chain, bot.remote -> other / no tools.
  • Rooms: a Bot's turn in a room that has anyone besides the owner and the owner's Bots (friend's Bot, other human, another owner's Bot, code-joined) counts as "others", read at call time.
  • Turns that move or restart: part switches (parts.js) and resume records (resume.js, inflight) carry the verdict; a record without one resumes as other.
  • Tool list vs guard: the guard is authoritative (settled ?? OTHER); the tool-list mask is optimistic for idle Sessions and refreshed on turn start / extra message / turn end, switch changes, sharing changes and tools/change.
  • Migration / disconnect: Connector tools only for the Bots the owner enables; shared Bots never get them #346's switch never stored an explicit off, so existing Bots simply read as on after upgrade; an explicit false stays off. Disconnect clears both flags for that connector on every Bot. The session URL still is not stored in state.json (id only).
  • UI (en / zh): Connectors row "On for all N Bots" / "On for K of N Bots" / "Off for every Bot. Choose which Bots may use it." plus " · Allowed for others on M"; Choose Bots and each Bot's Connectors card show two switches per Bot: the Bot's own and "<name>: also when others use it" (zh "别人使用时也开启"), never disabled by sharing, greyed only while the owner's own switch is off ("Turn it on for yourself first."). Hints name who it is shared with ("Shared with Bob. They cannot use it unless you turn this on."). Footer explains default on for you / off for others, including rooms with other people. All zh strings added; old strings removed.
  • Docs: core-host.md (decision + rule), core-client.md, team.md, README en/zh; changelog fragment 369-connectors-default-on.changed.md; the unreleased Connector tools (Composio, GitHub) reach every Bot, including Bots shared under a per-Bot grant; make them per-Bot opt-in #335 fragment trimmed so it no longer contradicts.
  • core/ and patches/ds-bot/ regenerated together (resume.js, work-dir.test.js added to topic 0001).

Tests

  • Rewrote connector-access.test.js (18) and connector-bots.test.js (5); new turn-origin.test.js (3); new part-switch test in work-dir.test.js.
  • Checked that they fail against origin/main's core/src (19 failures there, pass on this branch).
  • ARCH_DOCS_BASE=origin/main TZ=UTC npm run verify (Node 24): all green, arch-docs OK, ds-bot-patches check OK.

Known limits

  • An owner typing in a room that has other people needs the "also when others use it" switch (a room is judged as a whole).
  • Owner turns still read earlier history that contains friend text; a Bot's text reaching another Bot through a group transcript is not tracked per author.
  • A turn whose trigger message is not seen, or a resumed turn with no stored verdict, counts as others.
  • A scheduled task on a shared Bot counts as others.
  • Shared use is one switch for everyone who is not the owner (no per-friend choice); no per-app choice per Bot.
  • Reconnecting after Disconnect resets to defaults.

Review notes

T3 (touches authz/reach). Not merged, not deployed, nothing run on the Mac. Needs maintainer approval; please review turn-origin.js and the guard in connector-access.js first.

@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-18T14:49:22.828376Z 3db8c05 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: 3db8c05ccc

ℹ️ 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/delivery.js
// (no running turn found, or a sender that is not a local Bot) passes on nothing.
const originOf = ({ from, to, fromId, remote, given }) => {
if (remote?.handle) return OTHER
if (fromId === undefined) return given === OTHER ? OTHER : OWNER

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve the requester origin across group relays

When a friend-driven Main/admin Bot calls post_to_group for a room containing only the owner's Bots, tools.js and groups.js do not carry that turn's origin, and room-participant.js later calls deliver with no fromId; this branch therefore stamps each member request as OWNER. Because settled sees no other occupants in that room, those member turns can use owner-only connectors even when shared use is disabled. Propagate the originating turn verdict through the post, round, and member deliveries instead of defaulting this path to the owner.

Useful? React with 👍 / 👎.

Comment on lines +252 to +255
const now = sharingOf(bot.id)
const before = sharedBefore.get(bot.id)
if (before !== undefined && before !== now) changed.push(bot.id)
sharedBefore.set(bot.id, now)

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 Track room membership changes independently of labels

The refresh key is only the display-label list returned by sharedWith, so distinct sharing states can compare equal. For example, if Writer already has a grant named Bob and a Bob participant leaves Writer's room during a turn that began while Bob was present, sharingOf remains "Bob"; no refresh runs, and the connector tools stay masked for the rest of what is now an owner-only turn even though the call guard allows them. Track the room-sharing predicate or membership identity separately rather than using the deduplicated UI labels as the change token.

Useful? React with 👍 / 👎.

@Birfy

Birfy commented Oct 18, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #370 (comment only, not an approval). Reviewed at 3fecb86; fixes pushed as f2e72f7, so any earlier PASS no longer applies to the new head.

Fixed in f2e72f7

  1. High: a friend could reach the owner's connectors through a group post (this is also Codex's P1 on delivery.js). A friend drives a shared Main Bot, which calls post_to_group into a room of the owner's own Bots. The host then delivered each member turn with no sender, which deliver stamps owner, and settled() saw no other people in the room, so the member ran with the owner's connectors. The report_back message to the poster was stamped owner the same way, and it carries the members' lines. Fix:
    • post_to_group stamps its calling turn's verdict on the post, and a late member reply stamps its own turn's.
    • The round takes the strictest verdict of the Bot posts that opened it. A post from a remote or unknown Bot, or one nobody stamped, counts as other. It passes that verdict to every member turn (runTurn → deliver).
    • The report is owner only if the round was and nobody else is in the room.
    • New test: a Bot post carries whose request it is into the room's member turns and into the report. It fails on 3fecb86's core/src.
  2. Low: the room-membership refresh compared labels (Codex's P2). sharingOf compared the deduplicated labels, so a "Bob" grant plus "Bob" in the room hid the label change. A turn that became owner-only kept its tools hidden, and the reverse is possible. The guard was always right and the tool list was only advisory, so nothing leaked. It now compares who is in the rooms. The existing room test is extended with this case, and it fails on the old code.

Docs (core-host.md) and the changelog fragment are updated for both. core/ and patches/ds-bot/ are regenerated, and ARCH_DOCS_BASE=origin/main TZ=UTC npm run verify passes locally.

Checked, no bug found

  • Spoofed stamps. source.origin is written only by deliver and post_to_group. Remote and remote-chat sources are other before the stamp is read. The plugin API (remote.chats.deliver) has no way to set origin. A gated send re-delivers its deliverArgs, so the stamp survives an approval.
  • Sticky verdict and merging. The verdict is the strictest across the turn and sticks. A friend line that arrives mid-turn, or queued before the turn starts, makes the turn other. An owner approval or question answer cannot clear it.
  • Fail-closed paths. No verdict, a Bot that is gone, an unresolved parent chain and bot.remote all refuse. Sub-agents follow their parent Session. Resume after a restart uses the stored verdict, and a record without one resumes as other.
  • Members of another home. Members can only reach sessions they own (sessionOwnedBy), and their room lines carry authorId, so a typed line without an author is the owner's.
  • Migration. A missing entry reads as on, a legacy true as on, and an explicit false stays off. Connector tools only for the Bots the owner enables; shared Bots never get them #346 never stored an explicit off, so Bots the user had switched off there cannot be recovered. Connector tools only for the Bots the owner enables; shared Bots never get them #346 is unreleased, so this is acceptable. Disconnect clears both fields on every Bot, and both fields round-trip through state.json.
  • Composio. connectors.js is untouched. The session address is still not stored, and the guard messages carry names only.
  • UI.
    • The own switch is never disabled.
    • The shared switch is disabled only while the own switch is off.
    • Counts use local own Bots only.
    • zh strings exist for every new string.

Remaining limits (documented, not fixed here)

  • Tool results can carry friend text into an owner turn (read_group_chat, read_chat, history). The turn is judged by who started it. This is prompt-injection exposure, the same class as "owner turns read earlier friend text", and it is now stated in core-host.md.
  • Scheduled tasks are one verdict. DSH does not say who created a task, so a task created by a friend-driven turn on a Bot that is not itself shared runs later as the owner's. The owner switch is the mitigation. Worth a follow-up issue.
  • A Session with no Bot reads as plain and keeps the tools. This predates this PR, and the relevant Sessions are not reachable by friends.
  • The schedule verdict inside a room is stamped as the owner's, but a Bot post with no stamp is other.

No conflicts with other open PRs. #367 and #368 are already merged into this branch, and the only other open PR is the release PR #152. This is still T3: not merged, not deployed. Needs a maintainer decision.

@Birfy
Birfy enabled auto-merge October 18, 2026 14:59
@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

Update: head is now e34e7a1 (f2e72f7's fixes, a test-timing fix, and main merged through #372). Earlier CI failure was my new group-round test waiting only a few event-loop turns on a slower runner; it now waits in real time. The #297 presence test failed once on the social job and passed on rerun. All checks are green. Any earlier PASS no longer applies to this head.

Merged via the queue into main with commit 082563a Oct 18, 2026
12 of 14 checks passed
@Birfy
Birfy deleted the fix/connectors-default-on-own-bots branch October 18, 2026 15:19
Birfy added a commit to Birfy/FlyBot that referenced this pull request Oct 20, 2026
…owLab#547)

Fixes ThinkFlowLab#545

Owner decision 2026-10-20 20:21 (Asia/Shanghai), per ThinkFlowLab#359 / AGENTS.md
hard rule 4: the owner's connectors and secrets are on by default for
all of the owner's own Bots.

**Root cause.** `connector-connect` (`core/src/host/connectors.js`)
saved `GITHUB_TOKEN` / `COMPOSIO_API_KEY` with `scope: []` on a first
connect, so the Secrets page and `list_secrets` showed "No Bot" and no
Bot's shell got the token. An empty scope list could not be told apart
from an owner's choice.

**Change.**
- `connectors.js`: no `scope: []`; a connector key gets the normal
default (all Bots), a reconnect keeps the existing scope.
- `secrets.js`: a scope the owner chose is stored with `scopeSet: true`
(Secrets page `secret-update` / `secret-set` with a scope, a card's
"Only <Bot>", a deleted Bot leaving a picked list). On load, an empty
list **without** `scopeSet` (the old connector default) becomes `'all'`
once and is written back by the holding host. Pre-ThinkFlowLab#545 empty lists the
owner picked by hand carry no marker and are widened too (not
distinguishable).
- Secrets page (`secret-card.js`): explicit three-way control **All my
Bots / Chosen Bots / No Bot**; the row tag says "All my Bots". zh
strings added.
- Owner's use only (self-review, second commit): a shell gets keys only
on the owner's own turn (`settled` in `turn-origin.js`), and outside a
known turn only while the Bot is shared with nobody; a friend's Bot
never. A capability (`canUseSecret`) on a Bot shared with anyone gets
only a key whose list names that Bot, never an `'all'` key — otherwise a
friend's turn could have a shared Bot save an HTTP capability that sends
`$DSH_SECRET_GITHUB_TOKEN` elsewhere.
- Connector *tools* were already on by default for the owner's own turns
(ThinkFlowLab#369/ThinkFlowLab#370), so `connector-access.js` is unchanged.
- Docs: core-host "Secret scope defaults (ThinkFlowLab#545)", core-client "Secrets
page scope control (ThinkFlowLab#545)". Fragment
`changelog.d/545-secrets-all-my-bots.fixed.md`.

**Risk tier:** touches `core/src/host/secrets.js` (secret exposure) →
expect T2/T3; see the controller's label.

## Verification (tip `2f484fe`)
- Red at base `b6e89d2` (tests only): `connectors.test.js` 2 fail (scope
`[]` ≠ `'all'`), `secrets.test.js` 2 new fail (migration, explicit No
Bot), `tool-cells.test.js` fails (no three-way `ScopeEditor`). Red at
`c0d189f` (first commit): the shared-Bot shell test and the shared-Bot
capability test.
- `DSH_HARNESS=core/dev/harness ARCH_DOCS_BASE=origin/main TZ=UTC npm
run verify`: exit 0 — core 1117/1117, social 312/312, tree 83/83,
timeline 93/93, .github scripts 93/93, interop 37/37, live-e2e 16/16;
lint + arch-docs + `build-client --check` + `ds-bot-patches check` ok.
- Smoke: `dev/bench.sh smoke545 3196 8896 reset && node dev/smoke.mjs
smoke545 3196` against deepseek-harness `dsh-v0.2.0-rc.2`: all 42 steps
passed. (Bench trap: pnpm 11 did not link `ds-bot` into the bench
profile; linked `profiles/web/node_modules/ds-bot` by hand.)
- CI at `2f484fe`: `tests-ok` green.

**Not run:** Desktop app; real-model run; the owner's running instance.

---------

Co-authored-by: Birfy <15378933+Birfy@users.noreply.github.com>
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.

Connector tools: on by default for the owner's own use of every Bot, also on shared Bots; other people's use is a separate opt-in

1 participant