mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
Task vfs
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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', {
|
||||
|
||||
Reference in New Issue
Block a user