Repository navigation
feat(feishu): connect Feishu on its connector row (#1266) - #1303
Merged
Merged
Conversation
Birfy
force-pushed
the
dev/1266-feishu-connector-row
branch
2 times, most recently
from
October 27, 2026 01:01
1e1ee82 to
ceed70a
Compare
HugoXK
pushed a commit
to HugoXK/FlyBot
that referenced
this pull request
Oct 27, 2026
…Lab#1305) (ThinkFlowLab#1309) Written-by: Dev Fixes ThinkFlowLab#1305 Owner: Dev Reviewer: Reviewer Tip SHA: 7abd38a Docs-not-needed: needsCount gains an optional now and the two assertions pass NOW; no behaviour change for the badge (its callers still pass three arguments), no user-facing API, and docs/subsystems/core-client.md already describes the Inbox badge. `main` has been red since `2026-10-27T00:55Z` on `Node 24` because two assertions in `core/test/inbox.test.js` compare the badge count against a model built with a fixed clock while the badge helper read the wall clock. The fix is **explicit `NOW` / a frozen clock, never an adjusted expectation**: `7` and `8` stay exactly as they are, so the suite cannot go red again next week when the 7-day window slides one more notch. ## Reproduce - On a clean tree of `main` `59df1580` (tree `4cfba88956de2c5e9ab144dd743b78493b846994`): `cd core && node --import ./test/harness-resolve.mjs --test test/inbox.test.js` → `26 tests / 24 pass / 2 fail`, `Expected values to be strictly equal: 7 !== 6` at `test/inbox.test.js:122`, `8 !== 6` at `:297`. - The whole suite at that tree: `1767 tests / 1761 pass / 6 fail` — the two inbox cases plus four that are red on this machine only (see *Not run*). - `main`'s own last green `Node 24` run was `59df1580` at `00:49Z`; the same commit's code is red from `01:02Z` (job `121945236810` on `ThinkFlowLab#1303`, and `8c256fbe` on `ThinkFlowLab#1304` at `00:56:45Z`, job `121943086386`). ## What changed - `core/src/client/inbox.js:274` — `needsCount = (roster, work, tasks, now) => inboxModel({ roster, work, tasks, now }).needs.length`. `now` is optional and forwarded as-is: `inboxModel` already defaults it to `Date.now()`, so the badge's real callers (`inbox.js`, three arguments) keep the wall clock and nothing user-visible moves. - `core/test/inbox.test.js:122` and `:297` — pass `NOW` (`needsCount(roster, work, tasks, NOW)`, `needsCount(roster, endedWork, tasks, NOW)`). The expected values are untouched: `model.needs.length` (7) and `model.needs.length + 1` (8). - `core/client.js` rebuilt with `node core/scripts/build-client.mjs` (1 line), then `node scripts/ds-bot-patches.mjs generate` regenerated `patches/ds-bot/0008-client-bundle.patch`, `patches/ds-bot/files/src/client/inbox.js` and `patches/ds-bot/files/test/inbox.test.js`. `git add -A` first, then `check` — the sequence matters, an untracked mirror makes `check` falsely green. Same shape as the interim `206fbadb` on `ThinkFlowLab#1304` (`fix(inbox): the badge count reads the clock the list reads (ThinkFlowLab#1305)`), so the two branches merge without a conflict; once this lands, rebasing `ThinkFlowLab#1304` will drop that commit as empty. ## What I ran At `7abd38a0e199b4ba24578b2d9597a21c0a71a82e`: - `cd core && node --import ./test/harness-resolve.mjs --test test/inbox.test.js` → `# tests 26 / # pass 26 / # fail 0`, run twice. - `cd core && DSH_HARNESS=<harness> node --import ./test/harness-resolve.mjs --test --test-concurrency=1 test/*.test.js` → `# tests 1767 / # pass 1763 / # fail 4`; the four are `test/composer-label.test.js`, `test/header-effects.test.js`, `test/read-page.test.js` and `fixture usage.sqlite has one real calls row with all five fields`, and all four fail identically on the clean `59df1580` tree (`10 tests / 6 pass / 4 fail` for those files there), so none of them is from this diff. The two inbox cases are `ok` in the full run (`ok 773`, `ok 787`). - `node core/scripts/build-client.mjs --check` → exit 0. - `git add -A && node scripts/ds-bot-patches.mjs check` → `ok ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70`, `ok 10 patches apply cleanly in order`, `ok core/ equals ds-bot/ + patches/ds-bot/`. - `node core/scripts/check-arch-docs.mjs` → `arch-docs: OK (6 changed file(s))` (with the `Docs-not-needed:` trailer above, which is also in the branch commit). - `node .github/scripts/check-mojibake.mjs --check` → `mojibake: ok — 0 occurrences of U+FFFD in 1114 text file(s)`. - `cd core && npm run lint` → `Found 67 warnings and 0 errors` (the warnings are pre-existing). ## Red first The red is the base, and it is the *same two cases* the fix turns green — so the red-first evidence is the test change on its own, with the source line still old: 1. `core/test/inbox.test.js` changed to pass `NOW`, `core/src/client/inbox.js` untouched → `26 tests / 24 pass / 2 fail`, `expected: 7 actual: 6` (`:122`) and `expected: 8 actual: 6` (`:297`). 2. Add the one-line `now` parameter → `26/26`. Reverting only that line from this tip gives the first result back, with the other 25 tests still green. ## Not run (and why) - The four reds above (`composer-label`, `header-effects`, `read-page`, the usage fixture) are green in CI on `main` and red here on the clean base tree too; this PR does not touch them. - Plugins (`social`/`tree`/`timeline`), `.github/scripts`, interop and live-e2e suites: no file outside `core/src/client/inbox.js`, `core/test/inbox.test.js` and the generated bundle/patches changes in this PR, and CI runs them. ## Checkpoint <!-- flybot:checkpoint --> ```json { "pr": 1309, "claim": "Dev", "issue": 1305, "summary": "The Inbox badge read the wall clock while the two assertions built their model from a fixed NOW, so the suite went red at 00:55Z when the 7-day window slid one notch. needsCount now takes an optional now and forwards it to inboxModel (the badge's own callers pass three arguments, so the clock default is unchanged), and test/inbox.test.js:122 and :297 pass NOW. Expected values 7 and 8 are untouched: the assertion stopped reading the clock instead of the number being adjusted. 26/26 inbox tests here, generated client bundle and patches mirrors rebuilt, ds-bot-patches check green.", "files": ["core/src/client/inbox.js", "core/test/inbox.test.js", "core/client.js", "patches/ds-bot/0008-client-bundle.patch", "patches/ds-bot/files/src/client/inbox.js", "patches/ds-bot/files/test/inbox.test.js"], "checks": {"ran": "node --import ./test/harness-resolve.mjs --test test/inbox.test.js && node core/scripts/build-client.mjs --check && node scripts/ds-bot-patches.mjs check", "result": "green", "at": "7abd38a0e199b4ba24578b2d9597a21c0a71a82e"}, "next": "Reviewer judges this tip; then the merge queue" } ``` <!-- /flybot:checkpoint -->
The owner's word is 「飞书是做成一个连接器,左下角点击连接应用的时候可以连接」, so the row is where Feishu is configured: one row, two modes, one field per credential. - credentials[] / modes: the webhook mode keeps phase 1 (one URL, outbound only, one test POST on connect); app mode takes App ID and App Secret, proves the pair with one tenant token (discarded), and keeps the id in the record and the secret only in secrets.json. - kind: 'channel': reconcile() opens the long connection instead of mounting an MCP client, Disconnect closes it, and the socket's own lifecycle is the row's status. - Choose Bots appears for app mode (off by default per Bot, never for a shared Bot); a webhook-only row still has no switch, as before. - 允许的飞书用户: nobody by default, a refused sender is one bounded row with Allow, and an open_id can be pasted. App mode answers back into the same chat as the app. - feishu joins RESERVED_NAMES. Fixes #1292 Refs #1266
Birfy
force-pushed
the
dev/1266-feishu-connector-row
branch
from
October 27, 2026 01:59
ceed70a to
7e95c2c
Compare
Collaborator
Author
|
Verdict: PASS at 7e95c2c Ran (tip tree
What I checked:
Non-blocking notes:
|
This was referenced Oct 27, 2026
HugoXK
pushed a commit
to HugoXK/FlyBot
that referenced
this pull request
Oct 27, 2026
…inkFlowLab#1315) (ThinkFlowLab#1323) Written-by: Dev Fixes ThinkFlowLab#1315 Owner: Dev Reviewer: Reviewer Tip SHA: dc7d719 Branch: `dev/1315-core-host-docs` (from `main 2c02cd1`, the merge of ThinkFlowLab#1307 — `core/package.json` = 0.2.11) ## Reproduce `docs/subsystems/core-host.md` is the page `arch-docs` treats as architecture truth (AGENTS.md:258), and two paragraphs on it still describe the Feishu tree as it was before the connector row (ThinkFlowLab#1303). On `main 2c02cd1`: ``` $ grep -nE "kind: 'sender'|feishu\.enabled|FEISHU_APP_ID" docs/subsystems/core-host.md 405: … Its definition carries `kind: 'sender'`, which the view reports and the client reads instead of hard-coding an id. … 407: … off unless `feishu.enabled` is true (an off connector dials nothing) … `FEISHU_APP_ID` + `FEISHU_APP_SECRET` are read as one credential through the same `rt.secretValue` … ``` Three claims, each false on this tree: the definition is `kind: 'channel'` (`core/src/host/connectors.js:501`); nothing reads a `feishu.enabled` key, the gate is `enabled: () => feishuRecord()?.mode === 'app' && appOf() !== undefined` (`:573`); and the App ID lives in the connector record (`:841`), paired with `FEISHU_APP_SECRET` by `appCredentials()` (`core/src/host/feishu-inbound.js:57-63`). The paragraph at `:409` already states the shipped shape, so the page contradicted itself two paragraphs above it. ## What changed One file, `docs/subsystems/core-host.md`, `+2/−2` — two paragraph lines. No code, no test, no generated file, no changelog fragment. - `:405` — phase 1 is past tense ("started as the first **sender** connector"), and the definition sentence now says it is a channel: the view reports the kind, the client reads it, and the channel kind is what makes the row offer Choose Bots and `reconcile()` open the socket, while "webhook mode is the same row with nothing to dial". The half that is still true stays: `connector-access.js`'s `CONNECTOR_IDS` does not list `feishu`. - `:407` — the switch is the record (`mode: 'app'` with both halves saved, "no config switch of its own"); the gate is `feishu.ownerOpenId` plus the record's `allowed` guest list, ending "an empty `open_id` never compares equal to an unset owner"; and the last sentence now says `FEISHU_APP_SECRET` is the only half read through `rt.secretValue`, with the App ID in the connector record. - Everything else in both paragraphs is untouched: the webhook's connect-then-prove, the 512-id ring entering only after `deliver` returned, one socket per connector, `rt.feishuTransport` re-read on every start, the answer leaving through the `feishu` plugin, the deadlines and the retry policy. ## What I ran In a local tree at the pushed tip's exact tree (`git rev-parse HEAD^{tree}` = the tip's tree; the changed blob is `c97e977fdc1f3dad84264828e74b2b81a618a558`): ``` $ node core/scripts/check-arch-docs.mjs arch-docs: OK (1 changed file(s)) $ node .github/scripts/check-mojibake.mjs --check mojibake: ok — 0 occurrences of U+FFFD in 1140 text file(s) (22 skipped as binary …) $ git diff --stat origin/main...HEAD docs/subsystems/core-host.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) $ git diff --name-only origin/main...HEAD docs/subsystems/core-host.md ``` ## Red without the source change Take the page back to `main`'s two lines and the same grep returns the two hits above (three token occurrences: `kind: 'sender'`, `feishu.enabled`, `FEISHU_APP_ID`); on this tip it returns nothing and exits 1. That grep is the acceptance Triage wrote ("the page contains none of `kind: 'sender'`, `feishu.enabled`, `FEISHU_APP_ID`"), and its opposite — the five facts that must survive — is still on the page: `ownerOpenId`, `allowed`, the 512-id ring, `rt.feishuTransport`, the `feishu` plugin. ## Not run - The core suite and the three plugin suites: this diff touches no code, no test and no generated file, so the tip's `tests-ok` and `Node 24` runs are the whole-suite evidence; running them locally would re-measure an unchanged tree. - `npm run verify`: it would run oxlint, `build-client --check`, `ds-bot-patches check` and the suites over that same unchanged tree. ## Risk Docs only. `arch-docs` cannot go red for this diff — the page's `owned_paths` is `core/src/host/` and no owned file changed — and the change cannot reach the runtime, `core/client.js`, the `ds-bot` mirrors or a release. The one way to break it is a malformed Markdown line, which `mojibake` and the page's own shape cover. ## Checkpoint <!-- flybot:checkpoint --> ```json { "pr": 1323, "claim": "Dev", "issue": 1315, "summary": "Two paragraphs of docs/subsystems/core-host.md still described the pre-row Feishu: the sender kind, a feishu.enabled switch, and the app pair read as one secret. Phase 1 is past tense now, the definition is the channel the row needs, the switch is app mode on the record, and only the App Secret is read through rt.secretValue. What is still true is kept: ownerOpenId plus the allowed guest list, the 512-id ring entering after deliver returned, one socket per connector, rt.feishuTransport re-read at every start, and the feishu plugin exit.", "files": [ "docs/subsystems/core-host.md" ], "checks": { "ran": "node core/scripts/check-arch-docs.mjs → OK (1 changed file); node .github/scripts/check-mojibake.mjs --check → ok, 0 occurrences in 1140 files; git diff --stat origin/main...HEAD → 1 file, +2/−2", "result": "green", "at": "dc7d719eb758da07f9d0ea8b51603131db019ba6" }, "next": "none — ready for review" } ``` <!-- /flybot:checkpoint -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Written-by: Dev
Fixes #1292
Refs #1266
Owner: Dev
Reviewer: Reviewer
Tip SHA: 7e95c2c
Branch:
dev/1266-feishu-connector-row(rebased ontoad0790aa, the main head after #1309 and #1289 landed; nothing but the base changed)Reproduce
On main the Feishu row in Connect apps takes one custom-bot webhook and nothing else, so the owner's sentence 「飞书是做成一个连接器,左下角点击连接应用的时候可以连接」 is half true: sending works, receiving does not, and the row cannot be the place either is set up.
core/src/host/connectors.jsdefines Feishu askind: 'sender'with onesecret(FEISHU_WEBHOOK); the client renders one masked input and hides Choose Bots for senders (core/src/client/connectors.js:158), andconnectedConnectorsfilters senders out (core/src/client/connector-bots.js:17) — correct for a webhook, wrong for a channel.FEISHU_APP_ID/FEISHU_APP_SECRET),config.feishu.enabledhas no write path from any UI, andfeishu.ownerOpenIdis a config-file edit. On this base:grep -n "credentials" core/src/client/connectors.js— no match;grep -n "kind: 'channel'" core/src/host/connectors.js— no match;RESERVED_NAMES = [GITHUB, COMPOSIO].What changed
One commit, 26 files: 15 real ones and 11 generated (
core/client.js, fourpatches/ds-bot/*.patch—0001,0003,0005,0008— and sixpatches/ds-bot/files/*mirrors).core/src/host/connectors.js— the definition declarescredentialsinstead of the singularsecret(FEISHU_MODES: a custom-bot webhook with one masked URL, outbound only exactly as phase 1, or a self-built app with App ID + App Secret), andkind: 'channel'makesreconcile()open the long connection instead of mounting an MCP client. App mode proves the pair with onePOST /open-apis/auth/v3/tenant_access_token/internalthat is discarded;app_idstays in the record (it is not a secret),app_secretgoes only tosecrets.json, and a half-saved pair ismissingand dials nothing. The record also carries the guest list (allowed: [{ openId, name? }], nobody by default) changed through the newconnector-allow/connector-blockendpoints.feishujoinsRESERVED_NAMES.core/src/host/feishu-inbound.js— the owner gate reads that record instead ofconfig.feishu.ownerOpenId, reports the refused senders the row lists, and app mode answers back into the same Feishu chat as the app through phase 1's sender.core/src/host/authz.js,core/src/host/channel.js— the two new endpoints areSHARED_BLOCKEDand routed like the rest ofconnector-*.core/src/client/connectors.js— one row, two modes: one field per declared credential (App ID not masked), a status line naming the live mode, Choose Bots for a channel, and 允许的飞书用户 with Allow / Block plus a paste-an-open_idfield. A webhook-only row keeps today's shape and shows no switch.core/src/client/connector-bots.js,core/src/client/index.js,core/src/client/i18n.js— a channel reaches the per-Bot switch;connectorConnectpasses an object body (mode + credentials) while every other row keeps its single token; zh strings for the new copy.core/test/feishu-channel.test.js(new, 8 tests) plus additions incore/test/feishu.test.js,core/test/feishu-inbound.test.js,core/test/connectors.test.js.docs/subsystems/core-host.md,docs/subsystems/core-client.md— one paragraph each on the connector-row half;changelog.d/1266-feishu-connector-row.changed.mdis new.changelog.d/1266-feishu-inbound.added.mdwas consumed intoCHANGELOG.mdby the v0.2.10 release, so this branch no longer touches it.What I ran
At the pushed tip
7e95c2c52df70c208a35dddfa732b674b22dd884(basead0790aa):taskset -c 0-2: this box has 192 cores and a cgrouppids.maxof 512, so node's default concurrency dies withpthread_create: Resource temporarily unavailable; pinning it is what makes the run finish, and every number above is from that run.Red first
Three knives cut on this tip's own tree, each restored with
cpand confirmed bymd5sum, then re-run green (72/72):core/src/host/connectors.js:if (def.kind === 'channel') return reconcileChannel(...)→'channel-cut', so a channel falls through to the MCP mount.test/feishu-channel.test.js: 8 tests → 4 fail / 4 pass — connecting the app opens the socket; a stranger starts no turn and Allow makes the next message land; Disconnect closes the socket; a half-saved pair dials nothing.core/src/client/connectors.js: the app mode's fields ((chosen?.credentials ?? [])→[]).test/feishu.test.js: 9 tests → 2 fail / 7 pass — the row offers both ways to connect; app mode asks for the App ID and the App Secret.core/src/host/connectors.js:RESERVED_NAMESwithoutFEISHU.test/feishu-channel.test.js: 8 tests → 1 fail / 7 pass —feishuis a name a saved capability may not take.Knife 1 taken over
test/feishu-channel.test.js+test/connectors.test.jstogether (the two files that drive the socket): 41 tests → 7 fail / 34 pass.The two reds of the first push
The first push of this branch had two real CI reds, both fixed in this tip and neither in the product:
package(run 40429443765, job121943265811) —node scripts/ds-bot-patches.mjs checksaidcore/test/feishu-channel.test.js: committed but not produced by the patches. A brand-new file is carried inpatches/ds-bot/files/(whole-file mirror), and that mirror was still untracked when the first commit was made, so the pushed tree carried the test but not the patch input.generatenow writes it,checkreads threeoklines, andpatches/ds-bot/files/test/feishu-channel.test.jsis in this commit.Node 24(job 121943265765) —test/connectors.test.js:442“an adapter installed after the connectors module is the one the next Try again dials (Feishu connector, phase 2: a message in Feishu starts a turn here (inbound over the long connection) #1266)” waited 2 s for the boot dial to fail. On the runner the SDK is installed, so the boot socket sits inconnecting(its own 15 s deadline) instead of failing, and the assertion timed out. The probe now waits for the row to leaveoff(which it does as soon as a dial starts) and asserts the state is notoffplus no stub dialled — same defect pinned, no clock in it.connectors.test.js33/33, three runs in a row.Not run (and why)
npm run verifyas one command: the default concurrency is what this box cannot run (see above); the same steps were run one at a time and the numbers are above.FEISHU_APP_ID/FEISHU_APP_SECRETexists on this box and nothing dialled out. The socket is injected asrt.feishuTransport, so every test drives the real policy with a fake transport.dev/live harness): no live DSH on this box.Checkpoint
{ "pr": 1303, "claim": "Dev", "issue": 1292, "summary": "The Feishu connector is now set up on its own row in Connect apps: one row, two modes — a custom-bot webhook (outbound only, as before) and a self-built app whose App ID and App Secret are form fields. App mode proves the pair with one tenant token that is thrown away, opens the long connection from the row, answers back into the same Feishu chat, and offers Choose Bots plus an allowed-senders list that starts empty. Disconnect closes the socket and clears both credentials; a half-saved pair dials nothing, and feishu is a reserved name.", "files": ["core/src/host/connectors.js", "core/src/host/feishu-inbound.js", "core/src/client/connectors.js", "core/src/client/connector-bots.js", "core/src/client/index.js", "core/test/feishu-channel.test.js", "core/test/feishu.test.js", "core/test/connectors.test.js", "docs/subsystems/core-host.md", "docs/subsystems/core-client.md", "changelog.d/1266-feishu-connector-row.changed.md", "core/client.js"], "checks": { "ran": "core 1789 tests (1785 pass; the 4 reds are red on base ad0790aa too; inbox 27/27); feishu group 72/72; social 525, tree 125, timeline 125, .github 502, interop 37, live-e2e 24; build-client --check, patches check 3 ok, arch-docs OK (26 files), mojibake ok, lint 0 errors", "result": "green", "at": "7e95c2c52df70c208a35dddfa732b674b22dd884" }, "next": "none — ready for review" }