Repository navigation
Decouple plugins: tree and social as peer plugins under plugins/ (#34 PR 2, stacked on #37) - #40
Conversation
… ROUTING send priority (#34) - core/plugins/tree -> plugins/tree; core/test/tree-host.test.js -> plugins/tree/test/host.test.js (boots core by its public entry 'ds-bot' via test/resolve.mjs -> FLYBOT_CORE | core/, in a plugin-local fake Cordis ctx; loads tree with ctx.plugin, disposes via ctx.effect scope) - plugins/tree/test/web-entry.test.js: real-Cordis browser entry test (flybot.tree.forest), moved from core - core changes via the patch workflow: delete test/tree-plugin.test.js, core client tests stop loading tree's bundle, local-ci runs ../plugins/tree, plugins/README; topics.json updated, patches regenerated (ds-bot-patches.mjs generate/check) - adapter: flybot.version check; bot.send.use priority ROUTING (flybot.bot.send.priority) instead of before:['social']; mock orders by priority bands - package.json: peer ds-bot >=0.1.5-flybot.1, own npm test, description fixed - CI (flybot-tests action, nightly), check-pack, READMEs point at plugins/tree
- plugins/social: peer plugin like plugins/tree (ds-bot >=0.1.5-flybot.1) - core/: drop core/plugins and social tests; regenerate patches (0006 retired); ds-bot/ untouched - tests: send ordering uses priority constants instead of before: ['social'] - CI: plugins (tree)/plugins (social) jobs with DSH_HARNESS and a no-skip gate, required by tests-ok - docs: install paths path:/plugins/* Tree decoupling (76bc623) by recursive-bot.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d9c8a97762
ℹ️ 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".
| - The core is now pristine upstream ds-bot (`ds-bot/`, pinned at Fei2411/DeepSeek-Bot@2b10c70) plus FlyBot's ordered patch series (`patches/ds-bot/`), applied into the committed `core/` (#34). Install paths move from `path:/ds-bot` to `path:/core` and `path:/plugins/{social,tree}`. The core's version is `0.1.5-flybot.1`. | ||
| - Send middleware can set a `priority` (`ROUTING` 100, `DEFAULT` 500, `APPROVAL` 900; `flybot.bot.send.priority`); names in `before`/`after` still work and win over priority. `@flybot/social` runs at `APPROVAL`. | ||
| - `types/flybot.d.ts` documents the browser `flybot` service and its slots. | ||
| - The plugins are peer packages at the repo root: `@flybot/tree` (`plugins/tree`, by recursive-bot) and `@flybot/social` (`plugins/social`, moved out of `core/plugins/social`). Each needs `ds-bot >=0.1.5-flybot.1`, tests itself against `core/`, and has its own CI job (`plugins (tree)`, `plugins (social)`, both required by `tests-ok`). Reinstall the social plugin from `path:/plugins/social`. |
There was a problem hiding this comment.
Re-add tree when migrating the plugin source paths
Existing profiles installed from the previous README have @flybot/tree saved as a Git dependency using path:/ds-bot/plugins/tree (or the stacked path:/core/plugins/tree). This commit deletes those directories, while README.md's Update command still runs pnpm update ... @flybot/tree and this sentence tells users to reinstall only social. pnpm update --help says it updates packages “based on the specified range,” so it re-resolves the saved, now-invalid Git path and aborts the whole update; document re-adding tree from path:/plugins/tree in both README variants too.
AGENTS.md reference: AGENTS.md:L79-L79
Useful? React with 👍 / 👎.
| }, | ||
| "peerDependencies": { | ||
| "ds-bot": "*" | ||
| "ds-bot": ">=0.1.5-flybot.1" |
There was a problem hiding this comment.
Keep future FlyBot prereleases in the peer range
With a later API-v1 FlyBot core such as 0.1.6-flybot.1, this range reports an unmet peer even though that release is newer and createAdapter accepts API v1. npm's bundled node-semver documentation says a prerelease is allowed only when a comparator with the same [major, minor, patch] tuple has a prerelease, so this comparator admits only prereleases on the 0.1.5 tuple (and even admits the unpatched stable 0.1.5) rather than future FlyBot builds; use a peer/versioning scheme that identifies the compatible FlyBot line.
Useful? React with 👍 / 👎.
| step="plugin tests (social)" | ||
| echo "=== $step ===" | ||
| (cd plugins/social && node --import ../../test/harness-resolve.mjs --test "test/*.test.js") | ||
| (cd ../plugins/social && npm test) |
There was a problem hiding this comment.
Run peer-plugin lint from local CI
On the advertised npm run ci:local macOS path, core's npm run lint no longer scans plugins after this diff, and these new package invocations run tests only; no call to scripts/lint-plugins.mjs remains before the script prints PASS. A peer-plugin no-undef or unused-variable failure therefore passes local CI even though the PR's plugins (...) job fails, so run the root plugin linter here as well.
Useful? React with 👍 / 👎.
Point doctor packages and install commands at plugins/social and plugins/tree (repo-root peers). Document npm run install:doctor in README EN/zh and plugins/README.md. Regenerate patches/ds-bot 0007. Verifier: - ran: node --test test/install-doctor.test.js (11 pass); ds-bot-patches check ok - not run: full npm test / smoke Co-authored-by: hs liu <hsliuustc0106@users.noreply.github.com>
* feat(core): install doctor for clean DSH plugin setup (part of #7) Add core/scripts/install-doctor.mjs and npm run install:doctor to verify Node >= 22.19 and the three plugin packages, then print the /core install commands. Hardened checkPluginPackage so null/non-string manifests return structured errors. Unit tests + plugins README note. Regenerate patches/ds-bot (0006/0007 + topics) so core/ still equals ds-bot + patches. Verifier: - ran: node --test test/install-doctor.test.js (11 pass); node scripts/ds-bot-patches.mjs check (ok); Codex r2 FAIL (missing patches) → fixed; Codex r3 INC (no code findings, sandbox limits) - not run: full npm test / smoke / GHA Co-authored-by: hs liu <hsliuustc0106@users.noreply.github.com> * fix(core): adapt install-doctor paths after plugins-decouple (#40) Point doctor packages and install commands at plugins/social and plugins/tree (repo-root peers). Document npm run install:doctor in README EN/zh and plugins/README.md. Regenerate patches/ds-bot 0007. Verifier: - ran: node --test test/install-doctor.test.js (11 pass); ds-bot-patches check ok - not run: full npm test / smoke Co-authored-by: hs liu <hsliuustc0106@users.noreply.github.com> --------- Co-authored-by: hs liu <hsliuustc0106@users.noreply.github.com>
* Logo: new Wingbot mark and FlyBot wordmark lockup (#32) * README: wordmark hero, CI badges, How FlyBot is built (#34) * README: name the pin, patches and core folders * README: drop CodeQL badge; plugins/ in architecture after #40 * README: use #main for the GitHub install example (no v0.1.5 tag) --------- Co-authored-by: Danyang Chen <cdy@recursive.bot>
#774) Written-by: Dev2 Fixes #773 Tip SHA: 14a13fc ## Reproduce A face-to-face room is joined by reading six digits off someone else's screen and typing them. Nothing on the waiting screen can be read by a phone, and the join form has no way in except the keyboard: on `main` `plugins/social/client.js` draws `FaceWaitCode` (the number) and a `fs-digits` input, `grep -c BarcodeDetector` is 0, and the only `getUserMedia` in the repo is the microphone in `speech.js` / `voice.js`. A room that already exists has the second half of the same hole: no way to invite anyone into it. `grep -c "face-invite" plugins/social/client.js` is 0 on `main`, the client has no link shape to paste or scan, and the host exposes only `social/face-join` with a 6-digit code. Bot Social's side is `Birfy/bot-social#39` / `#40` (`POST /v1/face-rooms/:id/invites`, `DELETE …/invites/:ticket`, `GET …/invites/:ticket`, `POST /v1/face-rooms/join-invite`, advertised as `face_room_invites`). ## What changed - `plugins/social/client.js`: a self-contained QR encoder (byte mode, error correction level M, versions 1–10 picking the smallest that fits) plus `QrImage`, an `svg` with one `path` over the dark modules, and `FaceQr` as the room-number wrapper. The room-code payload is `FLYBOT-` + the six digits and nothing else: a deadline inside the image would re-encode every second and a screenshot would carry a stale number. The invitation payload is the invite URL itself, so a phone camera opens the same room. - `FaceWaitCode` takes `qrAt` / `qrOffsetMs` and draws the QR under the number, from Bot Social's deadline (corrected by the clock offset), in the dialog and in the room panel. The code goes when joining ends and as soon as the room opens. - Scanning: a **Scan a code** button reads frames with the platform `BarcodeDetector` and the camera with `getUserMedia`. The decoder is checked **before** the camera prompt, a refused camera gets one sentence, a payload that is neither `FLYBOT-<six digits>` nor an invite link is refused, and the camera is released on every path. A scan only fills the same field and calls the same `join()` (`join` gained an optional `{ code }`), never a second way in. - **Camera fixes from the INC** (`#774` review, [6352294330](#774 (comment))): `new BarcodeDetector({ formats: ['qr_code'] })` now sits in a `try`/`catch` — a platform that has the decoder but refuses the format used to leave the camera running with the prompt stuck at `Point the camera at a FlyBot room code.` and one unhandled rejection; now it answers `This browser cannot scan codes. Type the 6-digit number instead.` through the same `finish` that stops the stream. And `startFaceScan` releases the previous scan's camera before opening a new one, so tapping **Scan a code** twice no longer orphans the first stream behind a module-level `faceScanStop`. - **Invite by link** on an open room (`FaceInviteCard`, `data-flybot=face-invite`): **Create link** mints a ticket (`POST /v1/face-rooms/{id}/invites`), the card draws that link as the QR, and shows the link as text, **Copy link**, the server deadline and **Withdraw link** (`DELETE …/invites/{ticket}`). The URL is built by the host from its own base (`faceInviteUrl`), never trusted from the reply. An expired link and a withdrawn one each say so and draw no image. - One shape in and out: `https://…/r/<ticket>` (`parseFaceInviteLink`). Pasting or scanning anything else is `That is not an invite link. It looks like https://…/r/…` and the 6-digit number stays the way in. Joining is `POST /v1/face-rooms/join-invite`, and each refusal keeps its own sentence: `invite_not_found`, `expired` and `revoked` each ask for a new one, `room_full`, `ended` (the room has closed), `already_member`. The ticket is Bot Social's 32-byte base64url (43 characters, inside the `[A-Za-z0-9_-]{16,64}` shape this client accepts); the public preview (`GET /v1/face-rooms/invites/{ticket}`) is mapped from the server's own `member_count` / `max_members`. - **Both invite paths are gated**: mint, revoke, preview and `social/face-join-invite` all go through `serverInvites()` and answer `{ unsupported: true }` on a server whose `GET /v1/platform` features lack `face_room_invites`, so no request is made to a route that is not there (this is the third INC item, [6352294330](#774 (comment))). - `docs/subsystems/social.md` and `changelog.d/773-face-room-qr.added.md`. This tip is rebuilt on current `main` (`fd205c40`): `social.js`, `social.test.js` and `docs/subsystems/social.md` carry `main`'s newer content plus this PR's additions (three-way merged), so merging does not revert `#776` / `#777`. ## What I ran At `14a13fcc61e5bf3cbc0845d1a1711cb657506899`, on a tree that is exactly `main` `fd205c40` + these seven files (tarball of `main` with the PR's files copied over, `git init` + commit so `ds-bot-patches` can run): - `plugins/social`: `node --import ./test/resolve.mjs --test "test/*.test.js"` → **354 tests, 354 pass, 0 fail** (baseline on `main`: 330/330). All 24 new tests are named `#773:`. - `node .github/scripts/changelog.mjs check` → 3 fragments well formed. - `node .github/scripts/testing-layers.mjs --check` → `315 test file(s) in 12 workflow(s)`, all classified. - `node scripts/ds-bot-patches.mjs check` → three ok (pin, 10 patches apply, `core/` equals `ds-bot/` + patches). - `node scripts/lint-plugins.mjs social` → 0 errors, 34 warnings, all pre-existing. - `core/scripts/check-arch-docs.mjs` → `OK (7 changed file(s))`. - `node --test ".github/scripts/*.test.mjs"` → **227/227**, including the `check-empty-catch` guard that failed the first tip. The QR matrices are pinned against an independent encoder (`node-qrcode`, byte mode, level M): 441 modules for `FLYBOT-482913` at version 1, and for the invite link the sha256 of the drawn `path` over the 33×33 version-4 matrix (mask 2). ## Red without the source change Each line is the mutation and the tests that failed (`--test-name-pattern "#773"`): - Do not render `FaceQr` under the number → 3 red (matrix, deadline, panel). - Make `qrJoinable` ignore the deadline → 3 red (dialog, panel, and an expired invite keeps its image). - Write the format bits without their BCH mask (`^ 0x5412`) → 1 red (the matrix test). - Ask for the camera before checking `BarcodeDetector` → 1 red. - Let the scan call `social/face-join` itself → 1 red (only the shared `join` moves the dialog on). - Accept any payload as a room code → 4 red (a foreign QR or pasted text would join). - Never release the camera → 1 red. - Drop the invite card from the open room → 4 red (invite QR, Create link, older-server sentence, expired/withdrawn). - Treat any text as an invite link → 4 red. - Let the invite endpoints ignore the feature list → 1 red (a POST to a route that is not there). - Give `expired` and `revoked` the same sentence → 1 red. - Construct the detector without `try`/`catch` → 1 red (`a decoder that refuses the qr_code format releases the camera and says so`). - Drop the "release the previous scan" step → 1 red (`tapping Scan twice releases the camera the first tap opened`). - Remove the `serverInvites()` gate from `face-join-invite` → 1 red (`joining by ticket on a server without face_room_invites never reaches the route`). ## Not run - No real camera and no real `BarcodeDetector` here: both are driven through stubs, and the encoder is checked against a reference implementation rather than a scan of a printed code. The first real scan will be in the app. - No live Bot Social carrying `#40` here: the feature gate and the REST calls are tested against stubbed servers (with and without `face_room_invites`). On this repo's servers the card is expected to say *Invites need a newer Bot Social* until `#40` is deployed — that is the designed answer, not a failure. - **CI on this tip is blocked by the account billing lock** (`The job was not started because your account is locked due to a billing issue.`), which affects every PR. The local run above is on the same tree CI would build; the tip can be re-run unchanged once billing is restored. - `npm run verify` as a whole was not run; `plugins/tree`, `plugins/timeline` and `core` are untouched by these seven files. ## Risk **T3** (client, host glue, tests, one docs page, one changelog fragment). The image is drawn from a payload this app chooses, so a wrong matrix can only produce an unscannable code; the room code's payload *is* the number the panel prints, so it cannot point at another room. A foreign QR or pasted text cannot join anything: both are refused with one sentence before any request. The camera is a permission surface, so the decoder check runs first, the stream is stopped on every exit (including a second tap and a detector that refuses the format), and the button is disabled while a join is in flight. An invite link is a bearer credential for as long as the room is open — the same window the 6-digit number already had — and **Withdraw link** revokes it server-side. On a server without `face_room_invites` nothing new is reachable and the typed number behaves as before. Deliberately out of scope, per the review and the owner decision: `face-invite-preview` / `previewInvite` stays wired end to end but is not called by any screen yet (follow-up issue records the "show the room before joining" step, including cleaning the server's `name` before rendering); `serverInvites()` still re-probes `GET /v1/platform` on each call instead of using the shared probe window — a one-line change left for that follow-up rather than folded into the camera fix.
ThinkFlowLab#931) Owner: Dev3 Reviewer: Reviewer2 Fixes ThinkFlowLab#928 ## Reproduce On `main` `137cd2fd`, with `review-policy.json` carrying `selfReview: ["Birfy", "hsliuustc0106"]` (what ThinkFlowLab#926 adds), `decide()` is fed the real comments of ThinkFlowLab#926's head `b80823ef`. The PASS by `hsliuustc0106` reports `counts: false` with `author association CONTRIBUTOR is not OWNER, MEMBER or COLLABORATOR, so it is not a conclusion`, and it is the head's only report. The cause is ordering, not the list: `ASSOCIATIONS` (`review-gate.mjs:36`) is checked at `:162`, `selfReview` is read at `:144` and only consulted at `:178` — after the association gate has already dropped the report. So adding the login changes nothing, which is what Reviewer measured on ThinkFlowLab#926. ## What changed - `.github/scripts/review-gate.mjs` (+5/−1): at the association gate, `const selfOnly = selfReview.has(login(r)) && login(r) === authorLogin`, and `if (!ASSOCIATIONS.has(association) && !selfOnly)`. One exemption, for one semantic: a listed login judging **its own** pull request. Every other check is untouched — `hold`, stale tip, `Written-by:`, `Ran:`, the reviewer list, the newest-word rule. - `.github/scripts/review-gate.test.mjs` (+23): one test, three cases — listed + `CONTRIBUTOR` + own pull request → `counts: true`; unlisted `passer-by` + `CONTRIBUTOR` → `counts: false`; listed + `CONTRIBUTOR` judging someone else's pull request → `counts: false`. The third case is what keeps the exemption from becoming a second reviewers list. - `AGENTS.md:150` and `docs/autonomous-dev.md:147`/`:163`: the exception written next to the rule it changes, dated as the owner decision it executes. Not in this PR: ThinkFlowLab#926 itself (a one-line JSON change on someone else's branch), and no other gate. `selfReview` is still a subset of `reviewers`; the boolean this adds is narrower than the association check it excuses. ## What I ran - `cd .github/scripts && node --test "*.test.mjs"` → **333 pass / 0 fail**. - `cd .github/scripts && node --test review-gate.test.mjs` → **40 pass / 0 fail** (the new test is ThinkFlowLab#40). - `node .github/scripts/changelog.mjs check` → 45 fragments well formed (no fragment here: the gate is not user-visible, same as ThinkFlowLab#912). - `node core/scripts/check-arch-docs.mjs --base 3c711b3` → `OK (17 changed file(s))`; no `docs/subsystems` page owns `.github/scripts/`, `AGENTS.md` or `docs/autonomous-dev.md`, and both policy documents are updated in this branch anyway. (The local base is stale — CI judges the real base.) - Before pushing, each of the four files was diffed against its **current `main`** content fetched through the API (`137cd2fd`): the only differences are the hunks above, so the branch cannot revert anything that landed on `main`. ## Red without the source change - Mutation A — delete `&& !selfOnly` from the guard: `node --test review-gate.test.mjs` → `not ok 40 - ThinkFlowLab#928: a listed self-reviewer who is a CONTRIBUTOR counts, only on their own pull request`, **39 pass / 1 fail**. - Mutation B — widen to `selfReview.has(login(r))`: same test red, **39 pass / 1 fail**. - Restored file is byte-identical to the branch version (`diff -q` clean), `40 pass / 0 fail`. - With `review-gate.mjs` at `main` and the new test kept, case 1 (`mine.ok`) is false, so the test fails — the source change is what makes it pass, not the fixture. ## Not run - Full `npm run verify`: this box has no `core/node_modules` / harness install, so `core` and plugin suites are not run here — CI runs them. Nothing in this branch touches `core/`, `plugins/` or `client.js`. - No smoke test: no `index.js` or `core/src/host/` change. - No live re-run against ThinkFlowLab#926's data after the change (its head is unchanged and still carries the old conclusion); the fixture in the test reproduces those comment records instead. ## Risk - The exemption is keyed on `selfReview` membership **and** `login === authorLogin`, so a login added to `selfReview` now also gets the association exemption on its own pull request. That is exactly the owner decision recorded in `AGENTS.md`, but it means `selfReview` is no longer purely "may PASS its own PR" — it also says "may be a `CONTRIBUTOR` while doing so". Anyone else's pull request is unaffected (pinned by the third case). - `merge-gate.mjs` imports this `decide`, so a queue entry is judged by the same rule; a `CONTRIBUTOR` who is not listed is still refused at the queue, as before. - ThinkFlowLab#926 stays open and unchanged by this PR; per the Chief it goes in together with this one, so the policy change and the code that makes it mean anything land as a pair. Docs-not-needed: no `docs/subsystems` page owns `.github/scripts/`, `AGENTS.md` or `docs/autonomous-dev.md` (checked against their `owned_paths`), and the rule itself is updated in `AGENTS.md` + `docs/autonomous-dev.md` in this branch.
Stacked on #37 (
ds-bot-pin): this branch contains #37's commits. Review and merge them together, #37 first. Refs #34 (PR 2).What changes
Birfy/recursive-bot:flybot-tree-decouple) unchanged, with recursive-bot as author. It moves tree toplugins/tree, rewrites patches 0003/0006/0007, makessendusepriority.ROUTING, and sets the peer dependency to>=0.1.5-flybot.1. Thanks @recursive-bot.core/plugins/socialtoplugins/social. It is now a peer plugin like tree (ds-bot >=0.1.5-flybot.1) with its own test runner (test/resolve.mjs,fake-ctx.js). Its web-entry test moved out of core.core/pluginsand the social tests in core are removed, and the patches are regenerated.0006-flybot-pluginsis retired because nothing is left in it.ds-bot/is untouched, andnode scripts/ds-bot-patches.mjs checkpasses.send.priority.ROUTING/APPROVALinstead ofbefore: ['social'].ubuntu-latest · Node 24 · DSH 0.2.0-rc.2keeps its name and now runs core only, with coverage.plugins (tree)andplugins (social)jobs lint the plugin (scripts/lint-plugins.mjs) and run its suite withDSH_HARNESS, so the frontend tests really run. The job fails if any test is skipped.tests-oknow needs both plugin jobs.path:/coreandpath:/plugins/{social,tree}. Existing social installs frompath:/core/plugins/socialneed to be reinstalled.Local results
checkokbuild-client --checkok