refactor: inbox (#137)

* refactor: inbox

* fix: migrations

* fix: migrations
This commit is contained in:
Acbox Liu
2026-02-26 20:16:02 +08:00
committed by GitHub
parent d2878d841b
commit fe10abf3fc
21 changed files with 404 additions and 62 deletions
+2 -1
View File
@@ -90,6 +90,7 @@ export const MCPConnectionModel = z.union([HTTPMCPConnectionModel, SSEMCPConnect
export const InboxItemModel = z.object({ export const InboxItemModel = z.object({
id: z.string(), id: z.string(),
source: z.string(), source: z.string(),
content: z.record(z.string(), z.unknown()).default({}), header: z.record(z.string(), z.unknown()).default({}),
content: z.string().default(''),
createdAt: z.string(), createdAt: z.string(),
}) })
+4 -2
View File
@@ -399,12 +399,14 @@ CREATE TABLE IF NOT EXISTS bot_history_message_assets (
CREATE INDEX IF NOT EXISTS idx_message_assets_message_id ON bot_history_message_assets(message_id); CREATE INDEX IF NOT EXISTS idx_message_assets_message_id ON bot_history_message_assets(message_id);
-- bot_inbox: per-bot message inbox for non-mentioned group messages, emails, etc. -- bot_inbox: per-bot message inbox for channel messages, notifications, etc.
CREATE TABLE IF NOT EXISTS bot_inbox ( CREATE TABLE IF NOT EXISTS bot_inbox (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE, bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
source TEXT NOT NULL DEFAULT '', source TEXT NOT NULL DEFAULT '',
content JSONB NOT NULL DEFAULT '{}'::jsonb, header JSONB NOT NULL DEFAULT '{}'::jsonb,
content TEXT NOT NULL DEFAULT '',
action TEXT NOT NULL DEFAULT 'notify',
is_read BOOLEAN NOT NULL DEFAULT FALSE, is_read BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(), created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
read_at TIMESTAMPTZ read_at TIMESTAMPTZ
@@ -0,0 +1,11 @@
-- 0018_inbox_refactor (down)
-- Revert bot_inbox to original schema: merge header+content back into content JSONB.
-- 1. Convert content back to JSONB, merging header and text.
ALTER TABLE bot_inbox ALTER COLUMN content DROP DEFAULT;
ALTER TABLE bot_inbox ALTER COLUMN content TYPE JSONB USING (COALESCE(header, '{}'::jsonb) || jsonb_build_object('text', content));
ALTER TABLE bot_inbox ALTER COLUMN content SET DEFAULT '{}'::jsonb;
-- 2. Drop added columns.
ALTER TABLE bot_inbox DROP COLUMN IF EXISTS action;
ALTER TABLE bot_inbox DROP COLUMN IF EXISTS header;
+27
View File
@@ -0,0 +1,27 @@
-- 0018_inbox_refactor
-- Refactor bot_inbox: split content JSONB into content TEXT + header JSONB, add action column.
-- 1. Add new columns (idempotent).
ALTER TABLE bot_inbox ADD COLUMN IF NOT EXISTS header JSONB NOT NULL DEFAULT '{}'::jsonb;
ALTER TABLE bot_inbox ADD COLUMN IF NOT EXISTS action TEXT NOT NULL DEFAULT 'notify';
-- 2. Migrate data and convert column type.
-- Only needed when content is still JSONB (upgrade path).
-- On a fresh DB (0001_init already defines content as TEXT), this is a no-op.
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'bot_inbox' AND column_name = 'content' AND data_type = 'jsonb'
) THEN
UPDATE bot_inbox
SET header = content - 'text',
action = 'notify'
WHERE content IS NOT NULL AND content::text <> '{}';
ALTER TABLE bot_inbox ALTER COLUMN content DROP DEFAULT;
ALTER TABLE bot_inbox ALTER COLUMN content TYPE TEXT USING COALESCE(content ->> 'text', '');
ALTER TABLE bot_inbox ALTER COLUMN content SET DEFAULT '';
END IF;
END
$$;
+3 -3
View File
@@ -1,6 +1,6 @@
-- name: CreateInboxItem :one -- name: CreateInboxItem :one
INSERT INTO bot_inbox (bot_id, source, content) INSERT INTO bot_inbox (bot_id, source, header, content, action)
VALUES (sqlc.arg(bot_id), sqlc.arg(source), sqlc.arg(content)) VALUES (sqlc.arg(bot_id), sqlc.arg(source), sqlc.arg(header), sqlc.arg(content), sqlc.arg(action))
RETURNING *; RETURNING *;
-- name: GetInboxItemByID :one -- name: GetInboxItemByID :one
@@ -35,7 +35,7 @@ WHERE bot_id = sqlc.arg(bot_id)
-- name: SearchInboxItems :many -- name: SearchInboxItems :many
SELECT * FROM bot_inbox SELECT * FROM bot_inbox
WHERE bot_id = sqlc.arg(bot_id) WHERE bot_id = sqlc.arg(bot_id)
AND content::text ILIKE '%' || sqlc.arg(query) || '%' AND content ILIKE '%' || sqlc.arg(query) || '%'
AND (sqlc.narg(start_time)::timestamptz IS NULL OR created_at >= sqlc.narg(start_time)::timestamptz) AND (sqlc.narg(start_time)::timestamptz IS NULL OR created_at >= sqlc.narg(start_time)::timestamptz)
AND (sqlc.narg(end_time)::timestamptz IS NULL OR created_at <= sqlc.narg(end_time)::timestamptz) AND (sqlc.narg(end_time)::timestamptz IS NULL OR created_at <= sqlc.narg(end_time)::timestamptz)
AND (sqlc.narg(include_read)::boolean IS NULL OR sqlc.narg(include_read)::boolean = TRUE OR is_read = FALSE) AND (sqlc.narg(include_read)::boolean IS NULL OR sqlc.narg(include_read)::boolean = TRUE OR is_read = FALSE)
+39 -12
View File
@@ -211,7 +211,16 @@ func (p *ChannelInboundProcessor) HandleInbound(ctx context.Context, cfg channel
if activeChatID == "" { if activeChatID == "" {
activeChatID = strings.TrimSpace(resolved.ChatID) activeChatID = strings.TrimSpace(resolved.ChatID)
} }
if !shouldTriggerAssistantResponse(msg) && !identity.ForceReply { // Determine inbox action: trigger (immediate response) or notify (passive).
inboxAction := inbox.ActionNotify
if shouldTriggerAssistantResponse(msg) || identity.ForceReply {
inboxAction = inbox.ActionTrigger
}
// All messages go through inbox first.
inboxItem := p.createInboxItem(ctx, identity, msg, text, attachments, resolved.RouteID, inboxAction)
if inboxAction != inbox.ActionTrigger {
if p.logger != nil { if p.logger != nil {
p.logger.Info( p.logger.Info(
"inbound not triggering assistant (group trigger condition not met)", "inbound not triggering assistant (group trigger condition not met)",
@@ -228,9 +237,14 @@ func (p *ChannelInboundProcessor) HandleInbound(ctx context.Context, cfg channel
if !strings.EqualFold(identity.BotType, "personal") { if !strings.EqualFold(identity.BotType, "personal") {
p.persistInboundUser(ctx, resolved.RouteID, identity, msg, text, attachments, "passive_sync") p.persistInboundUser(ctx, resolved.RouteID, identity, msg, text, attachments, "passive_sync")
} }
p.createInboxItem(ctx, identity, msg, text, attachments, resolved.RouteID)
return nil return nil
} }
// Mark the trigger inbox item as read immediately.
if inboxItem.ID != "" {
p.markInboxItemRead(ctx, inboxItem)
}
userMessagePersisted := p.persistInboundUser(ctx, resolved.RouteID, identity, msg, text, attachments, "active_chat") userMessagePersisted := p.persistInboundUser(ctx, resolved.RouteID, identity, msg, text, attachments, "active_chat")
// Issue chat token for reply routing. // Issue chat token for reply routing.
@@ -717,17 +731,18 @@ func (p *ChannelInboundProcessor) createInboxItem(
text string, text string,
attachments []conversation.ChatAttachment, attachments []conversation.ChatAttachment,
routeID string, routeID string,
) { action string,
) inbox.Item {
if p.inboxService == nil { if p.inboxService == nil {
return return inbox.Item{}
} }
botID := strings.TrimSpace(ident.BotID) botID := strings.TrimSpace(ident.BotID)
if botID == "" { if botID == "" {
return return inbox.Item{}
} }
trimmedText := strings.TrimSpace(text) trimmedText := strings.TrimSpace(text)
if trimmedText == "" && len(attachments) == 0 { if trimmedText == "" && len(attachments) == 0 {
return return inbox.Item{}
} }
displayName := strings.TrimSpace(ident.DisplayName) displayName := strings.TrimSpace(ident.DisplayName)
if displayName == "" { if displayName == "" {
@@ -750,17 +765,29 @@ func (p *ChannelInboundProcessor) createInboxItem(
strings.TrimSpace(msg.Conversation.Name), strings.TrimSpace(msg.Conversation.Name),
attachmentPaths, attachmentPaths,
) )
content := meta.ToMap() header := meta.ToMap()
content["text"] = trimmedText header["route_id"] = strings.TrimSpace(routeID)
content["route_id"] = strings.TrimSpace(routeID)
if _, err := p.inboxService.Create(ctx, inbox.CreateRequest{ item, err := p.inboxService.Create(ctx, inbox.CreateRequest{
BotID: botID, BotID: botID,
Source: msg.Channel.String(), Source: msg.Channel.String(),
Content: content, Header: header,
}); err != nil && p.logger != nil { Content: trimmedText,
Action: action,
})
if err != nil && p.logger != nil {
p.logger.Warn("create inbox item failed", slog.Any("error", err), slog.String("bot_id", botID)) p.logger.Warn("create inbox item failed", slog.Any("error", err), slog.String("bot_id", botID))
} }
return item
}
func (p *ChannelInboundProcessor) markInboxItemRead(ctx context.Context, item inbox.Item) {
if p.inboxService == nil || item.ID == "" {
return
}
if err := p.inboxService.MarkRead(ctx, item.BotID, []string{item.ID}); err != nil && p.logger != nil {
p.logger.Warn("mark inbox item read failed", slog.Any("error", err), slog.String("item_id", item.ID))
}
} }
func buildChannelMessage(output conversation.AssistantOutput, capabilities channel.ChannelCapabilities) channel.Message { func buildChannelMessage(output conversation.AssistantOutput, capabilities channel.ChannelCapabilities) channel.Message {
+3 -1
View File
@@ -175,7 +175,8 @@ type gatewaySkill struct {
type gatewayInboxItem struct { type gatewayInboxItem struct {
ID string `json:"id"` ID string `json:"id"`
Source string `json:"source"` Source string `json:"source"`
Content map[string]any `json:"content"` Header map[string]any `json:"header"`
Content string `json:"content"`
CreatedAt string `json:"createdAt"` CreatedAt string `json:"createdAt"`
} }
@@ -408,6 +409,7 @@ func (r *Resolver) resolve(ctx context.Context, req conversation.ChatRequest) (r
inboxGatewayItems = append(inboxGatewayItems, gatewayInboxItem{ inboxGatewayItems = append(inboxGatewayItems, gatewayInboxItem{
ID: item.ID, ID: item.ID,
Source: item.Source, Source: item.Source,
Header: item.Header,
Content: item.Content, Content: item.Content,
CreatedAt: item.CreatedAt.Format(time.RFC3339), CreatedAt: item.CreatedAt.Format(time.RFC3339),
}) })
+28 -10
View File
@@ -37,25 +37,35 @@ func (q *Queries) CountUnreadInboxItems(ctx context.Context, botID pgtype.UUID)
} }
const createInboxItem = `-- name: CreateInboxItem :one const createInboxItem = `-- name: CreateInboxItem :one
INSERT INTO bot_inbox (bot_id, source, content) INSERT INTO bot_inbox (bot_id, source, header, content, action)
VALUES ($1, $2, $3) VALUES ($1, $2, $3, $4, $5)
RETURNING id, bot_id, source, content, is_read, created_at, read_at RETURNING id, bot_id, source, header, content, action, is_read, created_at, read_at
` `
type CreateInboxItemParams struct { type CreateInboxItemParams struct {
BotID pgtype.UUID `json:"bot_id"` BotID pgtype.UUID `json:"bot_id"`
Source string `json:"source"` Source string `json:"source"`
Content []byte `json:"content"` Header []byte `json:"header"`
Content string `json:"content"`
Action string `json:"action"`
} }
func (q *Queries) CreateInboxItem(ctx context.Context, arg CreateInboxItemParams) (BotInbox, error) { func (q *Queries) CreateInboxItem(ctx context.Context, arg CreateInboxItemParams) (BotInbox, error) {
row := q.db.QueryRow(ctx, createInboxItem, arg.BotID, arg.Source, arg.Content) row := q.db.QueryRow(ctx, createInboxItem,
arg.BotID,
arg.Source,
arg.Header,
arg.Content,
arg.Action,
)
var i BotInbox var i BotInbox
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.BotID, &i.BotID,
&i.Source, &i.Source,
&i.Header,
&i.Content, &i.Content,
&i.Action,
&i.IsRead, &i.IsRead,
&i.CreatedAt, &i.CreatedAt,
&i.ReadAt, &i.ReadAt,
@@ -90,7 +100,7 @@ func (q *Queries) DeleteInboxItemsByBot(ctx context.Context, botID pgtype.UUID)
} }
const getInboxItemByID = `-- name: GetInboxItemByID :one const getInboxItemByID = `-- name: GetInboxItemByID :one
SELECT id, bot_id, source, content, is_read, created_at, read_at FROM bot_inbox SELECT id, bot_id, source, header, content, action, is_read, created_at, read_at FROM bot_inbox
WHERE id = $1 WHERE id = $1
AND bot_id = $2 AND bot_id = $2
` `
@@ -107,7 +117,9 @@ func (q *Queries) GetInboxItemByID(ctx context.Context, arg GetInboxItemByIDPara
&i.ID, &i.ID,
&i.BotID, &i.BotID,
&i.Source, &i.Source,
&i.Header,
&i.Content, &i.Content,
&i.Action,
&i.IsRead, &i.IsRead,
&i.CreatedAt, &i.CreatedAt,
&i.ReadAt, &i.ReadAt,
@@ -116,7 +128,7 @@ func (q *Queries) GetInboxItemByID(ctx context.Context, arg GetInboxItemByIDPara
} }
const listInboxItems = `-- name: ListInboxItems :many const listInboxItems = `-- name: ListInboxItems :many
SELECT id, bot_id, source, content, is_read, created_at, read_at FROM bot_inbox SELECT id, bot_id, source, header, content, action, is_read, created_at, read_at FROM bot_inbox
WHERE bot_id = $1 WHERE bot_id = $1
AND ($2::boolean IS NULL OR is_read = $2::boolean) AND ($2::boolean IS NULL OR is_read = $2::boolean)
AND ($3::text IS NULL OR source = $3::text) AND ($3::text IS NULL OR source = $3::text)
@@ -152,7 +164,9 @@ func (q *Queries) ListInboxItems(ctx context.Context, arg ListInboxItemsParams)
&i.ID, &i.ID,
&i.BotID, &i.BotID,
&i.Source, &i.Source,
&i.Header,
&i.Content, &i.Content,
&i.Action,
&i.IsRead, &i.IsRead,
&i.CreatedAt, &i.CreatedAt,
&i.ReadAt, &i.ReadAt,
@@ -168,7 +182,7 @@ func (q *Queries) ListInboxItems(ctx context.Context, arg ListInboxItemsParams)
} }
const listUnreadInboxItems = `-- name: ListUnreadInboxItems :many const listUnreadInboxItems = `-- name: ListUnreadInboxItems :many
SELECT id, bot_id, source, content, is_read, created_at, read_at FROM bot_inbox SELECT id, bot_id, source, header, content, action, is_read, created_at, read_at FROM bot_inbox
WHERE bot_id = $1 WHERE bot_id = $1
AND is_read = FALSE AND is_read = FALSE
ORDER BY created_at ASC ORDER BY created_at ASC
@@ -193,7 +207,9 @@ func (q *Queries) ListUnreadInboxItems(ctx context.Context, arg ListUnreadInboxI
&i.ID, &i.ID,
&i.BotID, &i.BotID,
&i.Source, &i.Source,
&i.Header,
&i.Content, &i.Content,
&i.Action,
&i.IsRead, &i.IsRead,
&i.CreatedAt, &i.CreatedAt,
&i.ReadAt, &i.ReadAt,
@@ -228,9 +244,9 @@ func (q *Queries) MarkInboxItemsRead(ctx context.Context, arg MarkInboxItemsRead
} }
const searchInboxItems = `-- name: SearchInboxItems :many const searchInboxItems = `-- name: SearchInboxItems :many
SELECT id, bot_id, source, content, is_read, created_at, read_at FROM bot_inbox SELECT id, bot_id, source, header, content, action, is_read, created_at, read_at FROM bot_inbox
WHERE bot_id = $1 WHERE bot_id = $1
AND content::text ILIKE '%' || $2 || '%' AND content ILIKE '%' || $2 || '%'
AND ($3::timestamptz IS NULL OR created_at >= $3::timestamptz) AND ($3::timestamptz IS NULL OR created_at >= $3::timestamptz)
AND ($4::timestamptz IS NULL OR created_at <= $4::timestamptz) AND ($4::timestamptz IS NULL OR created_at <= $4::timestamptz)
AND ($5::boolean IS NULL OR $5::boolean = TRUE OR is_read = FALSE) AND ($5::boolean IS NULL OR $5::boolean = TRUE OR is_read = FALSE)
@@ -267,7 +283,9 @@ func (q *Queries) SearchInboxItems(ctx context.Context, arg SearchInboxItemsPara
&i.ID, &i.ID,
&i.BotID, &i.BotID,
&i.Source, &i.Source,
&i.Header,
&i.Content, &i.Content,
&i.Action,
&i.IsRead, &i.IsRead,
&i.CreatedAt, &i.CreatedAt,
&i.ReadAt, &i.ReadAt,
+3 -1
View File
@@ -105,7 +105,9 @@ type BotInbox struct {
ID pgtype.UUID `json:"id"` ID pgtype.UUID `json:"id"`
BotID pgtype.UUID `json:"bot_id"` BotID pgtype.UUID `json:"bot_id"`
Source string `json:"source"` Source string `json:"source"`
Content []byte `json:"content"` Header []byte `json:"header"`
Content string `json:"content"`
Action string `json:"action"`
IsRead bool `json:"is_read"` IsRead bool `json:"is_read"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
ReadAt pgtype.Timestamptz `json:"read_at"` ReadAt pgtype.Timestamptz `json:"read_at"`
+1 -1
View File
@@ -142,7 +142,7 @@ func (h *InboxHandler) Create(c echo.Context) error {
return echo.NewHTTPError(http.StatusBadRequest, err.Error()) return echo.NewHTTPError(http.StatusBadRequest, err.Error())
} }
req.BotID = botID req.BotID = botID
if len(req.Content) == 0 { if strings.TrimSpace(req.Content) == "" {
return echo.NewHTTPError(http.StatusBadRequest, "content is required") return echo.NewHTTPError(http.StatusBadRequest, "content is required")
} }
item, err := h.service.Create(c.Request().Context(), req) item, err := h.service.Create(c.Request().Context(), req)
+27 -10
View File
@@ -28,11 +28,18 @@ func NewService(log *slog.Logger, queries *sqlc.Queries) *Service {
} }
} }
const (
ActionTrigger = "trigger"
ActionNotify = "notify"
)
type Item struct { type Item struct {
ID string `json:"id"` ID string `json:"id"`
BotID string `json:"bot_id"` BotID string `json:"bot_id"`
Source string `json:"source"` Source string `json:"source"`
Content map[string]any `json:"content"` Header map[string]any `json:"header"`
Content string `json:"content"`
Action string `json:"action"`
IsRead bool `json:"is_read"` IsRead bool `json:"is_read"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
ReadAt time.Time `json:"read_at,omitempty"` ReadAt time.Time `json:"read_at,omitempty"`
@@ -41,7 +48,9 @@ type Item struct {
type CreateRequest struct { type CreateRequest struct {
BotID string `json:"bot_id"` BotID string `json:"bot_id"`
Source string `json:"source"` Source string `json:"source"`
Content map[string]any `json:"content"` Header map[string]any `json:"header"`
Content string `json:"content"`
Action string `json:"action"`
} }
type ListFilter struct { type ListFilter struct {
@@ -69,15 +78,21 @@ func (s *Service) Create(ctx context.Context, req CreateRequest) (Item, error) {
if err != nil { if err != nil {
return Item{}, err return Item{}, err
} }
content, err := json.Marshal(req.Content) header, err := json.Marshal(req.Header)
if err != nil { if err != nil {
return Item{}, err return Item{}, err
} }
action := req.Action
if action != ActionTrigger && action != ActionNotify {
action = ActionNotify
}
row, err := s.queries.CreateInboxItem(ctx, sqlc.CreateInboxItemParams{ row, err := s.queries.CreateInboxItem(ctx, sqlc.CreateInboxItemParams{
BotID: botUUID, BotID: botUUID,
Source: req.Source, Source: req.Source,
Content: content, Header: header,
Content: req.Content,
Action: action,
}) })
if err != nil { if err != nil {
return Item{}, err return Item{}, err
@@ -236,18 +251,20 @@ func (s *Service) Delete(ctx context.Context, botID, itemID string) error {
// --- conversion helpers --- // --- conversion helpers ---
func rowToItem(row sqlc.BotInbox) Item { func rowToItem(row sqlc.BotInbox) Item {
var content map[string]any var header map[string]any
if len(row.Content) > 0 { if len(row.Header) > 0 {
_ = json.Unmarshal(row.Content, &content) _ = json.Unmarshal(row.Header, &header)
} }
if content == nil { if header == nil {
content = map[string]any{} header = map[string]any{}
} }
return Item{ return Item{
ID: pgUUIDToString(row.ID), ID: pgUUIDToString(row.ID),
BotID: pgUUIDToString(row.BotID), BotID: pgUUIDToString(row.BotID),
Source: row.Source, Source: row.Source,
Content: content, Header: header,
Content: row.Content,
Action: row.Action,
IsRead: row.IsRead, IsRead: row.IsRead,
CreatedAt: db.TimeFromPg(row.CreatedAt), CreatedAt: db.TimeFromPg(row.CreatedAt),
ReadAt: db.TimeFromPg(row.ReadAt), ReadAt: db.TimeFromPg(row.ReadAt),
+1
View File
@@ -137,6 +137,7 @@ func (e *Executor) CallTool(ctx context.Context, session mcpgw.ToolSessionContex
entry := map[string]any{ entry := map[string]any{
"id": item.ID, "id": item.ID,
"source": item.Source, "source": item.Source,
"header": item.Header,
"content": item.Content, "content": item.Content,
"is_read": item.IsRead, "is_read": item.IsRead,
"created_at": item.CreatedAt.Format(time.RFC3339), "created_at": item.CreatedAt.Format(time.RFC3339),
+8 -1
View File
@@ -29,13 +29,20 @@ ${skill.content}
const formatInbox = (items: InboxItem[]): string => { const formatInbox = (items: InboxItem[]): string => {
if (!items || items.length === 0) return '' if (!items || items.length === 0) return ''
const formatted = items.map((item) => ({
id: item.id,
source: item.source,
header: item.header,
content: item.content,
createdAt: item.createdAt,
}))
return ` return `
## Inbox (${items.length} unread) ## Inbox (${items.length} unread)
These are messages from other channels NOT from the current conversation. Use ${quote('send')} or ${quote('react')} if you want to respond to any of them. These are messages from other channels NOT from the current conversation. Use ${quote('send')} or ${quote('react')} if you want to respond to any of them.
<inbox> <inbox>
${JSON.stringify(items)} ${JSON.stringify(formatted)}
</inbox> </inbox>
Use ${quote('search_inbox')} to find older messages by keyword. Use ${quote('search_inbox')} to find older messages by keyword.
+2 -1
View File
@@ -33,7 +33,8 @@ export const allActions = Object.values(AgentAction)
export interface InboxItem { export interface InboxItem {
id: string id: string
source: string source: string
content: Record<string, unknown> header: Record<string, unknown>
content: string
createdAt: string createdAt: string
} }
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+43 -4
View File
@@ -499,6 +499,12 @@ export type HandlersPingResponse = {
status?: string; status?: string;
}; };
export type HandlersRefreshResponse = {
access_token?: string;
expires_at?: string;
token_type?: string;
};
export type HandlersSkillItem = { export type HandlersSkillItem = {
content?: string; content?: string;
description?: string; description?: string;
@@ -623,19 +629,23 @@ export type InboxCountResult = {
}; };
export type InboxCreateRequest = { export type InboxCreateRequest = {
action?: string;
bot_id?: string; bot_id?: string;
content?: { content?: string;
header?: {
[key: string]: unknown; [key: string]: unknown;
}; };
source?: string; source?: string;
}; };
export type InboxItem = { export type InboxItem = {
action?: string;
bot_id?: string; bot_id?: string;
content?: { content?: string;
created_at?: string;
header?: {
[key: string]: unknown; [key: string]: unknown;
}; };
created_at?: string;
id?: string; id?: string;
is_read?: boolean; is_read?: boolean;
read_at?: string; read_at?: string;
@@ -975,7 +985,7 @@ export type SearchprovidersProviderMeta = {
provider?: string; provider?: string;
}; };
export type SearchprovidersProviderName = 'brave' | 'bing' | 'google'; export type SearchprovidersProviderName = 'brave' | 'bing' | 'google' | 'tavily';
export type SearchprovidersUpdateRequest = { export type SearchprovidersUpdateRequest = {
config?: { config?: {
@@ -1130,6 +1140,35 @@ export type PostAuthLoginResponses = {
export type PostAuthLoginResponse = PostAuthLoginResponses[keyof PostAuthLoginResponses]; export type PostAuthLoginResponse = PostAuthLoginResponses[keyof PostAuthLoginResponses];
export type PostAuthRefreshData = {
body?: never;
path?: never;
query?: never;
url: '/auth/refresh';
};
export type PostAuthRefreshErrors = {
/**
* Unauthorized
*/
401: HandlersErrorResponse;
/**
* Internal Server Error
*/
500: HandlersErrorResponse;
};
export type PostAuthRefreshError = PostAuthRefreshErrors[keyof PostAuthRefreshErrors];
export type PostAuthRefreshResponses = {
/**
* OK
*/
200: HandlersRefreshResponse;
};
export type PostAuthRefreshResponse = PostAuthRefreshResponses[keyof PostAuthRefreshResponses];
export type GetBotsData = { export type GetBotsData = {
body?: never; body?: never;
path?: never; path?: never;
+66 -4
View File
@@ -61,6 +61,40 @@ const docTemplate = `{
} }
} }
}, },
"/auth/refresh": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "Issue a new JWT using the existing claims with updated expiration",
"tags": [
"auth"
],
"summary": "Refresh Token",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handlers.RefreshResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/bots": { "/bots": {
"get": { "get": {
"description": "List bots accessible to current user (admin can specify owner_id)", "description": "List bots accessible to current user (admin can specify owner_id)",
@@ -7213,6 +7247,20 @@ const docTemplate = `{
} }
} }
}, },
"handlers.RefreshResponse": {
"type": "object",
"properties": {
"access_token": {
"type": "string"
},
"expires_at": {
"type": "string"
},
"token_type": {
"type": "string"
}
}
},
"handlers.SkillItem": { "handlers.SkillItem": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -7514,10 +7562,16 @@ const docTemplate = `{
"inbox.CreateRequest": { "inbox.CreateRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
"action": {
"type": "string"
},
"bot_id": { "bot_id": {
"type": "string" "type": "string"
}, },
"content": { "content": {
"type": "string"
},
"header": {
"type": "object", "type": "object",
"additionalProperties": {} "additionalProperties": {}
}, },
@@ -7529,16 +7583,22 @@ const docTemplate = `{
"inbox.Item": { "inbox.Item": {
"type": "object", "type": "object",
"properties": { "properties": {
"action": {
"type": "string"
},
"bot_id": { "bot_id": {
"type": "string" "type": "string"
}, },
"content": { "content": {
"type": "object", "type": "string"
"additionalProperties": {}
}, },
"created_at": { "created_at": {
"type": "string" "type": "string"
}, },
"header": {
"type": "object",
"additionalProperties": {}
},
"id": { "id": {
"type": "string" "type": "string"
}, },
@@ -8381,12 +8441,14 @@ const docTemplate = `{
"enum": [ "enum": [
"brave", "brave",
"bing", "bing",
"google" "google",
"tavily"
], ],
"x-enum-varnames": [ "x-enum-varnames": [
"ProviderBrave", "ProviderBrave",
"ProviderBing", "ProviderBing",
"ProviderGoogle" "ProviderGoogle",
"ProviderTavily"
] ]
}, },
"searchproviders.UpdateRequest": { "searchproviders.UpdateRequest": {
+66 -4
View File
@@ -52,6 +52,40 @@
} }
} }
}, },
"/auth/refresh": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "Issue a new JWT using the existing claims with updated expiration",
"tags": [
"auth"
],
"summary": "Refresh Token",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handlers.RefreshResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/bots": { "/bots": {
"get": { "get": {
"description": "List bots accessible to current user (admin can specify owner_id)", "description": "List bots accessible to current user (admin can specify owner_id)",
@@ -7204,6 +7238,20 @@
} }
} }
}, },
"handlers.RefreshResponse": {
"type": "object",
"properties": {
"access_token": {
"type": "string"
},
"expires_at": {
"type": "string"
},
"token_type": {
"type": "string"
}
}
},
"handlers.SkillItem": { "handlers.SkillItem": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -7505,10 +7553,16 @@
"inbox.CreateRequest": { "inbox.CreateRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
"action": {
"type": "string"
},
"bot_id": { "bot_id": {
"type": "string" "type": "string"
}, },
"content": { "content": {
"type": "string"
},
"header": {
"type": "object", "type": "object",
"additionalProperties": {} "additionalProperties": {}
}, },
@@ -7520,16 +7574,22 @@
"inbox.Item": { "inbox.Item": {
"type": "object", "type": "object",
"properties": { "properties": {
"action": {
"type": "string"
},
"bot_id": { "bot_id": {
"type": "string" "type": "string"
}, },
"content": { "content": {
"type": "object", "type": "string"
"additionalProperties": {}
}, },
"created_at": { "created_at": {
"type": "string" "type": "string"
}, },
"header": {
"type": "object",
"additionalProperties": {}
},
"id": { "id": {
"type": "string" "type": "string"
}, },
@@ -8372,12 +8432,14 @@
"enum": [ "enum": [
"brave", "brave",
"bing", "bing",
"google" "google",
"tavily"
], ],
"x-enum-varnames": [ "x-enum-varnames": [
"ProviderBrave", "ProviderBrave",
"ProviderBing", "ProviderBing",
"ProviderGoogle" "ProviderGoogle",
"ProviderTavily"
] ]
}, },
"searchproviders.UpdateRequest": { "searchproviders.UpdateRequest": {
+42 -2
View File
@@ -825,6 +825,15 @@ definitions:
status: status:
type: string type: string
type: object type: object
handlers.RefreshResponse:
properties:
access_token:
type: string
expires_at:
type: string
token_type:
type: string
type: object
handlers.SkillItem: handlers.SkillItem:
properties: properties:
content: content:
@@ -1022,9 +1031,13 @@ definitions:
type: object type: object
inbox.CreateRequest: inbox.CreateRequest:
properties: properties:
action:
type: string
bot_id: bot_id:
type: string type: string
content: content:
type: string
header:
additionalProperties: {} additionalProperties: {}
type: object type: object
source: source:
@@ -1032,13 +1045,17 @@ definitions:
type: object type: object
inbox.Item: inbox.Item:
properties: properties:
action:
type: string
bot_id: bot_id:
type: string type: string
content: content:
additionalProperties: {} type: string
type: object
created_at: created_at:
type: string type: string
header:
additionalProperties: {}
type: object
id: id:
type: string type: string
is_read: is_read:
@@ -1599,11 +1616,13 @@ definitions:
- brave - brave
- bing - bing
- google - google
- tavily
type: string type: string
x-enum-varnames: x-enum-varnames:
- ProviderBrave - ProviderBrave
- ProviderBing - ProviderBing
- ProviderGoogle - ProviderGoogle
- ProviderTavily
searchproviders.UpdateRequest: searchproviders.UpdateRequest:
properties: properties:
config: config:
@@ -1824,6 +1843,27 @@ paths:
summary: Login summary: Login
tags: tags:
- auth - auth
/auth/refresh:
post:
description: Issue a new JWT using the existing claims with updated expiration
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handlers.RefreshResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/handlers.ErrorResponse'
security:
- BearerAuth: []
summary: Refresh Token
tags:
- auth
/bots: /bots:
get: get:
description: List bots accessible to current user (admin can specify owner_id) description: List bots accessible to current user (admin can specify owner_id)