Skip to content

fix(social): Face-to-face past rooms, + menu, names, and faster poll (#129–#133) - #9

Closed
Birfy wants to merge 170 commits into
mainfrom
cursor/face-ui-round2-dae8
Closed

Birfy wants to merge 170 commits into
mainfrom
cursor/face-ui-round2-dae8

Conversation

@Birfy

@Birfy Birfy commented Oct 16, 2026

Copy link
Copy Markdown
Owner

Fixes ThinkFlowLab#129, ThinkFlowLab#130, ThinkFlowLab#131, ThinkFlowLab#132, ThinkFlowLab#133

What changed

Expired matching and a peer leaving the last remote seat move Face-to-face rooms into Past rooms (Open only, no stale Leave). Chat and sidebar previews show the inner words of a remote line, not <untrusted-message>, and use the person's real name instead of User. Missing API key / model failures show a short friendly line; the raw error stays in diagnostics. The sidebar + menu is Face-to-face group, Add friend, Accept invite, and one-click Copy invite link (Friend requests is gone; individual Bots stay behind New chat). Hold labels use the real Bot name (Chief → the room); room titles include people or the code/time; Auto-send is the one hold label; Friends' Bots shows either the Bot count or N to approve. Live face rooms poll about every 4s (about 3s in the open room), then 15s when idle.

Verification at the tip

Tip SHA: af5af0d
Ran: ARCH_DOCS_BASE=upstream/main npm run verify (lint, patch check, core tests, social tests, tree tests, changelog check)
Not run (and why): smoke test (dev/bench.sh) — no harness checkout at ../deepseek-harness; unit tests cover host poll, past-room move, + slots, unwrap, and titles.

Open in Web Open in Cursor 

Danyang Chen and others added 30 commits October 14, 2026 15:27
The fixed delay raced the summary's state write under parallel test load (Node 22 on CI).
Fields per plugin, a bot.send middleware chain (allow/deny/hold, approvals),
transports, remote targets and remote chat members, tool registration with
visibleTo, UI endpoints and frontend slots, TypeScript types, and tests.
Bots attach files (message_friend_bot files) and the owner attaches them in the remote
member composer; held sends list names and sizes, and bytes are uploaded (hash re-checked)
only after approval. Incoming files render as chips in the remote bubble; get_friend_file
saves a hash-verified copy and hands text content over as untrusted. bot.send carries
attachment metadata; types updated.
… names; mount them as DSH bundles

The DSH client module loader looks bundles up by package name, so @flybot/social and
@flybot/tree (registered as flybot-social / flybot-tree) failed to load in the browser.
Both packages now also declare dsh.bundle so 'dsh plugin add' mounts them, and tree's
peer dependency is ds-bot (there is no @flybot/core).
… service

flybot.sidebar.section is a FlyBot registry slot, not a DSH shell slot, so ctx.slots.register
failed and the web boot reported '@flybot/tree: failed'. Adds a test that boots the social
and tree web entries under real Cordis 4.
…ld change); idea from recursive-bot 7a3071e
When the plugin manager reloads ds-bot in a running DSH, the new instance can apply
before the old one is disposed. DSH's connection refuses a second owner of an exact
Fetch path ('already registered'), which failed the new apply; once the old instance
went away nothing served /api/bot, so every browser call (Friends' Bots 'Create invite
link' included) got HTTP 404 until a restart. The route now retries a taken path until
the previous owner lets go.
ds-bot sidebar tokens (--bt-*) like the tree section; collapsible header with a Bot count
and '+ Add'; friends grouped by home with avatar, name, handle and a remote tag; empty
states; invite link in a read-only field with a Copy button; inline paste+Join row;
readable request and pending lists; no horizontal overflow. Errors are worded in English
from their code (the host's localized 'HTTP 404' text was Chinese in a zh UI).
…7a3071e, 2aab086)

Integrates recursive-bot's sidebar redesign (collapsible header with count, empty state,
indented tree with guide lines, one-line role, click opens the Bot's chat) and the
ensureRoot fix (root created at startup with a concurrency guard; autoRoot: false turns
it off) on top of 090c5c5 (inject ['flybot'], section via ctx.flybot.slots) and 25e1c93
(10 s refresh).
…om recursive-bot 4699878)

ds-bot's Main Bot (isMain / state.mainBotIds, preferring 'Chief'; config.rootBotId
overrides) is adopted as the root with treeRole 'root'. A Root auto-created by earlier
versions hands its children to Chief and loses its tree fields and tree prompt block; the
bot itself is not deleted. The tree text lives in a marked block of systemPrompt so
Chief's own text is never overwritten. Keeps inject ['flybot'] and the 10 s refresh.
…n the clipboard

'Join my FlyBot home <home>. In FlyBot: open Friends' Bots, click + Add, paste this link
and click Join. Using a Grok bot? ... Link: <url> (single use, expires <date>)'. The field
still shows the URL; 'Copy link only' copies the bare URL.
…ure)

