Repository navigation
MUL-8505: feat(lark): read bounded native context for the current chat task - #9825
jiahao6635 wants to merge 6 commits into
Conversation
|
@jiahao6635 is attempting to deploy a commit to the IndexLabs Team on Vercel. A member of the Team first needs to authorize it. |
multica-eve
left a comment
There was a problem hiding this comment.
This extends the existing multica chat thread flow so Feishu agents can fetch additional, bounded native context for their current conversation/topic when the supplied context is insufficient, while retaining saved-transcript reads and inbound enrichment.
Requesting changes for the two reproducible correctness issues below: rejected tokens remain cached, and native history loses mention identities. Please fix both and add regression coverage.
Validation on commit 462766566: focused race tests across the Lark reader/transport, history handlers, daemon prompts and CLI rendering passed, as did the ByteIdentical check, affected-package go vet, and diff checks. Two additional temporary regression tests failed and reproduced the inline findings. No live Feishu tenant or real model execution was tested.
| decodeErr := json.Unmarshal(body, &envelope) | ||
| // Only documented access refusals on successful/business-error responses | ||
| // establish unavailability. Unknown codes and 5xx remain read failures. | ||
| if decodeErr == nil && envelope.Code != nil && (resp.StatusCode >= 200 && resp.StatusCode < 300 || resp.StatusCode == 400 || resp.StatusCode == 403) && nativeHistoryAccessRefused(*envelope.Code) { | ||
| return NativeHistoryPage{}, ErrNativeHistoryUnavailable | ||
| } | ||
| if resp.StatusCode < 200 || resp.StatusCode >= 300 { | ||
| return NativeHistoryPage{}, errors.New("native history HTTP status " + strconv.Itoa(resp.StatusCode)) | ||
| } | ||
| if decodeErr != nil || envelope.Code == nil || *envelope.Code != 0 { | ||
| return NativeHistoryPage{}, errors.New("native history refused or malformed response") |
There was a problem hiding this comment.
[P2] Evict rejected tokens before returning
When Feishu rejects the cached token with HTTP 400 and code 99991663, this path returns an error without invalidating the token. Subsequent otherwise-authorized native reads reuse that rejected token until the cache expires or another API call invalidates it. This recreates the cache failure addressed by #7618 in the new request path.
I reproduced this with two independent ListNativeHistoryPage calls: the token endpoint returns stale first and fresh on the next token request; the messages endpoint rejects stale and accepts fresh. The current implementation contacts the token endpoint only once, and the second read still returns HTTP 400.
At minimum, invalidate the cached entry on recognized token-error codes before returning, so the next independent read can recover. The policy of not retrying permission refusals can remain intact. Please add a regression for this non-2xx token-error response.
There was a problem hiding this comment.
Fixed in 53a6a8b and cb397e4. Recognized token errors now evict the rejected token, including HTTP 400 responses, but only if it is still the cached token. The failed request is not replayed. Regression tests cover recovery on a later read, concurrent refresh, app isolation, and permission-refusal behavior.
| body := flattenContent(m.MessageType, m.Content) | ||
| if body == "" { | ||
| continue | ||
| } | ||
| if len(out) >= limit || textBytes+len(body) > maxText { | ||
| limited = true | ||
| continue | ||
| } | ||
| textBytes += len(body) | ||
| out = append(out, channel.HistoryMessage{ID: m.MessageID, Author: "User", AuthorID: m.SenderID, Role: channel.HistoryRoleUser, Text: body, TS: m.CreateTime}) |
There was a problem hiding this comment.
[P2] Resolve mentions using each historical message's metadata
flattenContent only performs structural flattening; it leaves message-local placeholders such as @_user_1 untouched. Although the REST decoder retains m.Mentions, this response neither resolves nor exposes that mapping.
For example, two messages assigning API work to Alice and UI work to Bob can both use @_user_1 with different per-message mention entries. I reproduced the result as @_user_1 owns API and @_user_1 owns UI, with neither person's identity recoverable from the response. An agent summarizing owners cannot reconstruct the assignments.
The existing inboundEnricher.flattenMessage already resolves these correctly via resolveMentions(raw, restMentionsToEvent(m.Mentions), "", ""). Reuse that resolution stage before enforcing the flattened-text byte budget, and add coverage for a placeholder reused for different people across messages.
There was a problem hiding this comment.
Fixed in 53a6a8b. Native history resolves placeholders using each message’s own mention metadata before the text-budget check, without contact lookups. Tests cover reused placeholders for different people, rich-text mentions, unknown mentions, and expansion beyond the output budget.
What does this PR do?
Feishu chat tasks already have two history capabilities:
multica chat historyreads the transcript saved in Multica, and inbound enrichment automatically supplies recent native context. The missing capability is an on-demand native read for the task's current conversation/topic.Extend the existing
multica chat threadcommand with a bounded, current-scope Feishu reader. Preserve the saved transcript and automatic enrichment paths.The reader returns human text/post context through the task's frozen trigger message. It does not expand arbitrary topics, fetch historical media or provide a complete native archive.
Related Issue
Related #5850. Its description predates the existing transcript and enrichment capabilities; this PR does not claim to implement every part of that request.
#9813 addresses selected context media and deferred preparation. This PR adds no historical media downloads. #9820, #9804 and #9813 are unchanged.
Type of Change
Changes Made
Task scope and generation boundaries
--beforecursors. A topic task can read only its bound topic, with no fallback to the parent group.Bounded native reads
has_morevalue and consistent continuation metadata.The ordinary-group filtering follows the official list contract: a normal group's
chatcontainer can include topic roots, while topic replies require thethreadcontainer. A topic root therefore must not invalidate otherwise valid main-conversation context.Discoverability and failure handling
chat historyreads the saved transcript, whilechat thread --output jsonreads bounded native context only in the current task scope./newor/clearreset anchor outside the 150-source-message scan makes the native read unavailable even when the latest trigger remains visible.Official reference: Get chat history.
No new scopes, credentials, migrations, historical media downloads, deployment changes or paid agent calls are introduced.
How to Test
Verified locally on Linux/amd64 with Go 1.26.9, the checkout's configured local migrated PostgreSQL database and the repository Agent CLI guard.
Initial implementation validation
During the initial implementation, a focused race run passed 28 top-level tests across history handlers, native reader/transport tests and CLI table rendering:
go test -race ./internal/handler ./internal/integrations/lark ./cmd/multica -run 'TestGetChatThread|TestGetChatHistory|TestGetChatChannelHistory|TestChatHistory|TestNativeHistory|TestChatReadTable' -count=1 -vThat run belongs to the initial candidate; it is not presented as a rerun of all those tests on the final revision. Subsequent pagination tightening was followed by another full Lark package race run. Initial vet checks covered Lark, handler, server and CLI packages, with Lark vet repeated after the pagination fix.
The pagination regression first failed because responses missing
has_more, or declaringhas_more=falsewith a nonempty continuation token, were accepted. Requiring explicit, consistent pagination metadata made the regression pass.Two temporary Go overlays left the candidate tree unchanged:
These were expected mutation failures, not failures of the candidate.
Review fixes and final validation
Review identified three gaps: ordinary-group topic roots invalidated a whole page, the actual Feishu prompt did not advertise the native command, and documented access refusals became generic 502 errors.
The new mixed-root, prompt and refusal regressions were run before their fixes and failed. The final implementation also includes a raw REST-shaped mixed-message fixture that verifies topic roots are omitted, ordinary context remains readable, and no thread expansion occurs.
After those fixes:
go test -race ./internal/integrations/lark -count=1passed.go test -race ./internal/handler ./internal/daemon -run 'TestGetChatThread|TestGetChatHistory|TestGetChatChannelHistory|TestChatHistory|TestBuildChatPrompt' -count=1 -vgo vet ./internal/integrations/lark ./internal/handler ./internal/daemon ./cmd/serverpassed.git diff --checkpassed.Coverage includes cross-workspace/installation/chat/topic rejection, missing topic identifiers, forged request IDs, immutable generations, retry input ownership, pending boundaries, retired routes, rebinding, changes during reads, triggers crowded out by new messages, invalid pagination, ordering, cancellation and output budgets.
The final refusal tests also distinguish documented access restrictions from unknown codes, invalid parameters, rate limits and 5xx, and verify one list request without raw upstream text in the returned error. Handler tests verify the safe unavailable notice and retain 502 behavior for unknown read failures.
An early test invocation used the default database and failed fixture setup because it lacked the schema. Validation then used the checkout's configured local database after migration. One intermediate mixed-root assertion also failed on capitalization in the expected notice; the returned messages were already correctly filtered. These intermediate failures were corrected before the passing runs above.
Risks and Verification Limits
chat history./newor/clearcan exceed the scan budget even while its latest trigger is still visible. The prompt and documentation describe this limitation explicitly.Checklist
AI Disclosure
AI tool used: OpenAI Codex
Prompt / approach: Implement the authorized narrow Feishu follow-up: on-demand native history only for the authenticated task's current conversation/topic, preserving existing transcript/enrichment behavior and immutable generation boundaries. Inspected main and related contributions, implemented bounded fail-closed reads, added scope/transport/handler/CLI regressions, reproduced pagination failures before fixing them, and performed two reverse-verification overlays.
A separate automated read-only review then identified ordinary-group topic-root handling, missing prompt discoverability and opaque access refusals. Those findings were checked against the official API contract, reproduced with regression tests, fixed on the same branch and followed by the supplemental checks above. Implementation, review and testing were automated; no independent human review is claimed.
Revision
jiahao6635:feat/lark-current-native-historye8b6a97643ccc35efbcf2a4675c8d088a288ed6e462766566268250fb36441c7a7efacc7086169da