Skip to content

Commit 1339915

Browse files
committed
Task vfs
1 parent 7fafc00 commit 1339915

3 files changed

Lines changed: 137 additions & 3 deletions

File tree

apps/sim/lib/copilot/vfs/serializers.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,3 +521,52 @@ export function serializeIntegrationSchema(tool: ToolConfig): string {
521521
2
522522
)
523523
}
524+
525+
export function serializeTaskSession(task: {
526+
id: string
527+
title: string
528+
messageCount: number
529+
createdAt: Date
530+
updatedAt: Date
531+
}): string {
532+
return [
533+
`# ${task.title}`,
534+
'',
535+
`- **Chat ID:** ${task.id}`,
536+
`- **Created:** ${task.createdAt.toISOString()}`,
537+
`- **Updated:** ${task.updatedAt.toISOString()}`,
538+
`- **Messages:** ${task.messageCount}`,
539+
'',
540+
].join('\n')
541+
}
542+
543+
export function serializeTaskChat(rawMessages: unknown[]): string {
544+
const filtered: { role: string; content: string }[] = []
545+
546+
for (const msg of rawMessages) {
547+
if (!msg || typeof msg !== 'object') continue
548+
const m = msg as Record<string, unknown>
549+
const role = m.role as string | undefined
550+
if (role !== 'user' && role !== 'assistant') continue
551+
552+
let content = ''
553+
if (role === 'assistant' && Array.isArray(m.contentBlocks)) {
554+
const textParts: string[] = []
555+
for (const block of m.contentBlocks) {
556+
if (block && typeof block === 'object' && (block as any).type === 'text' && (block as any).content) {
557+
textParts.push((block as any).content)
558+
}
559+
}
560+
content = textParts.join('')
561+
}
562+
563+
if (!content && typeof m.content === 'string') {
564+
content = m.content
565+
}
566+
567+
if (!content) continue
568+
filtered.push({ role, content })
569+
}
570+
571+
return JSON.stringify(filtered, null, 2)
572+
}