A plugin can give the Bots a structure through the FlyBot registry slot
'flybot.sidebar.structure' ({ parentOf(botId) }). The Bot list then renders as one tree:
the Main Bot on top as the root (a row, not the tile), children indented with expand/
collapse, each with ds-bot's usual avatar, preview, unread mark and click-to-open. Bots
with no known parent (or a cycle) go under the Main Bot. Rooms and Agents follow as before.
There is no whole-home invite any more. social/create-invite (alias social.createInvite)
needs { botId }, registers that Bot's member identity and invites to it; it returns
{ botId, botName, handle, home, link, url, code, expiresAt, status, text } where text is
the copy message naming the Bot. The panel picks the Bot (default Chief); each Bot's
settings has 'Invite to this Bot'. ctx.flybot.social.canInvite(botId) is synchronous
(social/status cached) so the tree can show per-node invite buttons.
…ire the old Root (from recursive-bot 52d349e)

Integrates recursive-bot's 52d349e: the separate Tree section is off by default
(sidebarSection: true), local Bots outside the tree attach under Chief at startup and on
bot.created, the old auto-created Root is retired (retired: true, not deleted), and a
synchronous ctx.flybot.tree.forest(bots) helper. On top: the client registers
flybot.sidebar.structure from forestOf, so ds-bot renders its own Bot list as one tree.
…eActions (from recursive-bot 3ee50ae)

