feat: Add persistent chat sessions for iterative phase/task refinement
Introduces a chat loop where users send instructions to an agent that applies changes (create/update/delete phases, tasks, pages) and stays alive for follow-up messages. Includes schema + migration, repository layer, chat prompt, file-io action field extension, output handler chat mode, revert support for deletes, tRPC procedures, events, frontend slide-over UI with inline changeset display and revert, and docs.
This commit is contained in:
@@ -264,7 +264,7 @@ export const agents = sqliteTable('agents', {
|
||||
})
|
||||
.notNull()
|
||||
.default('idle'),
|
||||
mode: text('mode', { enum: ['execute', 'discuss', 'plan', 'detail', 'refine'] })
|
||||
mode: text('mode', { enum: ['execute', 'discuss', 'plan', 'detail', 'refine', 'chat'] })
|
||||
.notNull()
|
||||
.default('execute'),
|
||||
pid: integer('pid'),
|
||||
@@ -308,7 +308,7 @@ export const changeSets = sqliteTable('change_sets', {
|
||||
initiativeId: text('initiative_id')
|
||||
.notNull()
|
||||
.references(() => initiatives.id, { onDelete: 'cascade' }),
|
||||
mode: text('mode', { enum: ['plan', 'detail', 'refine'] }).notNull(),
|
||||
mode: text('mode', { enum: ['plan', 'detail', 'refine', 'chat'] }).notNull(),
|
||||
summary: text('summary'),
|
||||
status: text('status', { enum: ['applied', 'reverted'] })
|
||||
.notNull()
|
||||
@@ -536,3 +536,71 @@ export const conversations = sqliteTable('conversations', {
|
||||
|
||||
export type Conversation = InferSelectModel<typeof conversations>;
|
||||
export type NewConversation = InferInsertModel<typeof conversations>;
|
||||
|
||||
// ============================================================================
|
||||
// CHAT SESSIONS
|
||||
// ============================================================================
|
||||
|
||||
export const chatSessions = sqliteTable('chat_sessions', {
|
||||
id: text('id').primaryKey(),
|
||||
targetType: text('target_type', { enum: ['phase', 'task'] }).notNull(),
|
||||
targetId: text('target_id').notNull(),
|
||||
initiativeId: text('initiative_id')
|
||||
.notNull()
|
||||
.references(() => initiatives.id, { onDelete: 'cascade' }),
|
||||
agentId: text('agent_id').references(() => agents.id, { onDelete: 'set null' }),
|
||||
status: text('status', { enum: ['active', 'closed'] })
|
||||
.notNull()
|
||||
.default('active'),
|
||||
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
||||
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
||||
}, (table) => [
|
||||
index('chat_sessions_target_idx').on(table.targetType, table.targetId),
|
||||
index('chat_sessions_initiative_id_idx').on(table.initiativeId),
|
||||
]);
|
||||
|
||||
export const chatSessionsRelations = relations(chatSessions, ({ one, many }) => ({
|
||||
initiative: one(initiatives, {
|
||||
fields: [chatSessions.initiativeId],
|
||||
references: [initiatives.id],
|
||||
}),
|
||||
agent: one(agents, {
|
||||
fields: [chatSessions.agentId],
|
||||
references: [agents.id],
|
||||
}),
|
||||
messages: many(chatMessages),
|
||||
}));
|
||||
|
||||
export type ChatSession = InferSelectModel<typeof chatSessions>;
|
||||
export type NewChatSession = InferInsertModel<typeof chatSessions>;
|
||||
|
||||
// ============================================================================
|
||||
// CHAT MESSAGES
|
||||
// ============================================================================
|
||||
|
||||
export const chatMessages = sqliteTable('chat_messages', {
|
||||
id: text('id').primaryKey(),
|
||||
chatSessionId: text('chat_session_id')
|
||||
.notNull()
|
||||
.references(() => chatSessions.id, { onDelete: 'cascade' }),
|
||||
role: text('role', { enum: ['user', 'assistant', 'system'] }).notNull(),
|
||||
content: text('content').notNull(),
|
||||
changeSetId: text('change_set_id').references(() => changeSets.id, { onDelete: 'set null' }),
|
||||
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
||||
}, (table) => [
|
||||
index('chat_messages_session_id_idx').on(table.chatSessionId),
|
||||
]);
|
||||
|
||||
export const chatMessagesRelations = relations(chatMessages, ({ one }) => ({
|
||||
chatSession: one(chatSessions, {
|
||||
fields: [chatMessages.chatSessionId],
|
||||
references: [chatSessions.id],
|
||||
}),
|
||||
changeSet: one(changeSets, {
|
||||
fields: [chatMessages.changeSetId],
|
||||
references: [changeSets.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export type ChatMessage = InferSelectModel<typeof chatMessages>;
|
||||
export type NewChatMessage = InferInsertModel<typeof chatMessages>;
|
||||
|
||||
Reference in New Issue
Block a user