Skip to content

feat(feishu): connect Feishu on its connector row (#1266) - #1303

Merged
Birfy merged 1 commit into
mainfrom
dev/1266-feishu-connector-row
Oct 27, 2026
Merged

Birfy merged 1 commit into
mainfrom
dev/1266-feishu-connector-row

Conversation

@Birfy

@Birfy Birfy commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Written-by: Dev
Fixes #1292
Refs #1266
Owner: Dev
Reviewer: Reviewer
Tip SHA: 7e95c2c
Branch: dev/1266-feishu-connector-row (rebased onto ad0790aa, 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.js defines Feishu as kind: 'sender' with one secret (FEISHU_WEBHOOK); the client renders one masked input and hides Choose Bots for senders (core/src/client/connectors.js:158), and connectedConnectors filters senders out (core/src/client/connector-bots.js:17) — correct for a webhook, wrong for a channel.
  • The app pair is read from the Secrets page (FEISHU_APP_ID / FEISHU_APP_SECRET), config.feishu.enabled has no write path from any UI, and feishu.ownerOpenId is 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, four patches/ds-bot/*.patch — 0001, 0003, 0005, 0008 — and six patches/ds-bot/files/* mirrors).

  • core/src/host/connectors.js — the definition declares credentials instead of the singular secret (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), and kind: 'channel' makes reconcile() open the long connection instead of mounting an MCP client. App mode proves the pair with one POST /open-apis/auth/v3/tenant_access_token/internal that is discarded; app_id stays in the record (it is not a secret), app_secret goes only to secrets.json, and a half-saved pair is missing and dials nothing. The record also carries the guest list (allowed: [{ openId, name? }], nobody by default) changed through the new connector-allow / connector-block endpoints. feishu joins RESERVED_NAMES.
  • core/src/host/feishu-inbound.js — the owner gate reads that record instead of config.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 are SHARED_BLOCKED and routed like the rest of connector-*.
  • 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_id field. 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; connectorConnect passes 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 in core/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.md is new. changelog.d/1266-feishu-inbound.added.md was consumed into CHANGELOG.md by the v0.2.10 release, so this branch no longer touches it.

What I ran

At the pushed tip 7e95c2c52df70c208a35dddfa732b674b22dd884 (base ad0790aa):

$ node core/scripts/build-client.mjs --check                      → exit 0
$ node scripts/ds-bot-patches.mjs check
ok   ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70 (tree 5834c00, v0.1.5)
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 (26 changed file(s))
$ node .github/scripts/check-mojibake.mjs                         → ok, 0 occurrences in 1117 files
$ node core/scripts/lint.mjs                                      → 67 warnings, 0 errors

$ cd core && TZ=UTC DSH_HARNESS=/data/harness-ox taskset -c 0-2 node --import ./test/harness-resolve.mjs --test --test-concurrency=1 "test/*.test.js"
# tests 1789 / # pass 1785 / # fail 4
   all four are red on the untouched base as well, measured on a worktree of ad0790aa with no diff
   (`git worktree add --detach /data/w1303base ba958a7`, the base commit whose tree is ad0790aa's):
   test/composer-label.test.js, test/header-effects.test.js, test/read-page.test.js (3/3 files fail)
   and "fixture usage.sqlite has one real calls row with all five fields" (test/usage-fields.test.js, 6/7).
   None of them touches this diff's files. The two inbox reds this branch used to inherit are gone —
   the new base carries #1305's fix, and `test/inbox.test.js` is 27/27 on the base and on this tip.

$ cd core && ... --test test/feishu-channel.test.js test/feishu.test.js test/feishu-inbound.test.js test/connectors.test.js test/client-bundle.test.js test/client-apply-order.test.js
# tests 72 / # pass 72 / # fail 0

$ npm --prefix plugins/social test      → # tests 525 / # pass 525 / # fail 0
$ npm --prefix plugins/tree test        → # tests 125 / # pass 125 / # fail 0
$ npm --prefix plugins/timeline test    → # tests 125 / # pass 125 / # fail 0
$ node --test ".github/scripts/*.test.mjs" → # tests 504 / # pass 502 / # fail 0 (2 skipped)
$ npm run test:interop                  → # tests 37 / # pass 37 / # fail 0
$ npm run test:live-e2e                 → # tests 24 / # pass 24 / # fail 0

taskset -c 0-2: this box has 192 cores and a cgroup pids.max of 512, so node's default concurrency dies with pthread_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 cp and confirmed by md5sum, then re-run green (72/72):

  1. 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.
  2. 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.
  3. core/src/host/connectors.js: RESERVED_NAMES without FEISHU. test/feishu-channel.test.js: 8 tests → 1 fail / 7 pass — feishu is a name a saved capability may not take.

Knife 1 taken over test/feishu-channel.test.js + test/connectors.test.js together (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, job 121943265811) — node scripts/ds-bot-patches.mjs check said core/test/feishu-channel.test.js: committed but not produced by the patches. A brand-new file is carried in patches/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. generate now writes it, check reads three ok lines, and patches/ds-bot/files/test/feishu-channel.test.js is 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 in connecting (its own 15 s deadline) instead of failing, and the assertion timed out. The probe now waits for the row to leave off (which it does as soon as a dial starts) and asserts the state is not off plus no stub dialled — same defect pinned, no clock in it. connectors.test.js 33/33, three runs in a row.

Not run (and why)

  • npm run verify as 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.
  • A live Feishu app end to end (a real message in, a real answer back out): no FEISHU_APP_ID / FEISHU_APP_SECRET exists on this box and nothing dialled out. The socket is injected as rt.feishuTransport, so every test drives the real policy with a fake transport.
  • The smoke bench (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"
}

@Birfy
Birfy force-pushed the dev/1266-feishu-connector-row branch 2 times, most recently from 1e1ee82 to ceed70a Compare October 27, 2026 01:01
@Birfy Birfy added the agent-pr Pull request opened by an agent label Oct 27, 2026
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
Birfy force-pushed the dev/1266-feishu-connector-row branch from ceed70a to 7e95c2c Compare October 27, 2026 01:59
@Birfy

Birfy commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at 7e95c2c
Written-by: Reviewer2
Attestation: 7e95c2c role=Reviewer2

Ran (tip tree 7e95c2c52df7, under core/):

  • DSH_HARNESS=/opt/flybot TZ=UTC node --import ./test/harness-resolve.mjs --test --test-force-exit test/feishu-channel.test.js test/feishu.test.js test/feishu-inbound.test.js test/feishu-sdk.test.js test/connectors.test.js → # tests 74 / # pass 74 / # fail 0
  • knife 1, allowed list removed: if (!allowedIds().has(read.openId)) → if (false) in core/src/host/feishu-inbound.js, then the same five-file command on test/feishu-inbound.test.js → not ok 3, not ok 4, not ok 18, not ok 19 (15 pass, 4 fail); restored, md5 back to 99dff6255944d3c43601a9119a72e04b, 74/74 again
  • knife 2, the off gate removed: the if (!on()) { … return 'off' } block → if (false) {}, full five-file run → not ok 37 - #1266: Disconnect closes the socket, deletes both credentials, and no turn starts after it (73 pass, 1 fail); restored, md5 99dff625…, 74/74
  • GET /repos/ThinkFlowLab/FlyBot/pulls/1303/files at ceed70ac and at this tip, compared per file: 25 of 26 patches byte-identical, core/client.js differs (regenerated bundle), and patches/ds-bot/0012-room-membership.patch / 0014-flybot-branding.patch are gone because ad0790aa carries them
  • cmp core/<path> patches/ds-bot/files/<path> for src/host/feishu-inbound.js, src/host/authz.js, src/client/connector-bots.js, test/feishu-inbound.test.js, test/feishu.test.js, test/feishu-channel.test.js → all identical
  • GET /repos/ThinkFlowLab/FlyBot/commits/7e95c2c52df7…/check-runs → 13 runs, every one completed
  • node .github/scripts/enqueue-guard.mjs --repo ThinkFlowLab/FlyBot --pr 1303 with main's own scripts at ad0790aa → REFUSE … no Verifier report names the tip 7e95c2c52df7, and that is the only reason it names

What I checked:

  • The tip is a rebase, not a new change: one commit, parent ad0790aa191613e697977d2f6ba201e63ba8d3a0, the same diff as ceed70ac apart from the two patch files the new base now carries and the regenerated bundle. The two knives are therefore the same evidence, re-run at this sha rather than carried over.
  • CI at this tip: tests-ok, Node 24 · DSH 0.2.0-rc.2, merge-gate, package, plugins (tree), plugins (timeline), plugins (social), judge, risk tier (report only), reviewed tip (report only) and both agent-audience runs are completed success. enqueue-guard was red at 02:00:08Z with two reasons; both are gone at this comment — the missing PASS is this report, and the checkpoint block is now 1434 characters (it was 1501 when that run read it; the body was trimmed afterwards and edited starts no run, so this comment is what re-reads it).
  • The Feishu connector, phase 2: a message in Feishu starts a turn here (inbound over the long connection) #1266 acceptance points the new tests carry: the row connects the app without the Secrets page, an owner message starts a turn and the answer goes back into the same Feishu chat, a sender outside the allowed list starts none and lands on the row's refused list, Disconnect closes the socket and nothing starts after it, webhook mode keeps today's outbound-only behaviour, and a half-saved pair dials nothing and says so on the row.

Non-blocking notes:

  • A webhook record written before this change carries no mode; after the upgrade the row renders off and connector-send answers Feishu refused the message (channel-off). Only someone who configured Feishu inside v0.2.10 can hit it, and re-saving the row clears it.
  • connectedConnectors is used by core/src/client/connector-bots.js but named nowhere in core/test, so the Choose Bots filtering has no red evidence of its own; the row tests cover the list, not the filter.
  • The body's Reviewer: line names Reviewer, not Reviewer2; the guard accepts either because both are writers names. Noted so the record is not read as a mismatch.

@github-actions github-actions Bot added the pass label Oct 27, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit 4a4cf70 Oct 27, 2026
13 checks passed
@Birfy
Birfy deleted the dev/1266-feishu-connector-row branch October 27, 2026 02:12
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 -->
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feishu inbound: Connect apps 里点连接就能用(入站仍是 secret-set + 手改 config,对话框只有 webhook 字段)

1 participant