Lark adapter was already implemented in Go (lark.go — outbound Custom Bot webhook + inbound Event Subscriptions with constant-time token verify), but the Canvas connect-form hardcoded a Telegram-shaped pair of inputs (bot_token + chat_id). Selecting "Lark / Feishu" from the dropdown silently sent the wrong field names — there was no way to enter a webhook URL. Fix: move form shape to the server. - Add `ConfigField` struct + `ConfigSchema()` method to the `ChannelAdapter` interface. Each adapter declares its own fields with label/type/required/sensitive/placeholder/help. - Implement per-adapter schemas: - Lark: webhook_url (required+sensitive) + verify_token (optional+sensitive) - Slack: bot_token/channel_id/webhook_url/username/icon_emoji - Discord: webhook_url + optional public_key - Telegram: bot_token + chat_id (unchanged UX, keeps Detect Chats) - Change `ListAdapters()` to return `[]AdapterInfo` with config_schema inline. Sorted deterministically by display name so UI ordering is stable across Go's random map iteration. - Update the 3 existing `ListAdapters` test sites to struct access. Canvas (`ChannelsTab.tsx`): - Replace the two hardcoded bot_token/chat_id inputs with a single schema-driven `SchemaField` component. Renders one input per field in the order the adapter returns them. - Form state becomes `formValues: Record<string,string>` keyed by `ConfigField.key`. Values reset on platform-switch so stale Telegram credentials can't leak into a new Lark channel. - "Detect Chats" stays but only renders for platforms in `SUPPORTS_DETECT_CHATS` (Telegram only — the only provider with getUpdates). - Only schema-known keys are posted in `config`, scrubbing any stale values from previous platform selections. Regression tests: - `TestLark_ConfigSchema` locks in the 2-field Lark contract with the required/sensitive flags correctly set. - `TestListAdapters_IncludesLark` confirms registry wiring + schema survives round-trip through ListAdapters. Known pre-existing `TestStripPluginMarkers_AwkScript` failure in internal/handlers is unrelated to this change (verified via stash+test on clean staging). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
46 lines
1.4 KiB
Go
46 lines
1.4 KiB
Go
package channels
|
|
|
|
// Registry of all available channel adapters.
|
|
// To add a new platform: implement ChannelAdapter, register here.
|
|
var adapters = map[string]ChannelAdapter{
|
|
"telegram": &TelegramAdapter{},
|
|
"slack": &SlackAdapter{},
|
|
"lark": &LarkAdapter{},
|
|
"discord": &DiscordAdapter{},
|
|
}
|
|
|
|
// GetAdapter returns the adapter for a channel type.
|
|
func GetAdapter(channelType string) (ChannelAdapter, bool) {
|
|
a, ok := adapters[channelType]
|
|
return a, ok
|
|
}
|
|
|
|
// AdapterInfo is the metadata payload returned by ListAdapters — the Canvas
|
|
// connect-channel form renders its field list dynamically from config_schema.
|
|
type AdapterInfo struct {
|
|
Type string `json:"type"`
|
|
DisplayName string `json:"display_name"`
|
|
ConfigSchema []ConfigField `json:"config_schema"`
|
|
}
|
|
|
|
// ListAdapters returns metadata about all available adapters, in a stable
|
|
// order (sorted by display name) so UI rendering + test assertions don't
|
|
// depend on Go's random map iteration.
|
|
func ListAdapters() []AdapterInfo {
|
|
result := make([]AdapterInfo, 0, len(adapters))
|
|
for _, a := range adapters {
|
|
result = append(result, AdapterInfo{
|
|
Type: a.Type(),
|
|
DisplayName: a.DisplayName(),
|
|
ConfigSchema: a.ConfigSchema(),
|
|
})
|
|
}
|
|
// Sort by display name for deterministic ordering.
|
|
for i := 1; i < len(result); i++ {
|
|
for j := i; j > 0 && result[j-1].DisplayName > result[j].DisplayName; j-- {
|
|
result[j-1], result[j] = result[j], result[j-1]
|
|
}
|
|
}
|
|
return result
|
|
}
|