This commit is contained in:
Siddharth Ganesan
2026-03-03 13:12:13 -08:00
parent 7fafc00a07
commit 1339915957
3 changed files with 137 additions and 3 deletions

View File

@@ -521,3 +521,52 @@ export function serializeIntegrationSchema(tool: ToolConfig): string {
2
)
}
export function serializeTaskSession(task: {
id: string
title: string
messageCount: number
createdAt: Date
updatedAt: Date
}): string {
return [
`# ${task.title}`,
'',
`- **Chat ID:** ${task.id}`,
`- **Created:** ${task.createdAt.toISOString()}`,
`- **Updated:** ${task.updatedAt.toISOString()}`,
`- **Messages:** ${task.messageCount}`,
'',
].join('\n')
}
export function serializeTaskChat(rawMessages: unknown[]): string {
const filtered: { role: string; content: string }[] = []
for (const msg of rawMessages) {
if (!msg || typeof msg !== 'object') continue
const m = msg as Record<string, unknown>
const role = m.role as string | undefined
if (role !== 'user' && role !== 'assistant') continue
let content = ''
if (role === 'assistant' && Array.isArray(m.contentBlocks)) {
const textParts: string[] = []
for (const block of m.contentBlocks) {
if (block && typeof block === 'object' && (block as any).type === 'text' && (block as any).content) {
textParts.push((block as any).content)
}
}
content = textParts.join('')
}
if (!content && typeof m.content === 'string') {
content = m.content
}
if (!content) continue
filtered.push({ role, content })
}
return JSON.stringify(filtered, null, 2)
}

View File

@@ -4,6 +4,7 @@ import {
account,
apiKey,
chat as chatTable,
copilotChats,
customTools,
document,
environment,
@@ -36,6 +37,8 @@ import {
serializeKBMeta,
serializeRecentExecutions,
serializeTableMeta,
serializeTaskChat,
serializeTaskSession,
serializeWorkflowMeta,
} from '@/lib/copilot/vfs/serializers'
import { listWorkspaceFiles } from '@/lib/uploads/contexts/workspace'
@@ -194,6 +197,8 @@ function getStaticComponentFiles(): Map<string, string> {
* knowledgebases/{name}/documents.json
* tables/{name}/meta.json
* files/{name}/meta.json
* tasks/{title}/session.md
* tasks/{title}/chat.json
* custom-tools/{name}.json
* environment/credentials.json
* environment/api-keys.json
@@ -219,6 +224,7 @@ export class WorkspaceVFS {
this.materializeFiles(workspaceId),
this.materializeEnvironment(workspaceId, userId),
this.materializeCustomTools(workspaceId),
this.materializeTasks(workspaceId, userId),
generateWorkspaceContext(workspaceId, userId).then((content) => {
this.files.set('WORKSPACE.md', content)
}),
@@ -643,6 +649,57 @@ export class WorkspaceVFS {
}
}
/**
* Materialize mothership task chats as browsable conversation files.
*/
private async materializeTasks(workspaceId: string, userId: string): Promise<void> {
try {
const taskRows = await db
.select({
id: copilotChats.id,
title: copilotChats.title,
messages: copilotChats.messages,
createdAt: copilotChats.createdAt,
updatedAt: copilotChats.updatedAt,
})
.from(copilotChats)
.where(
and(
eq(copilotChats.workspaceId, workspaceId),
eq(copilotChats.userId, userId),
eq(copilotChats.type, 'mothership')
)
)
for (const task of taskRows) {
const title = task.title || 'Untitled task'
const safeName = sanitizeName(title)
const prefix = `tasks/${safeName}/`
const messages = Array.isArray(task.messages) ? task.messages : []
this.files.set(
`${prefix}session.md`,
serializeTaskSession({
id: task.id,
title,
messageCount: messages.length,
createdAt: task.createdAt,
updatedAt: task.updatedAt,
})
)
if (messages.length > 0) {
this.files.set(`${prefix}chat.json`, serializeTaskChat(messages))
}
}
} catch (err) {
logger.warn('Failed to materialize tasks', {
workspaceId,
error: err instanceof Error ? err.message : String(err),
})
}
}
/**
* Materialize environment data: credentials, API keys, env variable names.
*/

View File

@@ -1,6 +1,7 @@
import { db } from '@sim/db'
import {
account,
copilotChats,
knowledgeBase,
userTableDefinitions,
userTableRows,
@@ -8,7 +9,7 @@ import {
workspace,
} from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, count, eq, isNull } from 'drizzle-orm'
import { and, count, desc, eq, isNull } from 'drizzle-orm'
import { listWorkspaceFiles } from '@/lib/uploads/contexts/workspace'
import { getUsersWithPermissions } from '@/lib/workspaces/permissions/utils'
@@ -24,7 +25,8 @@ export async function generateWorkspaceContext(
userId: string
): Promise<string> {
try {
const [wsRow, members, workflows, kbs, tables, files, credentials] = await Promise.all([
const [wsRow, members, workflows, kbs, tables, files, credentials, recentTasks] =
await Promise.all([
db
.select({ id: workspace.id, name: workspace.name, ownerId: workspace.ownerId })
.from(workspace)
@@ -72,7 +74,24 @@ export async function generateWorkspaceContext(
})
.from(account)
.where(eq(account.userId, userId)),
])
db
.select({
id: copilotChats.id,
title: copilotChats.title,
updatedAt: copilotChats.updatedAt,
})
.from(copilotChats)
.where(
and(
eq(copilotChats.workspaceId, workspaceId),
eq(copilotChats.userId, userId),
eq(copilotChats.type, 'mothership')
)
)
.orderBy(desc(copilotChats.updatedAt))
.limit(5),
])
const sections: string[] = []
@@ -157,6 +176,15 @@ export async function generateWorkspaceContext(
sections.push('## Credentials\n(none)')
}
// Recent tasks (mothership conversations)
if (recentTasks.length > 0) {
const lines = recentTasks.map((t) => {
const date = t.updatedAt.toISOString().split('T')[0]
return `- **${t.title || 'Untitled'}** (${t.id}) — ${date}`
})
sections.push(`## Recent Tasks (${recentTasks.length})\n${lines.join('\n')}`)
}
return sections.join('\n\n')
} catch (err) {
logger.error('Failed to generate workspace context', {