Add hybrid research copilot workspace

This commit is contained in:
2026-03-14 19:32:00 -04:00
parent 7a42d73a48
commit 2ee9a549a3
27 changed files with 2864 additions and 323 deletions

View File

@@ -44,10 +44,12 @@ type ResearchMemoSection =
| 'risks'
| 'disconfirming_evidence'
| 'next_actions';
type SearchSource = 'documents' | 'filings' | 'research';
type FinancialCadence = 'annual' | 'quarterly' | 'ltm';
type SearchDocumentScope = 'global' | 'user';
type SearchDocumentSourceKind = 'filing_document' | 'filing_brief' | 'research_note';
type SearchIndexStatus = 'pending' | 'indexed' | 'failed';
type ResearchCopilotMessageRole = 'user' | 'assistant';
type FinancialSurfaceKind =
| 'income_statement'
| 'balance_sheet'
@@ -636,7 +638,7 @@ export const filingLink = sqliteTable('filing_link', {
export const taskRun = sqliteTable('task_run', {
id: text('id').primaryKey().notNull(),
user_id: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }),
task_type: text('task_type').$type<'sync_filings' | 'refresh_prices' | 'analyze_filing' | 'portfolio_insights' | 'index_search'>().notNull(),
task_type: text('task_type').$type<'sync_filings' | 'refresh_prices' | 'analyze_filing' | 'portfolio_insights' | 'index_search' | 'research_brief'>().notNull(),
status: text('status').$type<'queued' | 'running' | 'completed' | 'failed'>().notNull(),
stage: text('stage').notNull(),
stage_detail: text('stage_detail'),
@@ -824,6 +826,38 @@ export const researchMemoEvidence = sqliteTable('research_memo_evidence', {
researchMemoEvidenceUnique: uniqueIndex('research_memo_evidence_unique_uidx').on(table.memo_id, table.artifact_id, table.section)
}));
export const researchCopilotSession = sqliteTable('research_copilot_session', {
id: integer('id').primaryKey({ autoIncrement: true }),
user_id: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }),
ticker: text('ticker').notNull(),
title: text('title'),
selected_sources: text('selected_sources', { mode: 'json' }).$type<SearchSource[]>().notNull(),
pinned_artifact_ids: text('pinned_artifact_ids', { mode: 'json' }).$type<number[]>().notNull(),
created_at: text('created_at').notNull(),
updated_at: text('updated_at').notNull()
}, (table) => ({
researchCopilotSessionTickerUnique: uniqueIndex('research_copilot_session_ticker_uidx').on(table.user_id, table.ticker),
researchCopilotSessionUpdatedIndex: index('research_copilot_session_updated_idx').on(table.user_id, table.updated_at)
}));
export const researchCopilotMessage = sqliteTable('research_copilot_message', {
id: integer('id').primaryKey({ autoIncrement: true }),
session_id: integer('session_id').notNull().references(() => researchCopilotSession.id, { onDelete: 'cascade' }),
user_id: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }),
role: text('role').$type<ResearchCopilotMessageRole>().notNull(),
content_markdown: text('content_markdown').notNull(),
citations: text('citations', { mode: 'json' }).$type<Record<string, unknown>[] | null>(),
follow_ups: text('follow_ups', { mode: 'json' }).$type<string[] | null>(),
suggested_actions: text('suggested_actions', { mode: 'json' }).$type<Record<string, unknown>[] | null>(),
selected_sources: text('selected_sources', { mode: 'json' }).$type<SearchSource[] | null>(),
pinned_artifact_ids: text('pinned_artifact_ids', { mode: 'json' }).$type<number[] | null>(),
memo_section: text('memo_section').$type<ResearchMemoSection | null>(),
created_at: text('created_at').notNull()
}, (table) => ({
researchCopilotMessageSessionIndex: index('research_copilot_message_session_idx').on(table.session_id, table.created_at),
researchCopilotMessageUserIndex: index('research_copilot_message_user_idx').on(table.user_id, table.created_at)
}));
export const authSchema = {
user,
session,
@@ -855,7 +889,9 @@ export const appSchema = {
searchChunk,
researchArtifact,
researchMemo,
researchMemoEvidence
researchMemoEvidence,
researchCopilotSession,
researchCopilotMessage
};
export const schema = {