Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ab7c545
feat(feishu): add updateable turn state cards
May 30, 2026
0e2e5e0
feat(feishu): debounce turn state card patches
May 30, 2026
94fb738
feat(feishu): classify state card update failures
May 30, 2026
cb1a6eb
feat(feishu): project progress into state cards
May 30, 2026
0e18378
feat(feishu): project uploaded artifacts into state cards
May 30, 2026
f09dce5
test(rfc): add shared self-regression runner
May 30, 2026
6239bca
test(slack): harden self-regression drive
May 30, 2026
29a9329
test(rfc): harden dashboard smoke evidence
May 30, 2026
d7a1512
test(admin): expose open inbound parity
May 30, 2026
ff7ac11
docs(rfc): update feishu parity progress
May 30, 2026
8db0ee9
test(feishu): require observe action evidence
May 30, 2026
6601bc0
test(rfc): add completion evidence audit
May 30, 2026
4c01c79
test(feishu): add observe self-regression evidence
May 30, 2026
503b881
test(rfc): accept feishu observe evidence
May 30, 2026
a5c8eb8
fix(codex): preserve app-server auth symlink
May 30, 2026
5a154d4
fix(feishu): use chat routes in Codex prompts
pengx17 May 30, 2026
7931344
test(slack): require distinct drive user token
May 30, 2026
21c1e98
fix(codex): isolate dual-platform app-server runtime
May 30, 2026
5085e9a
test(rfc): add slack drive completion evidence
May 30, 2026
6079c60
fix(feishu): align session replies with Slack
May 31, 2026
78996cd
fix(feishu): render session links as cards
May 31, 2026
0b9ca8e
fix(feishu): parse markdown for rich posts
May 31, 2026
3266a4e
fix(feishu): make post-state silent
May 31, 2026
6119fcc
Merge remote-tracking branch 'workwork/main' into codex/feishu-rfc-du…
May 31, 2026
02a8130
test(feishu): cover silent state follow-ups
May 31, 2026
a4f1389
fix(feishu): record visible turn states
May 31, 2026
9b1946c
fix(audit): accept saved Feishu preflight evidence
May 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 34 additions & 30 deletions docs/rfcs/0001-slack-feishu-dual-platform/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ This file contains the implementation-facing architecture detail for [RFC 0001](

## One-Screen Summary

| Decision area | Current contract |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Product target | China Feishu group support beside existing Slack; global Lark and Feishu private chats stay out of scope. |
| Runtime shape | Slack and Feishu share one broker process through platform-aware adapters, session coordinates, and `/chat/*` APIs. |
| Session rule | New session identity includes `platform`; legacy Slack keys remain readable and are never reinterpreted as Feishu. |
| Feishu routing | Group @bot starts/resumes; non-@ group follow-up needs an active session plus verified all-message delivery or bounded recovery; private/self/bot/app events do not create sessions. |
| Content rule | Text, rich `post`, interactive cards, images, and files preserve enough structure/raw references for current behavior and later UX polish. |
| Release blocker | Slack compatibility and platform isolation must remain provable while Feishu is added. |
| Decision area | Current contract |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Product target | China Feishu group support beside existing Slack; global Lark and Feishu private chats stay out of scope. |
| Runtime shape | Slack and Feishu share one broker process through platform-aware adapters, session coordinates, and `/chat/*` APIs. |
| Session rule | New session identity includes `platform`; legacy Slack keys remain readable and are never reinterpreted as Feishu. |
| Feishu routing | Group @bot starts/resumes a Feishu topic session; replies in that topic are the Slack-thread equivalent. Rootless non-@ group follow-up needs an active session plus verified all-message delivery or bounded recovery; private/self/bot/app events do not create sessions. |
| Content rule | Text, rich `post`, interactive cards, images, and files preserve enough structure/raw references for current behavior and later UX polish. |
| Release blocker | Slack compatibility and platform isolation must remain provable while Feishu is added. |

## Read Layers

Expand All @@ -33,7 +33,7 @@ Feishu adds different platform constraints:

- group messages and private chats have different event shapes and permissions;
- all-group-message delivery is permission-gated;
- message trees are not identical to Slack threads;
- Feishu topics are the product equivalent of Slack threads when `root_id` / `thread_id` is present, but rootless group messages and permissions still differ;
- rich text, cards, files, and callbacks are separate payload families;
- long connection delivery requires fast ack and queued work;
- real tenant setup is necessary to prove sensitive permissions.
Expand Down Expand Up @@ -82,15 +82,15 @@ The design must add Feishu without turning Slack into a compatibility afterthoug

## Terminology

| Term | Meaning |
| ---------------- | ---------------------------------------------------------------------------------------------------------- |
| `platform` | `slack` or `feishu`. Required on new shared contracts. |
| `conversationId` | Slack channel ID or Feishu chat ID. |
| `rootMessageId` | Platform root for a session: Slack `thread_ts` or Feishu root/message/thread coordinate chosen by adapter. |
| `messageId` | Platform message identifier used for idempotency. |
| `sessionKey` | Canonical key derived from `platform`, `conversationId`, and `rootMessageId`. |
| `all` mode | Feishu mode expecting all group messages through `im:message.group_msg`. |
| `at_only` mode | Degraded Feishu mode assuming only @bot delivery. |
| Term | Meaning |
| ---------------- | ------------------------------------------------------------------------------------------------------------- |
| `platform` | `slack` or `feishu`. Required on new shared contracts. |
| `conversationId` | Slack channel ID or Feishu chat ID. |
| `rootMessageId` | Platform root for a session: Slack `thread_ts` or the Feishu topic root/message coordinate chosen by adapter. |
| `messageId` | Platform message identifier used for idempotency. |
| `sessionKey` | Canonical key derived from `platform`, `conversationId`, and `rootMessageId`. |
| `all` mode | Feishu mode expecting all group messages through `im:message.group_msg`. |
| `at_only` mode | Degraded Feishu mode assuming only @bot delivery. |

## Target Architecture

Expand Down Expand Up @@ -172,21 +172,23 @@ Explicitly unsupported in MVP:

Inbound Feishu events should normalize into one of these routes:

| Event | Route | Session behavior |
| -------------------------------------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| group @bot text | `accepted_start_or_resume` | Create or resume session |
| group non-@ text in `all` mode with active session | `accepted_followup` | Queue or steer into active turn; if no Feishu root/thread coordinate matches, use the latest active session in the same group |
| group non-@ text without active session | `ignored_no_active_session` | No session |
| private chat | `ignored_private_chat` | No session |
| bot/app/self message | `ignored_self` | No session |
| duplicate `message_id` | `deduped` | No duplicate turn/reply |
| rich `post` | `accepted_rich` when group route matches | Preserve raw payload and readable summary |
| interactive card callback | `accepted_card_callback` | Ack quickly, enqueue session action |
| Event | Route | Session behavior |
| --------------------------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------- |
| group @bot text | `accepted_start_or_resume` | Create or resume session |
| group reply in the same Feishu topic | `accepted_followup` | Resume the same session; if idle, start a new turn, and if active, steer into the active turn |
| rootless group non-@ text in `all` mode | `accepted_followup` | Queue or steer only when a latest active session exists in the same group |
| group non-@ text without active session | `ignored_no_active_session` | No session |
| private chat | `ignored_private_chat` | No session |
| bot/app/self message | `ignored_self` | No session |
| duplicate `message_id` | `deduped` | No duplicate turn/reply |
| rich `post` | `accepted_rich` when group route matches | Preserve raw payload and readable summary |
| interactive card callback | `accepted_card_callback` | Ack quickly, enqueue session action |

Rules:

- [x] Session creation requires explicit group @bot.
- [x] Non-@ follow-up requires an existing active session and all-message delivery or recovered history; rootless group follow-ups in `all` mode attach to the latest active session in the same group.
- [x] Feishu topic replies use the topic root as the session root, matching Slack thread lifecycle semantics.
- [x] Rootless non-@ follow-up requires an existing active session and all-message delivery or recovered history; rootless group follow-ups in `all` mode attach to the latest active session in the same group.
- [x] All callbacks must ack before Codex work runs.
- [x] Dedupe must prefer Feishu `message_id` for message events.

Expand Down Expand Up @@ -277,6 +279,8 @@ Platform-aware `/jobs/register` coordinates share the same `platform` validation

`GET /chat/thread-history` also accepts `beforeCursor`/`before_cursor` for platform pagination. Feishu maps it to the Open Platform `page_token`, returns `hasMore`, and exposes `nextCursor` when Feishu reports another bounded page. Explicit history `limit` values must be positive integers before broker delegation and are clamped by the target platform max history limit. History `format` values must be `json` or `text`; invalid values return 400 `invalid_format` before broker delegation.

`POST /chat/post-state` is a silent lifecycle API, matching `/slack/post-state`: it records `final`, `block`, or `wait` state for broker bookkeeping and must not create a visible Feishu `Codex finished` card. Visible updates must go through `/chat/post-message`; Feishu progress messages may update the active turn-state card, but terminal silent state must not masquerade as a user-facing reply.

Compatibility:

- Existing `/slack/*` routes remain.
Expand All @@ -290,7 +294,7 @@ Runtime instructions must:
- [x] Say which platform a session came from.
- [x] State that Feishu private chats are unsupported.
- [x] Explain that non-@ follow-up reliability depends on all-group-message permission.
- [x] Tell Codex not to assume Slack thread semantics for Feishu.
- [x] Tell Codex that a Feishu topic is the Slack-thread equivalent when `root_message_id` / `platform_thread_id` are present, while rootless group messages remain a Feishu-specific degraded case.
- [x] Use rich/card formats only through broker-supported `/chat/post-message` payloads.
- [x] Keep Feishu prompts on platform-aware `/chat/*` APIs and platform-aware `/jobs/register` coordinates; Slack legacy job coordinate aliases remain compatible.
- [x] Show Slack legacy coordinate aliases only in Slack prompts; Feishu prompts use `conversation_id` and `root_message_id`.
Expand Down
2 changes: 1 addition & 1 deletion docs/rfcs/0001-slack-feishu-dual-platform/review-gates.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ Do not mark Feishu support complete until:

## Completion Evidence Ledger

Use this ledger to read completion evidence without losing the split between local proof and real tenant proof. The middle column is what local code/docs/tests prove; the right column is the real tenant evidence required before an audit box can be checked. `pnpm rfc:feishu-audit` reports both sides, while `pnpm rfc:feishu-audit:local` remains only a local gate. In this PR, local gates and saved real-tenant evidence pass; future drift should be treated as incomplete until both sides pass again.
Use this ledger to read completion evidence without losing the split between local proof and real tenant proof. The middle column is what local code/docs/tests prove; the right column is the real tenant evidence required before an audit box can be checked. `pnpm rfc:feishu-audit` reports local plus saved real-tenant evidence, while `pnpm rfc:feishu-audit:local` remains only a local gate. Final acceptance must also run `pnpm rfc:feishu-completion-audit -- --json`; that gate stays red until Slack self-regression drive, Feishu self-regression observe, and real Codex coding smoke bundles are all present. In this PR, local gates and saved real-tenant evidence pass; future drift should be treated as incomplete until both sides pass again.

| Area | Local evidence now | Real tenant gate still required |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
Expand Down
Loading
Loading