recursive-bot's 3ee50ae: flybot.sidebar.structure entries may carry nodeActions({ bot }),
which ds-bot draws after each tree row; the tree uses it for a hover-only 'Copy invite link'
shown when social.canInvite(botId) is true (stopPropagation, copies createInvite's text).
Review changes: ds-bot wraps nodeActions in a real error boundary (a try/catch around
createElement never sees render errors); social exposes social.onChange(fn) and refreshes
its status every 30 s, so the tree re-renders on status changes instead of every node
polling every 3 s (the poll stays as a fallback for an older social).
ConnectPlugins drew a warning badge and the tooltip 'Plugin compatibility is not fully
verified.' unconditionally; nothing checked anything. DSH's real compatibility check
(dsh-app-boot evaluatePluginCompatibility) blocks incompatible plugins at install, so the
badge only alarmed. Removes the badge, tooltip, its i18n string and styles. Rebuilt client.js.
…ms); Friends' Bots panel keeps only friends, requests and Join
FlyBot plugin layer (ctx.flybot) with social and tree plugins
…e; clipboard write starts inside the click (ClipboardItem), no bare-link fallback
…ialized creates (Fixes #15)

Patch by recursive-bot (Birfy/recursive-bot flybot-tree 7a64def).
…ny not delivered; legacy conversation kept (Fixes #8)
…ys hold a Bot's files, show full path, async single read, clean staging (Fixes #10)
cursoragent and others added 29 commits October 15, 2026 11:52
ThinkFlowLab#87 registers only published Bots, so joining a face room with an
unpublished Bot failed with "has no Bot Social identity yet". Register
it on join with the same add_home_member path, keep it room-only
(unpublished, not polled, not listed for friends), and revoke that
identity when the Bot leaves its last face room.

Co-authored-by: Danyang Chen <birfychen@gmail.com>
…egister-cf0e

fix(social): register a Bot with Bot Social at face-to-face join if it has no identity (ThinkFlowLab#83)
There is no global published flag and no "publish all my Bots"
concept. Every access is one of my Bots granted to one person
(optionally scoped to a group). Default deny, Chief included.
Accepting Invite to this Bot creates the grant; Manage access
lists who has it, in which groups, and Revoke. Friends' Bots
lists only Bots granted to me. Face-to-face join by 6-digit
number stays a room-only implicit grant. Upgrading means no
Bot is shared until it is granted.

Core reach() is grant-based. @flybot/social isolates grant and
invite server calls in src/mcp-client.js. Hide-a-friend's-Bot
and in-chat approval prompts are unchanged.

Co-authored-by: Danyang Chen <birfychen@gmail.com>
…t-inchat-approval-2176

Per-Bot grants instead of publish, remove a friend's Bot, in-chat approvals (ThinkFlowLab#90)
hideRemote waited for room leave and a fresh roster fetch, so the click
felt stuck. Persist the hide and return the locally filtered list; leave
rooms in parallel and refresh in the background.

Friends' Bots tracked one hiding handle, so a second Remove re-enabled
the first. Track a set per handle. Replace the large button with a
hover/focus × that stays on the keyboard tab order and the context menu.

Co-authored-by: Danyang Chen <birfychen@gmail.com>
…h-2176

Remove this Bot: fast optimistic hide, per-row pending state, compact control
## What

- **AGENTS.md** rewritten toward a short pointer style:
- Pipeline contract: issue -> branch -> PR (`Fixes #N`) -> CI `tests-ok`
-> auto-merge (squash only), with the existing rules kept (review PASS
on the tip; maintainer delegates the merge; stop conditions).
- Conflict recipe for generated files: take main's `patches/ds-bot`,
rebuild `client.js`, `node scripts/ds-bot-patches.mjs generate` +
`check`.
- Node 22+ requirement stated up front (Node 20 fails with "Could not
find test/*.test.js").
  - Removed the nonexistent `app/` references (two places).
- **`npm run verify`** (new repo-root `package.json`, private, no
dependencies, `engines.node >=22`): core lint (oxlint + arch-docs),
plugin lint, `build-client.mjs --check`, `ds-bot-patches.mjs check`,
core suite, `plugins/social` and `plugins/tree` suites. Same order as
CI's `flybot-tests` action plus the patches check from the `package`
job. It does not run pack check, typecheck, actionlint or smoke tests.

Why a root `package.json` instead of `core/package.json`: `core/` is
generated from `ds-bot/` + patches, so editing `core/package.json` means
regenerating patch 0007 (a conflict hotspot). The root file touches no
shipped package, no lockfile, and dependabot only watches `/core`.

## Verified

Node 24.21: `npm run verify` exits 0 (core 674/674, social 68/68, tree
54/54, 0 skipped; lint, `--check`, patches check, arch-docs OK).

Docs-only change plus one helper file; no `core/` or `patches/` change,
so no patch regeneration and no subsystem doc needed (arch-docs OK).

Co-authored-by: Danyang Chen <15378933+Birfy@users.noreply.github.com>
… chat (ThinkFlowLab#92) (ThinkFlowLab#96)

Closes ThinkFlowLab#92.

- Face-to-face join moves from the Friends' Bots panel into **+ Add**
and opens its own screen.
- Multi-select the Bots to bring, enter the 6-digit number (joins
directly, one join per Bot with the same code), live waiting room with
Bot + owner and a countdown, **Enter chat** starts the chat and opens
the group; the screen follows when someone else starts it.
- The old inline "Join with a number" block is removed. English only, no
mention of "published".
- Tests, bundle check, CHANGELOG and docs updated.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…' Bots panel (ThinkFlowLab#92) (ThinkFlowLab#100)

Follow-up to ThinkFlowLab#96 / ThinkFlowLab#93 / ThinkFlowLab#91.

- **Face-to-face group** is now an entry in the global sidebar **+**
(new-chat picker), right after New group chat, via a new
`flybot.new-chat.action` slot (patch layer). It also stays in Friends'
Bots + Add and works while the roster is loading.
- **Removed Bots stay gone:** a hide is forgotten only after two
consecutive successful non-legacy roster fetches without that handle (or
an explicit revoke); hidden handles are kept out of
`flybot.remote.publish`, so persisted remote chats do not return in the
sidebar, the send-to picker or mentions. Home-level rows are filtered
the same way.
- **Performance:** parallel mount, stale-while-revalidate roster
(Refreshing\u2026 instead of blocking Loading\u2026), keep-alive HTTP,
non-blocking grant listing, slower/paused polling.
- Tests, bundle check, patches check, CHANGELOG updated.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…ort-only risk tier (ThinkFlowLab#102)

Phase 0 of the autonomous-dev plan: report only. Nothing here blocks,
labels, comments on or merges anything.

## What changed
- `docs/autonomous-dev.md`: the design adapted to no GitHub App and no
Cursor cloud agent (controller on `GITHUB_TOKEN` in Actions, LLM agents
outside Actions, agents act only on `agent-ready`), the limits of
`GITHUB_TOKEN` and workarounds, and the next phases.
- Agent-task issue form (applies `agent-task`, never `agent-ready`), PR
template, label definitions plus `sync-labels.mjs` (dry run by default;
nothing runs it).
- Risk tier: `risk-rules.json`, `risk-gate.mjs` with tests, and
`risk-tier.yml` (`pull_request`, read-only, `continue-on-error`, not
required, job summary only).
- CODEOWNERS skipped on purpose: it would request both maintainers on
most PRs and enforce nothing.
- `npm run verify` and the CI `package` job run the new script tests.

## Verification at the tip
Tip SHA: see the head of this PR.
Ran: `npm run verify` (exit 0, 17 new tests), actionlint 1.7.7, replay
over the last 30 merged PRs: T0 1, T1 2, T2 2, T3 25.
Not run: the risk-tier workflow on GitHub before this PR (its first run
falls back to the PR's own copy of the script, as the base has none),
label script against the live repo.

---------

Co-authored-by: Danyang Chen <15378933+Birfy@users.noreply.github.com>
…1, PR A) (ThinkFlowLab#103)

Phase 1, PR A of 3 (conflict-proofing, from the measured hotspots in the
speed-up plan). No behavior change to FlyBot itself; three commits, one
per hotspot.

## What changed
1. **Patches no longer conflict on generated headers.** `generate`
writes edits to upstream files without `index <hash>` lines and with
each hunk's new-side start set to its old-side start (git's position
hint; the old side is fixed by the pristine upstream, the true new side
moves with every hunk above). Files upstream does not have go whole into
`patches/ds-bot/files/` instead of one `@@ -0,0 +1,N @@` hunk whose N
changes with every edit; they need no `topics.json` entry. `generate`
applies each file's diff alone and compares it with `core/` before
writing; `check` also compares the executable bit. 29 files moved; 4
patches that only added files are gone.
2. **CHANGELOG: one fragment per change.**
`changelog.d/<name>.<kind>.md`; `.github/scripts/changelog.mjs
check|preview|release <version>` (no network, no shell). CI `package`
job runs `check`. Existing `[Unreleased]` entries stay and are folded in
at release. `changelog.d/**` is T1 in `risk-rules.json`.
3. **Docs hotspot.** `ds-bot-core.md` split into `core-host`,
`core-client` and a small pin/patch page; generated
`patches/ds-bot/*.patch|files|topics.json` are no longer owned (so a
core change does not force a doc edit for them). Invariants sit under
headings so PRs append in different places.

## Evidence (replays in /workspace on the box; scripts:
conflict-replay.sh, docs-hotspot-replay.mjs)
- Two branches edit the same three core files (hooks.js new file,
index.js, store.js), X near the top, Y near the bottom, each regenerates
patches, then merge:
- before (main): CONFLICT in
`patches/ds-bot/0002-flybot-api-host.patch`, 4 regions: three `index
<hash>` lines, the new-file `@@ -0,0 +1,802 @@` vs `+1,803`, and a
shifted hunk header `+539,7` vs `+540,6`.
- after: merge CLEAN; `ds-bot-patches.mjs check` passes on the merged
tree **without regenerating**; both edits present.
- Last 30 merges on main, pages each would have been forced to edit:
`ds-bot-core.md` 16 → 3; new `core-host` 10, `core-client` 7.

## Verification at the tip
Tip SHA: 3896928 (CI green: test,
plugins x2, package, tests-ok)
Ran: `npm run verify` (Node 24), actionlint, `check-pack.mjs`, new tests
(`ds-bot-patches.test.mjs`, `changelog.test.mjs`)
Not run (and why): smoke test (no host code changed); typecheck step
(types untouched)

## Not done / follow-ups
- `core/client.js` and `0008-client-bundle.patch` still conflict
whenever two PRs change the client (generated bundle; build it instead
of patching it). Out of scope.
- Still conflicts, on purpose: two edits within ~6 lines of each other,
or the same lines.
- Hunk-start hint: if a repeated context ever made it misapply,
`generate` falls back to exact numbers for that file (never hit on
current patches).
- After this merges, open PRs need one rebase: take main's
`patches/ds-bot`, run `generate` (AGENTS.md recipe).
… (Phase 1, PR B) (ThinkFlowLab#104)

Phase 1, PR B of 3 (merge plumbing). **Enables nothing**: without a
merge queue the new trigger never fires. No repo setting, ruleset or
branch protection is touched.

## What changed
- `test.yml`: `merge_group:` trigger (no filters). `test`, `plugins`,
`package` and `tests-ok` need no other change: nothing keys on the
`pull_request` event, `tests-ok` keeps `if: always()` and needs all
three.
- `.github/scripts/test-workflow.test.mjs`: checks that shape (it fails
on main's `test.yml`, passes here).
- `docs/autonomous-dev.md`: what was verified and what was not about
merge queue availability and `GITHUB_TOKEN` auto-merge.

## Verified / not
- Verified (read-only `gh api`): repo is public and org-owned; docs say
queues are available for that. Live protection already requires `test`
and `tests-ok`; squash only; auto-merge allowed; no repo rulesets.
- Verified locally: the `arch-docs` fetch step works on a one-commit
shallow checkout of a merge-queue-shaped commit (it deepens until
`merge-base` resolves).
- Not verified: org plan (`plan` is null without `admin:org`), a real
`merge_group` run (needs a queue), and whether an auto-merge by
`GITHUB_TOKEN` starts the `main` push workflows. Documented rule and
public reports say it does not, so the controller never enables
auto-merge and agents use their own credential. Fallback: `gh workflow
run Test --ref main`.

## Verification at the tip
Tip SHA: a705fac (CI green: test,
plugins x2, package, tests-ok)
Ran: `npm run verify`, actionlint, new workflow-shape test (red without
the trigger)
Not run (and why): a real merge queue (would change repo settings)
…kFlowLab#105)

Phase 1, PR C of 3: the label-only controller. **Dry run by default;
this PR sets no repository variable and creates no label.**

## What changed
- `.github/workflows/controller.yml` (schedule every 30 min +
`workflow_dispatch`, `GITHUB_TOKEN` only, no secrets, `permissions: {}`
at top). Job `plan`: read-only token, writes the proposed label changes
to the job summary. Job `apply`: the only one with write (`issues`,
`pull-requests`), runs only when the plan has operations **and** repo
variable `CONTROLLER_LIVE` is exactly `true`. `AGENT_PAUSE=true` stops
everything.
- `.github/scripts/controller.mjs` + `controller.test.mjs` (19 tests) +
`.github/agent/controller.json` (maintainers, TTL, caps).
- It only manages labels: `risk:T0..T3` (risk-gate tier) and `agent-pr`
on PRs; removes `agent-ready` (and `agent-working`) when a
non-maintainer labeled the issue or edited it after approval; removes
`agent-working` from claims past the TTL (6 h) with no recent push on a
linked PR. Missing labels are skipped, never created. `hold` = hands
off.
- It never merges, approves, comments on, edits or closes anything,
never checks out or runs PR/issue content; `validatePlan` refuses any op
outside the label contract (it can never add `agent-ready`); tests scan
the source and the workflow for this.
- `labels.json`: + `risk:T0..T3` (they do not exist live until a
maintainer runs `sync-labels --apply`). Docs: `docs/controller.md`.

## Evidence
- Read-only dry run against the live repo (GET and a GraphQL query
only): 5 open PRs, all T3, `risk:T3 label-missing` skipped, 0 changes,
nothing written.
- GraphQL shapes (edits by editor, rename and label events) checked
against real issues.

## Verification at the tip
Tip SHA: 13eab55 (CI green: test,
plugins x2, package, tests-ok)
Ran: `npm run verify`, actionlint + shellcheck, controller tests, live
read-only `plan`
Not run (and why): live `apply` (would write labels on the real repo);
whether `issues: write` alone suffices to label a PR is untested
(documented in docs/controller.md)

## To go live (maintainer)
`node .github/scripts/sync-labels.mjs --repo ThinkFlowLab/FlyBot
--apply`, read a few dry-run summaries, then set variable
`CONTROLLER_LIVE=true`. Pause: `AGENT_PAUSE=true`.
…Lab#107)

Follow-up to ThinkFlowLab#100 (owner feedback).

- Each Bot shows its own distinct avatar in the Face-to-face Bot picker
and in the waiting room.
- The waiting room is a wrapping tile grid (avatar on top, name below),
no owner subtitle or heavy badge.
- Leave and Enter chat share one footer row.
- Tests updated; local verify passed.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…opups) (ThinkFlowLab#108)

The web UI chrome still said DS Bot. This changes user-visible branding
to FlyBot (en + zh) via the patch layer (0014-flybot-branding), uses
docs/images/logo.svg for the header mark, and labels updates as FlyBot
core {version}. ds-bot/ is untouched; package names, the update check
and upstream links are unchanged. Local verify passed (arch-docs, bundle
check, patches check, core/social/tree tests).

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…owLab#109)

Fixes two bugs seen in a Face-to-face room.

- The Auto-send my Bot's room messages setting called REST /v1/me with
the connector token, which Bot Social rejects
(connector_token_rest_forbidden). It now reads and changes the setting
through the allowed /mcp path; the owner's click is the consent.
- Friend Bots now show their own avatar (member list, message author,
waiting grid), with a stable generated fallback from the handle.

Tests added; see commit for details.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…human (ThinkFlowLab#52) (ThinkFlowLab#94)

## Why

A held send was decidable by anyone who could reach the decide endpoint.
The record carried no owner, no binding to the request content, and no
origin, so on a host with member sessions one member could decide
another's queued send, and a claim inside the message ("my owner
approved this") was indistinguishable from authority. \ThinkFlowLab#52's acceptance
calls for exactly this binding; it is also the held-send half of
team-gate 3 (docs/subsystems/team.md).

## What

- **Provenance stamped at hold (server-derived, `src/host/hooks.js`)**:
`requestId` (the send's exchangeId), sha256 `version` over the request
content (from/to/text/role/attachment names), `origin` (`via`, `hop`,
room audience when known), and `ownerId` — the sender Bot's owner,
resolved from team state. Claims in `meta` or the text are data on the
record, never authority.
- **Owner-bound decisions**: `flybot/decide` now passes authz for any
signed-in member (carved out of `bot/blocked-shared` in
`src/host/authz.js`), and hooks.js binds the decision to the sender
Bot's owner's session — `bot/forbidden` for anyone else,
`bot/unauthorized` without a session, a body `actorId` grants nothing
(`src/host/channel.js` threads the actor into `flybotEndpoint`).
- **Revalidation at approve/execute**: `release()` re-checks the record
before dispatch; a changed request, a deleted sender or target Bot, a
Bot that changed owners, or a demoted Chief marks the record `stale` —
never dispatched. `deleteBot` and Chief replacement (`setMain`)
invalidate affected pending holds eagerly. Deciding a non-pending record
answers `bot/stale` (same code as `answerApproval`).
- **Per-actor visibility**: under enforcement the pending list shows a
member only their own holds; stale records whose sender Bot and owner
are both gone keep their attribution in state but appear in no list
(audit surface is ThinkFlowLab#57).
- **Approval card context** (`src/client/flybot-slots.js`): the card
shows the sending Bot and a "delegated" marker when the request was
relayed (hop > 0); EN + ZH strings.
- **Legacy holds** (pre-version-stamp) keep working: they skip only the
tamper check; their owner is derived at decide time.

Not in this PR: `social/decide-approval` stays a deployment-wide plugin
write (blocked-shared while more than one member exists) — that follows
the ThinkFlowLab#90/ThinkFlowLab#91 per-Bot-grant server work, and in-chat approval cards are
ThinkFlowLab#90's scope. Team mode stays disabled.

Fixes ThinkFlowLab#52

## Verification

Tips reviewed: `fe8eef2` (initial, Verifier FAIL — see review round 1) →
**`f87ed85` (current)**.

Ran at `f87ed85` (worktree `/tmp/flybot-52`, harness via symlinked
`dev/harness/node_modules`):

- `npm test` — **684/684 pass**, including new
`core/test/approval-binding.test.js` (10 tests) and the updated
`authz.test.js` flybot-endpoint rules.
- Fail-without-change: `approval-binding.test.js` written first; at base
`811df19` **9 of 10 fail** for the right reasons (missing provenance
stamp, `blocked-shared` instead of owner-bound decide, list leak, no
stale invalidation). The 10th ("deciding needs a member session") passes
at base via the existing anonymous-call deny and is kept as a regression
guard.
- `node scripts/build-client.mjs --check` — pass (client.js rebuilt and
committed).
- `npm run lint` — oxlint 0 errors, 27 warnings (identical to base,
compared by stashing the diff); `arch-docs` **OK at this tip** (21
changed files, `plugin-runtime.md` updated in f87ed85).
- `node scripts/ds-bot-patches.mjs generate && check` — pass at this tip
(`core/ == ds-bot/ + patches`; the fe8eef2 commit shipped a stale 0002
patch — regenerated in f87ed85).
- Smoke (`src/host/` changed): `dev/bench.sh smoke 3096 8796 reset` +
`node dev/smoke.mjs smoke 3096` at `fe8eef2` — **all 40 steps passed**;
the f87ed85 delta touches the same approval paths (invalidation scope,
staleReason wording, test hygiene) and is unit-covered; smoke not re-run
at `f87ed85`.
- CI at `f87ed85`: `package` pass; `ubuntu-latest` / plugin jobs pending
at time of writing.

Not run: DSH Desktop check, real-model steward eval, Windows locally,
smoke at `f87ed85`.

## Reviewer notes

- The worktree needs the harness symlink or `DSH_HARNESS` for tests
(fresh worktrees have no `dev/harness/node_modules`).
- To re-verify red-at-base mechanically: worktree at `811df19`, copy
only `core/test/approval-binding.test.js` in, run it — expect the 9
failures listed above.
- The authz.test.js change replaces ThinkFlowLab#91's "decide is blocked-shared with
two members" assertion: deciding is now per-owner enforced in hooks
(that test now also asserts a *different* plugin write,
`flybot/social/decide-approval`, still gets `bot/blocked-shared`).
- macOS bench gotchas: banner flush may hang `bench.sh reset` even
though the web server is up — check `web.log` for the token and `lsof
-iTCP:3096`; `stop` may leave listeners, kill the process groups.

Co-authored-by: hsliu_ustc <hsliu_ustc@noreply.gitcode.com>
…lowLab#53) (ThinkFlowLab#101)

## Why

On a host with member sessions, private context leaked across owners
through four seams: a member Bot's group Session began with its owner's
own-chat brief whoever else sat in the room; the memory review read
every attributed human as a peer and could publish **team** memory out
of another owner's room (any room admin could, and still can on
single-owner rooms); `list_bots` disclosed other owners' `workDir` and
instructions; and a `dsh-session:` reference chip copied any Session's
snapshot to every recipient of the message. ThinkFlowLab#53's acceptance: markers in
each owner's private context are unreachable from another owner's
requests and shared-room context without an explicit authorized share.

**Stacked on ThinkFlowLab#94** (`fix-52-approval-binding`, f87ed85) — this branch
bases on it; retarget to `main` after ThinkFlowLab#94 merges. Host mechanism only:
the share has no room-UI toggle yet (documented), and previously
received messages are not retroactively unseen (the issue says to
document exactly that).

## What

- **Brief gate** (`src/host/groups.js`): the own-chat brief enters a
group Session only for a single-owner room, or where the Bot's owner
shared it — `share-brief` endpoint → `rt.setBriefShare`
(`src/host/room-membership.js`), a `briefShared` flag **on the room
participant** (leaving drops it; owner-checked, not admin-checked).
Enforcement off = shipped single-human behavior.
- **Memory review** (`src/host/memory.js`): `isUserLine` reads the
member Bot's owner as "the user" (retention + read-back anchor) and
another owner's human as a named peer; `teamAllowedFrom` refuses
team-scope writes out of a room with more than one owner — entries land
in the Bot's own memory.
- **Roster** (`src/host/tools.js`): `list_bots` shows another owner's
Bot by name, role, model; its folder and instructions stay private.
- **References** (`src/host/channel.js`): `dsh-session:` chips in `send`
and `room-post` must resolve through `sessionOwnedBy` for the author
(`bot/forbidden` otherwise); authz allows the new `share-brief` for any
signed-in actor (the owner check is on the record).

Fixes ThinkFlowLab#53

## Verification (updated: rebase + review round 1)

Tips: `58fdae3` (initial, Verifier FAIL on packaging + narrative) →
**`775bae1`** (current; branch rewritten as ONE commit `Share private
context across owners only by an explicit share (ThinkFlowLab#53)` directly on the
rebased ThinkFlowLab#94 tip `985c82e`, which sits on origin/main `1736161`).

Ran at `775bae1`:
- \`npm run verify\` (pipeline gate) — **exit 0**.
- Red-at-base at \`985c82e\` + only the two test files: context-sharing
**5 fail / 1 pass** — the 5 feature tests fail for the right reasons
(brief present cross-owner; `bot/not-found: Unknown endpoint
share-brief` on the owner-share and foreign-share tests — so the round-1
"2 passing guards" claim was wrong: the foreign-share guard fails at
base too, via `bot/not-found` rather than `bot/forbidden`); memory **2
fail** — the classification test fails on the batch text, and the
team-gate test fails with **nothing written at all** (base never writes
b3's MEMORY.md from this fixture), not a cross-owner team write.
- \`node --test\` on the two files at the tip: **66/66 across both
files** (8 new).
- Smoke: ports 3094/8794 — **all 40 steps passed**; bench stopped, data
removed.

## Round-1 findings → fixes

1. **Patches block** — fixed: `context-sharing.test.js` joined topic
0002, patches regenerated in-commit.
2. **Narrative** — corrected above (5/1, and the memory base failure
mode stated exactly).
3. **Remote owners** — fixed: a granted remote Bot's handle now counts
as another owner in both `briefAllowed` (groups.js) and
`teamAllowedFrom` (memory.js); a remote participant in an otherwise
single-owner room suppresses the brief (new test).
4. **Chip gates beyond send/room-post** — follow-up filed as ThinkFlowLab#106; noted
in team.md as not-done-here.

Fixes ThinkFlowLab#53

Docs-not-needed: ThinkFlowLab#53 context-sharing narrative lives in
docs/subsystems/team.md (Context sharing foundation) with core-host.md
cross-ref; merge-queue squash must retain this trailer.

Co-authored-by: hsliu_ustc <hsliu_ustc@noreply.gitcode.com>
…nd-share) (ThinkFlowLab#112)

Bug: after creating a group and adding another person's Bot, that Bot's
owner never saw the group.

Cause: (a) FlyBot never told the other side a room existed (New group
passed the remote chat session as a local member, and add/create did not
notify Bot Social); (b) Bot Social only has 1:1 conversations, so an
empty room is invisible; (c) the inbound-share projection (ThinkFlowLab#50) was
never wired in production.

Fix (FlyBot only): a FLYBOT-ROOM-SHARE envelope over 1:1 send_as_member
carries join/leave and chat lines. The owner's pollOnce applies it
before 1:1 delivery and creates an inbound-share room (Shared room from
<inviter>); leave, hide or grant revoke marks it left. New group splits
remote vs local members. The owner's Bot replies still go through
in-chat approvals. Local verify passed.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
The Bot tree's collapse/expand chevron was a 10px glyph in a 16x36
strip. It is now a 16px glyph on a 28x28 hit target, vertically
centered, with a hover background and a :focus-visible ring. Rotation,
aria-expanded/labels, indent and row height are unchanged. The optional
Tree plugin section gets the same treatment. Local verify passed; not
clicked in a live window.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…Add friend moved into the + menu (ThinkFlowLab#116)

## Why

Approving a Bot Social hold meant leaving the conversation for a bloated
sidebar card, and the Friends' Bots panel (approvals, Auto-send,
Face-to-face rooms, "+ Add") had grown heavy. Creation actions belong in
the single top-level "+" menu.

## What

- **Inline approvals (ThinkFlowLab#114):** the open chat shows the held draft with
Approve / Reject for the owner ("Waiting for the owner's approval" for
everyone else), Approve all when several are held, and a wait badge on
the chat row.
- **Compact panels (ThinkFlowLab#115):** Friends' Bots is a one-line jump list;
Auto-send is one compact toggle; Face-to-face rooms are one row each,
with closed/left rooms under "Past rooms".
- **"+ Add" merged into the global "+":** Add friend and Friend requests
are rows next to Face-to-face group. The Friends' Bots header is just
title, count and the "N to approve" chip. Each action opens the same
flow as before.

Fixes ThinkFlowLab#114
Fixes ThinkFlowLab#115

## Verification

- `ARCH_DOCS_BASE=upstream/main npm run verify` exit 0 at 906205b;
patches generate/check and changelog check pass.
- Not run: smoke, live click-through.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
ThinkFlowLab#119)

Fixes #16

Owner decision: **Option B**. Tree Bots (not only Main/root) may call
`create_child_bot` to create children under themselves. AGENTS.md and
the related docs are updated to match how the tree plugin already
behaved (`visibleTo: inTree`, parent = caller).

Guardrails unchanged: `maxChildren` and depth limits.

A newly created child starts with **no person-grant**, even if the
parent is already granted. Sharing still requires an independent per-Bot
grant (default deny). No publish mechanism.

Tests assert Option B creation for a non-Main tree Bot, that limits
still apply, and that the child has no auto-grant.
`ARCH_DOCS_BASE=upstream/main npm run verify` passes. Tree plugin + docs
only; no core/patch regeneration.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
ThinkFlowLab#118)

Fixes ThinkFlowLab#117

Fixes found while running two real FlyBot instances side by side on the
Face-to-face flow:

- Waiting card no longer clipped or stale; it compacts after the first
message.
- Tiles show the owner's name, and each Bot keeps its own avatar.
- The second instance now sees the join and receives messages, including
lines the owner types (a `FLYBOT-FACE-HUMAN` envelope; typing is
consent, applied as human on the other side).

Checks: `ARCH_DOCS_BASE=upstream/main npm run verify` passes. Tests
added in core, social and remote-room suites; changelog fragment and
subsystem docs updated. No change to the pinned `ds-bot/`; generated
patches regenerated.

Not covered: Bot replies (needs a model key).

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…nkFlowLab#98) (ThinkFlowLab#120)

Fixes ThinkFlowLab#98

Rebase of [@hsliuustc0106](https://github.com/hsliuustc0106)'s
[ThinkFlowLab#99](ThinkFlowLab#99) onto current
`main`, with the design-review fixes applied. **Supersedes ThinkFlowLab#99.**

- Fence by origin (remote participant, local `bot.remote` record,
non-team botId, or `line.remote`), not only face rooms.
- Skip a complete existing wrap so fanout does not nest; still fence a
substring mention of the marker.
- Room `RemoteMessage` unwraps with `remoteMessageText()` like the 1:1
path.
- Inbound-share / face human lines are treated as remote-origin and
fenced.

Deferred: forgeable `FLYBOT-FACE-HUMAN` `who` lives in `plugins/social`
(follow-up).

`ARCH_DOCS_BASE=upstream/main npm run verify` passes. Please close ThinkFlowLab#99
once this is preferred.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
ThinkFlowLab#121)

Fixes ThinkFlowLab#54

Rebase of [@hsliuustc0106](https://github.com/hsliuustc0106)'s
[ThinkFlowLab#110](ThinkFlowLab#110) onto current
`main`, with grant-model review fixes. **Supersedes ThinkFlowLab#110.**

- Task lifecycle: open → claimed → working → blocked ⇄ working →
result-ready → accepted (wrong-owner moves refused; failed/cancelled
cannot display as accepted).
- Create does **not** require owning the Bot: matching grant or shared
room is enough for a granted child to hand off; outgoing holds stay the
owner's.
- `list_bots` restricted to own Bots + granted-to-same-owner +
current-room; granted children are scoped to their `createdBy` subtree.
- Memory review labels another human as `peer`, not `bot`.
- Changelog fragments only (`CHANGELOG.md` untouched); patches
regenerated.

Remaining related work (not this PR): in-chat task cards (ThinkFlowLab#57); ThinkFlowLab#111
follow-ups; grant-tied `briefAllowed` share (deferred, noted in
`docs/subsystems/team.md`).

`ARCH_DOCS_BASE=upstream/main npm run verify` passes (core 721). Please
close ThinkFlowLab#110 once this is preferred.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…hinkFlowLab#124 ThinkFlowLab#125 ThinkFlowLab#126) (ThinkFlowLab#127)

Fixes ThinkFlowLab#124
Fixes ThinkFlowLab#125
Fixes ThinkFlowLab#126

Found in a two-instance cloud UI self-test on `87c6940`.

**ThinkFlowLab#124** Face-room human messages now go through the room post path
(implicit owner when not signed in) with a quiet local notice, and skip
the local Bot turn unless a local Bot is @mentioned. The other instance
receives it via `FLYBOT-FACE-HUMAN`.

**ThinkFlowLab#125** The waiting screen is a tile grid (avatar above name, with
owner), Leave and Enter chat on one row, live member count, and owner +
Bot name in join lines. Leaving after the matching window ended moves
the room to Past rooms. Past rows are unique with a single Open; live
rows are click-to-open with Leave.

**ThinkFlowLab#126** The sidebar `+` is a short action menu. Add friend has
one-click **Copy invite link**. Hold approvals show as `Chief → the
room` instead of raw ids, and the Friends' Bots column no longer stacks
jump lists. Softened "matching window" and "Add Agent" wording in the
social `+`.

Deferred: grant-tied `briefAllowed`; envelope `who` is still taken from
JSON rather than the sending member record; DSH core still says "Add DSH
Agent" outside the social `+`.

`ARCH_DOCS_BASE=upstream/main npm run verify` passes. Smoke bench not
run (no Harness checkout).

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…ThinkFlowLab#122 ThinkFlowLab#123) (ThinkFlowLab#128)

Fixes ThinkFlowLab#122
Fixes ThinkFlowLab#123

**ThinkFlowLab#122** Empty or `@` handles stay empty (`norm`, REST `get`,
hide-handle). A stored `asBot: "@"` is healed, and the poll only sends
`as` when it is a real handle. Rooms discovered through `list` are
seeded from `my_bots`. A `face_room_not_found` on a room that is still
listed repairs the meta and retries once.

**ThinkFlowLab#123** Approval waiting notices no longer call `deliver` with a
home-level handle. They open or resolve a listed friend Bot's 1:1 chat
so the line lands in the chat. If there is none, they skip with
`approval chat note skipped: no 1:1 chat for …` instead of throwing.

`ARCH_DOCS_BASE=upstream/main npm run verify` passes; `ds-bot/`
untouched, no patch regeneration.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Unwrap stored untrusted-message wraps in room previews and bubbles, resolve
User to the signed-in display name, and show a short friendly line instead of
raw model/API-key errors. The sidebar + menu keeps plugin actions and toasts a
returned string, without listing every Bot (Chief clutter).

Co-authored-by: Danyang Chen <birfychen@gmail.com>
Expired matching and a peer leaving move rooms to Past rooms without a stale
Leave. Titles include people or the code/time. The + menu holds Add friend,
Accept invite, one-click Copy invite link, and Face-to-face group. Auto-send is
the one hold label; the header shows either the Bot count or N to approve.
Inbox and face poll drop to about 4s while a room is live.

Co-authored-by: Danyang Chen <birfychen@gmail.com>
@Birfy Birfy closed this in 59637e2 Oct 23, 2026
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.

Face-to-face: stale active room cards (peer left / ended) should move to Past rooms automatically

4 participants