diff --git a/apps/web/src/i18n/locales/en.json b/apps/web/src/i18n/locales/en.json
index 326feabe..81cc1bbb 100644
--- a/apps/web/src/i18n/locales/en.json
+++ b/apps/web/src/i18n/locales/en.json
@@ -501,11 +501,6 @@
"editAvatarDescription": "Set the image URL for the bot avatar.",
"avatarUpdateSuccess": "Avatar updated",
"avatarUpdateFailed": "Failed to update avatar",
- "typePlaceholder": "Select bot type",
- "types": {
- "personal": "Personal",
- "public": "Public"
- },
"active": "Active",
"inactive": "Inactive",
"lifecycle": {
@@ -758,7 +753,6 @@
"browserContext": "Browser Context",
"browserContextPlaceholder": "Select browser context (disabled if empty)",
"allowGuest": "Allow Guest Access",
- "allowGuestPersonalHint": "Personal bots do not support guest access. Use a public bot instead.",
"loopDetectionTitle": "Detect and auto-block output loops",
"searchModel": "Search models…",
"noModel": "No models available",
diff --git a/apps/web/src/i18n/locales/zh.json b/apps/web/src/i18n/locales/zh.json
index 165e3b61..06ba29bd 100644
--- a/apps/web/src/i18n/locales/zh.json
+++ b/apps/web/src/i18n/locales/zh.json
@@ -497,11 +497,6 @@
"editAvatarDescription": "设置 Bot 头像图片的链接地址",
"avatarUpdateSuccess": "头像已更新",
"avatarUpdateFailed": "更新头像失败",
- "typePlaceholder": "选择 Bot 类型",
- "types": {
- "personal": "个人",
- "public": "公开"
- },
"active": "运行中",
"inactive": "未启用",
"lifecycle": {
@@ -754,7 +749,6 @@
"browserContext": "浏览器上下文",
"browserContextPlaceholder": "选择浏览器上下文(未配置时不启用)",
"allowGuest": "允许游客访问",
- "allowGuestPersonalHint": "个人 Bot 不支持游客访问,请使用公开 Bot。",
"loopDetectionTitle": "自动检测并阻止模型循环输出",
"searchModel": "搜索模型…",
"noModel": "暂无可选模型",
diff --git a/apps/web/src/pages/bots/components/bot-access.vue b/apps/web/src/pages/bots/components/bot-access.vue
index e7031676..6c7c383a 100644
--- a/apps/web/src/pages/bots/components/bot-access.vue
+++ b/apps/web/src/pages/bots/components/bot-access.vue
@@ -18,22 +18,16 @@
{{ $t('bots.access.allowGuestDescription') }}
-
- {{ $t('bots.settings.allowGuestPersonalHint') }}
-
allowGuestDraft = !!val"
/>
-
- {{ botTypeLabel }}
-
- ·
{{ $t('common.createdAt') }} {{ formattedDate }}
@@ -95,12 +85,6 @@ const formattedDate = computed(() => {
const { hasIssue, isPending, issueTitle, statusLabel, statusVariant } = useBotStatusMeta(botRef, t)
-const botTypeLabel = computed(() => {
- const type = props.bot.type
- if (type === 'personal' || type === 'public') return t('bots.types.' + type)
- return type ?? ''
-})
-
function onOpenDetail() {
if (isPending.value) return
router.push({ name: 'bot-detail', params: { botId: props.bot.id } })
diff --git a/apps/web/src/pages/bots/components/bot-files.vue b/apps/web/src/pages/bots/components/bot-files.vue
index 5325a3d2..3faf2e4a 100644
--- a/apps/web/src/pages/bots/components/bot-files.vue
+++ b/apps/web/src/pages/bots/components/bot-files.vue
@@ -3,7 +3,6 @@ import FileManager from '@/components/file-manager/index.vue'
defineProps<{
botId: string
- botType?: string
}>()
diff --git a/apps/web/src/pages/bots/components/bot-settings.vue b/apps/web/src/pages/bots/components/bot-settings.vue
index c22fec3e..434f50a5 100644
--- a/apps/web/src/pages/bots/components/bot-settings.vue
+++ b/apps/web/src/pages/bots/components/bot-settings.vue
@@ -337,7 +337,6 @@ import { resolveApiErrorMessage } from '@/utils/api-error'
const props = defineProps<{
botId: string
- botType?: string
}>()
const { t } = useI18n()
diff --git a/apps/web/src/pages/bots/components/create-bot.vue b/apps/web/src/pages/bots/components/create-bot.vue
index d1c1ba16..46758d88 100644
--- a/apps/web/src/pages/bots/components/create-bot.vue
+++ b/apps/web/src/pages/bots/components/create-bot.vue
@@ -57,38 +57,6 @@
-
-
-
-
-
-
-
-
-
-
@@ -128,12 +96,6 @@ import {
Separator,
Label,
Spinner,
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectTrigger,
- SelectValue,
} from '@memoh/ui'
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
@@ -151,7 +113,6 @@ const { run } = useDialogMutation()
const formSchema = toTypedSchema(z.object({
display_name: z.string().min(1),
avatar_url: z.string().optional(),
- type: z.string(),
}))
const form = useForm({
@@ -159,7 +120,6 @@ const form = useForm({
initialValues: {
display_name: '',
avatar_url: '',
- type: 'personal',
},
})
@@ -175,7 +135,6 @@ watch(open, (val) => {
values: {
display_name: '',
avatar_url: '',
- type: 'personal',
},
})
} else {
@@ -189,7 +148,6 @@ const handleSubmit = form.handleSubmit(async (values) => {
body: {
display_name: values.display_name,
avatar_url: values.avatar_url || undefined,
- type: values.type,
is_active: true,
},
}),
diff --git a/apps/web/src/pages/bots/detail.vue b/apps/web/src/pages/bots/detail.vue
index 75afe32b..a39d5b00 100644
--- a/apps/web/src/pages/bots/detail.vue
+++ b/apps/web/src/pages/bots/detail.vue
@@ -91,7 +91,6 @@
/>
{{ statusLabel }}
- {{ botTypeLabel }}
@@ -283,15 +282,15 @@ const tabList = computed(() => {
{ value: 'email', label: 'bots.tabs.email', component: BotEmail, params: { 'bot-id': bot_id } },
{ value: 'container', label: 'bots.tabs.container', component: BotContainer, params: {} },
{ value: 'terminal', label: 'bots.tabs.terminal', component: BotTerminal, params: { 'bot-id': bot_id } },
- { value: 'files', label: 'bots.tabs.files', component: BotFiles, params: { 'bot-id': bot_id, 'bot-type': bot.value?.type } },
+ { value: 'files', label: 'bots.tabs.files', component: BotFiles, params: { 'bot-id': bot_id } },
{ value: 'mcp', label: 'bots.tabs.mcp', component: BotMcp, params: { 'bot-id': bot_id } },
{ value: 'subagents', label: 'bots.tabs.subagents', component: BotSubagents, params: { 'bot-id': bot_id } },
{ value: 'heartbeat', label: 'bots.tabs.heartbeat', component: BotHeartbeat, params: { 'bot-id': bot_id } },
{ value: 'schedule', label: 'bots.tabs.schedule', component: BotSchedule, params: { 'bot-id': bot_id } },
{ value: 'history', label: 'bots.tabs.history', component: BotHistory, params: { 'bot-id': bot_id } },
{ value: 'skills', label: 'bots.tabs.skills', component: BotSkills, params: { 'bot-id': bot_id } },
- { value: 'access', label: 'bots.tabs.access', component: BotAccess, params: { 'bot-id': bot_id, 'bot-type': bot.value?.type } },
- { value: 'settings', label: 'bots.tabs.settings', component: BotSettings, params: { 'bot-id': bot_id, 'bot-type': bot.value?.type } }
+ { value: 'access', label: 'bots.tabs.access', component: BotAccess, params: { 'bot-id': bot_id } },
+ { value: 'settings', label: 'bots.tabs.settings', component: BotSettings, params: { 'bot-id': bot_id } }
]
})
@@ -369,12 +368,6 @@ const {
statusVariant,
} = useBotStatusMeta(bot, t)
-const botTypeLabel = computed(() => {
- const type = bot.value?.type
- if (type === 'personal' || type === 'public') return t('bots.types.' + type)
- return type ?? ''
-})
-
const checks = ref([])
const checksLoading = ref(false)
diff --git a/apps/web/src/pages/bots/index.vue b/apps/web/src/pages/bots/index.vue
index 092c59c3..b7da5de9 100644
--- a/apps/web/src/pages/bots/index.vue
+++ b/apps/web/src/pages/bots/index.vue
@@ -82,8 +82,7 @@ const filteredBots = computed(() => {
if (!keyword) return allBots.value
return allBots.value.filter((bot) =>
bot.display_name?.toLowerCase().includes(keyword)
- || bot.id?.toLowerCase().includes(keyword)
- || bot.type?.toLowerCase().includes(keyword),
+ || bot.id?.toLowerCase().includes(keyword),
)
})
diff --git a/apps/web/src/pages/chat/components/bot-sidebar.vue b/apps/web/src/pages/chat/components/bot-sidebar.vue
index 704c3eb1..68bb024c 100644
--- a/apps/web/src/pages/chat/components/bot-sidebar.vue
+++ b/apps/web/src/pages/chat/components/bot-sidebar.vue
@@ -27,12 +27,6 @@
{{ bot.display_name || bot.id }}
-
- {{ botTypeLabel(bot.type) }}
-
@@ -87,13 +81,6 @@ const isActive = (id: string) => computed(() => {
return currentBotId.value === id
})
-function botTypeLabel(type: string): string {
- if (!type) return ''
- const key = `bots.types.${type}`
- const out = t(key)
- return out !== key ? out : type
-}
-
function handleSelect(bot: BotsBot) {
chatStore.selectBot(bot.id)
}
diff --git a/db/migrations/0033_remove_bot_type_distinction.down.sql b/db/migrations/0033_remove_bot_type_distinction.down.sql
new file mode 100644
index 00000000..908839c6
--- /dev/null
+++ b/db/migrations/0033_remove_bot_type_distinction.down.sql
@@ -0,0 +1,2 @@
+ALTER TABLE bots ADD COLUMN type TEXT NOT NULL DEFAULT 'personal';
+ALTER TABLE bots ADD CONSTRAINT bots_type_check CHECK (type IN ('personal', 'public'));
diff --git a/db/migrations/0033_remove_bot_type_distinction.up.sql b/db/migrations/0033_remove_bot_type_distinction.up.sql
new file mode 100644
index 00000000..e45ac8be
--- /dev/null
+++ b/db/migrations/0033_remove_bot_type_distinction.up.sql
@@ -0,0 +1,2 @@
+ALTER TABLE bots DROP CONSTRAINT IF EXISTS bots_type_check;
+ALTER TABLE bots DROP COLUMN type;
diff --git a/db/queries/bots.sql b/db/queries/bots.sql
index 8b29d122..088f9eea 100644
--- a/db/queries/bots.sql
+++ b/db/queries/bots.sql
@@ -1,15 +1,15 @@
-- name: CreateBot :one
-INSERT INTO bots (owner_user_id, type, display_name, avatar_url, is_active, metadata, status)
-VALUES ($1, $2, $3, $4, $5, $6, $7)
-RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at;
+INSERT INTO bots (owner_user_id, display_name, avatar_url, is_active, metadata, status)
+VALUES ($1, $2, $3, $4, $5, $6)
+RETURNING id, owner_user_id, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at;
-- name: GetBotByID :one
-SELECT id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at
+SELECT id, owner_user_id, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at
FROM bots
WHERE id = $1;
-- name: ListBotsByOwner :many
-SELECT id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at
+SELECT id, owner_user_id, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at
FROM bots
WHERE owner_user_id = $1
ORDER BY created_at DESC;
@@ -22,14 +22,14 @@ SET display_name = $2,
metadata = $5,
updated_at = now()
WHERE id = $1
-RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at;
+RETURNING id, owner_user_id, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at;
-- name: UpdateBotOwner :one
UPDATE bots
SET owner_user_id = $2,
updated_at = now()
WHERE id = $1
-RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at;
+RETURNING id, owner_user_id, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at;
-- name: UpdateBotStatus :exec
UPDATE bots
diff --git a/db/queries/conversations.sql b/db/queries/conversations.sql
index 1125cd9f..330c6d4e 100644
--- a/db/queries/conversations.sql
+++ b/db/queries/conversations.sql
@@ -2,7 +2,7 @@
SELECT
b.id AS id,
b.id AS bot_id,
- (COALESCE(NULLIF(sqlc.arg(kind)::text, ''), CASE WHEN b.type = 'public' THEN 'group' ELSE 'direct' END))::text AS kind,
+ (COALESCE(NULLIF(sqlc.arg(kind)::text, ''), 'direct'))::text AS kind,
CASE WHEN sqlc.arg(kind) = 'thread' THEN sqlc.arg(parent_chat_id)::uuid ELSE NULL::uuid END AS parent_chat_id,
COALESCE(NULLIF(sqlc.arg(title)::text, ''), b.display_name) AS title,
COALESCE(sqlc.arg(created_by_user_id)::uuid, b.owner_user_id) AS created_by_user_id,
@@ -19,7 +19,7 @@ LIMIT 1;
SELECT
b.id AS id,
b.id AS bot_id,
- CASE WHEN b.type = 'public' THEN 'group' ELSE 'direct' END AS kind,
+ 'direct'::text AS kind,
NULL::uuid AS parent_chat_id,
b.display_name AS title,
b.owner_user_id AS created_by_user_id,
@@ -35,7 +35,7 @@ WHERE b.id = $1;
SELECT
b.id AS id,
b.id AS bot_id,
- CASE WHEN b.type = 'public' THEN 'group' ELSE 'direct' END AS kind,
+ 'direct'::text AS kind,
NULL::uuid AS parent_chat_id,
b.display_name AS title,
b.owner_user_id AS created_by_user_id,
@@ -53,7 +53,7 @@ ORDER BY b.updated_at DESC;
SELECT
b.id AS id,
b.id AS bot_id,
- CASE WHEN b.type = 'public' THEN 'group' ELSE 'direct' END AS kind,
+ 'direct'::text AS kind,
NULL::uuid AS parent_chat_id,
b.display_name AS title,
b.owner_user_id AS created_by_user_id,
@@ -64,7 +64,7 @@ SELECT
'participant'::text AS access_mode,
(CASE
WHEN b.owner_user_id = sqlc.arg(user_id) THEN 'owner'
- ELSE COALESCE(bm.role, ''::text)
+ ELSE ''::text
END)::text AS participant_role,
NULL::timestamptz AS last_observed_at
FROM bots b
@@ -87,7 +87,7 @@ LIMIT 1;
SELECT
b.id AS id,
b.id AS bot_id,
- CASE WHEN b.type = 'public' THEN 'group' ELSE 'direct' END AS kind,
+ 'direct'::text AS kind,
NULL::uuid AS parent_chat_id,
b.display_name AS title,
b.owner_user_id AS created_by_user_id,
@@ -111,7 +111,7 @@ WITH updated AS (
SELECT
updated.id AS id,
updated.id AS bot_id,
- CASE WHEN updated.type = 'public' THEN 'group' ELSE 'direct' END AS kind,
+ 'direct'::text AS kind,
NULL::uuid AS parent_chat_id,
updated.display_name AS title,
updated.owner_user_id AS created_by_user_id,
diff --git a/internal/acl/service_test.go b/internal/acl/service_test.go
index 2c943e3a..23f3f2e8 100644
--- a/internal/acl/service_test.go
+++ b/internal/acl/service_test.go
@@ -59,26 +59,25 @@ func makeBotRow(botID, ownerUserID pgtype.UUID) *fakeRow {
scanFunc: func(dest ...any) error {
*dest[0].(*pgtype.UUID) = botID
*dest[1].(*pgtype.UUID) = ownerUserID
- *dest[2].(*string) = bots.BotTypePublic
- *dest[3].(*pgtype.Text) = pgtype.Text{String: "bot", Valid: true}
- *dest[4].(*pgtype.Text) = pgtype.Text{}
- *dest[5].(*bool) = true
- *dest[6].(*string) = bots.BotStatusReady
- *dest[7].(*int32) = 30
- *dest[8].(*int32) = 0
- *dest[9].(*int32) = 50
- *dest[10].(*string) = "auto"
- *dest[11].(*bool) = false
- *dest[12].(*string) = "medium"
+ *dest[2].(*pgtype.Text) = pgtype.Text{String: "bot", Valid: true}
+ *dest[3].(*pgtype.Text) = pgtype.Text{}
+ *dest[4].(*bool) = true
+ *dest[5].(*string) = bots.BotStatusReady
+ *dest[6].(*int32) = 30
+ *dest[7].(*int32) = 0
+ *dest[8].(*int32) = 50
+ *dest[9].(*string) = "auto"
+ *dest[10].(*bool) = false
+ *dest[11].(*string) = "medium"
+ *dest[12].(*pgtype.UUID) = pgtype.UUID{}
*dest[13].(*pgtype.UUID) = pgtype.UUID{}
*dest[14].(*pgtype.UUID) = pgtype.UUID{}
- *dest[15].(*pgtype.UUID) = pgtype.UUID{}
- *dest[16].(*bool) = false
- *dest[17].(*int32) = 30
- *dest[18].(*string) = ""
- *dest[19].(*[]byte) = []byte(`{}`)
+ *dest[15].(*bool) = false
+ *dest[16].(*int32) = 30
+ *dest[17].(*string) = ""
+ *dest[18].(*[]byte) = []byte(`{}`)
+ *dest[19].(*pgtype.Timestamptz) = pgtype.Timestamptz{}
*dest[20].(*pgtype.Timestamptz) = pgtype.Timestamptz{}
- *dest[21].(*pgtype.Timestamptz) = pgtype.Timestamptz{}
return nil
},
}
diff --git a/internal/bots/service.go b/internal/bots/service.go
index 69b9b5c1..894a20d1 100644
--- a/internal/bots/service.go
+++ b/internal/bots/service.go
@@ -36,11 +36,6 @@ var (
ErrOwnerUserNotFound = errors.New("owner user not found")
)
-// AccessPolicy controls bot access behavior.
-type AccessPolicy struct {
- AllowGuest bool
-}
-
// NewService creates a new bot service.
func NewService(log *slog.Logger, queries *sqlc.Queries) *Service {
if log == nil {
@@ -70,8 +65,8 @@ func (s *Service) AddRuntimeChecker(c RuntimeChecker) {
}
}
-// AuthorizeAccess checks whether userID may access the given bot.
-func (s *Service) AuthorizeAccess(ctx context.Context, userID, botID string, isAdmin bool, policy AccessPolicy) (Bot, error) {
+// AuthorizeAccess checks whether userID may access the given bot (owner or admin only).
+func (s *Service) AuthorizeAccess(ctx context.Context, userID, botID string, isAdmin bool) (Bot, error) {
if s.queries == nil {
return Bot{}, errors.New("bot queries not configured")
}
@@ -85,11 +80,6 @@ func (s *Service) AuthorizeAccess(ctx context.Context, userID, botID string, isA
if isAdmin || bot.OwnerUserID == userID {
return bot, nil
}
- if bot.Type == BotTypePublic {
- if policy.AllowGuest {
- return bot, nil
- }
- }
return Bot{}, ErrBotAccessDenied
}
@@ -109,10 +99,6 @@ func (s *Service) Create(ctx context.Context, ownerUserID string, req CreateBotR
if err := s.ensureUserExists(ctx, ownerUUID); err != nil {
return Bot{}, err
}
- normalizedType, err := normalizeBotType(req.Type)
- if err != nil {
- return Bot{}, err
- }
displayName := strings.TrimSpace(req.DisplayName)
if displayName == "" {
displayName = "bot-" + uuid.NewString()
@@ -132,7 +118,6 @@ func (s *Service) Create(ctx context.Context, ownerUserID string, req CreateBotR
}
row, err := s.queries.CreateBot(ctx, sqlc.CreateBotParams{
OwnerUserID: ownerUUID,
- Type: normalizedType,
DisplayName: pgtype.Text{String: displayName, Valid: displayName != ""},
AvatarUrl: pgtype.Text{String: avatarURL, Valid: avatarURL != ""},
IsActive: isActive,
@@ -431,33 +416,20 @@ func (s *Service) ensureUserExists(ctx context.Context, userID pgtype.UUID) erro
return nil
}
-func normalizeBotType(raw string) (string, error) {
- normalized := strings.ToLower(strings.TrimSpace(raw))
- if normalized == "" {
- return BotTypePersonal, nil
- }
- switch normalized {
- case BotTypePersonal, BotTypePublic:
- return normalized, nil
- default:
- return "", fmt.Errorf("invalid bot type: %s", raw)
- }
-}
-
func asSQLCBot(v any) sqlc.Bot {
switch r := v.(type) {
case sqlc.Bot:
return r
case sqlc.CreateBotRow:
- return sqlc.Bot{ID: r.ID, OwnerUserID: r.OwnerUserID, Type: r.Type, DisplayName: r.DisplayName, AvatarUrl: r.AvatarUrl, IsActive: r.IsActive, Status: r.Status, MaxContextLoadTime: r.MaxContextLoadTime, MaxContextTokens: r.MaxContextTokens, MaxInboxItems: r.MaxInboxItems, Language: r.Language, ReasoningEnabled: r.ReasoningEnabled, ReasoningEffort: r.ReasoningEffort, ChatModelID: r.ChatModelID, SearchProviderID: r.SearchProviderID, MemoryProviderID: r.MemoryProviderID, HeartbeatEnabled: r.HeartbeatEnabled, HeartbeatInterval: r.HeartbeatInterval, HeartbeatPrompt: r.HeartbeatPrompt, Metadata: r.Metadata, CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt}
+ return sqlc.Bot{ID: r.ID, OwnerUserID: r.OwnerUserID, DisplayName: r.DisplayName, AvatarUrl: r.AvatarUrl, IsActive: r.IsActive, Status: r.Status, MaxContextLoadTime: r.MaxContextLoadTime, MaxContextTokens: r.MaxContextTokens, MaxInboxItems: r.MaxInboxItems, Language: r.Language, ReasoningEnabled: r.ReasoningEnabled, ReasoningEffort: r.ReasoningEffort, ChatModelID: r.ChatModelID, SearchProviderID: r.SearchProviderID, MemoryProviderID: r.MemoryProviderID, HeartbeatEnabled: r.HeartbeatEnabled, HeartbeatInterval: r.HeartbeatInterval, HeartbeatPrompt: r.HeartbeatPrompt, Metadata: r.Metadata, CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt}
case sqlc.GetBotByIDRow:
- return sqlc.Bot{ID: r.ID, OwnerUserID: r.OwnerUserID, Type: r.Type, DisplayName: r.DisplayName, AvatarUrl: r.AvatarUrl, IsActive: r.IsActive, Status: r.Status, MaxContextLoadTime: r.MaxContextLoadTime, MaxContextTokens: r.MaxContextTokens, MaxInboxItems: r.MaxInboxItems, Language: r.Language, ReasoningEnabled: r.ReasoningEnabled, ReasoningEffort: r.ReasoningEffort, ChatModelID: r.ChatModelID, SearchProviderID: r.SearchProviderID, MemoryProviderID: r.MemoryProviderID, HeartbeatEnabled: r.HeartbeatEnabled, HeartbeatInterval: r.HeartbeatInterval, HeartbeatPrompt: r.HeartbeatPrompt, Metadata: r.Metadata, CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt}
+ return sqlc.Bot{ID: r.ID, OwnerUserID: r.OwnerUserID, DisplayName: r.DisplayName, AvatarUrl: r.AvatarUrl, IsActive: r.IsActive, Status: r.Status, MaxContextLoadTime: r.MaxContextLoadTime, MaxContextTokens: r.MaxContextTokens, MaxInboxItems: r.MaxInboxItems, Language: r.Language, ReasoningEnabled: r.ReasoningEnabled, ReasoningEffort: r.ReasoningEffort, ChatModelID: r.ChatModelID, SearchProviderID: r.SearchProviderID, MemoryProviderID: r.MemoryProviderID, HeartbeatEnabled: r.HeartbeatEnabled, HeartbeatInterval: r.HeartbeatInterval, HeartbeatPrompt: r.HeartbeatPrompt, Metadata: r.Metadata, CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt}
case sqlc.ListBotsByOwnerRow:
- return sqlc.Bot{ID: r.ID, OwnerUserID: r.OwnerUserID, Type: r.Type, DisplayName: r.DisplayName, AvatarUrl: r.AvatarUrl, IsActive: r.IsActive, Status: r.Status, MaxContextLoadTime: r.MaxContextLoadTime, MaxContextTokens: r.MaxContextTokens, MaxInboxItems: r.MaxInboxItems, Language: r.Language, ReasoningEnabled: r.ReasoningEnabled, ReasoningEffort: r.ReasoningEffort, ChatModelID: r.ChatModelID, SearchProviderID: r.SearchProviderID, MemoryProviderID: r.MemoryProviderID, HeartbeatEnabled: r.HeartbeatEnabled, HeartbeatInterval: r.HeartbeatInterval, HeartbeatPrompt: r.HeartbeatPrompt, Metadata: r.Metadata, CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt}
+ return sqlc.Bot{ID: r.ID, OwnerUserID: r.OwnerUserID, DisplayName: r.DisplayName, AvatarUrl: r.AvatarUrl, IsActive: r.IsActive, Status: r.Status, MaxContextLoadTime: r.MaxContextLoadTime, MaxContextTokens: r.MaxContextTokens, MaxInboxItems: r.MaxInboxItems, Language: r.Language, ReasoningEnabled: r.ReasoningEnabled, ReasoningEffort: r.ReasoningEffort, ChatModelID: r.ChatModelID, SearchProviderID: r.SearchProviderID, MemoryProviderID: r.MemoryProviderID, HeartbeatEnabled: r.HeartbeatEnabled, HeartbeatInterval: r.HeartbeatInterval, HeartbeatPrompt: r.HeartbeatPrompt, Metadata: r.Metadata, CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt}
case sqlc.UpdateBotProfileRow:
- return sqlc.Bot{ID: r.ID, OwnerUserID: r.OwnerUserID, Type: r.Type, DisplayName: r.DisplayName, AvatarUrl: r.AvatarUrl, IsActive: r.IsActive, Status: r.Status, MaxContextLoadTime: r.MaxContextLoadTime, MaxContextTokens: r.MaxContextTokens, MaxInboxItems: r.MaxInboxItems, Language: r.Language, ReasoningEnabled: r.ReasoningEnabled, ReasoningEffort: r.ReasoningEffort, ChatModelID: r.ChatModelID, SearchProviderID: r.SearchProviderID, MemoryProviderID: r.MemoryProviderID, HeartbeatEnabled: r.HeartbeatEnabled, HeartbeatInterval: r.HeartbeatInterval, HeartbeatPrompt: r.HeartbeatPrompt, Metadata: r.Metadata, CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt}
+ return sqlc.Bot{ID: r.ID, OwnerUserID: r.OwnerUserID, DisplayName: r.DisplayName, AvatarUrl: r.AvatarUrl, IsActive: r.IsActive, Status: r.Status, MaxContextLoadTime: r.MaxContextLoadTime, MaxContextTokens: r.MaxContextTokens, MaxInboxItems: r.MaxInboxItems, Language: r.Language, ReasoningEnabled: r.ReasoningEnabled, ReasoningEffort: r.ReasoningEffort, ChatModelID: r.ChatModelID, SearchProviderID: r.SearchProviderID, MemoryProviderID: r.MemoryProviderID, HeartbeatEnabled: r.HeartbeatEnabled, HeartbeatInterval: r.HeartbeatInterval, HeartbeatPrompt: r.HeartbeatPrompt, Metadata: r.Metadata, CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt}
case sqlc.UpdateBotOwnerRow:
- return sqlc.Bot{ID: r.ID, OwnerUserID: r.OwnerUserID, Type: r.Type, DisplayName: r.DisplayName, AvatarUrl: r.AvatarUrl, IsActive: r.IsActive, Status: r.Status, MaxContextLoadTime: r.MaxContextLoadTime, MaxContextTokens: r.MaxContextTokens, MaxInboxItems: r.MaxInboxItems, Language: r.Language, ReasoningEnabled: r.ReasoningEnabled, ReasoningEffort: r.ReasoningEffort, ChatModelID: r.ChatModelID, SearchProviderID: r.SearchProviderID, MemoryProviderID: r.MemoryProviderID, HeartbeatEnabled: r.HeartbeatEnabled, HeartbeatInterval: r.HeartbeatInterval, HeartbeatPrompt: r.HeartbeatPrompt, Metadata: r.Metadata, CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt}
+ return sqlc.Bot{ID: r.ID, OwnerUserID: r.OwnerUserID, DisplayName: r.DisplayName, AvatarUrl: r.AvatarUrl, IsActive: r.IsActive, Status: r.Status, MaxContextLoadTime: r.MaxContextLoadTime, MaxContextTokens: r.MaxContextTokens, MaxInboxItems: r.MaxInboxItems, Language: r.Language, ReasoningEnabled: r.ReasoningEnabled, ReasoningEffort: r.ReasoningEffort, ChatModelID: r.ChatModelID, SearchProviderID: r.SearchProviderID, MemoryProviderID: r.MemoryProviderID, HeartbeatEnabled: r.HeartbeatEnabled, HeartbeatInterval: r.HeartbeatInterval, HeartbeatPrompt: r.HeartbeatPrompt, Metadata: r.Metadata, CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt}
default:
return sqlc.Bot{}
}
@@ -487,7 +459,6 @@ func toBot(row sqlc.Bot) (Bot, error) {
return Bot{
ID: row.ID.String(),
OwnerUserID: row.OwnerUserID.String(),
- Type: row.Type,
DisplayName: displayName,
AvatarURL: avatarURL,
IsActive: row.IsActive,
diff --git a/internal/bots/service_test.go b/internal/bots/service_test.go
index c01930bf..c7a247f0 100644
--- a/internal/bots/service_test.go
+++ b/internal/bots/service_test.go
@@ -40,40 +40,38 @@ func (d *fakeDBTX) QueryRow(ctx context.Context, sql string, args ...any) pgx.Ro
return &fakeRow{scanFunc: func(_ ...any) error { return pgx.ErrNoRows }}
}
-// makeBotRow creates a fakeRow that populates a sqlc.Bot via Scan.
-// Column order: id, owner_user_id, type, display_name, avatar_url, is_active, status,
+// makeBotRow creates a fakeRow that populates a sqlc.GetBotByIDRow via Scan.
+// Column order: id, owner_user_id, display_name, avatar_url, is_active, status,
// max_context_load_time, max_context_tokens, max_inbox_items, language,
// reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id,
// heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at.
-func makeBotRow(botID, ownerUserID pgtype.UUID, botType string, allowGuest bool) *fakeRow {
+func makeBotRow(botID, ownerUserID pgtype.UUID) *fakeRow {
return &fakeRow{
scanFunc: func(dest ...any) error {
- if len(dest) < 22 {
+ if len(dest) < 21 {
return pgx.ErrNoRows
}
*dest[0].(*pgtype.UUID) = botID
*dest[1].(*pgtype.UUID) = ownerUserID
- *dest[2].(*string) = botType
- *dest[3].(*pgtype.Text) = pgtype.Text{String: "test-bot", Valid: true}
- *dest[4].(*pgtype.Text) = pgtype.Text{}
- *dest[5].(*bool) = true
- *dest[6].(*string) = BotStatusReady
- *dest[7].(*int32) = 30 // MaxContextLoadTime
- *dest[8].(*int32) = 4096 // MaxContextTokens
- *dest[9].(*int32) = 10 // MaxInboxItems
- *dest[10].(*string) = "en"
- _ = allowGuest
- *dest[11].(*bool) = false // ReasoningEnabled
- *dest[12].(*string) = "medium" // ReasoningEffort
- *dest[13].(*pgtype.UUID) = pgtype.UUID{} // ChatModelID
- *dest[14].(*pgtype.UUID) = pgtype.UUID{} // SearchProviderID
- *dest[15].(*pgtype.UUID) = pgtype.UUID{} // MemoryProviderID
- *dest[16].(*bool) = false // HeartbeatEnabled
- *dest[17].(*int32) = 30 // HeartbeatInterval
- *dest[18].(*string) = "" // HeartbeatPrompt
- *dest[19].(*[]byte) = []byte(`{}`)
+ *dest[2].(*pgtype.Text) = pgtype.Text{String: "test-bot", Valid: true}
+ *dest[3].(*pgtype.Text) = pgtype.Text{}
+ *dest[4].(*bool) = true
+ *dest[5].(*string) = BotStatusReady
+ *dest[6].(*int32) = 30 // MaxContextLoadTime
+ *dest[7].(*int32) = 4096 // MaxContextTokens
+ *dest[8].(*int32) = 10 // MaxInboxItems
+ *dest[9].(*string) = "en"
+ *dest[10].(*bool) = false // ReasoningEnabled
+ *dest[11].(*string) = "medium" // ReasoningEffort
+ *dest[12].(*pgtype.UUID) = pgtype.UUID{} // ChatModelID
+ *dest[13].(*pgtype.UUID) = pgtype.UUID{} // SearchProviderID
+ *dest[14].(*pgtype.UUID) = pgtype.UUID{} // MemoryProviderID
+ *dest[15].(*bool) = false // HeartbeatEnabled
+ *dest[16].(*int32) = 30 // HeartbeatInterval
+ *dest[17].(*string) = "" // HeartbeatPrompt
+ *dest[18].(*[]byte) = []byte(`{}`)
+ *dest[19].(*pgtype.Timestamptz) = pgtype.Timestamptz{}
*dest[20].(*pgtype.Timestamptz) = pgtype.Timestamptz{}
- *dest[21].(*pgtype.Timestamptz) = pgtype.Timestamptz{}
return nil
},
}
@@ -97,47 +95,23 @@ func TestAuthorizeAccess(t *testing.T) {
name string
userID string
isAdmin bool
- policy AccessPolicy
- botType string
- allowGst bool
wantErr bool
wantErrIs error
}{
{
name: "owner always allowed",
userID: ownerID,
- policy: AccessPolicy{},
- botType: BotTypePublic,
wantErr: false,
},
{
name: "admin always allowed",
userID: strangerID,
isAdmin: true,
- policy: AccessPolicy{},
- botType: BotTypePublic,
wantErr: false,
},
{
- name: "stranger denied without guest on public bot",
+ name: "stranger denied",
userID: strangerID,
- policy: AccessPolicy{AllowGuest: false},
- botType: BotTypePublic,
- wantErr: true,
- wantErrIs: ErrBotAccessDenied,
- },
- {
- name: "stranger allowed when policy allows guest",
- userID: strangerID,
- policy: AccessPolicy{AllowGuest: true},
- botType: BotTypePublic,
- wantErr: false,
- },
- {
- name: "guest not allowed on personal bot",
- userID: strangerID,
- policy: AccessPolicy{AllowGuest: true},
- botType: BotTypePersonal,
wantErr: true,
wantErrIs: ErrBotAccessDenied,
},
@@ -148,12 +122,12 @@ func TestAuthorizeAccess(t *testing.T) {
db := &fakeDBTX{
queryRowFunc: func(_ context.Context, _ string, args ...any) pgx.Row {
_ = args
- return makeBotRow(botUUID, ownerUUID, tt.botType, tt.allowGst)
+ return makeBotRow(botUUID, ownerUUID)
},
}
svc := NewService(nil, sqlc.New(db))
- _, err := svc.AuthorizeAccess(context.Background(), tt.userID, botID, tt.isAdmin, tt.policy)
+ _, err := svc.AuthorizeAccess(context.Background(), tt.userID, botID, tt.isAdmin)
if tt.wantErr {
if err == nil {
t.Fatal("expected error, got nil")
diff --git a/internal/bots/types.go b/internal/bots/types.go
index a5600d45..ecac2fa1 100644
--- a/internal/bots/types.go
+++ b/internal/bots/types.go
@@ -9,7 +9,6 @@ import (
type Bot struct {
ID string `json:"id"`
OwnerUserID string `json:"owner_user_id"`
- Type string `json:"type"`
DisplayName string `json:"display_name"`
AvatarURL string `json:"avatar_url,omitempty"`
IsActive bool `json:"is_active"`
@@ -35,7 +34,6 @@ type BotCheck struct {
// CreateBotRequest is the input for creating a bot.
type CreateBotRequest struct {
- Type string `json:"type"`
DisplayName string `json:"display_name,omitempty"`
AvatarURL string `json:"avatar_url,omitempty"`
IsActive *bool `json:"is_active,omitempty"`
@@ -77,10 +75,6 @@ type RuntimeChecker interface {
ListChecks(ctx context.Context, botID string) []BotCheck
}
-const (
- BotTypePersonal = "personal"
- BotTypePublic = "public"
-)
const (
BotStatusCreating = "creating"
diff --git a/internal/channel/inbound/channel_test.go b/internal/channel/inbound/channel_test.go
index d874b01e..cdc7fe29 100644
--- a/internal/channel/inbound/channel_test.go
+++ b/internal/channel/inbound/channel_test.go
@@ -337,7 +337,7 @@ func (f *fakeChatService) Persist(_ context.Context, input messagepkg.PersistInp
func TestChannelInboundProcessorWithIdentity(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-1"}}
- policySvc := &fakePolicyService{allow: false}
+ policySvc := &fakePolicyService{}
chatSvc := &fakeChatService{resolveResult: route.ResolveConversationResult{ChatID: "chat-1", RouteID: "route-1"}}
gateway := &fakeChatGateway{
resp: conversation.ChatResponse{
@@ -383,11 +383,14 @@ func TestChannelInboundProcessorWithIdentity(t *testing.T) {
}
}
-func TestChannelInboundProcessorDenied(t *testing.T) {
+func TestChannelInboundProcessorDeniedByACL(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-2"}}
- chatSvc := &fakeChatService{}
+ policySvc := &fakePolicyService{}
+ chatSvc := &fakeChatService{resolveResult: route.ResolveConversationResult{ChatID: "chat-denied", RouteID: "route-denied"}}
gateway := &fakeChatGateway{}
- processor := NewChannelInboundProcessor(slog.Default(), nil, chatSvc, chatSvc, gateway, channelIdentitySvc, nil, nil, "", 0)
+ processor := NewChannelInboundProcessor(slog.Default(), nil, chatSvc, chatSvc, gateway, channelIdentitySvc, policySvc, nil, "", 0)
+ aclSvc := &fakeChatACL{allowed: false}
+ processor.SetACLService(aclSvc)
sender := &fakeReplySender{}
cfg := channel.ChannelConfig{ID: "cfg-1", BotID: "bot-1", ChannelType: channel.ChannelType("feishu")}
@@ -407,9 +410,6 @@ func TestChannelInboundProcessorDenied(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
- if len(sender.sent) != 1 || !strings.Contains(sender.sent[0].Message.PlainText(), "denied") {
- t.Fatalf("expected access denied reply, got: %+v", sender.sent)
- }
if gateway.gotReq.Query != "" {
t.Error("denied user should not trigger chat call")
}
@@ -417,7 +417,7 @@ func TestChannelInboundProcessorDenied(t *testing.T) {
func TestChannelInboundProcessorACLGuestDeniedDowngradesToNotify(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-acl-deny"}}
- policySvc := &fakePolicyService{botType: "public"}
+ policySvc := &fakePolicyService{}
chatSvc := &fakeChatService{resolveResult: route.ResolveConversationResult{ChatID: "chat-acl", RouteID: "route-acl"}}
gateway := &fakeChatGateway{}
processor := NewChannelInboundProcessor(slog.Default(), nil, chatSvc, chatSvc, gateway, channelIdentitySvc, policySvc, nil, "", 0)
@@ -462,7 +462,7 @@ func TestChannelInboundProcessorACLGuestDeniedDowngradesToNotify(t *testing.T) {
func TestChannelInboundProcessorACLReceivesThreadScope(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-thread-scope"}}
- policySvc := &fakePolicyService{botType: "public"}
+ policySvc := &fakePolicyService{}
chatSvc := &fakeChatService{resolveResult: route.ResolveConversationResult{ChatID: "chat-thread", RouteID: "route-thread"}}
gateway := &fakeChatGateway{}
processor := NewChannelInboundProcessor(slog.Default(), nil, chatSvc, chatSvc, gateway, channelIdentitySvc, policySvc, nil, "", 0)
@@ -501,7 +501,7 @@ func TestChannelInboundProcessorACLReceivesThreadScope(t *testing.T) {
func TestChannelInboundProcessorIgnoreEmpty(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-3"}}
- policySvc := &fakePolicyService{allow: false}
+ policySvc := &fakePolicyService{}
chatSvc := &fakeChatService{}
gateway := &fakeChatGateway{}
processor := NewChannelInboundProcessor(slog.Default(), nil, chatSvc, chatSvc, gateway, channelIdentitySvc, policySvc, nil, "", 0)
@@ -570,7 +570,7 @@ func TestBuildInboundQueryAttachmentFallbackWithContainerRefs(t *testing.T) {
func TestChannelInboundProcessorAttachmentOnlyUsesFallbackQuery(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-fallback"}}
- policySvc := &fakePolicyService{botType: "public"}
+ policySvc := &fakePolicyService{}
chatSvc := &fakeChatService{resolveResult: route.ResolveConversationResult{ChatID: "chat-fallback", RouteID: "route-fallback"}}
gateway := &fakeChatGateway{
resp: conversation.ChatResponse{
@@ -614,7 +614,7 @@ func TestChannelInboundProcessorAttachmentOnlyUsesFallbackQuery(t *testing.T) {
func TestChannelInboundProcessorSilentReply(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-4"}}
- policySvc := &fakePolicyService{botType: "public"}
+ policySvc := &fakePolicyService{}
chatSvc := &fakeChatService{resolveResult: route.ResolveConversationResult{ChatID: "chat-4", RouteID: "route-4"}}
gateway := &fakeChatGateway{
resp: conversation.ChatResponse{
@@ -650,7 +650,7 @@ func TestChannelInboundProcessorSilentReply(t *testing.T) {
func TestChannelInboundProcessorGroupPassiveSync(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-5"}}
- policySvc := &fakePolicyService{botType: "public"}
+ policySvc := &fakePolicyService{}
chatSvc := &fakeChatService{resolveResult: route.ResolveConversationResult{ChatID: "chat-5", RouteID: "route-5"}}
gateway := &fakeChatGateway{
resp: conversation.ChatResponse{
@@ -692,7 +692,7 @@ func TestChannelInboundProcessorGroupPassiveSync(t *testing.T) {
func TestChannelInboundProcessorGroupMentionTriggersReply(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-6"}}
- policySvc := &fakePolicyService{botType: "public"}
+ policySvc := &fakePolicyService{}
chatSvc := &fakeChatService{resolveResult: route.ResolveConversationResult{ChatID: "chat-6", RouteID: "route-6"}}
gateway := &fakeChatGateway{
resp: conversation.ChatResponse{
@@ -752,7 +752,7 @@ func (s *failingOpenStreamSender) OpenStream(_ context.Context, _ string, _ chan
func TestChannelInboundProcessorDoesNotPersistBeforeOpenStream(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-openstream"}}
- policySvc := &fakePolicyService{botType: "public"}
+ policySvc := &fakePolicyService{}
chatSvc := &fakeChatService{resolveResult: route.ResolveConversationResult{ChatID: "chat-openstream", RouteID: "route-openstream"}}
gateway := &fakeChatGateway{}
processor := NewChannelInboundProcessor(slog.Default(), nil, chatSvc, chatSvc, gateway, channelIdentitySvc, policySvc, nil, "", 0)
@@ -785,7 +785,7 @@ func TestChannelInboundProcessorDoesNotPersistBeforeOpenStream(t *testing.T) {
func TestChannelInboundProcessorPersistsAttachmentAssetRefs(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-asset"}}
- policySvc := &fakePolicyService{botType: "public"}
+ policySvc := &fakePolicyService{}
chatSvc := &fakeChatService{resolveResult: route.ResolveConversationResult{ChatID: "chat-asset", RouteID: "route-asset"}}
gateway := &fakeChatGateway{
resp: conversation.ChatResponse{
@@ -838,7 +838,7 @@ func TestChannelInboundProcessorPersistsAttachmentAssetRefs(t *testing.T) {
func TestChannelInboundProcessorIngestsPlatformKeyWithResolver(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-resolver"}}
- policySvc := &fakePolicyService{botType: "public"}
+ policySvc := &fakePolicyService{}
chatSvc := &fakeChatService{resolveResult: route.ResolveConversationResult{ChatID: "chat-resolver", RouteID: "route-resolver"}}
gateway := &fakeChatGateway{
resp: conversation.ChatResponse{
@@ -895,7 +895,7 @@ func TestChannelInboundProcessorIngestsPlatformKeyWithResolver(t *testing.T) {
func TestChannelInboundProcessorIngestsBase64Attachment(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-base64"}}
- policySvc := &fakePolicyService{botType: "public"}
+ policySvc := &fakePolicyService{}
chatSvc := &fakeChatService{resolveResult: route.ResolveConversationResult{ChatID: "chat-base64", RouteID: "route-base64"}}
gateway := &fakeChatGateway{
resp: conversation.ChatResponse{
@@ -967,7 +967,7 @@ func TestChannelInboundProcessorIngestsBase64Attachment(t *testing.T) {
func TestChannelInboundProcessorIngestsQQFileAttachmentKeepsOriginalExtWhenMimeGeneric(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-qq-file"}}
- policySvc := &fakePolicyService{botType: "public"}
+ policySvc := &fakePolicyService{}
chatSvc := &fakeChatService{resolveResult: route.ResolveConversationResult{ChatID: "chat-qq-file", RouteID: "route-qq-file"}}
gateway := &fakeChatGateway{
resp: conversation.ChatResponse{
@@ -1032,7 +1032,7 @@ func TestChannelInboundProcessorIngestsQQFileAttachmentKeepsOriginalExtWhenMimeG
func TestChannelInboundProcessorPersonalGroupNonOwnerIgnored(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-member"}}
- policySvc := &fakePolicyService{allow: false, botType: "personal", ownerUserID: "channelIdentity-owner"}
+ policySvc := &fakePolicyService{ownerUserID: "channelIdentity-owner"}
chatSvc := &fakeChatService{resolveResult: route.ResolveConversationResult{ChatID: "chat-personal-1", RouteID: "route-personal-1"}}
gateway := &fakeChatGateway{
resp: conversation.ChatResponse{
@@ -1074,7 +1074,7 @@ func TestChannelInboundProcessorPersonalGroupNonOwnerIgnored(t *testing.T) {
func TestChannelInboundProcessorPersonalGroupOwnerWithoutMentionUsesPassivePersistence(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-owner"}}
- policySvc := &fakePolicyService{allow: false, botType: "personal", ownerUserID: "channelIdentity-owner"}
+ policySvc := &fakePolicyService{ownerUserID: "channelIdentity-owner"}
chatSvc := &fakeChatService{resolveResult: route.ResolveConversationResult{ChatID: "chat-personal-2", RouteID: "route-personal-2"}}
gateway := &fakeChatGateway{
resp: conversation.ChatResponse{
@@ -1121,7 +1121,7 @@ func TestChannelInboundProcessorProcessingStatusSuccessLifecycle(t *testing.T) {
registry := channel.NewRegistry()
registry.MustRegister(&fakeProcessingStatusAdapter{notifier: notifier})
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-1"}}
- policySvc := &fakePolicyService{botType: "public"}
+ policySvc := &fakePolicyService{}
chatSvc := &fakeChatService{resolveResult: route.ResolveConversationResult{ChatID: "chat-1", RouteID: "route-1"}}
gateway := &fakeChatGateway{
resp: conversation.ChatResponse{
@@ -1178,7 +1178,7 @@ func TestChannelInboundProcessorProcessingStatusFailureLifecycle(t *testing.T) {
registry := channel.NewRegistry()
registry.MustRegister(&fakeProcessingStatusAdapter{notifier: notifier})
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-2"}}
- policySvc := &fakePolicyService{botType: "public"}
+ policySvc := &fakePolicyService{}
chatSvc := &fakeChatService{resolveResult: route.ResolveConversationResult{ChatID: "chat-2", RouteID: "route-2"}}
chatErr := errors.New("chat gateway unavailable")
gateway := &fakeChatGateway{err: chatErr}
@@ -1223,7 +1223,7 @@ func TestChannelInboundProcessorProcessingStatusErrorsAreBestEffort(t *testing.T
registry := channel.NewRegistry()
registry.MustRegister(&fakeProcessingStatusAdapter{notifier: notifier})
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-3"}}
- policySvc := &fakePolicyService{botType: "public"}
+ policySvc := &fakePolicyService{}
chatSvc := &fakeChatService{resolveResult: route.ResolveConversationResult{ChatID: "chat-3", RouteID: "route-3"}}
gateway := &fakeChatGateway{
resp: conversation.ChatResponse{
@@ -1270,7 +1270,7 @@ func TestChannelInboundProcessorProcessingFailedNotifyErrorDoesNotOverrideChatEr
registry := channel.NewRegistry()
registry.MustRegister(&fakeProcessingStatusAdapter{notifier: notifier})
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-4"}}
- policySvc := &fakePolicyService{botType: "public"}
+ policySvc := &fakePolicyService{}
chatSvc := &fakeChatService{resolveResult: route.ResolveConversationResult{ChatID: "chat-4", RouteID: "route-4"}}
chatErr := errors.New("chat failed")
gateway := &fakeChatGateway{err: chatErr}
diff --git a/internal/channel/inbound/identity.go b/internal/channel/inbound/identity.go
index 0879cd49..ec3008b3 100644
--- a/internal/channel/inbound/identity.go
+++ b/internal/channel/inbound/identity.go
@@ -28,7 +28,6 @@ type InboundIdentity struct {
UserID string
DisplayName string
AvatarURL string
- BotType string
ForceReply bool
}
@@ -68,7 +67,6 @@ type ChannelIdentityService interface {
// PolicyService resolves access policy for a bot.
type PolicyService interface {
- BotType(ctx context.Context, botID string) (string, error)
BotOwnerUserID(ctx context.Context, botID string) (string, error)
}
@@ -182,57 +180,18 @@ func (r *IdentityResolver) Resolve(ctx context.Context, cfg channel.ChannelConfi
return state, err
}
- // Personal bots are owner-only and must not depend on member/guest/preauth bypass.
- if r.policy != nil {
- botType, err := r.policy.BotType(ctx, botID)
- if err != nil {
- return state, err
- }
- state.Identity.BotType = botType
- if strings.EqualFold(strings.TrimSpace(botType), "personal") {
- ownerUserID, err := r.policy.BotOwnerUserID(ctx, botID)
- if err != nil {
- return state, err
- }
- isOwner := strings.TrimSpace(state.Identity.UserID) != "" &&
- strings.TrimSpace(ownerUserID) == strings.TrimSpace(state.Identity.UserID)
- if !isOwner {
- // Ignore all non-owner messages for personal bots.
- state.Decision = &IdentityDecision{Stop: true}
- return state, nil
- }
- // Owner is authorized, but group trigger policy is still decided by
- // shouldTriggerAssistantResponse in channel routing.
- return state, nil
- }
- }
-
+ // Owner bypass — owner messages always pass identity resolution.
if r.policy != nil && strings.TrimSpace(state.Identity.UserID) != "" {
ownerUserID, err := r.policy.BotOwnerUserID(ctx, botID)
if err != nil {
return state, err
}
- // Bot owner should not depend on bot_members linkage.
if strings.TrimSpace(ownerUserID) == strings.TrimSpace(state.Identity.UserID) {
return state, nil
}
}
- if strings.EqualFold(strings.TrimSpace(state.Identity.BotType), "public") {
- return state, nil
- }
-
- // In group conversations, silently drop unauthorized messages to avoid spamming
- // the channel with "access denied" replies (same behavior as personal bot non-owner).
- if isGroupConversationType(msg.Conversation.Type) {
- state.Decision = &IdentityDecision{Stop: true}
- return state, nil
- }
-
- state.Decision = &IdentityDecision{
- Stop: true,
- Reply: channel.Message{Text: r.unboundReply},
- }
+ // Non-owner messages pass identity resolution; downstream ACL decides allow/deny.
return state, nil
}
diff --git a/internal/channel/inbound/identity_test.go b/internal/channel/inbound/identity_test.go
index 90c86c45..91137d9a 100644
--- a/internal/channel/inbound/identity_test.go
+++ b/internal/channel/inbound/identity_test.go
@@ -4,7 +4,6 @@ import (
"context"
"errors"
"log/slog"
- "strings"
"testing"
"time"
@@ -69,29 +68,10 @@ func (f *fakeChannelIdentityService) LinkChannelIdentityToUser(_ context.Context
}
type fakePolicyService struct {
- allow bool
- botType string
ownerUserID string
err error
}
-func (f *fakePolicyService) AllowGuest(_ context.Context, _ string) (bool, error) {
- if f.err != nil {
- return false, f.err
- }
- return f.allow, nil
-}
-
-func (f *fakePolicyService) BotType(_ context.Context, _ string) (string, error) {
- if f.err != nil {
- return "", f.err
- }
- if strings.TrimSpace(f.botType) == "" {
- return "public", nil
- }
- return f.botType, nil
-}
-
func (f *fakePolicyService) BotOwnerUserID(_ context.Context, _ string) (string, error) {
if f.err != nil {
return "", f.err
@@ -163,7 +143,7 @@ func (f *fakeDirectoryAdapter) ResolveEntry(ctx context.Context, cfg channel.Cha
func TestIdentityResolverAllowGuestWithoutMembershipSideEffect(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-1"}}
- policySvc := &fakePolicyService{allow: true, botType: "public"}
+ policySvc := &fakePolicyService{}
resolver := NewIdentityResolver(slog.Default(), nil, channelIdentitySvc, policySvc, nil, "")
msg := channel.InboundMessage{
@@ -207,7 +187,7 @@ func TestIdentityResolverResolveDisplayNameFromDirectory(t *testing.T) {
}
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-directory"}}
- policySvc := &fakePolicyService{allow: false, botType: "public"}
+ policySvc := &fakePolicyService{}
resolver := NewIdentityResolver(slog.Default(), registry, channelIdentitySvc, policySvc, nil, "")
msg := channel.InboundMessage{
@@ -247,7 +227,7 @@ func TestIdentityResolverDirectoryLookupFailureDoesNotFallbackToOpenID(t *testin
}
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-directory-fail"}}
- policySvc := &fakePolicyService{allow: false, botType: "public"}
+ policySvc := &fakePolicyService{}
resolver := NewIdentityResolver(slog.Default(), registry, channelIdentitySvc, policySvc, nil, "")
msg := channel.InboundMessage{
@@ -282,7 +262,7 @@ func TestIdentityResolverFeishuUsesOpenIDAsCanonicalSubject(t *testing.T) {
"u-userid": {ID: "channelIdentity-userid"},
},
}
- policySvc := &fakePolicyService{allow: false, botType: "public"}
+ policySvc := &fakePolicyService{}
resolver := NewIdentityResolver(slog.Default(), nil, channelIdentitySvc, policySvc, nil, "")
msg := channel.InboundMessage{
@@ -327,7 +307,7 @@ func TestIdentityResolverDirectoryAvatarURLPropagated(t *testing.T) {
}
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-avatar"}}
- policySvc := &fakePolicyService{allow: false, botType: "public"}
+ policySvc := &fakePolicyService{}
resolver := NewIdentityResolver(slog.Default(), registry, channelIdentitySvc, policySvc, nil, "")
msg := channel.InboundMessage{
@@ -360,7 +340,7 @@ func TestIdentityResolverDirectoryAvatarURLPropagated(t *testing.T) {
func TestIdentityResolverExistingMemberPasses(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-2"}}
- policySvc := &fakePolicyService{allow: false, botType: "public"}
+ policySvc := &fakePolicyService{}
resolver := NewIdentityResolver(slog.Default(), nil, channelIdentitySvc, policySvc, nil, "")
msg := channel.InboundMessage{
@@ -381,7 +361,7 @@ func TestIdentityResolverExistingMemberPasses(t *testing.T) {
func TestIdentityResolverPublicBotGuestPasses(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-5"}}
- policySvc := &fakePolicyService{allow: false, botType: "public"}
+ policySvc := &fakePolicyService{}
resolver := NewIdentityResolver(slog.Default(), nil, channelIdentitySvc, policySvc, nil, "Access denied.")
msg := channel.InboundMessage{
@@ -400,9 +380,9 @@ func TestIdentityResolverPublicBotGuestPasses(t *testing.T) {
}
}
-func TestIdentityResolverPersonalBotRejectsGroupMessages(t *testing.T) {
+func TestIdentityResolverNonOwnerGroupMessagePassesToACL(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-group"}}
- policySvc := &fakePolicyService{allow: false, botType: "personal", ownerUserID: "channelIdentity-owner"}
+ policySvc := &fakePolicyService{ownerUserID: "channelIdentity-owner"}
resolver := NewIdentityResolver(slog.Default(), nil, channelIdentitySvc, policySvc, nil, "")
msg := channel.InboundMessage{
@@ -420,20 +400,14 @@ func TestIdentityResolverPersonalBotRejectsGroupMessages(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
- if state.Decision == nil || !state.Decision.Stop {
- t.Fatal("personal bot should reject group messages")
- }
- if channelIdentitySvc.calls != 1 {
- t.Fatalf("expected channelIdentity resolution once before owner check, got %d", channelIdentitySvc.calls)
- }
- if !state.Decision.Reply.IsEmpty() {
- t.Fatal("non-owner group message should be silently ignored")
+ if state.Decision != nil {
+ t.Fatal("non-owner group message should pass identity resolution (ACL decides later)")
}
}
func TestIdentityResolverPersonalBotAllowsOwnerInGroup(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-owner"}}
- policySvc := &fakePolicyService{allow: false, botType: "personal", ownerUserID: "channelIdentity-owner"}
+ policySvc := &fakePolicyService{ownerUserID: "channelIdentity-owner"}
resolver := NewIdentityResolver(slog.Default(), nil, channelIdentitySvc, policySvc, nil, "")
msg := channel.InboundMessage{
@@ -461,7 +435,7 @@ func TestIdentityResolverPersonalBotAllowsOwnerInGroup(t *testing.T) {
func TestIdentityResolverPersonalBotAllowsOwnerDirectWithoutMembership(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-owner-direct"}}
- policySvc := &fakePolicyService{allow: false, botType: "personal", ownerUserID: "channelIdentity-owner-direct"}
+ policySvc := &fakePolicyService{ownerUserID: "channelIdentity-owner-direct"}
resolver := NewIdentityResolver(slog.Default(), nil, channelIdentitySvc, policySvc, nil, "")
msg := channel.InboundMessage{
@@ -487,7 +461,7 @@ func TestIdentityResolverPersonalBotAllowsOwnerDirectWithoutMembership(t *testin
}
}
-func TestIdentityResolverPersonalBotDoesNotFallbackToFeishuUserID(t *testing.T) {
+func TestIdentityResolverFeishuUnlinkedOpenIDPassesToACL(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{
bySubject: map[string]identities.ChannelIdentity{
"ou-open-owner": {ID: "channelIdentity-open-owner"},
@@ -497,7 +471,7 @@ func TestIdentityResolverPersonalBotDoesNotFallbackToFeishuUserID(t *testing.T)
"channelIdentity-user-owner": "owner-user-1",
},
}
- policySvc := &fakePolicyService{allow: false, botType: "personal", ownerUserID: "owner-user-1"}
+ policySvc := &fakePolicyService{ownerUserID: "owner-user-1"}
resolver := NewIdentityResolver(slog.Default(), nil, channelIdentitySvc, policySvc, nil, "")
msg := channel.InboundMessage{
@@ -521,11 +495,9 @@ func TestIdentityResolverPersonalBotDoesNotFallbackToFeishuUserID(t *testing.T)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
- if state.Decision == nil || !state.Decision.Stop {
- t.Fatal("personal bot should deny when only feishu user_id is linked")
- }
- if state.Identity.UserID != "" {
- t.Fatalf("expected no linked owner user via user_id fallback, got: %s", state.Identity.UserID)
+ // Without linked user, non-owner messages pass identity resolution; ACL decides later.
+ if state.Decision != nil {
+ t.Fatal("unlinked user should pass identity resolution (ACL decides later)")
}
if state.Identity.ChannelIdentityID != "channelIdentity-open-owner" {
t.Fatalf("expected open_id identity, got: %s", state.Identity.ChannelIdentityID)
@@ -535,9 +507,9 @@ func TestIdentityResolverPersonalBotDoesNotFallbackToFeishuUserID(t *testing.T)
}
}
-func TestIdentityResolverPersonalBotRejectsNonOwnerDirectEvenIfMember(t *testing.T) {
+func TestIdentityResolverNonOwnerDirectPassesToACL(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-non-owner"}}
- policySvc := &fakePolicyService{allow: true, botType: "personal", ownerUserID: "channelIdentity-owner"}
+ policySvc := &fakePolicyService{ownerUserID: "channelIdentity-owner"}
resolver := NewIdentityResolver(slog.Default(), nil, channelIdentitySvc, policySvc, nil, "Access denied.")
msg := channel.InboundMessage{
@@ -555,11 +527,8 @@ func TestIdentityResolverPersonalBotRejectsNonOwnerDirectEvenIfMember(t *testing
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
- if state.Decision == nil || !state.Decision.Stop {
- t.Fatal("non-owner direct message should be rejected for personal bot")
- }
- if !state.Decision.Reply.IsEmpty() {
- t.Fatal("non-owner direct message should be silently ignored")
+ if state.Decision != nil {
+ t.Fatal("non-owner direct message should pass identity resolution (ACL decides later)")
}
}
@@ -685,7 +654,7 @@ func TestIdentityResolverBindCodeNotScopedToCurrentBot(t *testing.T) {
func TestIdentityResolverPublicBotGroupGuestPasses(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-group-denied"}}
- policySvc := &fakePolicyService{allow: false, botType: "public"}
+ policySvc := &fakePolicyService{}
resolver := NewIdentityResolver(slog.Default(), nil, channelIdentitySvc, policySvc, nil, "Access denied.")
msg := channel.InboundMessage{
@@ -710,7 +679,7 @@ func TestIdentityResolverPublicBotGroupGuestPasses(t *testing.T) {
func TestIdentityResolverPublicBotDirectGuestPasses(t *testing.T) {
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-direct-denied"}}
- policySvc := &fakePolicyService{allow: false, botType: "public"}
+ policySvc := &fakePolicyService{}
resolver := NewIdentityResolver(slog.Default(), nil, channelIdentitySvc, policySvc, nil, "Access denied.")
msg := channel.InboundMessage{
diff --git a/internal/db/sqlc/bots.sql.go b/internal/db/sqlc/bots.sql.go
index 91091c4a..a23de393 100644
--- a/internal/db/sqlc/bots.sql.go
+++ b/internal/db/sqlc/bots.sql.go
@@ -12,14 +12,13 @@ import (
)
const createBot = `-- name: CreateBot :one
-INSERT INTO bots (owner_user_id, type, display_name, avatar_url, is_active, metadata, status)
-VALUES ($1, $2, $3, $4, $5, $6, $7)
-RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at
+INSERT INTO bots (owner_user_id, display_name, avatar_url, is_active, metadata, status)
+VALUES ($1, $2, $3, $4, $5, $6)
+RETURNING id, owner_user_id, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at
`
type CreateBotParams struct {
OwnerUserID pgtype.UUID `json:"owner_user_id"`
- Type string `json:"type"`
DisplayName pgtype.Text `json:"display_name"`
AvatarUrl pgtype.Text `json:"avatar_url"`
IsActive bool `json:"is_active"`
@@ -30,7 +29,6 @@ type CreateBotParams struct {
type CreateBotRow struct {
ID pgtype.UUID `json:"id"`
OwnerUserID pgtype.UUID `json:"owner_user_id"`
- Type string `json:"type"`
DisplayName pgtype.Text `json:"display_name"`
AvatarUrl pgtype.Text `json:"avatar_url"`
IsActive bool `json:"is_active"`
@@ -55,7 +53,6 @@ type CreateBotRow struct {
func (q *Queries) CreateBot(ctx context.Context, arg CreateBotParams) (CreateBotRow, error) {
row := q.db.QueryRow(ctx, createBot,
arg.OwnerUserID,
- arg.Type,
arg.DisplayName,
arg.AvatarUrl,
arg.IsActive,
@@ -66,7 +63,6 @@ func (q *Queries) CreateBot(ctx context.Context, arg CreateBotParams) (CreateBot
err := row.Scan(
&i.ID,
&i.OwnerUserID,
- &i.Type,
&i.DisplayName,
&i.AvatarUrl,
&i.IsActive,
@@ -100,7 +96,7 @@ func (q *Queries) DeleteBotByID(ctx context.Context, id pgtype.UUID) error {
}
const getBotByID = `-- name: GetBotByID :one
-SELECT id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at
+SELECT id, owner_user_id, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at
FROM bots
WHERE id = $1
`
@@ -108,7 +104,6 @@ WHERE id = $1
type GetBotByIDRow struct {
ID pgtype.UUID `json:"id"`
OwnerUserID pgtype.UUID `json:"owner_user_id"`
- Type string `json:"type"`
DisplayName pgtype.Text `json:"display_name"`
AvatarUrl pgtype.Text `json:"avatar_url"`
IsActive bool `json:"is_active"`
@@ -136,7 +131,6 @@ func (q *Queries) GetBotByID(ctx context.Context, id pgtype.UUID) (GetBotByIDRow
err := row.Scan(
&i.ID,
&i.OwnerUserID,
- &i.Type,
&i.DisplayName,
&i.AvatarUrl,
&i.IsActive,
@@ -161,7 +155,7 @@ func (q *Queries) GetBotByID(ctx context.Context, id pgtype.UUID) (GetBotByIDRow
}
const listBotsByOwner = `-- name: ListBotsByOwner :many
-SELECT id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at
+SELECT id, owner_user_id, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at
FROM bots
WHERE owner_user_id = $1
ORDER BY created_at DESC
@@ -170,7 +164,6 @@ ORDER BY created_at DESC
type ListBotsByOwnerRow struct {
ID pgtype.UUID `json:"id"`
OwnerUserID pgtype.UUID `json:"owner_user_id"`
- Type string `json:"type"`
DisplayName pgtype.Text `json:"display_name"`
AvatarUrl pgtype.Text `json:"avatar_url"`
IsActive bool `json:"is_active"`
@@ -204,7 +197,6 @@ func (q *Queries) ListBotsByOwner(ctx context.Context, ownerUserID pgtype.UUID)
if err := rows.Scan(
&i.ID,
&i.OwnerUserID,
- &i.Type,
&i.DisplayName,
&i.AvatarUrl,
&i.IsActive,
@@ -280,7 +272,7 @@ UPDATE bots
SET owner_user_id = $2,
updated_at = now()
WHERE id = $1
-RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at
+RETURNING id, owner_user_id, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at
`
type UpdateBotOwnerParams struct {
@@ -291,7 +283,6 @@ type UpdateBotOwnerParams struct {
type UpdateBotOwnerRow struct {
ID pgtype.UUID `json:"id"`
OwnerUserID pgtype.UUID `json:"owner_user_id"`
- Type string `json:"type"`
DisplayName pgtype.Text `json:"display_name"`
AvatarUrl pgtype.Text `json:"avatar_url"`
IsActive bool `json:"is_active"`
@@ -319,7 +310,6 @@ func (q *Queries) UpdateBotOwner(ctx context.Context, arg UpdateBotOwnerParams)
err := row.Scan(
&i.ID,
&i.OwnerUserID,
- &i.Type,
&i.DisplayName,
&i.AvatarUrl,
&i.IsActive,
@@ -351,7 +341,7 @@ SET display_name = $2,
metadata = $5,
updated_at = now()
WHERE id = $1
-RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at
+RETURNING id, owner_user_id, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at
`
type UpdateBotProfileParams struct {
@@ -365,7 +355,6 @@ type UpdateBotProfileParams struct {
type UpdateBotProfileRow struct {
ID pgtype.UUID `json:"id"`
OwnerUserID pgtype.UUID `json:"owner_user_id"`
- Type string `json:"type"`
DisplayName pgtype.Text `json:"display_name"`
AvatarUrl pgtype.Text `json:"avatar_url"`
IsActive bool `json:"is_active"`
@@ -399,7 +388,6 @@ func (q *Queries) UpdateBotProfile(ctx context.Context, arg UpdateBotProfilePara
err := row.Scan(
&i.ID,
&i.OwnerUserID,
- &i.Type,
&i.DisplayName,
&i.AvatarUrl,
&i.IsActive,
diff --git a/internal/db/sqlc/conversations.sql.go b/internal/db/sqlc/conversations.sql.go
index 58ebe8d8..5d09117d 100644
--- a/internal/db/sqlc/conversations.sql.go
+++ b/internal/db/sqlc/conversations.sql.go
@@ -15,7 +15,7 @@ const createChat = `-- name: CreateChat :one
SELECT
b.id AS id,
b.id AS bot_id,
- (COALESCE(NULLIF($1::text, ''), CASE WHEN b.type = 'public' THEN 'group' ELSE 'direct' END))::text AS kind,
+ (COALESCE(NULLIF($1::text, ''), 'direct'))::text AS kind,
CASE WHEN $1 = 'thread' THEN $2::uuid ELSE NULL::uuid END AS parent_chat_id,
COALESCE(NULLIF($3::text, ''), b.display_name) AS title,
COALESCE($4::uuid, b.owner_user_id) AS created_by_user_id,
@@ -94,7 +94,7 @@ const getChatByID = `-- name: GetChatByID :one
SELECT
b.id AS id,
b.id AS bot_id,
- CASE WHEN b.type = 'public' THEN 'group' ELSE 'direct' END AS kind,
+ 'direct'::text AS kind,
NULL::uuid AS parent_chat_id,
b.display_name AS title,
b.owner_user_id AS created_by_user_id,
@@ -264,7 +264,7 @@ const listChatsByBotAndUser = `-- name: ListChatsByBotAndUser :many
SELECT
b.id AS id,
b.id AS bot_id,
- CASE WHEN b.type = 'public' THEN 'group' ELSE 'direct' END AS kind,
+ 'direct'::text AS kind,
NULL::uuid AS parent_chat_id,
b.display_name AS title,
b.owner_user_id AS created_by_user_id,
@@ -332,7 +332,7 @@ const listThreadsByParent = `-- name: ListThreadsByParent :many
SELECT
b.id AS id,
b.id AS bot_id,
- CASE WHEN b.type = 'public' THEN 'group' ELSE 'direct' END AS kind,
+ 'direct'::text AS kind,
NULL::uuid AS parent_chat_id,
b.display_name AS title,
b.owner_user_id AS created_by_user_id,
@@ -394,7 +394,7 @@ const listVisibleChatsByBotAndUser = `-- name: ListVisibleChatsByBotAndUser :man
SELECT
b.id AS id,
b.id AS bot_id,
- CASE WHEN b.type = 'public' THEN 'group' ELSE 'direct' END AS kind,
+ 'direct'::text AS kind,
NULL::uuid AS parent_chat_id,
b.display_name AS title,
b.owner_user_id AS created_by_user_id,
@@ -405,7 +405,7 @@ SELECT
'participant'::text AS access_mode,
(CASE
WHEN b.owner_user_id = $1 THEN 'owner'
- ELSE COALESCE(bm.role, ''::text)
+ ELSE ''::text
END)::text AS participant_role,
NULL::timestamptz AS last_observed_at
FROM bots b
@@ -507,12 +507,12 @@ WITH updated AS (
SET display_name = $1,
updated_at = now()
WHERE bots.id = $2
- RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, language, reasoning_enabled, reasoning_effort, max_inbox_items, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, heartbeat_model_id, tts_model_id, browser_context_id, metadata, created_at, updated_at
+ RETURNING id, owner_user_id, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, language, reasoning_enabled, reasoning_effort, max_inbox_items, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, heartbeat_model_id, tts_model_id, browser_context_id, metadata, created_at, updated_at
)
SELECT
updated.id AS id,
updated.id AS bot_id,
- CASE WHEN updated.type = 'public' THEN 'group' ELSE 'direct' END AS kind,
+ 'direct'::text AS kind,
NULL::uuid AS parent_chat_id,
updated.display_name AS title,
updated.owner_user_id AS created_by_user_id,
diff --git a/internal/db/sqlc/models.go b/internal/db/sqlc/models.go
index 7fec54f3..8dbf1243 100644
--- a/internal/db/sqlc/models.go
+++ b/internal/db/sqlc/models.go
@@ -11,7 +11,6 @@ import (
type Bot struct {
ID pgtype.UUID `json:"id"`
OwnerUserID pgtype.UUID `json:"owner_user_id"`
- Type string `json:"type"`
DisplayName pgtype.Text `json:"display_name"`
AvatarUrl pgtype.Text `json:"avatar_url"`
IsActive bool `json:"is_active"`
diff --git a/internal/handlers/acl.go b/internal/handlers/acl.go
index 28e0445f..2efdc815 100644
--- a/internal/handlers/acl.go
+++ b/internal/handlers/acl.go
@@ -304,7 +304,7 @@ func (h *ACLHandler) requireManageAccess(c echo.Context) (string, string, error)
if botID == "" {
return "", "", echo.NewHTTPError(http.StatusBadRequest, "bot id is required")
}
- if _, err := AuthorizeBotAccess(c.Request().Context(), h.botService, h.accountService, actorID, botID, bots.AccessPolicy{}); err != nil {
+ if _, err := AuthorizeBotAccess(c.Request().Context(), h.botService, h.accountService, actorID, botID); err != nil {
return "", "", err
}
return botID, actorID, nil
diff --git a/internal/handlers/containerd.go b/internal/handlers/containerd.go
index faff303e..fd5ce7cb 100644
--- a/internal/handlers/containerd.go
+++ b/internal/handlers/containerd.go
@@ -849,11 +849,11 @@ func (*ContainerdHandler) requireChannelIdentityID(c echo.Context) (string, erro
}
func (h *ContainerdHandler) authorizeBotAccess(ctx context.Context, channelIdentityID, botID string) (bots.Bot, error) {
- return AuthorizeBotAccess(ctx, h.botService, h.accountService, channelIdentityID, botID, bots.AccessPolicy{})
+ return AuthorizeBotAccess(ctx, h.botService, h.accountService, channelIdentityID, botID)
}
// requireBotAccessWithGuest is like requireBotAccess but also allows guest access
-// for public bots when the caller explicitly opts into guest-compatible access.
+// via ACL when the caller explicitly opts into guest-compatible access.
func (h *ContainerdHandler) requireBotAccessWithGuest(c echo.Context) (string, error) {
channelIdentityID, err := h.requireChannelIdentityID(c)
if err != nil {
@@ -863,8 +863,7 @@ func (h *ContainerdHandler) requireBotAccessWithGuest(c echo.Context) (string, e
if botID == "" {
return "", echo.NewHTTPError(http.StatusBadRequest, "bot id is required")
}
- policy := bots.AccessPolicy{AllowGuest: true}
- if _, err := AuthorizeBotAccess(c.Request().Context(), h.botService, h.accountService, channelIdentityID, botID, policy); err != nil {
+ if _, err := AuthorizeBotAccess(c.Request().Context(), h.botService, h.accountService, channelIdentityID, botID); err != nil {
return "", err
}
return botID, nil
diff --git a/internal/handlers/handler_helpers.go b/internal/handlers/handler_helpers.go
index e08500c4..06df3c77 100644
--- a/internal/handlers/handler_helpers.go
+++ b/internal/handlers/handler_helpers.go
@@ -25,8 +25,8 @@ func RequireChannelIdentityID(c echo.Context) (string, error) {
return channelIdentityID, nil
}
-// AuthorizeBotAccess validates that the given identity has access to the specified bot.
-func AuthorizeBotAccess(ctx context.Context, botService *bots.Service, accountService *accounts.Service, channelIdentityID, botID string, policy bots.AccessPolicy) (bots.Bot, error) {
+// AuthorizeBotAccess validates that the given identity has owner/admin access to the specified bot.
+func AuthorizeBotAccess(ctx context.Context, botService *bots.Service, accountService *accounts.Service, channelIdentityID, botID string) (bots.Bot, error) {
if botService == nil || accountService == nil {
return bots.Bot{}, echo.NewHTTPError(http.StatusInternalServerError, "bot services not configured")
}
@@ -34,7 +34,7 @@ func AuthorizeBotAccess(ctx context.Context, botService *bots.Service, accountSe
if err != nil {
return bots.Bot{}, echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
- bot, err := botService.AuthorizeAccess(ctx, channelIdentityID, botID, isAdmin, policy)
+ bot, err := botService.AuthorizeAccess(ctx, channelIdentityID, botID, isAdmin)
if err != nil {
if errors.Is(err, bots.ErrBotNotFound) {
return bots.Bot{}, echo.NewHTTPError(http.StatusNotFound, "bot not found")
diff --git a/internal/handlers/heartbeat.go b/internal/handlers/heartbeat.go
index f343b6ae..6b09ed9d 100644
--- a/internal/handlers/heartbeat.go
+++ b/internal/handlers/heartbeat.go
@@ -115,5 +115,5 @@ func (*HeartbeatHandler) requireUserID(c echo.Context) (string, error) {
}
func (h *HeartbeatHandler) authorizeBotAccess(ctx context.Context, userID, botID string) (bots.Bot, error) {
- return AuthorizeBotAccess(ctx, h.botService, h.accountService, userID, botID, bots.AccessPolicy{})
+ return AuthorizeBotAccess(ctx, h.botService, h.accountService, userID, botID)
}
diff --git a/internal/handlers/inbox.go b/internal/handlers/inbox.go
index 52dcc4ed..4ab8adf2 100644
--- a/internal/handlers/inbox.go
+++ b/internal/handlers/inbox.go
@@ -252,7 +252,7 @@ func (h *InboxHandler) Count(c echo.Context) error {
}
func (h *InboxHandler) authorizeBotAccess(ctx context.Context, channelIdentityID, botID string) (bots.Bot, error) {
- return AuthorizeBotAccess(ctx, h.botService, h.accountService, channelIdentityID, botID, bots.AccessPolicy{})
+ return AuthorizeBotAccess(ctx, h.botService, h.accountService, channelIdentityID, botID)
}
func parseIntOr(s string, fallback int) int {
diff --git a/internal/handlers/local_channel.go b/internal/handlers/local_channel.go
index ba91aeb7..f913d66c 100644
--- a/internal/handlers/local_channel.go
+++ b/internal/handlers/local_channel.go
@@ -453,7 +453,7 @@ func (*LocalChannelHandler) requireChannelIdentityID(c echo.Context) (string, er
}
func (h *LocalChannelHandler) authorizeBotAccess(ctx context.Context, channelIdentityID, botID string) (bots.Bot, error) {
- return AuthorizeBotAccess(ctx, h.botService, h.accountService, channelIdentityID, botID, bots.AccessPolicy{AllowGuest: true})
+ return AuthorizeBotAccess(ctx, h.botService, h.accountService, channelIdentityID, botID)
}
// ---------------------------------------------------------------------------
diff --git a/internal/handlers/mcp.go b/internal/handlers/mcp.go
index 440edf06..4e2650c6 100644
--- a/internal/handlers/mcp.go
+++ b/internal/handlers/mcp.go
@@ -410,5 +410,5 @@ func (*MCPHandler) requireChannelIdentityID(c echo.Context) (string, error) {
}
func (h *MCPHandler) authorizeBotAccess(ctx context.Context, channelIdentityID, botID string) (bots.Bot, error) {
- return AuthorizeBotAccess(ctx, h.botService, h.accountService, channelIdentityID, botID, bots.AccessPolicy{})
+ return AuthorizeBotAccess(ctx, h.botService, h.accountService, channelIdentityID, botID)
}
diff --git a/internal/handlers/mcp_oauth.go b/internal/handlers/mcp_oauth.go
index b6039687..b243fd8d 100644
--- a/internal/handlers/mcp_oauth.go
+++ b/internal/handlers/mcp_oauth.go
@@ -245,5 +245,5 @@ func (*MCPOAuthHandler) requireChannelIdentityID(c echo.Context) (string, error)
}
func (h *MCPOAuthHandler) authorizeBotAccess(ctx context.Context, channelIdentityID, botID string) (bots.Bot, error) {
- return AuthorizeBotAccess(ctx, h.botService, h.accountService, channelIdentityID, botID, bots.AccessPolicy{})
+ return AuthorizeBotAccess(ctx, h.botService, h.accountService, channelIdentityID, botID)
}
diff --git a/internal/handlers/memory.go b/internal/handlers/memory.go
index 29e4a376..b9844990 100644
--- a/internal/handlers/memory.go
+++ b/internal/handlers/memory.go
@@ -664,7 +664,7 @@ func (h *MemoryHandler) requireBotAccess(c echo.Context) (string, error) {
if err != nil {
return "", err
}
- if _, err := AuthorizeBotAccess(c.Request().Context(), h.botService, h.accountService, channelIdentityID, botID, bots.AccessPolicy{}); err != nil {
+ if _, err := AuthorizeBotAccess(c.Request().Context(), h.botService, h.accountService, channelIdentityID, botID); err != nil {
return "", err
}
return botID, nil
diff --git a/internal/handlers/message.go b/internal/handlers/message.go
index c2552739..62160021 100644
--- a/internal/handlers/message.go
+++ b/internal/handlers/message.go
@@ -354,11 +354,11 @@ func (*MessageHandler) requireChannelIdentityID(c echo.Context) (string, error)
}
func (h *MessageHandler) authorizeBotAccess(ctx context.Context, channelIdentityID, botID string) (bots.Bot, error) {
- return AuthorizeBotAccess(ctx, h.botService, h.accountService, channelIdentityID, botID, bots.AccessPolicy{AllowGuest: true})
+ return AuthorizeBotAccess(ctx, h.botService, h.accountService, channelIdentityID, botID)
}
func (h *MessageHandler) authorizeBotManage(ctx context.Context, channelIdentityID, botID string) (bots.Bot, error) {
- return AuthorizeBotAccess(ctx, h.botService, h.accountService, channelIdentityID, botID, bots.AccessPolicy{})
+ return AuthorizeBotAccess(ctx, h.botService, h.accountService, channelIdentityID, botID)
}
func (h *MessageHandler) requireReadable(ctx context.Context, conversationID, channelIdentityID string) error {
diff --git a/internal/handlers/schedule.go b/internal/handlers/schedule.go
index 05c80158..cbcd0064 100644
--- a/internal/handlers/schedule.go
+++ b/internal/handlers/schedule.go
@@ -220,5 +220,5 @@ func (*ScheduleHandler) requireUserID(c echo.Context) (string, error) {
}
func (h *ScheduleHandler) authorizeBotAccess(ctx context.Context, userID, botID string) (bots.Bot, error) {
- return AuthorizeBotAccess(ctx, h.botService, h.accountService, userID, botID, bots.AccessPolicy{})
+ return AuthorizeBotAccess(ctx, h.botService, h.accountService, userID, botID)
}
diff --git a/internal/handlers/settings.go b/internal/handlers/settings.go
index 684e8f75..4569a27c 100644
--- a/internal/handlers/settings.go
+++ b/internal/handlers/settings.go
@@ -96,9 +96,6 @@ func (h *SettingsHandler) Upsert(c echo.Context) error {
}
resp, err := h.service.UpsertBot(c.Request().Context(), botID, req)
if err != nil {
- if errors.Is(err, settings.ErrPersonalBotGuestAccessUnsupported) {
- return echo.NewHTTPError(http.StatusBadRequest, "personal bot does not support guest access")
- }
if errors.Is(err, settings.ErrInvalidModelRef) {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
@@ -148,5 +145,5 @@ func (*SettingsHandler) requireChannelIdentityID(c echo.Context) (string, error)
}
func (h *SettingsHandler) authorizeBotAccess(ctx context.Context, channelIdentityID, botID string) (bots.Bot, error) {
- return AuthorizeBotAccess(ctx, h.botService, h.accountService, channelIdentityID, botID, bots.AccessPolicy{})
+ return AuthorizeBotAccess(ctx, h.botService, h.accountService, channelIdentityID, botID)
}
diff --git a/internal/handlers/subagent.go b/internal/handlers/subagent.go
index 7de1e013..50e1b46e 100644
--- a/internal/handlers/subagent.go
+++ b/internal/handlers/subagent.go
@@ -434,5 +434,5 @@ func (*SubagentHandler) requireChannelIdentityID(c echo.Context) (string, error)
}
func (h *SubagentHandler) authorizeBotAccess(ctx context.Context, channelIdentityID, botID string) (bots.Bot, error) {
- return AuthorizeBotAccess(ctx, h.botService, h.accountService, channelIdentityID, botID, bots.AccessPolicy{})
+ return AuthorizeBotAccess(ctx, h.botService, h.accountService, channelIdentityID, botID)
}
diff --git a/internal/handlers/token_usage.go b/internal/handlers/token_usage.go
index 86618430..a2a26e3b 100644
--- a/internal/handlers/token_usage.go
+++ b/internal/handlers/token_usage.go
@@ -85,7 +85,7 @@ func (h *TokenUsageHandler) GetTokenUsage(c echo.Context) error {
if botID == "" {
return echo.NewHTTPError(http.StatusBadRequest, "bot id is required")
}
- if _, err := AuthorizeBotAccess(c.Request().Context(), h.botService, h.accountService, userID, botID, bots.AccessPolicy{}); err != nil {
+ if _, err := AuthorizeBotAccess(c.Request().Context(), h.botService, h.accountService, userID, botID); err != nil {
return err
}
diff --git a/internal/handlers/users.go b/internal/handlers/users.go
index 35b97c63..64e1679c 100644
--- a/internal/handlers/users.go
+++ b/internal/handlers/users.go
@@ -938,7 +938,7 @@ func (h *UsersHandler) SendBotMessageSession(c echo.Context) error {
}
func (h *UsersHandler) authorizeBotAccess(ctx context.Context, channelIdentityID, botID string) (bots.Bot, error) {
- return AuthorizeBotAccess(ctx, h.botService, h.service, channelIdentityID, botID, bots.AccessPolicy{})
+ return AuthorizeBotAccess(ctx, h.botService, h.service, channelIdentityID, botID)
}
func (*UsersHandler) requireChannelIdentityID(c echo.Context) (string, error) {
diff --git a/internal/policy/service.go b/internal/policy/service.go
index e5eb0a73..a7b3a249 100644
--- a/internal/policy/service.go
+++ b/internal/policy/service.go
@@ -10,8 +10,7 @@ import (
)
type Decision struct {
- BotID string
- BotType string
+ BotID string
}
type Service struct {
@@ -38,24 +37,10 @@ func (s *Service) Resolve(ctx context.Context, botID string) (Decision, error) {
if botID == "" {
return Decision{}, errors.New("bot id is required")
}
- bot, err := s.bots.Get(ctx, botID)
- if err != nil {
+ if _, err := s.bots.Get(ctx, botID); err != nil {
return Decision{}, err
}
- decision := Decision{
- BotID: botID,
- BotType: strings.TrimSpace(bot.Type),
- }
- return decision, nil
-}
-
-// BotType returns the normalized bot type. Implements router.PolicyService.
-func (s *Service) BotType(ctx context.Context, botID string) (string, error) {
- decision, err := s.Resolve(ctx, botID)
- if err != nil {
- return "", err
- }
- return decision.BotType, nil
+ return Decision{BotID: botID}, nil
}
// BotOwnerUserID returns bot owner's user id. Implements router.PolicyService.
diff --git a/internal/settings/service.go b/internal/settings/service.go
index 01b39938..ea843b65 100644
--- a/internal/settings/service.go
+++ b/internal/settings/service.go
@@ -24,8 +24,7 @@ type Service struct {
}
var (
- ErrPersonalBotGuestAccessUnsupported = errors.New("personal bots do not support guest access")
- ErrModelIDAmbiguous = errors.New("model_id is ambiguous across providers")
+ ErrModelIDAmbiguous = errors.New("model_id is ambiguous across providers")
ErrInvalidModelRef = errors.New("invalid model reference")
)
@@ -67,8 +66,6 @@ func (s *Service) UpsertBot(ctx context.Context, botID string, req UpsertRequest
if err != nil {
return Settings{}, err
}
- isPersonalBot := strings.EqualFold(strings.TrimSpace(botRow.Type), "personal")
-
allowGuest, err := s.allowGuestEnabled(ctx, botID)
if err != nil {
return Settings{}, err
@@ -86,12 +83,7 @@ func (s *Service) UpsertBot(ctx context.Context, botID string, req UpsertRequest
if strings.TrimSpace(req.Language) != "" {
current.Language = strings.TrimSpace(req.Language)
}
- if isPersonalBot {
- if req.AllowGuest != nil && *req.AllowGuest {
- return Settings{}, ErrPersonalBotGuestAccessUnsupported
- }
- current.AllowGuest = false
- } else if req.AllowGuest != nil {
+ if req.AllowGuest != nil {
current.AllowGuest = *req.AllowGuest
}
if req.ReasoningEnabled != nil {
diff --git a/packages/sdk/src/types.gen.ts b/packages/sdk/src/types.gen.ts
index ebc604c2..72269806 100644
--- a/packages/sdk/src/types.gen.ts
+++ b/packages/sdk/src/types.gen.ts
@@ -305,7 +305,6 @@ export type BotsBot = {
};
owner_user_id?: string;
status?: string;
- type?: string;
updated_at?: string;
};
@@ -329,7 +328,6 @@ export type BotsCreateBotRequest = {
metadata?: {
[key: string]: unknown;
};
- type?: string;
};
export type BotsListBotsResponse = {
diff --git a/spec/docs.go b/spec/docs.go
index e4e7857a..009e0685 100644
--- a/spec/docs.go
+++ b/spec/docs.go
@@ -9589,9 +9589,6 @@ const docTemplate = `{
"status": {
"type": "string"
},
- "type": {
- "type": "string"
- },
"updated_at": {
"type": "string"
}
@@ -9642,9 +9639,6 @@ const docTemplate = `{
"metadata": {
"type": "object",
"additionalProperties": {}
- },
- "type": {
- "type": "string"
}
}
},
diff --git a/spec/swagger.json b/spec/swagger.json
index b11ea4f2..9b3c3e65 100644
--- a/spec/swagger.json
+++ b/spec/swagger.json
@@ -9580,9 +9580,6 @@
"status": {
"type": "string"
},
- "type": {
- "type": "string"
- },
"updated_at": {
"type": "string"
}
@@ -9633,9 +9630,6 @@
"metadata": {
"type": "object",
"additionalProperties": {}
- },
- "type": {
- "type": "string"
}
}
},
diff --git a/spec/swagger.yaml b/spec/swagger.yaml
index 3ed340e8..bd41330c 100644
--- a/spec/swagger.yaml
+++ b/spec/swagger.yaml
@@ -482,8 +482,6 @@ definitions:
type: string
status:
type: string
- type:
- type: string
updated_at:
type: string
type: object
@@ -518,8 +516,6 @@ definitions:
metadata:
additionalProperties: {}
type: object
- type:
- type: string
type: object
bots.ListBotsResponse:
properties: