feat(transcript): public conversation-history detail page with Feishu OAuth (backend)#310
Open
uestney wants to merge 1 commit into
Open
feat(transcript): public conversation-history detail page with Feishu OAuth (backend)#310uestney wants to merge 1 commit into
uestney wants to merge 1 commit into
Conversation
… OAuth Add an opt-in `📜 查看完整对话` link to every Feishu card that opens a public read-only React page showing the full turn history (assistant text + tool calls + tool results), gated by Feishu OAuth + open_id whitelist. Why: - Feishu cards are "update-in-place" so intermediate tool calls and thinking are overwritten by later progress — users see only the final text. The new page replays the complete jsonl transcript. - OAuth + whitelist (per-bot `transcriptAllowOpenIds`) prevents random link sharing from exposing conversations. - Opt-in via `publicBaseUrl` in `bots.json`: bots without it render cards exactly as before (no link, no exposure). Backend pieces: - `src/api/routes/transcript-routes.ts` — three endpoints: `/api/auth/feishu/login`, `/api/auth/feishu/callback`, `/api/transcript/:chatId?turn=<n|all>`. Whitelist + JWT cookie. - `src/feishu/oauth.ts` — HMAC-signed state, OIDC token exchange, hand-rolled HS256 JWT (no jsonwebtoken dep). `METABOT_SESSION_SECRET` auto-generated to `.env.local` on first start. - `src/session/transcript-reader.ts` — parses jsonl content blocks (text / tool_use / tool_result), groups by turn (= user message boundaries), matches tool_use_id ↔ tool_result. - `src/session/session-registry.ts` — three pure path helpers (`encodeWorkdir`, `projectTranscriptDir`, `sessionJsonlPath`) shared with the reader. - `src/feishu/card-builder-v2.ts` — emits the markdown link when the bot has a `transcriptLink` set on its CardState. - `src/bridge/message-bridge.ts` + `src/engines/claude/stream-processor.ts` — per-chat turn counter feeds the transcript URL into CardState. - `src/config.ts` — typed `publicBaseUrl` + `transcriptAllowOpenIds` on the bot config. Tests: new `tests/oauth.test.ts` + `tests/transcript-reader.test.ts`; extended `tests/card-builder-v2.test.ts` to assert the markdown link. Docs: README.md / README_EN.md / CLAUDE.md / .env.example / bots.example.json all describe the new fields.
5 tasks
This was referenced May 21, 2026
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.
Summary
Add an opt-in public conversation-history detail page accessible from Feishu cards via a
📜 查看完整对话markdown link. Page renders the complete turn history (assistant text + tool calls + tool results) from the Claude jsonl transcript, gated by Feishu OAuth + per-botopen_idwhitelist.This is the backend half. The matching frontend PR (
feat/transcript-page-frontend) ships the ReactTranscriptViewcomponent and route registration inweb/. Either PR can merge in any order — backend cards still render (just with a dead link until the frontend ships); the frontend page shows a friendly error against an old backend.What's in this PR
src/api/routes/transcript-routes.ts):GET /api/auth/feishu/login?return=<url>— kicks off Feishu OAuth (passport.feishu.cn).GET /api/auth/feishu/callback?code=...&state=...— verifies HMAC-signed state, exchanges code foropen_id, signs an HttpOnlymb_sessionJWT cookie (7d), 302 toreturn.GET /api/transcript/:chatId?turn=<n|all>— cookie + whitelist gated; returns parsed transcript JSON.src/feishu/oauth.ts) — HMAC-signed state, OIDC token exchange, hand-rolled HS256 JWT (nojsonwebtokendep).METABOT_SESSION_SECRETauto-generated to.env.localon first start.src/session/transcript-reader.ts) — walks~/.claude/projects/<workdir>/<sessionId>.jsonl, groups messages by turn (= user message boundaries), splitscontent[]into text / tool_use / tool_result, matchestool_use_id↔ next-user-message'stool_resultblock.src/session/session-registry.ts—encodeWorkdir,projectTranscriptDir,sessionJsonlPath(storage-agnostic, no SQLite touched).src/feishu/card-builder-v2.tsemits📜 [查看完整对话历史](<publicBaseUrl>/web/transcript/<chatId>?turn=<n>)after the footer when the bot hastranscriptLinkset.MessageBridgeincrements a chatId → turnIndex map each turn,StreamProcessor.setTranscriptLinkplumbs it into CardState.publicBaseUrl?: stringandtranscriptAllowOpenIds?: string[]on each bot inbots.json. WithoutpublicBaseUrl, the link is not rendered — fully backwards compatible.src/api/http-server.ts—/api/auth/feishu/*(browser hits, no Bearer header) and/api/transcript/*(cookie-authed) bypass the global Bearer check.Whitelist precedence
bots.json→ per-bottranscriptAllowOpenIds: string[](recommended)..env→METABOT_TRANSCRIPT_ALLOW_OPEN_IDS(comma-separated).open_idso the admin can copy/paste it into the whitelist.Test plan
npm run build— green.npm test— 323/323 passing (realtests/dir; the 10 failures in stale_backup_dev/are unrelated local-only files not on this branch).npm run lint— 0 errors (2 pre-existing warnings in unrelated files).127.0.0.1:10016).open_idso admins can self-serve.publicBaseUrl→ card renders exactly as before (no link), no warnings.Not in this PR
TranscriptViewReact component — separate PR.