Files
multica/server/internal/integrations/lark/params.go
beast fd813f2569 fix(lark): isolate topic-group sessions by thread (#5061)
* fix(lark): isolate topic-group sessions by thread

A Feishu topic group (话题群) collapsed every topic into one
chat_session: the session binder passed the raw group chat id as the
engine BindingKey, violating the engine.EnsureSessionInput contract
that a threaded platform must never key sessions by raw chat id.
Multiple users @-mentioning the bot in different topics shared one
transcript, and replies all landed in whichever topic wrote
last_thread_id last.

Adopt the Slack channel:threadRoot model: a message inside a topic
(thread_id present) keys the session by "chat:thread" and persists the
real chat id in the binding config (larkBindingConfig); outbound paths
(chat reply, error card) resolve the send target via outboundChatID —
config first, falling back to the key for pre-topic rows, which keeps
legacy bindings routing unchanged. P2p and plain (non-topic) group
chats keep the raw chat id key and existing behavior.

No migration: existing topic-group sessions stay as-is; new topic
messages create per-topic sessions from the first @-mention onward.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(lark): remove unused CreateLarkChatSessionBinding helper

The helper and its CreateChatSessionBindingParams had no callers; all chat-session bindings are created through the shared engine.EnsureSession path. Dropping the dead code removes a way to bypass the engine and create topic bindings with a hardcoded empty config.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-08 16:11:18 +08:00

138 lines
4.1 KiB
Go

package lark
import "github.com/jackc/pgx/v5/pgtype"
// Domain parameter types for the channel-backed Feishu store. They replace the
// retired db.*LarkParams shapes generated from queries/lark.sql, using the same
// channel-neutral field names as the domain entities in store.go. The store
// (channel_store.go) maps them onto the channel_* writes, folding the
// feishu-specific identifiers into the JSONB config at the DB boundary.
// GetInstallationInWorkspaceParams scopes an installation lookup to a workspace.
type GetInstallationInWorkspaceParams struct {
ID pgtype.UUID
WorkspaceID pgtype.UUID
}
// UpsertInstallationParams carries the flat feishu installation fields for an
// install / re-install.
type UpsertInstallationParams struct {
WorkspaceID pgtype.UUID
AgentID pgtype.UUID
AppID string
AppSecretEncrypted []byte
BotOpenID string
InstallerUserID pgtype.UUID
TenantKey pgtype.Text
BotUnionID pgtype.Text
Region string
}
// SetInstallationStatusParams flips an installation's status (active/revoked).
type SetInstallationStatusParams struct {
ID pgtype.UUID
Status string
}
// SetInstallationBotUnionIDParams records the bot's union_id (backfill).
type SetInstallationBotUnionIDParams struct {
ID pgtype.UUID
BotUnionID pgtype.Text
}
// AcquireWSLeaseParams fences the WS supervisor lease for an installation.
type AcquireWSLeaseParams struct {
NewToken pgtype.Text
NewExpiresAt pgtype.Timestamptz
ID pgtype.UUID
}
// ReleaseWSLeaseParams releases a WS supervisor lease the caller still holds.
type ReleaseWSLeaseParams struct {
ID pgtype.UUID
CurrentToken pgtype.Text
}
// GetUserBindingByOpenIDParams looks up a binding by its channel-native user id.
type GetUserBindingByOpenIDParams struct {
InstallationID pgtype.UUID
ChannelUserID string
}
// CreateUserBindingParams binds a workspace member to a channel-native user id.
type CreateUserBindingParams struct {
WorkspaceID pgtype.UUID
MulticaUserID pgtype.UUID
InstallationID pgtype.UUID
ChannelUserID string
UnionID pgtype.Text
}
// GetChatSessionBindingParams looks up a chat binding by its channel chat id.
type GetChatSessionBindingParams struct {
InstallationID pgtype.UUID
ChannelChatID string
}
// UpdateChatSessionBindingReplyTargetParams records the latest inbound trigger
// message + thread so the outbound patcher can thread its reply.
type UpdateChatSessionBindingReplyTargetParams struct {
ChatSessionID pgtype.UUID
LastMessageID pgtype.Text
LastThreadID pgtype.Text
}
// ClaimInboundDedupParams claims the two-phase idempotency row for a message.
type ClaimInboundDedupParams struct {
InstallationID pgtype.UUID
MessageID string
}
// MarkInboundDedupProcessedParams marks a claimed message processed (fenced).
type MarkInboundDedupProcessedParams struct {
InstallationID pgtype.UUID
MessageID string
ClaimToken pgtype.UUID
}
// ReleaseInboundDedupParams releases a claim on processing failure (fenced).
type ReleaseInboundDedupParams struct {
InstallationID pgtype.UUID
MessageID string
ClaimToken pgtype.UUID
}
// RecordInboundDropParams writes a non-content drop audit row.
type RecordInboundDropParams struct {
EventType string
DropReason string
InstallationID pgtype.UUID
ChannelChatID pgtype.Text
ChannelEventID pgtype.Text
ChannelMessageID pgtype.Text
}
// CreateBindingTokenParams mints a short-lived channel binding token.
type CreateBindingTokenParams struct {
TokenHash string
WorkspaceID pgtype.UUID
InstallationID pgtype.UUID
ChannelUserID string
ExpiresAt pgtype.Timestamptz
}
// CreateOutboundCardMessageParams records an outbound card for a task/session.
type CreateOutboundCardMessageParams struct {
ChatSessionID pgtype.UUID
ChannelChatID string
ChannelCardMessageID string
Status string
TaskID pgtype.UUID
}
// UpdateOutboundCardStatusParams transitions an outbound card's status.
type UpdateOutboundCardStatusParams struct {
ID pgtype.UUID
Status string
}