apps/sim/lib/copilot/vfs/workspace-vfs.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
account,
55
apiKey,
66
chat as chatTable,
7+
copilotChats,
78
customTools,
89
document,
910
environment,
@@ -36,6 +37,8 @@ import {
3637
serializeKBMeta,
3738
serializeRecentExecutions,
3839
serializeTableMeta,
40+
serializeTaskChat,
41+
serializeTaskSession,
3942
serializeWorkflowMeta,
4043
} from '@/lib/copilot/vfs/serializers'
4144
import { listWorkspaceFiles } from '@/lib/uploads/contexts/workspace'
@@ -194,6 +197,8 @@ function getStaticComponentFiles(): Map<string, string> {
194197
* knowledgebases/{name}/documents.json
195198
* tables/{name}/meta.json
196199
* files/{name}/meta.json
200+
* tasks/{title}/session.md
201+
* tasks/{title}/chat.json
197202
* custom-tools/{name}.json
198203
* environment/credentials.json
199204
* environment/api-keys.json
@@ -219,6 +224,7 @@ export class WorkspaceVFS {
219224
this.materializeFiles(workspaceId),
220225
this.materializeEnvironment(workspaceId, userId),
221226
this.materializeCustomTools(workspaceId),
227+
this.materializeTasks(workspaceId, userId),
222228
generateWorkspaceContext(workspaceId, userId).then((content) => {
223229
this.files.set('WORKSPACE.md', content)
224230
}),
@@ -643,6 +649,57 @@ export class WorkspaceVFS {
643649
}
644650
}
645651

652+
/**
653+
* Materialize mothership task chats as browsable conversation files.
654+
*/
655+
private async materializeTasks(workspaceId: string, userId: string): Promise<void> {
656+
try {
657+
const taskRows = await db
658+
.select({
659+
id: copilotChats.id,
660+
title: copilotChats.title,
661+
messages: copilotChats.messages,
662+
createdAt: copilotChats.createdAt,
663+
updatedAt: copilotChats.updatedAt,
664+
})
665+
.from(copilotChats)
666+
.where(
667+
and(
668+
eq(copilotChats.workspaceId, workspaceId),
669+
eq(copilotChats.userId, userId),
670+
eq(copilotChats.type, 'mothership')
671+
)
672+
)
673+
674+
for (const task of taskRows) {
675+
const title = task.title || 'Untitled task'
676+
const safeName = sanitizeName(title)
677+
const prefix = `tasks/${safeName}/`
678+
const messages = Array.isArray(task.messages) ? task.messages : []
679+
680+
this.files.set(
681+
`${prefix}session.md`,
682+
serializeTaskSession({
683+
id: task.id,
684+
title,
685+
messageCount: messages.length,
686+
createdAt: task.createdAt,
687+
updatedAt: task.updatedAt,
688+
})
689+
)
690+
691+
if (messages.length > 0) {
692+
this.files.set(`${prefix}chat.json`, serializeTaskChat(messages))
693+
}
694+
}
695+
} catch (err) {
696+
logger.warn('Failed to materialize tasks', {
697+
workspaceId,
698+
error: err instanceof Error ? err.message : String(err),
699+
})
700+
}
701+
}
702+
646703
/**
647704
* Materialize environment data: credentials, API keys, env variable names.
648705
*/

apps/sim/lib/copilot/workspace-context.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { db } from '@sim/db'
22
import {
33
account,
4+
copilotChats,
45
knowledgeBase,
56
userTableDefinitions,
67
userTableRows,
78
workflow,
89
workspace,
910
} from '@sim/db/schema'
1011
import { createLogger } from '@sim/logger'
11-
import { and, count, eq, isNull } from 'drizzle-orm'
12+
import { and, count, desc, eq, isNull } from 'drizzle-orm'
1213
import { listWorkspaceFiles } from '@/lib/uploads/contexts/workspace'
1314
import { getUsersWithPermissions } from '@/lib/workspaces/permissions/utils'
1415

@@ -24,7 +25,8 @@ export async function generateWorkspaceContext(
2425
userId: string
2526
): Promise<string> {
2627
try {
27-
const [wsRow, members, workflows, kbs, tables, files, credentials] = await Promise.all([
28+
const [wsRow, members, workflows, kbs, tables, files, credentials, recentTasks] =
29+
await Promise.all([
2830
db
2931
.select({ id: workspace.id, name: workspace.name, ownerId: workspace.ownerId })
3032
.from(workspace)
@@ -72,7 +74,24 @@ export async function generateWorkspaceContext(
7274
})
7375
.from(account)
7476
.where(eq(account.userId, userId)),
75-
])
77+
78+
db
79+
.select({
80+
id: copilotChats.id,
81+
title: copilotChats.title,
82+
updatedAt: copilotChats.updatedAt,
83+
})
84+
.from(copilotChats)
85+
.where(
86+
and(
87+
eq(copilotChats.workspaceId, workspaceId),
88+
eq(copilotChats.userId, userId),
89+
eq(copilotChats.type, 'mothership')
90+
)
91+
)
92+
.orderBy(desc(copilotChats.updatedAt))
93+
.limit(5),
94+
])
7695

7796
const sections: string[] = []
7897

@@ -157,6 +176,15 @@ export async function generateWorkspaceContext(
157176
sections.push('## Credentials\n(none)')
158177
}
159178

179+
// Recent tasks (mothership conversations)
180+
if (recentTasks.length > 0) {
181+
const lines = recentTasks.map((t) => {
182+
const date = t.updatedAt.toISOString().split('T')[0]
183+
return `- **${t.title || 'Untitled'}** (${t.id}) — ${date}`
184+
})
185+
sections.push(`## Recent Tasks (${recentTasks.length})\n${lines.join('\n')}`)
186+
}
187+
160188
return sections.join('\n\n')
161189
} catch (err) {
162190
logger.error('Failed to generate workspace context', {

0 commit comments

Comments
 (0)