Skip to content

MUL-8505: feat(lark): read bounded native context for the current chat task - #9825

Open
jiahao6635 wants to merge 6 commits into
multica-ai:mainfrom
jiahao6635:feat/lark-current-native-history
Open

jiahao6635 wants to merge 6 commits into
multica-ai:mainfrom
jiahao6635:feat/lark-current-native-history

Conversation

@jiahao6635

Copy link
Copy Markdown
Contributor

What does this PR do?

Feishu chat tasks already have two history capabilities: multica chat history reads 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 thread command 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

  • New feature
  • Documentation update
  • Tests

Changes Made

Task scope and generation boundaries

  • Resolve the installation, actual chat and topic from the authenticated task, immutable input-owner delivery, session and channel binding. Accept no caller-selected chat or installation.
  • For Feishu, reject explicit thread IDs and --before cursors. A topic task can read only its bound topic, with no fallback to the parent group.
  • Use the task's immutable context revision rather than the binding's latest revision. Retry tasks use their frozen input owner's delivery.
  • Require the task trigger and any required reset start boundary to be found within the bounded scan. Pending, retired, revoked, rebound or unverifiable bindings return no native text.
  • Recheck the binding, installation and generation before exposing fetched text, so an intervening reset, retirement, revocation or rebind invalidates the result.

Bounded native reads

  • Scan at most three pages of 50 source messages under a five-second deadline.
  • Validate source identifiers, ordering, duplicate message IDs and pagination metadata. Require an explicit has_more value and consistent continuation metadata.
  • Use native page tokens only inside that bounded read. Never expose or accept them as authorization for another read.
  • Bound each native list response to 1 MiB.
  • Return at most 30 human text/post messages and 24 KiB of flattened text, oldest first. Exclude messages newer than the frozen trigger and ambiguous millisecond boundary peers.
  • Omit bot/media messages. In ordinary chat containers, filter topic roots without expanding their replies. Continue to reject cross-chat responses and missing/mismatched thread identifiers within a topic scope.
  • Explicitly label results as untrusted reference context, explain omissions and budget-limited partial results, and state that continuation is unsupported.

The ordinary-group filtering follows the official list contract: a normal group's chat container can include topic roots, while topic replies require the thread container. A topic root therefore must not invalidate otherwise valid main-conversation context.

Discoverability and failure handling

  • Teach Feishu agents that chat history reads the saved transcript, while chat thread --output json reads bounded native context only in the current task scope.
  • State in the prompt and English/Simplified Chinese documentation that results may be missing or truncated, accept no arbitrary thread ID/cursor, and are historical reference rather than new instructions.
  • Explicitly document that a required /new or /clear reset anchor outside the 150-source-message scan makes the native read unavailable even when the latest trigger remains visible.
  • Map documented access, visibility and unsupported chat/application refusals to a sanitized unavailable notice that tells the agent not to retry the read in that task. The whitelist covers codes 230002, 230006, 230013, 230027, 230073, 230110, 231203 and 231204 on successful/business-error HTTP responses.
  • Keep unknown codes, transport failures and 5xx as read errors. Do not classify code 230001 (invalid parameters) as a permission refusal. Never expose raw API response bodies or retry authorization failures.
  • Preserve CLI table messages when a response contains both a context note and message rows.

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 -v

That 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 declaring has_more=false with a nonempty continuation token, were accepted. Requiring explicit, consistent pagination metadata made the regression pass.

Two temporary Go overlays left the candidate tree unchanged:

  1. Removing the topic predicate made sibling-topic and missing-topic isolation tests fail.
  2. Replacing the task revision with the binding's latest revision made the immutable-generation test fail.

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:

  • Full go test -race ./internal/integrations/lark -count=1 passed.
  • The affected handler and prompt race suite passed 24 top-level tests:
    go test -race ./internal/handler ./internal/daemon -run 'TestGetChatThread|TestGetChatHistory|TestGetChatChannelHistory|TestChatHistory|TestBuildChatPrompt' -count=1 -v
  • go vet ./internal/integrations/lark ./internal/handler ./internal/daemon ./cmd/server passed.
  • git diff --check passed.
  • The earlier CLI rendering test was unchanged by the review fixes and was not rerun in this final supplement.

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

  • This returns bounded human text/post context, not complete bot answers, attachments or a complete archive. The saved Multica transcript remains available through chat history.
  • Required anchors may be deleted, unavailable or outside the three-page scan. In those cases the reader fails closed rather than widening the scope or relaxing generation boundaries.
  • A long conversation after /new or /clear can exceed the scan budget even while its latest trigger is still visible. The prompt and documentation describe this limitation explicitly.
  • Topic roots are omitted from ordinary-conversation results, and their replies are never fetched. Within a topic, missing or mismatched thread identifiers still invalidate the read.
  • Existing application permissions may be insufficient. Documented access refusals return an unavailable notice without requesting permissions or retrying authorization failures.
  • Source validation remains conservative. Unexpected identifiers, ordering or pagination metadata can make a read unavailable.
  • The final consistency recheck can reject a result if relevant binding, installation or generation state changes during the read.
  • No live Feishu tenant, real model/agent execution, full repository suite, browser/E2E testing, deployment or remote CI was verified. There are no UI changes requiring screenshots.

Checklist

  • Included the project context and thinking path
  • Ran relevant local tests and distinguished initial checks from final supplemental validation
  • Added regression coverage
  • Updated relevant documentation and actual Feishu agent guidance
  • Checked Chinese documentation against repository conventions
  • Documented risks and verification limits
  • Address reviewer comments before requesting merge

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

  • Branch: jiahao6635:feat/lark-current-native-history
  • Base: e8b6a97643ccc35efbcf2a4675c8d088a288ed6e
  • Final head: 462766566268250fb36441c7a7efacc7086169da
  • Remote branch SHA verified after push.
  • PR creation/edit/comment operations were previously denied with HTTP 403; they were not retried or bypassed.

@vercel

vercel Bot commented Nov 2, 2026

Copy link
Copy Markdown

@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 multica-eve changed the title feat(lark): read bounded native context for the current chat task MUL-8505: feat(lark): read bounded native context for the current chat task Nov 3, 2026

@multica-eve multica-eve left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +55 to +65
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")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +230 to +239
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})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants