feat(server): improve context management (#15448)

#### PR Dependency Tree


* **PR #15448** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added workspace artifact upload, browsing, removal, deduplication, and
library ownership support.
* Copilot now supports scoped document and artifact search, canvas
reading, live editor context, and frontend tools.
* Added scope and focus selectors with source-resolution receipts in
chat.
* Added embedding health, progress, synchronization, and retrieval
capabilities.
* Added BYOK policy visibility, provider restrictions, endpoint dialect
selection, and validation.
* Added delegated editor interactions and userdata document
authorization.

* **Bug Fixes**
* Improved attachment handling, cancellation, access control, retrieval
fallbacks, workspace synchronization, and configuration validation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-08-10 09:27:58 +08:00
committed by GitHub
parent 42322d13fe
commit ee899a267b
311 changed files with 20468 additions and 14806 deletions
+74 -131
View File
@@ -191,7 +191,6 @@ model Workspace {
docs WorkspaceDoc[]
blobs Blob[]
ignoredDocs AiWorkspaceIgnoredDocs[]
embedFiles AiWorkspaceFiles[]
byokConfigs AiWorkspaceByokConfig[]
aiUsageEvents AiUsageEvent[]
comments Comment[]
@@ -209,6 +208,7 @@ model Workspace {
docAccessPolicies DocAccessPolicy[]
docGrants DocGrant[]
mcpCredentials McpCredential[]
artifacts WorkspaceArtifact[]
@@index([lastCheckEmbeddings])
@@index([createdAt])
@@ -652,8 +652,6 @@ model Snapshot {
// we need to clear all hanging updates and snapshots before enable the foreign key on workspaceId
// workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
embedding AiWorkspaceEmbedding[]
@@id([workspaceId, id])
@@index([workspaceId, updatedAt])
@@map("snapshots")
@@ -710,6 +708,11 @@ enum AiSessionMessageRole {
system
assistant
user
// the database type keeps the legacy name so the previous release, whose
// Prisma client casts enum values as "AiPromptRole", can keep writing
// ai_sessions_messages while it runs against the same database
@@map("AiPromptRole")
}
model AiSessionMessage {
@@ -721,10 +724,12 @@ model AiSessionMessage {
streamObjects Json? @db.Json
attachments Json? @db.Json
params Json? @db.Json
scopeSnapshot Json? @map("scope_snapshot") @db.JsonB
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
session AiSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
session AiSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
artifacts AiMessageArtifact[]
@@index([sessionId])
@@index([sessionId, compatSubmissionId])
@@ -747,10 +752,10 @@ model AiSession {
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(3)
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(3)
focus Json? @db.JsonB
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
messages AiSessionMessage[]
context AiContext[]
actionRuns AiActionRun[]
//NOTE:
@@ -764,6 +769,50 @@ model AiSession {
@@map("ai_sessions_metadata")
}
model WorkspaceArtifact {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.VarChar
contentHash String @map("content_hash") @db.VarChar
displayName String? @map("display_name") @db.VarChar
fileName String? @map("file_name") @db.VarChar
canonicalMediaType String @map("canonical_media_type") @db.VarChar
sizeBytes BigInt @map("size_bytes")
storageScope String @map("storage_scope") @db.VarChar
storageKey String @map("storage_key") @db.Text
status String @db.VarChar
libraryOwned Boolean @default(false) @map("library_owned")
reservationExpiresAt DateTime? @map("reservation_expires_at") @db.Timestamptz(3)
readyAt DateTime? @map("ready_at") @db.Timestamptz(3)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
messages AiMessageArtifact[]
@@unique([workspaceId, contentHash])
@@unique([workspaceId, id])
@@index([workspaceId, status])
@@index([status, reservationExpiresAt])
@@map("workspace_artifacts")
}
model AiMessageArtifact {
messageId String @map("message_id") @db.VarChar
workspaceId String @map("workspace_id") @db.VarChar
artifactId String @map("artifact_id") @db.Uuid
role String @db.VarChar
displayName String? @map("display_name") @db.VarChar
metadata Json? @db.JsonB
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
message AiSessionMessage @relation(fields: [messageId], references: [id], onDelete: Cascade)
artifact WorkspaceArtifact @relation(fields: [workspaceId, artifactId], references: [workspaceId, id], onDelete: Cascade)
@@id([messageId, artifactId, role])
@@index([workspaceId, artifactId])
@@map("ai_message_artifacts")
}
model AiActionRun {
id String @id @default(uuid()) @db.VarChar
userId String @map("user_id") @db.VarChar
@@ -821,59 +870,6 @@ model AiTranscriptTask {
@@map("ai_transcript_tasks")
}
model AiContext {
id String @id @default(uuid()) @db.VarChar
sessionId String @map("session_id") @db.VarChar
config Json @db.Json
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
embeddings AiContextEmbedding[]
session AiSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
@@map("ai_contexts")
}
model AiContextEmbedding {
id String @id @default(uuid()) @db.VarChar
contextId String @map("context_id") @db.VarChar
fileId String @map("file_id") @db.VarChar
// a file can be divided into multiple chunks and embedded separately.
chunk Int @db.Integer
content String @db.VarChar
embedding Unsupported("vector(1024)")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
context AiContext @relation(fields: [contextId], references: [id], onDelete: Cascade)
@@unique([contextId, fileId, chunk])
@@index([embedding], map: "ai_context_embeddings_idx")
@@map("ai_context_embeddings")
}
model AiWorkspaceEmbedding {
workspaceId String @map("workspace_id") @db.VarChar
docId String @map("doc_id") @db.VarChar
// a doc can be divided into multiple chunks and embedded separately.
chunk Int @db.Integer
content String @db.VarChar
embedding Unsupported("vector(1024)")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
// workspace level search not available for non-cloud workspaces
// so we can match this record with the snapshot one by one
snapshot Snapshot @relation(fields: [workspaceId, docId], references: [workspaceId, id], onDelete: Cascade)
@@id([workspaceId, docId, chunk])
@@index([embedding], map: "ai_workspace_embeddings_idx")
@@map("ai_workspace_embeddings")
}
model AiWorkspaceIgnoredDocs {
workspaceId String @map("workspace_id") @db.VarChar
docId String @map("doc_id") @db.VarChar
@@ -886,78 +882,26 @@ model AiWorkspaceIgnoredDocs {
@@map("ai_workspace_ignored_docs")
}
model AiWorkspaceFiles {
workspaceId String @map("workspace_id") @db.VarChar
fileId String @map("file_id") @db.VarChar
blobId String @default("") @map("blob_id") @db.VarChar
fileName String @map("file_name") @db.VarChar
mimeType String @map("mime_type") @db.VarChar
size Int @db.Integer
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
embeddings AiWorkspaceFileEmbedding[]
@@id([workspaceId, fileId])
@@map("ai_workspace_files")
}
model AiWorkspaceFileEmbedding {
workspaceId String @map("workspace_id") @db.VarChar
fileId String @map("file_id") @db.VarChar
// a file can be divided into multiple chunks and embedded separately.
chunk Int @db.Integer
content String @db.VarChar
embedding Unsupported("vector(1024)")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
file AiWorkspaceFiles @relation(fields: [workspaceId, fileId], references: [workspaceId, fileId], onDelete: Cascade)
@@id([workspaceId, fileId, chunk])
@@index([embedding], map: "ai_workspace_file_embeddings_idx")
@@map("ai_workspace_file_embeddings")
}
model AiWorkspaceBlobEmbedding {
workspaceId String @map("workspace_id") @db.VarChar
blobId String @map("blob_id") @db.VarChar
// a file can be divided into multiple chunks and embedded separately.
chunk Int @db.Integer
content String @db.VarChar
embedding Unsupported("vector(1024)")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
blob Blob @relation(fields: [workspaceId, blobId], references: [workspaceId, key], onDelete: Cascade)
@@id([workspaceId, blobId, chunk])
@@index([embedding], map: "ai_workspace_blob_embeddings_idx")
@@map("ai_workspace_blob_embeddings")
}
model AiWorkspaceByokConfig {
id String @id @default(uuid()) @db.VarChar
workspaceId String @map("workspace_id") @db.VarChar
provider String @db.VarChar
name String @db.VarChar
description String? @db.VarChar
encryptedApiKey String @map("encrypted_api_key") @db.Text
definition Json @db.JsonB
revision Int @default(1)
credentialGeneration Int @default(1) @map("credential_generation")
validation Json? @db.JsonB
sortOrder Int @default(0) @map("sort_order")
enabled Boolean @default(true)
lastUsedAt DateTime? @map("last_used_at") @db.Timestamptz(3)
lastErrorAt DateTime? @map("last_error_at") @db.Timestamptz(3)
lastError String? @map("last_error") @db.Text
createdBy String? @map("created_by") @db.VarChar
updatedBy String? @map("updated_by") @db.VarChar
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
id String @id @default(uuid()) @db.VarChar
workspaceId String @map("workspace_id") @db.VarChar
provider String @db.VarChar
name String @db.VarChar
description String? @db.VarChar
encryptedApiKey String @map("encrypted_api_key") @db.Text
definition Json @db.JsonB
revision Int @default(1)
credentialGeneration Int @default(1) @map("credential_generation")
validation Json? @db.JsonB
sortOrder Int @default(0) @map("sort_order")
enabled Boolean @default(true)
lastUsedAt DateTime? @map("last_used_at") @db.Timestamptz(3)
lastErrorAt DateTime? @map("last_error_at") @db.Timestamptz(3)
lastError String? @map("last_error") @db.Text
createdBy String? @map("created_by") @db.VarChar
updatedBy String? @map("updated_by") @db.VarChar
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
@@ -1209,8 +1153,7 @@ model Blob {
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(3)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
AiWorkspaceBlobEmbedding AiWorkspaceBlobEmbedding[]
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
@@id([workspaceId, key])
@@index([workspaceId, status, deletedAt])